日升时奋斗,日落时自省
目录
1、实现框架
2、负载均衡模块
3、封装负载均衡器
4、网关模块
5、服务模块
5.1、注册为灰度服务实例
5.2、设置负载均衡器
5.3、传递灰度标签
1、实现框架
Spring Cloud全链路灰色发布实现构架:
灰度发布的具体实现 :
前端程序在灰度测试用户Header头中打上标签,例如Header中添加一个参数"gray-tay:true",其表示要进行会灰度测试的(访问灰度服务),而其他则为正式服务
在负载均衡器Spring Cloud LoadBalancer中,拿到Header中的"gray-tag"进行判断,如果此标签不为空,并等于"true"的话,表示要访问灰度发布的服务,否则只访问正式的服务
在网关Spring Cloud Gateway中,Header标签"gray-tag:true"继续往下一服务中传传递(因为通过网关的时候,是从新发送请求,Header中是没有灰度标签的)
在后续的调用服务中,需要实现以下两个关键功能:
- 在负载均衡器Spring Cloud LoadBalancer中,判断灰度发布标签,将请求分发到对应服务
- 将灰度发布标签(如果存在),继续传递给下一个调用的服务
经过第四步的反复传递之后,整个Spring Cloud全链路的灰度发布就完成了
2、负载均衡模块
实现ReactorServiceInstanceLoadBalancer接口
public class GrayLoadBalancer implements ReactorServiceInstanceLoadBalancer{private static final Log log = LogFactory.getLog(GrayLoadBalancer.class);private final String serviceId;private AtomicInteger position; // 位置,下标private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;public GrayLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider, String serviceId) {this.serviceId = serviceId;this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;this.position = new AtomicInteger(new Random().nextInt(1000)); //随机进行设置一个值}public Mono<Response<ServiceInstance>> choose(Request request) {// 提供备选的服务实例列表ServiceInstanceListSupplier supplier = (ServiceInstanceListSupplier) this.serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);// 选择服务实例return supplier.get(request).next().map((serviceInstances) -> {return this.processInstanceResponse(supplier, serviceInstances, request);});}private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,List<ServiceInstance> serviceInstances,Request request) {// 从备选的服务列表中选择一个具体的服务实例Response<ServiceInstance> serviceInstanceResponse = this.getInstanceResponse(serviceInstances,request);if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {((SelectedInstanceCallback) supplier).selectedServiceInstance((ServiceInstance) serviceInstanceResponse.getServer());}return serviceInstanceResponse;}private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances,Request request) {// 实例为空 首先进行实例判空if (instances.isEmpty()) {if (log.isWarnEnabled()) {//判空后 给予警告log.warn("No servers available for service: " + this.serviceId);}//返回响应return new EmptyResponse();} else { // 服务不为空// 灰度节点的业务实现// 0.得到 Request 对象[通过方法参数的传递得到此对象]// 1.从 Request 对象的 Header 中得到灰度标签RequestDataContext requestContext = (RequestDataContext) request.getContext();HttpHeaders headers = requestContext.getClientRequest().getHeaders();List<String> headersList = headers.get(GlobalVariable.GRAY_TAGE);if (headersList != null && headersList.size() > 0 &&headersList.get(0).equals("true")) { // 灰度请求// 灰度列表 这里采用lambda的方法进行过滤List<ServiceInstance> grayList = instances.stream().filter(i -> i.getMetadata().get(GlobalVariable.GRAY_TAGE) != null &&i.getMetadata().get(GlobalVariable.GRAY_TAGE).equals("true")).toList();if (grayList.size() > 0) { // 存在灰度服务节点instances = grayList;}} else { // 正式节点// 2.将实例进行分组【正式服务列表|灰度服务列表】instances = instances.stream().filter(i -> i.getMetadata().get(GlobalVariable.GRAY_TAGE) == null ||!i.getMetadata().get(GlobalVariable.GRAY_TAGE).equals("true")).toList();}// 3.使用负载均衡算法选择上一步列表中的某一个节点int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;ServiceInstance instance = (ServiceInstance)instances.get(pos % instances.size());return new DefaultResponse(instance);}}
}
3、封装负载均衡器
负载均衡器修改之后,我们需要使用修改后的负载均衡器
public class GrayLoadBalancerConfig {@Beanpublic ReactorLoadBalancer<ServiceInstance> GrayLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);return new GrayLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);}
}
4、网关模块
网关是能收到灰度发布标识,但是调用的服务是不能获得Header中的灰度标签,需要在网关这里传递给调用的服务
@Component // 实现全局过滤器 进行过滤设置
public class LoadBalancerFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 事件处理 exchange 获取请求头ServerHttpRequest request=exchange.getRequest();// 事件处理 exchange 获取响应头ServerHttpResponse response = exchange.getResponse();// 请求头参数 获取的参数 获取 key 对应的 value值if(request.getQueryParams().getFirst(GlobalVariable.GRAY_TAGE)!=null){//获取响应请求头 设置灰度标签的参数 为 trueresponse.getHeaders().set(GlobalVariable.GRAY_TAGE,"true");}// 返回 一个调用链 过滤条件的return chain.filter(exchange);}
}
5、服务模块
5.1、注册为灰度服务实例
在负载均衡器上获得了metaData数据,这个数据就是从灰色服务实例中获取,数据存储在nacos中
spring:application:name: user-service-graycloud:nacos:discovery:server-addr: localhost:8848username: nacospassword: nacosmetadata: {"gray-tag":"true"} #metadata可以自定义一些元数据
server:port: 0
5.2、设置负载均衡器
在服务启动类设置负载均衡器和开启OpenFeign服务:
defaultConfiguration = GrayLoadBalancerConfig.class全局负载均衡器的配置
@SpringBootApplication
@EnableFeignClients //启用Fegin客户端使用
//这里需要调用我们写的负载均衡器,我们自定义的负载均衡器主要就是为了检测gray参数
@LoadBalancerClients(defaultConfiguration = GrayLoadBalancerConfig.class)
public class NewUserServiceApplication {public static void main(String[] args) {SpringApplication.run(NewUserServiceApplication.class, args);}}
5.3、传递灰度标签
针对其他服务实例需要处理请求头信息
RequestInterceptor是一个Spring Cloud Feign中的接口,它用于在发起Feign请求前和请求后执行一些自定义的操作,例如添加请求头信息、记录请求日志、实现认证授权等
RequestInterceptor接口中包含一个方法:
apply方法:在发送请求之前执行的操作,比如添加请求头、修改请求路径或请求参数等
@Component
public class FeignRequestInterceptor implements RequestInterceptor {//相当于是一个前置操作 处理请求头信息,记录请求日志、实现认证授权@Overridepublic void apply(RequestTemplate requestTemplate) {//获取请求参数ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();//拿到请求 HttpServletRequest request = attributes.getRequest();//获取请求头参数 当然调用上面这条代码的 request是一样的Enumeration<String> headerNames = attributes.getRequest().getHeaderNames();//将获取的请求中的所有参数while(headerNames.hasMoreElements()){//将key先获取String key=headerNames.nextElement();//通过key 获得 valueString value=request.getHeader(key);//将获取的参数 放到header头中采用键值 key:value 放到请求模板中requestTemplate.header(key,value);}}
}