目的
Spring cloud 微服務、k8s容器化部署的架構下,單個服務升級過程中,不停止對外提供服務,使得用戶對整個升級過程無感知,從而實現服務的優雅升級。
負載均衡器
1. spring cloud ribbon(k8s同namespace):k8s同一命名空間之間的服務調用,采用ribbon、eureka做服務注冊和負載均衡。
問題:
a、ribbon和eureka(CP)注重服務的可用性,所以存在服務實例的緩存,本地緩存:(1)ribbon本地緩存 (2)eureka client本地緩存 。遠程緩存:(1)eureka server 三級緩存
b、service 提供者停止,pod 向容器發送SIGTERM信號,service 提供者會向eureka server下線服務,這個時候service消費者會存在原有service的實例信息
2. k8s service (k8s不同namespce): k8s不同命名空間之間的服務調用,采用k8s service、kube-proxy做服務注冊和負載均衡。
問題:
a、k8s service 支持服務的負載均衡,只有容器探針檢測成功后,才會停止舊的容器Service。采用兩種探針實現RollingUpdate。但是前提要存在多個實例
基於spring cloud 負載均衡的優雅停機
針對spring cloud負載均衡存在的問題,要做到優雅停機,需要進行如下配置:
a、縮短緩存時間
1. 修改Client配置(ribbon和eureka client)
1 ribbon: 2 # ribbon刷新eureka的時間5s, 默認30s 3 ServerListRefreshInterval: 5000 4 #注冊中心 5 eureka: 6 client: 7 serviceUrl: 8 defaultZone: http://eureka-server:8761/eureka 9 # eureka客戶端需要多長時間發定時刷新本地緩存時間,默認30s 10 registry-fetch-interval-seconds: 5 11 instance: 12 prefer-ip-address: true 13 instance-id: ${spring.cloud.client.ipAddress}:${server.port} 14 # eureka客戶端需要多長時間發送心跳給eureka服務器,表明他仍然活着,默認30秒 15 lease-renewal-interval-in-seconds: 5 16 # eureka服務器在接受到實力的最后一次發出的心跳后,需要等待多久才可以將此實例刪除 默認90秒 17 lease-expiration-duration-in-seconds: 15
縮短ribbon本地緩存時間,eureka client拉取注冊表、心跳間隔時間
2. 修Server配置(eureka server)
1 eureka: 2 server: 3 # 禁用readOnlyCacheMap 4 useReadOnlyResponseCache: false 5 # 主動失效檢測間隔, 默認60s 6 evictionIntervalTimerInMs: 5000 7 instance: 8 # eureka服務器的標識,如果是集群就可以寫成 eurekaSer1,eurekaSer2,eurekaSer3.. 9 hostname: eureka-server 10 health-check-url-path: /actuator/health 11 client: 12 # 開啟客戶端存活狀態監測 13 healthcheck: 14 enabled: true 15 registerWithEureka: false 16 fetchRegistry: false
縮短失效檢測的實現,同時禁用readOnlye緩存。 eureka server 存在三級緩存 ,均是純內存操作,詳細見下圖
(1)registry(ConcurrentMap):注冊表
(2)readWriteCacheMap(GuavaCache):讀寫緩存
(3)readOnlyCacheMap(ConcurrentMap):只讀緩存
b、Service先下線,再停機
1、增加Service 下線端點

1 @ConfigurationProperties(prefix = "endpoints.offline") 2 public class OfflineEndpoint extends AbstractEndpoint<String> { 3 4 public OfflineEndpoint() { 5 super("offline", false); 6 } 7 8 9 @Override 10 public String invoke() { 11 String remoteIp = IPUtils.getIp(); 12 String localIp = IPUtils.getLocalIp(); 13 log.info("remoteIp:{} trigger service offline", remoteIp); 14 if(localIp.substring(0, localIp.lastIndexOf(".")).equals(remoteIp.substring(0, remoteIp.lastIndexOf(".")))) { 15 log.info("service start execute offline"); 16 DiscoveryManager.getInstance().shutdownComponent(); 17 try { 18 Thread.sleep(10000); 19 } catch (InterruptedException e) { 20 log.error("sleep exception",e); 21 throw new RuntimeException(e); 22 } 23 log.info("service end execute offline"); 24 return "SUCCESS"; 25 } else { 26 log.warn("remoteIp:{} localIp:{} not trigger service ll_offline", remoteIp, localIp); 27 return "NOT_ALLOW"; 28 } 29 30 } 31 }
2、利用pod preStop hook
1 lifecycle: 2 preStop: 3 httpGet: 4 port: 9494 5 scheme: HTTP 6 path: offline
c、Spring Cloud升級流程總結
針對容器化RollingUpdate,Service 舊版本A,新版本A1發布流程如下:
1、新版本容器Service A1,而在Service A1啟動后就會注冊到Eureka Server,
2、Service A1,只有容器探針檢測成功后,才會停止舊的容器Service A。所以此時 Service A1 和 Service A都注冊在 Eureka Server上,接受流量
3、Service A1,容器探測成功,下線Service A
4、Service A 觸發 preStop的offline操作,將Service A 從Eureka Server中下線,直至緩存更新完成
5、Service A停止,Service A1 上限,完成單次服務滾動更新
基於k8s service 負載均衡的優雅停機
實例注冊:容器啟動成功並不會注冊到service,只有需要容器readinessPro成功后,才會注冊到service。
實例注銷:容器執行preStop的同時就會將實例信息從service注銷
k8s Deployment本身就支持Rolling update,所以這里優雅停機:可以先擴容到2個實例,然后再縮容到1個實例,實現單服務的滾動更新