写点什么

RocketMQ 实战—营销系统业务和方案介绍

作者:EquatorCoco
  • 2025-02-10
    福建
  • 本文字数:11006 字

    阅读完需:约 36 分钟

千万级用户促销活动全量推送、千万级用户惰性发放优惠券、百万级画像群体推送爆款商品的架构方案

 

1.电商核心交易场景的业务流程


 

2.电商支付后履约场景的业务流程


 

3.电商营销场景的业务说明


营销系统主要有优惠券和促销活动两种营销方式。

 

(1)优惠券


营销系统会发各种各样的优惠券给用户,让用户领券。有些 APP 会不知不觉给用户发放一些优惠券,希望通过优惠券来吸引用户,让用户去购买商品。

 

什么是发券、领券、用券、销券:

 

营销系统发送优惠券时,其实就是在数据库中给这个用户增加一条优惠券的数据,这样就完成了发券。但此时这个优惠券还不属于用户,需要用户去确认并且执行领券操作,确认该优惠券是自己的。这样,在营销系统的数据库中才有用户的一条优惠券数据,在后续的购买流程中用户才能用券。当用户使用了优惠券进行订单支付后,还要对该优惠券进行销券处理。

 

(2)促销活动


针对指定的商品进行满减、立减、赠品、折扣等促销活动之前,营销系统需要推送促销活动让所有用户知道。其中的推送模式有如下几种:短信、邮箱、APP 推送、站内信等。

 

4.电商促销活动的 Push 推送


当营销系统发放完优惠券后,需要对用户进行 Push 推送,通知用户可以领券或者用户已经获得一张优惠券了。所以在营销系统中,无论是优惠券还是促销活动,都需要推送给用户,吸引用户购物和消费。

 

营销系统除了需要推送优惠券、促销活动给用户吸引其来购物和消费外,还会定时推送热门商品。推荐系统会根据用户的历史商品浏览记录、商品购买记录,通过算法推导出用户可能感兴趣的商品,还有热门商品。热门商品包括:浏览量最高的爆款、购买量最高的爆款、评价最高的爆款等。


 

5.会员与推送的数据库表结构设计


营销系统需要通过会员系统获取用户的数据,才能将优惠券、促销活动、推荐商品推送给用户。

 

(1)⽤户表 membership_account


存储⽤户账号信息。



(2)⽤户会员等级信息表 membership_point


存储会员等级,会员积分等数据。



(3)消息推送信息 push_message


存储每⼀次发送消息的发送记录。


 

6.营销系统的数据库表结构


(1)优惠活动信息表 sales_promotion


存储每⼀次优惠活动的发起记录。



(2)优惠券活动表 sales_promotion_coupon


记录每⼀次发放优惠券的发放记录。



(3)优惠券信息表 sales_promotion_coupon_item


记录每个⽤户有哪些优惠券。



(4)热⻔商品推荐任务表 hot_goods_crontab


记录热⻔商品的推送任务数据,job 定时扫描这个表,并根据存储的数据向对应⼈群推送热⻔商品。



(5)⽤户活跃等级表 membership_filter


存储⽤户的活跃天数,会员等级等数据。正常情况下,这些数据是由运营设定规则,由⼤数据团队计算统计数据并保存起来供运营指定运营策略。营销系统可以根据这些数据来执行不同的推送策略。



(6)推送消息任务表 push_message_crontab


记录每⼀个推送消息任务的数据,job 定时扫描这个表执⾏消息推送任务。


 

7.营销系统的基础技术架构


以 Nacos 作为服务注册中心,推送系统、营销系统、会员系统启动时都会向 Nacos 进行服务注册。推送系统、营销系统、会员系统通过服务注册中心拿到各系统的地址,就可以通过 Dubbo 进行相互间的 RPC 服务提供和调用。

 

当营销系统需要进行消息推送时,会先部署一个 RocketMQ 消息中间件集群,然后将消息发送到 RocketMQ 集群,接着推送系统就可以从 RocketMQ 集群消费这些消息进行推送。

 

此外如果需要进行定时任务调度,那么就可能会涉及到 XXLJob 的调度。假设推送系统部署了多台机器,希望消费消息时,能以分布式的方式在每个推送系统里处理一部分定时任务。此时就需要部署一个 XXLJob 调度管理中心,然后在上面配置一组 Excutors。而每个推送系统如果要和 XXLJob 进行对接,也需要配置一个 Excutor。当推送系统启动后,它的 Excutor 会往 XXLJob 调度管理中心进行注册。这样,XXLJob 调度管理中心的某个 Excutors 便能知道各推送系统上 Excutor 的 IP 和端口号。

 

之后,开发人员便能通过 XXLJob 调度管理中心配置一个任务管理,对定时任务进行管理,让定时任务可以绑定到一个 Excutors 分组上,也就是定时任务在执行时会发送请求给推送系统的 Excutor 来执行分布式调度任务的。推送系统的 Excutor 便会通过 SpringBean 来执行任务。



营销系统的基础技术架构总结:系统与系统间通过 RPC 进行调用,Nacos 是注册中心,Dubbo 是 RPC 框架,RocketMQ 是消息中间件进行消息中转,分布式定时调度任务使用的是 XXLJob。

 

8.XXLJob 分布式调度运行原理


当多个推送系统节点都要从数据库里查询同一批推送任务时,XXLJob 如何决定谁来执行哪些任务?比如当从数据库查出同一批推送任务有 10 个,推送系统 1 可以执行 5 个任务,推送系统 2 可以执行另外 5 个任务。

 

说明一:XXLJob 首先要配置一组 Excutors,该组 Excutors 会有名字。推送系统在启动时就需要启动一个 Excutor,并且会注册到 XXLJob 里指定名字的一个 Excutors 中。XXLJob 收到推送系统 Excutor 的注册请求后,会根据注册的名字把它们划分到对应的一组 Excutors 里面。

 

说明二:然后开发人员便可以在 XXLJob 配置定时调度任务,绑定某组 Excutors 以及指定执行任务的 SpringBean。当配置好的定时任务的执行时间到达时,就会找到绑定的 Excutors,发送执行任务请求给推送系统的 Excutor。

 

说明三:推送系统的 Excutor 收到 XXLJob 发送的执行任务请求后,便会找到指定的 SpringBean 去执行任务。每个推送系统的 SpringBean 接着会从 MySQL 数据库里查询出相关的推送任务。

 

说明四:为了决定每个推送系统的 SpringBean 该执行从 MySQL 数据库查询出来的哪些推送任务。XXLJob 在发送执行任务请求给推送系统的 Excutor 时,会带上 shardIndex 和 shardNums。

 

其中 shardNums 指的是当前执行定时任务的推送系统 Excutor 一共有多少个节点,每一个节点可以认为是任务执行的分片。shardIndex 就是对各个定时任务节点进行标号,比如发给推送系统 1 的 shardIndex=1,发送给推送系统 2 的 shardIndex=2。

 

说明五:这时推送系统的 SpringBean 从数据库查出来一批推送任务时:就会根据任务 ID 的 Hash 值对 shardNums 进行取模。通过取模结果和推送系统所属的 shardIndex 是否一样,来决定这个任务是属于哪个分片,从而实现多个节点对同一批任务的分布式调度。


 

9.电商营销系统的工程结构



一.demo-eshop-membership-service 的配置文件和代码结构如下:


spring:  application:    name: demo-eshop-membership  # 数据源配置  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/demo_eshop_rocketmq?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai    username: default    password: default  cloud:    nacos:      discovery:        server-addr: localhost:8848        username: nacos        password: nacos
redis: host: localhost port: 6379 password: default timeout: 3000
server: port: 18015
dubbo: scan: base-packages: com.demo.eshop.membership.api registry: address: spring-cloud://localhost protocol: name: dubbo port: 28094 consumer: check: false provider: threads: 800 actives: 200
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true mapper-locations: classpath:mapper/*.xml
复制代码



二.demo-eshop-promotion-service 的配置文件和代码结构如下:


spring:  application:    name: demo-eshop-push  # 数据源配置  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/demo_eshop_rocketmq?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai    username: default    password: default  cloud:    nacos:      discovery:        server-addr: localhost:8848        username: nacos        password: nacos
redis: host: 127.0.0.1 port: 6379 password: default timeout: 3000
server: port: 18017
dubbo: scan: base-packages: com.demo.eshop.push.api registry: address: spring-cloud://localhost protocol: name: dubbo port: 28095 consumer: check: false provider: threads: 800 actives: 200
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true mapper-locations: classpath:mapper/*.xml
rocketmq: name-server: localhost:9876
复制代码



三.demo-eshop-push-service 的配置文件和代码结构如下:


spring:  application:    name: demo-eshop-push  # 数据源配置  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/demo_eshop_rocketmq?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai    username: default    password: default  cloud:    nacos:      discovery:        server-addr: localhost:8848        username: nacos        password: nacos
redis: host: localhost port: 6379 password: default timeout: 3000
server: port: 18016
dubbo: scan: base-packages: com.demo.eshop.push.api registry: address: spring-cloud://localhost protocol: name: dubbo port: 28096 consumer: check: false provider: threads: 800 actives: 200
mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true mapper-locations: classpath:mapper/*.xml
rocketmq: name-server: localhost:9876
# xxl jobxxl: job: admin: addresses: http://127.0.0.1:8080/xxl-job-admin executor: appname: message-push port: 9999
复制代码


 

10.电商营销系统的营销技术挑战


(1)电商营销系统的营销技术挑战


不论是优惠券活动,还是优惠活动,或者是热⻔商品定时推送,都会面临这样的难点和挑战:需要给大量的用户进行推送,这个用户量可能会非常大,而且限制在一定时间内完成所有推送任务。比如中小型电商平台每天需要进行推送的用户量是千万级的,一天下来需要推送的消息次数甚至到亿级。所以在量⾮常⼤的时候,需要高并发地先发送消息到 RocketMQ 中,然后再由消费者慢慢消费,比如调⽤第三方系统的 SDK 完成消息推送或写⼊数据到数据库。

 

核心链路如下:



(2)千万级用户量下的营销技术挑战


营销系统的三大工作就是:

工作一:面向全员发推送促销活动

工作二:面向特定人群或全员发放优惠券

工作三:给所有用户推送热门爆款商品

 

这三项工作的挑战在于,都需要面对大量用户进行数据和任务的处理。在一个中型电商平台中,拥有的是千万级用户量,比如 1000 万用户。如果向这 1000 万用户推送爆款商品,那么就需要构建发送任务来调用第三方平台的接口来完成推送。如果向这 1000 万用户发放优惠券,那么就需要往数据库插入 1000 万条用户持有优惠券的数据记录。

 

11.少量数据测试版技术方案说明


下面先按用户量比较少来设计营销系统,如下所示:


 

12.第一版全量推送方案的缺陷


一.直接查询全量用户数据会压垮数据库


如果通过"select * from table"直接查询千万级的全量用户,可能会直接把 MySQL 压垮,查不到数据。

 

二.每页查询少则耗时每页查询多则耗内存


如果通过"select * from table limit"分页查询一批一批地查,那么每一批要查多少数据也不好确定。如果一批查 1000 条数据,千万级用户就要查 1 万次,每次查询耗费 100ms,就需要总共查 1000s。如果一批查 10000 条数据,当每次查询的数据量大了以后,对机器的内存消耗就会很大,可能就要消耗几百 MB 的内存。当内存消耗过大,而且由于处理很多数据比较耗的时候,就又会导致频繁 YGC + 大量数据进入 JVM 老年代。老年代的内存又会频繁地满了而触发 FGC,最后引发系统频繁停顿的问题。

 

三.全量遍历用户一用户一消息既耗时又耗网络


如果对查出来的千万级用户进行全量遍历,一个用户封装成一条 Push 消息发到 MQ 里,就会有 1000 万条 Push 消息。1000 万条 Push 消息会对 RocketMQ 发起 1000 万次请求,每次 Push 消息耗费 10ms,总共就需要 10 万秒。所以全量遍历用户并根据每个用户产生一条消息,会导致千万级消息推送给 MQ,产生大量的网络通信开销和耗时过长。


 

13.第一版全量发优惠券方案缺陷


针对全量用户(千万级)/特定用户(百万级)发优惠券:

 

一.直接查询全量用户数据会压垮数据库


如果直接查询千万级的全量用户/特定用户(百万级),数据库能否扛住。

 

二.每页查询少则耗时每页查询多则耗内存


如果分批查,单次查询量少就要查询多次而导致耗时,单次查询量多就要消耗内存而导致频繁 FGC。

 

三.给用户发放优惠券时要对数据库高并发写


对每个用户插入发放优惠券的数据时,数据库面临高并发写问题,影响系统运行的稳定性。

 

四.全量遍历用户一用户一消息既耗时又耗网络


遍历全量用户/特定用户(百万级),每个用户一条推送消息,则面临千万级/百万级消息推送耗时问题。

 

14.推送异步化以及千万用户分片


(1)对推送任务进行预处理实现推送异步化


千万级/百万级用户量的推送面临的问题:

一.大数据量查会对数据库有压力,分批查可能会有耗时或内存消耗过大的问题

二.大数据量瞬时高并发写数据库存在压力

三.千万级/百万级消息写入 RocketMQ 耗时

 

因此,最好不要先直接去数据库查出千万级用户数据,让推送系统同步发起推送处理。而是先基于 RocketMQ 做一个异步化,也就是对推送任务做一些预处理。

 

经过预处理后,会产生少量的消息,把这些消息量比较少的预处理结果推送到 RocketMQ 中。然后再让推送系统消费这些预处理结果,把千万级用户量的推送,进行异步化处理。

 

(2)推送任务的预处理细节


首先使用 count()或 max()去数据库查询用户总数,而不是直接查询千万级的用户数据。假设查出来的用户总数是 1000 万,那么就可以对推送任务进行分片拆分。将整个推送任务拆分成 1 万个分片任务,每个任务处理 1000 个用户的推送。

 

这就是所谓千万级用户分片,这 1 万个分片任务会被推送到 RocketMQ 里,对应 1 万条消息。如果每条消息发送到 RocketMQ 需要耗费 10ms,那么完成 1 万条消息的推送就需要 1000s,还是比较耗时。

 

这 1 万条消息的推送,可以进行 batch 化推送,也就是每 100 条消息合并成一条 batch 消息后再发送到 RocketMQ 里。这样只需要发送 100 次 batch 消息给 RocketMQ,假设发送一次 batch 消息耗时 50ms,那么总耗时才 5 秒。

 

但即使只需要 5s 完成预处理,也不能让运营人员在创建促销活动时,同步等待 5s。所以当运营人员使用营销系统创建促销活动时,先不要查询用户总数完成预处理,而是先发布一条消息到 RocketMQ。然后营销系统自己又会作为 RocketMQ 的消费者来异步消费该消息,异步完成预处理的工作。


 

15.千万级用户分布式推送方案


(1)根据千万级的用户产生万级的分片任务消息


将一个千万级用户的推送任务,分拆成 1 万个推送任务,每个任务会对 1000 个用户进行推送。每个任务消息的具体内容其实就是:针对 userId=1 到 userId=1000 的用户发起推送。这样的任务消息会有 1 万条,每发送一条这样的消息到 MQ 就需要 10ms,总共就需要 1000 秒=17 分。

 

(2)对万级的分片任务消息进行合并批量发送

为了避免在处理千万级用户分片时耗时过慢的问题,可以利用 RocketMQ 支持的 batch 批量写机制。也就是每 100 条任务消息合并成一条 batch 任务消息来发送,总共只需要发送 100 次这样的 batch 任务消息即可。假设发送一次 batch 消息耗时 50ms~100ms,那么总耗时才 5 秒~10 秒。

 

(3)创建促销活动接口异步化处理提升体验


可见,对推送任务的查询、分片、合并这些预处理过程,再快也需要几秒的时间。所以这个环节不应该合并在创建促销活动的接口中,而是通过 RocketMQ 实现异步化处理。也就是 RocketMQ 可以实现耗时任务的异步化,提升促销活动接口的性能。

 

(4)万级的分片任务消息分布式推送完成消费


对于已经写入 RocketMQ 集群的那 1 万条任务消息,默认会均匀分散地落到集群的各个 Queue 里。而多机器部署的推送系统会组成一个 ConsumerGroup 一起去均匀消费这 1 万条任务消息。所以 1 万条推送任务会均匀分配给各个推送系统,从而实现分布式推送。也就是说,千万级用户的推送任务可以均匀分布到各个推送系统的机器上,每台推送系统机器只需要负责处理一部分任务的推送即可。

 

16.推送系统单线程完成推送任务的时间分析


目前有 1 万个推送任务,每个推送任务需要给 1000 个用户进行推送。推送系统拿到一个推送任务时,得到的其实是用户自增 ID 的一个范围,比如 1~1000、1001~2000。

 

此时推送系统会根据 ID 范围去调用会员系统批量查询用户的接口,来查出这 1000 个用户与推送相关的信息。会员系统提供给推送系统的用于批量查询用户的接口,会提供开始和结束 ID,以及有数量限制如最多查 2000 条数据。查出来这 1000 个用户后,推送系统会进行遍历,调用第三方平台的 SDK 完成推送。

 

一般来说,推送系统调用第三方平台的 SDK 给用户推送短信、邮件等之后,不用等待响应,因为第三方平台内部也会做异步化的推送处理。

 

假设推送系统调用第三方平台的 SDK 完成一个用户的推送需要 200ms,那么推送系统完成一个推送任务,也就是 1000 个用户的推送,采用单线程的方式就要 1000 * 200ms = 3 分钟。

 

如果推送系统部署了 2 台机器,那么要完成千万级用户量的推送,每台机器就需要完成 5000 个任务。所以每台机器完成一次千万级用户量的推送就需要总共 5000 * 3 = 15000 分钟(上百个小时),这太慢了。


 

17.推送系统多线程完成推送任务的方案


如果一个推送系统获取一个一个的推送任务后,采用单线程对里面的 1000 个用户依次发送推送,并且每次向第三方平台发送推送请求需要耗时 200ms,那么完成一个推送任务就需要 200ms * 1000 = 3 分钟。如果此时只有两台机器部署了推送系统,那么每台机器需要处理 5000 个任务,也就是 15000 分钟,大概一周时间。

 

因此,必须要把单台机器的推送效率提升到极致,每台机器必须是多线程高并发地发起推送请求。假设每台机器开启 30 个线程,每个线程并发地去处理一个推送任务。那么每台机器每隔 3 分钟就可以处理 30 个推送任务,1 小时就可以处理 600 个推送任务。

 

如果推送系统是普通的 4 核 8G 的机器,那么可以开启 30 个线程并发处理推送任务。于是部署 5 台 4 核 8G 的机器,每台机器开启 30 个线程并发处理 30 个推送任务,3 小时就可以完成千万级用户的推送。

 

所以随着机器配置的提升,比如 8 核 16G 开 80 个线程,快则 1 小时之内,慢则两三小时,其实都可以完成千万级用户推送。

 

线程数量一般是 30、50、80、100,具体开启多少个线程还是应该以压测时的 CPU 负载、网络带宽消耗、推送效率为准。CPU 负载一般在 80%以下,通常压测到 50%-70%即可,而网络带宽可以压测到快打满。


 

18.千万级用户推送的削峰填谷


在千万级用户推送的场景里,RocketMQ 扮演的角色主要是两个:一个是异步化解耦提升接口性能,一个是对瞬时大量消息进行削峰填谷。

 

(1)异步提升接口性能


运营人员在营销系统创建促销活动时,会发布促销活动已创建的消息到 RocketMQ。营销系统会消费促销活动已创建的消息,然后进行预处理。进行预处理时,会查询千万级用户总数、将千万级用户推送任务分片成 1 万个任务、然后每 100 个任务 batch 批量发送到 RocketMQ。

 

(2)瞬时大量消息削峰填谷


进行预处理时,营销系统对千万级用户的推送任务分片成 1 万个任务时,就会瞬时发送 1 万条消息到 RocketMQ。此时只能用 RocketMQ 削峰填谷,因为营销系统直接调用推送系统的 Push 接口处理一个任务,最快也要几分钟。

 

所以营销系统在进行预处理时,不能同步给推送系统处理,最好用 RocketMQ 对这 1 万条推送任务的消息进行暂存,之后由 RocketMQ 的消费者对这些消息慢慢地按自己的速率进行处理。

 

19.千万级用户惰性发优惠券方案


惰性发券是互联网公司里非常经典的一个方案。互联网公司给全量用户发券,基本用的都是惰性发券的思路和方案,而不会使用上面的千万级用户 Push 方案。

 

说明一:运营人员使用营销系统创建发优惠券的促销活动时,首先会把优惠券和促销活动的数据写入 MySQL 数据库中,然后把优惠券数据写入 Redis 缓存中,这样便完成了整个优惠券的创建处理了。

 

说明二:当用户成功登录电商 APP 的账号系统时,账号系统会发布一条用户已登录的消息到 RocketMQ 消息中间件集群。

 

说明三:营销系统会持有一个 RocketMQ 消费者,专门消费 RocketMQ 中用户已登录的消息。

 

说明四:当营销系统消费到用户已登录的消息时,会到 Redis 缓存里查询当前是否有优惠券需要对该用户发券。也就是判断当前是否有优惠券需要发放 + 该用户还没发放该优惠券 + 优惠券还在有效期范围内。如果有,那么营销系统的这个 RocketMQ 消费者就会进行惰性发券,把对该用户发券的数据记录写到 MySQL + Redis 里。从而实现如下千万级用户惰性发券的效果:

 

当用户使用电商 APP 完成账号系统的登录后,就会发布一条某用户已经登录的消息到 RocketMQ 消息中间件集群。营销系统会从 RocketMQ 中消费某用户已经登录的消息,去 Redis 分布式缓存集群里查询是否有该用户的发券记录。如果有就不需要再发券了,如果没有就需要向该用户发券。营销系统向用户发完券后,会将发券记录写入 Redis 分布式缓存集群里,避免下次用户登录时重复发券。


 

20.基于 RocketMQ 分发事件提高扩展性


(1)使用 RocketMQ 主要为了实现三个效果


一.耗时任务异步化提升接口性能

二.瞬时高并发操作和操作进行削峰填谷

三.发布事件消息实现多系统接耦和提升系统扩展性

 

上面介绍的惰性发券,就是经典的提高系统扩展性的例子。惰性发券要实现的效果是:当用户登录后,要检查是否需要对全量用户发放优惠券、当前用户是否发过优惠券。要实现这个效果,其实有两种思路。

 

(2)思路一不使用 RocketMQ 会让系统间耦合


思路一:用户登录后,账号系统 RPC 调用营销系统,让营销系统检查是否要发优惠券,如果需要就发券

 

在这个思路中,账号系统由于需要 RPC 调用营销系统,所以两者便耦合在一起了。若营销系统接口有变化,则会影响账号系统,账号系统需要跟着营销系统的改变而改变。若营销系统接口性能出现问题,由于账号系统会同步 RPC 调用营销系统判断是否要发券,这会影响账号系统的登录接口性能。若后续需要在用户登录后增加积分,则影响了系统扩展性,除了会员系统需要修改外,还要账号系统也跟着进行修改,影响协作效率。

 

(3)思路二使用 RocketMQ 解耦系统提升扩展性


思路二:用户登录后,账号系统发布一条用户已登录事件消息到 RocketMQ,谁对用户已登录事件消息感兴趣就自行消费处理

 

在这个思路中,账号系统不再受营销系统接口改动的影响,也不再受营销系统接口性能的影响。其他系统如会员系统在登录时的新需求,同样无需账号系统介入。从而提升了系统扩展性。

 

21.指定用户群体推送领券消息的方案


(1)千万级用户量下对全体用户推送消息


首先,当运营人员创建对全体用户推送消息的促销活动时,会通过 RocketMQ 进行异步化处理。

 

然后,在异步化处理时,会对千万级用户的推送任务进行分片拆分,拆分成多个任务,每 100 个任务会 batch 批量发送到 RocketMQ。

 

接着,部署多个推送系统以多个 RocketMQ 消费者的角色实现分布式推送。

 

最后,推送系统每消费一个任务,用 30~100 个线程的线程池去并发完成一个任务中的用户的消息推送。

 

从而实现对千万级用户的全体推送,在时间和效率上是可控的。

 

(2)千万级用户量下对全体用户发券


运营人员在创建对全体用户发券的促销活动时,会首先将券写入数据库进行持久化,然后将券写入到 Redis 缓存。

 

由于用户登录(每天第一次进入 APP)属于低并发的事件,1000 万用户每天的日活用户大概也就 100 万左右。所以当用户登录时,便会将用户登录消息写入 RocketMQ,来实现账号系统和其他系统的解耦以及账号系统的高可扩展性。

 

营销系统会消费 RocketMQ 的用户登录消息,然后去 Redis 缓存查询券和发券记录,完成惰性发券。

 

通过这样的惰性发券,来实现对千万级用户的发券平均分摊到很多天(活跃的用户)来完成。

 

(3)对特定用户群体推送优惠券


特定用户群体可能用户数很大(百万级),但领券时不一定会同时过来开始领。

 

首先,当运营人员创建对特定用户群体发券的促销活动时,也是通过 RocketMQ 进行异步化处理。

 

然后,在异步化处理时,会去会员系统查询特定用户群体的用户数量,并且将推送任务分片拆分成多个任务,批量发送到 MQ。

 

接着,部署多个推送系统以多个 RocketMQ 消费者的角色实现分布式推送。

 

最后,推送系统每消费一个任务,是用 30~100 个线程的线程池去并发完成对一个任务中的用户的领券消息 Push。

 

从而实现对百万级特定用户的优惠券推送,在时间和效率上是可控的(分片任务里的用户会通过分页查询出来)。

 

22.基于大数据的爆款商品计算


营销系统有个功能是定期推送爆款商品给用户,需要根据不同的用户画像群体计算爆款商品,可能由 AI 团队负责,也可能由营销技术团队的 AI 小组负责。

 

关于爆款商品计算服务:其实不是每天都要去计算一次爆款商品推荐的。因为如果每天都给用户推荐一次它可能感兴趣的爆款商品,那么就会形成对用户群体的骚扰性推送。即便是促销活动,也最多每个月进行一次全量用户推送/指定用户群体发券,通常是几个月一次。

 

商品会有自己的标签,用户在 APP 上的行为也会产生自己的标签。通过用户画像标签,可以组合成一类用户最感兴趣的商品门类里的爆款商品。数据仓库里一般都有哪个商品购买最多、哪个商品浏览多少次、哪个商品好评率多少等信息。


 

23.爆款商品分布式调度推送方案


通过用户画像标签,可以组合出某类用户最感兴趣的商品中的爆款商品。在每天计算出来的数据里,包含了很多类用户的爆款商品,一组用户画像标签会对应一类用户。

 

由于营销系统是多台机器集群部署的,为了让营销系统可以分布式调度处理不同用户画像标签的爆款商品推送,可以通过 XXLJob 来实现分布式定时调度。

 

一.首先 XXLJob 调度管理中心会有一组 Excutors。

 

二.然后营销系统在启动时会有一个 Excutor 向 XXLJob 调度管理中心的这组 Excutors 进行注册。

 

三.接着开发者会在 XXLJob 调度管理中心进行调度任务的配置,比如该调度任务就是每天定时调度一次。当 XXLJob 调度管理中心的该调度任务每天定时执行时,就会找到所绑定的 Excutors 分组。通过该 Excutors 分组发送请求给营销系统的 Excutor,并且带上 shardIndex + shardNums 分片数据。

 

四.当营销系统的 Excutor 收到请求后,会根据调度任务的配置,找到执行任务的 Bean,让 Excutor 执行 Bean 的逻辑。执行任务的 Bean 会收到当前所属的分片编号 shradIndex 和分片总数 shardNums,并且可以查出当天所有用户画像标签爆款的商品推荐及其数据编号,通过数据编号的 Hash 值对 shardNums 取模=shardIndex 来挑选出当前营销系统应该处理哪些编号的商品推荐。

 

五.营销系统知道自己需要处理哪些编号下的爆款商品推荐后,就根据其对应的用户画像标签,去大数据系统查询出这些用户总数量,可能几十万级~几百万级。然后根据用户总数量进行推送任务的分片,将总的推送任务拆分成多个推送任务,再把分片后的任务 batch 批量发到 MQ。

 

六.推送系统接着会对 MQ 里的已分片的推送任务进行消费,通过分页查出具体的用户利用线程池进行爆款商品推送。


文章转载自:东阳马生架构

原文链接:https://www.cnblogs.com/mjunz/p/18706926

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
RocketMQ实战—营销系统业务和方案介绍_数据库_EquatorCoco_InfoQ写作社区