Swoole Redis 连接池的实现

Easter79
• 阅读 540

第 85 篇文章

这是关于 Swoole 入门学习的第九篇文章:Swoole Redis 连接池的实现。

概述

收到读者反馈,“亮哥,文章能多点图片吗?就是将运行结果以图片的形式展示...”

我个人觉得这是比较懒、动手能力差的表现,恩... 要勤快些。

但谁让文章是写给你们看的那,我以后尽量文章写的图文并茂一点。

上篇文章 分享了 MySQL 连接池,这篇文章 咱们来分享下 Redis 连接池。

在上篇文章的基础上进行简单调整即可,将实例化 MySQL 的地方,修改成实例化 Redis 即可,还要注意一些方法的调整。

这篇文章仅仅只实现一个 Redis 连接池,篇幅就太少了,顺便将前几篇整合一下。

Demo 中大概包含这些点:

  • 实现 MySQL 连接池

  • 实现 MySQL CURD 方法的定义

  • 实现 Redis 连接池

  • 实现 Redis 方法的定义

  • 满足 HTTP、TCP、WebSocket 调用

  • 提供 Demo 供测试

  • 调整 目录结构

HTTP 调用:

  • 实现 读取 MySQL 中数据的 Demo

  • 实现 读取 Redis 中数据的 Demo

Swoole  Redis 连接池的实现

TCP 调用:

  • 实现 读取 MySQL 中数据的 Demo

  • 实现 读取 Redis 中数据的 Demo

Swoole  Redis 连接池的实现

WebSocket 调用:

  • 实现 每秒展示 API 调用量 Demo

Swoole  Redis 连接池的实现

目录结构

  1. ├─ client

  2. │ ├─ http

  3. │ ├── mysql.php //测试 MySQL 连接

  4. │ ├── redis.php //测试 Redis 连接

  5. │ ├─ tcp

  6. │ ├── mysql.php //测试 MySQL 连接

  7. │ ├── redis.php //测试 Redis 连接

  8. │ ├─ websocket

  9. │ ├── index.html //实现 API 调用量展示

  10. ├─ controller

  11. │ ├─ Order.php //实现 MySQL CURD

  12. │ ├─ Product.php //实现 Redis 调用

  13. │ ├─ Statistic.php //模拟 API 调用数据

  14. ├─ server

  15. │ ├─ config

  16. │ ├── config.php //默认配置

  17. │ ├── mysql.php //MySQL 配置

  18. │ ├── redis.php //Redis 配置

  19. │ ├─ core

  20. │ ├── Common.php //公共方法

  21. │ ├── Core.php //核心文件

  22. │ ├── HandlerException.php //异常处理

  23. │ ├── callback //回调处理

  24. │ ├── OnRequest.php

  25. │ ├── OnReceive.php

  26. │ ├── OnTask.php

  27. │ ├── ...

  28. │ ├── mysql

  29. │ ├── MysqlDB.php

  30. │ ├── MysqlPool.php

  31. │ ├── redis

  32. │ ├── RedisDB.php

  33. │ ├── RedisPool.php

  34. │ ├─ log -- 需要 读/写 权限

  35. │ ├── ...

  36. ├─ index.php //入口文件

代码

server/core/redis/RedisPool.php

  1. <?php

  2. if (!defined('SERVER_PATH')) exit("No Access");

  3. class RedisPool

  4. {

  5. private static $instance;

  6. private $pool;

  7. private $config;

  8. public static function getInstance($config = null)

  9. {

  10. if (empty(self::$instance)) {

  11. if (empty($config)) {

  12. throw new RuntimeException("Redis config empty");

  13. }

  14. self::$instance = new static($config);

  15. }

  16. return self::$instance;

  17. }

  18. public function __construct($config)

  19. {

  20. if (empty($this->pool)) {

  21. $this->config = $config;

  22. $this->pool = new chan($config['master']['pool_size']);

  23. for ($i = 0; $i < $config['master']['pool_size']; $i++) {

  24. go(function() use ($config) {

  25. $redis = new RedisDB();

  26. $res = $redis->connect($config);

  27. if ($res === false) {

  28. throw new RuntimeException("Failed to connect redis server");

  29. } else {

  30. $this->pool->push($redis);

  31. }

  32. });

  33. }

  34. }

  35. }

  36. public function get()

  37. {

  38. if ($this->pool->length() > 0) {

  39. $redis = $this->pool->pop($this->config['master']['pool_get_timeout']);

  40. if (false === $redis) {

  41. throw new RuntimeException("Pop redis timeout");

  42. }

  43. defer(function () use ($redis) { //释放

  44. $this->pool->push($redis);

  45. });

  46. return $redis;

  47. } else {

  48. throw new RuntimeException("Pool length <= 0");

  49. }

  50. }

  51. }

server/core/redis/RedisDB.php

  1. <?php

  2. if (!defined('SERVER_PATH')) exit("No Access");

  3. class RedisDB

  4. {

  5. private $master;

  6. private $slave;

  7. private $config;

  8. public function __call($name, $arguments)

  9. {

  10. // TODO 主库的操作

  11. $command_master = ['set', 'hset', 'sadd'];

  12. if (!in_array($name, $command_master)) {

  13. $db = $this->_get_usable_db('slave');

  14. } else {

  15. $db = $this->_get_usable_db('master');

  16. }

  17. $result = call_user_func_array([$db, $name], $arguments);

  18. return $result;

  19. }

  20. public function connect($config)

  21. {

  22. //主库

  23. $master = new Swoole\Coroutine\Redis();

  24. $res = $master->connect($config['master']['host'], $config['master']['port']);

  25. if ($res === false) {

  26. throw new RuntimeException($master->errCode, $master->errMsg);

  27. } else {

  28. $this->master = $master;

  29. }

  30. //从库

  31. $slave = new Swoole\Coroutine\Redis();

  32. $res = $slave->connect($config['slave']['host'], $config['slave']['port']);

  33. if ($res === false) {

  34. throw new RuntimeException($slave->errCode, $slave->errMsg);

  35. } else {

  36. $this->slave = $slave;

  37. }

  38. $this->config = $config;

  39. return $res;

  40. }

  41. private function _get_usable_db($type)

  42. {

  43. if ($type == 'master') {

  44. if (!$this->master->connected) {

  45. $master = new Swoole\Coroutine\Redis();

  46. $res = $master->connect($this->config['master']['host'], $this->config['master']['port']);

  47. if ($res === false) {

  48. throw new RuntimeException($master->errCode, $master->errMsg);

  49. } else {

  50. $this->master = $master;

  51. }

  52. }

  53. return $this->master;

  54. } elseif ($type == 'slave') {

  55. if (!$this->slave->connected) {

  56. $slave = new Swoole\Coroutine\Redis();

  57. $res = $slave->connect($this->config['slave']['host'], $this->config['slave']['port']);

  58. if ($res === false) {

  59. throw new RuntimeException($slave->errCode, $slave->errMsg);

  60. } else {

  61. $this->slave = $slave;

  62. }

  63. }

  64. return $this->slave;

  65. }

  66. }

  67. }

client/http/redis.php

  1. <?php

  2. $demo = [

  3. 'type' => 'SW',

  4. 'token' => 'Bb1R3YLipbkTp5p0',

  5. 'param' => [

  6. 'class' => 'Product',

  7. 'method' => 'set',

  8. 'param' => [

  9. 'key' => 'C4649',

  10. 'value' => '订单-C4649'

  11. ],

  12. ],

  13. ];

  14. $ch = curl_init();

  15. $options = [

  16. CURLOPT_URL => 'http://10.211.55.4:9509/',

  17. CURLOPT_POST => 1,

  18. CURLOPT_POSTFIELDS => json_encode($demo),

  19. ];

  20. curl_setopt_array($ch, $options);

  21. curl_exec($ch);

  22. curl_close($ch);

client/tpc/redis.php

  1. <?php

  2. class Client

  3. {

  4. private $client;

  5. public function __construct() {

  6. $this->client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);

  7. $this->client->on('Connect', [$this, 'onConnect']);

  8. $this->client->on('Receive', [$this, 'onReceive']);

  9. $this->client->on('Close', [$this, 'onClose']);

  10. $this->client->on('Error', [$this, 'onError']);

  11. }

  12. public function connect() {

  13. if(!$fp = $this->client->connect("0.0.0.0", 9510, 1)) {

  14. echo "Error: {$fp->errMsg}[{$fp->errCode}]".PHP_EOL;

  15. return;

  16. }

  17. }

  18. public function onConnect() {

  19. fwrite(STDOUT, "测试RPC (Y or N):");

  20. swoole_event_add(STDIN, function() {

  21. $msg = trim(fgets(STDIN));

  22. if ($msg == 'y') {

  23. $this->send();

  24. }

  25. fwrite(STDOUT, "测试RPC (Y or N):");

  26. });

  27. }

  28. public function onReceive($cli, $data) {

  29. echo '[Received]:'.$data;

  30. }

  31. public function send() {

  32. $demo = [

  33. 'type' => 'SW',

  34. 'token' => 'Bb1R3YLipbkTp5p0',

  35. 'param' => [

  36. 'class' => 'Product',

  37. 'method' => 'get',

  38. 'param' => [

  39. 'code' => 'C4649'

  40. ],

  41. ],

  42. ];

  43. $this->client->send(json_encode($demo));

  44. }

  45. public function onClose() {

  46. echo "Client close connection".PHP_EOL;

  47. }

  48. public function onError() {

  49. }

  50. }

  51. $client = new Client();

  52. $client->connect();

client/websocket/index.html

  1. <!DOCTYPE html>

  2. <html>

  3. <head>

  4. <meta charset="utf-8">

  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">

  6. <meta name="viewport" content="width=device-width, initial-scale=1">

  7. <meta name="description" content="">

  8. <meta name="keywords" content="">

  9. <title>Demo</title>

  10. <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>

  11. <script src="http://echarts.baidu.com/gallery/vendors/echarts/echarts.min.js"></script>

  12. </head>

  13. <body>

  14. <!-- 为ECharts准备一个具备大小(宽高)的Dom -->

  15. <div id="main" style="width: 900px;height:400px;"></div>

  16. <script type="text/javascript">

  17. if ("WebSocket" in window) {

  18. // 基于准备好的dom,初始化echarts实例

  19. var myChart = echarts.init(document.getElementById('main'));

  20. var wsServer = 'ws://10.211.55.4:9509';

  21. var ws = new WebSocket(wsServer);

  22. ws.onopen = function (evt) {

  23. if (ws.readyState == 1) {

  24. console.log('WebSocket 连接成功...');

  25. } else {

  26. console.log('WebSocket 连接失败...');

  27. }

  28. if (ws.readyState == 1) {

  29. ws.send('开始请求...');

  30. } else {

  31. alert('WebSocket 连接失败');

  32. }

  33. };

  34. ws.onmessage = function (evt) {

  35. console.log('Retrieved data from server: ' + evt.data);

  36. var evt_data = jQuery.parseJSON(evt.data);

  37. myChart.setOption({

  38. xAxis: {

  39. data : evt_data.time

  40. },

  41. series: [{

  42. data: evt_data.value

  43. }]

  44. });

  45. };

  46. ws.onerror = function (evt) {

  47. alert('WebSocket 发生错误');

  48. console.log(evt);

  49. };

  50. ws.onclose = function() {

  51. alert('WebSocket 连接关闭');

  52. console.log('WebSocket 连接关闭...');

  53. };

  54. // 指定图表的配置项和数据

  55. $.ajax({

  56. url : 'http://10.211.55.4:9509/', // 请求url

  57. type : "post", // 提交方式

  58. dataType : "json", // 数据类型

  59. data : {

  60. 'type' : 'SW',

  61. 'token' : 'Bb1R3YLipbkTp5p0',

  62. 'param' : {

  63. 'class' : 'Statistic',

  64. 'method' : 'init'

  65. }

  66. },

  67. beforeSend:function() {

  68. },

  69. success : function(rs) {

  70. if (rs.code != 1) {

  71. alert('获取数据失败');

  72. } else {

  73. var option = {

  74. title: {

  75. text: 'API 调用量',

  76. x:'center'

  77. },

  78. tooltip: {

  79. trigger: 'axis',

  80. axisPointer: {

  81. animation: false

  82. }

  83. },

  84. xAxis: {

  85. type : 'category',

  86. data : rs.data.time

  87. },

  88. yAxis: {

  89. type: 'value',

  90. boundaryGap: [0, '100%'],

  91. name: '使用量',

  92. splitLine: {

  93. show: false

  94. }

  95. },

  96. series: [{

  97. name: '使用量',

  98. type: 'line',

  99. showSymbol: false,

  100. hoverAnimation: false,

  101. data: rs.data.value

  102. }]

  103. };

  104. // 使用刚指定的配置项和数据显示图表。

  105. if (option && typeof option === "object") {

  106. myChart.setOption(option, true);

  107. }

  108. }

  109. },

  110. error : function(){

  111. alert('服务器请求异常');

  112. }

  113. });

  114. } else {

  115. alert("您的浏览器不支持 WebSocket!");

  116. }

  117. </script>

  118. </body>

  119. </html>

还涉及到,OnMessage.php、OnTask.php 、OnWorkerStart.php 等,就不贴代码了。

运行

小框架的启动/关闭/热加载,看看这篇文章: 第六篇:Swoole 整合成一个小框架

里面 Demo 在 client 文件夹下。

http 目录下的文件,放到自己虚拟目录下,用浏览器访问。

tcp 目录下的文件,在 CLI 下运行。

websocket 目录下的文件,直接点击在浏览器访问。

扩展

官方协程 Redis 客户端手册:

https://wiki.swoole.com/wiki/page/589.html

大家可以尝试使用官方提供的其他方法。

小结

Demo 代码仅供参考,里面有很多不严谨的地方,根据自己的需要进行修改 ...

上面的 Demo 需要源码的,加我微信。(菜单-> 加我微信-> 扫我)

推荐阅读

本文欢迎转发,转发请注明作者和出处,谢谢!Swoole  Redis 连接池的实现

本文分享自微信公众号 - 新亮笔记(XinLiangTalk)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k