一. 架構設計
微服務架構圖
架構原理
1. 微服務系統在啟動時將自己注冊到服務注冊中心,同時對外發布 Http 接口供其它系統調用(一般都是基於Spring MVC)
2、服務消費者基於 Feign 調用服務提供者對外發布的接口,先對調用的本地接口加上注解@FeignClient,Feign會針對 加了該注解的接口生成動態代理,服務消費者會針對 Feign 生成的動態代理去調用方法時,在底層會生成Http協議格式的請求,類似 /stock/deduct?productId=100
3、Feign 最終會調用Ribbon從本地的Nacos注冊表的緩存里根據服務名取出服務提供在機器的列表,然后進行負載均衡 並選擇一台機器出來,對選出來的機器IP和端口拼接之前生成的url請求,生成類似調用http接口地址 http://192.168.0.60:9000/stock/deduct?productId=100,最后基於HTTPClient調用請求
基於微服務架構的原理,來設計灰度方案
概要流程:
1. 全局配置灰度是否啟用--在nacos中配置, 動態更新
2. 配置灰度規則, version=2.0, class="1234567" maxClassId="010-1234567"
3. 設置灰度服務器, 哪些服務器是灰度服務器。 為其打標簽
4. 啟動所有服務, 服務在nacos上進行注冊
5. 客戶端發起請求, 帶着header參數
6. zuul進行過濾,判斷是否符合灰度條件, 如果符合,打上灰度標簽
7. 通過feign將灰度標簽進行透傳
8. 通過ribbon選擇跳轉的服務器, 可以指定負載均衡策略
9. 下一個服務器繼續跳轉,帶上feign的灰度標簽,繼續請求。
以上是這個灰度方案實現的整體邏輯和思路
二. 具體操作及規划
2.1 灰度的目標
不同的流量過來, 根據元數據匹配, 走不同的微服務
當流量請求過來以后, 根據其匹配的灰度規則的不同, 走的服務有所不同, 可以將其分為三種類型.
1. 不匹配任何灰度規則, 則走無灰度服務
2. 匹配灰度規則, 則走對應的灰度服務
3. 同時匹配多個灰度規則, 選擇灰度服務
2.2 設置灰度規則
1. 全局灰度標簽設置在nacos中, nacos配置的灰度標簽的開閉, 可實時自動更新同步.
2. 灰度管理后台, 管理后台主要有兩大塊內容.
1) 配置灰度規則
1. 根據需要設置灰度規則, 比如: 城市, 大班, 小班, 版本號, 學科等,
2) 設置灰度服務器
1. 調用nacos接口, 獲取所有微服務ip+port
2. 為灰度服務器打灰度標簽
3. 做同步策略, 當灰度服務標簽內容有變化, 通知網關, 做相應更新
2.3. 網關設置--攔截請求, 為其打灰度標簽
網關其實就是各種各樣的過濾器, 常用的過濾器類型有:pre:前置過濾器, routing: 路由過濾器, post過濾器, error過濾器
這里我們定義一個前置過濾器, 過濾所有 過來的請求, 判斷其是否匹配灰度規則
執行步驟:
1. 初始化灰度規則, 我們首先判斷nacos中灰度規則是否啟用, 啟用則去灰度管理服務器獲取有效的灰度規則
2. 判斷請求頭是否和某一灰度規則匹配, 如果匹配, 則將請求header添加到請求上下文, 后續feign進行透傳. 同時添加到ribbon請求上下文, 做服務選擇.
4. ribbon設置 -- 根據灰度規則, 選擇灰度服務器
ribbon是客戶端負載均衡, 通過對ribbon上下文中的灰度標簽和微服務列表中灰度標簽的比較, 來選擇一台服務器, 作為目標跳轉服務器
5. 自定義Feign攔截器, 實現參數(灰度標簽)的透傳
feign的實質是攔截器, feign將攔截所有的請求跳轉, 主要作用是用來做header參數透傳, 保證服務間的調用也可以正確選擇灰度服務器.
三. 各組件功能原理
3.1 zuul網關
1. 標准的過濾器類型
pre:前置過濾器
在請求被路由到原服務器之前, 要執行的過濾器
- 認證 : 認證安全, 是否符合條件, 認證為安全的才能放過
- 選路由: 當前這個請求來了, 應該調用后面的哪個微服務呢? A還是B
- 請求日志: 請求日志, 日志來了, 寫日志, 對其進行監控
routing: 路由過濾器
處理將請求發送到源服務器的過濾器
post過濾器
在響應從源服務器返回時要被執行的過濾器
- 對響應增加http請求頭: 要增加調試的header日志
- 收集統計和度量: 這次請求, 它的性能如何, 有沒有出錯? 可以搜集一些信息
- 將響應以流的方式返回客戶端
error過濾器
2. 請求處理的生命周期
1) 請求過來了, 首先會進入一系列的前置過濾器pre filter.
2)前置過濾器處理完了, 進入routing filter路由過濾器, routing filter路由過濾器是真正的向后台服務發起請求, 接收響應的過濾器
3) 經過routing filter路由過濾器, 最后會傳遞過post filter 后置過濾器,進行一些后續的處理, 這時候已經拿到響應了, 然后在返回給客戶端.
4) 在這三個過濾器過濾的過程中,任何一個環節發生錯誤, 都會進入error filter. 有error filter進行統一的錯誤處理. error filter錯誤過濾器會發送給post filter, 也是以響應的方式發回給客戶端.
這是一個請求, 在網關處理的生命周期.
3. 自定義路由攔截器
這個過濾器extends ZuulFilter
package com.lxl.www.gateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; @Component public class TokenFilter extends ZuulFilter { @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 1; } @Override public boolean shouldFilter() { return true; } /** * 實現token 攔截驗證 * @return * @throws ZuulException */ @Override public Object run() throws ZuulException { return null; } }
這個過濾器extends自ZuulFilter, 后面的案例多了, 我們就發現, 其實zuul網關的本質就是攔截器, zuul的各種功能,也是通過攔截器來實現的
filterType() : 攔截器的類型是前置攔截器.
filterOrder(): 執行順序是第一個執行.
shouldFilter(): 過濾器執行的條件, 這里是所有的連接都需要過這個攔截器, 所以直接設置為true
run(): 攔截器的核心邏輯.
4. spring-cloud-zuul已經實現的過濾器
pre過濾器:
PreDecorationFilter ServletDetectionFilter FormBodyWrapperFilter DebugFilter Servlet30WrapperFilter
routing 過濾器
RibbonRoutingFilter SimpleHostRoutingFilter
post過濾器
SendResponseFilter SendForwardFilter
error過濾器
SendErrorFilter
3.2 ribbon
1. ribbon是一個客戶端負載均衡
ribbon的實現原理 原來我們的http請求是 http://ip:port/**** 使用ribbon: 需要使用項目名/ 那么也就是根據項目名 尋找一台服務 然后將項目名定位到一台服務的過程
2、 重新定義ribbon負載均衡策略
public class TheSameClusterPriorityRule extends AbstractLoadBalancerRule { @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * 主要實現choose方法 * */ @Override public Server choose(Object o) { }
3.3 feign
1、 feign也是客戶端負載均衡
Ribbon VS Feign
feign和ribbon是Spring Cloud的Netflix中提供的兩個實現軟負載均衡的組件,Ribbon和Feign都是用於 調用其他服務的,方式不同。Feign則是在Ribbon的基礎上進行了一次改進,采用接口的方式。 將需要調用的其他服務的方法定義成抽象方法即可,不需要自己構建 http 請求
2、 自定義feign攔截器
四.項目demo設計與規划
五.關鍵代碼實現
第一步: 啟動nacos服務.
nacos使用的是本地mysql數據庫, 而我們的mysql是放在docker上的,所以,
首先啟動mysql
docker start bdc382d8f7f8
然后啟動nacos,這里nacos使用的版本是1.2.1
./startup.sh -m standalone
在瀏覽器輸入http://localhost:8848/nacos, 登錄后進入nacos管理后台
本次我們使用的全局配置主要是global.yml
其實里面就定義了一個配置, 灰度標簽是否啟用. gray_enable. 如果標簽值為1, 表示啟用; 為0,表示不啟用. 這是一個整體的灰度規則
第二步: 啟動灰度管理后台 -- gray-admin
我們的管理后台是gray-admin. 按照上面的規划
1. 注冊到nacos, 並讀取nacos灰度管理標簽
2. 端口號設置為9000
3. bootstrap.yml配置文件
spring: cloud: nacos: config: server-addr: 127.0.0.1:8848 file-extension: yml namespace: 482c42bd-fba1-4147-a700-5b678d7c0747 group: ZUUL_TEST extension-configs[0]: data-id: global.yml group: GLOBAL_CONFIG
這里主要是golbal.yml配置文件
4. application.yml配置文件
server: port: 9000 spring: application: name: gray-admin datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/gray-admin?useUnicode=true&characterEncoding=utf-8 username: root password: 123456 # 服務模塊 devtools: restart: # 熱部署開關 enabled: true livereload: enabled: true # MyBatis mybatis: # 搜索指定包別名 typeAliasesPackage: com.lxl.admin # 配置mapper的掃描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/*Mapper.xml # 加載全局的配置文件 configLocation: classpath:mapper/mybatis-config.xml
通過配置文件, 我們看到, 引入了mysql,也就是最終灰度規則等信息是保存在mysql進行持久化
5. 接下來看一下mysql的數據結構
一共有兩張表: gray_rule和gray_relation
gray_rule: 保存的是灰度規則.
設計這張表的目的是: 對灰度規則進行統一管理. 目前都有哪些規則, 可以修改哪些規則
gray_relation: 保存的是服務器當前使用的灰度規則
設計這張表的目的是: 方便對服務器灰度規則進行管理. 比如服務器1, 當前使用的是什么規則? 已經對服務器進行立即灰度, 去灰, 斷流, 優雅停服等操作.
表一: gray_rule
表二: gray_relation
6. 管理后台頁面
管理后台需要啟動, 使用的是fslayui
進入項目的根目錄:
npm start
在頁面瀏覽器輸入一下地址
localhost:3000
a. 灰度規則管理頁面
新增灰度規則,編輯規則, 啟用/停用灰度規則,刪除灰度規則
b. 微服務灰度服務管理頁面--只顯示設置了灰度規則的實例
這里可以關聯灰度規則, 立即灰度, 服務灰度斷流,服務去灰,優雅停服
7. 下面我們按照上面的設計規划來給服務器設置灰度規則
一共啟動了這些服務,是不是很神奇, 我的電腦太強大了, 可以一下子其懂8個服務.
然后將端口號為8102, 8202, 8302設置為灰度服務器
首先:關聯灰度規則
然后點擊"立即灰度", 啟用灰度
查看nacos服務器, 我們可以看到已經給服務器打上了灰度標簽
這里的主要流程是, 通過頁面操作給服務打上灰度標簽.
3. 網關關鍵代碼實現
package com.lxl.credit.gray; import com.fasterxml.jackson.databind.ObjectMapper; import com.lxl.credit.client.GrayClient; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.*; import com.lxl.ribbon.config.*; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE; /** * 判斷請求是否應該走灰度服務器 */ public class GrayZuulFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(GrayZuulFilter.class); // 是否初始化過灰度規則, 全局只需要存一份 public static boolean initGray = false; // 是否啟用灰度規則, 全局存一份 //@Value("${gray_enable}") public static int grayEnable=1; @Autowired GrayClient grayClient; // 灰度規則 public static List<Map<String, String>> grayRules = new ArrayList<>(); private static ObjectMapper mapper = new ObjectMapper(); /* @Autowired GrayService grayService;*/ /** * 這是一個前置過濾器 * @return */ @Override public String filterType() { return PRE_TYPE; } /** * 過濾的順序是1 * @return */ @Override public int filterOrder() { return 1; } /** * 過濾器執行的條件 * 所有url鏈接全部需要走這個過濾器 * @return */ @Override public boolean shouldFilter() { return true; } /** * 過濾器執行的內容 * @return */ @Override public Object run() { // 第一步: 初始化灰度規則 if (!initGray) { //初始化灰度規則 getGrayRules(); } // 第二步: 獲取請求頭(包括請求的來源url和method) Map<String, String> headerMap = getHeadersInfo(); log.info("headerMap:{},grayRules:{}", headerMap, grayRules); // 刪除之前的路由到灰度的標記 /* if (RibbonFilterContextHolder.getCurrentContext().getAttributes().get(GrayConstant.GRAY_TAG) != null) { RibbonFilterContextHolder.getCurrentContext().remove(GrayConstant.GRAY_TAG); }*/ //灰度開關關閉 -- 無需走灰度, 執行正常的ribbon負載均衡轉發策略 if (grayEnable == 0) { log.info(">>>>>>>>>灰度開關已關閉<<<<<<<<<"); return null; } if (!grayRules.isEmpty()) { for (Map<String, String> grayRuleMap : grayRules) { try { // 獲取本次灰度的標簽,標簽的內容是灰度的規則內容 String grayTag = grayRuleMap.get(GrayConstant.GRAY_TAG); // 第三步: 過濾有效的灰度標簽 Map<String, String> resultGrayRuleMap = new HashMap<>(); //去掉值為空的灰度規則 grayRuleMap.forEach((K, V) -> { if (StringUtils.isNotBlank(V)) { resultGrayRuleMap.put(K, V); } }); resultGrayRuleMap.remove(GrayConstant.GRAY_TAG); //將灰度標簽(規則)小寫化 Map<String, String> lowerGrayRuleMap = transformUpperCase(resultGrayRuleMap); // 第四步: 判斷請求頭是否匹配灰度規則 if (headerMap.entrySet().containsAll(resultGrayRuleMap.entrySet()) || headerMap.entrySet().containsAll(lowerGrayRuleMap.entrySet())) { // 這是網關通訊使用的全局對象RequestContext RequestContext requestContext = RequestContext.getCurrentContext(); // 把灰度規則添加到網關請求頭, 后面的請求都可以使用該參數 requestContext.addZuulRequestHeader(GrayConstant.GRAY_HEADER, grayTag); // 將灰度規則添加到ribbon的上下文 RibbonFilterContextHolder.getCurrentContext().add(GrayConstant.GRAY_TAG, grayTag); log.info("添加灰度tag成功:lowerGrayRuleMap:{},grayTag:{}", lowerGrayRuleMap, grayTag); } } catch (Exception e) { log.error("灰度匹配失敗", e); } } } return null; } /** * 初始化灰度規則 */ private synchronized void getGrayRules() { try { if (!initGray) { // 未啟用灰度規則, 返回 if (grayEnable == 0) { initGray = true; return; } // 獲取在gray-admin-view中配置的所有可用的灰度規則 // 這里可能有多套灰度規則,所以是一個list // [{"areaCode":"010", "version": "1.0", "grayTag": "areaCode=010&version=1.0"}] grayRules = grayClient.getCurrentCrayRules(); initGray = true; } } catch (Exception e) { e.printStackTrace(); } } /** * 獲取header map * 將請求頭轉換成map, 同時增加-path參數和-method參數 * @return */ private Map<String, String> getHeadersInfo() { Map<String, String> map = new HashMap<>(); /** * 獲取請求的參數 */ RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); map.put("-path", String.valueOf(request.getRequestURL())); map.put("-method", request.getMethod()); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); map.put(key, value); } return map; } public static Map<String, String> transformUpperCase(Map<String, String> orgMap) { Map<String, String> resultMap = new HashMap<>(); if (orgMap == null || orgMap.isEmpty()) { return resultMap; } Set<String> keySet = orgMap.keySet(); for (String key : keySet) { String newKey = key.toLowerCase(); newKey = newKey.replace("_", ""); resultMap.put(newKey, orgMap.get(key)); } return resultMap; } }
這里的邏輯很清晰了
首先: 獲取灰度規則標簽. 什么時候獲取呢? 第一次請求過來的時候, 去請求灰度標簽. 放到全局的map集合中. 后面, 直接拿來就用
第二: 獲取請求過來的header, 和灰度規則進行匹配, 如果匹配上了, 那么打灰度標簽, 將其灰度請求頭添加到請求上下文, 同時添加到ribbon請求的上下文中
接下來, 走feign實現header透傳
4. feign關鍵代碼實現
package com.lxl.ribbon.interceptor; import com.alibaba.fastjson.JSON; import com.lxl.ribbon.config.RibbonFilterContextHolder; import com.lxl.ribbon.constants.GrayConstant; import feign.RequestInterceptor; import feign.RequestTemplate; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; @Component public class FeignRequestInterceptor implements RequestInterceptor { private static Logger log = LoggerFactory.getLogger(FeignRequestInterceptor.class); @Override public void apply(RequestTemplate requestTemplate) { RequestAttributes ra = RequestContextHolder.getRequestAttributes(); //處理特殊情況 if (null == ra) { return; } ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); //處理特殊情況 if (null == request) { return; } log.info("[feign攔截器] ribbon上下文屬性:{}", JSON.toJSONString(RibbonFilterContextHolder.getCurrentContext().getAttributes())); if (RibbonFilterContextHolder.getCurrentContext().getAttributes().get(GrayConstant.GRAY_TAG) != null) { RibbonFilterContextHolder.getCurrentContext().remove(GrayConstant.GRAY_TAG); } if (StringUtils.isNotBlank(request.getHeader(GrayConstant.GRAY_HEADER))) { log.info("灰度feign收到header:{}", request.getHeader(GrayConstant.GRAY_HEADER)); RibbonFilterContextHolder.getCurrentContext().add(GrayConstant.GRAY_TAG, request.getHeader(GrayConstant.GRAY_HEADER)); requestTemplate.header(GrayConstant.GRAY_HEADER, request.getHeader(GrayConstant.GRAY_HEADER)); } } }
其實feign的主要作用就是透傳, 為什么要透傳了呢? 微服務之間的請求, 不只是是首次定向的服務需要進行灰度, 那么后面服務內部相互調用也可能要走灰度, 那么最初請求的請求頭就很重要了. 要一直傳遞下去.
而requestTemplate.header(GrayConstant.GRAY_HEADER, request.getHeader(GrayConstant.GRAY_HEADER));就可以實現參數在整個請求進行透傳.
請求的參數帶好了, 下面就要進行服務選擇了, 有n台服務器, 到底要選擇哪台服務器呢? 就是ribbon的負載均衡選擇了
5. ribbon關鍵代碼實現
package com.lxl.ribbon.rules; import com.alibaba.cloud.nacos.NacosDiscoveryProperties; import com.alibaba.cloud.nacos.ribbon.NacosServer; import com.alibaba.nacos.api.naming.NamingService; import com.alibaba.nacos.api.naming.pojo.Instance; import com.lxl.ribbon.config.RibbonFilterContext; import com.lxl.ribbon.config.RibbonFilterContextHolder; import com.lxl.ribbon.constants.GrayConstant; import com.lxl.ribbon.loadbalance.WeightedBalancer; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.BaseLoadBalancer; import com.netflix.loadbalancer.Server; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.*; /** * 根據元數據進行灰度規則匹配 */ public class MetadataBalancerRule extends AbstractLoadBalancerRule { private static Logger log = LoggerFactory.getLogger(MetadataBalancerRule.class); private static Random r = new Random(); @Autowired private NacosDiscoveryProperties nacosDiscoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } /** * 實現父類的負載均衡規則 * * @param key * @return */ @Override public Server choose(Object key) { //return choose(getLoadBalancer(), key); try { // 調用父類方法, 獲取當前的負載均衡器 BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer(); //獲取當前的服務名 String serviceName = loadBalancer.getName(); log.info("[ribbon負載均衡策略] 當前服務名: {}", serviceName); //獲取服務發現客戶端 NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); // 獲取指定的服務實例列表 List<Instance> allInstances = namingService.getAllInstances(serviceName); log.info("[ribbon負載均衡策略] 可用的服務實例: {}", allInstances); if (allInstances == null || allInstances.size() == 0) { log.warn("沒有可用的服務器"); return null; } RibbonFilterContext context = RibbonFilterContextHolder.getCurrentContext(); log.info("MetadataBalancerRule RibbonFilterContext:{}", context.getAttributes()); Set<Map.Entry<String, String>> ribbonAttributes = context.getAttributes().entrySet(); /** * 服務分為三種類型 * 1. 設置為灰度的服務 --- 灰度服務 * 2. 先設置了灰度, 后取消了灰度的服務 --- 去灰服務 * 3. 普通服務-非灰服務 */ // 可供選擇的灰度服務 List<Instance> grayInstances = new ArrayList<>(); // 非灰服務 List<Instance> noneGrayInstances = new ArrayList<>(); Instance toBeChooseInstance; if (!context.getAttributes().isEmpty()) { for (Instance instance : allInstances) { Map<String, String> metadata = instance.getMetadata(); if (metadata.entrySet().containsAll(ribbonAttributes)) { log.info("進行灰度匹配,已匹配灰度服務:{},灰度tag為:{}", instance, context.getAttributes().get(GrayConstant.GRAY_TAG)); grayInstances.add(instance); } else if (!StringUtils.isBlank(metadata.get(GrayConstant.GRAY_TAG))) { // 非灰度服務 noneGrayInstances.add(instance); } } } log.info("[ribbon負載均衡策略] 灰度服務: {}, 非灰服務:{}", grayInstances, noneGrayInstances); // 如果灰度服務不為空, 則走灰度服務 if (grayInstances != null && grayInstances.size() > 0) { // 走灰度服務 -- 從本集群中按照權重隨機選擇一個服務實例 toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(grayInstances); log.info("[ribbon負載均衡策略] 灰度規則匹配成功, 匹配的灰度服務是: {}", toBeChooseInstance); return new NacosServer(toBeChooseInstance); } // 灰度服務為空, 走非斷灰的服務 if (noneGrayInstances != null && noneGrayInstances.size() > 0) { // 走非灰服務 -- 從本集群中按照權重隨機選擇一個服務實例 toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(noneGrayInstances); log.info("[ribbon負載均衡策略] 不走灰度, 匹配的非灰度服務是: {}", toBeChooseInstance); return new NacosServer(toBeChooseInstance); } else { log.info("未找到可匹配服務,實際服務:{}", allInstances); toBeChooseInstance = WeightedBalancer.chooseInstanceByRandomWeight(allInstances); log.info("[ribbon負載均衡策略] 未找到可匹配服務, 隨機選擇一個: {}", toBeChooseInstance); return new NacosServer(toBeChooseInstance); } } catch (Exception e) { e.printStackTrace(); } return null; } }
這里自定義實現了負載均衡策略. 首先判斷這個請求是否走灰度? 然后從服務器中選擇一台, 根據nacos的加權隨機原則, 隨機選擇一台服務器
6. order服務關鍵代碼
package com.lxl.order.controller; import com.lxl.order.client.CreditClient; import com.lxl.order.client.StockClient; import com.lxl.order.client.WmsClient; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @Slf4j @RestController @RequestMapping("order/") public class OrderController { @Autowired private CreditClient creditClient; @Autowired private StockClient stockClient; @Autowired private WmsClient wmsClient; @GetMapping("create") public String createOrder() { // 創建一個訂單 log.info("[創建了一個訂單]"); // 通知庫存減庫存 stockClient.reduceCredit(); // 通知積分系統加積分 creditClient.addCredit(); // 通知倉儲系統發貨 // wmsClient.pull(); return "訂單創建完畢"; } }
order就是一個請求, 他通過feign調用了其他服務.
7.stock關鍵代碼實現
下面我們模擬一個請求調用
package com.lxl.admin.controller; import com.lxl.admin.client.CreditClient; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j @RequestMapping("stock/") public class StockController { @Autowired private CreditClient creditClient; @PostMapping("reduce") public String reduceCredit() { log.info("[減庫存] 庫存減1"); // 通知積分系統加積分 creditClient.addCredit(); return "減庫存完成"; } }
這里就模擬了加積分分操作
也就是訂單請求進來,調用了庫存服務, 庫存服務又調用了積分服務. 我們來觀察是否會都走灰度服務器
8. 整體調用流程及效果
下面我們模擬一個請求調用
as