专注WEB开发 分享经验,沉淀知识

如何一次性推送百万级别的消息

 作者:chenxing  时间:2021-02-26 17:57  阅读:266  评论:

这样子把目标用户和消息推送这两个任务独立开,推送的时候可以开启多进程,大大的提高推送效率

分析一次公众号模板消息推送。

业务场景:

需要给不同的用户,推送不同的公众号模板消息

方案分析:

这里有两种执行方案

方案1.使用总关注用户的数据,循环校验每个用户属于哪个标签,然后调用公众号模板消息接口推送

方案2.

o1. 先跑出目标用户:一次查询源数据,一次查出标签用户,通过程序运算(数据交集,差集)得到目标用户,存到redis set集合中。

o2. 开启多进程,按照每个进程需要执行的目标数据量,从redis set 取出,直接循环调用公众号模板消息接口推送

方案优势对比:方案1只适用于数据量少的场景,优势是代码实现起来简单。数据一大对数据库io就很多。方案2先跑目标用户,然后在执行发送,对数据io很小,但对程序内存要求比较高,需要分配比较高的内存,执行效率高。 百万级别的用户推送得选择方案2。我们这里用代码实现下这一方案。

这里我们简化下业务标签,关注用户(100w),和标签1用户(100w)的数据,以下使用伪代码分析。


class Weixin_template {

o#模板消息执行方案:
    #1.根据分层策略从数据库中筛选目标用户存储在redis中
    #2.多进程调用消息接口推送
    #   2.1 10w数据开启一个进程处理
    #3.防重处理
    #   3.1 一个用户只接收一个消息策略:redis集合不区分策略和公众号
    #   3.2 -个消息策略多个公众只触达一个公众号:redis集合区分策略不区分公众号
    #4.临时存储数据及时del掉

    public function __construct() {
        set_time_limit(0);
        error_reporting(E_ALL);
        ini_set('display_errors', 1);
        //分配2G内存
        ini_set('memory_limit', '2048M');
        ini_set('max_execution_time', '0');
    }

o//获取标签1目标用户
opublic function target_users_set_tag1($$appid, $redis_key) {
o    echo $appid . ":target_users_set_tag1 start!\n";
o    $targe_users = [];
o    $users_subscribe = [];

o    //关注用户
o    echo "users_subscribe select doing.....\n";
o    $users_subscribe = $this->get_db_users_subscribe($appid);
o    //标签1用户
o    echo "get_db_users_tag1 select doing.....\n";
o    $users_tag1 = $this->get_db_users_tag1();
o    
o    //目标用户:关注用户并且是标签1的用户(交集运算)
o    $targe_users = array_intersect($users_subscribe, $users_tag1);
o   //目标用户保存到redis set 中
o    $this->redis_set_add($redis_key, $targe_users);

o    echo sprintf("target_users_set_tag1:%s\n", sizeof($targe_users));

o    echo $appid . ":target_users_set_tag1 finish!\n\n";
o}

o//执行模板消息
opublic function send_template_msg($tag, $appid, $send_num, $is_prod=0) {
o// redis = get_redis();
        if ($is_prod == 1) {
        //获取目标用户:利用spop实现多进程处理目标用户
            $send_users = $redis->sPop($target_users_key, $send_num);
        } else {
        //获取测试用户
            $send_users = $this->users_test();
        }

        //获取发送内容
        $content = get_content($tag, $appid);

        echo $target_users_key . "\n";

        $counts = 0;

        $total_key = sprintf('total_send_template_msg_%s', $date);
        foreach ($send_users as $send_user) {
        //消息防重:防止消息轰炸
            if ($redis->sIsMember($sended_users_key, $send_user) == 1) {
                $redis->sAdd(sprintf('isset_sended_users_%s', $date), $send_user);
                continue;
            }
            if ($redis->sIsMember($total_key, $send_user) == 1) {
                // echo $send_user . "\n";
                $redis->sAdd(sprintf('isset_total_users_%s', $date), $send_user);
                continue;
            }
            //发送消息接口
            $res = $this->api_template_msg($content);
            //保存目标用户
            // echo $send_user . "\n";
            $redis->sAdd($sended_users_key, $send_user);
            $redis->sAdd($total_key, $send_user);
            $counts++;
            if (($counts % 1000) == 0) {
                echo sprintf('count:%s,  %s', $counts, $send_user);
            }
        }
        $redis->expire($sended_users_key, 3600*24*30);
        $redis->expire($total_key, 3600*24*30);
        echo sprintf("isset_sended_users:%s\n", $redis->sCard('isset_sended_users'));
        echo sprintf("isset_total_users:%s\n", $redis->sCard('isset_total_users'));
        echo sprintf("%s:%s\n", $sended_users_key, $redis->sCard($sended_users_key));
        echo sprintf("%s:%s\n", $total_key, $redis->sCard($total_key));
o}

}

这样子把目标用户和消息推送这两个任务独立开,推送的时候可以开启多进程,大大的提高推送效率。

除特别注明外,本站所有文章均为作者原创。 或分享自己的编程经验,或探讨工作中的问题,或聊以人生趣事。 转载请注明出处来自 http://www.qiusuoweb.com/137.html

发布评论

 提交评论
有人回复时邮件通知我

 评论(0)

站长头像
chenxing(PHP攻城狮)

运营天数

总访问量

文章数量

-

-

-

交流群:157451741

新浪微博:草莽兴

 近期文章

阿里开发mysql规范手册(阅读整理)

 2021-09-30 15:27  36

如何一次性推送百万级别的消息

 2021-02-26 17:57  266

redis set集合取出一组数据并删除

 2021-02-26 12:50  415

一年时间又回到这里

 2019-07-27 16:54  407

 最新评论

 诚心: 09月29日 23:01
学到了
来源: 如何一次性推送百万级别的消息
 Nick: 04月14日 12:26
网上的资料还是太老,都只是取一个元素,解决了一大难题
来源: redis set集合取出一组数据并删除
 skywalker: 11月03日 18:21
简洁明了
来源: mysql 获取某个日期的前一天或后一天
 lisheng: 05月09日 19:26
兴哥牛B加油哈兴哥成功的道路上你又进了一步 哈哈
来源: 一年时间又回到这里
 心态炸裂: 03月24日 10:54
No3.blindcomfirm 多了一个l,望改正!!!
来源: 微信小程序获取input值的总结
 666666: 11月08日 13:49
66666
来源: 一年时间又回到这里