微服務中,新版服務上線的時候,為了保證不出什么問題,可以將少量的請求轉發到新的服務上,然后其他的請求還是轉發到舊的服務上去,等線上的新服務測試通過以后,就可以重新平均分配請求。這種功能就稱為灰度發布。
要完成灰度發布,要做的就是修改ribbon的負載均衡策略,通過一些特定的標識,比如我們針對某個接口路徑/gray/publish/test。將10%的請求轉發到新的服務上,將90%的請求轉發到舊的服務上,諸如此類,我們可以制定各種規則進行灰度測試。
在微服務中,我們可以通過eureka的metamata進行自定義元數據,然后來修改ribbon的負載均衡策略。
在此,可以先通過代碼進行簡單地灰度發布,在實際應用中,可以通過數據庫配置進行灰度靈活發布。
首先,我們如果要部署新版的服務user-server,我們用不同的端口啟動這個服務,並配置不同的自定義元數據,配置如下:
server: port: 8181 #Eureka注冊配置 eureka: client: serviceUrl: defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/ instance: metadata-map: forward: 1
server: port: 8182 #Eureka注冊配置 eureka: client: serviceUrl: defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/ instance: metadata-map: forward: 2
我們啟動了8181和8182兩個服務,並分別定義了元數據forward: 1和forward: 2,然后,開始修改zuul網關,首先導入修改ribbon負載均衡策略的依賴
<!-- 修改Zuul的負載均衡策略,也就是進行灰度發布使用的 --> <dependency> <groupId>io.jmnarloch</groupId> <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId> <version>2.1.0</version> </dependency>
然后,開始修改ribbon的負載均衡策略
package com.dkjk.gateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.*; /** * @author: qjc * @createTime: 2020/7/29 * @Description: 接口安全驗證過濾器 */ @Component @Slf4j public class ValidFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { // 進行跨域請求的時候,並且請求頭中有額外參數,比如token,客戶端會先發送一個OPTIONS請求來探測后續需要發起的跨域POST請求是否安全可接受 // 所以這個請求就不需要攔截,下面是處理方式 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); if (request.getMethod().equals(RequestMethod.OPTIONS.name())) { log.info("OPTIONS請求不做攔截操作"); return false; } return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String requestURI = request.getRequestURI(); if (requestURI.contains("/gray/publish/test")){ int send = (int) (Math.random() * 100); if (send >= 0 && send < 10) { //也就是百分之10的請求轉發到forward=1的服務上去 RibbonFilterContextHolder.getCurrentContext().add("forward", "1");//這句話就代表將請求路由到metadata-map里forward為1的那個服務 } else { //百分之90的請求轉發到forward=2的服務上去 RibbonFilterContextHolder.getCurrentContext().add("forward", "2");//這句話就代表將請求路由到metadata-map里forward為2的那個服務 } } return null; } }
接下來就可以測試接口/gray/publish/test的轉發情況了,結果就是10%的請求轉發到了8181上(即forward為1的服務上),90%的請求轉發到了8182(即forward為2的服務上)
PS:在生產上使用的時候,可以通過創建數據庫表來動態配置負載均衡策略,比如創建一張數據表:
CREATE TABLE `gray_release_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `server_name` varchar(255) DEFAULT NULL, //服務名 `path` varchar(255) DEFAULT NULL,//需要進行灰度發布的接口路徑 `percent` int(11) DEFAULT NULL,//負載均衡策略,百分之percent的請求轉發到forward上 `forward` int(11) DEFAULT NULL,//自定義元數據值 PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
然后每隔一段時間同步配置表中的信息,也就是寫個定時任務。