🌈🌈🌈🌈🌈🌈🌈🌈
欢迎关注公众号(通过文章导读关注),发送笔记
可领取 Redis、JVM 等系列完整 pdf!
【11来了】文章导读地址:点击查看文章导读!
🍁🍁🍁🍁🍁🍁🍁🍁
初版营销系统设计方案
对营销系统,主要有以下几个任务:
- 搞促销活动,对全员用户/部分用户进行推送
- 发优惠券,给全员用户/部分用户发券
- 给所有用户每天推送热门商品,吸引用户
首先,营销系统初版的一个功能流程如下图:
营销系统中的几个任务的共性
:需要对大量的用户数据进行一个任务处理,而用户量是很大的,对于中型电商平台来说,用户量是达到千万级的,因此系统中会出现各种性能问题
那么先实现初版营销系统基础的功能架构,将 RocketMQ 进行落地实现,并保证整体业务流程可以完整运行,之后再针对不同场景进行性能的优化,使能支撑千万级用户量
那么目前初版的营销系统主要存在以下几个方面的性能问题:
-
数据库查询方面
:从数据库中全量查询用户数据,数据量太大,一下就把 MySQL 打死了,一下查询几个 GB 的数据,根本查不出来那么如果批量查询用户数据呢?其实也是存在问题的,批量查出来用户数据,放到内存中,再对用户数据进行处理,那么处理期间无法进行垃圾回收,导致
大量内存空间被占用
,大量数据进入老年代,老年代满了之后频繁触发 full gc,造成系统频繁停顿,并且批量查询用户数据,假设 1000w 个用户,一次查询 1w 条,也需要查询 1000 次,这么多的网络请求对于机器的压力还是很大的 -
数据库插入方面
:营销系统需要为用户生成优惠券,那么还需要向数据库中插入大量优惠券的数据,也会对数据库造成很大压力 -
推送 MQ 方面
:需要为每个用户封装一条 push 消息发送到 MQ 中,那么用户量 1000w 的时候,需要对 MQ 发送 1000w 次请求,需要推送的数据量太大了,造成了大量的网络通信开销,并且推送时间也会比较长 -
RPC 通信方面
:对于大多数电商系统来说,用户系统是和营销系统分开的,之间通过 RCP 通信的,那么查询大量用户数据之后,通过 RPC 进行传输,RPC 调用的压力也是一个问题
千万级用户分片+批量推送方案、惰性发券
性能瓶颈
:
- 千万级用户量的 push 推送
- 大数据量查询/插入
- 大量数据存入内存时的消耗
- 千万级消息写入 MQ 耗时
首先针对大数据量的数据库查询以及 MQ 推送,通过分布式推送来优化性能:
首先,针对于千万级用户量的 push 推送,先不去进行千万级用户的全量查询,而是通过向 MQ 中发出需要 创建营销活动的一个消息
,通过消费者监听这个消息去查出用户总量,根据用户总量进行一个拆分,比如说 1000w 用户拆分成 1w 个批次,那么每个批次就是 1000 个用户
拆分之后,将 一个批次作为一条消息
发送到 RocketMQ 中,假设发送一条消息耗费 10ms,1w 条消息也就 100s 而已
推送到 MQ 中,消息会均匀分散地写入到各个 queue 里面去在消费端,将推送系统集群部署,多个推送系统去消费 Topic 中的 queue
最终千万级用户的推送任务被分发到推送系统的各个机器上去,实现分布式推送的效果
多线程推送优化:
在上图流程的第 8 个步骤,调用第三方 Push 平台的 SDK 去进行推送,那么一个消息中是有 1000 个用户的,那么假设发送给第三方平台一次 PUSH 请求要花费 200ms,那么 1000 个用户也需要花费 1000 * 200ms = 200s,那么对于千万级用户来说,通过单线程处理需要花费 1000w * 200ms = 200w秒(约为23天),那肯定是不行的
对于推送系统推送来说,之前将 1000w 的用户拆分为 1w 个批次了,也就是 1w 个消息,每个消息有 1000 个用户的 id,也就是推送系统需要向第三方 Push 平台推送 1w 个消息,假设第三方 Push 平台推送 1 个用户需要耗费 200ms,那么 1000 个用户也就需要耗费 200s ≈ 3 分钟,也就是推送系统向第三方 Push 平台推送 1 条消息(包含 1000 个用户)需要消耗 3 分钟时间,那么 1w 条消息也就是需要花费 10000 * 3 = 30000 分钟,大约是 20 天,这速度太慢了
因此通过 多线程
高并发发起推送任务,假设开启 30 个线程,那么一个推送系统 3 分钟就可以推送 30 条消息,3 个小时可以推送差不多 2000 个消息,那么总共 10000 条消息,只需要 5 个机器同时开 30 个线程跑 3 个小时,就可以将 10000 个消息推送完毕
可以根据机器 CPU 的配置,可以适当增加或减少线程的个数
千万级用户惰性发券场景:
互联网公司经典场景:千万级用户惰性发优惠券
业务场景是:运营人员发布一个促销活动,需要给每一个用户都发优惠券,那么在发布完活动之后,先不立即执行发券的动作,因为这样会导致对 MQ 以及 MySQL 压力都过大,通过 惰性发券
来分散发券的压力,是如何惰性发券呢?
当发布促销活动后,先将活动存入到 Redis 集群中去,当用户登陆的时候,会去向 MQ 中发送一个【用户登录】的消息,那么营销系统中就会有一个【用户登录】消息的消费者,监听到之后,就会去 Redis 中查看当前用户是否已经发过优惠券,如果没有的话,就执行发券的操作
最后总结一下:
对于 千万级用户推送
场景下的解决方案:
- 先将需要推送给用户的活动创建成 MQ 中的一个消息
- 在营销系统中通过一个消费者来去
异步
的进行用户推送的动作 - 用户量太大,那么就先去数据库查出用户总量,对用户 id 进行一个
分片
,一个分片存 1000 个用户 id,那么 1000w 的用户只需要 1w 个分片即可,这里一个分片就是 MQ 中的一个消息 - 由于推送第三瓶平台需要对每个用户都推送,如果使用单线程,花费时间太长,因此通过加入
多线程
,并且部署多个推送节点来加快消息的推送