1. 概述

在微服务开发时,比如像JPAAS开发,我开发一个OA的微服务,其他的微服务我可能都部署在服务器上。

OA服务有多个人参与开发,在网关路由访问时,由于OA服务有多个实例,这个时候怎么保证一定路由到当前开发者的实例上来,不然不方便调试。

另外feign调用问题:

比如有BPM和FORM的服务,比如我需要在启动流程时,调用到本地的表单服务,这样方便调试。

2. 解决办法

2.1 方案1

开发时,直接将所有的服务跑在同一台机器上。这样的造成的问题,每个人都需要跑完整的服务,这样机器的压力比较大。

2.2 方案2

部署结构如上图,其中nginx 部署到开发者本地,api网关可以部署到公共的服务器上。

这里其实要解决两个问题:

  • 如何客户访问的时候,总是访问到你的服务。
  • 通过FEIGN调用的时候,总是能够访问到同一台机器的相关服务,比如BPM访问到同一台机器的表单服务。

解决思路通过微服务的元数据解决

  • 直接通过网关请求
  1. 在发起请求时,在请求头中加入一个叫 developer 的请求头。
  2. 网关负责根据这个请求头找到,元数据包含这个的服务实例,然后请求转到该实例。
  • 通过feign请求
  1. 在调用时将请求头进行转发。
  2. 通过过滤器将请求头放到上下文线程变量中。
  3. 通过CustomIsolationRule进行服务实例选择。

2.2.1 NGINX 部署到开发者的本地

location /api/ {
             proxy_set_header Host       $host;
             proxy_set_header developer        ray;
             proxy_pass http://127.0.0.1:9900/;
        }

在访问网关时,我们统一给所有的请求添加一个请求头 developer 这个开发者配置的不一样。

2.2.2 微服务配置如下

微服务做如下配置:

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          developer: ray

启动微服务,我们可以看到上面的配置生效了。

这个 developer 需要和nginx 配置的保持一致。

这个是我们需要开发或调试的微服务需要加上。

2.2.3 允许根据元数据查询实例

nacos-config-dev.properties 配置参数

redxun.ribbon.isolation.enabled 为true。

就是允许选择实例。

2.2.3 实现代码

2.2.3.1 网关路由代码

网关路由解决的问题是,根据nginx 传递过来的 请求头选择服务实例。

LbIsolationFilter

public class LbIsolationFilter extends LoadBalancerClientFilter {
    public LbIsolationFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
        super(loadBalancer, properties);
    }

    @Override
    protected ServiceInstance choose(ServerWebExchange exchange) {
        String developer = exchange.getRequest().getHeaders().getFirst(CommonConstant.DEVELOPER);
        if (StrUtil.isNotEmpty(developer)) {
            if (this.loadBalancer instanceof RibbonLoadBalancerClient) {
                RibbonLoadBalancerClient client = (RibbonLoadBalancerClient) this.loadBalancer;
                String serviceId = ((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).getHost();
                return client.choose(serviceId, developer);
            }
        }
        return super.choose(exchange);
    }
}

调用选择实例代码:

CustomIsolationRule

public class CustomIsolationRule extends RoundRobinRule {
    private final static String KEY_DEFAULT = "default";
    /**
     * 优先根据版本号取实例
     */
    @Override
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        }
        String developer;
        if (key != null && !KEY_DEFAULT.equals(key)) {
            developer = key.toString();
        } else {
            developer = LbIsolationContextHolder.getDeveloper();
        }

        List<Server> targetList = null;
        List<Server> upList = lb.getReachableServers();
        if (StrUtil.isNotEmpty(developer)) {
            //取指定版本号的实例
            targetList = upList.stream().filter(
                    server -> developer.equals(
                            ((NacosServer) server).getMetadata().get(CommonConstant.DEVELOPER)
                    )
            ).collect(Collectors.toList());
        }

        if (CollUtil.isEmpty(targetList)) {
            //只取无版本号的实例
            targetList = upList.stream().filter(
                    server -> {
                        String metadataVersion = ((NacosServer) server).getMetadata().get(CommonConstant.DEVELOPER);
                        return StrUtil.isEmpty(metadataVersion);
                    }
            ).collect(Collectors.toList());
        }

        if (CollUtil.isNotEmpty(targetList)) {
            return getServer(targetList);
        }
        return super.choose(lb, key);
    }

    /**
     * 随机取一个实例
     */
    private Server getServer(List<Server> upList) {
        int nextInt = RandomUtil.randomInt(upList.size());
        return upList.get(nextInt);
    }
}

2.2.3.2 feign调用相关代码

feign 需要解决的是,根据上下文选择调用那个服务实例。

TokenRelayRequestIntecepor

String developer=request.getHeader(CommonConstant.DEVELOPER);
        // 2. 将token传递
        if (StringUtils.isNotBlank(developer)) {
            requestTemplate.header(CommonConstant.DEVELOPER, developer);
        }

调用feign的时候,自动先设置上下文的请求头。

LbIsolationFilter 此代码在 jpaas-ribbon-spring-boot-starter 项目中。

public class LbIsolationFilter extends OncePerRequestFilter {
    @Value("${" + ConfigConstants.CONFIG_RIBBON_ISOLATION_ENABLED + ":false}")
    private boolean enableIsolation;

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return !enableIsolation;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws IOException, ServletException {
        try {
            String developer = request.getHeader(CommonConstant.DEVELOPER);
            if(StringUtils.isNotEmpty(developer)){
                LbIsolationContextHolder.setDeveloper(developer);
            }
            filterChain.doFilter(request, response);
        } finally {
            LbIsolationContextHolder.clear();
        }
    }
}

此过滤器将 上下文 developer 放置到 线程变量中,然后由上面的代码 CustomIsolationRule 负责选择实例。

3.1 总结

在开发部署时,我们需要做以下几步,就可以解决实例乱窜的问题。

3.1.1. nginx 的修改

nginx 需要部署在开发者本地机器。

location /api/ {
             proxy_set_header Host       $host;
             proxy_set_header developer        ray;
             proxy_pass http://127.0.0.1:9900/;
        }

3.1.2 微服务增加配置

spring:
  cloud:
    nacos:
      discovery:
        metadata:
          developer: ray

3.1.3 配置nacos-config-dev.properties

nacos-config-dev.properties 配置参数

redxun.ribbon.isolation.enabled 为true。

就是允许选择实例。

如果生成部署,我们可以将上面的enable修改成false。

文档更新时间: 2021-07-19 18:40   作者:zyg