php| 201809 技术小结

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 内容简介:- OpenSSL vs LibreSSL- swoole 4.1.0 添加 coroutine runtime 支持原生 redis/pdo/mysqli- php 实战 rabbitmq 任务队列: 多work + 协程- QPS 限制: 令牌桶算法 + php 实战

内容简介:

  • 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;
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
8月前
|
安全 数据库连接 PHP
PHP编程中的关键性技术探究
在当今信息化社会,PHP作为一种流行的服务器端脚本语言,已经被广泛应用于网站开发和动态网页生成等领域。本文将深入探讨PHP编程中的关键性技术,包括数据库连接、安全性防护、性能优化等方面,旨在帮助读者更好地理解和运用PHP语言。
|
8月前
|
设计模式 程序员 PHP
PHP程序员的技术成长之路
技术成长是每个PHP程序员不断追求的目标,而这一过程并非只是关于学习新的语言特性或框架,更多的是关乎思维方式和解决问题的能力。本文将探讨PHP程序员在技术成长之路上所面临的挑战,并提出一些建议,帮助他们不断提升自己的技术水平。
51 5
|
2月前
|
XML 前端开发 JavaScript
PHP与Ajax在Web开发中的交互技术。PHP作为服务器端脚本语言,处理数据和业务逻辑
本文深入探讨了PHP与Ajax在Web开发中的交互技术。PHP作为服务器端脚本语言,处理数据和业务逻辑;Ajax则通过异步请求实现页面无刷新更新。文中详细介绍了两者的工作原理、数据传输格式选择、具体实现方法及实际应用案例,如实时数据更新、表单验证与提交、动态加载内容等。同时,针对跨域问题、数据安全与性能优化提出了建议。总结指出,PHP与Ajax的结合能显著提升Web应用的效率和用户体验。
56 3
|
4月前
|
缓存 程序员 PHP
为什么说 Swoole 是 PHP 程序员技术水平的分水岭?
【9月更文挑战第8天】Swoole 被视为 PHP 程序员技术水平的分水岭,因为它要求程序员深入理解底层原理(如网络编程、异步和并发模型),具备性能优化能力(如高效服务器开发、数据库连接池管理),拥有架构设计能力(如微服务架构、项目复杂度管理),并具备持续学习和自我提升意识。熟练掌握 Swoole 的程序员在技术能力和综合素质方面更具优势。
|
4月前
|
缓存 NoSQL PHP
使用PHP-redis实现键空间通知监听key失效事件的技术与代码示例
通过上述方法,你可以有效地在PHP中使用Redis来监听键空间通知,特别是针对键失效事件。这可以帮助你更好地管理缓存策略,及时响应键的变化。
105 3
|
6月前
|
设计模式 安全 关系型数据库
PHP开发涉及一系列步骤和技术
【7月更文挑战第2天】PHP开发涉及一系列步骤和技术
143 57
|
5月前
|
机器学习/深度学习 人工智能 自然语言处理
PHP编程中的面向对象基础利用AI技术提升文本分类效率
【8月更文挑战第28天】在PHP的编程世界中,面向对象编程(OOP)是一块基石,它不仅塑造了代码的结构,也影响了开发者的思考方式。本文将深入探讨PHP中面向对象的基础概念,通过浅显易懂的语言和生动的比喻,带领初学者步入这个充满魅力的世界。我们将一起探索类与对象的秘密,理解构造函数和析构函数的重要性,以及继承和多态性的魔法。准备好了吗?让我们开始这段激动人心的旅程!
|
4月前
|
缓存 网络协议 程序员
为什么说 Swoole 是 PHP 程序员技术水平的分水岭?
【9月更文挑战第7天】Swoole 因其异步非阻塞编程模式、高性能服务器开发能力、性能优化工具及拓展技术视野等特点,被视为 PHP 程序员技术水平的分水岭。它要求程序员掌握异步编程、协程、网络协议等知识,并具备性能优化和系统管理能力,从而全面提升技术水平。
|
4月前
|
消息中间件 NoSQL Go
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【9月更文挑战第7天】在从 PHP 的 ThinkPHP 框架迁移到 Go 的 Gin 框架时,涉及 Redis 延时消息队列的技术实践主要包括:理解延时消息队列概念,其能在特定时间处理消息,适用于定时任务等场景;在 ThinkPHP 中使用 Redis 实现延时队列;在 Gin 中结合 Go 的 Redis 客户端库实现类似功能;Go 具有更高性能和简洁性,适合处理大量消息。迁移过程中需考虑业务需求及系统稳定性。
|
5月前
|
SQL 安全 PHP
探寻PHP的现代演进之路:从Web开发到框架创新——揭秘PHP语言如何引领技术潮流
【8月更文挑战第2天】探索PHP的现代演进:从Web开发到框架创新
64 1