个人随笔
目录
高并发问题的解决策略简述
2025-04-21 21:06:29

有些系统侧重于高并发读,比如搜素引擎,电商商品列表,商品详情,有些系统侧重于高并发写比如广告的扣费系统,对点击浏览次数收费,有些系统同时侧重于高并发读和写,比如秒杀系统,微信红包,微博这些,那么我们有什么解决策略呢?

高并发读

策略1:加缓存

我们可以通过加分布式缓存,比如redis,也可以加本地缓存,比如Memcached。也可以通过MSQL的主从,读从库。也可以用CDN对静态文件进行加速。

策略2:并发读

比如我们可以用异步远程调用,假设对3个RPC调用,分别耗时T1、T2、T3,如果是同步的话耗时就是T1+T2+T3,但是起3个线程异步调用的话就只需要Max(T1,T2,T3),当然要保证这三个调用没有耦合关系,可以并行。也还可以进行冗余调用,比如前端调用后台接口,后台部署了多个节点,我同时发起多次调用,哪个接口先返回就用哪个,其他都丢弃,当然这种方法会导致调用翻倍,看如何权衡。

策略3:重写轻读

比如我们经常遇到的看板数据,或者一些报表,需要联合查询很多个表得到,如果每次都直接查询,则会很慢,我们可以再写入数据的时候就写入目标表(宽表),前端就只需要查询宽表即可,把耗时操作放到写的时候,当然这里具体实现可以用定时同步,也可以实时触发。

总结:读写分离

⽆论加缓存、动静分离,还是重写轻读,其实本质上都是读写分离,这也就是微服务架构⾥经常提到的CQRS(Command Query Responsibility eparation)。

(1)分别为读和写设计不同的数据结构。在C端,当同时⾯临读和写的⾼并发压⼒时,把系统分成读和写两个视⾓来设计,各⾃设计适合⾼并发读和写的数据结构或数据模型。缓存其实是读写分离的⼀个简化,或者说是特例。

(2)写的这⼀端,通常也就是在线的业务 DB,通过分库分表抵抗写的压⼒。读的这⼀端为了抵抗⾼并发压⼒,针对业务场景,可能是<K,V>缓存,也可能是提前做好Join的宽表,又或者是ES搜索引擎。如果ES的性能不⾜,则⾃⼰建⽴倒排索引和搜索引擎。

(3)读和写的串联。定时任务定期把业务数据库中的数据转换成适合⾼并发读的数据结构;或者是写的⼀端把数据的变更发送到消息中间件,然后读的⼀端消费消息;或者直接监听业务数据库中的Binlog,监听数据库的变化来更新读的⼀端的数据。

(4)读⽐写有延迟。因为写的数据是在实时变化的,读的数据肯定会有延迟,读和写之间是最终⼀致性,⽽不是强⼀致性,但这并不影响业务的正常运⾏。拿库存系统举例,假设⽤户读到某个商品的库存是9件,实际可能是8件(某个⽤户刚买⾛了1件),也可能是10件(某个⽤户刚刚取消了⼀个订单),但等⽤户下单的⼀刻,会去实时地扣减数据库⾥⾯的库存,也就是左边的写是“实时、完全准确”的,即使右边的读有⼀定时间延迟也没有影响。

同样,拿微博系统举例,⼀个⽤户发了微博后,并不要求其粉丝⽴即能看到。延迟⼏秒钟才看到微博也可以接受,因为粉丝并不会感知到⾃⼰看到的微博是⼏秒钟之前的。这⾥需要做⼀个补充:对于⽤户⾃⼰的数据,⾃⼰写⾃⼰读(⽐如账
号⾥⾯的钱、⽤户下的订单),在⽤户体验上肯定要保证⾃⼰修改的数据马上能看到。这种在实现上读和写可能是完全同步的(对⼀致性要求⾮常⾼,⽐如涉及钱的场景);也可能是异步的,但要控制读⽐写的延迟⾮常⼩,⽤户感知不到。虽然读的数据可以⽐写的数据有延迟(最终⼀致性),但还是要保证据不能丢失、不能乱序,这就要求读和写之间的数据传输通道要⾮常可靠。抽象地来看,数据通道传输的是⽇志流,消费⽇志的⼀端只是⼀个状态机。

高并发写

策略1:数据分片

数据分⽚也就是对要处理的数据或请求分成多份并⾏处理。比如分表分库,分表后,还是在⼀个数据库、⼀台机器上,但可以更充分地利⽤CPU、内存等资源;分库后,可以利⽤多台机器的资源。

比如JDK的JDK的ConcurrentHashMap实现ConcurrentHashMap在内部分成了若⼲槽(个数是2的整数次⽅,默认是16个槽),也就是若⼲⼦HashMap。这些槽可以并发地读写,槽与槽之间是独⽴的,不会发⽣数据互斥。

再比如ES的分布式索引,在搜索引擎⾥有⼀个基本策略是分布式索引。⽐如有10亿个⽹页或商品,如果建在⼀个倒排索引⾥⾯,则索引很⼤,也不能并发地询。可以把这10亿个⽹页或商品分成n份,建成n个⼩的索引。⼀个查询请求来了以后,并⾏地在n个索引上查询,再把查询结果进⾏合并。

策略2:任务分片

数据分⽚是对要处理的数据(或者请求)进⾏分⽚,任务分⽚是对处理程序本⾝进⾏分⽚。比如CPU的指令流水线,大数据处理的Map/Reduce等。

策略3:异步化

比如借助消息队列,客户端的请求不立即处理,先落盘(存到数据库或消息中间件),然后⽤后台任务定时处理,让客户端通过另外⼀个HTTP或RPC接⼜轮询结果,或者服务器通过接⼜或消息主动通知客户端。

对于“异步”⽽⾔,站在客户端的⾓度来讲,是请求服务器做⼀个事情,客户端不等结果返回,就去做其他的事情,回头再去轮询,或者让服务器回调通知。站在服务器⾓度来看,是接收到⼀个客户的请求之后不⽴即处理,也不⽴马返回结果,⽽是在“后台慢慢地处理”,稍后返回结果。因为客户端不等上⼀个请求返回结果就可以发送⼀个请求,可以源源不断地发送请求,从⽽就形成了异步化。

再比如短信验证码发送,电商的拆单等。

策略4:批量

比如批量插入数据库,合并进行订单推送。

策略5:串⾏化+多进程单线程+异步I/O

在Java⾥⾯,为了提⾼并发度,经常喜欢使⽤多线程。但多线程有两⼤问题:锁竞争;线程切换开销⼤,导致线程数⽆法开很多然后看Nginx、Redis,它们都是单线程模型,因为有了异步I/O后,可以把请求串⾏化处理。第⼀,没有了锁的竞争;第⼆,没有了 I/O 的阻塞,这样单线程也⾮常⾼效。既然要利⽤多核优势,那就开多个实例。再复杂⼀些,开多个进程,每个进程专职负责⼀个业务模块,进程之间通过各种IPC机制实现通信,这种⽅法在C++中⼴泛使⽤。这种做法综
合了任务分⽚、异步化、串⾏化三种思路。

 5

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2