项目结构如下
代码如下:
pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>sca.pro</groupId>
<artifactId>sca-parent</artifactId>
<version>1.0.1</version>
</parent>
<groupId>sca.gary.publish</groupId>
<artifactId>gray-spring-boot-starter</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Compile dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-loadbalancer</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
sca.gary.publish.graypublish.config.GrayConfig,\
sca.gary.publish.graypublish.feignInterceptor.FeignRequestInterceptor
GrayConfig:这里我是希望我的ribbon配置全局生效,如果不希望全局生效,只希望对某些服务生效,可以在对应服务上添加如下,可写多个服务
@RibbonClients(value = { @RibbonClient(value = "nacos中的服务名称",configuration = GrayConfig.class) })
package sca.gary.publish.graypublish.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import sca.gary.publish.graypublish.GrayRule;
@Configuration
public class GrayConfig {
@Bean
public GrayRule grayRule(){
return new GrayRule();
}
}
ContantsString:版本号请求头key
package sca.gary.publish.graypublish.contans;
public class ContantsString {
public static final String GRAY_KEY="version";
}
FeignRequestInterceptor:feign拦截器,当远程调用时,将版本号保存到ttl中,供给服务负载使用,并把当前请求头中的版本号放在远程调用的请求头中,防止它仍需要远程调用
package sca.gary.publish.graypublish.feignInterceptor;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sca.gary.publish.graypublish.contans.ContantsString;
import sca.gary.publish.graypublish.ttl.ThreadLocalUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
Map<String, String> headers = getHeaders(request);
for (Map.Entry<String, String> entry : headers.entrySet()) {
//② 设置请求头到新的Request中
template.header(entry.getKey(), entry.getValue());
}
}
/**
* 获取原请求头
*/
private Map<String, String> getHeaders(HttpServletRequest request) {
Map<String, String> map = new LinkedHashMap<>();
Enumeration<String> enumeration = request.getHeaderNames();
if (enumeration != null) {
while (enumeration.hasMoreElements()) {
String key = enumeration.nextElement();
String value = request.getHeader(key);
//将灰度标记的请求头透传给下个服务
if (ContantsString.GRAY_KEY.equals(key)){
//① 保存灰度发布的标记
ThreadLocalUtils.set(ContantsString.GRAY_KEY,value);
map.put(key, value);
}
}
}
return map;
}
}
ThreadLocalUtils:用于存储网关传递过来的版本号,实现当前服务的负载选择
package sca.gary.publish.graypublish.ttl;
import com.alibaba.ttl.TransmittableThreadLocal;
import java.util.HashMap;
import java.util.Map;
public class ThreadLocalUtils {
private static TransmittableThreadLocal<Map<String, Object>> cache = new TransmittableThreadLocal<>();
/**
* 设置对象到本地变量
*
* @param object
*/
public static void set(String key, Object object) {
if (!isCaheIsNull()) {
cache.get().put(key, object);
} else {
Map<String, Object> map = new HashMap<>();
map.put(key, object);
cache.set(map);
}
}
/**
* 从本地变量中获取变量
*
* @return
*/
public static Object get(String key) {
if (!isCaheIsNull()) {
return cache.get().get(key);
} else {
return null;
}
}
/**
* 根据KEY移除缓存里的数据
*
* @param key
*/
public static void remove(String key) {
if (isCaheIsNull()) {
return;
} else {
cache.get().remove(key);
}
}
/**
* 释放本地线程资源
*/
public static void clear() {
cache.remove();
}
/**
* 是否存在本地变量
*
* @return
*/
private static boolean isCaheIsNull() {
return cache.get() == null;
}
}
GrayRule:
最重要的就是这个类,重写了ribbon的负载策略,通过从网关传递过来的版本号,和每个服务中的元数据版本号进行对比,如果相同则调用它们的版本,如果没有找到对应版本的服务,则将获取到的所有服务按照原规则进行负载
package sca.gary.publish.graypublish;
import com.alibaba.cloud.nacos.ribbon.NacosServer;
import com.google.common.base.Optional;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import org.springframework.util.ObjectUtils;
import sca.gary.publish.graypublish.contans.ContantsString;
import sca.gary.publish.graypublish.ttl.ThreadLocalUtils;
import java.util.ArrayList;
import java.util.List;
public class GrayRule extends ZoneAvoidanceRule {
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
try {
//从ThreadLocal中获取灰度标记
Object version = ThreadLocalUtils.get(ContantsString.GRAY_KEY);
//获取所有可用服务
List<Server> serverList = this.getLoadBalancer().getReachableServers();
//灰度发布的服务
List<Server> grayServerList = new ArrayList<>();
if (ObjectUtils.isEmpty(version)){
return originChoose(serverList,key);
}
for(Server server : serverList) {
NacosServer nacosServer = (NacosServer) server;
//从nacos中获取元素剧进行匹配
if(nacosServer.getMetadata().containsKey(ContantsString.GRAY_KEY)
&& nacosServer.getMetadata().get(ContantsString.GRAY_KEY).equals(version.toString()) ){
grayServerList.add(server);
}
}
if (!ObjectUtils.isEmpty(grayServerList)){
return originChoose(grayServerList,key);
}
return originChoose(serverList,key);
} finally {
//清除灰度标记
ThreadLocalUtils.clear();
}
}
private Server originChoose(List<Server> noMetaServerList, Object key) {
Optional<Server> serverOptional = getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList, key);
if (serverOptional.isPresent()) {
return serverOptional.get();
} else {
return null;
}
}
}
使用方式:
1.首先保证每个服务都有feign的依赖
2.添加依赖如下
<dependency> <groupId>sca.gary.publish</groupId> <artifactId>gray-spring-boot-starter</artifactId> <version>1.0.1</version> <exclusions> <exclusion> <groupId>io.github.openfeign</groupId> <artifactId>feign-core</artifactId> </exclusion> </exclusions> </dependency>
3.yaml添加配置
spring:
cloud:
nacos:
discovery:
metadata:
version: 2.0
3.如果是希望全局生效,那么直接在 GrayConfig 类上加注解就行,否则配合
@RibbonClients(value = { @RibbonClient(value = "服务名",configuration = GrayConfig.class) })