目录
3.3、创建ProviderSentinelController 类
4.1.2、分析异常 创建DefaultRequestOriginParser类
4.1.3、创建ServiceBlockExceptionHandler类
4.5.1、创建RequestOriginParser接口的实现类
4.5.3、刷新访问localhost:8082/provider/sentinel01?origin=app2
1、Sentinel概述
Sentinel:流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
流量控制:
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果进行资源调用。
熔断降级:
通过并发线程数进行限制 和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。 通过响应时间对资源进行降级 除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
系统负载保护 :
Sentinel 同时对系统的维度提供保护。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。 针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
Sentinel | |
隔离策略 | 信号量隔离 |
熔断降级策略 | 基于响应时间或失败比例 |
实时指标实现 | 滑动窗口 |
规则配置 | 支持多种数据源 |
扩展性 | 多个扩展点 |
基于注解的支持 | 支持注解 |
限流 | 基于QPS,支持基于调用关系的限流 |
流量整形 | 支持慢启动,匀速器模式 |
系统负载保护 | 支持负载保护 |
控制台 | 开箱即用,可配置规则,查看秒级监控,机器发现等 |
常见的价格适配 | Servlet,Spring Cloud,Dubbo,gRPC等 |
Sentinel两大核心:
核心库(Java 客户端):能够运行于所有 Java 运行时环境,同时对Dubbo /Spring Cloud 等框架也有较好的支持。
控制台(Dashboard):基于 Spring Boot 开发,打包后可以直接运行。
2、安装sentinel
2.1、下载sentinel的1.8.1版本
sentinel 下载点我 sentinel和之前的依赖版本是有匹配度的,若版本太超前会导致不匹配发生冲突的问题,此处下载1.8.1版本即可。
2.2、加载sentinel1.8.1版本的jar包
在你的项目目录下新建目录sentinel,并在其下打开命令行操作,加载sentinel-dashboard-1.8.1.jar
java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
3、关于sentinel配置
3.1、pom.xml导入
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
3.2、bootstrap.yml配置
sentinel:
transport: #随意制定一个未使用的端口号
dashboard: localhost:8180 #制定当前sentinel控制台的地址
eager: true #服务启动后就发送一个心跳信息
web-context-unify: false
#注意sentinel和nacos属于同级 注意缩进
3.3、创建ProviderSentinelController 类
package com.jt.provider.controller;
import com.jt.provider.service.ResourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/provider")
public class ProviderSentinelController {
@RequestMapping("/sentinel01")
public String doSentinel01(){
return "sentinel 01 test ok!";
}
@RequestMapping("/sentinel02")
public String doSentinel02(){
return "sentinel 02 test ok!";
}
@Autowired
private ResourceService resourceService;
@RequestMapping("/sentinel03")
public String doSentinel03(){
resourceService.doGetResource();
return "sentinel 03 test ok!";
}
}
3.4、创建ResourceService类
package com.jt.provider.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
@Service
public class ResourceService {
@SentinelResource("doGetResource")
public String doGetResource(){
return "do get resource!";
}
}
启动服务器进入sentinel dashboard
3.5、sentinel入门
启动sca-provider服务后对制定的服务进行访问
刷新控制台查看实时监控
3.6、设置限流策略
选择需要限流的链路进行限流,设置限流策略
找到制定的服务进行访问
反复刷新会出现如下,会有限流信息输出
3.7、关联设置
sentinel的流控,设置关联
当刷新02时在1s内刷新01会出现如下限流信息出来
3.8、链路设置
链路模式下得限流操作
刷新一次出现如下,若反复刷新会报500错,表示被限流了,得等一等再次刷新即可
4、降级设置
4.1、降级设置准备
4.1.1、编辑sentinel04方法
//AtomicLong 类支持线程安全的自增自减操作
private AtomicLong atomicLong=new AtomicLong(1);
@GetMapping("/sentinel04")
public String doSentinel04() throws InterruptedException {
//获取自增对象的值,然后再加1
long num=atomicLong.getAndIncrement();
if(num%2==0){//模拟50%的慢调用比例
Thread.sleep(200);
}
return "sentinel 04 test";
}
//http://localhost:8082/provider/sentinel/findById?id=10
@GetMapping("/sentinel/findById")
@SentinelResource("resource")
public String doFindById(@RequestParam("id") Integer id){
return "resource id is "+id;
}
4.1.2、分析异常 创建DefaultRequestOriginParser类
package com.jt.provider.controller;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* 构建RequestOriginParser对象,对请求数据进行解析
* 1)请求行
* 2)请求头
* 3)请求体
*/
@Component
public class DefaultRequestOriginParser
implements RequestOriginParser {
/**
* 当设置了授权规则后,系统底层拦截到请求,会调用此方法,对请求数据进行解析
* httpServletRequest
* http://ip:port/path?origin=aaa
*/
@Override
public String parseOrigin(HttpServletRequest request) {
String origin=request.getParameter("origin");
return origin;
}
}
4.1.3、创建ServiceBlockExceptionHandler类
package com.jt.provider.controller;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
/**
* 自定义限流,降级等异常处理对象
*/
@Slf4j
@Component
public class ServiceBlockExceptionHandler implements BlockExceptionHandler {
/**
* 用于处理BlockException类型以及子类类型异常
*/
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
BlockException e) throws Exception {
//设置响应数据编码
response.setCharacterEncoding("utf-8");
//告诉客户端响应数据的类型,以及客户端显示内容的编码
response.setContentType("text/html;charset=utf-8");
//向客户端响应一个json格式的字符串
//String str="{\"status\":429,\"message\":\"访问太频繁了\"}";
Map<String,Object> map=new HashMap<>();
map.put("status", 429);
map.put("message","访问太频繁了");
String jsonStr=new ObjectMapper().writeValueAsString(map);
PrintWriter out = response.getWriter();
out.print(jsonStr);
out.flush();
out.close();
}
}
4.2、选择要降级的资源04,进行配置新增。
4.3、多次访问测试
4.4、热点设置
4.4.1、添加doFindById方法
//http://localhost:8082/provider/sentinel/findById?id=10
@GetMapping("/sentinel/findById")
@SentinelResource("resource")
public String doFindById(@RequestParam("id") Integer id){
return "resource id is "+id;
}
4.4.2、刷新访问
4.4.3、新增热点设置
4.5、授权设置
4.5.1、创建RequestOriginParser接口的实现类
package com.jt.provider.controller;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* 构建RequestOriginParser对象,对请求数据进行解析
* 1)请求行
* 2)请求头
* 3)请求体
*/
@Component
public class DefaultRequestOriginParser
implements RequestOriginParser {
/**
* 当设置了授权规则后,系统底层拦截到请求,会调用此方法,对请求数据进行解析
* httpServletRequest
* http://ip:port/path?origin=aaa
*/
@Override
public String parseOrigin(HttpServletRequest request) {
String origin=request.getParameter("origin");
return origin;
}
}
4.5.2、新增授权规则
使用 Sentinel 的黑白名单控制,根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过.
4.5.3、刷新访问localhost:8082/provider/sentinel01?origin=app2
当把app2从授权规则中去除则可以访问
5、拦截器
5.1、创建FrameworkTests类进行测试
package com.jt.common.interceptor;
import java.time.LocalTime;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/*
* 如何理解框架?设计好一个半成品(类似模板)
* 框架设计时会有一些对象的定义以及这些对象的执行流程,类似一个执行链
* */
//定义一个拦截器对象的接口
interface HandlerInterceptor{
default void before(){}
default void after(){}
}
//处理器接口
interface Handler{
void processed();//处理业务的方法
}
//定义一个执行链
class ExecutionChain{//我是执行链的设计者
private List<HandlerInterceptor> interceptors=new CopyOnWriteArrayList<>();//拦截器
//new CopyOnWriteArrayList<>()确保线程安全
private Handler handler;//处理器
public ExecutionChain(List<HandlerInterceptor> interceptors,Handler handler){
this.handler=handler;
this.interceptors.addAll(interceptors);}//此构造方法 给上面类里属性赋值传参
public void execute(){
//负责执行业务的方法(处理请求,不考虑参数...)
//1.before
for (int i = 0; i <interceptors.size() ; i++) {
interceptors.get(i).before();
}
//2.processed
handler.processed();
//3.after
for (int i = interceptors.size()-1; i >=0 ; i--) {
interceptors.get(i).after();
}
}
}
public class FrameworkTests {
public static void main(String[] args) {
//创建执行链和执行链对象
List<HandlerInterceptor> interceptors=new CopyOnWriteArrayList<>();
interceptors.add(new HandlerInterceptor() {
@Override
public void before() {
System.out.println("记录考试开始时间"+ LocalTime.now());
}
@Override
public void after() {
System.out.println("考试结束时间:"+LocalTime.now());
}
});
ExecutionChain chain=new ExecutionChain(interceptors, new Handler() {
@Override
public void processed() {
System.out.println("开始考试");
}
});
//执行执行链
chain.execute();
}
}
5.2、测试结果
5.3、创建TimeInterceptor类
package com.jt.provider.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalTime;
public class TimeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)throws Exception{
System.out.println("=====拦截器已运行=====");
LocalTime now =LocalTime.now();//获取当前时间
int hour =now.getHour();//获取当前时间的小时单位
if (hour <8||hour>=21)
throw new RuntimeException("请在8~21点之间访问");
return true;
}
创建SpringWebConfig类
package com.jt.provider;
import com.jt.provider.interceptor.TimeInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class SpringWebConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new TimeInterceptor())
.addPathPatterns("/provider/sentinel01");
}
}
对01方法进行拦截(把时间改了if (hour <8||hour>=12))
把时间改了(hour <8||hour>=21)