更新时间:2020/10/08 18:10,更新到了sentinel持久化 更新时间:2020/10/08 02:22,更新到了@SentinelResource
本文主要对Spring Cloud Alibaba中的Sentinel进行学习与记录,偏向于实战,本文会持续更新,不断地扩充
注意:本文仅为记录学习轨迹,如有侵权,联系删除
以上面的技术结构图为例,学到这里,上面的大多组件基本已经学过,现在是Sentinel
这里给出它的官方链接:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D 如果打不开可以访问这个链接:https://www.oschina.net/p/sentinel?hmsr=aladdin1e1
Sentinel 是面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel Dashboard可以看一下官网:https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
Sentinel本来想在服务器上用docker进行拉取部署,结果,经过一天的尝试,发现如果用docker拉取部署Sentinel,即Sentinel在云服务器上面,而项目如果在本地的话,Sentinel无法监控接口,网上的解决方案大多一样,像是配置文件加个client-ip,还有docker中的Sentinel时间与本地时间要一致等,都没法解决,之后在网上看到这样的一句话 也不知道是真假,最后无奈之下只能自己下一个sentinel的jar包,在本地运行 访问页面,账户密码都是sentinel
创建子模块cloud-alibaba-sentiel-server8401,引入pom
<dependencies> <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--openFeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--web启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--监控--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>application.yml
server: port: 8401 spring: application: name: cloud-alibaba-sentinel-server8401 cloud: nacos: discovery: server-addr: 39.96.22.34:8080 #将服务注册进nginx代理的nacos集群 namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id #sentinel配置 sentinel: transport: #client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP) dashboard: 127.0.0.1:8080 #sentinel的地址 port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口 management: endpoints: web: exposure: include: '*'随便创建几个接口
@RestController public class SentinelController { @GetMapping("/sentinel01") public String sentinel01(){ return "---------------------------this is sentinel01"; } @GetMapping("/sentinel02") public String sentinel02(){ return "---------------------------this is sentinel02"; } }主启动类,注意注解的添加 启动该项目,sentinel默认采用懒加载,所以点击实时监控等子菜单都空白的,需要先加载接口后,才会显示数据 sentinel可以用来做服务限流、服务降级和服务熔断等,它跟之前的Hystrix完全不同,sentinel更加简单更加友好,同时功能更加强大。下面开始一一介绍它的功能。
同时,同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在"实时监控"下。实时监控仅存储 5 分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。
这里以http://localhost:8401/sentinel02建接口为例,任何对该接口的访问,都会在实时监控上面展示,包括访问了多少次,时间、失败或成功等详细信息,但是实时监控仅存储 5 分钟以内的数据,之后变为了空白。
"簇点链路"中显示刚刚调用的资源(单机实时),即刚刚访问过的接口,都会显示在簇点链路上,簇点链路(单机调用链路)页面实时的去拉取指定客户端资源的运行情况。它一共提供两种展示模式:一种用树状结构展示资源的调用链路,另外一种则不区分调用链路展示资源的实时情况。 上面显示访问过接口,关于这个模块主要学习上面的4种操作,流控、降级、热点和授权,直接可视化操作,注意: 簇点链路监控是内存态的信息,它仅展示启动后调用过的资源。
关于流控,这里给出官网:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6#%E5%9F%BA%E4%BA%8E%E8%B0%83%E7%94%A8%E5%85%B3%E7%B3%BB%E7%9A%84%E9%99%90%E6%B5%81
即流量控制,控制某一时刻的流量,对于流控可以在"簇点链路”那里直接根据资源进行流控规则的添加,也可以在子菜单“流控规则”那里进行添加,所有添加的流控信息,都可以在流控规则菜单那里看到
主要以下面这张图进行流控规则的解读 对应的解读如下
例子1:QPS + 直接 + 快速失败 下面先直接上例子,对sentinel01接口进行流量控制 上面的例子表示,当请求该接口时,每秒中请求的数量达到自己设置的阈值后,会进行限流,返回限流信息 例子1:QPS + 关联 + 快速失败
流控模式是关联的情况下,举个例子,两个模块,订单支付和订单打印两个模块,前者调用后者,当订单打印的业务太多处理不过来的情况下,订单支付就可以暂时先别去调用订单打印模块了,这个功能就可以用关联的流控模式来做,这两个模块关联后,当订单打印模块QPS达到阈值后,限流订单支付模块,这就是关联的流控模式
应用场景: 比如支付接口达到阈值,就要限流下订单的接口,防止一直有订单
下面以两个接口(sentinel03和sentinel04)来实现这种关联 当接口关联的资源sentinel04达到阈值后,自己sentinel03会被限流 例子1:QPS + 直接 + Warm up
应用场景:当秒杀系统开启的时候,瞬间会有大量的请求打进来,容易将系统打垮,这个时候就可以用这种方法,慢慢将请求放进来,慢慢的将阈值升到设置的阈值
例子1:QPS + 直接 + 排队等待
即服务降级,对于降级可以在"簇点链路”那里直接根据资源进行降级规则的添加,也可以在子菜单“降级规则”那里进行添加,所有添加的降级信息,都可以在降级规则菜单那里看到
编写测试接口 这里故意暂停1秒,添加降级规则
默认当1秒内请求的数量超过5个的时候,并且平均响应的时间超过200毫秒,此时再有请求进来的时候,就会进行降级熔断,并且在时间窗口这个时间内该服务不可访问,这里的降级带有熔断的效果 此时,每秒都有10个请求发送出去,在访问浏览器,就会发现已经触发了降级规则,而且在时间窗口期内无法访问
编写测试接口 配置降级规则 同样的用压力测试工具进行测试,每秒发送10个请求,每次都是触发错误,达到降级规则后触发降级熔断
测试接口
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
编写测试接口
@GetMapping("/sentinel07") @SentinelResource("hotKeySentinel07") public String sentinel07( @RequestParam(value = "id",required = false) String id, @RequestParam(value = "name",required = false) String name, @RequestParam(value = "age", required = false) String age ){ System.out.println("---------------------------this is sentinel07"); StringBuilder sb = new StringBuilder(); if (!StringUtils.isBlank(id)) { sb = sb.append("id = " + id +"\t"); } if(!StringUtils.isBlank(name)){ sb = sb.append("name = " + name +"\t"); } if(!StringUtils.isBlank(age)){ sb = sb.append("age = " + age); } System.out.println("sb = " + sb); return "---------------------------this is sentinel07,"+sb; }注意在热点接口上面添加注解@SentinelResource,作唯一标志,然后是热点参数的限流 默认参与QPS模式,参数索引对应接口参数,这里对该接口的第一个参数id做了限流,只要参数里面带有id参数,并且在1秒内请求的数量达到设置的单机阈值,就会抛出异常,注意,这次是抛出异常,而不是限流信息
这个是重点注解,也是必须要掌握的重点,下面列出所有的该注解的属性
属性名是否必填说明value是资源名称 。(必填项,需要通过 value 值找到对应的规则进行配置)entryType否entry类型,标记流量的方向,取值IN/OUT,默认是OUTblockHandler否处理BlockException的函数名称(可以理解为对Sentinel的配置进行方法兜底)。函数要求:1.必须是 public 修饰2.返回类型与原方法一致3. 参数类型需要和原方法相匹配,并在最后加 BlockException 类型的参数。4. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。blockHandlerClass 否存放blockHandler的类。对应的处理函数必须 public static 修饰,否则无法解析,其他要求:同blockHandler。fallback否用于在抛出异常的时候提供fallback处理逻辑(可以理解为对Java异常情况方法兜底)。fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:1.返回类型与原方法一致2.参数类型需要和原方法相匹配,Sentinel 1.6开始,也可在方法最后加 Throwable 类型的参数。3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定fallbackClass里面的方法。fallbackClass否存放fallback的类。对应的处理函数必须static修饰,否则无法解析,其他要求:同fallback。defaultFallback否用于通用的 fallback 逻辑。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:1.返回类型与原方法一致2.方法参数列表为空,或者有一个 Throwable 类型的参数。3.默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。exceptionsToIgnore否指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。exceptionsToTrace否需要trace的异常上面所做的一切,像是限流、降级、热点等一旦遇到异常会直接将异常信息直接打回页面,而且,限流信息都是默认的限流信息:Blocked by Sentinel (flow limiting),为了解决这些问题,就需要用到@SentinelResource,下面开始实战
创建一个handler包,里面存放遇到异常时的自定义异常类(CustomerFallbackHandler)的兜底方法和自定义限流信息类(CustomerBlockHandler)
public class CustomerFallbackHandler { /** * sentinel08异常的兜底方法,注意参数必须一致 * @param e * @return */ public static String sentinel08FallbackMethod(Throwable e) { return "sentinel08异常的兜底方法:" + e.getMessage(); } /** * sentinel09异常的兜底方法,注意参数必须一致 * @param e * @return */ public static String sentinel09FallbackMethod(Long id,Throwable e) { return "id = "+id+",sentinel09异常的兜底方法:" + e.getMessage(); } } public class CustomerBlockHandler { /** * Sentinel08的自定义限流信息,注意参数必须一致 * @param e * @return */ public static String flowLimitBlockExceptionSentinel08(BlockException e){ return "Sentinel08的自定义限流信息"; } /** * Sentinel09的自定义限流信息,注意参数必须一致 * @param id * @param e * @return */ public static String flowLimitBlockExceptionSentinel09(Long id,BlockException e){ return "id = "+id+",Sentinel09的自定义限流信息"; } }创建接口
@GetMapping("/sentinel08") @SentinelResource( value = "sentinel08", //唯一标识 fallback = "sentinel08FallbackMethod", //遇到异常的兜底方法 fallbackClass = CustomerFallbackHandler.class, //遇到异常的兜底方法对应的类 blockHandler = "flowLimitBlockExceptionSentinel08", //自定义限流信息的方法 blockHandlerClass = CustomerBlockHandler.class) //自定义限流信息的方法对应的类 public String sentinel08(){ int a = 10/0; return sentinelServer.sentinel08(); } @GetMapping("/sentinel09") @SentinelResource( value = "sentinel09", //唯一标识 fallback = "sentinel09FallbackMethod", //遇到异常的兜底方法 fallbackClass = CustomerFallbackHandler.class, //遇到异常的兜底方法对应的类 blockHandler = "flowLimitBlockExceptionSentinel09",//自定义限流信息的方法 blockHandlerClass = CustomerBlockHandler.class)//自定义限流信息的方法对应的类 public String sentinel09(@RequestParam(value = "id",required = false) Long id){ int a = 10/id.intValue(); return sentinelServer.sentinel09(id); }注意,上面的接口和对应的fallback、blockHandler 的方法参数必须一致
测试
前面已经创建了这么多的模块,还有关于Sentinel的学习等,下面直接整合Ribbon和openfeign进行服务的调用
首先创建两个服务模块cloud-alibaba-provider-payment9003和cloud-alibaba-provider-payment9004,这里以cloud-alibaba-provider-payment9003为例,9004项目内容基本一致,首先引入pom
<dependencies> <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--web启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--监控--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>application.yml
server: port: 9003 spring: application: name: cloud-alibaba-provider-server cloud: nacos: discovery: server-addr: 39.96.22.34:8080 #将服务注册进nginx代理的nacos集群 namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id #sentinel配置 sentinel: transport: #client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP) dashboard: 127.0.0.1:8080 #sentinel的地址 port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口 management: endpoints: web: exposure: include: '*'主启动类
@SpringBootApplication @EnableDiscoveryClient public class NacosPayment9003 { public static void main(String[] args) { SpringApplication.run(NacosPayment9003.class,args); } }服务层
@Service public class PaymentServer { @Value("${server.port}") private String port; public String payment01(){ return "port:"+port+", this is PaymentServer---->payment01"; } public String payment02(Long id) { return "port:"+port+", this is PaymentServer---->payment02,id = "+id; } }自定义限流方法
public class CustomerBlockHandler { public static String payment02Block(Long id,Throwable e){ return "id = "+id+",this is payment02自定义限流方法,异常:"+e.getMessage(); } public static String payment01Block(Throwable e){ return "this is payment01自定义限流方法,异常:"+e.getMessage(); } }异常的兜底方法
public class CustomerFallbackHandler { public static String payment02Fallback(Long id,Throwable e){ return "id = "+id+",this is payment02异常的兜底方法,异常:"+e.getMessage(); } public static String payment01Fallback(Throwable e){ return "this is payment01异常的兜底方法,异常:"+e.getMessage(); } }编写接口
@RestController public class PaymentController { @Autowired private PaymentServer paymentServer; @GetMapping("/payment01") @SentinelResource( value = "sentinelPayment01", fallback = "payment01Fallback", fallbackClass = CustomerFallbackHandler.class, blockHandler = "payment01Block", blockHandlerClass = CustomerBlockHandler.class ) public String Payment01(){ return paymentServer.payment01(); } @GetMapping("/payment02/{id}") @SentinelResource( value = "sentinelPayment02", fallback = "payment02Fallback", fallbackClass = CustomerFallbackHandler.class, blockHandler = "payment02Block", blockHandlerClass = CustomerBlockHandler.class ) public String payment02(@PathVariable("id") Long id){ if(id == null){ throw new NullPointerException("id 不可以为null"); }else if(id < 0){ throw new IllegalArgumentException("id 不可以为负数"); }else { return paymentServer.payment02(id); } } }上面注意接口的@SentinelResource注解,里面配置了异常的兜底方法和自定义限流方法
同样的,9004项目也是如此,启动9003项目,测试接口
创建客户端cloud-alibaba-nacos-consumer-order84用来调用上面的9003和9004服务,首先引入依赖
<dependencies> <!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--web启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--监控--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--单元测试--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>application.yml
server: port: 84 spring: application: name: nacos-consumer-order84 cloud: nacos: discovery: server-addr: 39.96.22.34:8080 #配置Nacos地址 namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id #sentinel配置 sentinel: transport: #client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP) dashboard: 127.0.0.1:8080 #sentinel的地址 port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口 #激活sentinel对feign的支持 feign: sentinel: enabled: true主启动类
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerOrder84 { public static void main(String[] args) { SpringApplication.run(ConsumerOrder84.class,args); } }服务层接口,用openfeign进行调用
@FeignClient(value = "cloud-alibaba-provider-server",fallback = PaymentServerFallback.class) @Service public interface PaymentServer { @GetMapping("/payment01") public String payment01(); @GetMapping("/payment02/{id}") public String payment02(@PathVariable("id") Long id); }对应实现类用于调用失败的方法处理
@Component public class PaymentServerFallback implements PaymentServer { @Override public String payment01() { return "84---->Payment01:限流方法"; } @Override public String payment02(Long id) { return "84---->Payment02:限流方法,id = "+id; } }编写接口
@RestController public class PaymentController { @Resource private PaymentServer paymentServer; @GetMapping("/consumer/payment01") public String Payment01(){ return paymentServer.payment01(); } @GetMapping("/consumer/payment02/{id}") public String payment02(@PathVariable("id") Long id){ return paymentServer.payment02(id); } }最后启动9003项目、9004项目和84项目 客户端调用服务端 关闭9003和9004项目,再次调用走的是自定义限流方法
当Sentinel Dashboard中添加的规则是存储在内存中的,只要项目一重启规则就丢失了,所以需要将规则持久化到nacos中,在nacos中添加规则,然后同步到dashboard中
以8401为例,实现规则的持久化,加入下面的依赖
<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>修改配置文件
server: port: 8401 spring: application: name: cloud-alibaba-sentinel-server8401 cloud: nacos: discovery: server-addr: 39.96.22.34:8080 #将服务注册进nginx代理的nacos集群 namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id #sentinel配置 sentinel: transport: #client-ip: 127.0.0.1 #客户端IP(指定部署springboot项目云服务器的外网IP) dashboard: 127.0.0.1:8080 #sentinel的地址 port: 8719 #默认端口,假如被占用会自动从8719开始依次+1扫描,直至找到没有被占用的端口 #sentinel持久化到nacos,存储在nacos配置文件中 datasource: dsl: nacos: server-addr: 39.96.22.34:8080 #nacos的地址 groupId: DEV_GROUP # 配置文件的分组 namespace: ffee2bbe-5ba5-4e6e-a822-6a00abe11769 #命名空间的id dataId: sentinel-server8401 # 名字与在nacosc创建的持久化文件名一致 data-type: json #文件类型是json rule-type: flow # 路由存储规则 # datasource: #添加Nacos数据源配置 # ds1: # nacos: # server-addr: 39.96.22.34:8080 #nacos的地址 # data-id: sentinel-server8401 # 名字与在nacosc创建的持久化文件名一致 # group-id: DEV_GROUP # 配置文件的分组 # data-type: json #文件类型是json # rule-type: flow # 路由存储规则 management: endpoints: web: exposure: include: '*'重点加入datasource 然后再nacos服务上对应配置文件进行相应文件的创建即可 json文件解读 启动项目即可直接看到sentinel配置了。