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

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]注销服务组件");
    }

    @WriteOperation
    public 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: 18001
  endpoints:
    web:
      exposure:
        include: "info,health,shutdown,shutdownRegister"
  endpoint:
    shutdown:
      enabled: true
    health:
      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 {

	@Resource
	private DiscoveryClient discoveryClient;


	public GrayGatewayLoadBalancerFilter(LoadBalancerClient loadBalancer, LoadBalancerProperties properties) {
		super(loadBalancer, properties);
	}

	@Override
	protected 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);
	}

	@Override
	public 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 {

    @Override
    public 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();
        }
    }
}
    @Bean
    public 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 {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        if (RpcContext.getContext().isConsumerSide()) {
            // Consumer
            return doConsumerFilter(invoker, invocation);
        }

        if (RpcContext.getContext().isProviderSide()) {
            // Provider
            return 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);

	@Autowired
	private NacosDiscoveryProperties nacosDiscoveryProperties;

	@Autowired
	private 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());
	}

	@Override
	public Server choose(Object key) {
		Server nacosServer = nacosFeignChoose();
		return nacosServer;
	}

	private ILoadBalancer lb;

	@Override
	public void setLoadBalancer(ILoadBalancer iLoadBalancer) {
		this.lb = iLoadBalancer;
	}

	@Override
	public 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;

	}

}
    @Bean
    public IRule grayLoadBalanceRule() {
        return new GrayLoadBalanceRule();
    }


http://www.kler.cn/a/392356.html

相关文章:

  • Java面向对象编程进阶之包装类
  • [运维][Nginx]Nginx学习(1/5)--Nginx基础
  • GxtWaitCursor:Qt下基于RAII的鼠标等待光标类
  • 结构体(c语言)
  • Springboot集成syslog+logstash收集日志到ES
  • 重构代码之内联临时变量
  • AI问答-base64:概念、原理、使用
  • 安卓全屏实现
  • IPv4与IPv6的优缺点
  • 【go从零单排】Stateful Goroutines(有状态的 goroutines)
  • 2024 年 Postman 网页版使用图文教程
  • Tomcat(6) 什么是Servlet容器?
  • 单例模式和适配器模式的简单介绍
  • [ACTF2020 新生赛]Upload 1--详细解析
  • JVM(一、基础知识)
  • 7. 基于 Redis 实现分布式锁
  • 基于Java Web的传智播客crm企业管理系统的设计与实现
  • 公开仓库改私有再配置公钥后Git拉取仍需要输入用户名的问题
  • 普通用户切换到 root 用户不需要输入密码配置(Ubuntu20)
  • vxe-table 3.10+ 进阶高级用法(一),根据业务需求自定义实现筛选功能
  • 【软考】系统架构设计师-计算机系统基础(2):操作系统
  • 【Linux】Linux 命令awk和sed的简单介绍
  • Vestar:AI造神邸,颠覆Meme叙事的新范式
  • 45.第二阶段x86游戏实战2-hook监控实时抓取游戏lua
  • 【python GUI编码入门-24】使用Tkinter构建一个简单的音乐播放器
  • 【Linux:IO多路复用(select函数)