一、简介
1.sentinel主要功能是微服务的限流、熔断、降级。相比与hystrix它提供了更为完善的控制台、流量整形、系统负载保护。
2.sentinel中重要的两个概念:资源(被限流或者降级的代码)、规则(具体限流或者降级触发的条件)
二、使用
1.引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>1.8.4</version>
</dependency>
如果是在web层的接口,因为引入的sentinel依赖中对springmvc框架有扩展,它会自动将接口识别为资源,如果是在service中使用如下,主要通过 @SentinelResource注解标注代码成为资源。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
@SentinelResource(value = "getUser",blockHandler = "handleException")
public UserEntity getById(Integer id) {
return userDao.getById(id);
}
public UserEntity handleException(Integer id, BlockException ex) {
UserEntity userEntity = new UserEntity();
userEntity.setUsername("===被限流降级啦===");
return userEntity;
}
}
三、原理
原理简介: sentinel实际上是对被定义为资源的代码前后进行增强。当请求第一次访问资源时(针对springmvc接口的资源),会初始化Sph sph = new CtSph()对象。
1.BIO的方式开启一个端口,这个端口会用来接受控制台发送的请求
2.会开启心跳检测任务,这个任务会携带客户端的信息(包含但不仅限于IP、Port)发送到sentinel控制台
3.会初始化一个slotchain用来检验请求是否满足各种规则。
上图是整体的通信流程,下面是整体的数据流程:
sentinel的持久化
通过这个流程图我们能发现一个问题,就是规则都保存在客户端。一旦我们微服务重启或者如果我们的微服务是分布式部署,那么配置的规则就会丢失或者只存在其中一台机器上。那么sentinel的规则持久化就是很有必要的。
1.下面介绍使用nacos作为配置持久化的流程。
上面这种模式存在的问题:
1.sentinel控制台修改的配置不会同步修改到nacos。
2.微服务端(sentinel客户端)通过代码定义的规则不会推到nacos
所以需要扩展对nacos的写功能
四、sentinel规则中用到的几种算法
1.时间滑动窗口
代码实现:
/**
* 滑动时间窗口限流实现
* 假设某个服务最多只能每秒钟处理100个请求,我们可以设置一个1秒钟的滑动时间窗口,
* 窗口中有10个格子,每个格子100毫秒,每100毫秒移动一次,每次移动都需要记录当前服务请求的次数
*/
public class SlidingTimeWindow {
//服务访问次数,可以放在Redis中,实现分布式系统的访问计数
Long counter = 0L;
//使用LinkedList来记录滑动窗口的10个格子。
LinkedList<Long> slots = new LinkedList<Long>();
public static void main(String[] args) throws InterruptedException {
SlidingTimeWindow timeWindow = new SlidingTimeWindow();
new Thread(new Runnable() {
@Override
public void run() {
try {
timeWindow.doCheck();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
while (true){
//TODO 判断限流标记
timeWindow.counter++;
Thread.sleep(new Random().nextInt(15));
}
}
private void doCheck() throws InterruptedException {
while (true) {
slots.addLast(counter);
if (slots.size() > 10) {
slots.removeFirst();
}
//比较最后一个和第一个,两者相差100以上就限流
if ((slots.peekLast() - slots.peekFirst()) > 100) {
System.out.println("限流了。。");
//TODO 修改限流标记为true
}else {
//TODO 修改限流标记为false
}
Thread.sleep(100);
}
}
}
2.漏桶算法
水的流出速度是固定的,流入速度不限制。相当于有大量请求进来,但是处理请求的速度是固定的。
代码实现:
/**
* 漏桶限流算法
*/
public class LeakyBucket {
public long timeStamp = System.currentTimeMillis(); // 当前时间
public long capacity; // 桶的容量
public long rate; // 水漏出的速度(每秒系统能处理的请求数)
public long water; // 当前水量(当前累积请求数)
public boolean limit() {
long now = System.currentTimeMillis();
water = Math.max(0, water - ((now - timeStamp)/1000) * rate); // 先执行漏水,计算剩余水量
timeStamp = now;
if ((water + 1) < capacity) {
// 尝试加水,并且水还未满
water += 1;
return true;
} else {
// 水满,拒绝加水
return false;
}
}
}
3.令牌桶算法
将令牌用固定速率放入桶中,每个请求想要通过都必须持有令牌。相当于限制了最高的请求数,但是过程中可变。
代码实现:
/**
* 令牌桶限流算法
*/
public class TokenBucket {
public long timeStamp = System.currentTimeMillis(); // 当前时间
public long capacity; // 桶的容量
public long rate; // 令牌放入速度
public long tokens; // 当前令牌数量
public boolean grant() {
long now = System.currentTimeMillis();
// 先添加令牌
tokens = Math.min(capacity, tokens + (now - timeStamp) * rate);
timeStamp = now;
if (tokens < 1) {
// 若不到1个令牌,则拒绝
return false;
} else {
// 还有令牌,领取令牌
tokens -= 1;
return true;
}
}
}
4.longadder原子累加器
原子累加器中重要的三个变量,在没有线程并发时使用base进行计数累加。在有并发时使用cells单独统计每个线程的累加。cellsBusy主要是为了用来标记cells是否有其它线程创建。