本文使用zuul網關實現灰度發布,包括了網關到服務、服務到服務的灰度。項目gitee:https://gitee.com/menbbo/gray-demo.git
服務部署可分為三種方式
1)藍綠發布
藍綠發布是通過冗余的方式來解決部署問題,生產環境為綠色配置,冗余的服務為藍色配置。在部署服務時,首先在冗余服務器上部署最新代碼,由部分用戶使用,
若使用沒有問題,則通過負載均衡將所有用戶請求轉發到冗余服務器中,即冗余的服務轉變為生產環境服務。優點是無需停機部署,服務回滾方便。缺點耗費服務器資源。
2)滾動發布
滾動發布指每次只部署一個或多個服務,直到服務部署完成為止。優點:用戶無感知,平滑過渡;相比藍綠發布節省服務器資源。缺點:部署復雜,且時間長;遇到
問題回滾比較復雜。
3)灰度發布
只升級部分服務,讓少量用戶訪問新部署的服務,其他用戶使用老服務,用戶反饋無誤后,整個集群部署,將用戶遷移到新服務上來。優點:在灰度時即可發現問題及
時處理,保證系統穩定性;如果出現問題,影響范圍小;用戶無感知,過渡平滑。
灰度發布實現步驟:
1)定義規則:哪些用戶可以訪問灰度環境,比如按百分比(10%的用戶可以訪問灰度),或讓固定用戶先體驗灰度環境;
2)利用網關實現路由策略,即網關到服務的路由;
3)服務與服務之間的調用使用ribbon實現灰度規則。
代碼實現
代碼使用zuul網關實現,項目包括了zuul、im、search三個服務,im服務調用search服務,具體實現如下。
1.引入maven依賴,關鍵依賴
<dependency> <groupId>io.jmnarloch</groupId> <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> <version>1.4.6.RELEASE</version> </dependency>
2.application.properties配置文件進行配置
server.port=9090 spring.application.name=zuul #注冊中心 eureka.client.service-url.defaultZone=http://localhost:8761/eureka eureka.instance.prefer-ip-address=true eureka.client.registerWithEureka=true #zuul網關路由 前綴 zuul.routes.prefix=/zuul zuul.routes.im.path=/im/** #im代表自定義服務 zuul.routes.im.service-id=im #false不會截取 true截取前綴 zuul.routes.im.stripPrefix=true #http://localhost:9090/zuul/im/index
3.GrayFilter過濾器實現網關到服務的灰度規則
@Component public class GrayFilter extends ZuulFilter { private static final String GRAY = "gray"; @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { //是否開啟過濾 return true; } @Override public Object run() throws ZuulException { //實現灰度邏輯 //前端在請求頭中攜帶灰度標識字段 //服務注冊時加入metadata數據,代表該服務節點為灰度節點 //首先從頭部中獲取標識 RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); String header = request.getHeader("gray_header"); //將灰度的請求轉發到meataData中forward為1的服務 if(StringUtils.equals(header,GRAY)){ RibbonFilterContextHolder.getCurrentContext().add("forward","1"); }else { RibbonFilterContextHolder.getCurrentContext().add("forward","2"); } return null; } }
4.im、search服務啟動時注冊metadata到注冊中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ eureka.client.enabled=true #eureka.instance.hostname=localhost #eureka.instance.instance-id=im #灰度端口 server.port=8081 #生產端口 #server.port=8082 spring.application.name=im #灰度發布需要metadata #灰度為1 eureka.instance.metadata-map.forward=1
eureka.client.service-url.defaultZone=http://localhost:8761/eureka server.port=8089 spring.application.name=search #灰度為1 eureka.instance.metadata-map.forward=1
5.實現服務到服務的灰度規則
/** * 定義服務間灰度調用規則 */ @Component public class GrayRule extends AbstractLoadBalancerRule { private static final String GRAY = "gray"; private static final String GRAY_HEADER = "forward"; private static final String GRAY_VALUE = "1"; private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public void initWithNiwsConfig(IClientConfig iClientConfig) { } @Override public Server choose(Object o) { ILoadBalancer loadBalancer = getLoadBalancer(); return this.choose(loadBalancer); } private Server choose(ILoadBalancer lb){ Server server = null; if (server==null){
//獲取請求頭中的參數,具體實現在gitee Map<String, String> stringStringMap = GrayRibbonParamater.get(); String grayParmater = null; if(stringStringMap!=null){ grayParmater = stringStringMap.get("gray_header"); } //獲得可到達的服務 List<Server> reachableServers = lb.getReachableServers(); for (Server reachableServer : reachableServers) { //獲取服務的metadata Map<String, String> metadata = ((DiscoveryEnabledServer) reachableServer).getInstanceInfo().getMetadata(); if(StringUtils.equals(metadata.get(GRAY_HEADER),GRAY_VALUE)&&StringUtils.equals(grayParmater,GRAY)){ return reachableServer; } } } return server; } }