springCloud — 高级篇(2)
本系列笔记涉及到的代码在GitHub上,地址:https://github.com/zsllsz/cloud
本文涉及知识点:
-
sentinel降级;
-
sentinel熔断;
-
sentinel规则持久化;
欢迎大家关注我的公众号 javawebkf,目前正在慢慢地将简书文章搬到公众号,以后简书和公众号文章将同步更新,且简书上的付费文章在公众号上将免费。
一、springCloud Alibaba sentinel 之降级规则
上一篇已经说了sentinel的流控,接下来看看sentinel的降级。
1、基本介绍:
sentinel的降级没有半开状态,有如下3种策略:
-
RT(平均响应时间):平均响应时间超出阈值且在时间窗口期内通过的大于等于5,两个条件同时满足后触发降级。窗口期过后关闭断路器。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
-
异常比例(秒级):QPS大于等于5且异常比例超过阈值时,触发降级。时间窗口结束后,关闭降级。
-
异常数(分钟级):异常数超过阈值时触发降级,时间窗口结束后关闭降级。
2、RT策略实例:
- 在8401的controller中加上一个方法,如下:
@GetMapping("/testC")
public String testC() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "=========== test RT ==========";
}
- sentinel降级配置:
这里配置的意思就是,1秒钟内有超过5个请求进入时,要求每个请求testC在200毫秒内响应,如果没有响应,那就跳闸1秒中,接下来的1秒内的请求都会被降级,1秒后恢复。用jmeter请求testC,用10个线程去请求,每1秒请求1次。然后再在浏览器请求testC,就会发现访问不了,返回Blocked by Sentinel (flow limiting)
。
3、异常比例策略实例:
- 在8401的controller中加上一个方法,如下:
@GetMapping("/testD")
public String testD() {
int x = 10 / 0;
return "============ test 异常比例 ============";
}
- sentinel降级配置:
这个配置意思就是,1秒中内超过5个请求的时候,如果有超过5*0.2=1
个请求异常了,那么在接下来的2秒内都会拉闸断电。降级后访问结果如下:
4、异常数策略实例:
最近1分钟内请求发生异常的数量超过阈值时会触发降级。由于这里的异常数是分钟级别统计的,所以如果时间窗口期设置的小于60秒,则结束熔断后可能再次进入熔断状态。所以,时间窗口期要大于等于60秒。
- 仍旧用testD进行测试
- sentinel降级配置:
这个配置表示,1分钟内对testD的请求发生异常的次数超过3次,那么接下来的66秒内都会拉闸断电。我们在1分钟内访问3次,然后再去访问,就会返回Blocked by Sentinel (flow limiting)
。
二、springCloud Alibaba sentinel 之热点规则
1、是什么?
就是针对热点数据做限流。比如id为1的商品是热点数据,那么可以针对id为1的这个商品做限流。
2、热点限流实例配置:
- 在8401的controller中加一个方法,如下:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey") // 这个value值随意,只要唯一即可,但是一般和@GetMapping中的一致
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "test hot key";
}
/**
* 兜底方法,参数除了原方法的参数,还要加上BlockException
* @param p1
* @param p2
* @param e
* @return
*/
public String deal_testHotKey(String p1, String p2, BlockException e) {
return "兜底方法";
}
- 热点规则配置:
这里配置的意思就是,testHotKey(就是@SentinelResource中的value值)这个资源,我对索引为0的参数(p1)进行监控,如果访问testHotKey带上了p1,并且QPS超过了1,那么接下来的1秒中内这个方法都会被降级。注意:索引为0的参数是p1,是controller中接收参数的顺序的索引。你访问http://192.168.0.104:8401/sentinel/testHotKey?p2=1
,这里只有一个参数p2,在url中它是第0个参数,但是在controller中不是,所以这样访问并不会被降级。
热点配置的高级选项:
- 参数例外项:上面的配置对p1进行限流,不管p1的值是多少,只要QPS超过1,就降级。现在的需求是如果p1的值是5,我就搞特殊的,因为它充了钱,所以让它QPS超过100才限流。配置如下图:
当参数不是5时,QPS超过1就会被限流降级,p1的值为5时,你狂点都可以正常访问。
注意:@SentinelResource只管我们控制台配置的违规情况,才会进行兜底,假如程序异常了,它是管不了的。比如我们在return 前加一行int a = 10 / 0
,它还是会返回error page的,而不是兜底方法。
三、springCloud Alibaba sentinel 之系统规则
上面的限流降级都是针对某个具体方法而言的,系统规则就是针对整个微服务系统来说的。比如配置了QPS阈值为100,也就是说,这个微服务的QPS超过100,整个服务都不可用了。就是控制的粒度更粗了,生产中不建议用这种方式。
1、系统规则的阈值类型:
- LOAD(对Linux和Unix机器生效):当系统装载数超过阈值就会限流,阈值一般设置为
cpu核心数 * 2.5
- RT(平均响应时间):单台机器上所有入口流量平均RT超过阈值时进行限流降级
- 线程数:单台机器上的所有入口流量的并发线程数超过阈值时进行限流降级
- 入口QPS:当单台机器所有入口流量的QPS超过阈值时进行限流降级
- CPU使用率:当系统cpu使用率超过阈值时进行限流降级
2、配置全局QPS:
之前testB是没配置任何限流规则的,1秒点n次都可以,现在配了系统规则后,发现testB也是只能1秒钟点1次了,说明配置生效。
四、sentinel的@SentinelResource详细用法
上面做限流时用到过这个注解,但是没说其用法,下面来学习一下它的用法。
1、按资源名称限流 + 后续处理:
- 修改8401的pom,添加自己定义的common模块,如下:
com.zhu.springcloud
cloud-api-commons
${project.version}
- 8401中新加一个controller,如下:
@RestController
@RequestMapping("/ratelimit")
public class RateLimitController {
@GetMapping("/bySource")
@SentinelResource(value = "bySource", blockHandler = "handleException")
public JsonResult> bySource() {
return new JsonResult(200, "按资源名称限流测试通过", new Payment(1L, "6666"));
}
public JsonResult> handleException(BlockException e){
return new JsonResult<>(400, e.getClass().getCanonicalName() + "/t服务不可用");
}
}
- 然后新增流控规则,阈值类型选择QPS,阈值设置为1。然后访问bySource,点击快一点,就返回如下内容:
{
code: 400,
message: "com.alibaba.csp.sentinel.slots.block.flow.FlowException 服务不可用",
data: null
}
- 现在关闭8401,然后刷新sentinel dashboard,发现刚才添加的流控规则没了。所以后续得做持久化处理。
2、按url进行限流:
什么叫按资源名称,什么叫按url?看下图:
第一个/ratelimit/bySource
就是url,第二个bySource
就是资源名称。上面演示了按资源名称来限流,下面演示按url来限流。
- 在RateLimitController 中添加如下方法:
@GetMapping("/byUrl")
@SentinelResource(value = "byUrl")
public JsonResult> byUrl(){
return new JsonResult(200, "按url限流测试通过", new Payment(1L, "6666"));
}
- 配置的时候,选择url,添加流控,阈值类型QPS,阈值1。QPS超过1就会返回错误提示
Blocked by Sentinel (flow limiting)
。
这两个配置案例主要是两个知识点:
- 可以按url配,也可以按资源名成配
- 自己写了blockHandler就会走自己写的,没写就返回默认提示
上面的配置案例存在的问题:
- 兜底方案与业务代码耦合
- 每个方法都要写一个兜底方法,即没有做全局的兜底方法
- 服务一关闭,配置就没有了,即没有做持久化
下面就来解决这些问题。
3、自定义限流处理逻辑:
这是为了解决上面的第一第二个问题的。
- 自定义限流处理类:
public class CustomerBlockHandler {
public static JsonResult> handlerException1(BlockException e){
return new JsonResult(444, "自定义返回信息1");
}
public static JsonResult> handlerException2(BlockException e){
return new JsonResult(444, "自定义返回信息2");
}
}
- RateLimitController:
@GetMapping("/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException1")
public JsonResult> customerBlockHandler(){
return new JsonResult(200, "自定义限流处理测试通过", new Payment(1L, "6666"));
}
- sentinel控制台配置:也是配置QPS、1就好了
- 测试:1秒点击两次,发现返回:
{
code: 444,
message: "自定义返回信息1",
data: null
}
说明自定义返回信息成功了。
五、sentinel的熔断功能
1、Ribbon系类:
新建名为cloudalibaba-provider-payment9003和cloudalibaba-provider-payment9004的module,两个module只是端口不一致:
- pom.xml:
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-devtools
runtime
true
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.zhu.springcloud
cloud-api-commons
${project.version}
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
- application.yml:
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 192.168.0.106:8848
management:
endpoints:
web:
exposure:
include:
- "*"
- 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) throws Exception {
SpringApplication.run(PaymentMain9003.class, args);
}
}
- 业务类:
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Value("${server.port}")
private int port;
public static Map map = new HashMap<>();
// 偷懒,不去连数据库查记录了
static {
map.put(1L, new Payment(1L,"111"));
map.put(1L, new Payment(2L,"222"));
map.put(1L, new Payment(3L,"333"));
}
@GetMapping("/{id}")
public JsonResult> payment(@PathVariable("id") Long id){
return new JsonResult<>(200, "success from " + port, map.get(id));
}
}
新建名为cloudalibaba-consumer-nacos-order84的消费者,通过ribbon去调用9003和9004:
- pom.xml:
com.zhu.springcloud
cloud-api-commons
${project.version}
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
- application.yml:
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 192.168.0.106:8848
sentinel:
transport:
client-ip: 192.168.0.104
dashboard: 192.168.0.106:8080
port: 8719
service-url:
nacos-user-service: http://nacos-payment-provider
- 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class OrderMain84 {
public static void main(String[] args) throws Exception {
SpringApplication.run(OrderMain84.class, args);
}
}
- ribbon配置类:
@Configuration
public class RibbonConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- controller:
@RestController
@RequestMapping("/order")
public class OrderController {
@Value("${service-url.nacos-user-service}")
private String url;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/fallback/{id}")
@SentinelResource(value = "fallback")
public JsonResult> fallback(@PathVariable("id") Long id){
JsonResult> result = restTemplate.getForObject(url + "/payment/" + id, JsonResult.class);
if (id == 4) {
throw new IllegalArgumentException("非法参数");
} else if (result.getData() == null) {
throw new NullPointerException("没有该id对应的记录");
}
return result;
}
}
现在访问http://192.168.0.104:84/order/fallback/1
,可以成功返回信息,并且一次9003一次9004。如果传的id是4,就会返回error page,非法参数,因为我们没有任何配置。要解决这个问题,可以在@SentinelResource中加上fallback属性,属性值就是发生异常时要调用的方法名,如下:
public JsonResult> handlerFallback(@PathVariable("id") Long id, Throwable e){
return new JsonResult<>(444, "没有id" + id + "对应的记录,这是兜底方法," + e.getMessage());
}
这样,再去访问http://192.168.0.104:84/order/fallback/4
,返回的信息就如下:
{
code: 444,
message: "没有id4对应的记录,这是兜底方法,非法参数",
data: null
}
这里说一下fallback和blockHandler的区别:
- fallback:管运行异常,运行时发生异常了,就走fallback
- blockHandler:sentinel控制台的配置违规处理,就是前面讲的那些降级规则,违规了就走blockHandler
下面就看一下两个都配置的情况:
@GetMapping("/fallback/{id}")
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
public JsonResult> fallback(@PathVariable("id") Long id){
JsonResult> result = restTemplate.getForObject(url + "/payment/" + id, JsonResult.class);
if (id == 4) {
throw new IllegalArgumentException("非法参数");
} else if (result.getData() == null) {
throw new NullPointerException("没有该id对应的记录");
}
return result;
}
public JsonResult> handlerFallback(@PathVariable("id") Long id, Throwable e){
return new JsonResult<>(444, "没有id" + id + "对应的记录,这是兜底方法," + e.getMessage());
}
public JsonResult> blockHandler(@PathVariable("id") Long id, BlockException e){
return new JsonResult<>(445, "id" + id + "的记录配置违规了,这是兜底方法," + e.getMessage());
}
然后在sentinel控制台配置一条流控规则,QPS阈值为1。现在访问http://192.168.0.104:84/order/fallback/4
,如果慢悠悠地点,返回的是fallback的内容,如果快快地点,QPS大于1,返回的就是blockHandler的内容。
异常忽略属性:exceptionsToIgnore = {XxxException.class}
:@SentinelResource注解还有这个属性可以配置,表示运行时发生了XxxException就忽略掉,不会走兜底的方法。
2、openFeign系列:
上面的order84是通过ribbon去调用9003和9004的,这里再来演示一下通过openFeign调用服务端的时候,如果用sentinel做熔断降级相关配置。新建一个名为cloudalibaba-consumer-nacos-order85的module:
- pom.xml:相比order84,只多了一个openfeign:
org.springframework.cloud
spring-cloud-starter-openfeign
- application.yml:相比order84,就是增加了sentinel激活openfeign的配置
server:
port: 85
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 192.168.0.106:8848
sentinel:
transport:
client-ip: 192.168.0.104
dashboard: 192.168.0.106:8080
port: 8719
service-url:
nacos-user-service: http://nacos-payment-provider
# 激活sentinel对openfeign的支持
feign:
sentinel:
enabled: true
- 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 激活openfeign
public class OrderMain85 {
public static void main(String[] args) throws Exception {
SpringApplication.run(OrderMain85.class, args);
}
}
- service:
@FeignClient(value = "nacos-payment-provider", fallback = OrderServiceImpl.class)
public interface OrderService {
@GetMapping("/payment/{id}")
public JsonResult> payment(@PathVariable("id") Long id);
}
- serviceImpl:
@Component
public class OrderServiceImpl implements OrderService{
@Override
public JsonResult> payment(Long id) {
return new JsonResult<>(446, "这是兜底的方法");
}
}
- controller:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/fallback/{id}")
public JsonResult> fallback(@PathVariable("id") Long id){
return orderService.payment(id);
}
}
现在访问http://192.168.0.104:85/order/fallback/1
可以成功调用9003和9004,现在把9003和9004服务停掉,再次访问,就返回了:
{
code: 446,
message: "这是兜底的方法",
data: null
}
说明降级配置成功。
3、熔断框架比较:
六、sentinel规则持久化
目前我们没有做sentinel规则持久化,也就是说,在sentinel控制台配置的规则,只要我们微服务一重启,配置的规则就消失了。
1、sentinel规则持久化的方案:
将规则配置进nacos进行保存,nacos也做了数据库的持久化,所以相当于间接地把sentinel规则也写进了数据库。
2、步骤:
以8401的那个项目为例,进行如下修改:
- pom.xml:添加如下依赖:
com.alibaba.csp
sentinel-datasource-nacos
- application.yml:添加nacos数据源配置
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: 192.168.0.106:8848
sentinel:
transport:
dashboard: 192.168.0.106:8080
client-ip: 192.168.0.104
port: 8719 # 默认8719,如果被占用会依次加1,直至找到没有被占用的端口
datasource: # 将规则配置进nacos
dsl:
nacos:
server-addr: 192.168.0.106:8848
data-id: cloudalibaba-sentinel-service
group-id: DEFAULT_GROUP
rule-type: flow
data-type: json
# actuator图形化配置
management:
endpoints:
web:
exposure:
include:
- "*"
- 然后在nacos中新建配置:Data ID就是8401的服务名称,group用默认的,配置格式选json,配置内容如下:
[
{
"resource":"/ratelimit/byUrl",
"limitApp":"default",
"grade":1,
"count":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":true
}
]
现在说明一下json中各个字段的意思:
- resource:资源名称
- limitApp:来源应用
- grade:阈值类型,0表示线程数,1表示QPS
- count:单机阈值
- strategy:流控模式,0表示直接,1表示关联,2表示链路
- controlBehavior:流控效果,0表示快速失败,1表示warm up,2表示排队等待
- clusterMode:是否集群
然后重启8401,访问一下/ratelimit/byUrl,然后发现sentinel流控中就有相关规则了,然后关闭8401,再重启,发现规则还在,并不用重新配置。关于sentinel的持久化还有很多方式,以后再细说。
共有 0 条评论