• OpenFeign集成Sentinel实现服务的熔断降级


    往期回顾

    Nacos的安装与配置

    Spring Cloud集成Nacos作为注册中心

    LoadBalacer集成Nacos实现负载均衡

    常见的负载均衡策略分析

    Spring Cloud集成Dubbo实现RPC调用

    SpringCloud集成Nacos作为配置中心

    Nacos整合OpenFegin实现RPC调用

    Nacos整合Gateway入门实例

    Spring Cloud Gateway的过滤器配置

    Nacos整合Gateway实现动态路由

    Sentinel的安装与配置

    @SentinelResource详解

    Sentinel的流控与熔断降级规则详解

    Sentinel集成Nacos对流控与降级规则的持久化

    Spring Cloud Gateway集成Sentinel流控

    前面我们已经介绍了Sentinel的各种特性,接下来我们一起来看看OpenFeign对Sentinel的集成吧

    引入依赖

    Feign 适配整合在 Spring Cloud Alibaba 中,所以我们直接引入spring cloud alibaba中的组件就好

    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    除此之外,我们还需要引入OpenFeign和Nacos的依赖

            
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-loadbalancerartifactId>
            dependency>
            <dependency>
                <groupId>com.alibaba.cloudgroupId>
                <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-bootstrapartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-openfeignartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    生产者接口

    在配置消费者之前,我们需要先编写好生产者的相关代码,这里引用之前的user-service,不再赘述,只贴出一些核心代码,完整的源码可以在gitee或者github上找到,文末会给出仓库地址

    package cuit.epoch.pymjl.controller;
    
    import com.alibaba.csp.sentinel.annotation.SentinelResource;
    import com.alibaba.csp.sentinel.slots.block.BlockException;
    import cuit.epoch.pymjl.entity.User;
    import cuit.epoch.pymjl.exception.AppException;
    import cuit.epoch.pymjl.result.CommonResult;
    import cuit.epoch.pymjl.result.ResultUtils;
    import cuit.epoch.pymjl.service.UserService;
    import lombok.extern.log4j.Log4j2;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import java.net.InetAddress;
    import java.net.UnknownHostException;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/8/25 12:48
     **/
    @RestController
    @RequestMapping("/user")
    @Log4j2
    public class UserController {
        private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
    
        @Resource
        UserService userService;
    
        @Value("${server.port}")
        private String port;
    
        @GetMapping("/test")
        @SentinelResource(value = "test", blockHandler = "handleTest")
        public CommonResult<String> test(HttpServletRequest request) throws UnknownHostException {
            System.out.printf("被[/%s:%s]调用了一次%n", request.getRemoteHost(), request.getRemotePort());
            String hostAddress = InetAddress.getLocalHost().getHostAddress() + ":" + port;
            return ResultUtils.success(hostAddress);
        }
    
        @GetMapping("/register")
        @SentinelResource("register")
        public CommonResult<String> register() {
            userService.register();
            return ResultUtils.success();
        }
    
        @GetMapping("/get/{id}")
        @SentinelResource(value = "getUser")
        public CommonResult<User> get(@PathVariable("id") Long id) {
            int cnt = ATOMIC_INTEGER.incrementAndGet();
            log.info("cnt=={}", cnt);
            if (cnt % 2 == 0) {
                throw new AppException("发生了异常");
            }
            return ResultUtils.success(userService.get(id));
        }
    
        public CommonResult<String> handleTest(HttpServletRequest request, BlockException blockException) {
            log.error("调用/user/test失败");
            return ResultUtils.fail("Sentinel流控,调用失败");
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    配置消费者

    这里我们结合着Sentinel控制台进行演示,所以我们需要先配置控制台的相关信息,在消费者的bootstrap.yaml 中如下配置

    spring:
      application:
        name: user-openfeign-consumer
      cloud:
        nacos:
          discovery:
            server-addr: 192.168.199.128:8848 #Nacos地址
          config:
            server-addr: 192.168.199.128:8848 #Nacos地址
            file-extension: yaml #这里我们获取的yaml格式的配置
        #sentinel控制台
        sentinel:
          transport:
            #配置 Sentinel dashboard 地址
            dashboard: 192.168.199.128:8858
            #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
            port: 8719
    
    #开启sentinel的支持
    feign:
      sentinel:
        enabled: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    我们还需要在主启动类上添加注解,开启对OpenFeign的支持

    package cuit.epoch.pymjl;
    
    import cuit.epoch.pymjl.config.OpenFeignConfig;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.openfeign.EnableFeignClients;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/8/31 23:37
     **/
    @SpringBootApplication
    @EnableFeignClients(defaultConfiguration = OpenFeignConfig.class)
    public class OpenFeignApplication {
        public static void main(String[] args) {
            SpringApplication.run(OpenFeignApplication.class, args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    编写FeignClient和相关配置,前面在Nacos整合OpenFegin实现RPC调用已经介绍过,这里直接给代码

    /**
     * 在启动类的注解@EnableFeignClients上指定
     * 局部生效就在@FeignClient中指定,不能加@Configuration注解
     *
     * @author Pymjl
     * @version 1.0
     * @date 2022/9/1 13:17
     **/
    public class OpenFeignConfig {
        @Bean
        public Logger.Level feignLogLevel() {
            // 日志级别为BASIC
            return Logger.Level.FULL;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    service接口

    package cuit.epoch.pymjl.service;
    
    import cuit.epoch.pymjl.entity.User;
    import cuit.epoch.pymjl.feign.FeignServiceFallback;
    import cuit.epoch.pymjl.result.CommonResult;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/9/1 0:03
     **/
    @FeignClient(value = "user-service", fallback = FeignServiceFallback.class)
    public interface UserFeignClient {
        /**
         * 注册
         *
         * @return {@code CommonResult}
         */
        @GetMapping("/user/register")
        CommonResult<String> register();
    
        /**
         * 得到
         *
         * @param id id
         * @return {@code CommonResult}
         */
        @GetMapping("/user/get/{id}")
        CommonResult<User> get(@PathVariable("id") Long id);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    编写降级异常处理类

    package cuit.epoch.pymjl.feign;
    
    import cuit.epoch.pymjl.constant.ResultEnum;
    import cuit.epoch.pymjl.entity.User;
    import cuit.epoch.pymjl.result.CommonResult;
    import cuit.epoch.pymjl.result.ResultUtils;
    import cuit.epoch.pymjl.service.UserFeignClient;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Pymjl
     * @version 1.0
     * @date 2022/9/17 22:06
     **/
    @Component
    public class FeignServiceFallback implements UserFeignClient {
        @Override
        public CommonResult<String> register() {
            return ResultUtils.fail(ResultEnum.SENTINEL_FALLBACK_ERROR);
        }
    
        @Override
        public CommonResult<User> get(Long id) {
            return ResultUtils.fail(ResultEnum.SENTINEL_FALLBACK_ERROR);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    测试

    接下来,启动项目,进行测试。我们先调用消费者的接口,然后可以在控制台中Sentinel看到

    image-20220917224730945

    然后我们添加如图所示的降级规则:

    image-20220917224828814

    各种流控降级规则你还不是很了解的话请参考前文:Sentinel的流控与熔断降级规则详解

    随后我们对接口进行测试访问:

    image-20220917224939455

    如图所示,当请求次数为偶数时就会抛出异常,然后服务进行降级,生产者代码逻辑如下:

    	private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
    
    	@GetMapping("/get/{id}")
        @SentinelResource(value = "getUser")
        public CommonResult<User> get(@PathVariable("id") Long id) {
            int cnt = ATOMIC_INTEGER.incrementAndGet();
            log.info("cnt=={}", cnt);
            if (cnt % 2 == 0) {
                throw new AppException("发生了异常");
            }
            return ResultUtils.success(userService.get(id));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当请求次数为奇数时就可以正常访问

    image-20220917225209290

    然后当我们快速连续的访问多次该接口以后,服务会根据我们配置的降级规则,Sentinel会对服务进行熔断,熔断后无论请求次数为奇数还是偶数,请求都会被拒绝,只有等熔断时长过了后才有可能恢复服务

    image-20220917225344654

    小结

    总之,因为Spring Cloud Alibab已经集成了Sentinel,所以我们使用Spring Cloud来集成Sentinel非常方便,总结下来就这几个步骤:

    1. 引入对应的依赖
    2. 开启Sentinel支持
    3. 编写FeignClient,并为FeignClient指定对应的降级处理类
    4. 编写对应的降级异常处理类,该类需要实现FeignClient

    Sentinel控制台配置的降级规则是保存到服务内存中的,服务一旦重启,所有规则都将丢失,生产环境下会使用远程数据源推送的方式,具体请参考:Sentinel集成Nacos对流控与降级规则的持久化

    好了,今天的讲解就到这里了,你可以在这找到项目源码:gitee github,谢谢大家的观看

    如果对您有用的话还请三连支持一下呀,谢谢各位看官老爷啦~

  • 相关阅读:
    SQLAlchemy学习-8.query查找之filter()和filter_by()区别
    2022年,我们为什么要学习C++?
    JSONP 跨域访问(2), JSONP劫持
    自媒体账号十万粉丝如何变现?
    元宇宙区块链游戏如何打金赚钱?
    gem5 使用记录, 基于理解来写个最简单的计数器程序
    优化导入大批量数据的Excel(上万行的导入)SpringBoot + Apache POI
    我在华为度过的 “两辈子”(学习那些在大厂表现优秀的人)
    Ubuntu连不上WiFi 或者虽然能连上校园网,但是浏览器打不开登录页面
    JavaScript高级知识-闭包
  • 原文地址:https://blog.csdn.net/apple_52109766/article/details/126912285