PHP中怎么搭建一个HTTP服务

本篇文章给大家分享的是有关PHP中怎么搭建一个HTTP服务,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

成都创新互联成立于2013年,先为嘉陵等服务建站,嘉陵等地企业,进行企业商务咨询服务。为嘉陵企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
$process = new Swoole\Process(function (Swoole\Process $process) {
    $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
    $server->set([
        'log_file' => '/dev/null',
        'log_level' => SWOOLE_LOG_INFO,
        'worker_num' => swoole_cpu_num() * 2,
        // 'hook_flags' => SWOOLE_HOOK_ALL,
    ]);
    $server->on('workerStart', function () use ($process, $server) {
        $process->write('1');
    });
    $server->on('request', function (Request $request, Response $response) use ($server) {
        try {
            $redis = new Redis;
            $redis->connect('127.0.0.1', 6379);
            $greeter = $redis->get('greeter');
            if (!$greeter) {
                throw new RedisException('get data failed');
            }
            $response->end("<h2>{$greeter}</h2>");
        } catch (\Throwable $th) {
            $response->status(500);
            $response->end();
        }
    });
    $server->start();
});
if ($process->start()) {
    register_shutdown_function(function () use ($process) {
        $process::kill($process->pid);
        $process::wait();
    });
    $process->read(1);
    System('ab -c 256 -n 10000 -k http://127.0.0.1:9501/ 2>&1');
}

首先,我们创建了一个Swoole\Process对象,这个对象会开启一个子进程,在子进程中,我创建了一个HTTP Server,这个服务器BASE模式的。除了BASE模式之外,还有一种PROCESS模式。在PROCESS模式下,套接字连接是在Master进程维持的,Master进程和Worker进程会多一层IPC通信的开销,但是,当Worker进程奔溃的时候,因为连接是在Master进程维持的,所以连接不会被断开。所以,Process模式适用于维护大量长连接的场景。

BASE模式是在每个工作进程维持自己的连接,所以性能会比Master更好。并且,在HTTP Server下,BASE模式会更加的适用。

这里,我们将worker_num,也就是进程的数量设置为当前机器CPU核数的两倍。但是,在实际的项目中,我们需要不断的压测,来调整这个参数。

workerStart的时候,也就是工作进程启动的时候,我们让子进程向管道中随意写入一个数据给父进程,父进程此时会读到一点数据,读到数据后,父进程才开始压测。

此时,压测的请求会进入onRequest回调。在这个回调中,我们创建了一个Redis客户端,这个客户端会连接Redis服务器,并请求一条数据。得到数据后,我们调用end方法来响应压测的请求。当错误时,我们返回一个错误码为500的响应。

在开始压测前,我们需要安装Redis扩展:

pecl install redis

然后php.ini配置中开启redis扩展即可。

我们还需要在Redis服务器里面插入一条数据:

127.0.0.1:6379> SET greeter swoole
OK
127.0.0.1:6379> GET greeter
"swoole"
127.0.0.1:6379>

OK,我们现在进行压测:

~/codeDir/phpCode/swoole/server # php server.php
Concurrency Level:      256
Time taken for tests:   2.293 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    4361.00 [#/sec] (mean)
Time per request:       58.702 [ms] (mean)
Time per request:       0.229 [ms] (mean, across all concurrent requests)
Transfer rate:          715.48 [Kbytes/sec] received

我们发现,现在的QPS比较低,只有4361.00

因为,我们目前使用的Redis扩展是PHP官方的同步阻塞客户端,没有利用到协程(或者说异步的特性)。当进程去连接Redis服务器的时候,可能会阻塞整个进程,导致进程无法处理其他的连接,这样,这个HTTP Server处理请求的速度就不可能快。但是,这个压测结果会比FPM下好,因为Swoole是常驻进程的。

现在,我们来开启Swoole提供的RuntimeHook机制,也就是在运行时动态的将PHP同步阻塞的方法全部替换为异步非阻塞的协程调度的方法。我们只需要在server->set配置中加入一行即可:

'hook_flags' => SWOOLE_HOOK_ALL

此时,我们再来运行这个脚本:

Concurrency Level:      256
Time taken for tests:   1.643 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    6086.22 [#/sec] (mean)
Time per request:       42.062 [ms] (mean)
Time per request:       0.164 [ms] (mean, across all concurrent requests)
Transfer rate:          998.52 [Kbytes/sec] received

我们发现,此时的QPS还是有一定的提升的。(这里,视频中压测的时候,会夯住请求,导致QPS非常的低,但是我实际测试的时候没有发生这个情况,估计是和Redis服务器本身对连接个数的的配置有关)

但是,为了避免请求数量过多,导致创建连接个数过多的问题,我们可以使用一个Redis连接池来解决。(同步阻塞是没有Redis连接过多的问题的,因为一旦worker进程阻塞住了,那么后面的请求就不会继续执行了,也就不会创建新的Redis连接了。因此,在同步阻塞的模式下,Redis的连接数量最大是worker进程的个数)

现在,我们来实现一下Redis连接池:

class RedisQueue
{
    protected $pool;
    public function __construct()
    {
        $this->pool = new SplQueue;
    }
    public function get(): Redis
    {
        if ($this->pool->isEmpty()) {
            $redis = new \Redis();
            $redis->connect('127.0.0.1', 6379);
            return $redis;
        }
        return $this->pool->dequeue();
    }
    public function put(Redis $redis)
    {
        $this->pool->enqueue($redis);
    }
    public function close()
    {
        $this->pool = null;
    }
}

这里通过spl的队列实现的连接池。如果连接池中没有连接的时候,我们就新建一个连接,并且把创建的这个连接返回;如果连接池里面有连接,那么我们获取队列中前面的一个连接。当我们用完连接的时候,就可以调用put方法归还连接。这样,我们就可以在一定程度上复用Redis的连接,缓解Redis服务器的压力,以及频繁创建Redis连接的开销也会降低。

我们现在使用这个连接池队列:

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
$process = new Swoole\Process(function (Swoole\Process $process) {
    $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
    $server->set([
        'log_file' => '/dev/null',
        'log_level' => SWOOLE_LOG_INFO,
        'worker_num' => swoole_cpu_num() * 2,
        'hook_flags' => SWOOLE_HOOK_ALL,
    ]);
    $server->on('workerStart', function () use ($process, $server) {
        $server->pool = new RedisQueue;
        $process->write('1');
    });
    $server->on('request', function (Request $request, Response $response) use ($server) {
        try {
            $redis = $server->pool->get();
            // $redis = new Redis;
            // $redis->connect('127.0.0.1', 6379);
            $greeter = $redis->get('greeter');
            if (!$greeter) {
                throw new RedisException('get data failed');
            }
            $server->pool->put($redis);
            $response->end("<h2>{$greeter}</h2>");
        } catch (\Throwable $th) {
            $response->status(500);
            $response->end();
        }
    });
    $server->start();
});
if ($process->start()) {
    register_shutdown_function(function () use ($process) {
        $process::kill($process->pid);
        $process::wait();
    });
    $process->read(1);
    System('ab -c 256 -n 10000 -k http://127.0.0.1:9501/ 2>&1');
}
class RedisQueue
{
    protected $pool;
    public function __construct()
    {
        $this->pool = new SplQueue;
    }
    public function get(): Redis
    {
        if ($this->pool->isEmpty()) {
            $redis = new \Redis();
            $redis->connect('127.0.0.1', 6379);
            return $redis;
        }
        return $this->pool->dequeue();
    }
    public function put(Redis $redis)
    {
        $this->pool->enqueue($redis);
    }
    public function close()
    {
        $this->pool = null;
    }
}

我们在worker进程初始化的时候,创建了这个RedisQueue。然后在onRequest的阶段,从这个RedisQueue里面获取一个Redis连接。

现在,我们来进行压测:

Concurrency Level:      256
Time taken for tests:   1.188 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    8416.18 [#/sec] (mean)
Time per request:       30.418 [ms] (mean)
Time per request:       0.119 [ms] (mean, across all concurrent requests)
Transfer rate:          1380.78 [Kbytes/sec] received

QPS提升到了8416.18

但是,通过splqueue实现的连接池是有缺陷的,因为这个队列是可以无限长的。这样,当并发量特别大的时候,还是会有可能创建非常多的连接,因为连接池里面可能始终都是空的。

这个时候,我们可以使用Channel来实现连接池。代码如下:

class RedisPool
{
    protected $pool;
    public function __construct(int $size = 100)
    {
        $this->pool = new \Swoole\Coroutine\Channel($size);
        for ($i = 0; $i < $size; $i++)
        {
            while (true) {
                try {
                    $redis = new \Redis();
                    $redis->connect('127.0.0.1', 6379);
                    $this->put($redis);
                    break;
                } catch (\Throwable $th) {
                    usleep(1 * 1000);
                    continue;
                }
            }
        }
    }
    public function get(): \Redis
    {
        return $this->pool->pop();
    }
    public function put(\Redis $redis)
    {
        $this->pool->push($redis);
    }
    public function close()
    {
        $this->pool->close();
        $this->pool = null;
    }
}

可以看到,我们在这个构造方法中,将这个Channelsize设置为这个传入的参数。并且,创建size个连接。这些连接会在初始化连接池的时候就被创建,处于就就绪状态。这个有好处也有坏处,坏处就是在每个进程初始化的时候,就会占用一些连接,但是此时的进程并不会接收连接。好处就是提前创建好了Redis连接,这样服务器响应的延迟就会降低。

虽然,其他地方的代码其实和RedisQueue的实现一样。但是,底层是和RedisQueue大有不同的。因为当Channel里面没有Redis连接的时候,会让当前的协程挂起,让其他的协程继续被执行。等有协程把Redis连接还回到连接池里面的时候,这个被挂起的协程才会继续执行。这就是协程协作的原理。

现在,我们修改服务器的代码:

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
$process = new Swoole\Process(function (Swoole\Process $process) {
    $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
    $server->set([
        'log_file' => '/dev/null',
        'log_level' => SWOOLE_LOG_INFO,
        'worker_num' => swoole_cpu_num() * 2,
        'hook_flags' => SWOOLE_HOOK_ALL,
    ]);
    $server->on('workerStart', function () use ($process, $server) {
        $server->pool = new RedisPool(64);
        $process->write('1');
    });
    $server->on('request', function (Request $request, Response $response) use ($server) {
        try {
            $redis = $server->pool->get();
            // $redis = new Redis;
            // $redis->connect('127.0.0.1', 6379);
            $greeter = $redis->get('greeter');
            if (!$greeter) {
                throw new RedisException('get data failed');
            }
            $server->pool->put($redis);
            $response->end("<h2>{$greeter}</h2>");
        } catch (\Throwable $th) {
            $response->status(500);
            $response->end();
        }
    });
    $server->start();
});
if ($process->start()) {
    register_shutdown_function(function () use ($process) {
        $process::kill($process->pid);
        $process::wait();
    });
    $process->read(1);
    System('ab -c 256 -n 10000 -k http://127.0.0.1:9501/ 2>&1');
}
class RedisPool
{
    protected $pool;
    public function __construct(int $size = 100)
    {
        $this->pool = new \Swoole\Coroutine\Channel($size);
        for ($i = 0; $i < $size; $i++)
        {
            while (true) {
                try {
                    $redis = new \Redis();
                    $redis->connect('127.0.0.1', 6379);
                    $this->put($redis);
                    break;
                } catch (\Throwable $th) {
                    usleep(1 * 1000);
                    continue;
                }
            }
        }
    }
    public function get(): \Redis
    {
        return $this->pool->pop();
    }
    public function put(\Redis $redis)
    {
        $this->pool->push($redis);
    }
    public function close()
    {
        $this->pool->close();
        $this->pool = null;
    }
}

只需要修改workerStart里面的部分即可,其他地方不需要做修改。这样,每个进程最多只能创建64Redis连接。

我们继续压测:

Concurrency Level:      256
Time taken for tests:   0.817 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    12234.30 [#/sec] (mean)
Time per request:       20.925 [ms] (mean)
Time per request:       0.082 [ms] (mean, across all concurrent requests)
Transfer rate:          2007.19 [Kbytes/sec] received

以上就是PHP中怎么搭建一个HTTP服务,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注创新互联行业资讯频道。

当前文章:PHP中怎么搭建一个HTTP服务
转载来源:https://www.cdcxhl.com/article22/jipdcc.html

成都网站建设公司_创新互联,为您提供网站内链全网营销推广定制开发关键词优化面包屑导航网站建设

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联

商城网站建设