• [Design] 后端程序的高并发与异步


    既然涉及到高并发这个概念,就少不了先谈这么几个概念,并发数、多进程、多线程、协程、负载均衡。

    操作系统上讲的并发是操作系统上有几个程序在同时执行,单核CPU在微观上是由CPU调度执行,非同时执行,多核CPU在微观上才是真正的并行。

    互联网产品的并发通常是指并发连接数,用户同时访问数量,哪些因素能影响到并发能力,既有编程模型,也有服务器负载能力。

    PHP 依赖多进程解决并发数,是最原始和耗费资源的一种方式。

    以 8G 内存服务器为例,一个PHP进程占用20M内存,最多也就开几百个进程,假如任务比较耗时且并发很高,那么新请求很快就得不到响应了。

    对于并发访问 PHP 提供 curl_multi_* 系列函数,纯接口的并发调用就不需要自己写多进程、多线程程序了。

    Java是多线程模型,每个进程可以创建多个线程,线程使用进程的上下文,每个线程只需要小部分运行资源,所以比进程轻量许多。

    进程和线程都是依赖操作系统的系统调用支持的,所以这部分调度开销是难以避免的。

    协程是程序级的调度模型,最轻量,好比框架级实现像操作系统一样能自由对程序中断、调度,比如借助于 C 的 setjmp 系列函数就能做到中断。

    不管编程模型多先进,单机总会达到并发上限,要想突破限制就必须引入负载均衡,通过横向扩展解决问题。

    上面所提到的编程模型优劣,本质上还都是语言层面的,短期内并不能真正解决问题,下面就延伸出从应用层面的考虑:

    为什么高性能都离不开异步,比如 Swoole ? 目的就是提升响应时间,提高Qps。

    在我们通常的业务开发过程中,逻辑代码一般是同步阻塞模式,一方面它容易理解,另一方面也方便进行一些测试。

    这些优势再加上大部分业务场景对并发并没有较高的要求,所以是可以接受的。

    但是对于一个大型网站,以及对响应速度和并发要求高的场景,这时候就需要做些优化了,尽量把阻塞操作给异步化。

    通过消息队列来做异步有哪些场景?

      加速响应:比如注册后的提醒邮件,注册操作成功后将发消息交给队列,直接返回信息给用户,写入队列的速度非常快,然后由订阅的异步任务处理邮件发送。

      应用解耦:比如用户发帖之后要给他的粉丝推送帖子,这时候实时性要求并不高,可以将新帖的消息写入队列,然后由队列处理程序操作。

      流量削峰:比如秒杀活动,我们不需要实时处理一些购买逻辑,只要将用户请求写入消息队列,长度达到限制就提示用户已结束,后续程序再对队列内容处理。

      日志收集:日志一般都需要进行写磁盘操作,大访问量会对 I/O 造成压力,降低程序性能;此时可以将日志写入消息队列,由处理程序订阅该队列进行消费。

      广播聊天:用户通过订阅频道来获得最新发布的消息。

    实例演示

    <?php
    /**
     * Pub.php
     *
     * @author ercom
     */
    
    // 1.连接并选择数据库
    $redis = new Redis();
    
    $bool = $redis->connect('127.0.0.1', 6379, 2.5, null, 100);
    
    if (! $bool) {
        die('连接失败');
    }
    
    $bool = $redis->select(0);
    
    // 此处是模拟将一个任务放置队列中, 并发布
    //$phone = 13199999999;
    //$redis->lpush('sms-signin', $phone);
    //$redis->publish('sms-signin', $phone);
    //
    
    // 2.模拟生成批量数据
    
    for ($i = 0; $i < 10; $i++) {
        $phone = mt_rand(10000000000, 99999999999);
    
        echo $phone . PHP_EOL;
    
        // 使用 publish 代替下面模拟的每个任务 100ms 的耗时操作,在 subscribe 中处理
        //echo $phone . PHP_EOL;
        //usleep(100000);
    
        $redis->publish('signin-sms', $phone);
    }

    如果不将消息写入队列,而是每次都自己执行,响应时间很长,用户体验不好。 

    通过订阅程序异步处理任务,用户无感知,并且体验会很好。

    <?php
    /**
     * Sub.php
     *
     * @author ercom
     */
    
    // 1.连接并选择数据库
    $redis = new Redis();
    
    $bool= $redis->connect('127.0.0.1', 6379, 2.5, null, 100);
    
    if (! $bool) {
        die('连接失败');
    }
    
    // * 阻止 redis read timeout
    $redis->setOption(Redis::OPT_READ_TIMEOUT, -1); // 2.订阅耗时任务并处理 // 如果这个订阅的任务比较重要,将对可用性有要求,日志收集等可以采用。 $redis->subscribe(['signin-sms', 'signin-mail', 'crawler-task-1'], function($redis, $chan, $msg) { switch ($chan) { case 'signin-sms': // 耗时1s, 发送并记录数据库 sleep(1); echo "{$msg} 发注册短信 "; break; case 'signin-mail': break; case 'crawler-task-1': // 其他耗时任务,通过 $msg 传递参数来执行 break; } });

    其它常见的消息队列产品有 RabbitMQ、ZeroMQ、ActiveMQ、Kafka ..

    Link:https://www.cnblogs.com/farwish/p/9513100.html

  • 相关阅读:
    nyoj 409——郁闷的C小加(三)——————【中缀式化前缀后缀并求值】
    中缀表达式转后缀表达式和前缀表达式
    Zoj 3870——Team Formation——————【技巧,规律】
    BNU4286——Adjacent Bit Counts——————【dp】
    BNU7538——Clickomania——————【区间dp】
    BNU4299——God Save the i-th Queen——————【皇后攻击,找到对应关系压缩空间】
    HDU 2795——Billboard——————【单点更新、求最小位置】
    HDU 4027—— Can you answer these queries?——————【线段树区间开方,区间求和】
    BNU34067——Pair——————【找规律】
    telnet 命令使用方法详解
  • 原文地址:https://www.cnblogs.com/farwish/p/9513100.html
Copyright © 2020-2023  润新知