Spring Cloud 網關服務 zuul 三 動態路由


zuul動態路由

網關服務是流量的唯一入口。不能隨便停服務。所以動態路由就顯得尤為必要。

數據庫動態路由基於事件刷新機制熱修改zuul的路由屬性。

DiscoveryClientRouteLocator

file

可以看到DiscoveryClientRouteLocator 是默認的刷新的核心處理類。


//重新加載路由信息方法 protected方法。需要子方法重新方法。
protected LinkedHashMap<String, ZuulRoute> locateRoutes() 

//觸發刷新的方法  RefreshableRouteLocator 接口
 public void refresh() {
			this.doRefresh();
	}

而這倆個方法都是繼承與SimpleRouteLocator 類,並進行了重新操作。其實官方的方法注釋說明了。如果需要動態讀取加載映射關系。則需要子類重寫這倆個方法。
進行具體的實現

首先pom jar包導入 需要連接mysql 數據庫

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!-- jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

路由實體 ZuulRouteEntity

package com.xian.cloud.entity;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * <Description> 路由實體類
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/10/30 15:00
 */
@Data
public class ZuulRouteEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * router Id
     */
    private Integer id;
    /**
     * 路由路徑
     */
    private String path;
    /**
     * 服務名稱
     */
    private String serviceId;
    /**
     * url代理
     */
    private String url;
    /**
     * 轉發去掉前綴
     */
    private String stripPrefix;
    /**
     * 是否重試
     */
    private String retryable;
    /**
     * 是否啟用
     */
    private String enabled;
    /**
     * 敏感請求頭
     */
    private String sensitiveheadersList;
    /**
     * 創建時間
     */
    private Date createTime;
    /**
     * 更新時間
     */
    private Date updateTime;
    /**
     * 刪除標識(0-正常,1-刪除)
     */
    private String delFlag;
}

新建DiscoveryRouteLocator 類 父類 接口 都不變化


package com.xian.cloud.router;


import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.xian.cloud.entity.ZuulRoute;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * <Description>
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/10/30 18:57
 */
@Slf4j
public class DiscoveryRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {

    private ZuulProperties properties;

    private JdbcTemplate jdbcTemplate;

    public DiscoveryRouteLocator(String servletPath, ZuulProperties properties, JdbcTemplate jdbcTemplate) {
        super(servletPath, properties);
        this.properties = properties;
        this.jdbcTemplate = jdbcTemplate;
        log.info("servletPath:{}",servletPath);
    }

    @Override
    public void refresh() {
        doRefresh();
    }

    @Override
    protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<String, ZuulProperties.ZuulRoute>();
        //從配置文件中加載路由信息
        routesMap.putAll(super.locateRoutes());
        //自定義加載路由信息
        routesMap.putAll(getRouteList());
        //優化一下配置
        LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
        for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            // Prepend with slash if not already present.
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (StringUtils.hasText(this.properties.getPrefix())) {
                path = this.properties.getPrefix() + path;
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }
            values.put(path, entry.getValue());
        }
        return values;
    }

    /**
     * 從數據庫讀取zuul路由規則
     * @return
     */
   private LinkedHashMap<String, ZuulProperties.ZuulRoute> getRouteList() {
        LinkedHashMap<String, ZuulProperties.ZuulRoute> zuulRoutes = new LinkedHashMap<>();
        List<ZuulRoute> sysZuulRoutes = jdbcTemplate.query("select * from sys_zuul_route where del_flag = 0", new BeanPropertyRowMapper<>(ZuulRoute.class));

       for (ZuulRoute route: sysZuulRoutes) {

           // 為空跳過
           if (Strings.isNullOrEmpty(route.getPath()) && Strings.isNullOrEmpty(route.getUrl())) {
               continue;
           }

           ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
           try {
               zuulRoute.setId(route.getServiceId());
               zuulRoute.setPath(route.getPath());
               zuulRoute.setServiceId(route.getServiceId());
               zuulRoute.setRetryable(Objects.equals("0", route.getRetryable()) ? Boolean.FALSE : Boolean.TRUE);
               zuulRoute.setStripPrefix(Objects.equals("0", route.getStripPrefix()) ? Boolean.FALSE : Boolean.TRUE);
               zuulRoute.setUrl(route.getUrl());
               List<String> sensitiveHeadersList = Arrays.asList(route.getSensitiveheadersList().split(","));
               if (sensitiveHeadersList != null) {
                   Set<String> sensitiveHeaderSet = Sets.newHashSet();
                   sensitiveHeadersList.forEach(sensitiveHeader -> sensitiveHeaderSet.add(sensitiveHeader));
                   zuulRoute.setSensitiveHeaders(sensitiveHeaderSet);
                   zuulRoute.setCustomSensitiveHeaders(true);
               }
           } catch (Exception e) {
               log.error("數據庫加載配置異常", e);
           }
           log.info("自定義的路由配置,path:{},serviceId:{}", zuulRoute.getPath(), zuulRoute.getServiceId());
           zuulRoutes.put(zuulRoute.getPath(), zuulRoute);

       }
        return zuulRoutes;
   }
}

我們還需要一個事件的生產者 和 消費者 直接圖方便 集成到一個類中

package com.xian.cloud.event;

import com.xian.cloud.router.DiscoveryRouteLocator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;

/**
 * <Description> 路由刷新事件發布,與事件監聽者
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/10/30 15:27
 */
@Service
public class RefreshRouteService implements ApplicationListener<ApplicationEvent> {

    @Autowired
    private ZuulHandlerMapping zuulHandlerMapping;

    private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();

    @Autowired
    ApplicationEventPublisher publisher;

    @Autowired
    private DiscoveryRouteLocator dynamicRouteLocator;

    /**
     * 動態路由實現 調用refreshRoute() 發布刷新路由事件
     */
    public void refreshRoute() {
        RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(dynamicRouteLocator);
        publisher.publishEvent(routesRefreshedEvent);
    }

    /**
     * 事件監聽者。監控檢測事件刷新
     * @param event
     */
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent){
            //主動手動刷新。上下文刷新,配置屬性刷新
            zuulHandlerMapping.setDirty(true);
        }else if(event instanceof HeartbeatEvent){
            //心跳觸發,將本地映射關系。關聯到遠程服務上
            HeartbeatEvent heartbeatEvent = (HeartbeatEvent)event;
            if(heartbeatMonitor.update(heartbeatEvent.getValue())){
                zuulHandlerMapping.setDirty(true);
            }
        }
    }
}

對外提供觸發接口

package com.xian.cloud.controller;

import com.xian.cloud.event.RefreshRouteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <Description> 手動刷新對外接口
 *
 * @author xianliru@100tal.com
 * @version 1.0
 * @createDate 2019/10/30 20:23
 */
@RestController
public class RefreshController {

    @Autowired
    private RefreshRouteService refreshRouteService;

    @GetMapping("/refresh")
    public String refresh() {
        refreshRouteService.refreshRoute();
        return "refresh";
    }

}

數據庫表結構

CREATE TABLE `sys_zuul_route` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'router Id',
  `path` varchar(255) NOT NULL COMMENT '路由路徑',
  `service_id` varchar(255) NOT NULL COMMENT '服務名稱',
  `url` varchar(255) DEFAULT NULL COMMENT 'url代理',
  `strip_prefix` char(1) DEFAULT '1' COMMENT '轉發去掉前綴',
  `retryable` char(1) DEFAULT '1' COMMENT '是否重試',
  `enabled` char(1) DEFAULT '1' COMMENT '是否啟用',
  `sensitiveHeaders_list` varchar(255) DEFAULT NULL COMMENT '敏感請求頭',
  `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
  `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  `del_flag` char(1) DEFAULT '0' COMMENT '刪除標識(0-正常,1-刪除)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='動態路由配置表'

file

將配置文件client 消費者服務 路由配置注釋掉。設置數據源。從數據庫中讀取

file

啟動服務打印日志

2019-10-30 20:49:39.946  INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator  : 添加數據庫自定義的路由配置,path:/client/**,serviceId:cloud-discovery-client
2019-10-30 20:49:40.397  INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator  : 添加數據庫自定義的路由配置,path:/client/**,serviceId:cloud-discovery-client

postman 請求client 接口 看看是否能轉發成功
file

基於zuul 動態網關路由完成。

后續還會更新網關的灰度方案、swagger2 整合的調試源服務。敬請期待!

摘自參考 spring cloud 官方文檔

參考書籍 重新定義spring cloud實戰

示例代碼地址

服務器nacos 地址 http://47.99.209.72:8848/nacos

往期地址 spring cloud alibaba 地址

spring cloud alibaba 簡介

Spring Cloud Alibaba (nacos 注冊中心搭建)

Spring Cloud Alibaba 使用nacos 注冊中心

Spring Cloud Alibaba nacos 配置中心使用

spring cloud 網關服務

Spring Cloud zuul網關服務 一

Spring Cloud 網關服務 zuul 二

如何喜歡可以關注分享本公眾號。
file

版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。轉載請附帶公眾號二維碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM