1. 概述
在微服务开发时,比如像JPAAS开发,我开发一个OA的微服务,其他的微服务我可能都部署在服务器上。
OA服务有多个人参与开发,在网关路由访问时,由于OA服务有多个实例,这个时候怎么保证一定路由到当前开发者的实例上来,不然不方便调试。
另外feign调用问题:
比如有BPM和FORM的服务,比如我需要在启动流程时,调用到本地的表单服务,这样方便调试。
2. 解决办法
2.1 方案1
开发时,直接将所有的服务跑在同一台机器上。这样的造成的问题,每个人都需要跑完整的服务,这样机器的压力比较大。
2.2 方案2
部署结构如上图,其中nginx 部署到开发者本地,api网关可以部署到公共的服务器上。
这里其实要解决两个问题:
- 如何客户访问的时候,总是访问到你的服务。
- 通过FEIGN调用的时候,总是能够访问到同一台机器的相关服务,比如BPM访问到同一台机器的表单服务。
解决思路通过微服务的元数据解决
- 直接通过网关请求
- 在发起请求时,在请求头中加入一个叫 developer 的请求头。
- 网关负责根据这个请求头找到,元数据包含这个的服务实例,然后请求转到该实例。
- 通过feign请求
- 在调用时将请求头进行转发。
- 通过过滤器将请求头放到上下文线程变量中。
- 通过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。