协程推送公众号百万模板消息

前面说过easywechat日常微信开发基本不会有性能问题,但当公众号粉丝数超百万时,大批量推送模板消息会遇见很多难以解决的问题,比如推送慢,推送超时,内存溢出等情况。

使用hyperf可以一定程度解决这些问题,但是在此说明,hyperf协程不管是大批量爬取数据,还是大批量推送速度肉眼可见的加快,但hyperf不是性能“银弹”,当服务器运行大运算量程序时,仍然会有瓶颈,服务器cpu,内存等限制,这时候程序已经是最优情况,推送速度提升数倍,但仍然不是自己想要的速度,这时需要升级服务器才能解决问题。

如果小伙伴有对hyperf不了解的可以先去看swoole文档和hyperf文档。

这里将自己写的代码贴出,希望能给小伙伴一些帮助。

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace App\Controller;
use QL\QueryList;
use Hyperf\DbConnection\Db;

use  Hyperf\HttpServer\Contract\RequestInterface;
use App\Utils\Cache;
use EasyWeChat\Factory;
use Illuminate\Support\Facades\Auth;
use EasyWeChat\Kernel\Messages\Image;
use Hyperf\Logger\LoggerFactory;

use Hyperf\Utils\ApplicationContext;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use Hyperf\Guzzle\CoroutineHandler;

class SendwechatmsgCoprogramController extends AbstractController
{
    /**
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;
    protected $config;
    public function __construct(LoggerFactory $loggerFactory)
    {
        // 第一个参数对应日志的 name, 第二个参数对应 config/autoload/logger.php 内的 key
        $this->logger = $loggerFactory->get('1.log', 'default');
        $this->config= [
            'app_id' => '',
            'secret' => '',
            'token'  => '',
            'aes_key' => '',
           
            // 指定 API 调用返回结果的类型:array(default)/collection/object/raw/自定义类名
            'response_type' => 'array',
            //...
            'oauth' => [
                'scopes'   => ['snsapi_userinfo'],//snsapi_base不询问,snsapi_userinfo询问
                'callback' => '/wechat/oauth_callback',
            ],
        ];
        
    }
    
    public function wuxianji($app,$next_openid="",$lv=1){
        if(!Cache::get('wuxianji5_'.$lv)){
            $userlists=$app->user->list($next_openid);
            $userlists['page']=$lv;
            Cache::set('wuxianji5_'.$lv,$userlists);
        }
        $userlists=Cache::get('wuxianji5_'.$lv);
        
        
        $count=$userlists['count'];
       
        if($count>0){
            $openids=$userlists['data']['openid'];
            $next_openid=$userlists['next_openid'];
            $page=$userlists['page'];
            $this->wuxianji($app,$next_openid,$lv+1);
           
        }else{
            $total=$userlists['total'];
            $page=intval(ceil($total/10000));
            Cache::set('wuxianji5_page',$page);
            return "ok";
        }
    }
    
    
    //模板消息通知
    public function start1(RequestInterface $request){
        $container = ApplicationContext::getContainer();
        $app = Factory::officialAccount($this->config);
        $handler = new CoroutineHandler();

        // 设置 HttpClient,部分接口直接使用了 http_client。
        $config = $app['config']->get('http', []);
        $config['handler'] = $stack = HandlerStack::create($handler);
        $app->rebind('http_client', new Client($config));
        // 部分接口在请求数据时,会根据 guzzle_handler 重置 Handler
        $app['guzzle_handler'] = $handler;
        
        // 如果使用的是 OfficialAccount,则还需要设置以下参数
        $app->oauth->setGuzzleOptions([
            'http_errors' => false,
            'handler' => $stack,
        ]);
        
        $this->wuxianji($app);
        $page=Cache::get('wuxianji5_page');
        for ($lv=$page;$lv>=1; $lv--){
            // $lv=$request->input('lv',1);
            $userlists=Cache::get('wuxianji5_'.$lv);
            $logger=$this->logger;
            echo("第".$lv."页开始时间:".time().PHP_EOL);
            $logger->info("第".$lv."页开始时间:".time().PHP_EOL);
            $count=$userlists['count'];
            // echo($count."条数据".PHP_EOL);
            // $logger->info($count."条数据".PHP_EOL);
            
            
            
            if($count>0){
                $limit=100;
                $batch=intval(ceil($count/$limit));
                echo("分".$batch."批推送".PHP_EOL);
                $logger->info("分".$batch."批推送".PHP_EOL);
                $openids=$userlists['data']['openid'];
                // $next_openid=$userlists['next_openid'];
                
                for ($b=$batch-1;$b>=0; $b--){
                    $openidsbatch= array_slice($openids,$b*$limit,$limit);
                    $count=count($openidsbatch);
                    
                    //等待协程  如果不加这个一下执行几千几万条,会有Allowed memory size of 268435456 bytes exhausted,内存溢出,所有我们协程一次执行一百和协程,等待一百协程执行成功后在执行下一个一百协程。
                
                    // 计数器
                    echo($b."批开始推送".PHP_EOL);
                    $logger->info($b."批开始推送".PHP_EOL);
                    
                    $wg = new \Hyperf\Utils\WaitGroup();
                    $wg->add($count);
                    // echo($b."批数量:".$count.PHP_EOL);
                    // $logger->info($b."批数量:".$count.PHP_EOL);
                    for ($i=$count-1;$i>=0; $i--){
                        co(function () use ($app,$openidsbatch,$i,$logger,$wg) {
                            
                            
                            // echo($openidsbatch[$i]);
                            // $logger->info($openidsbatch[$i]);
                            $app->template_message->send([
                                'touser' =>  $openidsbatch[$i],
                                'template_id' => '',
                                'url' => '',
                                'data' => [
                                    'first' => [
                                      'value' => "重要通知!重要通知!\n【小初学习帮】特邀北京一线特级教师来助力寒假,为孩子们线上授课!",
                                    ],
                                    'keyword1' => [
                                      'value' => '(限时免费)(限时免费)',
                                    ],
                                    'keyword2'=>[
                                      'value' => '小学、初中、高中都可学习',
                                    ],
                                    'keyword3'=>[
                                      'value' => '解题大招+干货技巧+思维方法',
                                    ],
                                    'keyword4'=>[
                                      'value' => '请家长们尽快领取!',
                                    ],
                                    'remark'=>""
                                ],
                                // //跳小程序所需数据,不需跳小程序可不用传该数据
                                // 'miniprogram' => [
                                //         // 所需跳转到的小程序appid(该小程序 appid 必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
                                //         'appid' => "appid",
                                //         // 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
                                //         'pagepath' => 'pages/xxx',
                                // ],
                                
                            ]);
                            // sleep(1);
                            // echo($i.PHP_EOL);
                            // $logger->info($i.PHP_EOL);
                            $wg->done();
                            
                        });
                    }
                    
                    $wg->wait();
                }
                
               
            }else{
              echo("全部完成".PHP_EOL);
            //   $logger->info("全部完成".PHP_EOL);
              echo("结束时间:".time().PHP_EOL);
            //   $logger->info("结束时间:".time().PHP_EOL);
          }
        }
      return "ok";
        
        
        
        
    }
    

}

如果是正常推三天两夜能推完给百万用户,加协程可以在10小时内推送完毕,推送速度快了六倍,将服务器2核8G服务器升级为2核16G,百万用户推送仅用了不到一个小时,服务器性能提升一倍单推送速度快了10倍,我觉得是因为加入协程情况下2核8G服务器根本带不动程序,程序是协程与阻塞方式共同进行的,当性能提升到可以带动程序时,运行速度就有了质的飞升。这个特意注明,当有推送给公众号百万用户该需求时一定选择性能超过2核16G的服务器。

0 评论
内联反馈
查看所有评论