案例分析 -- 反应式编程框架 Flower 的设计

程序运行: 普通场景
请求到达:用户请求服务器,服务器使用Web端口(比如:80)服务用户请求。
1请求/1线程:Web容器(比如Tomcat)为每个用户请求启动一个线程,由这个线程同步处理全部请求。
数据库访问:再数据库连接池中获取数据库连接,访问数据库。
访问数据库物理文件(磁盘IO)
程序同步阻塞:
-1. 数据未到达而被阻塞:用户请求使用网络连接到服务器Web端口,由于网络原因,后者客户端原因,数据发送比较慢,
还没有到达服务器Web端口,线程从网络连接中读取数据,读取不到数据,就会等待数据而被阻塞。
0. 线程同步访问被阻塞:通过网络同步访问第三方服务,同步访问数据库而被阻塞。
等待数据库连接而被阻塞:数据库连接数量可能小于线程数量,多个线程同时争抢数据库连接,没有抢到数据库连接的线程就会被阻塞。
数据库请求排队阻塞:数据库连接发送请求通过网络连接到达数据库,多个请求需要排队
访问文件系统(磁盘IO操作)而被阻塞: 访问数据库的文件系统,因IO操作而被阻塞
程序崩溃:高并发,高负载场景
高并发请求同时到达:大量用户请求同时到达服务器。
Web容器创建大量线程:线程数量急剧上升。创建线程是个昂贵操作,需要耗费服务器资源(CPU,内存),到达一定数量,耗尽服务器资源,无法再创建更多的线程
后续请求等待: 后续到达的用户请求需要等待,超时后,仍然无法处理,从用户请求的角度看,服务器不可用。
Web容器内线程争夺资源被阻塞:Web容器内大量线程为了完成服务,争抢资源,资源不足时,线程因为等待其他资源而被阻塞。线程不释放自己的资源,导致后来的线程无资源可用也会被阻塞====>整个系统响应变慢。
硬件资源不足导致系统越来越慢:CPU,内存,带宽不足时,系统就会越来越慢。
大量的用户请求无法得到处理,系统就崩溃啦。
分析崩溃原因:
核心原因:阻塞。 阻塞导致线程不能释放自己的资源,其他线程无资源可用,自己因被阻塞而无法向下运行。后来的请求,无法被处理,最终导致系统崩溃。简言之:阻塞导致线程争抢资源,系统资源被耗尽。
解决方法:解除阻塞----异步。
解决方案:反应式编程框架----Flower。
异步关注点:如何改善系统性能?如何实现异步的?编程如何实现的?编程框架如何设计的?

解析:
1.请求并发到达服务器,访问Web端口(80端口),每个请求建立Socket连接,这些连接等待Web容器处理请求。
2.Web容器使用有限的几个线程(容器线程),甚至一个线程,处理所有的用户请求连接---多路复用
===>多路socket的请求使用一个或者几个有限的线程,遍历多个socket网络连接,为他们进行网络通信操作。
有限的线程可以复用更多的并发请求。
3.容器线程从socket连接中抽取request和response对象,然后将request和response对象交给后面的应用程序处理。
socket的连接中如果没有数据,就没有容器线程处理这个connection。===>没有线程阻塞。
socket的connection连接中如果有数据,容量线程将数据抽取为request和response对象,交给后面的应用程序。
==>有限的容器线程,就可以处理很多个并发连接===>避免无限制创建线程,导致系统资源耗尽。
4.Flower运行环境有自己的线程。提供自己的线程池。容器线程提交request和response数据到Flower后,
Flower将数据交给Service处理。线程再某个service上执行。
5.ServiceA处理完后,调用ServiceB。
传统方式--同步调用:ServiceA同步调用ServiceB,ServiceB进行IO操作被阻塞,ServiceA也会被阻塞,不能往下执行。ServiceA和ServiceB都被阻塞。
Flower异步调用:ServiceA处理完成后,发送消息给ServiceB。ServiceB使用akka线程执行自己,
拿到ServiceA传递过来的消息,进行处理。完成后,发送消息给ServiceC。
Flower中并发执行:ServiceA,ServiceB,ServiceC并发执行。ServiceA处理完A用户request的第一步后,发送消息给ServiceB,ServiceB执行的时候;ServiceA开始处理B用户的request。
===>ServiceA和ServiceB使用不同的线程同时处理。
6.异步数据库访问:
传统方式:获取数据库连接,getConnection(),如果没有可用数据库连接,就会被阻塞。
Flower方式:异步数据库连接驱动,返回的是Future,知道Future完成操作Complete,
由另外一个线程拿到Connection提交到数据库==>获取数据库连接的过程是不阻塞的。
效果解析:因为线程少,不会造成很多的资源消耗;因为不阻塞,提高系统的并发量
==>整体的性能很好,系统的吞吐量TPS和响应时间表现都很好。
Flower实现异步的基础是Akka的Actor:

原理解析:生产者和消费者模式,使用消息队列,分离生产者和消费者。===>解决直接调用的阻塞问题。
Service被封装在Actor中,Actor之间相互通信。
Sender发送消息给一个Actor。
同步调用:Sender 直接调用Actor,Actor自己被阻塞,Sender也会被阻塞。
异步调用:使用消息机制。Sender不直接调用Actor,而是发送消息Message给Actor。
2.消息发送:Actor在自己门牌号前挂了一个邮箱(队列),发送过来的消息,先入队放入MailBox邮箱。
3.消息分派:Dispatcher分发器定时分派分发邮箱中的消息给Actor
4.Actor处理消息
5.处理完成后,封装消息发送到下一个Actor。
Akka示例:
public class Hello extends UntypedActor{
public void onReceive(Object message){
System.out.println("Hello,world");
}
}
ActorRef hello=...;
hello.tell("hi!");
Service开发示例:
//1.开发异步编程Service
public class ServiceA implements Service<String,String> {
public String process(String message,ServiceContext context){
if(message!=null && message intanceof String){
return ((String)message).trim();
}
return "";
}
}
//2.编排Service流程,serviceA->serviceB->serviceC
ServiceFlow.getOrCreate(flowName,serviceFactory)
.buildFlow(ServiceA.class,ServiceB.class)
.buildFlow(ServiceB.class,ServiceC.class);
//3.调用异步服务:
flowerFactory.getServiceFacade().asyncCallService(flowName,"hello world");
兼容Spring的Flower Web开发:
@RestController
@RequestMapping("/order/")
@Flower(value="createOrderFlow",flowNumber=32)
public class CreateOrderController extends FlowerController{
@RequestMapping(value="createOrder")
public void createOrder(OrderDTO orderDTO,HttpServletRequest request)throws exception{
doProcess(orderDTO);
}
protected void doProcess(Object param,HttpServletRequest request)throws IOException{
AsyncContext context=null;
if(request!=null){ context=req.startAsync();}
flowRouter.asncCallService(param,context);
}
}
Flower 异步访问数据库:
@FlowerService
public class UserService implements Service<User,CompletableFuture<Serializable>>{
@Autowired
private UserDTO userDTO;
@Override
public CompletableFuture<Serializable> process(User message,ServiceContext context)throws throwable{
System.out.println("处理用户信息:"+message);
return userDTO.insert(message);
}
}
Flower 核心模块设计:


评论