java打印对象内存地址 分布式事务 事务消息 分布式事务 几种解决方案 分布式事务-Seata 分布式事务-Seata 分布式事务-LCN-TCC 分布式事务-LCN 分布式事务-消息队列-定时任务-本地事件表 Zuul网关实战02 Zuul网关实战01 灰度发布落地实战2 灰度发布落地实战1 Gsnova on Heroku build Systemd Debian system initialization manage multi id_rsa ubuntu 64bits cannot run 32bits app REHL power auditing Debug Assembly for ARMv8 on QEMU ARM体系结构--寄存器 Run Debian iso on QEMU ARMv8 QEMU ARM64 guide cross compiler install buildroot install QEMU install python入门--数据结构 python入门--内置数据类型 python入门--类 异常 python入门--条件表达式 方法 python入门--数字 字符串 数组 RTC驱动分析 块设备驱动 TCP UDP socket 触摸屏驱动 USB驱动 LED按键中断 LCD 驱动 驱动信号 根文件系统 实验 内核实验 字符设备驱动程序 绪论 uboot 实验 LCD 实验 系统时钟和UART 中断控制器 Nand Flash控制器 MMU 实验 储存管理器实验 GPIO实验 点亮LED 编译加载驱动 制作烧写内核 dnw替代方法 MINI2440 TQ2440安装配套Linux 使用NFS 制作烧写跟文件系统 grub引导Windows 烧写裸版程序-linux Ubuntu 网络没有 eth0 Linux自动挂载 烧写裸板程序 电路基础 Mac词典 Vim插件 Assembly 综合研究 Assembly 指令总结 Assembly 直接定址表 Assembly 使用BIOS进行键盘输入和磁盘读写 Assembly 外中断 Assembly 端口 Assembly int指令 Assembly 内中断 Assembly 标志寄存器 Assembly 转移指令的原理 Assembly Call和ret指令 Assembly 数据处理两个基本问题 Assembly 灵活定位内存地址 Assembly 包含多个段的程序 Assembly [bx] loop Assembly 第一个程序 Assembly 寄存器 (内存访问) Assembly 寄存器 AWS VPN with EC2 hidden file in picture(linux) Assembly 基础 idea shortcuts 常用快捷键 idea plugin folder install ruby and jekyll

灰度发布落地实战2

2020年10月10日

灰度发布落地实战2


Java落地代码实现:

第二种方案,基于服务调用服务

原理:用户请求打到 服务A,aop切面拦截请求取得request header中的 version字段,根据不同的规则,使用ribbon转发到不同的服务(根据eureka cilent 中的 metadata)

服务A –> 服务B

项目结构介绍:

cloud-eureka:7900

api-passenger:8080

service-sms:8003 和 8004

这次是 api-passenger 调用 service-sms 服务,服务与服务之间的灰度发布实现

ribbon在 api-passenger中,因为进rule和出rule是一个线程,所以可以用ThreadLocal把它获取到

在 api-passenger 中定义 TestCallServiceSmsController 用于测试调用服务:

/**
 * 用于服务与服务之间的灰度发布测试
 */
@RestController
@RequestMapping("/test")
public class TestCallServiceSmsController {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 该方法会调用 service-sms 中的方法
     * @return
     */
    @GetMapping("/call")
    public String testCall(){

        return restTemplate.getForObject("http://service-sms/test/sms-test",String.class);
    }

    @GetMapping("/test")
    public String testCall1(){

        return "api-passenger";
    }
}

定义灰度规则类:

/**
 * 自定义Ribbon路由规则,用于灰度发布
 */
public class GreyRule extends AbstractLoadBalancerRule {

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {

    }

    @Override
    public Server choose(Object o) {
        return choose(getLoadBalancer(), o);
    }

    /**
     * 重载choose() 通过 LB 取得 server
     * @param lb
     * @param key
     * @return
     */
    public Server choose(ILoadBalancer lb, Object key){
        System.out.println("灰度  rule");

        Server server = null;
        while(server == null){
            // 获取所有 可达的服务
            List<Server> reachableServers = lb.getReachableServers();

            // 获取 当前线程的参数 用户id verion=1
            Map<String,String> map = RibbonParameters.get();
            String version = "";
            if (map != null && map.containsKey("version")){
                version = map.get("version");
            }
            System.out.println("当前rule version:"+version);

            // 根据用户选服务
            for (int i = 0; i < reachableServers.size(); i++) {
                server = reachableServers.get(i);
                // 用户的version我知道了,服务的自定义meta我不知道。

                // eureka:
                //  instance:
                //    metadata-map:
                //      version: v2
                // 不能调另外 方法实现 当前 类 应该实现的功能,尽量不要乱尝试
                Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();

                String serverVersion = metadata.get("version");
                // 服务的meta也有了,用户的version也有了。
                if (version.trim().equals(serverVersion)){
                    return server;
                }

            }
        }
        // 怎么让server 取到合适的值。这个server会在循环内被赋值
        //根据业务场景,制定规则,这里可以选择用默认服务兜底
        return server;
    }
}

题外话:

com.netflix.loadbalancer.Server类,没有获取metadata的方法

我们可以参考Server的子类,IDEA中把鼠标点中Server进去看子类

发现DiscoveryEnabledServer中有getMetadata()方法

定义 RibbonParameters 类:

/**
 * 用于 保存、获取 每个线程中的 request header
 */
@Component
public class RibbonParameters {

    private static final ThreadLocal local = new ThreadLocal();

    public static <T> T get(){
        return (T)local.get();
    }

    public static <T> void set(T t){
        local.set(t);
    }
}

注意:在 GreyRule中 使用了 RibbonParameters.get() ,使用ThreadLocal get()之前,必须先set() 否则会空指针

我们在aop切面中拦截请求,在GreyRule之前进行ThreadLocal set()

切面代码:

/**
 * 拦截请求,AOP实现,获取request header
 */
@Aspect
@Component
public class RequestAspect {

    /**
     * 定义切入点
     */
    @Pointcut("execution(* com.kennedy.apipassenge.controller..*Controller*.*(..))")
    private void anyMehtod(){

    }

    /**
     * 在之前切入
     * 此时IDEA中左侧栏能看到被拦截的方法
     * @param joinPoint
     */
    @Before(value = "anyMehtod()")
    public void before(JoinPoint joinPoint){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String version = request.getHeader("version");

        Map<String,String> map = new HashMap<>();
        map.put("version",version);

        RibbonParameters.set(map);  //写入ThreadLocal
    }
}

定义Ribbon配置类

/**
 * 自定义Ribbon配置,用于启动类
 */
public class GrayRibbonConfiguration {

    @Bean
    public IRule ribbonRule(){
        return new GreyRule();
    }
}

启动类:

@SpringBootApplication
@RibbonClient(name = "service-sms" , configuration = GrayRibbonConfiguration.class)
public class ApiPassengeApplication {

    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(ApiPassengeApplication.class, args);
    }

}

service-sms 项目 yml 配置不用变,参见上篇文章

测试:

使用postman调用GET请求:http://localhost:8080/test/call

request header中设置 version = v1

结果:

v1用户访问 service-sms:8003

v2用户访问 service-sms:8004

实现了服务与服务之间灰度发布


还可以使用 io.jmnarloch 实现

pom.xml引入:

<dependency>
    <groupId>io.jmnarloch</groupId>
    <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
    <version>2.1.0</version>
</dependency>

项目中的 GreyRule、GreyRibbonConfiguration、RibbonParameters 可以删掉

只需要修改切面类:

/**
 * 拦截请求,AOP实现,获取request header
 */
@Aspect
@Component
public class RequestAspect {

    /**
     * 定义切入点
     */
    @Pointcut("execution(* com.kennedy.apipassenge.controller..*Controller*.*(..))")
    private void anyMehtod(){

    }

    /**
     * 在之前切入
     * 此时IDEA中左侧栏能看到被拦截的方法
     * @param joinPoint
     */
    @Before(value = "anyMehtod()")
    public void before(JoinPoint joinPoint){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String version = request.getHeader("version");

//        Map<String,String> map = new HashMap<>();
//        map.put("version",version);
//
//        RibbonParameters.set(map);  //写入ThreadLocal

        //灰度规则 匹配的地方 查db, redis
        if (version.trim().equals("v1")) {
            RibbonFilterContextHolder.getCurrentContext().add("version", "v1");
        } else if (version.trim().equals("v2")){
            RibbonFilterContextHolder.getCurrentContext().add("version", "v2");
        }
    }
}

部署运行,进行同样的测试成功

使用 RibbonFilterContextHolder 实现,内部也是使用了ThreadLocal实现,贴上源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package io.jmnarloch.spring.cloud.ribbon.support;

import io.jmnarloch.spring.cloud.ribbon.api.RibbonFilterContext;

public class RibbonFilterContextHolder {
    private static final ThreadLocal<RibbonFilterContext> contextHolder = new InheritableThreadLocal<RibbonFilterContext>() {
        protected RibbonFilterContext initialValue() {
            return new DefaultRibbonFilterContext();
        }
    };

    public RibbonFilterContextHolder() {
    }

    public static RibbonFilterContext getCurrentContext() {
        return (RibbonFilterContext)contextHolder.get();
    }

    public static void clearCurrentContext() {
        contextHolder.remove();
    }
}

总结:

AOP切面截取request header 传入ThreadLocal

自定义Ribbon规则GreyRule中,从ThreadLocal拿到request header,根据不同规则,路由到不同服务,也是使用自定义eureka metadata

手写ThreadLocal 和使用 io.jmnarloch 原理是一样的

项目代码地址:

https://github.com/kennedy-han/grey-notice-zuul