今年双 11 全民购物狂欢节进入第十一个年头,1 分 36 秒,交易额冲到 100 亿 !比 2018 年快了近 30 秒,比 2017 年快了近 1 分半!这个速度再次刷新天猫双 11 成交总额破 100 亿的纪录。web
服务等级协议
咱们常说的 N 个 9,就是对 SLA 的一个描述。SLA 全称是 Service Level Agreement,翻译为服务水平协议,也称服务等级协议,它代表了公有云提供服务的等级以及质量。算法
例如阿里云对外承诺的就是一个服务周期内集群服务可用性不低于 99.99%,若是低于这个标准,云服务公司就须要赔偿客户的损失。数据库
作到 4 个 9 够好了吗缓存
对互联网公司来讲,SLA 就是网站或者 API 服务可用性的一个保证。服务器
9 越多表明整年服务可用时间越长服务更可靠,4 个 9 的服务可用性,听起来已经很高了,但对于实际的业务场景,这个值可能并不够。微信
咱们来作一个简单的计算,假设一个核心链路依赖 20 个服务,强依赖同时没有配置任何降级,而且这 20 个服务的可用性达到 4 个 9,也就是 99.99%。架构
微服务的雪崩效应并发
限流降级怎么作
缓存以及队列等手段,增长系统的容量。限流和降级则是关心在到达系统瓶颈时系统的响应,更看重稳定性。app
限流和降级负载均衡
超时降级
失败次数降级
故障降级

熔断隔离
若是不对服务资源作隔离,一旦一个服务出现了问题,整个系统的稳定性都会受到影响!服务隔离的目的就是避免服务之间相互影响。
何处隔离:一次服务调用,涉及到的是服务提供方和调用方,咱们所指的资源,也是两方的服务器等资源,服务隔离一般能够从提供方和调用方两个方面入手。
隔离什么:广义的服务隔离,不只包括服务器资源,还包括数据库分库,缓存,索引等,这里咱们只关注服务层面的隔离。
降级和熔断的区别
熔断,通常是中止服务:典型的就是股市的熔断,若是大盘不受控制,直接休市,不提供服务,是保护大盘的一种方式。
降级,一般是有备用方案:从北京到济南,下雨致使航班延误,我能够乘坐高铁,若是高铁票买不到,也能够乘坐汽车或者开车过去。
二者的区别:降级通常是主动的,有预见性的,熔断一般是被动的,服务 A 降级之后,通常会有服务 B 来代替,而熔断一般是针对核心链路的处理。
经常使用限流算法设计
计数器法
这时候判断,若是计数器的值小于限流值,而且与上一次请求的时间间隔还在一分钟内,容许请求经过,不然拒绝请求,若是超出了时间间隔,要将计数器清零。
public class CounterLimiter {
//初始时间
private static long startTime = System.currentTimeMillis();
//初始计数值
private static final AtomicInteger ZERO = new AtomicInteger(0);
//时间窗口限制
private static final long interval = 10000;
//限制经过请求
private static int limit = 100;
//请求计数
private AtomicInteger requestCount = ZERO;
//获取限流
public boolean tryAcquire() {
long now = System.currentTimeMillis();
//在时间窗口内
if (now < startTime + interval) {
//判断是否超过最大请求
if (requestCount.get() < limit) {
requestCount.incrementAndGet();
return true;
}
return false;
} else {
//超时重置
startTime = now;
requestCount = ZERO;
return true;
}
}
}
漏桶算法
漏桶算法的示意图以下:
这里简单实现一下,也可使用 Guava 的 SmoothWarmingUp 类,能够更好的控制漏桶算法:
public class LeakyLimiter {
//桶的容量
private int capacity;
//漏水速度
private int ratePerMillSecond;
//水量
private double water;
//上次漏水时间
private long lastLeakTime;
public LeakyLimiter(int capacity, int ratePerMillSecond) {
this.capacity = capacity;
this.ratePerMillSecond = ratePerMillSecond;
this.water = 0;
}
//获取限流
public boolean tryAcquire() {
//执行漏水,更新剩余水量
refresh();
//尝试加水,水满则拒绝
if (water + 1 > capacity) {
return false;
}
water = water + 1;
return true;
}
private void refresh() {
//当前时间
long currentTime = System.currentTimeMillis();
if (currentTime > lastLeakTime) {
//距上次漏水的时间间隔
long millisSinceLastLeak = currentTime - lastLeakTime;
long leaks = millisSinceLastLeak * ratePerMillSecond;
//容许漏水
if (leaks > 0) {
//已经漏光
if (water <= leaks) {
water = 0;
} else {
water = water - leaks;
}
this.lastLeakTime = currentTime;
}
}
}
}
令牌桶算法
若是令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。

最后桶中能够保存的最大令牌数永远不会超过桶的大小,每当一个请求过来时,就会尝试从桶里移除一个令牌,若是没有令牌的话,请求没法经过。
public class TokenBucketLimiter {
private long capacity;
private long windowTimeInSeconds;
long lastRefillTimeStamp;
long refillCountPerSecond;
long availableTokens;
public TokenBucketLimiter(long capacity, long windowTimeInSeconds) {
this.capacity = capacity;
this.windowTimeInSeconds = windowTimeInSeconds;
lastRefillTimeStamp = System.currentTimeMillis();
refillCountPerSecond = capacity / windowTimeInSeconds;
availableTokens = 0;
}
public long getAvailableTokens() {
return this.availableTokens;
}
public boolean tryAcquire() {
//更新令牌桶
refill();
if (availableTokens > 0) {
--availableTokens;
return true;
} else {
return false;
}
}
private void refill() {
long now = System.currentTimeMillis();
if (now > lastRefillTimeStamp) {
long elapsedTime = now - lastRefillTimeStamp;
int tokensToBeAdded = (int) ((elapsedTime / 1000) * refillCountPerSecond);
if (tokensToBeAdded > 0) {
availableTokens = Math.min(capacity, availableTokens + tokensToBeAdded);
lastRefillTimeStamp = now;
}
}
}
}
漏桶和令牌桶的比较
使用 RateLimiter 实现限流
RateLimter 提供的 API 能够直接应用,其中 acquire 会阻塞,相似 JUC 的信号量 Semphore,tryAcquire 方法则是非阻塞的:
public class RateLimiterTest {
public static void main(String[] args) throws InterruptedException {
//容许10个,permitsPerSecond
RateLimiter limiter = RateLimiter.create(10);
for(int i=1;i<20;i++){
if (limiter.tryAcquire(1)){
System.out.println("第"+i+"次请求成功");
}else{
System.out.println("第"+i+"次请求拒绝");
}
}
}
}
总结
本文分享自微信公众号 - JAVA干货分享(JAVA347411)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。