SpringCloud實(shí)現(xiàn)全鏈路灰度發(fā)布的示例詳解
灰度發(fā)布(Gray Release,也稱為灰度發(fā)布或金絲雀發(fā)布)是指在軟件或服務(wù)發(fā)布過(guò)程中,將新版本的功能或服務(wù)以較小的比例引入到生產(chǎn)環(huán)境中,僅向部分用戶或節(jié)點(diǎn)提供新功能的一種發(fā)布策略。
在傳統(tǒng)的全量發(fā)布中,新版本的功能會(huì)一次性全部部署到所有的用戶或節(jié)點(diǎn)上。然而,這種方式潛在的風(fēng)險(xiǎn)是,如果新版本存在缺陷或問(wèn)題,可能會(huì)對(duì)所有用戶或節(jié)點(diǎn)產(chǎn)生嚴(yán)重的影響,導(dǎo)致系統(tǒng)崩潰或服務(wù)不可用。
相比之下,灰度發(fā)布采用較小的規(guī)模,并逐步將新版本的功能引入到生產(chǎn)環(huán)境中,僅向一小部分用戶或節(jié)點(diǎn)提供新功能。通過(guò)持續(xù)監(jiān)測(cè)和評(píng)估,可以在發(fā)現(xiàn)問(wèn)題時(shí)及時(shí)回滾或修復(fù)。這種逐步引入新版本的方式可以降低風(fēng)險(xiǎn),并提高系統(tǒng)的穩(wěn)定性和可靠性。
1.實(shí)現(xiàn)思路
灰色發(fā)布的常見(jiàn)實(shí)現(xiàn)思路有以下幾種:
- 根據(jù)用戶劃分:根據(jù)用戶標(biāo)識(shí)或用戶組進(jìn)行劃分,在整個(gè)用戶群體中只選擇一小部分用戶獲得新功能。
- 根據(jù)地域劃分:在不同地區(qū)或不同節(jié)點(diǎn)上進(jìn)行劃分,在其中的一小部分地區(qū)或節(jié)點(diǎn)進(jìn)行新功能的發(fā)布。
- 根據(jù)流量劃分:根據(jù)流量的百分比或請(qǐng)求次數(shù)進(jìn)行劃分,只將一部分請(qǐng)求流量引導(dǎo)到新功能上。
而在生產(chǎn)環(huán)境中,比較常用的是根據(jù)用戶標(biāo)識(shí)來(lái)實(shí)現(xiàn)灰色發(fā)布,也就是說(shuō)先讓一小部分用戶體驗(yàn)新功能,以發(fā)現(xiàn)新服務(wù)中可能存在的某種缺陷或不足。
2.具體實(shí)現(xiàn)
Spring Cloud 全鏈路灰色發(fā)布的關(guān)鍵實(shí)現(xiàn)思路如下圖所示:
灰度發(fā)布的具體實(shí)現(xiàn)步驟如下:
1.前端程序在灰度測(cè)試的用戶 Header 頭中打上標(biāo)簽,例如在 Header 中添加“grap-tag: true”,其表示要進(jìn)行灰常測(cè)試(訪問(wèn)灰度服務(wù)),而其他則為訪問(wèn)正式服務(wù)。
2.在負(fù)載均衡器 Spring Cloud LoadBalancer 中,拿到 Header 中的“grap-tag”進(jìn)行判斷,如果此標(biāo)簽不為空,并等于“true”的話,表示要訪問(wèn)灰度發(fā)布的服務(wù),否則只訪問(wèn)正式的服務(wù)。
3.在網(wǎng)關(guān) Spring Cloud Gateway 中,將 Header 標(biāo)簽“grap-tag: true”繼續(xù)往下一個(gè)調(diào)用服務(wù)中傳遞。
4.在后續(xù)的調(diào)用服務(wù)中,需要實(shí)現(xiàn)以下兩個(gè)關(guān)鍵功能:
- 在負(fù)載均衡器 Spring Cloud LoadBalancer 中,判斷灰度發(fā)布標(biāo)簽,將請(qǐng)求分發(fā)到對(duì)應(yīng)服務(wù)。
- 將灰度發(fā)布標(biāo)簽(如果存在),繼續(xù)傳遞給下一個(gè)調(diào)用的服務(wù)。
經(jīng)過(guò)第四步的反復(fù)傳遞之后,整個(gè) Spring Cloud 全鏈路的灰度發(fā)布就完成了。
3.核心實(shí)現(xiàn)思路和代碼
灰度發(fā)布的關(guān)鍵實(shí)現(xiàn)技術(shù)和代碼如下。
3.1 區(qū)分正式服務(wù)和灰度服務(wù)
在灰度發(fā)布的執(zhí)行流程中,有一個(gè)核心的問(wèn)題,如果在 Spring Cloud LoadBalancer 進(jìn)行服務(wù)調(diào)用時(shí),區(qū)分正式服務(wù)和灰度服務(wù)呢?
這個(gè)問(wèn)題的解決方案是:在灰度服務(wù)既注冊(cè)中心的 MetaData(元數(shù)據(jù))中標(biāo)識(shí)自己為灰度服務(wù)即可,而元數(shù)據(jù)中沒(méi)有標(biāo)識(shí)(灰度服務(wù))的則為正式服務(wù),以 Nacos 為例,它的設(shè)置如下:
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" } # 標(biāo)識(shí)自己為灰度服務(wù)
3.2 負(fù)載均衡調(diào)用灰度服務(wù)
Spring Cloud LoadBalancer 判斷并調(diào)用灰度服務(wù)的關(guān)鍵實(shí)現(xiàn)代碼如下:
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) { // 實(shí)例為空 if (instances.isEmpty()) { if (log.isWarnEnabled()) { log.warn("No servers available for service: " + this.serviceId); } return new EmptyResponse(); } else { // 服務(wù)不為空 RequestDataContext dataContext = (RequestDataContext) request.getContext(); HttpHeaders headers = dataContext.getClientRequest().getHeaders(); // 判斷是否為灰度發(fā)布(請(qǐng)求) if (headers.get(GlobalVariables.GRAY_KEY) != null && headers.get(GlobalVariables.GRAY_KEY).get(0).equals("true")) { // 灰度發(fā)布請(qǐng)求,得到新服務(wù)實(shí)例列表 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) { // 存在灰度發(fā)布節(jié)點(diǎn) instances = findInstances; } } else { // 查詢非灰度發(fā)布節(jié)點(diǎn) // 灰度發(fā)布測(cè)試請(qǐng)求,得到新服務(wù)實(shí)例列表 instances = instances.stream(). filter(s -> s.getMetadata().get(GlobalVariables.GRAY_KEY) == null || !s.getMetadata().get(GlobalVariables.GRAY_KEY).equals("true")) .toList(); } // 隨機(jī)正數(shù)值 ++i( & 去負(fù)數(shù)) int pos = this.position.incrementAndGet() & Integer.MAX_VALUE; // ++i 數(shù)值 % 實(shí)例數(shù) 取模 -> 輪詢算法 int index = pos % instances.size(); // 得到服務(wù)實(shí)例方法 ServiceInstance instance = (ServiceInstance) instances.get(index); return new DefaultResponse(instance); } }
以上代碼為自定義負(fù)載均衡器,并使用了輪詢算法。如果 Header 中有灰度標(biāo)簽,則只查詢灰度服務(wù)的節(jié)點(diǎn)實(shí)例,否則則查詢出所有的正式節(jié)點(diǎn)實(shí)例(以供服務(wù)調(diào)用或服務(wù)轉(zhuǎn)發(fā))。
3.3 網(wǎng)關(guān)傳遞灰度標(biāo)識(shí)
要在網(wǎng)關(guān) Spring Cloud Gateway 中傳遞灰度標(biāo)識(shí),只需要在 Gateway 的全局自定義過(guò)濾器中設(shè)置 Response 的 Header 即可,具體實(shí)現(xiàn)代碼如下:
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; @Component public class LoadBalancerFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 得到 request、response 對(duì)象 ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); if (request.getQueryParams().getFirst(GlobalVariables.GRAY_KEY) != null) { // 設(shè)置金絲雀標(biāo)識(shí) response.getHeaders().set(GlobalVariables.GRAY_KEY, "true"); } // 此步驟正常,執(zhí)行下一步 return chain.filter(exchange); } }
3.4 Openfeign 傳遞灰度標(biāo)簽
HTTP 調(diào)用工具 Openfeign 傳遞灰度標(biāo)簽的實(shí)現(xiàn)代碼如下:
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; @Component public 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()); } } /** * 獲取原請(qǐng)求頭 */ 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; } }
小結(jié)
灰度發(fā)布是微服務(wù)時(shí)代保證生產(chǎn)環(huán)境安全的必備措施,而其關(guān)鍵實(shí)現(xiàn)思路是:
1、注冊(cè)中心區(qū)分正常服務(wù)和灰度服務(wù);
2、負(fù)載均衡正確轉(zhuǎn)發(fā)正常服務(wù)和灰度服務(wù);
3、網(wǎng)關(guān)和 HTTP 工具傳遞灰度標(biāo)簽。
這樣,我們就完整的實(shí)現(xiàn) Spring Cloud 全鏈路灰度發(fā)布功能了。
到此這篇關(guān)于SpringCloud實(shí)現(xiàn)全鏈路灰度發(fā)布的示例詳解的文章就介紹到這了,更多相關(guān)SpringCloud灰度發(fā)布內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring Cloud 優(yōu)雅下線以及灰度發(fā)布實(shí)現(xiàn)
- SpringCloud實(shí)現(xiàn)灰度發(fā)布的方法步驟
- springcloud+nacos實(shí)現(xiàn)灰度發(fā)布示例詳解
- 關(guān)于SpringCloud灰度發(fā)布的實(shí)現(xiàn)
- SpringCloud灰度發(fā)布的設(shè)計(jì)與實(shí)現(xiàn)詳解
- SpringCloud的全鏈路灰度發(fā)布方案詳解
- Spring?Cloud實(shí)現(xiàn)灰度發(fā)布的示例代碼
- Spring Cloud Gateway實(shí)現(xiàn)灰度發(fā)布方案
相關(guān)文章
微信js sdk invalid signature簽名錯(cuò)誤問(wèn)題的解決方法分析
這篇文章主要介紹了微信js sdk invalid signature簽名錯(cuò)誤問(wèn)題的解決方法,結(jié)合實(shí)例形式分析了微信簽名錯(cuò)誤問(wèn)題相關(guān)解決方法,需要的朋友可以參考下2019-04-04SWT(JFace) 體驗(yàn)之FontRegistry
測(cè)試代碼如下:2009-06-06spring boot實(shí)現(xiàn)阿里云視頻點(diǎn)播上傳視頻功能(復(fù)制粘貼即可)
這篇文章主要介紹了spring boot實(shí)現(xiàn)阿里云視頻點(diǎn)播上傳視頻功能(復(fù)制粘貼即可),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12SpringBoot重啟后,第一次請(qǐng)求接口請(qǐng)求慢的問(wèn)題及解決
這篇文章主要介紹了SpringBoot重啟后,第一次請(qǐng)求接口請(qǐng)求慢的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05mybatis insert 返回自增主鍵的實(shí)現(xiàn)示例
mybatis 在新增之后怎么也獲取不到自增主鍵,本文主要介紹了mybatis insert 返回自增主鍵的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06Spring如何通過(guò)注解引入外部資源(PropertySource?Value)
這篇文章主要為大家介紹了Spring通過(guò)注解@PropertySource和@Value引入外部資源的方法實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07Java操作excel的三種常見(jiàn)方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Java操作excel的三種常見(jiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04