在微服務架構體系下,隨着時間的推移,不免會碰到因爲前期服務粒度的劃分不能徹底知足後續需求的增加,形成"微"服務的二度拆分。拆分不可避免的會致使服務在整個系統中的鏈路發生變化,如何保證鏈路上游服務在無感知的狀況下完成鏈路下游服務的拆分,動態路由能夠幫助咱們解決這個問題。
先介紹一下服務架構設計java
基於spring cloud zuul
1.目標:實現一個通用的動態路由服務
2.參考:更深層次的探討,請進 傳送門
3.功能: 在zuul-gateway-demo
的基礎上,簡化了路由配置表;新增路由刷新策略保證細粒度的path優先路由;自動刷新頻率的配置化;mysql
<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>
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
啓動類 :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); }
自定義路由配置類: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 DESC
sql
時間刷新路由: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); } } }
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
先取後予,歡迎你們討論和補充!