美文网首页IT程序员
php| 201809 技术小结

php| 201809 技术小结

作者: daydaygo | 来源:发表于2018-09-09 17:31 被阅读296次

date: 2018-09-08 00:43:36
title: php| 201809 技术小结

内容简介:

  • OpenSSL vs LibreSSL
  • swoole 4.1.0 添加 coroutine runtime 支持原生 redis/pdo/mysqli
  • php 实战 rabbitmq 任务队列: 多work + 协程
  • QPS 限制: 令牌桶算法 + php 实战

OpenSSL vs LibreSSL

上一篇 php| 初探 rabbitmq 中, 使用的 composer package 版的 php rabbitmq client. rabbitmq 还支持扩展板的 php rabbitmq client: ext-amqp. 抱着 扩展(ext, C语言编写) 比 包(package, php编写) 性能高 的朴实想法, 于是决定试试 ext-amqp 扩展. 然后就遇到了问题:

# 安装 amqp, 报错
pecl install amqp
checking for amqp using pkg-config... configure: error: librabbitmq not found

apk search rabbitmq # 查询相似依赖包
apk add rabbitmq-c-dev # 安装, 报错
ERROR: unsatisfiable constraints:
  openssl-dev-1.0.2o-r1:
    conflicts: libressl-dev-2.6.5-r0[pc:libcrypto=1.0.2o] libressl-dev-2.6.5-r0[pc:libssl=1.0.2o]
               libressl-dev-2.6.5-r0[pc:openssl=1.0.2o]
    satisfies: world[openssl-dev]
  libressl-dev-2.6.5-r0:
    conflicts: openssl-dev-1.0.2o-r1[pc:libcrypto=2.6.5] openssl-dev-1.0.2o-r1[pc:libssl=2.6.5]
               openssl-dev-1.0.2o-r1[pc:openssl=2.6.5]
    satisfies: rabbitmq-c-dev-0.8.0-r3[libressl-dev]

ok, 问题出来了: 系统中已经安装的 OpenSSL, 和 LibreSSL 不兼容

不兼容的原因, GitHub上有答案:

openssl-dev and libressl-dev provide many files that have the same path, therefore the two packages cannot be installed at the same time.

image

看来江湖要起一番争斗了: 扔掉 OpenSSL,拥抱 LibreSSL——远离心脏出血与溺亡

当然为此也争扎了一番, 说一下我的看法:

  • 之前一直使用的 OpenSSL(apk add openssl-dev), LibreSSL 是第一次接触
  • 发行版已经将原有的 OpenSSL 替换为 LibreSSL 了, Alpine Linux 自 3.5.0 起 -> 我喜欢使用 Alpine Linux
  • 打算切一下试试, 探一探江湖的水有多深

swoole 4.1.0 添加 coroutine runtime 支持原生 redis/pdo/mysqli 协程化

这个新特性非常令人激动, 很早就想试了, 不bb, 放测试结果.

不太熟悉协程的小伙伴, 可以移步 swoole| swoole 协程初体验

  • 4.1.0版本之前

要支持协程 redis, 需要 swoole 编译支持:

RUN curl -O https://gitee.com/swoole/swoole/repository/archive/v4.1.0.zip && unzip v4.1.0.zip && \
    apk add linux-headers openssl-dev nghttp2-dev hiredis-dev && \
    cd swoole && \
    phpize && \
    ./configure --enable-openssl --enable-async-redis --enable-http2 && make && make install && \
    docker-php-ext-enable swoole && \
    rm -rf v4.1.0.zip swoole

需要安装 hiredis, 并在编译参数中添加 --enable-async-redis

代码使用:

// 多协程版, 真正使用到协程带来的 IO 阻塞时的调度
for ($i = 0; $i < $cnt; $i++) {
    go(function () {
        $redis = new co\Redis();
        $redis->connect('redis', 6379);
        $redis->auth('123');
        $redis->get('key');
    });
}
  • 4.1.0 版本

编译:

RUN pecl install swoole && docker-php-ext-enable swoole

PS: 如果需要修改 swoole 编译参数开启其他功能, 还是需要采用上面的方式进行调整

测试代码:

$cnt = 2000;

// 普通版
for ($i=0; $i<$cnt; $i++) {
    test_redis();
}

// 协程版
Swoole\Runtime::enableCoroutine();
for ($i=0; $i<$cnt; $i++) {
    go(function () {
        test_redis();
    });
}

function test_redis() {
    $redis = new Redis();
    $redis->connect("redis", 6379);
    $redis->set('test', 'daydaygo');
    $r = $redis->get('test');
    // var_dump($r);
}

耗时对比:

time php origi_redis_co.php

# 普通版
real    0m 1.35s
user    0m 0.08s
sys 0m 0.41s

# 协程版
real    0m 0.74s
user    0m 0.10s
sys 0m 0.37s

只要开启 Swoole\Runtime::enableCoroutine(), 就可以轻松切换到协程

需要注意的点:

  • 目前只有原生 redis/pdo/mysqli 支持, 其他服务还是需要使用配套的协程 client, 比如 http, rabbitmq
  • Swoole\Runtime::enableCoroutine() 需要配合 go() 一起使用, 否则会报错 WARNING yield: Socket::yield() must be called in the coroutine

php 实战 rabbitmq 任务队列: 多work + 协程

任务队列是个老话题了, 使用 redis 自建或者手写一个都不叫事儿

  • 基于 swoole 的分布式多进程任务系统: kcloze/swoole-jobs
  • 基于 程序员的瑞士军刀 redis LIST 数据类型自建

自建的好处是灵活, 不好的地方就是要实现一些基础功能:

  • 监控: 有哪些队列, 堆积了有多少, 任务处理的速度
  • 重试

如果还在自建任务队列, 推荐试试 rabbitmq 先.

官方任务队列的 demo -> Tutorial two: Work Queues:

php new_task.php "A very hard task which takes two seconds.."
php worker.php

简单修改 worker.php 代码结构, 注意看注释:


<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;

$callback = function (AMQPMessage $msg) {
    // todo: 需要处理的任务

    // 处理完后进行消息确认
    $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
};

$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->queue_declare('task_queue', false, true, false, false);
$channel->basic_qos(null, 1, null);
$channel->basic_consume('task_queue', '', false, false, false, false, $callback);

// 取一条进行测试
// if (count($channel->callbacks)) {
//     $channel->wait();
// }

// 循环直到处理完
while (count($channel->callbacks)) {
    $channel->wait();
}

$channel->close();
$connection->close();
  • 使用多进程加速

有了 Swoole\Process\Pool, 要实现多进程实在太轻松:

use Swoole\Process\Pool;

$pool = new Pool($workNum); // 需要开启的进程数
$pool->on('workerStart', function ($pool, $workerId) {
    // 上面的代码放进来即可
});
$pool->start();
  • 使用协程加速

swoole 也提供了 rabbitmq 的协程版 client, 项目地址 swoole/php-amqplib

使用 composer 引入:

{
    "name": "test",
    "description": "test",
    "minimum-stability": "dev", // 允许使用 dev 版的包
    "require": {
        "php-amqplib/php-amqplib": "dev-master" // 这里使用的 master 分支, 其他分支则改成 dev-branchname
    },
    "repositories": [
        {
            "type": "vcs", // 包的实际地址
            "url": "https://github.com/swoole/php-amqplib"
        }
    ]
}

对应的 worker 代码:

<?php

require "../vendor/autoload.php";

use PhpAmqpLib\Connection\AMQPSwooleConnection;
use PhpAmqpLib\Message\AMQPMessage;
use PhpAmqpLib\Channel\AMQPChannel;

$callback = function (AMQPMessage $msg) {
    // todo: 消息处理
    var_dump($msg->body);

    // 处理完后确认消息
    /** @var AMQPChannel $ch */
    $ch = $msg->delivery_info['channel'];
    $ch->basic_ack($msg->delivery_info['delivery_tag']);
};

// 多协程调度
for ($i=0; $i<$coNum; $i++) {
    go(function () use ($callback) {
        $connection = new AMQPSwooleConnection('rabbitmq', 5672, 'guest', 'guest');
        $channel = $connection->channel();
        $channel->queue_declare('task_queue', false, true, false, false);
        $channel->basic_consume('task_queue', '', false, false, false, false, $callback);
    
        // 循环直到处理完
        while (count($channel->callbacks)) {
            $channel->wait();
        }
    });
}

协程版 AMQP 通过重写 PhpAmqpLib\Connection\AMQPSwooleConnection 实现, 底层使用 Swoole\Coroutine\Client

QPS 限制: 令牌桶算法 + php 实战

遇到外部接口的 QPS 限制, 感谢 dreamer_link 提供的方案

/**
 * 令牌桶实现限流
 * @link https://www.jianshu.com/p/9f76dd2757c7
 * @param string $key 设置key
 * @param int $initNum 周期内访问次数
 * @param int $expire 周期, 单位秒
 * @return bool
 */
public static function qpsLimit($key, $initNum, $expire)
{
    $time  = time();
    $redis = Yii::$app->redis->conn;
    $redis->watch($key);
    $limitVal = $redis->hGetAll($key);
    if ($limitVal) {
        $newNum   = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($time - $limitVal['time'])));
        if ($newNum > 0) {
            $redisVal = ['num' => $newNum, 'time' => time()];
        } else {
            // 当前时刻令牌消耗完
            return false;
        }
    } else {
        $redisVal = ['num' => $initNum, 'time' => time()];
    }
    $redis->multi();
    $redis->hMSet($key, $redisVal);
    if (!$redis->exec()) {
        // 访问频次过多
        return false;
    }
    return true;
}

相关文章

  • php| 201809 技术小结

    date: 2018-09-08 00:43:36title: php| 201809 技术小结 内容简介: Op...

  • 张向东摄影课_白平衡与构图

    201809

  • PHP 小结

    & 引用,意味着两个变量都指向同一个数据。它将使两个变量共享一块内存,如果这个内存存储的数据变了,那么两个变量的值...

  • PHP小结

    php打印输出的时候是在杨坤: header里面是传的值 杨坤: preview,是接收到的值 杨坤: 注意你接收...

  • PHP小结

    一维数组去重 array_unique(explode(",",$delVal));//拆分并去重 随机ID $c...

  • php

    php 技术

  • PHP输入数据库中文乱码解决

    学习PHP技术

  • 201809…

    是几号给忘了 今天 真是奇怪啊 明明每天看手机都有日历的 突然又想写字了 那个记录我所有过去的扣扣号被封了 大概是...

  • 201809

    这是一幅关于绘制思维导图四步曲的作品,接下来的思维导图都是我在网上听课所绘制的思维导图。

  • PHP全栈学习笔记12

    php简介,php历史,php后端工程师职业前景,php技术方向,php后端工程师职业体系介绍。 php是世界上使...

网友评论

    本文标题:php| 201809 技术小结

    本文链接:https://www.haomeiwen.com/subject/vzrdgftx.html