zuul 動態路由

前言

在微服務架構體系下,隨着時間的推移,不免會碰到因爲前期服務粒度的劃分不能徹底知足後續需求的增加,形成"微"服務的二度拆分。拆分不可避免的會致使服務在整個系統中的鏈路發生變化,如何保證鏈路上游服務在無感知的狀況下完成鏈路下游服務的拆分,動態路由能夠幫助咱們解決這個問題。
先介紹一下服務架構設計java

一 待拆分鏈路(open)

待拆分鏈路圖

二 拆分後鏈路

拆分後鏈路圖

三 動態路由

基於spring cloud zuul
1.目標:實現一個通用的動態路由服務
2.參考:更深層次的探討,請進 傳送門
3.功能: 在zuul-gateway-demo的基礎上,簡化了路由配置表;新增路由刷新策略保證細粒度的path優先路由;自動刷新頻率的配置化;mysql

四 dynamic-route

1.項目依賴

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-actuator</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>               
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>compile</scope>
	</dependency>

    <dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>1.1.1</version>
	</dependency>

	<!-- 數據庫 -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>${mysql.version}</version>
	</dependency>
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid</artifactId>
		<version>${alibaba.druid.version}</version>
	</dependency>
	
	<!-- spring cloud zuul -->
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-zuul</artifactId>
		<version>${spring.cloud.version}</version>
	</dependency>

</dependencies>

2 配置文件

application.ymlgit

zuul:
  host:
    socket-timeout-millis: 150000
    connect-timeout-millis: 150000
    max-total-connections: 200 # 路由最大鏈接數
    max-per-route-connections: 100 #每一個路由併發數
ribbon:
  eureka:
    enabled: false #不使用註冊中心
# 路由自動刷新頻率(分鐘)
ebtce:
  routeRefresh: 15

3 服務啓動

啓動類 :DynamicRouteApplicationgithub

//注入ZuulAutoFilter對象
@Bean
public ZuulAutoFilter zuulAutoFilter(){
	return new ZuulAutoFilter();
}

@Autowired
private ZuulRouteVoService zuulRouteVoService;

// 注入監聽線程對象
@Autowired
private MonitorThread monitorThread;

@Autowired
public void  setInitialize(){
     //讀取路由表,信息放入一個Map對象中
	zuulRouteVoService.initZuulRouteVoMap();
// 設置爲守護線程
	monitorThread.setDaemon(true);
	monitorThread.start();
}

public static void main(String[] args) {
// 啓動服務
	SpringApplication.run(DynamicRouteApplication.class, args);
// 控制監聽線程的狀態: 等待(0) 啓動(1) 退出(-1)
	MonitorThreadControl.setState(1);
}

4 zuul 路由表生成

自定義路由配置類:CustomZuulConfigweb

@Bean
    public CustomRouteLocator routeLocator(){
        CustomRouteLocator routeLocator = new CustomRouteLocator(serverProperties().getServletPrefix(), zuulProperties());
        return routeLocator;
    }

路由發現:CustomRouteLocatorspring

@Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes(){
	// 加載配置信息
	LinkedHashMap<String, ZuulRouteVo> routeLinkedHashMap = ZuulRouteVoService.ZUUL_ROUTE_VO_MAP;

	//優化一下配置
	LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
	log.info("========= dynamic route info ========== ");
	for(Map.Entry<String, ZuulRouteVo> entry : routeLinkedHashMap.entrySet()){
		String path = entry.getKey();
		if(!path.startsWith("/")){
			path = "/" + path;
		}

		if(StringUtils.hasText(this.properties.getPrefix())){
			path = this.properties.getPrefix();
			if(!path.startsWith("/")){
				path = "/" + path;
			}
		}

		ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
		BeanUtils.copyProperties(entry.getValue(), zuulRoute);
		values.put(path, zuulRoute);

		log.info(JSON.toJSONString(zuulRoute));
	}
//  路由信息裝配完成後,清除臨時存放路由信息的Map對象
	ZuulRouteVoService.clearRouteMap();
	return values;
}

在這裏咱們發現,zuul把路由信息寫到了一個LinkedHashMap對象中,路徑匹配時將循環LinkedHashMap對象,發現符合條件的路徑表達式即將對其進行路由,先匹配先得。爲保證細粒度的路徑優先匹配,保證LinkedHashMap對象初始化時粒度細的表達式在前便可。
SELECT CONCAT(id, '') as id, host, path from dynamic_route where is_read = 1 ORDER BY path DESCsql

5 動態刷新

時間刷新路由:RefreshRouteService數據庫

// 1 事件觸發路由刷新
public void refreshRoute(){
    // 讀取路由表信息
	zuulRouteVoService.initZuulRouteVoMap();
    //有須要刷新的路由才觸發刷新,若路徑已存在,刷新將被重寫
	if(!ZuulRouteVoService.ZUUL_ROUTE_VO_MAP.isEmpty()){
		RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
		publisher.publishEvent(routesRefreshedEvent);
		log.info("== dynamic route refresh success! ==");
	}else{
		log.info("== routing information is not updated ==");
	}
}

// 2. 刷新事件監聽線程
@Override
public void run(){
	while (true){
		int state = MonitorThreadControl.getState();
		if(state == 0)
			continue;

		if(state < 0){
			log.warn("============ MonitorThread EXIT  ===============");
			return;
		}

		try {

			// ensure that the last route refresh is completed
			if(ZuulRouteVoService.ZUUL_ROUTE_VO_MAP.isEmpty()){
				refreshRouteService.refreshRoute();
			}

		}catch (Exception e){
			log.error("", e);
		}

		try {
			Thread.sleep(refreshTime * (60 * 1000));
		} catch (InterruptedException e) {
		   log.error("", e);
		}
	}
}

6 路由數據模型

CREATE TABLE dynamic_route (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
host varchar(100) NOT NULL,
path varchar(256) NOT NULL,
is_read tinyint(1) NOT NULL DEFAULT '0' COMMENT '1:路由 0:不路由',
PRIMARY KEY (id),
UNIQUE KEY idx_path (path)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8

id:路由服務id
host:對應路由url
path:路由路徑表達式
is_read:是否讀取/路由,控制路徑的路由狀態mybatis

五 源碼獲取

dynamic-route架構

後記

先取後予,歡迎你們討論和補充!

相關文章
相關標籤/搜索