灰度發布(Gray Release,也稱為灰度發布或金絲雀發布)是指在軟件或服務發布過程中,將新版本的功能或服務以較小的比例引入到生產環境中,僅向部分用戶或節點提供新功能的一種發布策略。
在傳統的全量發布中,新版本的功能會一次性全部部署到所有的用戶或節點上。然而,這種方式潛在的風險是,如果新版本存在缺陷或問題,可能會對所有用戶或節點產生嚴重的影響,導致系統崩潰或服務不可用。
相比之下,灰度發布采用較小的規模,并逐步將新版本的功能引入到生產環境中,僅向一小部分用戶或節點提供新功能。通過持續監測和評估,可以在發現問題時及時回滾或修復。這種逐步引入新版本的方式可以降低風險,并提高系統的穩定性和可靠性。
灰色發布的常見實現思路有以下幾種:
而在生產環境中,比較常用的是根據用戶標識來實現灰色發布,也就是說先讓一小部分用戶體驗新功能,以發現新服務中可能存在的某種缺陷或不足。
Spring Cloud 全鏈路灰色發布的關鍵實現思路如下圖所示:
灰度發布的具體實現步驟如下:
經過第四步的反復傳遞之后,整個 Spring Cloud 全鏈路的灰度發布就完成了。
灰度發布的關鍵實現技術和代碼如下。
在灰度發布的執行流程中,有一個核心的問題,如果在 Spring Cloud LoadBalancer 進行服務調用時,區分正式服務和灰度服務呢?
這個問題的解決方案是:在灰度服務既注冊中心的 MetaData(元數據)中標識自己為灰度服務即可,而元數據中沒有標識(灰度服務)的則為正式服務,以 Nacos 為例,它的設置如下:
spring: application: name: canary-user-service cloud: nacos: discovery: username: nacos password: nacos server-addr: localhost:8848 namespace: public register-enabled: true metadata: { "grap-tag":"true" } # 標識自己為灰度服務
Spring Cloud LoadBalancer 判斷并調用灰度服務的關鍵實現代碼如下:
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 { // 服務不為空 RequestDataContext dataContext = (RequestDataContext) request.getContext(); HttpHeaders headers = dataContext.getClientRequest().getHeaders(); // 判斷是否為灰度發布(請求) if (headers.get(GlobalVariables.GRAY_KEY) != null && headers.get(GlobalVariables.GRAY_KEY).get(0).equals("true")) { // 灰度發布請求,得到新服務實例列表 List<ServiceInstance> findInstances = instances.stream(). filter(s -> s.getMetadata().get(GlobalVariables.GRAY_KEY) != null && s.getMetadata().get(GlobalVariables.GRAY_KEY).equals("true")) .toList(); if (findInstances.size() > 0) { // 存在灰度發布節點 instances = findInstances; } } else { // 查詢非灰度發布節點 // 灰度發布測試請求,得到新服務實例列表 instances = instances.stream(). filter(s -> s.getMetadata().get(GlobalVariables.GRAY_KEY) == null || !s.getMetadata().get(GlobalVariables.GRAY_KEY).equals("true")) .toList(); } // 隨機正數值 ++i( & 去負數) int pos = this.position.incrementAndGet() & Integer.MAX_VALUE; // ++i 數值 % 實例數 取模 -> 輪詢算法 int index = pos % instances.size(); // 得到服務實例方法 ServiceInstance instance = (ServiceInstance) instances.get(index); return new DefaultResponse(instance); } }
以上代碼為自定義負載均衡器,并使用了輪詢算法。如果 Header 中有灰度標簽,則只查詢灰度服務的節點實例,否則則查詢出所有的正式節點實例(以供服務調用或服務轉發)。
要在網關 Spring Cloud Gateway 中傳遞灰度標識,只需要在 Gateway 的全局自定義過濾器中設置 Response 的 Header 即可,具體實現代碼如下:
package com.example.gateway.config;import com.loadbalancer.canary.common.GlobalVariables;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.http.HttpStatus;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;@Componentpublic class LoadBalancerFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 得到 request、response 對象 ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); if (request.getQueryParams().getFirst(GlobalVariables.GRAY_KEY) != null) { // 設置金絲雀標識 response.getHeaders().set(GlobalVariables.GRAY_KEY, "true"); } // 此步驟正常,執行下一步 return chain.filter(exchange); }}
HTTP 調用工具 Openfeign 傳遞灰度標簽的實現代碼如下:
import feign.RequestInterceptor;import feign.RequestTemplate;import jakarta.servlet.http.HttpServletRequest;import org.springframework.stereotype.Component;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import java.util.Enumeration;import java.util.LinkedHashMap;import java.util.Map;@Componentpublic class FeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { // 從 RequestContextHolder 中獲取 HttpServletRequest ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); // 獲取 RequestContextHolder 中的信息 Map<String, String> headers = getHeaders(attributes.getRequest()); // 放入 openfeign 的 RequestTemplate 中 for (Map.Entry<String, String> entry : headers.entrySet()) { template.header(entry.getKey(), entry.getValue()); } } /** * 獲取原請求頭 */ private Map<String, String> getHeaders(HttpServletRequest request) { Map<String, String> map = new LinkedHashMap<>(); Enumeration<String> enumeration = request.getHeaderNames(); if (enumeration != null) { while (enumeration.hasMoreElements()) { String key = enumeration.nextElement(); String value = request.getHeader(key); map.put(key, value); } } return map; }}
灰度發布是微服務時代保證生產環境安全的必備措施,而其關鍵實現思路是:
這樣,我們就完整的實現 Spring Cloud 全鏈路灰度發布功能了。
本文鏈接:http://www.www897cc.com/showinfo-26-23626-0.htmlSpringCloud全鏈路灰色具體發布!
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
下一篇: 八個 yyds 的開源前端實戰項目!