使用网关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里面的自定义标签数据是否改变

posted @ 2021-11-04 10:15  龙宇在天  阅读(420)  评论(0编辑  收藏  举报
//右侧添加目录 //增加页面点击显示24字社会主义核心价值观