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

引入依赖

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

<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> 

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

<!--添加loadbalancer依赖
            由于 Netflix Ribbon 进入停更维护阶段,因此 SpringCloud 2020.0.1 版本之后 删除了eureka中的ribbon,
            替代ribbon的是spring cloud自带的LoadBalancer,默认使用的是轮询的方式
            新版本的 Nacos discovery 都已经移除了 Ribbon ,此时我们需要引入 loadbalancer 代替,才能调用服务提供者提供的服务
        --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> 

生产者接口

在配置消费者之前,我们需要先编写好生产者的相关代码,这里引用之前的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流控,调用失败");
    }

}

配置消费者

这里我们结合着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 

我们还需要在主启动类上添加注解,开启对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);
    }
}
/**
 * 在启动类的注解@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;
    }
}

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<String>}
     */ @GetMapping("/user/register")
    CommonResult<String> register(); /**
     * 得到
     *
     * @param id id
     * @return {@code CommonResult<User>}
     */ @GetMapping("/user/get/{id}") CommonResult<User> get(@PathVariable("id") Long id);
}

编写降级异常处理类

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);
    }
}

测试

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



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



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



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

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));
    }

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



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



小结

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

  1. 引入对应的依赖
  2. 开启Sentinel支持
  3. 编写FeignClient,并为FeignClient指定对应的降级处理类
  4. 编写对应的降级异常处理类,该类需要实现FeignClient
#Java##程序员#
全部评论
sentinel怎么持久化呀
点赞 回复 分享
发布于 2022-09-27 16:52 河南

相关推荐

ResourceUt...:楼主有自己的垃圾箱,公司也有自己的人才库
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务