redis集群搭建以及Yii1.1连接redis集群

傷城~ 2022-06-11 09:59 401阅读 0赞

微笑微笑微笑本来博客搭建在自己的服务器上,不知道多久没有维护和更新后,再一看域名都需要重新备案了。介于本人的“懒惰性”,还是迁过来不浪费资源了。

最近搭建一个业务,号称每秒响应需要1W次请求,然后LZ各种折腾php、mysql和nginx配置。最后放弃mysql使用redis单机版,最后的最后使用redis集群。

1、生产环境

ubuntu16.04

redis-3.0.7

内网机器IP:192.168.140.180和192.168.140.181

机器CPU:4核

2、搭建

LZ在搭建集群前是使用的redis单机版,而单机版的启动直接使用redis.server /etc/redis/redis.conf的,也就是说完全没有理解redis的单核运行原理 【手动滑稽,LZ是个菜】,还是记录一下redis的单线程:

redis是用”单线程-多路复用io模型”来实现高性能的内存数据服务的,这种机制避免了使用锁,但是同时这种机制在进行sunion之类的比较耗时的命令时会使redis的并发下降。因为是单一线程,所以同一时刻只有一个操作在进行,所以,耗时的命令会导致并发的下降,不只是读并发,写并发也会下降。而单一线程也只能用到一个cpu核心。

CPU 是一个重要的影响因素,由于是单线程模型,Redis 更喜欢大缓存快速 CPU, 而不是多核。在多核 CPU 服务器上面,Redis 的性能还依赖 NUMA 配置和 处理器绑定位置。 最明显的影响是 redis-benchmark 会随机使用 CPU 内核。
然后LZ的业务负载机器上面还跑着redis的实例,最后造成了这种情况:
20170804165139386

简直不要太开心,8核机器redis用了50%的CPU??? WTF???当时LZ的内心是崩溃的。所以查找了相当的redis集群资料开始搭建过程。

2.1.1 下载和安装redis源码

  1. cd /data/bao
  2. wget "http://download.redis.io/releases/redis-3.0.7.tar.gz"
  3. tar -zxvf redis-3.0.7.tar.gz 

然后进入到安装目录

  1. cd redis-3.0.7
  2. make test

ubuntu 下会报错:
You need tcl 8.5 or newer in order to run the Redis test

然后我们需要一下操作:

  1. wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz
  2. sudo tar xzvf tcl8.6.1-src.tar.gz -C /usr/local/
  3. cd /usr/local/tcl8.6.1/unix/
  4. sudo ./configure
  5. sudo make
  6. sudo make install

再返回进入到redis目录下

  1. cd /data/bao/redis-3.0.7 && make && make install

OK,搭建了六套相关环境暂时还没有出现过其他问题.现在源码安装好了redis,redis的启动和cli是和系统同样的,也就是

  1. cd /data/bao/redis-3.0.7/src

下有 redis-server 和 redis-cli 文件。./redis-cli 就可以使用连接了。

2.1.2 搭建redis集群
OK,两台机器做同样的命令:
用端口为名称建立redis集群的配置,LZ搭建4主4从,所以启用四个端口

  1. mkdir -p /data/redis/7000
  2. mkdir -p /data/redis/7001
  3. mkdir -p /data/redis/7002
  4. mkdir -p /data/redis/7003

将编译好的redis-server移过来,只是为了启动方便

  1. cp /data/bao/redis-3.0.7/src/redis-server /data/redis

编写相对简答的redis.conf文件

  1. vi /data/redis/redis.con
  2. port 7000 #运行端口,和你将要放入的文件夹名称对应
  3. cluster-enabled yes
  4. cluster-config-file nodes.conf #节点配置信息
  5. cluster-node-timeout 5000 #超时
  6. appendonly no
  7. daemonize yes #后台运行

储存后将这个文件复制到各个以端口命名的文件夹内,并将port修改为文件夹名称存储.

完成之后启用redis-cluster,LZ比较懒,写了个脚本启动。

  1. cd /data/redis/7000/ && taskset -c 0 /data/redis/redis-server /data/redis/7000/redis.conf
  2. cd /data/redis/7001/ && taskset -c 1 /data/redis/redis-server /data/redis/7001/redis.conf
  3. cd /data/redis/7002/ && taskset -c 2 /data/redis/redis-server /data/redis/7002/redis.conf
  4. cd /data/redis/7003/ && taskset -c 3 /data/redis/redis-server /data/redis/7003/redis.conf

这里的taskset -c 0 是指定redis运行在某个cpu上,cpu的个数从0开始往上计算,如LZ的机器是4核,所以开了4个端口使用4个CPU来运行cluster。这种方式比较极限,机器上也不敢运行太多其它东西了。

2.1.3 使用ruby管理redis集群
安装ruby redis插件,听说编写redis的人本身是写ruby的,所以又用ruby编写了一套管理脚本,保证redis的代码清洁,也为后来埋下了坑-。-

  1. apt update
  2. apt install ruby
  3. gem install redis
  4. cd /data/bao/redis-3.0.7/src
  5. redis-trib.rb create --replicas 1 192.168.140.180:7000 192.168.140.180:7001 192.168.140.180:7002 192.168.140.180:7003 192.168.140.180:7000 192.168.140.181:7000 192.168.140.181:7001 192.168.140.181:7002 192.168.140.181:7003

redis-trib.rb 针对集群的运用:
1、create:创建集群
2、check:检查集群
3、info:查看集群信息
4、fix:修复集群
5、reshard:在线迁移slot
6、rebalance:平衡集群节点slot数量
7、add-node:将新节点加入集群
8、del-node:从集群中删除节点
9、set-timeout:设置集群节点间心跳连接的超时时间
10、call:在集群全部节点上执行命令
11、import:将外部redis数据导入集群

create —replicas 第一个参数1 是指每个master节点将有1个子节点

OK,运行完毕不出意外的话全部都是success,有错误的话问问度娘, ubuntu16下LZ暂时没有出现很难解的错误。

3.1.1 链接redis集群并测试
进入集群池命令:

  1. redis-cli -p 7000 -c

具体set/get的测试就不列举了,蛋疼-。- 但在get的时候或许你会发现下面情况:(error) MOVED 1409 192.168.140.180:7000 ,这个也不是错误,是redis的集群机制。redis的各个master之间是互不干涉的,只是在存储的时候存储池给每个master分配了一些槽和数据。而你在某个节点get命令的时候,redis会先计算这个值存在的哈希,计算出他的存储节点,若不在这台机器上则报上方信息并告知你正确的节点。这样redis就有了两次I/O开销。额,还没到这一层面还是安静的看大神分析吧。

redis cluster浅析和Bada对比 原文

20170804180815849

最后LZ测试运行后的结果,跑业务的时候最高占用也仅在20%,和之前可怕的单核形成强烈的对比。。。

4.1.1 yii1.1 连接集群

楼主使用的是yiiredis拓展,github地址:https://github.com/phpnode/YiiRedis

然而,这个拓展没有cluster的链接【微笑】

没办法,山寨的楼主只能用phpredis来写个简单的拓展放里边。。。

  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: land.zhang
  5. * Date: 2017/8/3
  6. * Time: 16:55
  7. */
  8. class RedisClusterConnection extends CApplicationComponent
  9. {
  10. /**
  11. * The redis client
  12. * @var RedisCluster
  13. */
  14. protected $_client;
  15. /**
  16. * The redis unix socket location
  17. * @var unixSocket
  18. */
  19. public $unixSocket=null;
  20. /**
  21. * Redis default prefix
  22. * @var string
  23. */
  24. public $prefix = "Yii.redis.";
  25. /**
  26. * The redis server password
  27. * @var password
  28. */
  29. public $password=null;
  30. /**
  31. * The redis socket timeout, defaults to 0 (unlimited)
  32. * @var timeout
  33. */
  34. public $timeout=0;
  35. /**
  36. * The redis cluster
  37. * @var array
  38. */
  39. public $clusterConfig=[];
  40. /**
  41. * Sets the redis client to use with this connection
  42. * @param RedisCluster $client the redis client instance
  43. */
  44. public function setClient(RedisCluster $client)
  45. {
  46. $this->_client = $client;
  47. }
  48. /**
  49. * Gets the redis client
  50. * @return RedisCluster the redis client
  51. */
  52. public function getClient($reconnect = false)
  53. {
  54. if ($this->_client === null || $reconnect) {
  55. if(empty($this->clusterConfig)){
  56. throw new CException('Redis Cluster Config Error');
  57. }
  58. $clusterList = [];
  59. foreach ($this->clusterConfig as $item){
  60. $clusterList[] = $item['host'] . ':' . $item['port'];
  61. }
  62. if(empty($clusterList)){
  63. throw new CException('Redis Cluster Config Error');
  64. }
  65. $this->_client = new RedisCluster(null,$clusterList);
  66. // $this->_client->connect($this->hostname, $this->port, $this->timeout);
  67. //phpredis not support password yet
  68. // if (isset($this->password)) {
  69. // if ($this->_client->auth($this->password) === false) {
  70. // throw new CException('Redis authentication failed!');
  71. // }
  72. // }
  73. // $this->_client->setOption(Redis::OPT_PREFIX, $this->prefix);
  74. // $this->_client->select($this->database);
  75. }
  76. return $this->_client;
  77. }
  78. /**
  79. * Returns a property value based on its name.
  80. * Do not call this method. This is a PHP magic method that we override
  81. * to allow using the following syntax to read a property
  82. * <pre>
  83. * $value=$component->propertyName;
  84. * </pre>
  85. * @param string $name the property name
  86. * @return mixed the property value
  87. * @throws CException if the property is not defined
  88. * @see __set
  89. */
  90. public function __get($name) {
  91. $getter='get'.$name;
  92. if (property_exists($this->getClient(),$name)) {
  93. return $this->getClient()->{$name};
  94. }
  95. elseif(method_exists($this->getClient(),$getter)) {
  96. return $this->$getter();
  97. }
  98. return parent::__get($name);
  99. }
  100. /**
  101. * Sets value of a component property.
  102. * Do not call this method. This is a PHP magic method that we override
  103. * to allow using the following syntax to set a property
  104. * <pre>
  105. * $this->propertyName=$value;
  106. * </pre>
  107. * @param string $name the property name
  108. * @param mixed $value the property value
  109. * @return mixed
  110. * @throws CException if the property is not defined or the property is read only.
  111. * @see __get
  112. */
  113. public function __set($name,$value)
  114. {
  115. $setter='set'.$name;
  116. if (property_exists($this->getClient(),$name)) {
  117. return $this->getClient()->{$name} = $value;
  118. }
  119. elseif(method_exists($this->getClient(),$setter)) {
  120. return $this->getClient()->{$setter}($value);
  121. }
  122. return parent::__set($name,$value);
  123. }
  124. /**
  125. * Checks if a property value is null.
  126. * Do not call this method. This is a PHP magic method that we override
  127. * to allow using isset() to detect if a component property is set or not.
  128. * @param string $name the property name
  129. * @return boolean
  130. */
  131. public function __isset($name)
  132. {
  133. $getter='get'.$name;
  134. if (property_exists($this->getClient(),$name)) {
  135. return true;
  136. }
  137. elseif (method_exists($this->getClient(),$getter)) {
  138. return true;
  139. }
  140. return parent::__isset($name);
  141. }
  142. /**
  143. * Sets a component property to be null.
  144. * Do not call this method. This is a PHP magic method that we override
  145. * to allow using unset() to set a component property to be null.
  146. * @param string $name the property name or the event name
  147. * @throws CException if the property is read only.
  148. * @return mixed
  149. */
  150. public function __unset($name)
  151. {
  152. $setter='set'.$name;
  153. if (property_exists($this->getClient(),$name)) {
  154. $this->getClient()->{$name} = null;
  155. }
  156. elseif(method_exists($this,$setter)) {
  157. $this->$setter(null);
  158. }
  159. else {
  160. parent::__unset($name);
  161. }
  162. }
  163. /**
  164. * Calls a method on the redis client with the given name.
  165. * Do not call this method. This is a PHP magic method that we override to
  166. * allow a facade in front of the redis object.
  167. * @param string $name the name of the method to call
  168. * @param array $parameters the parameters to pass to the method
  169. * @return mixed the response from the redis client
  170. */
  171. public function __call($name, $parameters) {
  172. return call_user_func_array(array($this->getClient(),$name),$parameters);
  173. }
  174. }

然后在配置里边加上如下代码

  1. 'clusterConfig' => [
  2. [
  3. 'host' => '192.168.140.180',
  4. 'port' => 7000,
  5. ],
  6. [
  7. 'host' => '192.168.140.180',
  8. 'port' => 7001,
  9. ],
  10. [
  11. 'host' => '192.168.140.181',
  12. 'port' => 7000,
  13. ],
  14. [
  15. 'host' => '192.168.140.181',
  16. 'port' => 7001,
  17. ],
  18. ],

嗯,然后你就能在代码里边欢快的set和get: Yii:app->clusterConfig->get() 。

由于是基于phpredis拓展的,所以需要在ubuntu上为php装上拓展别忘了亲。。。拓展的安装在楼主的另外一篇文章 搭建nginx+php+mysql脚本。

发表评论

表情:
评论列表 (有 0 条评论,401人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Redis介绍以及

    [主从:][Link 1] > 通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重启会从硬盘上加载

    相关 Redis--

    集群概述 redis是一个开源的key value存储系统,受到了广大互联网公司的青睐。redis3.0版本之前只支持单例模式,在3.0版本及以后才支持集群,我这里