惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Latest
Security Latest
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Threatpost
NISL@THU
NISL@THU
A
Arctic Wolf
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Tenable Blog
O
OpenAI News
Know Your Adversary
Know Your Adversary
Google Online Security Blog
Google Online Security Blog
Cloudbric
Cloudbric
PCI Perspectives
PCI Perspectives
爱范儿
爱范儿
GbyAI
GbyAI
U
Unit 42
IT之家
IT之家
Cyberwarzone
Cyberwarzone
T
The Exploit Database - CXSecurity.com
罗磊的独立博客
Last Week in AI
Last Week in AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hacker News - Newest:
Hacker News - Newest: "LLM"
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
AWS News Blog
AWS News Blog
Schneier on Security
Schneier on Security
L
LINUX DO - 最新话题
Latest news
Latest news
Hacker News: Ask HN
Hacker News: Ask HN
W
WeLiveSecurity
TaoSecurity Blog
TaoSecurity Blog
Attack and Defense Labs
Attack and Defense Labs
Scott Helme
Scott Helme
Security Archives - TechRepublic
Security Archives - TechRepublic
S
Securelist
Help Net Security
Help Net Security
C
Cybersecurity and Infrastructure Security Agency CISA
V
V2EX
S
Security @ Cisco Blogs
月光博客
月光博客
P
Proofpoint News Feed
B
Blog RSS Feed
The GitHub Blog
The GitHub Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Hugging Face - Blog
Hugging Face - Blog
L
LangChain Blog
博客园 - 叶小钗
C
Check Point Blog
腾讯CDC
The Cloudflare Blog
Simon Willison's Weblog
Simon Willison's Weblog

博客园 - 李潘

我们为什么需要分布式系统? 怎么算是在工作中负责? 怎么更好的沟通? 怎么用工作邮件? 为工作排好优先级 聊聊数据压缩 怎么使用硬件同步原语替代锁? 怎么正确使用锁? 聊聊缓存 为什么Kafka的性能那么好? 怎么避免服务内存溢出? 应用程序之间的通信传输协议 怎么选择数据序列化方案? 怎么处理消息积压问题? 怎么处理消息重发的问题? 怎么做才能不丢消息? 怎么用消息队列实现分布式事务? 聊聊消息队列中的基础概念 我们要选择哪个消息队列产品?
怎么利用异步设计提升系统性能?
李潘 · 2023-03-12 · via 博客园 - 李潘

异步是一种程序设计的思想,使用异步模式设计的程序可以显著减少线程等待,从而在高吞吐量的场景中,极大提升系统的整体性能,降低请求时延。

同步设计流程

我们假设要做一个转账的业务,即从账户A中转账100元到账户B中,它包含2步:

  1. 从A的账户中减少100元
  2. 给B的账户增加100元

我们可以设计2个Service:

  • Transfer服务,负责转账,接口是Transfer(A, B, 100)
  • Account服务,负责账户管理,接口是Add(A, -100)和Add(B, 100)

转账业务的伪代码如下:

Transfer(accountFrom, accountTo, amount){
    Add(accountFrom, -1*amount)
    Add(accountTo, 1*amount)
    return OK
}

假设Add操作的平均响应时延是50ms,那么我们的Transfer操作平均实验大约是100ms。在实际运行过程中,每处理一次Transfer操作,需要耗时100ms,并且在100ms内需要独占一个线程,即每个线程每秒最多可以处理10次Transfer操作。

假设我们在一个服务器上同时打开的线程数量上限是10000,那么这个服务器每秒可以处理的请求上限是:10000*10 = 100000次。

当客户请求数量超过上限时,请求就需要排队,那么Transfer操作的响应时延变成:排队等待时延+处理时延(100ms),即在大量请求的场景中,我们的服务响应时延增加了。

但是,在这种情况下,服务器的资源并没有被消耗很多,例如CPU、内存、网卡等资源都很空闲,我们的100000个线程大部分时间在等待Add操作返回结果。

上面就是同步设计方式,在这种情况下,整个服务器的所有线程大部分时间都没有在工作,而是在等待。

异步设计流程

对于同样的业务场景,我们来看一下异步方式下的伪代码:

TransferAsync(accountFrom, accountTo, amount, OnComplete){
    AddAsync(accountFrom, -1*amount, OnDebit(accountTo, 1*amount, OnAllDone(OnComplete)))
}

OnDebit(amountTo, amount, OnAllDone(OnComplete)){
    AddAsync(accountTo, amount, OnAllDone(OnComplete))
}

OnAllDone(OnComplete){
    OnComplete()
}

这里TransferAsync和之前的Transfer相比,增加了一个参数,这个参数是一个回调方法OnComplete()。

上面方法的语义:请帮我执行转账操作,当转账完成后,请调用OnComplete()方法,调用TransferAsync的线程不必等待转账完成就可以立即返回,当转账结束后,OnComplete()方法会被调用来执行后续的工作。

上面代码中,我们定义了2个回调方法:

  1. OnDebit():扣减账户accountFrom完成后调用的回调方法
  2. OnAllDone():转入账户accountTop完成后调用的回调方法

整个异步操作的语义如下:

  1. 异步从accountFrom的账户中减去相应的钱数,然后调用OnDebit方法
  2. 在OnDebit方法中,异步把减去的钱数加到accountTop账户中,然后执行OnAllDone方法
  3. 在OnAllDone方法中,调用OnComplete方法

采用异步方式后,整个流程的时序和同步实现是完全一样的,区别在于线程模型由同步顺序调用改为异步调用和回调的机制。

由于流程的时序和同步方式相同,在低请求量的场景下,平均响应时延还是100ms,在超高请求量的场景下,异步机制不需要线程等待执行结果,只需要个位数的线程,即可实现同步场景大量线程一样的吞吐量。

在异步模式下,由于没有了线程数量上的限制,总体吞吐量上限会大大超过同步实现,并且在服务器CPU、网络带宽资源达到极限之前,响应时延不会随着请求数量增加而显著升高,几乎可以一直保持100ms左右的响应时延。

异步框架:CompletableFuture

Java语言中常用的异步框架包括CompletableFuture和RxJava,我们主要来看CompletableFuture。

它是Java 8中新增的一个强大的异步编程的类,包括了我们在开发异步程序过程中需要的大部分功能。

针对上述的转账场景,我们来看一下如何使用CompletableFuture实现。

首先定义2个接口:


public interface AccountService {

    CompletableFuture<Void> add(int account, int amount);
}

public interface TransferService {

    CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount);
}

然后实现转账功能:


public class TransferServiceImpl implements TransferService {
    @Inject
    private  AccountService accountService; 

    @Override
    public CompletableFuture<Void> transfer(int fromAccount, int toAccount, int amount) {
      return accountService.add(fromAccount, -1 * amount)
        .thenCompose(v -> accountService.add(toAccount, amount));    
    }
}

客户端调用TransferService时,可以使用同步方式,也可以使用异步方式:


public class Client {
    @Inject
    private TransferService transferService; 
    private final static int A = 1000;
    private final static int B = 1001;

    public void syncInvoke() throws ExecutionException, InterruptedException {
        // 同步调用
        transferService.transfer(A, B, 100).get();
        System.out.println("转账完成!");
    }

    public void asyncInvoke() {
        // 异步调用
        transferService.transfer(A, B, 100)
            .thenRun(() -> System.out.println("转账完成!"));
    }
}

异步设计的思想:当我们要执行一项比较耗时的操作时,不去等待操作结束,而是给这个操作一个命令:“当操作完成后,接下来去执行什么”。

使用异步编程带来的好处是可以减少或者避免线程等待,只用很少的线程就可以达到超高的吞吐能力。

使用异步编程带来的问题是复杂度增加,代码可读性和可维护性会下降。