依赖如下
<dependency>
<groupId>com.netflix.hystrix</groupId>
<artifactId>hystrix-core</artifactId>
<version>1.5.18</version>
</dependency>
什么是Hystrix
2018.11发布了最后一个版本,目前处理维护阶段,不再升级版本
-
用途:
-
- 停止级联故障。fallback和优雅的降级,Fail fast和快速恢复
-
- 实时监控和配置实时变更
-
- 资源隔离,部分不可用不会导致整体系统不可用
-
场景:商品列表接口中,需要获取红包、价格、标签等数据。这时候可以给这个一个线程池。 如果线程池打满,也不会影响当前服务的非商品列表接口
-
使用的框架:hystrix主要使用Rxjava,上手可参考:www.jianshu.com/p/5e93c9101…
滑动窗口执行案例
执行步骤:
- 假设滑动窗口总为1s,4个桶bucket,那每个bucket的空间为0.25s,限流总数为10(记录为max=10)
- 然后如下各个桶的访问记录为
2.1.. 桶t1(在0~0.25s之间),访问了4次。这时候访问总次数记录为total=4(此时total<=max)
2.2.. 桶t2(在0.25~0.50s之间),访问了4次。这时候访问总次数记录为total=8(此时total<=max)
2.3.. 桶t3(在0.50~0.75s之间),访问了1次。这时候访问总次数记录为total=9(此时total<=max) 2.4.. 桶t4(在0.75~1.00s之间),尝试访问了2次。由于max=10,当第total=11即第二次访问时,会拒绝访问,因为当前窗口总数上线已满(此时total=10) 2.5.. 桶t5(在1.00~1.25s之间),尝试访问了3次。由于此时t1已经出了窗口,t1访问的4次会被释放出来, 这时候总访问字数total=10 - 4 + 3 = 9 < max = 10,所以可正常访问,不会被拒绝
滑动窗口的基本流程如下:
滑动窗口执行实现例子
滑动窗口实现例子如下
/**
* 自定义滑动时间窗口demo - Hystrix也是类似采用这种。
* - 实现runnable方法:用于控制滑动动作,重置桶的值以及总量值
*
* @author lidishan
*/
public class MyDefinedSlideWinDemoLimiter implements RateLimiter, Runnable {
/** 每秒最多允许5个请求,这是默认值,你可以通过构造方法指定 **/
private static final int DEFAULT_ALLOWED_VISIT_PER_SECOND = 5;
/** 最大访问每秒 **/
private long maxVisitPerSecond;
/** 默认把1s分为十个桶,这是默认值 **/
private static final int DEFAULT_BUCKET = 10;
private int bucket;
/** 每个桶对应当前的请求数 **/
private static AtomicInteger[] countPerBucket = null;
/** 总请求数 **/
private AtomicInteger count;
private volatile int index;
/** 构造器 **/
public MyDefinedSlideWinDemoLimiter() {
this(DEFAULT_BUCKET, DEFAULT_ALLOWED_VISIT_PER_SECOND);
}
public MyDefinedSlideWinDemoLimiter(int bucket, long maxVisitPerSecond) {
this.bucket = bucket;
this.maxVisitPerSecond = maxVisitPerSecond;
countPerBucket = new AtomicInteger[bucket];
for (int i = 0; i < bucket; i++) {
countPerBucket[i] = new AtomicInteger();
}
count = new AtomicInteger(0);
}
/**
* 是否超过限制:当前QPS总数是否超过了最大值(默认每秒5个)
* 注意:这里应该是>=。因为其实如果桶内访问数量已经等于5了,就应该限制住外面的再进来
*/
@Override
public boolean isOverLimit() {
return currentQps() >= maxVisitPerSecond;
}
@Override
public int currentQps() {
return count.get();
}
/**
* 访问一次,次数+1(只要请求进来了就+1),并且告知是否加载
* 请注意:放在指定的桶
*/
@Override
public boolean visit() {
countPerBucket[index].incrementAndGet();
count.incrementAndGet();
return isOverLimit();
}
@Override
public void run() {
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~窗口向后滑动一下~~~~~~~~~~~~~~~~~~~~~~~~~~");
// 桶内的指针向前滑动一下:表示后面的visit请求应该打到下一个桶内
index = (index + 1) % bucket;
// 初始化新桶。并且拿出旧值(其实就是把当前这个桶的值释放出来,然后看下这个桶之前是否有访问过,有的话就对count总数减去,然后告诉可以进行访问)
int val = countPerBucket[index].getAndSet(0);
// 这个步骤一定不要变了:因为废弃了一个桶,所以总值要减去~
if (val == 0) {
// 这个桶等于0,说明这个时刻没有流量进来
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~窗口没能释放出流量,继续保持限流~~~~~~~~~~~~~~~~~~~~~~~~~~");
} else {
count.addAndGet(-val);
System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~窗口释放出了[" + val + "]个访问名额,你可以访问了~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
}
public static void main(String[] args) throws Exception {
MyDefinedSlideWinDemoLimiter rateLimiter = new MyDefinedSlideWinDemoLimiter();
// 使用一个线程定时滑动这个窗口:100ms滑动一次(一般保持个桶的跨度保持一致)
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(rateLimiter, 100, 100, TimeUnit.MILLISECONDS);
// 此处使用单线程访问,你可以改造成多线程版本
while (true) {
String currThreadName = Thread.currentThread().getName();
boolean overLimit = rateLimiter.isOverLimit();
if (overLimit) {
System.out.printf("线程[%s]===被限流了===,因为访问次数已经超过阈值[%s]\n%n", currThreadName, rateLimiter.currentQps());
} else {
rateLimiter.visit();
System.out.printf("线程[%s]访问成功,当前访问总数[%s]\n%n", currThreadName, rateLimiter.currentQps());
}
Thread.sleep(10);
}
}
}
public interface RateLimiter {
// 是否要限流
boolean isOverLimit();
// 当前QPS总数值(也就是窗口期内的访问总量)
int currentQps();
// touch一下;增加一次访问量
boolean visit();
}
Hystrix滑动窗口实现
Hystrix通过滑动窗口来对数据进行统计,一个滑动窗口包含10个桶。每个桶宽度是1秒,负责当前时间段1秒的 成功、失败、超时、拒绝 次数的统计
即每个桶都记录了四个指标:成功量、失败量、超时量、拒绝量,当前滑动时间窗口总数=成功量+失败量+超时量+拒绝量 (所有桶bucket)
其Hystrix滑动时间窗口核心实现为(看看就好,rxjava不用会)
- 提供了HealthCountsStream提供实时健康检查数据,其中里面有个对象HealthCounts记录滑动窗口期间请求数(总数、失败数、失败百分比)
- 有滑动时间窗口,肯定也有持续累积窗口BucketedCumulativeCounterStream
public abstract class BucketedRollingCounterStream<Event extends HystrixEvent, Bucket, Output> extends BucketedCounterStream<Event, Bucket, Output> {
private Observable<Output> sourceStream;
private final AtomicBoolean isSourceCurrentlySubscribed = new AtomicBoolean(false);
protected BucketedRollingCounterStream(HystrixEventStream<Event> stream, final int numBuckets, int bucketSizeInMs,
final Func2<Bucket, Event, Bucket> appendRawEventToBucket,
final Func2<Output, Bucket, Output> reduceBucket) {
super(stream, numBuckets, bucketSizeInMs, appendRawEventToBucket);
Func1<Observable<Bucket>, Observable<Output>> reduceWindowToSummary = window -> window.scan(getEmptyOutputValue(), reduceBucket).skip(numBuckets);
this.sourceStream = bucketedStream // 数据流,每个对象代表单元窗口产生的桶 stream broken up into buckets
.window(numBuckets, 1) // 按照滑动窗口桶的个数进行桶的聚集 emit overlapping windows of buckets
.flatMap(reduceWindowToSummary) // 将一系列的桶聚集成最后的数据对象 convert a window of bucket-summaries into a single summary
.doOnSubscribe(() -> isSourceCurrentlySubscribed.set(true))
.doOnUnsubscribe(() -> isSourceCurrentlySubscribed.set(false))
.share() // 共享。不同的订阅者看到的数据是一致的 multiple subscribers should get same data
.onBackpressureDrop(); // 被压流量控制,当消费者消费速度过慢时就丢弃数据,不进行积压 if there are slow consumers, data should not buffer
}
@Override
public Observable<Output> observe() {
return sourceStream;
}
/* package-private */ boolean isSourceCurrentlySubscribed() {
return isSourceCurrentlySubscribed.get();
}
}
public static class HealthCounts {
private final long totalCount;// 总数
private final long errorCount;// 错误总数
private final int errorPercentage;// 错误百分比
}
下面再展示一个Hystrix降级的流程图: