使用网关zuul完成灰度发布
环境
java 1.8
springboot 2.3.0.RELEASE
spring-cloud.version Hoxton.SR5
背景:实现 zuul->服务 的灰度发布,实现不同的用户固定访问不同的服务
思路:使用zuul的过滤器Filter,在路由的时候根据灰度规则,选择一个合适的服务
zuul服务配置
pom.xml配置如下
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.dandan</groupId> <artifactId>cloud-zuul</artifactId> <version>0.0.1-SNAPSHOT</version> <name>cloud-zuul</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR5</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--zuul --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- mysql:MyBatis相关依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- mysql:mysql驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mysql:阿里巴巴数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <!-- 实现通过 metadata 进行灰度路由 --> <dependency> <groupId>io.jmnarloch</groupId> <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId> <version>2.1.0</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.yml配置
server: port: 9100 spring: application: name: cloud-zuul #数据库连接配置 datasource: #配置当前使用的数据源的操作类型 type: com.alibaba.druid.pool.DruidDataSource #配置MySQL的驱动程序类 driver-class-name: com.mysql.cj.jdbc.Driver #数据库连接地址 url: jdbc:mysql://192.168.1.113:3306/online-taxi-three?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai #数据库连接用户名 username: root #数据库连接密码 password: 123456 #进行数据库连接池的配置 dbcp2: #初始化提供的连接数 initial-size: 5 #数据库连接池的最小维持连接数 min-idle: 5 #最大的连接数 max-total: 5 #等待连接获取的最大超时时间 max-wait-millis: 200 validation-query: SELECT 1 test-while-idle: true test-on-borrow: false test-on-return: false #mybatis配置 mybatis: mapper-locations: - classpath:mapper/*.xml eureka: client: service-url: defaultZone: http://eureka-7900:7900/eureka instance: hostname: localhost instance-id: online-taxi-zuul
在启动类开启网关代理服务
@SpringBootApplication @EnableZuulProxy public class CloudZuulApplication { public static void main(String[] args) { SpringApplication.run(CloudZuulApplication.class, args); } }
zuul过滤器部分代码
import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; /** * @version 1.0 * @author: zheaven * @date: 2021/11/3 13:30 */ @Component public class GrayFilter extends ZuulFilter { @Override public String filterType() { return FilterConstants.ROUTE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() throws ZuulException { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); int userId = Integer.parseInt(request.getHeader("userId")); // 根据用户id 查 规则 查库 meta_version=v1, metadata-map // 1,2是从数据库中查出来的 // 金丝雀 if (userId == 1){ RibbonFilterContextHolder.getCurrentContext().add("version","v1"); // 普通用户 }else if (userId == 2){ RibbonFilterContextHolder.getCurrentContext().add("version","v2"); } return null; } }
数据库sql
CREATE TABLE `common_gray_rule` ( `id` int(16) NOT NULL, `user_id` int(16) DEFAULT NULL, `service_name` varchar(32) DEFAULT NULL, `meta_version` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='模拟灰度发布'; INSERT INTO `common_gray_rule`(`id`, `user_id`, `service_name`, `meta_version`) VALUES (1, 1, 'service-sms', 'v1');
网关zuul对应要访问的服务 service-sms
pom.xml配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
application.yml配置
# 可以在http://localhost:7900/eureka/apps上查到metadata-map里面的自定义标签数据 # 在idea中配置两台service-sms服务,只为了方便启动,如果想配置成两个单独的服务,可稍加修改 --- spring: profiles: v1 server: port: 8091 eureka: instance: metadata-map: version: v1 a: a1 --- spring: profiles: v2 server: port: 8092 eureka: instance: metadata-map: version: v2 a: a2 --- spring: application: name: service-sms eureka: client: service-url: # 默认从第一个server拉取注册表,失败后找第二台,重试次数为3次,配置的第四个server无效 # 建议每个服务的server顺序不一致,防止第一个server压力过大 defaultZone: http://localhost:7900/eureka #,http://localhost:7901/eureka,http://localhost:7902/eureka # 从server拉取注册表的间隔时间 registry-fetch-interval-seconds: 30 # 是否向eureka服务器注册信息,默认是true enabled: true instance: # client续约的间隔时间,默认是30s lease-renewal-interval-in-seconds: 30
启动类
@SpringBootApplication public class ServiceSmsApplication { public static void main(String[] args) { SpringApplication.run(ServiceSmsApplication.class, args); } }
测试controller类
package com.dandan.servicesms.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController @RequestMapping("/test") public class ServiceSmsTestController { @Value("${server.port}") private String port; @GetMapping("/sms-test") public String test(){ return "sms-test:"+port; } }
cloud-eureka服务
pom.xml配置
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.0.RELEASE</version> <relativePath/> </parent> <groupId>com.dandan</groupId> <artifactId>cloud-eureka</artifactId> <version>0.0.1-SNAPSHOT</version> <name>cloud-eureka</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR4</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.yml
这里是多个eureka配置,启动一个即可
spring:
application:
name: cloud-eureka
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
# 两个节点集群搭建只写对方的访问路径
# 三个节点以上的集群搭建必须全部写上
# eureka集群搭建 https://docs.spring.io/spring-cloud-netflix/docs/3.0.3/reference/html/#spring-cloud-eureka-server-peer-awareness
defaultZone: http://localhost:7900/eureka,http://localhost:7901/eureka,http://localhost:7902/eureka
server:
# 自我保护看自己情况
enable-self-preservation: true
# 续约阈值,和自我保护相关
renewal-percent-threshold: 0.85
# server剔除过期服务的时间间隔
eviction-interval-timer-in-ms: 1000
# 是否开启readOnly读缓存
use-read-only-response-cache: true
# 关闭 readOnly
response-cache-update-interval-ms: 1000
---
spring:
profiles: 7900
server:
port: 7900
eureka:
instance:
hostname: eureka-7900
---
spring:
profiles: 7901
server:
port: 7901
eureka:
instance:
hostname: eureka-7901
---
spring:
profiles: 7902
server:
port: 7902
eureka:
instance:
hostname: eureka-7902
启动类
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class CloudEurekaApplication { public static void main(String[] args) { SpringApplication.run(CloudEurekaApplication.class, args); } }
配置完成之后启动 eureka、zuul、service-sms服务,发送请求
http://localhost:9100/service-sms/test/sms-test
header中配置{userId:1}的访问如下配置的服务
eureka:
instance:
metadata-map:
# eureka提供自定义的键值对
version: v1
不需要启停服务,实时修改eureka上对应服务的自定义元数据 metadata-map
# PUT localhost:8761/eureka/apps/{spring.application.name}/{服务实例名}/metadata?version=v2
localhost:7900/eureka/apps/service-sms/windows10.microdone.cn:service-sms:8091/metadata?version=v2
官网链接:https://github.com/Netflix/eureka/wiki/Eureka-REST-operations
通过http://localhost:7900/eureka/apps上查看metadata-map里面的自定义标签数据是否改变