by shihang.mai
zuul过滤器+ribbon自定义路由规则+aop
1. 代码
表结构
- id
- serverId
- userId
- meta-version
例子以不同的用户,访问不同的服务举例
- 在eureka-client先设置meta-map的version
新服务A
eureka:
instance:
metadataMap:
version: v2
旧服务A
eureka:
instance:
metadataMap:
version: v1
- 在zuul中自定义一个过滤器
@Component
public class GrayFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
int userId = Integer.parseInt(request.getHeader("userId"));
// 这里不写具体的调用,根据userId去db获取规则,可能获取不到,即存储中并没对应数据
String versioned="v2";
if(null!=versioned){
//请求访问到新服务上
RibbonFilterContextHolder.getCurrentContext().add("version",versioned);
}
//代码还需完善..
return null;
}
}
引入jar
<dependency>
<groupId>io.jmnarloch</groupId>
<artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
<version>2.1.0</version>
</dependency>
到此,可以完成网关到服务则灰度.还需要做服务之间的灰度.
- 在服务之间我们通过自定义ribbon规则实现,在服务中加入路由Rule
@Component
public class GrayRule extends AbstractLoadBalancerRule {
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
return null;
}
public Server choose(ILoadBalancer lb, Object o) {
//获取所有的可达的服务
List<Server> reachableServers = lb.getReachableServers();
//获取当前线程的userId-----见第4步
Map<String,String> map = RibbonParam.get();
String userId = map.get("userId");
//根据用户id,到db查找version,不写查找db代码
String version = "v2";
Server returnServer = null;
//根据version路由
for (int i = 0; i < reachableServers.size(); i++) {
Server server = reachableServers.get(i);
//这里找到这个类,是因为自己慢慢试的
DiscoveryEnabledServer des=(DiscoveryEnabledServer)server;
Map<String, String> metadata = des.getInstanceInfo().getMetadata();
String eurukaClientVersion = metadata.get("version");
if(eurukaClientVersion.equals(version)){
returnServer = server;
break;
}
}
return null;
}
}
这里用reachableServers获取单一个server时,需要转为DiscoveryEnabledServer才能获取到自定义的meta-map信息
- 请求进来服务,然后经过ribbon路由,它们是一个线程的,故用ThreadLocal存储信息
public class RibbonParam {
private static final ThreadLocal tl = new ThreadLocal();
public static <T> T get(){
return (T)tl.get();
}
public static <T> void set(T t){
tl.set(t);
}
- 那什么时候把信息set进ThreadLocal呢,当然是用aop
@Aspect
@Component
public class RequestAspect {
@Pointcut("execution(* com.shihangmai.eurekaprovider.*.*(..))")
private void allMethod(){
}
@Before(value = "allMethod()")
public void before(JoinPoint joinPoint){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String userId = request.getHeader("userId");
Map<String,String> map = new HashMap<>(2);
map.put("userId",userId);
RibbonParam.set(map);
}
}
- 注册bean
public class GrayRibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new GrayRule();
}
}
这个类并不需要加注解,原因见第7步
- 在springboot启动类上加上注解
@RibbonClient(name="eureka-provider",configuration = GrayRibbonConfiguration.class)
这样做的话,只有请求这个服务的时候利用自定义的路由规则.
到此,网关到服务,服务到服务间的灰度都做完了
- 实际上,3-7步有一个框架已经做了,直接删除便是,就是
<dependency>
<groupId>io.jmnarloch</groupId>
<artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
<version>2.1.0</version>
</dependency>
-
在启动类上的注解RibbonClient也删除
-
当我们引入了这个starter后,只需要在aop中直接加入逻辑即可
@Aspect
@Component
public class RequestAspect {
@Pointcut("execution(* com.shihangmai.eurekaprovider.*.*(..))")
private void allMethod(){
}
@Before(value = "allMethod()")
public void before(JoinPoint joinPoint){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String userId = request.getHeader("userId");
//根据userId到db中获取version
String dbVersion = "v2";
if(null!= dbVersion){
RibbonFilterContextHolder.getCurrentContext().add("version", dbVersion);
}
}
}
2. 解析

- 新服务A、旧服务A,新服务B、旧服务B均需要注册meta-map:version信息
- 当请求过来,我们在zuul中自定义一个过滤器,从HttpServlet中获取到token并解析出userId,并将userId到数据库中查出路由的版本。当然这个数据库可以换为redis。
- 这样,我们网关就可以根据userId找到库中的路由版本,然后因为服务都注册了meta-map:version,路由到对应的服务器上
以上是zuul到服务,灰度
- 首先,请求经过ribbon再从服务出去,都是同一个线程。在zuul调用服务后,在服务侧用aop在调用方法前进行加强,从HttpServletRequest获取userId并放入ThreadLocal
- 在服务侧自定义一个路由规则并注入到spring上下文,在路由规则中,获取ThreadLocal中的userId,然后也是到db找出对应的version,再循环遍历所有可达的服务列表的meta-data,找到后返回Server即可
- 在服务启动类中指定请求类和对应的自定义路由规则即可。
以上是服务l到服务,灰度
4-6可直接用框架代替为
- 首先,请求经过ribbon再从服务出去,都是同一个线程。在zuul调用服务后,在服务侧用aop在调用方法前进行加强,将从HttpServletRequest获取userId,然后userId到库中查找对应的version,直接add入RibbonFilterContextHolder.getCurrentContext()即可
网友评论