事务布景

众所周知,在咱们迭代事务需求和Fix Bug时往往需要重启咱们的服务,在高并发场景下,如果还像N年前一样,通过上机操作敲Linux命令执行Kill操作,在分布式环境下显然会让你敲断手指,而且这种方法重启还会影响用户的操作,导致一些线上事端,所以,在微服务、分布式体系中,滑润翻滚发布无疑是非常重要的(主打的便是一个丝滑)。

SpringCloud微服务架构导致发布的一些坏处

1.Nacos组件导致的一些坏处

众所周知,Nacos利用心跳机制每隔一段时间(可装备,defalut:30s)服务端与客户端都会互相发送心跳包,当服务端接收到的客户端心跳包有反常情况时,Nacos服务端会剔除客户端在服务端中的注册信息,意味着这个服务是不可用状态,这种方法其实是有坏处的,这其实也是心跳机制的坏处,比如我服务A重启了,Nacos服务端并没有马上感知服务A的反常情况,这个时候还以为其是可用状态,这个时候如果有流量打进来,通过负载均衡很明显会有流量进入到服务A,无法即时感知客户端反常,便是它的最大坏处。

2.SpringCloud Gateway组件导致的一些坏处

SpringCloud Gateway组件对比以前的Zuul网关,在功能和吞吐提升了许多,首要是因为它是根据Spring5.0+SpringBoot2.0+Spring Reactor,没错看到Reactor你就会想到功能强悍的Netty,SpringCloud Gateway正是用了这一高功能通讯框架。

提到网关,榜首咱们要想到的便是负载均衡机制,之前面试过不少后端的兄弟,你们的体系怎么做的负载均衡,他们中多数人的回答是运用了Nginx的负载均衡机制,我…,咱不废话,接着说SpringCloud Gateway的负载均衡机制,当运用Ribbon作为负载中间件时会运用定时线程从Nacos注册中心拉取服务列表然后放到Gateway服务的本地缓存中,拉取时间默认也是30s,相同的也是无法及时感知Nacos注册中心的服务反常情况,所以其实许多人在运用SpringCloud Gateway中会经常出现一些500,503等问题,下面我将针对这些组件的坏处做一个完善解决计划。

针对Nacos与GateWay坏处的解决计划

Nacos与Gateway之间即时感知坏处的优化切入思路

  1. 在程序停止前通过Nacos上下线事情监听回调操作中自动调用Nacos服务下线操作,让Nacos服务端能即时剔除重启的客户端,防止流量流入
  2. SpringCloud GateWay组件中能够重写其负载均衡战略,也便是在服务上下线回调监听事情中刷新在网关服务本地缓存中的Nacos服务信息,就好比咱们事务中更新了数据库要刷一遍缓存一般,操作起来没啥难度。

线上优化实战

  • 问题简介

根据线上日志发现,gateway网关在服务重启时会有调用失利的现象,调用失利或许会导致一些数据的丢失甚至引发一些金钱、充值相关的数据有误,尤其在并发越高的情况下,这种现象表现得越发严重,不才曾经的项目中有因为此等原因导致了服务雪崩的情况,所以针对此网关进行一个优化。

  • 优化思路可行性剖析
  1. 榜首种方法 重写gateway的负载均衡器,从可行性来看问题不大,但是咱们的首要问题是针对服务上下线无法及时感知而优化,并不是针对其负载均衡器进行深度优化,所以此方法虽可完成,但开发、成果成本或许稍大,不优先采纳

  2. 第二种方法 剖析Nacos与Gateway之间的关联联系可知,咱们能够通过Nacos上下线的事情监听回调来操作Gateway 令其刷新。

  • 代码

    @Slf4j
    @Component
    public class ApplicationEventListener implements ApplicationListener {
        @Value("${spring.application.name}")
        private String applicationName;
        @Value("${server.port}")
        private int port;
        @Autowired
        private DiscoveryClient discoveryClient;
        @Autowired
        private NacosAutoServiceRegistration nacosAutoServiceRegistration;
        @Override
        public void onApplicationEvent(ApplicationEvent applicationEvent) {
            if (applicationEvent instanceof ApplicationStartedEvent) {
                log.info("【{}】【{}】使用启动", IpUtil.getIntranetIp(), applicationName);
                Executors.newSingleThreadExecutor().execute(() -> checkDiscoveryClient());
            } else if (applicationEvent instanceof ContextClosedEvent) {
                log.info("【{}】【{}】程序已停止...", IpUtil.getIntranetIp(), applicationName);
                ApplicationCheckUtil.setSystemIsNormal(false);
                //高雅停机
                nacosAutoServiceRegistration.stop();
                SpringApplication.exit(SpringContextUtil.getApplicationContext());
                ((ConfigurableApplicationContext) SpringContextUtil.getApplicationContext()).close();
            }
        }
        private void checkDiscoveryClient() {
            try {
                List<ServiceInstance> serviceInstanceList;
                AtomicBoolean currentInstanceHasRegister = new AtomicBoolean(false);
                while (true) {
                    serviceInstanceList = discoveryClient.getInstances(applicationName);
                    serviceInstanceList.forEach(serviceInstance -> {
                        log.info("host:{} | port:{} | serviceId:{}", serviceInstance.getHost(), serviceInstance.getPort(), serviceInstance.getServiceId());
                        if (IpUtil.getIntranetIp().equals(serviceInstance.getHost()) && serviceInstance.getPort() == port) {
                            currentInstanceHasRegister.set(true);
                        }
                    });
                    if (currentInstanceHasRegister.get()) {
                        log.info("当时服务实例已成功注册到Nacos中...");
                        break;
                    }
                    TimeUnit.SECONDS.sleep(1);
                }
            } catch (Exception e) {
                log.error("检测服务注册反常...");
            } finally {
                DingTalkUtil.send(DingTalkType.PUBLISH_NOTICE, String.format("【%s】【%s】使用启动成功", IpUtil.getIntranetIp(), applicationName));
                ApplicationCheckUtil.setSystemIsNormal(true);
            }
        }
    }
    

首要中心代码就一行:nacosAutoServiceRegistration.stop();

线上优化成果目标

通过此优化 频繁重启了几次服务,而且用测验工具一向并发调用重启的服务,并未发现有调用反常的情况,至此 服务端真正意义上的滑润重启略微提升了一个层次,但还不够,请接着往下看。

K8S翻滚发布更新计划

用户请求服务进程

【从0-1 千万级直播项目实战】微服务滚动发布方案 | 优雅且平滑

服务重启预想进程

【从0-1 千万级直播项目实战】微服务滚动发布方案 | 优雅且平滑

【从0-1 千万级直播项目实战】微服务滚动发布方案 | 优雅且平滑

翻滚发布流程

【从0-1 千万级直播项目实战】微服务滚动发布方案 | 优雅且平滑

k8s参数装备

服务在翻滚更新时,deployment操控器的目的是:给旧版本(old_rs)副本数减少至0、给新版本(new_rs)副本数量增至希望值(replicas)。我们在运用时,通常容易忽视操控速率的特性,以下是kubernetes提供的两个参数:

1. maxUnavailable:和希望ready的副本数比,不可用副本数最大份额(或最大值),这个值越小,越能确保服务稳定,更新越滑润;

2. maxSurge:和希望ready的副本数比,超越希望副本数最大份额(或最大值),这个值调的越大,副本更新速度越快。

spec:
  ---副本数量
  replicas: 5
  selector:
    matchLabels:
      app: user-service
  minReadySeconds: 120
  strategy:
    ---翻滚更新方法
    type: RollingUpdate
    rollingUpdate:
      ---超越希望副本数最大份额(或最大值)
      maxSurge: 1
      ---不可用副本数最大份额(或最大值)
      maxUnavailable: 3

至此,一套完好高可用、稳定的微服务翻滚更新计划已完成,根本现已能够满意目前的运用情况。

后续优化展望

  1. 添加灰度发布计划
  2. 添加长衔接服务的滑润发布计划(涉及到的知识点很干,我们敬请期待)