by shihang.mai
zuul過濾器+ribbon自定義路由規(guī)則+aop
1. 代碼
表結(jié)構(gòu)
- id
- serverId
- userId
- meta-version
例子以不同的用戶,訪問不同的服務(wù)舉例
- 在eureka-client先設(shè)置meta-map的version
新服務(wù)A
eureka:
instance:
metadataMap:
version: v2
舊服務(wù)A
eureka:
instance:
metadataMap:
version: v1
- 在zuul中自定義一個(gè)過濾器
@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"));
// 這里不寫具體的調(diào)用,根據(jù)userId去db獲取規(guī)則,可能獲取不到,即存儲(chǔ)中并沒對(duì)應(yīng)數(shù)據(jù)
String versioned="v2";
if(null!=versioned){
//請(qǐng)求訪問到新服務(wù)上
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>
到此,可以完成網(wǎng)關(guān)到服務(wù)則灰度.還需要做服務(wù)之間的灰度.
- 在服務(wù)之間我們通過自定義ribbon規(guī)則實(shí)現(xiàn),在服務(wù)中加入路由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) {
//獲取所有的可達(dá)的服務(wù)
List<Server> reachableServers = lb.getReachableServers();
//獲取當(dāng)前線程的userId-----見第4步
Map<String,String> map = RibbonParam.get();
String userId = map.get("userId");
//根據(jù)用戶id,到db查找version,不寫查找db代碼
String version = "v2";
Server returnServer = null;
//根據(jù)version路由
for (int i = 0; i < reachableServers.size(); i++) {
Server server = reachableServers.get(i);
//這里找到這個(gè)類,是因?yàn)樽约郝嚨? 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獲取單一個(gè)server時(shí),需要轉(zhuǎn)為DiscoveryEnabledServer才能獲取到自定義的meta-map信息
- 請(qǐng)求進(jìn)來服務(wù),然后經(jīng)過ribbon路由,它們是一個(gè)線程的,故用ThreadLocal存儲(chǔ)信息
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);
}
- 那什么時(shí)候把信息set進(jìn)ThreadLocal呢,當(dāng)然是用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);
}
}
- 注冊(cè)bean
public class GrayRibbonConfiguration {
@Bean
public IRule ribbonRule(){
return new GrayRule();
}
}
這個(gè)類并不需要加注解,原因見第7步
- 在springboot啟動(dòng)類上加上注解
@RibbonClient(name="eureka-provider",configuration = GrayRibbonConfiguration.class)
這樣做的話,只有請(qǐng)求這個(gè)服務(wù)的時(shí)候利用自定義的路由規(guī)則.
到此,網(wǎng)關(guān)到服務(wù),服務(wù)到服務(wù)間的灰度都做完了
- 實(shí)際上,3-7步有一個(gè)框架已經(jīng)做了,直接刪除便是,就是
<dependency>
<groupId>io.jmnarloch</groupId>
<artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
<version>2.1.0</version>
</dependency>
在啟動(dòng)類上的注解RibbonClient也刪除
當(dāng)我們引入了這個(gè)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");
//根據(jù)userId到db中獲取version
String dbVersion = "v2";
if(null!= dbVersion){
RibbonFilterContextHolder.getCurrentContext().add("version", dbVersion);
}
}
}
2. 解析

灰度發(fā)布簡(jiǎn)圖
- 新服務(wù)A、舊服務(wù)A,新服務(wù)B、舊服務(wù)B均需要注冊(cè)meta-map:version信息
- 當(dāng)請(qǐng)求過來,我們?cè)趜uul中自定義一個(gè)過濾器,從HttpServlet中獲取到token并解析出userId,并將userId到數(shù)據(jù)庫中查出路由的版本。當(dāng)然這個(gè)數(shù)據(jù)庫可以換為redis。
- 這樣,我們網(wǎng)關(guān)就可以根據(jù)userId找到庫中的路由版本,然后因?yàn)榉?wù)都注冊(cè)了meta-map:version,路由到對(duì)應(yīng)的服務(wù)器上
以上是zuul到服務(wù),灰度 - 首先,請(qǐng)求經(jīng)過ribbon再從服務(wù)出去,都是同一個(gè)線程。在zuul調(diào)用服務(wù)后,在服務(wù)側(cè)用aop在調(diào)用方法前進(jìn)行加強(qiáng),從HttpServletRequest獲取userId并放入ThreadLocal
- 在服務(wù)側(cè)自定義一個(gè)路由規(guī)則并注入到spring上下文,在路由規(guī)則中,獲取ThreadLocal中的userId,然后也是到db找出對(duì)應(yīng)的version,再循環(huán)遍歷所有可達(dá)的服務(wù)列表的meta-data,找到后返回Server即可
- 在服務(wù)啟動(dòng)類中指定請(qǐng)求類和對(duì)應(yīng)的自定義路由規(guī)則即可。
以上是服務(wù)l到服務(wù),灰度
4-6可直接用框架代替為
- 首先,請(qǐng)求經(jīng)過ribbon再從服務(wù)出去,都是同一個(gè)線程。在zuul調(diào)用服務(wù)后,在服務(wù)側(cè)用aop在調(diào)用方法前進(jìn)行加強(qiáng),將從HttpServletRequest獲取userId,然后userId到庫中查找對(duì)應(yīng)的version,直接add入RibbonFilterContextHolder.getCurrentContext()即可