zoukankan      html  css  js  c++  java
  • swoole入门abc

    1. 入门abc

    1.1 github账号添加

    • 第一步依然是配置git用户名和邮箱
    
    git config user.name "用户名"
    git config user.email "邮箱"
    
    
    • 生成ssh key时同时指定保存的文件名
    
    ssh-keygen -t rsa -f ~/.ssh/id_rsa.github -C "email"
    
    
    • 新增并配置config文件

      touch ~/.ssh/config

    在config文件里添加如下内容(User表示你的用户名)

    
    Host *.github.com
    IdentityFile ~/.ssh/id_rsa.github
    User test
    
    
    • 上传key到github
    
      pbcopy < ~/.ssh/id_rsa.github.pub
      eval "$(ssh-agent -s)"
      ssh-add ~/.ssh/id_rsa.github
    
    
    
    git remote add origin git@github.com:zhuanxuhit/laravel-doc.git
    git push -u origin master
    
    

    1.2 case:Rate Limit

    以rate limit为例来使用swoole开发。

    1.2.1 最简单的隔离算法

    思想很简单,计算相邻两次请求之间的间隔,当速率大于Rate的时候,就拒绝请求。

    技能分析:

    1. swoole的swoole_http_server功能,监听端口,等待客户端请求
    2. 注册回调,当请求到来的时候,处理请求

    代码示例:

    
    <?php namespace SwooleRate;
    
    class Simple {
    
        protected $http;
    
        protected $lastTime;
    
        protected $rate = 0.1;
        /**
         * Simple constructor.
         *
         * @param $port
         */
        public function __construct($port)
        {
            $this->http = new swoole_http_server('0.0.0.0',$port);
            $this->http->on('request',array($this,'onRequest'));
        }
    
        public function onRequest( swoole_http_request $request, swoole_http_response $response )
        {
            $lastTime = $this->lastTime;
            $currentTime = microtime(true);
    
            if(($currentTime-$lastTime)<1/$this->rate){
               // deny
            }
            else {
                $this->lastTime = $currentTime;
                // access
            }
        }
    
        public function start()
        {
            $this->http->start();
        }
    }
    
    $simple = new Simple(9090);
    $simple->start();
    
    

    分析上面的代码,我们发现会有什么问题?如果两个请求同时进来,都读到了lastTime,没有被拒绝,但是这两个请求本身是已经请求过快了。

    这个疑问产生的原因是对于swoole的网络处理模型不是很清晰,如果请求是串行处理的,那不会有什么问题?但是如果请求是并发处理,那多个请求可能读到的是同一个时间戳,导致瞬间并发很大,出现问题。

    首先来解决第一个问题:swoole是什么

    swoole 是一个网络通信框架,首要解决的问题是什么?通信问题,之后就是高性能这个话题了,高性能主要从3个方面考虑

    1) I/O调度模型:同步阻塞I/O(BIO)还是非阻塞I/O(NIO)。

    2) 序列化框架的选择:文本协议、二进制协议或压缩二进制协议。

    3) 线程调度模型:串行调度还是并行调度,锁竞争还是无锁化算法。

    swoole在IO模型上是使用异步阻塞IO实现,调度模型则是采用Reactor,简单说就是有一个线程专门负责IO操作,当关心事件发生的时候,进行回调函数处理,具体分析见下一章。

    通过修改代码,使用ab test工具,我能够简单的模拟上面的讨论到的并发问题:

    
    public function onRequest( swoole_http_request $request, swoole_http_response $response )
        {
            $lastTime = $this->lastTime;
            $currentTime = microtime(true);
            $pid =($this->http->worker_pid);
            if(($currentTime-$lastTime)<1/$this->rate){
                
                echo "deny worker_pid: $pid lastTime:$lastTime currentTime:$currentTime
    ";
            }
            else {
                $this->lastTime = $currentTime;
               
                echo "accept worker_pid: $pid lastTime:$lastTime currentTime:$currentTime
    ";
            }
        }
    
    

    测试脚本

    ab -n10 -c5 http://0.0.0.0:9090/

    测试结果:

    
    accept worker_pid: 45674 lastTime: currentTime:1463470993.3306
    accept worker_pid: 45675 lastTime: currentTime:1463470993.331
    accept worker_pid: 45671 lastTime: currentTime:1463470993.3318
    accept worker_pid: 45672 lastTime: currentTime:1463470993.3322
    accept worker_pid: 45673 lastTime: currentTime:1463470993.333
    deny worker_pid: 45674 lastTime:1463470993.3306 currentTime:1463470993.3344
    deny worker_pid: 45673 lastTime:1463470993.333 currentTime:1463470993.3348
    deny worker_pid: 45675 lastTime:1463470993.331 currentTime:1463470993.3351
    deny worker_pid: 45671 lastTime:1463470993.3318 currentTime:1463470993.3352
    deny worker_pid: 45672 lastTime:1463470993.3322 currentTime:1463470993.3354
    
    

    可以很显然的看到,并发请求来的时候,读到的lastTime都是未设置过的

    模拟出并发问题后,这个关于swoole中有进程模型也很好测试出来:

    
    public function serverInfoDebug()
        {
            return json_encode(
                [
                    'master_id' => $this->http->master_pid,//返回当前服务器主进程的PID。
                    'manager_pid' => $this->http->manager_pid,//返回当前服务器管理进程的PID。
                    'worker_id' => $this->http->worker_id,//得到当前Worker进程的编号,包括Task进程
                    'worker_pid' => $this->http->worker_pid,//得到当前Worker进程的操作系统进程ID。与posix_getpid()的返回值相同。
                ]
            );
        }
    
    

    启动成功后会创建worker_num+2个进程。主进程+Manager进程+worker_num个Worker进程。

    完整地址:
    https://github.com/zhuanxuhit/php-recipes/blob/master/app/SwooleAbc/Rate/Simple.php

    那回到上面的问题,怎么解决并发问题呢?在C++等语言中,很好解决这个问题,使用锁,互斥访问。

    写到这的时候,发现个问题,发现在回调中,每个worker在处理onRequest函数的时候,this都是一个新的,为什么呢?因为worker进程都是由Manager进程fork()出来的,自然数据是新的一份了。

    现在要解决的问题变为:如何在swoole中实现多个进程的数据共享功能

    可以看到https://github.com/swoole/swoole-src/issues/242
    其中建议,可以通过使用swoole提供的swoole_table来做,代码如下:

    
      public function onRequest( swoole_http_request $request, swoole_http_response $response )
        {
            $currentTime = microtime(true);
            $pid =($this->http->worker_pid);
            $this->table->lock();
            $lastTime = $this->table->get( 'lastTime' );
            $lastTime = $lastTime['lastTime'];
            if(($currentTime-$lastTime)<1/$this->rate){
                $this->table->unlock();
                //deny
            }
            else {
                $this->table->set( 'lastTime', [ 'lastTime' => $currentTime] );
                $this->table->unlock();
                // access
            }
        }
    
    

    再次测试,能够发现很好的满足了要求。

    1.2.2 最清晰的吊桶算法

    隔离算法的问题很明显,使用ab -n2 -c2 http://0.0.0.0:9090/,同时并发2个请求就被拒绝了,因此只计算了相邻两次的间隔,而没有关注1s内的请求,因此一个改进思路就是以s为key,记录时间戳下来。下面是实现

    
        public function onRequest( swoole_http_request $request, swoole_http_response $response )
        {
            $currentTime = time();
            $this->table->lock();
            $count = $this->table->get( (string)$currentTime );
    //        (new Dumper)->dump($count);
            if($count){
                $count = $count['count'];
            }
            else {
                $count = 0;
            }
    
            if($count >$this->rate){
                $this->table->unlock();
                // deny
            }
            else {
                $this->table->set( (string)$currentTime, [ 'count' => $count + 1] );
                $this->table->unlock();
                //accept
            }
        }
    
    
    

    由于每s的数据都记录了,没有过期,导致数据不断增长,有问题,而且由于1s是分割的,不是连续的,必然会造成最开始脑图中的bad case。

    于是就有了下面的第三个方法:最精确的队列算法

    1.2.3 最精确的队列算法

    思路上就是将请求入队,记录请求的时间,这样就可以判断任意连续的多个请求,其是否是在1s之内了

    首先看下这个算法思路:假设rate=5,当请求到来的时候,得到当前请求编号,然后减5得到index,然后判断两次请求之间的时间间隔,是否大于1s,如果大于则accept,否则deny

    n-5 n-4 n-3 n-2 n-1 n n+1 n+2 n+3 n+4 n+5

    现在来的请求是n,则去n-5,为什么是减5,因此rate是5,则当qps为6的时候就deny,因此需要判断n-5到n这6个请求的间隔!

    算法有了,下面就是在swoole中怎么实现队列的问题了,这个队列还需要在进程间共享。

    我们可以使用swoole_table来实现,另外还需要一个计数器,给每个请求编号,实现如下:

    
    // 每个请求过来后的是否判断通过,这个操作必须要单点,串行,所以也就是说必须要加速
            $this->table->lock();
            $count = $this->counter->add(1);
            $bool = true;
            $currentCount = $count + 1;
            $previousCount = $count - $this->rate;
            if($currentCount<=$this->rate){
                $this->table->set( $count, [ 'timeStamp' => $currentTime ] );
                $this->table->unlock();
            }
            else {
                $previousTime = $this->table->get( $previousCount );
                if ( $currentTime - $previousTime['timeStamp'] > 1 ) {
                    $this->table->set( $currentCount, [ 'timeStamp' => $currentTime ] );
                    $this->table->unlock();
                } else {
                    // 去除 deny
                    $bool = false;
                    $this->counter->sub( 1 );
                    $this->table->unlock();
                }
            }
    
    

    上面有一个核心点,之前一直没有注意到的:对所有请求的处理都是需要互斥的,即是一个单点,处理完后才能转发给真正的业务逻辑进行处理。

    因此可以将Rate的逻辑抽离出来,作为一个服务提供,这个以后讲服务化的时候再做的。

    1.2.4 最传统的令牌算法

    令牌算法类似小米抢购,放量出来一定的票,当人想进来抢的时候,必须有F码才能进行抢购,而票的放出是按一定速率产生的。

    上面算法实现时,需要用到swoole的定时器功能,需要在OnWorkerStart的回调的时候使用

    
    public function onWorkerStart(swoole_server $server, $worker_id)
        {
            if($worker_id ==0){
                $server->tick( 1000/$this->rate, [$this,'addTicket'] );
            }
        }
    
    

    而请求到来的时候,就是通过getTicket获取资格,没票的时候,直接返回false

    完整的github地址:
    https://github.com/zhuanxuhit/php-recipes/tree/master/app/SwooleAbc/Rate
    Rate limit的介绍:http://www.bittiger.io/classpage/hfjPKuZaLxPLyL5iN
    gitbook地址:
    https://zhuanxuhit.gitbooks.io/swoole-abc/content/chapter1.html

    原文地址:https://www.jianshu.com/p/7c16cb61b59d
  • 相关阅读:
    OAuth2.0
    Base64编解码 代码
    Intent之Action
    android 之 WebView详解
    FCKeditor 在ASP.Net 中的使用说明
    css条件注释
    AspNetPager 服务器控件使用实例
    【转】分页控件ASPNETPAGET
    SQL server挂了之后
    应用主题后FCKeditor上传问题的解决及相应的改进
  • 原文地址:https://www.cnblogs.com/lovellll/p/10409495.html
Copyright © 2011-2022 走看看