本文使用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;
}
}
