A股曾经搞过熔断,而Actor也有熔断机制。其原理如下:
三个状态: 关闭 打开 半打开
Akka的熔断对actor定义了三种状态,分别是open,half-open,close
Open:表示熔断打开,所有后续的请求都直接返回,不进行逻辑处理
Half-open:表示熔断半开,如果第一次请求,能正确执行,即不超时无异常,则熔断机制关闭,切换状态到CLOSED,否则切换加Open,等待重置时间。
Close:表示熔断关闭,所有请求都进行逻辑处理
当actor失败次数达到配置中允许最大次数,则切换至open状态,即熔断开启,后序的请求会直接返回,不进行真正的逻辑处理
当到达重置时间,状态会切换至half-open状态,其后的请求若处理成功,则状态切换回closed,即熔断关闭,若请求仍然超时不成功,则状态切换回open,等待下一个重置时间
核心代码:
this.breaker = new CircuitBreaker(
getContext().dispatcher(),
getContext().system().scheduler(),
2,//允许最大失败次数
Duration.create(2, TimeUnit.SECONDS),//超时时间
Duration.create(8, TimeUnit.SECONDS))//重置时间
.onOpen(this::notifyMeOnOpen).onHalfOpen(this::notifyHalfOpen)
.onClose(this::notifyClose);
代码示例:
package com.zte.sunquan.cb.actor;
import akka.actor.AbstractActor;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.pattern.CircuitBreaker;
import scala.Function0;
import scala.concurrent.duration.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import static akka.pattern.PatternsCS.pipe;
/**
* Created by sunquan on 2017/9/19.
* 1超时,2超时,进入half-open
*/
public class DangerousJavaActor2 extends AbstractActor {
private final LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private final CircuitBreaker breaker;
public static Props props() {
return Props.create(DangerousJavaActor2.class);
}
public DangerousJavaActor2() {
this.breaker = new CircuitBreaker(
getContext().dispatcher(),
getContext().system().scheduler(),
2,
Duration.create(2, TimeUnit.SECONDS),
Duration.create(8, TimeUnit.SECONDS))
.onOpen(this::notifyMeOnOpen)
.onHalfOpen(this::notifyMeOnHalfOpen)
.onClose(this::notifyMeOnClose);
}
@Override
public Receive createReceive() {
return receiveBuilder().match(String.class, m -> m.startsWith("invoke"), (s) ->
pipe(breaker.callWithCircuitBreakerCS(() ->
CompletableFuture.supplyAsync(this::blockCall)
), getContext().dispatcher()
).to(sender()))
.match(String.class, m -> m.startsWith("call"), (s) ->
sender().tell(breaker
.callWithSyncCircuitBreaker(this::blockCall), self()))
// .match(String.class, (s) ->{
// int i=0;
// sender().tell(breaker
// .withSyncCircuitBreaker(new Function0<Object>() {
// }), self());
// }
.build();
}
public void notifyMeOnOpen() {
log.warning("My CircuitBreaker is now open.");
}
public void notifyMeOnHalfOpen() {
log.warning("My CircuitBreaker is now half-open.");
}
public void notifyMeOnClose() {
log.warning("My CircuitBreaker is now close.");
}
private int i = 2;
public String bolockCall(int i) {
System.out.println("blockCall:" + i + ",thread:" + Thread.currentThread().getName());
// throw new RuntimeException("sq");
if (i <= 0)
return "hello,world";
else {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "error";
}
public String blockCall() {
System.out.println("blockCall:" + i + ",thread:" + Thread.currentThread().getName());
// throw new RuntimeException("sq");
if (i <= 0)
return "hello,world";
else {
i--;
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "error";
}
}
@Test
public void startTest2() throws InterruptedException {
ActorSystem system = ActorSystem.create();
ActorRef printActor = system.actorOf(Props.create(PrintActor.class));
ActorRef myActor = system.actorOf(DangerousJavaActor2.props());
//1.由于maxFailure为2次,且超时时间为2秒,而事件处理需要3秒,两次调用后,熔断机制触发 open
myActor.tell("invoke1", printActor);
myActor.tell("invoke2", printActor);
//2.无消息发送,等待最终超时8秒后,切换至half-open
Thread.sleep(15000);
//3.发送后,又超时,又熔断,切换回open
myActor.tell("invoke3", printActor);
Scanner scanner = new Scanner(System.in);
scanner.next();
}
打印:
blockCall:2,thread:ForkJoinPool.commonPool-worker-2
blockCall:1,thread:ForkJoinPool.commonPool-worker-3
[WARN] [12/19/2017 14:43:13.281] [default-akka.actor.default-dispatcher-10] [akka://default/user/$b] My CircuitBreaker is now open.
[WARN] [12/19/2017 14:43:21.282] [default-akka.actor.default-dispatcher-2] [akka://default/user/$b] My CircuitBreaker is now half-open.
blockCall:0,thread:ForkJoinPool.commonPool-worker-1
[WARN] [12/19/2017 14:43:26.216] [default-akka.actor.default-dispatcher-2] [akka://default/user/$b] My CircuitBreaker is now close.
hello,world:Tue Dec 19 14:43:26 CST 2017
分析:
invoke1,invoke2的两次调用,i都大于0,等待3秒,造成超时,从而触发熔断
等待8秒重置时间后,进行半开
第三次invoke,i此时为0,直接返回hello,world,执行成功,熔断关闭
@Test
public void startTest3() throws InterruptedException {
ActorSystem system = ActorSystem.create();
ActorRef printActor = system.actorOf(Props.create(PrintActor.class));
ActorRef myActor = system.actorOf(DangerousJavaActor2.props());
//1.由于maxFailure为2次,且超时时间为2秒,而事件处理需要3秒,两次调用后,熔断机制触发 open
myActor.tell("call1", printActor);
myActor.tell("call2", printActor);
//2.无消息发送,等待最终超时8秒后,切换至half-open
Thread.sleep(25000);
//3.发送后,又超时,又熔断,切换回open
myActor.tell("call3", printActor);
Scanner scanner = new Scanner(System.in);
scanner.next();
}
打印:
blockCall:2,thread:default-akka.actor.default-dispatcher-5
blockCall:2,thread:default-akka.actor.default-dispatcher-12
[ERROR] [12/19/2017 15:06:37.817] [default-akka.actor.default-dispatcher-5] [akka://default/user/$b] Circuit Breaker Timed out. (akka.pattern.CircuitBreaker$$anon$1: Circuit Breaker Timed out.)
[ERROR] [12/19/2017 15:06:40.826] [default-akka.actor.default-dispatcher-9] [akka://default/user/$b] Circuit Breaker Timed out. (akka.pattern.CircuitBreaker$$anon$1: Circuit Breaker Timed out.)
blockCall:2,thread:default-akka.actor.default-dispatcher-9
[ERROR] [12/19/2017 15:07:02.791] [default-akka.actor.default-dispatcher-11] [akka://default/user/$b] Circuit Breaker Timed out. (akka.pattern.CircuitBreaker$$anon$1: Circuit Breaker Timed out.)
分析:
首先i的值,在每次执行时都没有变,可见
callWithCircuitBreakerCS与callWithSyncCircuitBreaker的区别,还在于后者里面的方法如果超时了,所有已执行逻辑将会回滚。(!!!!非回滚,见评论)
为了使用熔断,所有的业务处理方法,都需要封装,以确保在熔断触发时,可以直接返回,下面为封装的几种方式:
· withCircuitBreaker 异步执行
· withSyncCircuitBreaker 同步执行
· callWithCircuitBreaker 异步执行
· callWithCircuitBreakerCS 异步执行
· callWithSyncCircuitBreaker 同步执行