1. Spring Cloud + Nacos + 負載均衡器,實現(xiàn)全鏈路灰度發(fā)布的最佳實戰(zhàn)

        共 35257字,需瀏覽 71分鐘

         ·

        2024-06-21 08:42

        概念

        灰度發(fā)布, 也叫金絲雀發(fā)布。是指在黑與白之間,能夠平滑過渡的一種發(fā)布方式。AB test就是一種灰度發(fā)布方式,讓一部分用戶繼續(xù)用A,一部分用戶開始用B,如果用戶對B沒有什么反對意見,那么逐步擴大范圍,把所有用戶都遷移到B上面來。

        灰度發(fā)布可以保證整體系統(tǒng)的穩(wěn)定,在初始灰度的時候就可以發(fā)現(xiàn)、調(diào)整問題,以保證其影響度,而我們平常所說的金絲雀部署也就是灰度發(fā)布的一種方式。

        具體到服務(wù)器上,實際操作中還可以做更多控制,譬如說,給最初更新的10臺服務(wù)器設(shè)置較低的權(quán)重、控制發(fā)送給這10臺服務(wù)器的請求數(shù),然后逐漸提高權(quán)重、增加請求數(shù)。一種平滑過渡的思路, 這個控制叫做“流量切分”。

        組件版本說明

        我們這項目已經(jīng)練習了兩年半了使用的版本不是很新,我這里的Demo也會使用這個版本,有感情了,使用新版本的朋友自己調(diào)整一下就行,實現(xiàn)思路是一樣的只是這些框架源碼可能會有變化。

        • spring-boot: 2.3.12.RELEASE
        • spring-cloud-dependencies: Hoxton.SR12
        • spring-cloud-alibaba-dependencies: 2.2.9.RELEASE

        spring-cloud 對應(yīng)版本關(guān)系圖

        ?

        https://blog.csdn.net/weixin_44606481/article/details/131726688

        ?

        核心組件說明

        • 注冊中心: Nacos
        • 網(wǎng)關(guān): SpringCloudGateway
        • 負載均衡器: Ribbon (使用SpringCloudLoadBalancer實現(xiàn)也是類似的)
        • 服務(wù)間RPC調(diào)用: OpenFeign

        灰度發(fā)布代碼實現(xiàn)

        要實現(xiàn)Spring Cloud項目灰度發(fā)布技術(shù)方案有很多,重點在于服務(wù)發(fā)現(xiàn),怎么將灰度流量只請求到灰度服務(wù),這里我們會使用Nacos作為注冊中心和配置中心,核心就是利用Nacos的Metadata設(shè)置一個version值,在調(diào)用下游服務(wù)是通過version值來區(qū)分要調(diào)用那個版本,這里會省略一些流程,文章末尾提供了源碼地址需要自提。

        代碼設(shè)計結(jié)構(gòu)

        這個是demo項目,結(jié)構(gòu)都按最簡單的來。

        spring-cloud-gray-example // 父工程
           kerwin-common // 項目公共模塊
           kerwin-gateway // 微服務(wù)網(wǎng)關(guān)
           kerwin-order // 訂單模塊
              order-app // 訂單業(yè)務(wù)服務(wù)
           kerwin-starter // 自定義springboot starter模塊
              spring-cloud-starter-kerwin-gray // 灰度發(fā)布starter包 (核心代碼都在這里)
           kerwin-user // 用戶模塊
              user-app // 用戶業(yè)務(wù)服務(wù)
              user-client // 用戶client(Feign和DTO)
        核心包spring-cloud-starter-kerwin-gray結(jié)構(gòu)介紹
        入口Spring Cloud Gateway實現(xiàn)灰度發(fā)布設(shè)計(一些基礎(chǔ)信息類在下面)

        在請求進入網(wǎng)關(guān)時開始對是否要請求灰度版本進行判斷,通過Spring Cloud Gateway的過濾器實現(xiàn),在調(diào)用下游服務(wù)時重寫一個Ribbon的負載均衡器實現(xiàn)調(diào)用時對灰度狀態(tài)進行判斷。

        存取請求灰度標記Holder(業(yè)務(wù)服務(wù)也是使用的這個)

        使用ThreadLocal記錄每個請求線程的灰度標記,會在前置過濾器中將標記設(shè)置到ThreadLocal中。

        public class GrayFlagRequestHolder {
            /**
             * 標記是否使用灰度版本
             * 具體描述請查看 {@link com.kerwin.gray.enums.GrayStatusEnum}
             */

            private static final ThreadLocal<GrayStatusEnum> grayFlag = new ThreadLocal<>();
            public static void setGrayTag(final GrayStatusEnum tag) {
                grayFlag.set(tag);
            }
            public static GrayStatusEnum getGrayTag() {
                return grayFlag.get();
            }
            public static void remove() {
                grayFlag.remove();
            }
        }

        前置過濾器

        在前置過濾器中會對請求是否要使用灰度版本進行判斷,并且會將灰度狀態(tài)枚舉GrayStatusEnum設(shè)置到GrayRequestContextHolder中存儲這一個請求的灰度狀態(tài)枚舉,在負載均衡器中會取出灰度狀態(tài)枚舉判斷要調(diào)用那個版本的服務(wù),同時這里還實現(xiàn)了Ordered 接口會對網(wǎng)關(guān)的過濾器進行的排序,這里我們將這個過濾器的排序設(shè)置為Ordered.HIGHEST_PRECEDENCE int的最小值,保證這個過濾器最先執(zhí)行。

        public class GrayGatewayBeginFilter implements GlobalFilterOrdered {
            @Autowired
            private GrayGatewayProperties grayGatewayProperties;
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                GrayStatusEnum grayStatusEnum = GrayStatusEnum.ALL;
                // 當灰度開關(guān)打開時才進行請求頭判斷
                if (grayGatewayProperties.getEnabled()) {
                    grayStatusEnum = GrayStatusEnum.PROD;
                    // 判斷是否需要調(diào)用灰度版本
                    if (checkGray(exchange.getRequest())) {
                        grayStatusEnum = GrayStatusEnum.GRAY;
                    }
                }
                GrayFlagRequestHolder.setGrayTag(grayStatusEnum);
                ServerHttpRequest newRequest = exchange.getRequest().mutate()
                        .header(GrayConstant.GRAY_HEADER, grayStatusEnum.getVal())
                        .build();
                ServerWebExchange newExchange = exchange.mutate()
                        .request(newRequest)
                        .build();
                return chain.filter(newExchange);
            }

            /**
             * 校驗是否使用灰度版本
             */

            private boolean checkGray(ServerHttpRequest request) {
                if (checkGrayHeadKey(request) || checkGrayIPList(request) || checkGrayCiryList(request) || checkGrayUserNoList(request)) {
                    return true;
                }
                return false;
            }

            /**
             * 校驗自定義灰度版本請求頭判斷是否需要調(diào)用灰度版本
             */

            private boolean checkGrayHeadKey(ServerHttpRequest request) {
                HttpHeaders headers = request.getHeaders();
                if (headers.containsKey(grayGatewayProperties.getGrayHeadKey())) {
                    List<String> grayValues = headers.get(grayGatewayProperties.getGrayHeadKey());
                    if (!Objects.isNull(grayValues)
                            && grayValues.size() > 0
                            && grayGatewayProperties.getGrayHeadValue().equals(grayValues.get(0))) {
                        return true;
                    }
                }
                return false;
            }

            /**
             * 校驗自定義灰度版本IP數(shù)組判斷是否需要調(diào)用灰度版本
             */

            private boolean checkGrayIPList(ServerHttpRequest request) {
                List<String> grayIPList = grayGatewayProperties.getGrayIPList();
                if (CollectionUtils.isEmpty(grayIPList)) {
                    return false;
                }
                String realIP = request.getHeaders().getFirst("X-Real-IP");
                if (realIP == null || realIP.isEmpty()) {
                    realIP = request.getRemoteAddress().getAddress().getHostAddress();
                }
                if (realIP != null && CollectionUtils.contains(grayIPList.iterator(), realIP)) {
                    return true;
                }
                return false;
            }

            /**
             * 校驗自定義灰度版本城市數(shù)組判斷是否需要調(diào)用灰度版本
             */

            private boolean checkGrayCiryList(ServerHttpRequest request) {
                List<String> grayCityList = grayGatewayProperties.getGrayCityList();
                if (CollectionUtils.isEmpty(grayCityList)) {
                    return false;
                }
                String realIP = request.getHeaders().getFirst("X-Real-IP");
                if (realIP == null || realIP.isEmpty()) {
                    realIP = request.getRemoteAddress().getAddress().getHostAddress();
                }
                // 通過IP獲取當前城市名稱
                // 這里篇幅比較長不具體實現(xiàn)了,想要實現(xiàn)的可以使用ip2region.xdb,這里寫死cityName = "本地"
                String cityName = "本地";
                if (cityName != null && CollectionUtils.contains(grayCityList.iterator(), cityName)) {
                    return true;
                }
                return false;
            }

            /**
             * 校驗自定義灰度版本用戶編號數(shù)組(我們系統(tǒng)不會在網(wǎng)關(guān)獲取用戶編號這種方法如果需要可以自己實現(xiàn)一下)
             */

            private boolean checkGrayUserNoList(ServerHttpRequest request) {
                List<String> grayUserNoList = grayGatewayProperties.getGrayUserNoList();
                if (CollectionUtils.isEmpty(grayUserNoList)) {
                    return false;
                }
                return false;
            }

            @Override
            public int getOrder() {
                // 設(shè)置過濾器的執(zhí)行順序,值越小越先執(zhí)行
                return Ordered.HIGHEST_PRECEDENCE;
            }
        }

        后置過濾器

        后置過濾器是為了在調(diào)用完下游業(yè)務(wù)服務(wù)后在響應(yīng)之前將 GrayFlagRequestHolder 中的 ThreadLocal 清除避免照成內(nèi)存泄漏。

        public class GrayGatewayAfterFilter implements GlobalFilterOrdered {

            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 請求執(zhí)行完必須要remore當前線程的ThreadLocal
                GrayFlagRequestHolder.remove();
                return chain.filter(exchange);
            }

            @Override
            public int getOrder() {
                // 設(shè)置過濾器的執(zhí)行順序,值越小越先執(zhí)行
                return Ordered.LOWEST_PRECEDENCE;
            }
        }

        全局異常處理器

        全局異常處理器是為了處理異常情況下將 GrayFlagRequestHolder 中的 ThreadLocal 清除避免照成內(nèi)存泄漏,如果在調(diào)用下游業(yè)務(wù)服務(wù)時出現(xiàn)了異常就無法進入后置過濾器。

        public class GrayGatewayExceptionHandler implements WebExceptionHandlerOrdered {
            @Override
            public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
                // 請求執(zhí)行完必須要remore當前線程的ThreadLocal
                GrayFlagRequestHolder.remove();
                ServerHttpResponse response = exchange.getResponse();
                if (ex instanceof ResponseStatusException) {
                    // 處理 ResponseStatusException 異常
                    ResponseStatusException responseStatusException = (ResponseStatusException) ex;
                    response.setStatusCode(responseStatusException.getStatus());
                    // 可以根據(jù)需要設(shè)置響應(yīng)頭等
                    return response.setComplete();
                } else {
                    // 處理其他異常
                    response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
                    // 可以根據(jù)需要設(shè)置響應(yīng)頭等
                    return response.setComplete();
                }
            }

            @Override
            public int getOrder() {
                // 設(shè)置過濾器的執(zhí)行順序,值越小越先執(zhí)行
                return Ordered.HIGHEST_PRECEDENCE;
            }
        }

        自定義Ribbon負載均衡路由(業(yè)務(wù)服務(wù)也是使用的這個)

        「灰度Ribbon負載均衡路由抽象類:」 這里提供了兩個獲取服務(wù)列表的方法,會對GrayFlagRequestHolder 中存儲的當前線程灰度狀態(tài)枚舉進行判斷,如果枚舉值為GrayStatusEnum.ALL則響應(yīng)全部服務(wù)列表不區(qū)分版本,如果枚舉值為GrayStatusEnum.PROD則返回生產(chǎn)版本的服務(wù)列表,如果枚舉值為GrayStatusEnum.GRAY則返回灰度版本的服務(wù)列表,版本號會在GrayVersionProperties 中配置,通過服務(wù)列表中在Nacos的metadata中設(shè)置的versionGrayVersionProperties的版本號進行匹配出對應(yīng)版本的服務(wù)列表。

        public abstract class AbstractGrayLoadBalancerRule extends AbstractLoadBalancerRule {
            @Autowired
            private GrayVersionProperties grayVersionProperties;

            @Value("${spring.cloud.nacos.discovery.metadata.version}")
            private String metaVersion;

            /**
             * 只有已啟動且可訪問的服務(wù)器,并對灰度標識進行判斷
             */

            public List<Server> getReachableServers() {
                ILoadBalancer lb = getLoadBalancer();
                if (lb == null) {
                    return new ArrayList<>();
                }
                List<Server> reachableServers = lb.getReachableServers();

                return getGrayServers(reachableServers);
            }

            /**
             * 所有已知的服務(wù)器,可訪問和不可訪問,并對灰度標識進行判斷
             */

            public List<Server> getAllServers() {
                ILoadBalancer lb = getLoadBalancer();
                if (lb == null) {
                    return new ArrayList<>();
                }
                List<Server> allServers = lb.getAllServers();
                return getGrayServers(allServers);
            }

            /**
             * 獲取灰度版本服務(wù)列表
             */

            protected List<Server> getGrayServers(List<Server> servers) {
                List<Server> result = new ArrayList<>();
                if (servers == null) {
                    return result;
                }
                String currentVersion = metaVersion;
                GrayStatusEnum grayStatusEnum = GrayFlagRequestHolder.getGrayTag();
                if (grayStatusEnum != null) {
                    switch (grayStatusEnum) {
                        case ALL:
                            return servers;
                        case PROD:
                            currentVersion = grayVersionProperties.getProdVersion();
                            break;
                        case GRAY:
                            currentVersion = grayVersionProperties.getGrayVersion();
                            break;
                    }
                }

                for (Server server : servers) {
                    NacosServer nacosServer = (NacosServer) server;
                    Map<String, String> metadata = nacosServer.getMetadata();
                    String version = metadata.get("version");
                    // 判斷服務(wù)metadata下的version是否于設(shè)置的請求版本一致
                    if (version != null && version.equals(currentVersion)) {
                        result.add(server);
                    }
                }
                return result;
            }
        }

        「自定義輪詢算法實現(xiàn)GrayRoundRobinRule:」 代碼篇幅太長了這里只截取代碼片段,我這里是直接拷貝了Ribbon的輪詢算法,將里面獲取服務(wù)列表的方法換成了自定義AbstractGrayLoadBalancerRule 中的方法,其它算法也可以通過類似的方式實現(xiàn)。

        業(yè)務(wù)服務(wù)實現(xiàn)灰度發(fā)布設(shè)計

        自定義SpringMVC請求攔截器

        自定義SpringMVC請求攔截器獲取上游服務(wù)的灰度請求頭,如果獲取到則設(shè)置到GrayFlagRequestHolder 中,之后如果有后續(xù)的RPC調(diào)用同樣的將灰度標記傳遞下去。

        @SuppressWarnings("all")
        public class GrayMvcHandlerInterceptor implements HandlerInterceptor {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                String grayTag = request.getHeader(GrayConstant.GRAY_HEADER);
                // 如果HttpHeader中灰度標記存在,則將灰度標記放到holder中,如果需要就傳遞下去
                if (grayTag!= null) {
                    GrayFlagRequestHolder.setGrayTag(GrayStatusEnum.getByVal(grayTag));
                }
                return true;
            }
            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

            }
            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                GrayFlagRequestHolder.remove();
            }
        }

        自定義OpenFeign請求攔截器

        自定義OpenFeign請求攔截器,取出自定義SpringMVC請求攔截器中設(shè)置到GrayFlagRequestHolder中的灰度標識,并且放到調(diào)用下游服務(wù)的請求頭中,將灰度標記傳遞下去。

        public class GrayFeignRequestInterceptor implements RequestInterceptor {

            @Override
            public void apply(RequestTemplate template) {
                // 如果灰度標記存在,將灰度標記通過HttpHeader傳遞下去
                GrayStatusEnum grayStatusEnum = GrayFlagRequestHolder.getGrayTag();
                if (grayStatusEnum != null ) {
                    template.header(GrayConstant.GRAY_HEADER, Collections.singleton(grayStatusEnum.getVal()));
                }
            }
        }
        基礎(chǔ)信息設(shè)計

        這里會定義一些基礎(chǔ)參數(shù),比如是否開啟灰度還有什么請求需要使用灰度版本等,為后續(xù)業(yè)務(wù)做準備。

        • 調(diào)用業(yè)務(wù)服務(wù)時設(shè)置的灰度統(tǒng)一請求頭
        public interface GrayConstant {
            /**
             * 灰度統(tǒng)一請求頭
             */

            String GRAY_HEADER="gray";
        }
        • 灰度版本狀態(tài)枚舉
        public enum GrayStatusEnum {
            ALL("ALL","可以調(diào)用全部版本的服務(wù)"),
            PROD("PROD","只能調(diào)用生產(chǎn)版本的服務(wù)"),
            GRAY("GRAY","只能調(diào)用灰度版本的服務(wù)");
            GrayStatusEnum(String val, String desc) {
                this.val = val;
                this.desc = desc;
            }
            private String val;
            private String desc;
            public String getVal() {
                return val;
            }
            public static GrayStatusEnum getByVal(String val){
                if(val == null){
                    return null;
                }
                for (GrayStatusEnum value : values()) {
                    if(value.val.equals(val)){
                        return value;
                    }
                }
                return null;
            }
        }
        • 網(wǎng)關(guān)灰度配置信息類
        @Data
        @Configuration
        @RefreshScope
        @ConfigurationProperties("kerwin.tool.gray.gateway")
        public class GrayGatewayProperties {

            /**
             * 灰度開關(guān)(如果開啟灰度開關(guān)則進行灰度邏輯處理,如果關(guān)閉則走正常處理邏輯)
             * PS:一般在灰度發(fā)布測試完成以后會將線上版本都切換成灰度版本完成全部升級,這時候應(yīng)該關(guān)閉灰度邏輯判斷
             */

            private Boolean enabled = false;

            /**
             * 自定義灰度版本請求頭 (通過grayHeadValue來匹配請求頭中的值如果一致就去調(diào)用灰度版本,用于公司測試)
             */

            private String grayHeadKey="gray";

            /**
             * 自定義灰度版本請求頭匹配值
             */

            private String grayHeadValue="gray-996";

            /**
             * 使用灰度版本IP數(shù)組
             */

            private List<String> grayIPList = new ArrayList<>();

            /**
             * 使用灰度版本城市數(shù)組
             */

            private List<String> grayCityList = new ArrayList<>();

            /**
             * 使用灰度版本用戶編號數(shù)組(我們系統(tǒng)不會在網(wǎng)關(guān)獲取用戶編號這種方法如果需要可以自己實現(xiàn)一下)
             */

            private List<String> grayUserNoList = new ArrayList<>();
        }
        • 全局版本配置信息類
        @Data
        @Configuration
        @RefreshScope
        @ConfigurationProperties("kerwin.tool.gray.version")
        public class GrayVersionProperties {
            /**
             * 當前線上版本號
             */

            private String prodVersion;

            /**
             * 灰度版本號
             */

            private String grayVersion;
        }
        • 全局自動配置類
        @Configuration
        // 可以通過@ConditionalOnProperty設(shè)置是否開啟灰度自動配置 默認是不加載的
        @ConditionalOnProperty(value = "kerwin.tool.gray.load",havingValue = "true")
        @EnableConfigurationProperties(GrayVersionProperties.class)
        public class GrayAutoConfiguration 
        {
            @Configuration(proxyBeanMethods = false)
            @ConditionalOnClass(value = GlobalFilter.class)
            @EnableConfigurationProperties(GrayGatewayProperties.class)
            static class GrayGatewayFilterAutoConfiguration 
        {
                @Bean
                public GrayGatewayBeginFilter grayGatewayBeginFilter() {
                    return new GrayGatewayBeginFilter();
                }
                @Bean
                public GrayGatewayAfterFilter grayGatewayAfterFilter() {
                    return new GrayGatewayAfterFilter();
                }
                @Bean
                public GrayGatewayExceptionHandler grayGatewayExceptionHandler(){
                    return new GrayGatewayExceptionHandler();
                }
            }

            @Configuration(proxyBeanMethods = false)
            @ConditionalOnClass(value = WebMvcConfigurer.class)
            static class GrayWebMvcAutoConfiguration 
        {
                /**
                 * Spring MVC 請求攔截器
                 * @return WebMvcConfigurer
                 */

                @Bean
                public WebMvcConfigurer webMvcConfigurer() {
                    return new WebMvcConfigurer() {
                        @Override
                        public void addInterceptors(InterceptorRegistry registry) {
                            registry.addInterceptor(new GrayMvcHandlerInterceptor());
                        }
                    };
                }
            }
            @Configuration
            @ConditionalOnClass(value = RequestInterceptor.class)
            static class GrayFeignInterceptorAutoConfiguration 
        {
                /**
                 * Feign攔截器
                 * @return GrayFeignRequestInterceptor
                 */

                @Bean
                public GrayFeignRequestInterceptor grayFeignRequestInterceptor() {
                    return new GrayFeignRequestInterceptor();
                }
            }
        }

        項目運行配置

        這里我會啟動五個服務(wù),一個網(wǎng)關(guān)服務(wù)、一個用戶服務(wù)V1版本、一個訂單服務(wù)V1版本、一個用戶服務(wù)V2版本、一個訂單服務(wù)V2版本,來演示灰度發(fā)布效果。

        ?

        PS:Nacos的命名空間我這里叫spring-cloud-gray-example可以自己創(chuàng)建一個也可以換成自己的命名空間,源碼里面配置都是存在的,有問題看源碼就行

        ?
        配置Nacos全局配置文件(common-config.yaml)

        所有服務(wù)都會使用到這個配置

        kerwin:
          tool:
            gray:
              ## 配置是否加載灰度自動配置類,如果不配置那么默認不加載
              load: true
              ## 配置生產(chǎn)版本和灰度版本號
              version:
                prodVersion: V1
                grayVersion: V2

        ## 配置Ribbon調(diào)用user-app和order-app服務(wù)時使用我們自定義灰度輪詢算法
        user-app:
          ribbon:
            NFLoadBalancerRuleClassName: com.kerwin.gray.loadbalancer.GrayRoundRobinRule
        order-app:
          ribbon:
            NFLoadBalancerRuleClassName: com.kerwin.gray.loadbalancer.GrayRoundRobinRule
        配置網(wǎng)關(guān)Nacos配置文件(gateway-app.yaml)
        kerwin:
          tool:
            gray:
              gateway:
                ## 是否開啟灰度發(fā)布功能
                enabled: true
                ## 自定義灰度版本請求頭
                grayHeadKey: gray
                ## 自定義灰度版本請求頭匹配值
                grayHeadValue: gray-996
                ## 使用灰度版本IP數(shù)組
                grayIPList:
                  - '127.0.0.1'
                ## 使用灰度版本城市數(shù)組
                grayCityList:
                  - 本地
        啟動網(wǎng)關(guān)服務(wù)

        網(wǎng)關(guān)服務(wù)啟動一個就行,直接Debug啟動即可,方便調(diào)試源碼

        啟動業(yè)務(wù)服務(wù)V1 和 V2版本(用戶服務(wù)和訂單服務(wù)都用這種方式啟動)

        先直接Debug啟動會在IDEA這個位置看到一個對應(yīng)啟動類名稱的信息

        點擊Edit編輯這個啟動配置

        復制一個對應(yīng)啟動配置作為V2版本,自己將Name改成自己能區(qū)分的即可

        配置啟動參數(shù),第一步點擊Modify options 然后第二步將Add VM options勾選上,第三步填寫對應(yīng)服務(wù)的啟動端口和Nacos的metadata.version,我這里用戶服務(wù)V1版本配置為-Dserver.port=7201 -Dspring.cloud.nacos.discovery.metadata.version=V1,用戶服務(wù)V2版本配置為-Dserver.port=7202 -Dspring.cloud.nacos.discovery.metadata.version=V2,訂單服務(wù)配置類似,配置好后點Apply。

        最后啟動好的服務(wù)信息

        灰度效果演示

        源碼中的user-app提供了一個獲取用戶信息的接口并且會攜帶當前服務(wù)的端口和版本信息,order-app服務(wù)提供了一個獲取訂單信息的接口,會去遠程調(diào)用user-app獲取訂單關(guān)聯(lián)的用戶信息,并且也會攜帶當前服務(wù)的端口和版本信息響應(yīng)。

        場景一(關(guān)閉灰度開關(guān):不區(qū)分調(diào)用服務(wù)版本)

        關(guān)閉灰度開關(guān)有兩個配置可以實現(xiàn)

        1、在項目啟動之前修改Nacos全局配置文件中的kerwin.tool.gray.load 配置是否加載灰度自動配置類,只要配置不為true就不會加載整個灰度相關(guān)類

        2、關(guān)閉網(wǎng)關(guān)灰度開關(guān),修改網(wǎng)關(guān)Nacos配置文件中的kerwin.tool.gray.gateway.enabled ,只要配置不為true就不會進行灰度判斷。

        調(diào)用演示

        這里調(diào)用不一定就是Order服務(wù)版本為V1 User服務(wù)版本也為V1,也有可能Order服務(wù)版本為V1 User服務(wù)版本也為V2.

        • 第一次調(diào)用,Order服務(wù)版本為V1,User服務(wù)版本也為V1
        • 第二次調(diào)用,Order服務(wù)版本為V2,User服務(wù)版本也為V2
        場景二(開啟灰度開關(guān):只調(diào)用生產(chǎn)版本)

        修改網(wǎng)關(guān)Nacos配置文件中的kerwin.tool.gray.gateway.enabled 設(shè)置為true,其它灰度IP數(shù)組和城市數(shù)組配置匹配不上就行,這樣怎么調(diào)用都是V1版本,因為在GrayVersionProperties版本配置中設(shè)置的生產(chǎn)版本就是為V1灰度版本為V2。

        場景三(開啟灰度開關(guān):通過請求頭、ip、城市匹配調(diào)用灰度版本)

        這里通過請求頭測試,攜帶請求頭gray=gray-996訪問網(wǎng)關(guān)那么流量就會都進入灰度版本V2。

        源碼

        ?

        https://gitee.com/kerwin_code/spring-cloud-gray-example

        ?

        存在問題

        1、如果項目中使用到了分布式任務(wù)調(diào)度那怎么區(qū)分灰度版本

        這里其實挺好解決的,就拿xxl-job來說,注冊不同的執(zhí)行器就行,在發(fā)布灰度版本時注冊到灰度版本的執(zhí)行器即可。

        2、如果項目中使用的了MQ我們收發(fā)消息怎么控制灰度

        這里和解決分布式任務(wù)調(diào)度思想是一樣的灰度版本的服務(wù)發(fā)送消息的時候投遞到另外一個MQ的服務(wù)端,就是弄兩套MQ服務(wù)端,生產(chǎn)的服務(wù)使用生產(chǎn)的MQ,灰度發(fā)布使用灰度的MQ

        3、這里整個實現(xiàn)流程不是很復雜,但也是很沒必要,只是提供一種實現(xiàn)方案可以參考

        其實通過Nginx + Lua腳本方式直接路由網(wǎng)關(guān),然后給灰度整套服務(wù)都使用一個Nacos灰度的命名空間,生產(chǎn)的使用生產(chǎn)的命名空間,這樣就能將兩套服務(wù)都隔離了,分布式任務(wù)調(diào)度、MQ等配置都可以獨立在自己命名空間的配置文件中豈不美哉


        瀏覽 104
        1點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        1點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 男人j放进女人p | 久久 无码 一区二区三区四区 | 韩国精品一区二区三区无码视频 | 国产高清肏逼视频 | y111111少妇一区二区三区 |