当前位置: 首页 > news >正文

spring cloud实战总结(优雅下线、灰度发布)

1、nacos优雅下线

@Endpoint(id = "shutdownRegister"
)
@Configuration
@Slf4j
public class ShutdownRegisterEndpoint implements ApplicationContextAware {private static final Map<String, String> NO_CONTEXT_MESSAGE = Collections.singletonMap("message", "No context to shutdownRegister.");private static final Map<String, String> SHUTDOWN_MESSAGE = Collections.singletonMap("message", "Register Shutting down, bye...");@Autowired(required = false)NacosServiceRegistry nacosServiceRegistry;@Autowired(required = false)NacosRegistration registration;private ConfigurableApplicationContext context;@Autowired(required = false)private ApplicationContext applicationContext;public ShutdownRegisterEndpoint() {log.info("[组件init]注销服务组件");}@WriteOperationpublic Map<String, String> shutdownRegister() {if (this.context == null) {return NO_CONTEXT_MESSAGE;}if (nacosServiceRegistry != null && registration != null) {log.info("注销nacos");nacosServiceRegistry.deregister(registration);}if (applicationContext != null) {applicationContext.publishEvent(new EleServerDownEvent(new Object()));}return SHUTDOWN_MESSAGE;}public void setApplicationContext(ApplicationContext context) throws BeansException {if (context instanceof ConfigurableApplicationContext) {this.context = (ConfigurableApplicationContext) context;}}
}public class EleServerDownEvent extends ApplicationEvent {private static final long serialVersionUID = 1305017141473336210L;public EleServerDownEvent(Object source) {super(source);}
}

使用方式:

# 健康监控
management:server:port: 18001endpoints:web:exposure:include: "info,health,shutdown,shutdownRegister"endpoint:shutdown:enabled: truehealth:show-details: always然后脚本启动命令如下:
curl --connect-timeout 5 -X POST http://$host_ip:18000/actuator/shutdownRegister
sleep 10
curl --connect-timeout 5 -X POST http://$host_ip:18000/actuator/shutdown
sleep 30
2、流量灰度发布

灰度发布通过在网关层根据特定规则将部分流量路由到新版本的服务实例上,同时保持大部分流量仍路由到旧版本服务实例,从而实现逐步过渡到新版本的目的。

主要步骤是:将应用部署到灰度实例上,在nacos元数据标识灰度服务,然后通过网关获取到url灰度标识,通过拦截器路由算法路由到灰度实例上

1、在url中header加入灰度标识,例如:   version: gray

2、网关匹配路由

/*** @Description 灰度负载均衡**/
public class GrayGatewayLoadBalancerFilter extends LoadBalancerClientFilter {@Resourceprivate DiscoveryClient discoveryClient;public GrayGatewayLoadBalancerFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {super(loadBalancer, properties);}@Overrideprotected ServiceInstance choose(ServerWebExchange exchange) {//所有服务数据URI originalUrl = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);if (originalUrl == null) {return super.choose(exchange);}String targetServerId = originalUrl.getHost();List<ServiceInstance> serverList = discoveryClient.getInstances(targetServerId);ServiceInstance choose = GrayGatewayGrayRouteUtil.choose(exchange, serverList);if (choose != null) {return choose;}return super.choose(exchange);}@Overridepublic int getOrder() {return Ordered.LOWEST_PRECEDENCE;}}

@Slf4j
public class GrayGatewayGrayRouteUtil {private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);private static final AtomicInteger GRAY_ATOMIC_INTEGER = new AtomicInteger(0);private GrayGatewayGrayRouteUtil() {}public static ServiceInstance choose(ServerWebExchange exchange, List<ServiceInstance> serverList) {//所有服务数据URI originalUrl = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);if (originalUrl == null) {log.error("error originalUrl is null");return null;}//没有服务返回空if (CollUtil.isEmpty(serverList)) {log.error("error serverList is isEmpty.");return null;}String grayHeader = exchange.getRequest().getHeaders().getFirst("version");List<ServiceInstance> instanceList = filterInstanceList(serverList, grayHeader);if (Objects.nonNull(instanceList) && CollUtil.isNotEmpty(instanceList)) {ServiceInstance serviceInstance = null;if(StringUtils.isNotBlank(grayHeader)){log.info("gray ServiceInstance grayHeader: {}", grayHeader);serviceInstance = instanceList.get(getGrayAndIncrement() % instanceList.size());} else {serviceInstance = instanceList.get(getAndIncrement() % instanceList.size());}return serviceInstance;}/*** 兜底* 1、不容错:所有正常的机器都在重启or 都宕机了 不可以请求到灰度服务上(第一版本的灰度发布策略只有1台灰度机器,这种极端情况下如果允许分发请求,1台机器承受不住所有的请求压力)* 2、灰度机器不存在,随机一台实例*/return StringUtils.isNotBlank(grayHeader) ? serverList.get(getAndIncrement() % serverList.size()) : null;}private static List<ServiceInstance> filterInstanceList(List<ServiceInstance> serverList, String grayHeader) {return serverList.stream().filter(item -> {Map<String, String> metadata = item.getMetadata();String serverGrayValue = metadata.get(GrayRouterConstants.GRAY_REGISTRY_TAG);//非灰度请求返回非灰度机器if (StringUtils.isBlank(grayHeader) && StringUtils.isBlank(serverGrayValue)) {return true;}//灰度请求返回灰度机器return StringUtils.isNotBlank(grayHeader) && StringUtils.isNotBlank(serverGrayValue) && serverGrayValue.equals(grayHeader);}).collect(Collectors.toList());}/*** 计算得到当前调用次数** @return*/public static int getAndIncrement() {int current;int next;do {current = ATOMIC_INTEGER.get();next = current >= Integer.MAX_VALUE ? 0 : current + 1;} while (!ATOMIC_INTEGER.compareAndSet(current, next));return next;}/*** 计算得到当前调用次数** @return*/public static int getGrayAndIncrement() {int current;int next;do {current = GRAY_ATOMIC_INTEGER.get();next = current >= Integer.MAX_VALUE ? 0 : current + 1;} while (!GRAY_ATOMIC_INTEGER.compareAndSet(current, next));return next;}
}

3、nacos灰度配置

spring:cloud:nacos:discovery:metadata:version: gray

4、应用层保存灰度信息,放到ThreadLocal中


/*** web 根据header的标识,设置当前线程走灰度服务参数*/
@Slf4j
public class GrayRouteFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;String grayName = httpRequest.getHeader(GrayRouterConstants.REQUEST_HEADER);if (StringUtils.isBlank(grayName)) {chain.doFilter(request, response);return;}GrayThreadLocal.setGrayName(grayName);try {log.info(" gray request grayName:{} ",grayName);chain.doFilter(request, response);} finally {GrayThreadLocal.remove();}}
}
    @Beanpublic FilterRegistrationBean<GrayRouteFilter> registerGrayRouteFilter() {FilterRegistrationBean<GrayRouteFilter> registration = new FilterRegistrationBean<>();registration.setFilter(new GrayRouteFilter());registration.addUrlPatterns("/*");registration.setName("grayRouteFilter");// 值越小,Filter越靠前。registration.setOrder(3);return registration;}

5、dubbo、feign配置


@Activate(group = {CommonConstants.CONSUMER, CommonConstants.PROVIDER}, order = 5)
@Slf4j
public class DubboGrayCodeFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {if (RpcContext.getContext().isConsumerSide()) {// Consumerreturn doConsumerFilter(invoker, invocation);}if (RpcContext.getContext().isProviderSide()) {// Providerreturn doProviderFilter(invoker, invocation);}return invoker.invoke(invocation);}/*** 服务提供方过滤** @param invoker* @param invocation* @return*/public Result doProviderFilter(Invoker<?> invoker, Invocation invocation) {GrayThreadLocal.remove();String grayName = RpcContext.getContext().getAttachment(GrayRouterConstants.REQUEST_HEADER);if (StringUtils.isNotBlank(grayName)) {log.info("gray doProviderFilter invocation grayName: {}", grayName);GrayThreadLocal.setGrayName(grayName);}try {return invoker.invoke(invocation);} finally {// 调用完成后移除线程变量属性GrayThreadLocal.remove();}}/*** 服务消费方过滤** @param invoker* @param invocation* @return*/public Result doConsumerFilter(Invoker<?> invoker, Invocation invocation) {String grayName = GrayThreadLocal.getGrayName();if (StringUtils.isNotBlank(grayName)) {String address = invoker.getUrl().getAddress();log.info("gray doConsumerFilter invocation grayName: {}, {}", grayName, address);RpcContext.getContext().setAttachment(GrayRouterConstants.REQUEST_HEADER,grayName);}return invoker.invoke(invocation);}}
//feign负载均衡
@Slf4j
public class GrayLoadBalanceRule implements IRule {private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);private static final AtomicInteger GRAY_ATOMIC_INTEGER = new AtomicInteger(0);@Autowiredprivate NacosDiscoveryProperties nacosDiscoveryProperties;@Autowiredprivate NacosServiceManager nacosServiceManager;/*** 计算得到当前调用次数*/public static int getAndIncrement() {int current;int next;do {current = ATOMIC_INTEGER.get();next = current >= Integer.MAX_VALUE ? 0 : current + 1;} while (!ATOMIC_INTEGER.compareAndSet(current, next));return next;}public static int getGrayAndIncrement() {int current;int next;do {current = GRAY_ATOMIC_INTEGER.get();next = current >= Integer.MAX_VALUE ? 0 : current + 1;} while (!GRAY_ATOMIC_INTEGER.compareAndSet(current, next));return next;}private static List<Instance> filterNacosInstanceList(List<Instance> serverList, String grayHeader) {return serverList.stream().filter(item -> {String serverGrayValue = item.getMetadata().get(GrayRouterConstants.GRAY_REGISTRY_TAG);
//			非灰度请求返回非灰度机器if (StringUtils.isBlank(grayHeader) && StringUtils.isBlank(serverGrayValue)) {return true;}
//			灰度请求返回灰度机器return StringUtils.isNotBlank(grayHeader) && StringUtils.isNotBlank(serverGrayValue) && serverGrayValue.equals(grayHeader);}).collect(Collectors.toList());}@Overridepublic Server choose(Object key) {Server nacosServer = nacosFeignChoose();return nacosServer;}private ILoadBalancer lb;@Overridepublic void setLoadBalancer(ILoadBalancer iLoadBalancer) {this.lb = iLoadBalancer;}@Overridepublic ILoadBalancer getLoadBalancer() {return lb;}private Server nacosFeignChoose() {ILoadBalancer lb = getLoadBalancer();if (lb == null) {return null;}String grayName = GrayThreadLocal.getGrayName();String serverName = FeignServerNameThreadLocal.getServerName();try {String group = this.nacosDiscoveryProperties.getGroup();DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();String name = loadBalancer.getName();NamingService namingService = nacosServiceManager.getNamingService(nacosDiscoveryProperties.getNacosProperties());List<Instance> instances = namingService.selectInstances(serverName, group, true);if (CollectionUtils.isEmpty(instances)) {log.info("no instance in service {}", name);return null;}List<Instance> serverList = filterNacosInstanceList(instances, grayName);if (Objects.nonNull(serverList) && CollUtil.isNotEmpty(serverList)) {NacosServer nacosServer = null;if(StringUtils.isNotBlank(grayName)){nacosServer = new NacosServer(serverList.get(getGrayAndIncrement() % serverList.size()));Instance instance = nacosServer.getInstance();String instanceId = instance.getInstanceId();log.info("gray instance in service {} ,serverName: {} ,instanceId:{}", name,serverName,instanceId);} else {nacosServer = new NacosServer(serverList.get(getAndIncrement() % serverList.size()));}return nacosServer;}} catch (Exception e) {log.warn("NacosRule error", e);}List<Server> allServers = lb.getAllServers();/*** 兜底* 1、不容错:所有正常的机器都在重启or 都宕机了 不可以请求到灰度服务上(第一版本的灰度发布策略只有1台灰度机器,这种极端情况下如果允许分发请求,1台机器承受不住所有的请求压力)* 2、灰度机器不存在,随机一台实例*/return StringUtils.isNotBlank(grayName) && CollUtil.isNotEmpty(allServers) ? allServers.get(getAndIncrement() % allServers.size()) : null;}}
    @Beanpublic IRule grayLoadBalanceRule() {return new GrayLoadBalanceRule();}


http://www.mrgr.cn/news/69802.html

相关文章:

  • 如何开展小组讨论以强化员工对六西格玛的关注度?
  • Python 爬虫运行状态监控:进度、错误与完成情况
  • Cursor的chat与composer的使用体验分享
  • 【机器学习】强化学习(1)——强化学习原理浅析(区分强化学习、监督学习和启发式算法)
  • “其它没有了”申请注册商标,驳回还在申请!
  • multi_agents
  • 推荐一款批量自动识别图片方向的软件:批量校正图像方向工具
  • PostgreSQL的奥秘:深入探究事务与锁的秘密世界
  • MySQL系列之如何在Linux只安装客户端
  • 《Python网络安全项目实战》项目4 编写网络扫描程序
  • 力扣力扣力:91.解码方法
  • 「C/C++」C++标准库 之 #include<iostream> 标准输入输出
  • CSS 色彩魔法:打造绚丽网页风格
  • c++左值、右值、完美转发、万能引用、参数展开
  • MyBatis6-逆向工程、分页插件
  • 问:聊聊Spring IOC机制
  • npm常用函数定义手册(持续更新)
  • 使用 nsenter 进入 Docker 容器的操作
  • 逆向攻防世界CTF系列21-answer_to_everything
  • 【Rust练习】20.进一步深入特征
  • Debezium系列之:Incremental snapshotting设计原理
  • 临床预测模型-静态诺模/列线图(Nomogram)+校准曲线(Calibration)分析学习
  • 动态规划-两个数组的dp问题——718.最长重复子数组
  • 【leetcode练习·二叉树】用「分解问题」思维解题 I
  • 《PyTorch深度学习快速入门教程》学习笔记(第20周)
  • 计算机网络基本概念总结