概述
一、概述
随着微服务在各个项目中使用越来越普遍,大多数公司选择使用Spring Cloud框架或者Dubbo来实现项目落地。
因此,在说动态路由之前,笔者先带大家来对比下这两个框架。
- 从性能效率上来看:dubbo 使用了 netty 比 spring cloud 的 http 快很多。大概是 2 倍的差距。
- 从开发效率上来看:公司同学一般都会 spring,入门 spring cloud 的门槛低且与 spring 融合度更高。dubbo 相对会花一些学习成本。
- 从社区活跃度来看:spring cloud 的活跃度明显高于 dubbo。而后续 dubbo 看到了微服务的流行,也逐渐恢复了更新。
- 从生态圈上来看:spring cloud 周边组件很多,通过 starter 自动装配即拿即用。而 dubbo 得自己集成其他的框架。
笔者所在的公司选择的Spring Cloud技术栈,主要有以下两个原因 :
- 对于性能没有极致的要求。
- 想尽快产出,减少学习成本,增加开发效率,减少企业成本。
网关分为Zuul,Kong,Nginx代理以及自研四种,一般中小公司使用Zuul即可。接着我们来了解下网关的作用,主要包括:动态路由,灰度发布,鉴权认证,性能监控,限流熔断等。
二、搭建基础环境
首先我们得去搭建三个服务,分别为注册中心:eureka-server,网关中心:zuul-gateway,还有个就是应用服务,就以库存服务为例:inventory-api。
在网关中配置路由,传统的做法如下:
zuul:
prefix: /api # 添加路由前缀
retryable: true
sensitive-headers:
add-host-header: true
routes:
inventory-api:
path: /inventory/**
service: inventory-api
strip-prefix: true
试想,随着我们的应用可能越来越多,如果在生产环境通过暂停网关服务,修改网关的路由配置,接着上线,对于很多公司的业务来说,会有较大的影响。因此我们就得考虑改造Zuul网关,通过动态路由来将我们的服务实现路由配置。
我们先来在库存服务中,新增一个简单的API接口
package com.calvin.api;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Title DemoController
* @Description Demo测试
* @author calvin
* @date: 2020/3/11 4:59 PM
*/
@RestController
@RequestMapping("demo")
public class DemoController {
@GetMapping("test")
public String demoTest() {
return "SUCCESS";
}
}
先测试不通过路由访问:http://localhost:10020/demo/test
接着根据网关服务里面配置好的路由,来根据路由访问:http://localhost:10010/api/inventory/demo/test
三、改造网关服务
首先我们得先将刚才在网关服务里面配置的路由注释掉。
设计思路:网关服务定时去读取数据库中新定义的路由表,通过路由表中的数据,实现路由管理。
定义路由表:gateway_api_route,并插入一条数据,数据为inventory-api服务。
DROP TABLE IF EXISTS `gateway_api_route`;
CREATE TABLE `gateway_api_route` (
`id` tinyint(10) NOT NULL AUTO_INCREMENT,
`path` varchar(255) NOT NULL,
`service_id` varchar(50) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`retryable` tinyint(1) DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`strip_prefix` int(11) DEFAULT NULL,
`api_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;
INSERT INTO `gateway_api_route`(`id`, `path`, `service_id`, `url`, `retryable`, `enabled`, `strip_prefix`, `api_name`) VALUES (1, '/inventory/**', 'inventory-api', '', 1, 1, 1, 'inventory-api');
接着就是代码实现部分了。(使用通用tk-mapper操作数据库,详见代码)
(1)定义实体类
package com.calvin.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
@Data
@Entity
@Table(name = "gateway_api_route")
public class GatewayApiRoute {
@Id
@KeySql(useGeneratedKeys = true)
@GeneratedValue
private String id;
private String path;
private String serviceId;
private String url;
private boolean stripPrefix = true;
private Boolean retryable;
private Boolean enabled;
}
(2)获取路由配置信息
package com.calvin.config;
import com.calvin.entity.GatewayApiRoute;
import com.calvin.mapper.GatewayApiRouteMapper;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* 获取路由配置信息
*/
@Slf4j
public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
private ZuulProperties properties;
@Autowired
private GatewayApiRouteMapper gatewayApiRouteMapper;
public DynamicRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
this.properties = properties;
}
@Override
public void refresh() {
doRefresh();
}
/**
* 修改路由对象信息
* @return
*/
@Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>();
// 加载application.yml中的路由表
routesMap.putAll(super.locateRoutes());
// 加载db中的路由表
routesMap.putAll(locateRoutesFromDB());
// 统一处理一下路由path的格式
LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() + path;
if (!path.startsWith("/")) {
path = "/" + path;
}
}
log.info("path:{},value:{}", path, entry.getValue());
values.put(path, entry.getValue());
}
return values;
}
/**
* 从数据库读取zuul路由规则
* @return
*/
private Map<String, ZuulProperties.ZuulRoute> locateRoutesFromDB() {
Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<>();
GatewayApiRoute gatewayApiRoute = new GatewayApiRoute();
gatewayApiRoute.setEnabled(Boolean.TRUE);
// 查询所有生效的路由
List<GatewayApiRoute> results = gatewayApiRouteMapper.select(gatewayApiRoute);
for (GatewayApiRoute result : results) {
if (StringUtils.isEmpty(result.getPath())) {
continue;
}
if (StringUtils.isEmpty(result.getServiceId()) && StringUtils.isEmpty(result.getUrl())) {
continue;
}
ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
try {
BeanUtils.copyProperties(result, zuulRoute);
} catch (Exception e) {
e.printStackTrace();
}
routes.put(zuulRoute.getPath(), zuulRoute);
}
return routes;
}
}
(3)声明Bean
package com.calvin.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DynamicRouteConfiguration {
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private ServerProperties server;
@Bean
public DynamicRouteLocator routeLocator() {
return new DynamicRouteLocator(server.getServlet().getServletPrefix(), this.zuulProperties);
}
}
(4)定时从数据库和配置文件中读取配置
package com.calvin.task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 定时从数据库和配置文件中读取配置
*/
@Component
@Configuration
@EnableScheduling
public class RefreshRouteTask {
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
private RouteLocator routeLocator;
@Scheduled(fixedRate = 5000)
private void refreshRoute() {
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
}
工程启动后,可以看到每隔5秒打印最新的路由信息(路由来源:application.yml已经配置和路由表中)
(5)测试是否生效
浏览器依旧访问:http://localhost:10010/api/inventory/demo/test从结果可以看到 ,已经成功实现了路由从数据库中动态读取。
代码托管:https://gitee.com/calvin1993/distributed-exercise
最后
以上就是威武故事为你收集整理的基于数据库实现微服务动态路由的全部内容,希望文章能够帮你解决基于数据库实现微服务动态路由所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复