在微服务架构中,保障服务的稳定性和高可用性至关重要。本文将详细介绍在 PmHub 中如何利用 Sentinel + Gateway 进行网关限流,以及集成 Sentinel + OpenFeign 实现自定义的 fallback 服务降级。
1 熔断降级的必要性
在微服务架构中,服务间的调用错综复杂。一个服务可能会调用远程服务、数据库或第三方 API,例如支付时调用银联 API、查询商品价格时访问数据库等。然而,依赖的服务稳定性不可控,若其出现不稳定情况,请求响应时间过长,调用服务的方法会阻塞,耗尽业务线程资源,最终导致服务不可用。为避免这种情况,对不稳定的弱依赖服务调用进行熔断降级是保障高可用的重要措施。
2 Sentinel 的发展历史
早些年,Netflix 的 Hystrix 是熔断降级的热门组件,但在 2018 年底 Netflix 宣布不再维护它。此后,阿里巴巴开源的 Sentinel 成为了不错的替代产品。此外,Resilience4J 也是 Hystrix 官方推荐的替代产品,具有轻量、简单、文档清晰丰富的特点。Sentinel 是面向分布式的流量治理组件,以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、自适应过载保护、热点流量防护等多个维度保障微服务的稳定性。Sentinel 自 2012 年诞生,历经多次版本更新。
3 Sentinel 的基本概念
-
资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
-
规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
4 Sentinel 的原理
在 Sentinel 中,所有资源对应一个资源名称和一个 Entry。Entry 可通过对主流框架的适配自动创建,也可通过注解或调用 API 显式创建。每个 Entry 创建时,会同时创建一系列功能插槽(slot chain)。
这些插槽有不同的职责,例如:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;StatisticSlot
则用于记录、统计不同纬度的 runtime 指标监控信息;FlowSlot
则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;DegradeSlot
则通过统计信息以及预设的规则,来做熔断降级;SystemSlot
则通过系统的状态,例如 load1 等,来控制总的入口流量;
总体的框架如下:
Sentinel 将 ProcessorSlot
作为 SPI 接口进行扩展(1.7.2 版本以前 SlotChainBuilder
作为 SPI),使 Slot Chain 具备扩展能力,可加入自定义 slot 并编排顺序,为 Sentinel 添加自定义功能。
5 Sentinel 的下载和使用
5.1 下载Sentinel
- 下载地址:https://github.com/alibaba/Sentinel/releases
- 官方参考文档地址:https://sentinelguard.io/zh-cn/docs/quick-start.html
5.2 启动Sentinel
Sentinel 控制台的运行依赖 JDK 1.8 及以上版本,在搭建环境前需确保本地已安装符合要求的 JDK。
Sentinel 控制台本质上是一个可执行的 jar 包,使用 Java 命令即可运行。在启动前,需要确认默认端口 8080 是否被占用,可通过以下命令进行查看:
linux版:lsof -i :8080
windows版:netstat -ano | findstr "8080"
若命令执行后无任何输出,表明 8080 端口未被占用,可直接使用以下命令启动 Sentinel 控制台:
java -jar sentinel-dashboard-1.8.7.jar
若 8080 端口已被占用,则需切换至其他端口,例如 8081,启动命令如下:
java -jar sentinel-dashboard-1.8.7.jar --server.port=8081
当控制台输出类似 “Starting DashboardApplication using Java
” 等相关信息时,即表明 Sentinel 控制台启动成功。
5.3 访问管理界面
启动成功后,可通过浏览器访问 Sentinel 管理界面,若使用默认 8080 端口,访问地址为 http://localhost:8080;若使用 8081 端口,则访问地址为 http://localhost:8081。默认的登录账号和密码均为 “sentinel
”,登录后即可进入 Sentinel 控制台管理界面。
5.4 功能验证
为验证 Sentinel 环境搭建是否成功,可编写测试用例进行验证。
5.4.1 引入依赖
在项目的 pom.xml 文件中引入 Sentinel 的核心依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.6</version>
</dependency>
其中,sentinel-core
为 Sentinel 的核心包,sentinel-transport-simple-http
用于实现与控制台的连通。
5.4.2 编写测试代码
新建一个测试类,示例代码如下:
class Demo {
public static void main(String[] args) {
// 配置规则
initFlowRules();
while (true) {
// 1.5.0版本开始可以直接利用try-with-resources特性
try (Entry entry = SphU.entry("HelloWorld")) {
// 被保护的逻辑
System.out.println("hello world");
} catch (BlockException ex) {
// 处理被流控的逻辑
System.out.println("blocked!");
}
}
}
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("HelloWorld");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置QPS限制为20
rule.setCount(20);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
上述代码通过 initFlowRules
方法配置了流量控制规则,将 “HelloWorld
” 资源的 QPS 限制为 20,在 main 方法中通过 SphU.entry
方法尝试进入资源保护块,若超出流量限制,则会捕获 BlockException
并执行相应的处理逻辑。
5.4.3 运行测试
以 debug 模式运行上述测试类,在控制台可观察到输出信息。
当循环次数达到 21 次时,由于超出了设定的 QPS 限制,将进入 BlockException
处理逻辑,输出 “blocked!
”,这表明 Sentinel 的流量控制功能已正常生效,至此,PmHub 中 Sentinel 环境搭建成功。
6 Sentinel 配合 Gateway 实现网关限流
Sentinel 支持对 Spring Cloud Gateway、Zuul 等主流的 API Gateway 进行限流。在 1.6.0 版本,Sentinel 提供了 Gateway 的适配模块,支持两种维度的限流:
- route 维度:在配置文件中配置路由条目,资源名为对应的 routeId,常用于对某个微服务进行限流,PmHub 主要使用该维度。
- 自定义 API 维度:利用 Sentinel 提供的 API 自定义一些 API 分组,可针对 URI 进行限流,跨多个服务。
具体实现步骤如下:
6.1 引入 Sentinel 依赖
在需要 Sentinel 的微服务中引入 Sentinel 依赖,可在 PmHub 中搜索 alibaba-sentinel 关键字查看。
<!-- SpringCloud Alibaba Sentinel 核心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel Gateway,如果不是gateway可以不用引入 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- Sentinel Datasource Nacos 用来做持久化存储-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
6.2 修改 yml 配置
在 pmhub-gateway 的服务配置 bootstrap.yml
中,增加 Sentinel 的配置。其中
eager
用于取消控制台的懒加载;dashboard
为 Sentinel 控制台地址;port
为 Sentinel 客户端与 Sentinel 控制台通信的端口,用于上报网关的流量数据、接收动态规则配置、进行心跳检测等。
6.3 使用 Nacos 对 Sentinel 进行持久化配置
Sentinel 的配置默认放在内存中,服务重启后配置会丢失。使用 Nacos 进行持久化配置,若 Nacos 已启动,可在 Nacos 的控制台找到 sentinel-pmhub-gateway
。
6.4 在 Sentinel 控制台查看 Gateway 的流量规则
Sentinel 之所以能够对流量进行控制,是因为它会监控应用的 QPS 流量或者并发线程数等指标,如果达到指定的阈值,就会进行流量控制,避免服务被瞬时的高并发流量击垮,从而保证服务的可靠性。
QPS 即每秒查询率,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。即每秒的响应请求数,也就是最大吞吐能力。
这里以 pmhub-gateway 网关流控规则为例:
这里 pmhub-system
配置的 1000,代表每秒最多可以处理 1000 个请求。
7 Sentinel 配合 OpenFeign 实现自定义 fallback 服务降级
7.1 @SentinelResource 注解
@SentinelResource
是 Sentinel 中重要的注解,能在代码层面对资源进行保护,结合控制台的规则配置,实现高效的限流与降级策略。
例如,在 PmHub 的系统服务中,可为获取用户信息的接口加上@SentinelResource
注解,blockHandler 参数用于限流,fallback 参数用于降级处理。
代码路径:com.laigeoffer.pmhub.system.controller.SysUserController#info
/**
* 根据用户名获取当前用户信息
*/
@InnerAuth
@GetMapping("/info/{username}")
@SentinelResource(value = "infoSentinelResource",blockHandler = "handlerBlockHandler", fallback = "doActionFallback") // 演示SentinelResource细粒度管控服务流控和降级
public R<LoginUser> info(@PathVariable("username") String username) {
SysUser sysUser = userService.selectUserByUserName(username);
if (StringUtils.isNull(sysUser)) {
return R.fail("用户名或密码错误");
}
// 角色集合
Set<String> roles = permissionService.getRolePermission(sysUser);
// 权限集合
Set<String> permissions = permissionService.getMenuPermission(sysUser);
LoginUser loginUser = new LoginUser();
loginUser.setUser(sysUser);
loginUser.setRoles(roles);
loginUser.setPermissions(permissions);
loginUser.setUserId(sysUser.getUserId());
loginUser.setDeptId(sysUser.getDeptId());
loginUser.setNickName(sysUser.getNickName());
return R.ok(loginUser);
}
来看一下登录接口涉及到的服务交互,用户发起请求,网关服务(pmhub-gateway
)接收到后进行服务转发,转发到认证服务(pmhub-auth
),认证服务调用系统服务(pmhub-system
)获取用户。
7.2 fallback 服务降级
fallback 服务降级是指当调用一个服务出现异常时,给访问者一个友好的反馈,降低服务负载,避免因异常请求引发其他服务崩溃。
7.3 通过 OpenFeign 进行过渡降级处理
对于用户的登录请求,认证服务不直接调用系统服务,PmHub 抽离出公共包 pmhub-api
,将用户接口统一放入,认证服务通过 OpenFeign 调用系统服务,并在 pmhub-api
服务中进行统一的服务降级。
7.3.1 OpenFeign的概念
OpenFeign
是一个 声明式 HTTP 客户端,用于在 Spring Cloud 微服务架构 中简化服务之间的通信。它提供了一种声明式的方式来定义和调用 HTTP 服务,我们开发者无需手动构建 HTTP 请求,就能轻松实现服务调用。
7.3.2 具体实现步骤
- 使用
@FeignClient
注解:在UserFeignService
中,在@FeignClient
注解上添加自定义的fallbackFactory
。
代码路径:com.laigeoffer.pmhub.api.system.UserFeignService
@FeignClient(contextId = "userFeignService", value = ServiceNameConstants.SYSTEM_SERVICE, fallbackFactory = UserFeginFallbackFactory.class)
public interface UserFeignService {
/**
* 根据用户名获取当前用户信息
*/
@GetMapping("/system/user/info/{username}")
R<LoginUser> info(@PathVariable("username") String username, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 根据 userId 获取用户信息
*/
@GetMapping("/system/user/getInfoByUserId/{userId}")
R<LoginUser> getInfoByUserId(@PathVariable("userId") Long userId, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 根据条件获取用户列表
*/
@PostMapping("/system/user/listOfInner")
R<List<SysUserVO>> listOfInner(@RequestBody SysUserDTO sysUserDTO, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 注册用户信息
*
* @param sysUser 用户信息
* @param source 请求来源
* @return 结果
*/
@PostMapping("/system/user/register")
R<Boolean> registerUserInfo(@RequestBody SysUser sysUser, @RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}
- 创建
UserFeginFallbackFactory
:进行自定义的降级处理,在FallbackFactory
中,不仅能捕获异常进行处理,还能通过Throwable
获取详细的异常信息,处理复杂的限流逻辑。
代码路径:com.laigeoffer.pmhub.api.system.factory.UserFeginFallbackFactory
@Component
public class UserFeginFallbackFactory implements FallbackFactory<UserFeignService>
{
private static final Logger log = LoggerFactory.getLogger(UserFeginFallbackFactory.class);
@Override
public UserFeignService create(Throwable throwable)
{
log.error("用户服务调用失败:{}", throwable.getMessage());
return new UserFeignService()
{
@Override
public R<LoginUser> info(String username, String source) {
return R.fail("根据用户名获取用户失败:" + throwable.getMessage());
}
@Override
public R<LoginUser> getInfoByUserId(Long userId, String source) {
return R.fail("根据userId获取用户失败:" + throwable.getMessage());
}
@Override
public R<List<SysUserVO>> listOfInner(SysUserDTO sysUserDTO, String source) {
return R.fail("根据调教获取用户列表失败:" + throwable.getMessage());
}
@Override
public R<Boolean> registerUserInfo(SysUser sysUser, String source) {
return R.fail("注册用户失败:" + throwable.getMessage());
}
};
}
}
8 总结
本文围绕 PmHub,介绍 Sentinel 在微服务架构中熔断降级的必要性、基本概念、原理等,阐述其下载使用方法,以及配合 Gateway 实现网关限流、配合 OpenFeign 实现自定义 fallback 服务降级,助力开发者保障微服务架构稳定性。
9 参考链接
- PmHub集成 Sentinel+OpenFeign实现网关流量控制,以及自定义fallback服务降级
- 项目仓库(GitHub)
- 项目仓库(码云): (国内访问速度更快)