亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原創

微服務全鏈路灰度發布

2024-08-05 09:31:37
67
0

前言

現業務線公有云服務已經上生產,為保證后續公有云的穩定性,魯棒性,安全性,兼容性等,需提供業務線公有云微服務的全鏈路灰度發布思路,注意是微服務全鏈路灰度!!!?全鏈路灰度發布在業內已經是非常成熟的技術了,暫時沒有什么技術難點,主要是開發規劃落實和推動?,這里記錄下思考學習過程。

為何要灰度發布?

業務線公有云產品對外的目的就是為了賺錢,沒賺錢那公有云產品沒任何意義,所以影響賺錢的因素我們都應該考慮處理掉。業內有一個指標叫?SLA(Service-Level Agreement)?:服務等級協議是服務提供者對客戶一個服務承諾,評估一個產品是否可用的方法。一般有四個SLA指標,可用性、準確性、系統容量和延遲。SLA指標降低了,那產品不可用了就影響算錢了,產品總是不可用了客戶就要換產品了也影響賺錢。 因此保證系統的SLA指標是重中之重。?而可灰度和可監控是保證SLA的重要因素??。因此我們要做業務線產品公有云微服務線的灰度發布,且必須得做。

有那些版本發布方法

企業微信截圖_17225668346174.png

一些說明
 業內常見的版本發布方法有全量發布,滾動發布,藍綠發布,灰度發布(金絲雀發布)。業務線產品主要使用灰度發布方案實現公有云服務的生產微感發布。同時簡單介紹下其他的發布思路。

全量發布

企業微信截圖_17225671468764.png

一些說明
全量發布主要適用于簡單小型的內部服務,直接將所有的微服務和老版本升級到最新。屬有損發布。
優點:成本低,運維簡單粗暴。?適用于小型內部業務服務?。缺點:版本切換用戶感知大,升級過程中服務無法訪問,無法支持類似于公有云的對外客戶服務。

滾動發布

企業微信截圖_17225673297593.png

一些說明
滾動發布每次只升級一個或多個服務,如果業務觀察無問題,再重復這個過程,直到全部服務升級到新版本。屬有損發布。不過相對于全量發布,用戶感知變小,業務方可以手動保證服務最低限度對外服務。同樣只適用于內部的業務服務。
優點:運維成本較低,只有在新老版本切換的時間點才會客戶有感。缺點:?版本回滾麻煩。版本切換用戶感知較大?,需要人工去停止業務的流量,且難判斷停止的老版本節點是否還有流量。發布和回滾時間長,同樣有很大的升級風險。

藍綠發布

企業微信截圖_17225674405198.png

一些說明
藍綠部署老集群綠版本不停機,部署一套完整的藍集群新版本,用戶可以將所有流量全部遷移到藍集群新版本中,也可以通過流量染色實現流量在藍綠集群中切換。藍綠版本之間物理或者邏輯隔離,沒有子請求交互。屬無損發布。一般公有云單組件有狀態服務產品,比如國內某些廠商的存儲服務就使用藍綠部署的升級,不僅可以做到版本升級,還可以做到集群遷移,擴縮容等功能。
優點:?版本切換用戶基本無感?,藍綠版本可以做到流量快速微感切換回滾。運維版本發布方便。缺點:?成本較高,需要部署一套完整的集群?,如果是涉及到底層的有狀態存儲服務,還需要做到數據的遷移和同步對齊。同時對于小版本和bugfix不友好,無法支持DevOps快速的迭代開發。

灰度發布(金絲雀發布)

企業微信截圖_17225675094211.png

一些說明
灰度發布也交金絲雀發布,是一種是一種漸進式的軟件發布方式,它允許將新功能或更新逐步給一部分用戶試用,而不是一次性全部替換。屬無損發布。灰度發布也可以分為單鏈路發布和全鏈路發布,單鏈路發布理解為特殊的藍綠部署,流量可以按比例分割,如老集群80%,灰度集群20%。當灰度集群穩定了后,逐步將老集群的所有流量遷移到新集群中。單鏈路方案有一些缺陷,比如bugfix和小版本快速迭代,只需要更新一個或者多個服務,而不是全部。因此便有了全鏈路發布,全鏈路發布的思路可以允許運維開發人員只發布部分應用。適合復雜大型對外微服務。
優點:?全鏈路發布對客戶基本無感,運維版本發布非常方便?,且保存了穩定的老版本回滾快速,風險低。同時支持線上灰度測試,生產治理可控。缺點:成本較高,包括開發和資源成本,開發和運維流程需要嚴格約束。系統架構稍微復雜了,需更精細管理和維護,提高了管理和監控成本。

小結

上文簡單介紹了全量發布,滾動發布,藍綠部署,灰度發布4種發布思路。每一種發布思路都有適合的場景,?對于當前業務線公有云的微服務體系而言,全鏈路灰度發布是最好的選擇?。可以做到客戶基本無感,回滾方便,低風險,運維方便。下面著重介紹下全鏈路發布方案的思路。

灰度發布有什么好處?

企業微信截圖_17225677788287.png

一些說明
灰度發布的好處有:?用戶無感,提高系統穩定性,大大大減少了運維成本,發版不影響服務賺錢,白天也可以發布啦,用戶和業務灰度測試,無縫回滾?,提高公有云產品SLA, 快速體驗新產品idea,新版本影響面風險可控,迭代反饋循環,新版本影響面風險可控。下面重點說明下以下幾項
1,用戶無感:因為灰度發布會同時存在一個或多個版本,版本在快速切換的時候,不影響產品的正常使用。
2,大大大減少了運維成本:傳統的發布方法需要再凌晨做全量的配置文件,數據庫,微服務等發布操作,工作復雜且高風險,灰度發布存在網關流量控制,版本發布不用一股腦全部替換,可以先一個一個完成配置,數據庫,灰度服務發布后,然后使用apisix快速切換無感切換流量即可。
3,提高系統穩定性:灰度發布因為不會全量替換老版本,可以一個一個服務灰度切換,因此系統不會由于大量操作而導致奔潰而影響這個系統的穩定。
4,提高公有云產品SLA:因為灰度發布不會影響系統對外服務,保證了全年大數據公有云產品對外的服務時間。
5,白天也可以發布啦:傳統的發布方法因為會影響客戶使用,由于發布工作量大,風險發,因此一般在晚上發布。而由于灰度發布的流量可控性,白天也可以發布服務,不會影響線上用戶的使用。
6,用戶和業務灰度測試:灰度發布可以控制部分流量到灰度版本,因此可以使部分客戶作嘗鮮新的功能的同時也可以作用戶測試,測試人員也可以使用指定的賬號做線上灰度測試。線下測試版本,架構,配置,數據,依賴,負載等維度不同難以全面覆蓋線上場景,灰度測試能彌補這種情況,使產品更加的完善完整正確。
7,無縫回滾:灰度發布會存在多個版本,如果在用戶和業務灰度測試過程中發現bug,可以在網關層快速切換流量。
所以讓我們快速開始實現灰度發布吧~

灰度發布的一些方法

由于我們需要全鏈路灰度發布,因此基本上可以套模型,模型為:?注冊中心+API服務網關+微服務RPC負載均衡+微服務框架流量透傳??。基本上滿足這個模型才可以做到全鏈路灰度發布。
image.png

一些說明
image.png

灰度發布實現

由上文已確定使用APISIX+NACOS+SpringCloud+RpcLoadblance+流量透傳方式實現全鏈路灰度發布,下面記錄下詳細的實現細節。

全鏈路灰度的必要因素

企業微信截圖_17225787074500.png

一些說明
如果要實現全鏈路灰度發布,那么在技術層面需要支持如下幾點
1,API對外網關和RPC組件能負載均衡以及路由:如果API網關和RPC組件無法做到負載均衡和路由,那么無法將請求流量分發給灰度節點。
2,流量的標記信息和染色信息能全鏈路透傳:因為要做到全鏈路灰度,如果無法將流量的標記信息和染色信息透傳到整個鏈路,那將無法做到整個鏈路的灰度。
3,微服務節點能標記分組和區分版本:如果微服務節點無法標記,那么rpc路由將無法識別下游的節點是否為灰度節點,也就無法做到灰度發布。
4,請求流量能染色:流量如果無法做到染色,那當前節點將無法獲得下游的灰度節點。
5,請求流量有版本和灰度標記信息:請求流量又灰度標記和版本信息才能作為灰度判斷的參數。

全鏈路灰度技術細節

image.png

RpcLoadblance和流量透傳在業內有多種思路,主要有SDK,Javaagent(Sermant),以及自己編碼(推薦)。我們這里為了避免又引入其他組件,直接修改ddaf框架新增 MyFeignRequestInterceptor(流量透傳) 和 GrayNacosLoadBalancer(灰度路由和負載) 工具。

流量透傳實現

public class MyFeignRequestInterceptor implements RequestInterceptor {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(MyFeignRequestInterceptor.class);
 
    @Override
    public void apply(RequestTemplate requestTemplate) {
        Map<String, String> headers = getHeaders();
        headers.forEach(requestTemplate::header);
    }
 
    private Map<String, String> getHeaders() {
        HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Map<String, String> map = new LinkedHashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        LOGGER.info("request Header:{}",map);
        return map;
    }
}

RPC LoadBalancer實現

public class GrayNacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(GrayNacosLoadBalancer.class);
 
    /**
     * 當前服務的名稱
     */
    private final String serviceId;
 
    /**
     * 負載均衡上游服務實例
     */
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
 
    /**
     * nacos配置信息
     */
    private final NacosDiscoveryProperties nacosDiscoveryProperties;
 
 
    public GrayNacosLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                 String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
    }
 
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier =
                serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get().next().mapNotNull(serviceInstances -> getInstanceResponse(serviceInstances, request));
    }
 
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances, Request request) {
        if (serviceInstances.isEmpty()) {
            LOGGER.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
 
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            List<ServiceInstance> instancesToChoose = serviceInstances;
            if (StringUtils.isNotBlank(clusterName)) {
                List<ServiceInstance> sameClusterInstances = serviceInstances.stream().filter(serviceInstance -> {
                    String cluster = serviceInstance.getMetadata().get("nacos.cluster");
                    return StringUtils.equals(cluster, clusterName);
                }).collect(Collectors.toList());
 
                //--是否為灰度請求
                if (isGrayRequest(request)) {
                    //--獲得灰度節點
                    sameClusterInstances =
                            sameClusterInstances.stream().filter(s -> s.getMetadata().get(SysConstants.KEY_GRAY_TAG) != null
                                    && s.getMetadata().get(SysConstants.KEY_GRAY_TAG).equals(SysConstants.VAL_GRAY_TAG)).collect(Collectors.toList());
                } else {
                    //--排除灰度節點
                    sameClusterInstances = sameClusterInstances.stream().
                            filter(s -> s.getMetadata().get(SysConstants.KEY_GRAY_TAG) == null ||
                                    !s.getMetadata().get(SysConstants.KEY_GRAY_TAG).equals(SysConstants.VAL_GRAY_TAG))
                            .collect(Collectors.toList());
                }
                if (!CollectionUtils.isEmpty(sameClusterInstances)) {
                    instancesToChoose = sameClusterInstances;
                }
            } else {
                LOGGER.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", serviceId,
                        clusterName, serviceInstances);
            }
 
            ServiceInstance instance = NacosBalancer.getHostByRandomWeight3(instancesToChoose);
            return new DefaultResponse(instance);
        } catch (Exception e) {
            LOGGER.warn("GrayNacosLoadBalancer error", e);
            return null;
        }
    }
 
    private boolean isGrayRequest(Request request) {
        RequestDataContext dataContext = (RequestDataContext) request.getContext();
        HttpHeaders headers = dataContext.getClientRequest().getHeaders();
        return headers.get(SysConstants.KEY_GRAY_TAG) != null && Objects.requireNonNull(headers.get(SysConstants.KEY_GRAY_TAG)).get(0).equals(SysConstants.VAL_GRAY_TAG);
    }
}

全鏈路灰度結構

企業微信截圖_17225789619280.png

一些說明
0,路由規則:灰度流量如果有灰度節點則走灰度節點,沒有灰度節點則走普通節點。而如果是正常流量則只能走普通節點。
1,外部流量通過APISIX網關,如果滿足gray=true條件,則認定為灰度流量。
2,APISIX要能提供灰度流量分割能力,將灰度流量負載和路由到第一個微服務中,同時也支持下游所有服務節點的對外服務路由配置。
3,微服務之間的流量透傳使用fegin攔截器實現,將所有的header信息透傳到下游的所有節點中。
4,如果當前微服務節點接受到了灰度流量,那么在spring cloud loadbalancer中要先從nacos注冊服務中獲得有gray標簽的節點,然后將這個灰度請求發往這個灰度節點中。
 5,灰度流量到達灰度節點,灰度節點完成灰度版本的業務邏輯,同樣按上一步的邏輯獲得下游的節點類型。如果是灰度請求則負載發往下游的灰度節點,否則直接發完正常的節點。

Demo演示

0,基礎環境準備

交付一套完整的K8S環境,基礎服務中需要有Nacos,APISIX,Rancher等基礎組件。
image.png

1,微服務部署

Demo服務請求鏈

企業微信截圖_17225790719713.png

流量通過APISIX請求dwuser,dwuser在通過dworder查詢訂單信息。在k8s上分別給dwuser和dworder部署base和gray版本。

實際操作

a)創建2個服務(dwuser,dworder),并打包成鏡像
image.png

b)k8s部署微服務

配置信息
image.png

k8s交付信息

Namespace:microservices
# 服務名稱,dwuser表示服務名,gray表示灰度版本,v1.0.1表示版本,普通版本名稱為dworder-release-v1.0.0
Name:dwuser-gray-v1.0.1
 
# 鏡像
Image:xxxx/dwuser:v1.0.0
 
# 環境變量
Environment Variables:
NACOS_USERNAME  NACOS_USERNAME 
NACOS_PASSWORD  NACOS_PASSWORD
LANG en_US.utf8
app.env dw_dev
 
# 注冊到Nacos的服務的元數據-服務節點版本
spring.cloud.nacos.discovery.metadata.version v1.0.1
 
# 注冊到Nacos的服務的元數據-服務是否為灰度節點
spring.cloud.nacos.discovery.metadata.gray true
 
spring.cloud.nacos.discovery.server-addr xxxx//xxxx:18848
JAVA_OPTS -Xms128M -Xmx128M -XX:AutoBoxCacheMax=20000 -XX:+AlwaysPreTouch -XX:-UseBiasedLocking -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=5 -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps

主要注意的配置項為spring.cloud.nacos.discovery.metadata.version和spring.cloud.nacos.discovery.metadata.gray。

c)查看服務是否注冊成功
image.png

d)配置APISIX服務路由
image.png

測試請求

正常請求
# 只有gray=true的才是灰度流量,其他的是正常流程
curl --request GET \
  --url xxxx://xxxx:xxxx/dwuser/v1/index/getUserInfo \
  --header 'Content-Type: application/json' \
  --header 'gray: true' \
  --header 'userid: 100' \
  --header 'version: 1'

image.png
可以看到version返回的字段均為base,表示訪問base版本的信息。

灰度請求
# 如果gray=false,路由到灰度的節點
curl --request GET \
  --url xxxx://xxxx:xxxx/dwuser/v1/index/getUserInfo \
  --header 'Content-Type: application/json' \
  --header 'gray: false' \
  --header 'userid: 100' \
  --header 'version: 1'

image.png

可以看到version返回的字段均為gray,表示訪問gray版本的信息。

總結

1,全鏈路灰度發布使用APISIX+NACOS+SpringCloud+GrayNacosLoadBalancer(RPC負載均衡和路由)+FeignRequestInterceptor(流量透傳)思路能在業務線公有云的場景下能落地實現。
2,如果需要做到完整的灰度發布,需要將網關換成APISIX。方便流量分割,染色,流量監控反饋等。
3,灰度發布需要后續大數據產品更精細化的管理和維護。
4,當前灰度發布方案只能針對微服務全鏈路的場景,如果需要存儲池支持,則需要做影子庫或影子表,不過如果租戶隔離做的OK,問題不大。
5,注意對外第一個網關也需要具備流量切割和負載均衡能力。

一些問題

1,數據庫等有狀態的服務和組件該如何做到全鏈路灰度?
 答:本文一開始就說了,主要針對的是無狀態微服務的全鏈路灰度發布,如果是數據庫比如MySQL這種有狀態存儲服務,業內主要提供的是影子表或者影子庫的思路,也就是創建一個新表,灰度完后再將數據同步到原表或者源庫中。而其他的有狀態的組件也需要做獨立的灰度支持,實現成本較高。

2,灰度的服務和版本建議留多久的時間?
 答:不行就是不行,行就是行。線上服務灰度的版本不建議超過1周,在灰度測試一段時間OK的情況下,盡快將灰度表示去掉作為普通節點使用,避免后續多個gray版本混亂了。

3,如果開發人員沒有把新版本標識位灰度就直接上線了會怎么樣?
 答:如果新版本沒有加上灰度標識,當前的灰度計算規則將直接把發布的版本作為普通的服務節點,所以說全鏈路灰度發布技術成熟,主要是開發規劃落實和推動。

4,服務節點是否為灰度版本的計算規則改如何確定?
 答:建議先不要太復雜,當前使用的是最簡單的spring.cloud.nacos.discovery.metadata加一個gray=true的tag方式,后續如果業務需要可以按版本,按多tag,甚至使用表達式引擎來處理。

5,全鏈路灰度使用場景有那些?
 答:a)大版本升級:所有版本或者2/3的服務要升級。建議使用單鏈路藍綠方式,使用全新的nacos/k8s新namespace。b)小版本或者bugfix,使用同一個namespace,然后使用流量染色的方式驗證版本。c)灰度測試,同樣可以對uid做染色,然后也可以使用一個灰度數據庫,線上驗證灰度流量。d)A/B測試流量分割,可以使用traffic-split方式做流量切割給少部分客戶做功能試用,和灰度測試場景差不多。

0條評論
0 / 1000
wanghg11
21文章數
4粉絲數
wanghg11
21 文章 | 4 粉絲
原創

微服務全鏈路灰度發布

2024-08-05 09:31:37
67
0

前言

現業務線公有云服務已經上生產,為保證后續公有云的穩定性,魯棒性,安全性,兼容性等,需提供業務線公有云微服務的全鏈路灰度發布思路,注意是微服務全鏈路灰度!!!?全鏈路灰度發布在業內已經是非常成熟的技術了,暫時沒有什么技術難點,主要是開發規劃落實和推動?,這里記錄下思考學習過程。

為何要灰度發布?

業務線公有云產品對外的目的就是為了賺錢,沒賺錢那公有云產品沒任何意義,所以影響賺錢的因素我們都應該考慮處理掉。業內有一個指標叫?SLA(Service-Level Agreement)?:服務等級協議是服務提供者對客戶一個服務承諾,評估一個產品是否可用的方法。一般有四個SLA指標,可用性、準確性、系統容量和延遲。SLA指標降低了,那產品不可用了就影響算錢了,產品總是不可用了客戶就要換產品了也影響賺錢。 因此保證系統的SLA指標是重中之重。?而可灰度和可監控是保證SLA的重要因素??。因此我們要做業務線產品公有云微服務線的灰度發布,且必須得做。

有那些版本發布方法

企業微信截圖_17225668346174.png

一些說明
 業內常見的版本發布方法有全量發布,滾動發布,藍綠發布,灰度發布(金絲雀發布)。業務線產品主要使用灰度發布方案實現公有云服務的生產微感發布。同時簡單介紹下其他的發布思路。

全量發布

企業微信截圖_17225671468764.png

一些說明
全量發布主要適用于簡單小型的內部服務,直接將所有的微服務和老版本升級到最新。屬有損發布。
優點:成本低,運維簡單粗暴。?適用于小型內部業務服務?。缺點:版本切換用戶感知大,升級過程中服務無法訪問,無法支持類似于公有云的對外客戶服務。

滾動發布

企業微信截圖_17225673297593.png

一些說明
滾動發布每次只升級一個或多個服務,如果業務觀察無問題,再重復這個過程,直到全部服務升級到新版本。屬有損發布。不過相對于全量發布,用戶感知變小,業務方可以手動保證服務最低限度對外服務。同樣只適用于內部的業務服務。
優點:運維成本較低,只有在新老版本切換的時間點才會客戶有感。缺點:?版本回滾麻煩。版本切換用戶感知較大?,需要人工去停止業務的流量,且難判斷停止的老版本節點是否還有流量。發布和回滾時間長,同樣有很大的升級風險。

藍綠發布

企業微信截圖_17225674405198.png

一些說明
藍綠部署老集群綠版本不停機,部署一套完整的藍集群新版本,用戶可以將所有流量全部遷移到藍集群新版本中,也可以通過流量染色實現流量在藍綠集群中切換。藍綠版本之間物理或者邏輯隔離,沒有子請求交互。屬無損發布。一般公有云單組件有狀態服務產品,比如國內某些廠商的存儲服務就使用藍綠部署的升級,不僅可以做到版本升級,還可以做到集群遷移,擴縮容等功能。
優點:?版本切換用戶基本無感?,藍綠版本可以做到流量快速微感切換回滾。運維版本發布方便。缺點:?成本較高,需要部署一套完整的集群?,如果是涉及到底層的有狀態存儲服務,還需要做到數據的遷移和同步對齊。同時對于小版本和bugfix不友好,無法支持DevOps快速的迭代開發。

灰度發布(金絲雀發布)

企業微信截圖_17225675094211.png

一些說明
灰度發布也交金絲雀發布,是一種是一種漸進式的軟件發布方式,它允許將新功能或更新逐步給一部分用戶試用,而不是一次性全部替換。屬無損發布。灰度發布也可以分為單鏈路發布和全鏈路發布,單鏈路發布理解為特殊的藍綠部署,流量可以按比例分割,如老集群80%,灰度集群20%。當灰度集群穩定了后,逐步將老集群的所有流量遷移到新集群中。單鏈路方案有一些缺陷,比如bugfix和小版本快速迭代,只需要更新一個或者多個服務,而不是全部。因此便有了全鏈路發布,全鏈路發布的思路可以允許運維開發人員只發布部分應用。適合復雜大型對外微服務。
優點:?全鏈路發布對客戶基本無感,運維版本發布非常方便?,且保存了穩定的老版本回滾快速,風險低。同時支持線上灰度測試,生產治理可控。缺點:成本較高,包括開發和資源成本,開發和運維流程需要嚴格約束。系統架構稍微復雜了,需更精細管理和維護,提高了管理和監控成本。

小結

上文簡單介紹了全量發布,滾動發布,藍綠部署,灰度發布4種發布思路。每一種發布思路都有適合的場景,?對于當前業務線公有云的微服務體系而言,全鏈路灰度發布是最好的選擇?。可以做到客戶基本無感,回滾方便,低風險,運維方便。下面著重介紹下全鏈路發布方案的思路。

灰度發布有什么好處?

企業微信截圖_17225677788287.png

一些說明
灰度發布的好處有:?用戶無感,提高系統穩定性,大大大減少了運維成本,發版不影響服務賺錢,白天也可以發布啦,用戶和業務灰度測試,無縫回滾?,提高公有云產品SLA, 快速體驗新產品idea,新版本影響面風險可控,迭代反饋循環,新版本影響面風險可控。下面重點說明下以下幾項
1,用戶無感:因為灰度發布會同時存在一個或多個版本,版本在快速切換的時候,不影響產品的正常使用。
2,大大大減少了運維成本:傳統的發布方法需要再凌晨做全量的配置文件,數據庫,微服務等發布操作,工作復雜且高風險,灰度發布存在網關流量控制,版本發布不用一股腦全部替換,可以先一個一個完成配置,數據庫,灰度服務發布后,然后使用apisix快速切換無感切換流量即可。
3,提高系統穩定性:灰度發布因為不會全量替換老版本,可以一個一個服務灰度切換,因此系統不會由于大量操作而導致奔潰而影響這個系統的穩定。
4,提高公有云產品SLA:因為灰度發布不會影響系統對外服務,保證了全年大數據公有云產品對外的服務時間。
5,白天也可以發布啦:傳統的發布方法因為會影響客戶使用,由于發布工作量大,風險發,因此一般在晚上發布。而由于灰度發布的流量可控性,白天也可以發布服務,不會影響線上用戶的使用。
6,用戶和業務灰度測試:灰度發布可以控制部分流量到灰度版本,因此可以使部分客戶作嘗鮮新的功能的同時也可以作用戶測試,測試人員也可以使用指定的賬號做線上灰度測試。線下測試版本,架構,配置,數據,依賴,負載等維度不同難以全面覆蓋線上場景,灰度測試能彌補這種情況,使產品更加的完善完整正確。
7,無縫回滾:灰度發布會存在多個版本,如果在用戶和業務灰度測試過程中發現bug,可以在網關層快速切換流量。
所以讓我們快速開始實現灰度發布吧~

灰度發布的一些方法

由于我們需要全鏈路灰度發布,因此基本上可以套模型,模型為:?注冊中心+API服務網關+微服務RPC負載均衡+微服務框架流量透傳??。基本上滿足這個模型才可以做到全鏈路灰度發布。
image.png

一些說明
image.png

灰度發布實現

由上文已確定使用APISIX+NACOS+SpringCloud+RpcLoadblance+流量透傳方式實現全鏈路灰度發布,下面記錄下詳細的實現細節。

全鏈路灰度的必要因素

企業微信截圖_17225787074500.png

一些說明
如果要實現全鏈路灰度發布,那么在技術層面需要支持如下幾點
1,API對外網關和RPC組件能負載均衡以及路由:如果API網關和RPC組件無法做到負載均衡和路由,那么無法將請求流量分發給灰度節點。
2,流量的標記信息和染色信息能全鏈路透傳:因為要做到全鏈路灰度,如果無法將流量的標記信息和染色信息透傳到整個鏈路,那將無法做到整個鏈路的灰度。
3,微服務節點能標記分組和區分版本:如果微服務節點無法標記,那么rpc路由將無法識別下游的節點是否為灰度節點,也就無法做到灰度發布。
4,請求流量能染色:流量如果無法做到染色,那當前節點將無法獲得下游的灰度節點。
5,請求流量有版本和灰度標記信息:請求流量又灰度標記和版本信息才能作為灰度判斷的參數。

全鏈路灰度技術細節

image.png

RpcLoadblance和流量透傳在業內有多種思路,主要有SDK,Javaagent(Sermant),以及自己編碼(推薦)。我們這里為了避免又引入其他組件,直接修改ddaf框架新增 MyFeignRequestInterceptor(流量透傳) 和 GrayNacosLoadBalancer(灰度路由和負載) 工具。

流量透傳實現

public class MyFeignRequestInterceptor implements RequestInterceptor {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(MyFeignRequestInterceptor.class);
 
    @Override
    public void apply(RequestTemplate requestTemplate) {
        Map<String, String> headers = getHeaders();
        headers.forEach(requestTemplate::header);
    }
 
    private Map<String, String> getHeaders() {
        HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Map<String, String> map = new LinkedHashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        LOGGER.info("request Header:{}",map);
        return map;
    }
}

RPC LoadBalancer實現

public class GrayNacosLoadBalancer implements ReactorServiceInstanceLoadBalancer {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(GrayNacosLoadBalancer.class);
 
    /**
     * 當前服務的名稱
     */
    private final String serviceId;
 
    /**
     * 負載均衡上游服務實例
     */
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
 
    /**
     * nacos配置信息
     */
    private final NacosDiscoveryProperties nacosDiscoveryProperties;
 
 
    public GrayNacosLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
                                 String serviceId, NacosDiscoveryProperties nacosDiscoveryProperties) {
        this.serviceId = serviceId;
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
    }
 
    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier =
                serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get().next().mapNotNull(serviceInstances -> getInstanceResponse(serviceInstances, request));
    }
 
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> serviceInstances, Request request) {
        if (serviceInstances.isEmpty()) {
            LOGGER.warn("No servers available for service: " + this.serviceId);
            return new EmptyResponse();
        }
 
        try {
            String clusterName = this.nacosDiscoveryProperties.getClusterName();
            List<ServiceInstance> instancesToChoose = serviceInstances;
            if (StringUtils.isNotBlank(clusterName)) {
                List<ServiceInstance> sameClusterInstances = serviceInstances.stream().filter(serviceInstance -> {
                    String cluster = serviceInstance.getMetadata().get("nacos.cluster");
                    return StringUtils.equals(cluster, clusterName);
                }).collect(Collectors.toList());
 
                //--是否為灰度請求
                if (isGrayRequest(request)) {
                    //--獲得灰度節點
                    sameClusterInstances =
                            sameClusterInstances.stream().filter(s -> s.getMetadata().get(SysConstants.KEY_GRAY_TAG) != null
                                    && s.getMetadata().get(SysConstants.KEY_GRAY_TAG).equals(SysConstants.VAL_GRAY_TAG)).collect(Collectors.toList());
                } else {
                    //--排除灰度節點
                    sameClusterInstances = sameClusterInstances.stream().
                            filter(s -> s.getMetadata().get(SysConstants.KEY_GRAY_TAG) == null ||
                                    !s.getMetadata().get(SysConstants.KEY_GRAY_TAG).equals(SysConstants.VAL_GRAY_TAG))
                            .collect(Collectors.toList());
                }
                if (!CollectionUtils.isEmpty(sameClusterInstances)) {
                    instancesToChoose = sameClusterInstances;
                }
            } else {
                LOGGER.warn("A cross-cluster call occurs,name = {}, clusterName = {}, instance = {}", serviceId,
                        clusterName, serviceInstances);
            }
 
            ServiceInstance instance = NacosBalancer.getHostByRandomWeight3(instancesToChoose);
            return new DefaultResponse(instance);
        } catch (Exception e) {
            LOGGER.warn("GrayNacosLoadBalancer error", e);
            return null;
        }
    }
 
    private boolean isGrayRequest(Request request) {
        RequestDataContext dataContext = (RequestDataContext) request.getContext();
        HttpHeaders headers = dataContext.getClientRequest().getHeaders();
        return headers.get(SysConstants.KEY_GRAY_TAG) != null && Objects.requireNonNull(headers.get(SysConstants.KEY_GRAY_TAG)).get(0).equals(SysConstants.VAL_GRAY_TAG);
    }
}

全鏈路灰度結構

企業微信截圖_17225789619280.png

一些說明
0,路由規則:灰度流量如果有灰度節點則走灰度節點,沒有灰度節點則走普通節點。而如果是正常流量則只能走普通節點。
1,外部流量通過APISIX網關,如果滿足gray=true條件,則認定為灰度流量。
2,APISIX要能提供灰度流量分割能力,將灰度流量負載和路由到第一個微服務中,同時也支持下游所有服務節點的對外服務路由配置。
3,微服務之間的流量透傳使用fegin攔截器實現,將所有的header信息透傳到下游的所有節點中。
4,如果當前微服務節點接受到了灰度流量,那么在spring cloud loadbalancer中要先從nacos注冊服務中獲得有gray標簽的節點,然后將這個灰度請求發往這個灰度節點中。
 5,灰度流量到達灰度節點,灰度節點完成灰度版本的業務邏輯,同樣按上一步的邏輯獲得下游的節點類型。如果是灰度請求則負載發往下游的灰度節點,否則直接發完正常的節點。

Demo演示

0,基礎環境準備

交付一套完整的K8S環境,基礎服務中需要有Nacos,APISIX,Rancher等基礎組件。
image.png

1,微服務部署

Demo服務請求鏈

企業微信截圖_17225790719713.png

流量通過APISIX請求dwuser,dwuser在通過dworder查詢訂單信息。在k8s上分別給dwuser和dworder部署base和gray版本。

實際操作

a)創建2個服務(dwuser,dworder),并打包成鏡像
image.png

b)k8s部署微服務

配置信息
image.png

k8s交付信息

Namespace:microservices
# 服務名稱,dwuser表示服務名,gray表示灰度版本,v1.0.1表示版本,普通版本名稱為dworder-release-v1.0.0
Name:dwuser-gray-v1.0.1
 
# 鏡像
Image:xxxx/dwuser:v1.0.0
 
# 環境變量
Environment Variables:
NACOS_USERNAME  NACOS_USERNAME 
NACOS_PASSWORD  NACOS_PASSWORD
LANG en_US.utf8
app.env dw_dev
 
# 注冊到Nacos的服務的元數據-服務節點版本
spring.cloud.nacos.discovery.metadata.version v1.0.1
 
# 注冊到Nacos的服務的元數據-服務是否為灰度節點
spring.cloud.nacos.discovery.metadata.gray true
 
spring.cloud.nacos.discovery.server-addr xxxx//xxxx:18848
JAVA_OPTS -Xms128M -Xmx128M -XX:AutoBoxCacheMax=20000 -XX:+AlwaysPreTouch -XX:-UseBiasedLocking -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=5 -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps

主要注意的配置項為spring.cloud.nacos.discovery.metadata.version和spring.cloud.nacos.discovery.metadata.gray。

c)查看服務是否注冊成功
image.png

d)配置APISIX服務路由
image.png

測試請求

正常請求
# 只有gray=true的才是灰度流量,其他的是正常流程
curl --request GET \
  --url xxxx://xxxx:xxxx/dwuser/v1/index/getUserInfo \
  --header 'Content-Type: application/json' \
  --header 'gray: true' \
  --header 'userid: 100' \
  --header 'version: 1'

image.png
可以看到version返回的字段均為base,表示訪問base版本的信息。

灰度請求
# 如果gray=false,路由到灰度的節點
curl --request GET \
  --url xxxx://xxxx:xxxx/dwuser/v1/index/getUserInfo \
  --header 'Content-Type: application/json' \
  --header 'gray: false' \
  --header 'userid: 100' \
  --header 'version: 1'

image.png

可以看到version返回的字段均為gray,表示訪問gray版本的信息。

總結

1,全鏈路灰度發布使用APISIX+NACOS+SpringCloud+GrayNacosLoadBalancer(RPC負載均衡和路由)+FeignRequestInterceptor(流量透傳)思路能在業務線公有云的場景下能落地實現。
2,如果需要做到完整的灰度發布,需要將網關換成APISIX。方便流量分割,染色,流量監控反饋等。
3,灰度發布需要后續大數據產品更精細化的管理和維護。
4,當前灰度發布方案只能針對微服務全鏈路的場景,如果需要存儲池支持,則需要做影子庫或影子表,不過如果租戶隔離做的OK,問題不大。
5,注意對外第一個網關也需要具備流量切割和負載均衡能力。

一些問題

1,數據庫等有狀態的服務和組件該如何做到全鏈路灰度?
 答:本文一開始就說了,主要針對的是無狀態微服務的全鏈路灰度發布,如果是數據庫比如MySQL這種有狀態存儲服務,業內主要提供的是影子表或者影子庫的思路,也就是創建一個新表,灰度完后再將數據同步到原表或者源庫中。而其他的有狀態的組件也需要做獨立的灰度支持,實現成本較高。

2,灰度的服務和版本建議留多久的時間?
 答:不行就是不行,行就是行。線上服務灰度的版本不建議超過1周,在灰度測試一段時間OK的情況下,盡快將灰度表示去掉作為普通節點使用,避免后續多個gray版本混亂了。

3,如果開發人員沒有把新版本標識位灰度就直接上線了會怎么樣?
 答:如果新版本沒有加上灰度標識,當前的灰度計算規則將直接把發布的版本作為普通的服務節點,所以說全鏈路灰度發布技術成熟,主要是開發規劃落實和推動。

4,服務節點是否為灰度版本的計算規則改如何確定?
 答:建議先不要太復雜,當前使用的是最簡單的spring.cloud.nacos.discovery.metadata加一個gray=true的tag方式,后續如果業務需要可以按版本,按多tag,甚至使用表達式引擎來處理。

5,全鏈路灰度使用場景有那些?
 答:a)大版本升級:所有版本或者2/3的服務要升級。建議使用單鏈路藍綠方式,使用全新的nacos/k8s新namespace。b)小版本或者bugfix,使用同一個namespace,然后使用流量染色的方式驗證版本。c)灰度測試,同樣可以對uid做染色,然后也可以使用一個灰度數據庫,線上驗證灰度流量。d)A/B測試流量分割,可以使用traffic-split方式做流量切割給少部分客戶做功能試用,和灰度測試場景差不多。

文章來自個人專欄
文章 | 訂閱
0條評論
0 / 1000
請輸入你的評論
0
0