《SpringBoot中间件设计与实战》第2章 服务治理,超时熔断

需求背景

在流量较大的场景下,举个例子,用户在电商平台下单后开始跳转到在线收银台进行支付。由于支付渠道和网络环境随时都有可能发生问题,那么你该怎么保证支付系统的可靠性呢?

保证支付系统的可靠性需要考虑的点非常多,但这里有一个最直接和重点的内容就支付响应时长,如果支付时间过长,那么暴增的支付请求可能会把整个服务拖垮,最终导致所有服务瘫痪。

这时你可能会想到一个功能组件,超时熔断hystrix。这也是大多数支付系统中必用的组件,但怎么用呢,我们是在所有的接口上都加一个这样的功能组件吗?显然这样做是不合适的,一般类似这样的组件可能会嵌入到你的RPC接口或者自研的网关上,也可能是在整个服务治理层的功能编排上。总之,它不会轻易的暴漏给你,让你硬编码到业务逻辑实现中。

那么,本章我们就抽丝剥茧把组件包装使用的最核心实现方式展示给你。记住任何实现方案都是以当前系统环境最适合方式设计的,并不一定非得拘泥于某种形式。

方案设计

面对复杂的场景问题,市面上基本上都有应对的方案。。就像我们本章节所需要的调用超时要熔断保护系统,就有相应的技术组件hystrix,它是 Netflix公司开源的一款容错框架,在大部分RPC服务中也都有引入使用。

那如果我们只是想方便、简单并且不需要关心如何创建和返回结果的使用这样一个服务,就可以把hystrix的框架包装在中间里,屏蔽调用逻辑。整体的设计方案如图4-1超时熔断中间件框架设计。

大致思路如下:

  • 使用自定义注解和切面技术,拦截需要被熔断保护的方法。
  • 拦截后到方法后,就可以通过hystrix给方法设定已配置好的超时熔断处理。

技术实现

工程结构如下:

工程源码地址

https://gitee.com/lldlld/spring-boot-middleware-demo/tree/master/02-熔断

熔断类直接的UML关系图如下

在这个中间件的实现中也没有额外的yml配置,仅是对切面的运用和hystrix的包装,包含如下信息:

  • 1.DoHystrix自定义注解中需要配置方法的超时时长和拦截后的返回信息。
  • 2.DoHystrixPoint自定义切面的实现比较简单,只是拦截方法和处理相应的操作。
  • 3.HystrixValvelmpl是熔断的具体实现,这里之所以添加接口的实现方式,是为了可以扩展更多的插件包服务。

自定义注解

/**
 * 类描述:
 * 博客 :https://blog.csdn.net/weixin_42329623
 * 公众号 安前码后
 * @author LiLiDong
 * @date 2023/5/3 15:03
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DoHystrix {

    /**
     * 失败结果
     */
    String returnJson() default "";

    /**
     * 超时熔断
     */
    int timeoutValue() default 0;

}
  • @Retention(RetentionPolicy.RUNTIME)标记注解在JVM运行时可见
  • @Target(ElementType.METHOD)标记注解的作用域在方法层面ElementType.METHOD
  • returnJson,熔断保护下返回的结果
  • timeoutValue,方法的调用安全范围时长,一般指的是 TP99、TP999的调用稳定值

熔断实现

/**
 * 类描述: 服务熔断包装
 * 博客 :https://blog.csdn.net/weixin_42329623
 * 公众号 安前码后
 * @author LiLiDong
 * @date 2023/5/3 15:03
 */
public class HystrixValveImpl extends HystrixCommand<Object> implements IValveService {

    private ProceedingJoinPoint jp;
    private Method method;
    private DoHystrix doHystrix;

    public HystrixValveImpl() {

        /**
         * 置HystrixCommand的属性
         *  GroupKey:            该命令属于哪一个组,可以帮助我们更好的组织命令。
         *   CommandKey:          该命令的名称
         *   ThreadPoolKey:       该命令所属线程池的名称,同样配置的命令会共享同一线程池,若不配置,会默认使用GroupKey作为线程池名称。
         *   CommandProperties:   该命令的一些设置,包括断路器的配置,隔离策略,降级设置,以及一些监控指标等。
         *   ThreadPoolProperties:关于线程池的配置,包括线程池大小,排队队列的大小等
         */
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GovernGroup"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("GovernKey"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("GovernThreadPool"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withCoreSize(10))
        );
    }


    @Override
    public Object access(ProceedingJoinPoint jp, Method method, DoHystrix doHystrix, Object[] args) throws Throwable {
        this.jp = jp;
        this.method = method;
        this.doHystrix = doHystrix;

        // 设置熔断超时时间
        Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("GovernGroup"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                        .withExecutionTimeoutInMilliseconds(doHystrix.timeoutValue()));

        return this.execute();

    }

    @Override
    protected Object run() throws Exception {
        try {
            return jp.proceed();
        } catch (Throwable throwable) {
            return null;
        }
    }

    @Override
    protected Object getFallback() {
        return JSON.parseObject(doHystrix.returnJson(), method.getReturnType());
    }
}
  • 本文主要是关于中间件对现有解决方案组件的包装,如果对熔断保护的内容感兴趣可以深入学习Hystrix
  • HystrixValvelmpl主要是对Hystrix熔断保护的封装,通过继承HystrixCommand构造函数中配置启动参数
  • 在切面接口方法调用中,设置超时熔断时间,withExecutionTimeoutInMilliseconds(doHystrix.timeoutValue())这个时间信息就是自定义注解中的配置信息
  • 另外这里有一个抽象方法run()和重写的方法 getFallback(),它们分别是返回正确方法调用结果和熔断保护时候返回的对象信息。

切面逻辑实现

/**
 * 类描述:
 * 博客 :https://blog.csdn.net/weixin_42329623
 * 公众号搜索:安前码后
 * @author LiLiDong
 * @date 2023/5/3 15:03
 */
@Aspect
@Component
public class DoHystrixPoint {

    @Pointcut("@annotation(com.aqmh.middleware.hystrix.annotation.DoHystrix)")
    public void aopPoint() {
    }

    @Around("aopPoint() && @annotation(doGovern)")
    public Object doRouter(ProceedingJoinPoint jp, DoHystrix doGovern) throws Throwable {
        IValveService valveService = new HystrixValveImpl();
        return valveService.access(jp, getMethod(jp), doGovern, jp.getArgs());
    }

    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }
}

  • @Aspect定义切面、@Component定义组件以求被实例化、@Pointcut定义切点,这些与我们之前的内容一样。
  • ·@Around(“aopPoint()&&@annotation(doGovern)”),这块的处理是本章节新增的,一般在方法入参中并没有直接提供自定义注解的获取,而是通过类和方法再反向找出来。而直接通过方法入参的方式可以更加方便的拿到注解,处理起来也更优雅。
  • 接下来就是方法内容的调用逻辑,valveService.access(ip, getMethod(jp), doGovern,jp.getArgs());,这步调用的就是我们包装好的熔断保护服务。

测试验证

项目结构如下:

源码地址

https://gitee.com/lldlld/spring-boot-middleware-demo/tree/master/02-%E7%86%94%E6%96%AD/aqmh-hystrix-spring-boot-starter-test

引入依赖

 <!-- 服务治理,熔断 -->
  <dependency>
      <groupId>com.aqmh</groupId>
      <artifactId>aqmh-hystrix-spring-boot-starter</artifactId>
      <version>1.0.0-SNAPSHOT</version>
  </dependency>

接口使用中间件定义的注解

测试结果

情景一,执行事件1000ms返回的结果:


情景二,执行时间为250ms返回的结果:

总结

  • 本章节主要体现的对已有组件的使用封装,通过中间件的设计屏蔽掉底层应用的复杂性,也让整个功能服务更加符合自己的业务特性,同时可以让使用此功能的研发不会过多的参与到插件的使用中,把更多的关心放在业务逻辑开发中。
  • 类似这样的中间件使用并没有涉及到SpringBoot 的加载和配置以及 Bean 的处理,这看上去更加简单即使是运用在 Spring 中也可以非常方便的迁移过去。其实大多数依赖SpringBoot开发的starter都是对 Spring 原有组件的包装,后面我们会涉及到
  • ·技术的使用更多的实际场景的考虑和运用,并不非得拘泥于某一种实现形式。在学习的过程中也可以把各项你需要的功能不断的扩展进去,体会依赖实际场景开发的具体实现结果。这样可以让你学到更多。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

做一枚快乐的程序员

觉得不错的话,可以点赞加关注哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值