20大进阶架构专题每日送达
![1ef0646ff0bb6070df1de2743cf60f70.png](https://img8.php1.cn/3cdc5/188e2/bdf/feebb6256d7f1e5d.jpeg)
一、服务器崩溃的思考
老板说,他要做个现场营销活动,线上线下都要参与推广,这个活动参与人数可能很大哦··· 果然,由于不是我写的代码,所以那天服务器就崩了,崩的时候很安静,写代码的那个人一个人走的,走的时候很安详。
: 当请求量到达百万级时候,为啥会崩溃呢?
![8cc49f878d568335f07cafb980e762ea.png](https://img8.php1.cn/3cdc5/188e2/bdf/7921fd724270037c.jpeg)
![45efbc0c5002a80ce82027fc040a3f33.png](https://img8.php1.cn/3cdc5/188e2/bdf/8687892efad0a237.jpeg)
![5c8f4927af73ba9e53ad55d4c80e4637.png](https://img8.php1.cn/3cdc5/188e2/bdf/3db04a099aea94be.jpeg)
微服务中是通过接口去向服务提供者发起http
请求或者rpc(tcp)
请求去获取数据,事实上大量请求中,服务端能处理的请求数量有限,服务中充斥着大量的线程,以及数据库等的连接也会被占用完,导致请求响应速度也越来越慢。
:
响应速度和我们的数据层有关系吗?
能不能去添加服务端服务器呢?
如果能减少客户端向服务端的请求就好了?
限流吗?当前场景能限流吗?
每个线程去查询数据,每次都只查询某一个结果,是不是太浪费了?
我们能不能想办法,提升我们系统的调用性能?
二、有人想看请求合并,今天她来了
上面的一些思路可以用加缓存,加MQ的方式去解决。但是缓存有限,MQ是个队列,有限流的效果。那么,如何才能提高系统的调用能力,我们学习一下,请求合并,结果分发。
大概的设计思路便是如下图所示:
常规请求
![a32dc31c2a4d2e9694f5e9af86489b8b.png](https://img8.php1.cn/3cdc5/188e2/bdf/b530b1641ac68d6c.jpeg)
请求合并
![ddb0c60e31abcbdcb5ce1926b6e1df40.png](https://img8.php1.cn/3cdc5/188e2/bdf/6765cac7e14e6862.jpeg)
说下我们的思路
查询商品信息的时候,如果同一商品同一时刻有100个请求,那么其中的99次查询是多余的,可以把100个请求合并成一个真实的后台接口调用,只要控制好线程安全即可。我的想法是使用并发计数器来实现再配合本地缓存,计数器可直接用JDK提供的AtomicInteger
,线程安全又提供原子操作。
以获取商品信息为例,每个商品id对应一个计数器,计数器初始值默认是0,当一个请求过来后通过incrementAndGet
使计数器自增1并返回自增后的值。当该值等于1,表明该线程在这个时间点上是第一个到达的线程,然后就去调用真实的业务逻辑,在查询到结果后放入到本地缓存中。当该值大于1的时候,表明之前已有线程正在调用业务逻辑,则进入等待状态,并循环的查询本地缓存中是否已有数据可用。获取到结果后都调用decrementAndGet
使计数器减1,计数器被减到0的时候就回到了初始状态,并且当减到0(代表最后一个线程)时清除缓存。
那还有在1000次请求中,请求的数据id不同,但是使用的服务接口相同,都是查询商品库的商品id
从1~1000
的数据,都是从表里面查询,queryDataById(dataId)
,那我也可以合并这些请求,改为批量查询,然后将数据分发返回。思路就是设计每个请求携带一个请求唯一的traceId
,有点像链路跟踪的感觉,简单点可以使用查询的id进行最为跟踪id,将请求放入一个队中,使用定时任务,比如每隔10ms去扫描队列,将这些业务合并请求统一去请求数据库层。
此方案有个数据延迟的地方,就是每次循环时的等待状态的时间。因为一次包含多次查库的业务调用,耗时基本都在几十毫秒,甚至是上百毫秒,可以把该等待睡眠设置小一点,比如10毫秒。这样即不会浪费CPU时间,实时性也比较高,但然也可以通过主动唤醒等待线程的方式,这个操作起来就比较复杂些。在这其中还可以添加一些异常处理、超时控制、最大重试次数,最大并发数(超时最大并发数就快速失败)等。
三、开始演练
import org.springframework.stereotype.Service;
import java.util.*;
/**
* 模拟远程调用ShopData接口
* @author Lijing
*/
@Service
public class QueryServiceRemoteCall {
/**
* 调用远程的商品信息查询接口
*
* @param code 商品编码
* @return 返回商品信息,map格式
*/
public HashMap queryShopDataInfoByCode(String code) {
try {
Thread.sleep(50L);
} catch (InterruptedException e) {
e.printStackTrace;
}
HashMap hashMap &#61; new HashMap<>;
hashMap.put("shopDataId