1 Redis简介
Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。
Redis 与其他 key – value 缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
2 Redis特性优势
- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
- 持久化 – 存在脑子里的知识可能忘记丢失,持久化存储到硬盘的数据才相对于最稳定。
- 丰富的数据类型 – Redis支持二进制的 Strings, Lists, Hashes, Sets 及 Sorted Sets 数据类型操作。
- 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性 – Redis同样也支持事务、流水线、发布/订阅、消息队列功能。
- 高可用以及分布式 – 主从配置简单,容易上手。可以提供基本的数据备份。在3.0以后的版本中,还提供了分布式的功能。
什么是原子性,什么是原子性操作?
举个例子:
A想要从自己的帐户中转1000块钱到B的帐户里。那个从A开始转帐,到转帐结束的这一个过程,称之为一个事务。在这个事务里,要做如下操作:
1. 从A的帐户中减去1000块钱。如果A的帐户原来有3000块钱,现在就变成2000块钱了。
2. 在B的帐户里加1000块钱。如果B的帐户如果原来有2000块钱,现在则变成3000块钱了。
如果在A的帐户已经减去了1000块钱的时候,忽然发生了意外,比如停电什么的,导致转帐事务意外终止了,而此时B的帐户里还没有增加1000块钱。那么,我们称这个操作失败了,要进行回滚。回滚就是回到事务开始之前的状态,也就是回到A的帐户还没减1000块的状态,B的帐户的原来的状态。此时A的帐户仍然有3000块,B的帐户仍然有2000块。
我们把这种要么一起成功(A帐户成功减少1000,同时B帐户成功增加1000),要么一起失败(A帐户回到原来状态,B帐户也回到原来状态)的操作叫原子性操作。
如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性。
2.1 Redis应用场景
1.缓存 – 键过期时间
缓存session会话;缓存用户信息,找不到再去Mysql查,查到然后回写到Redis;优惠券过期时间;
2.排行榜 – 列表 & 有序集合
热度排名排行榜
发布时间排行榜
3.计数器应用 – 天然支持计数器
帖子浏览数
视频播放次数
商品浏览数
4.社交网络 – 集合
踩/赞,粉丝,共同好友/喜好,推送
5.消息队列系统 – 发布订阅
配合ELK实现日志收集
3 Redis与其他key-value存储有什么不同?
- Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
4 Redis安装
下载并安装
wget http://download.redis.io/releases/redis-5.0.2.tar.gz
tar xzf redis-5.0.2.tar.gz
ln -s redis-5.0.2 redis
cd redis
make (解释:编译redis源码)
遇到问题【adlist.o】Error 127
由于Redis是C语言开发的,因此需要安装gcc编译器来编译代码,我们下载的Redis包里面是源代码,需要编译:
# 需要安装gcc
yum install gcc -y
然后再次make,又遇问题
请重新解压出Redis文件然后再次编译
安装成功之后,启动redis,并查看端口
# cd src
# ./redis-server &
# ss -lntp|grep 6379
LISTEN 0 128 *:6379 *:* users:(("redis-server",pid=8990,fd=7))
LISTEN 0 128 [::]:6379 [::]:* users:(("redis-server",pid=8990,fd=6))
进入redis
[root@mysql-65 redis-6.0.9]# ./redis-cli
127.0.0.1:6379>
编辑redis服务启动文件
#1、创建redis用户并授权
[root@centos7 ~]# useradd -M -s /sbin/nologin redis
#2、创建目录和redis配置文件
[root@mysql-65 6379]# mkdir -p /data/6379/
[root@centos7 ~]# chown -R redis:redis /data/6379/ #注意目录权限
[root@mysql-65 6379]# cp -a /usr/local/redis/redis.conf /data/6379/
#3、编辑redis服务启动文件
[root@mysql-65 6379]# cat /usr/lib/systemd/system/redis.service
[Unit]
Description=Redis
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/redis/src/redis-server /data/6379/redis.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/usr/local/redis/src/redis-cli shutdown
User=redis
Group=redis
PrivateTmp=true
[Install]
WantedBy=multi-user.target
#4、验证redis启动
[root@mysql-65 6379]# systemctl daemon-reload
[root@mysql-65 6379]# systemctl enable redis
[root@mysql-65 6379]# systemctl start redis
[root@mysql-65 6379]# ss -lntp|grep redis
LISTEN 0 511 192.168.1.65:6379 *:* users:(("redis-server",pid=17478,fd=7))
LISTEN 0 511 127.0.0.1:6379 *:* users:(("redis-server",pid=17478,fd=6))
4.1 Redis安装警告问题解决
1.WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128
backlog参数控制的是三次握手的时候server端收到client ack确认号之后的队列值,即全连接队列
vim /etc/sysctl.conf
net.core.somaxconn = 1024
然后执行sysctl -p
2.WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect
0:表示内核将检查是否有足够的可用内存供应用进程使用。如果有足够的可用内存,内存申请允许。否则,内存申请失败,并把错误返回给应用程序
1:表示内核允许分配所有的物理内存,而不管当前的内存状态如何
2:表示内核允许分配超过所有物理内存和交换空间总和的内存
vim /etc/sysctl.conf
vm.overcommit_memory = 1
然后执行sysctl -p
3.WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled (set to 'madvise' or 'never')
警告:你在内核中启用了透明大页面(THP)支持。这将在Redis中造成延迟和内存使用问题。要解决此问题,请以根用户身份运行命令“echo never> / sys / kernel / mm / transparent_hugepage / enabled”,并将其添加到你的/etc/rc.local中,以便在重启后保留设置。禁用THP后,必须重新启动Redis
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' >> /etc/rc.d/rc.local
chmod +x /etc/rc.d/rc.local
然后重启redis即可
5 Redis基础配置文件介绍
[root@mysql-65 redis]# cd /data/6379/
[root@mysql-65 6379]# egrep -v "^#|^$" /data/6379/redis.conf
bind 127.0.0.1 192.168.1.65
protected-mode yes
port 6379
tcp-backlog 511
timeout 300
tcp-keepalive 300
daemonize yes
supervised no
pidfile /data/6379/redis.pid
loglevel notice
logfile "/data/6379/redis.log"
databases 16
always-show-logo yes
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
rdb-del-sync-files no
dir /data/6379
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-diskless-load disabled
repl-disable-tcp-nodelay no
replica-priority 100
acllog-max-len 128
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
lazyfree-lazy-user-del no
oom-score-adj no
oom-score-adj-values 0 200 800
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
jemalloc-bg-thread yes
配置文件说明:
daemonize yes #Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程
protected-mode yes #redis3.2之后加入的新特性,在没有设置bind IP和密码的时候,redis只允许访问127.0.0.1:6379,可以远程连接,但当访问将提示警告信息并拒绝远程访问
port 6379 #指定Redis监听端口,默认端口为6379
tcp-backlog 511 #三次握手的时候server端收到client ack确认好之后的队列值,即全队列长度
tcp-keepalive 300 #tcp会话保持时间300s
supervised no #和OS相关参数,可设置通过upstart和systemd管理Redis守护进程,centos7后都使用systemd
logfile /data/6379/redis.log #日志文件位置
dir /data/6379 #指定本地数据库存放目录
dbfilename dump.rdb #指定本地数据库文件名,默认值为dump.rdb
pidfile /data/6379/redis.pid #当rdis以守护进程方式运行时,redis会生成redis.pid文件,可以通过pidfile指定
bind 192.168.1.65 127.0.0.1 #绑定主机地址,可以使本机IP也可以127.0.0.1
timeout 300 #当客户端闲置超时时间默认单位为秒,默认为0,表示永不超时
loglevel verbose #日志级别
databases 16 #设置数据库的数量,默认数据库为0-15,共16个库
always-show-logo yes #在启动redis时是否显示redis的logo
save 900 1 #持久化同步到数据文件的配置,表示在900秒内有一个键内容发生更改就会触发快照机制
stop-writes-on-bgsave-error yes #yes时因空间满等原因快照无法保存出错时,禁止redis写入操作,建议为no
rdbcompression yes #存储到本地数据文件是否压缩,默认为yes,redis采用LZF压缩,当数据量较小,为节省CPU可以考虑关闭。数据较大时,考虑磁盘空间以及I/O性能进行选择。
rdbchecksum yes #是否对备份文件开启RC64校验,默认是开启
dbfilename dump.rdb #指定本地数据库文件名,默认值为dump.rdb
dir /data/6379 #指定本地数据库存放目录
replica-serve-stale-data yes
#当从库同主库失去连接或者复制正在进行,从库有两种运行方式:
#1.设置为yes(默认设置),从库会继续响应客户端的读请求,此为建议值
#2.设置为no,除去指定的命令之外的任何请求都会返回一个错误"SYNC with master in progress"
replica-read-only yes #是否设置从库只读,建议值为yes,否则主库同步从库时可能会覆盖数据,造成数据丢失
repl-diskless-sync no #是否使用socket方式复制数据(无盘同步),新slave连接连接时候需要做数据的全量同步,redis server就要从内存dump出新的RDB文件,然后从master传到slave,有两种方式把RDB文件传输给客户端:
#1、基于硬盘(disk-backed):为no时,master创建一个新进程dump生成RDB磁盘文件,RDB完成之后由父进程(即主进程)将RDB文件发送给slaves,此为推荐值
#2、基于socket(diskless):master创建一个新进程直接dump RDB至slave的网络socket,不经过主进程和硬盘基于硬盘(为no),RDB文件创建后,一旦创建完毕,可以同时服务更多的slave,但是基于socket(为yes), 新slave连接到master之后得逐个同步数据。当磁盘I/O较慢且网络较快时,可用diskless(yes),否则使用磁盘(no)
repl-diskless-sync-delay 30 #diskless时复制的服务器等待的延迟时间,设置0为关闭,在延迟时间内到达的客户端,会一起通过diskless方式同步数据,但是一旦复制开始,master节点不会再接收新slave的复制请求,直到下一次同步开始才再接收新请求。即无法为延迟时间后到达的新副本提供服务,新副本将排队等待下一次RDB传输,因此服务器会等待一段时间才能让更多副本到达。推荐值:30-60
repl-ping-replica-period 10 #slave根据master指定的时间进行周期性的PING master 监测master状态
repl-timeout 60 #复制连接的超时时间,需要大于repl-ping-slave-period,否则会经常报超时
repl-disable-tcp-nodelay no #是否在slave套接字发送SYNC之后禁用 TCP_NODELAY,如果选择"yes",Redis将合并多个报文为一个大的报文,从而使用更少数量的包向slaves发送数据,但是将使数据传输到slave上有延迟,Linux内核的默认配置会达到40毫秒,如果 "no" ,数据传输到slave的延迟将会减少,但要使用更多的带宽
repl-backlog-size 512mb #复制缓冲区内存大小,当slave断开连接一段时间后,该缓冲区会累积复制副本数据,因此当slave 重新连接时,通常不需要完全重新同步,只需传递在副本中的断开连接后没有同步的部分数据即可。只有在至少有一个slave连接之后才分配此内存空间。
repl-backlog-ttl 3600 #多长时间内master没有slave连接,就清空backlog缓冲区
replica-priority 100 #当master不可用,Sentinel会根据slave的优先级选举一个master,此值最低的slave会当选master,而配置成0,永远不会被选举,一般多个slave都设为一样的值,让其自动选择
min-replicas-to-write 3 #至少有3个可连接的slave,mater才接受写操作,并同步到slave中
min-replicas-max-lag 10 #和上面至少3个slave的ping延迟不能超过10秒,否则master也将停止写操作
slaveof <masterip> <masterport> #主从配置时的选项,设置为本机为slav服务时,通过配置好的master地址以及端口,在slave启动时,它会自动从master进行数据同步
masterauth <master-password> #当master服务设置了密码保护时,slav服务连接master的密码,一般很少设置密码,特殊情况下使用
requirepass foobared #设置redis连接密码,如果配置了连接密码,客户端在连接redis时需要通过AUTH<password>命令提供密码,默认关闭
rename-command FLUSHALL SUPER_FLUSHALL #重命名一些高危命令,示例:rename-command FLUSHALL "" 禁用命令
maxclients 10000 #设置同一时间最大客户端连接数,当前默认查询为10000.
maxmemory 4294967296 #redis使用的最大内存,单位为bytes字节,0为不限制,建议设为物理内存一半,8G内存的计算方式8(G)*1024(MB)1024(KB)*1024(Kbyte),需要注意的是缓冲区是不计算在maxmemory内。
appendonly no #指定是否在每次更新操作后进行日志记录,看数据的重要性,如果一条记录都不允许丢失的话,建议开启。
appendfilename appendonly.aof #指定AOF文件名,默认为appendonly.aof
appendfsync everysec #指定更新日志条件,共有3个可选值:
#no:表示等操作系统进行数据缓存同步到磁盘(快)
#always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
#everysec:表示每秒同步一次(折中,默认值)
no-appendfsync-on-rewrite no #在aof rewrite期间,是否对aof新记录的append暂缓使用文件同步策略,主要考虑磁盘IO开支和请求阻塞时间。默认为no,表示"不暂缓",新的aof记录仍然会被立即同步,Linux的默认fsync策略是30秒,如果为yes 可能丢失30秒数据,但由于yes性能较好而且会避免出现阻塞因此比较推荐。
vm-enabled no #指定是否启用虚拟内存机制,默认值为0
vm-swap-file /tmp/redis.swap #虚拟内存文件路径
vm-max-memory 0 #设置开启虚拟内存后,redis将使用的最大物理内存的大小,默认为0
include /path/to/local.conf #指定包含其他的配置文件
auto-aof-rewrite-percentage 100 # 当Aof log增长超过指定百分比例时,重写AOF文件, 设置为0表示不自动重写Aof 日志,重写是为了使aof体积保持最小,但是还可以确保保存最完整的数据
auto-aof-rewrite-min-size 64mb #触发aof rewrite的最小文件大小
aof-load-truncated yes #是否加载由于其他原因导致的末尾异常的AOF文件(主进程被kill/断电等),建议yes
aof-use-rdb-preamble yes #redis4.0新增RDB-AOF混合持久化格式,在开启了这个功能之后,AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容,其中RDB格式的内容用于记录已有的数据,而AOF格式的内存则用于记录最近发生了变化的数据,这样Redis就可以同时兼有RDB持久化和AOF持久化的优点(既能够快速地生成重写文件,也能够在出现问题时,快速地载入数据)。
lua-time-limit 5000 #lua脚本的最大执行时间,单位为毫秒
cluster-enabled yes #是否开启集群模式,默认是单机模式
cluster-config-file nodes-6379.conf #由node节点自动生成的集群配置文件名称
cluster-node-timeout 15000 #集群中node节点连接超时时间,超过此时间,会踢出集群
cluster-replica-validity-factor 10 #在执行故障转移的时候可能有些节点和master断开一段时间数据比较旧,这些节点就不适用于选举为master,超过这个时间的就不会被进行故障转移,计算公式:(node-timeout * replica-validity-factor) + repl-ping-replica-period
cluster-migration-barrier 1 #集群迁移屏障,一个主节点至少拥有一个正常工作的从节点,即如果主节点的slave节点故障后会将多余的从节点分配到当前主节点成为其新的从节点。
cluster-require-full-coverage yes #集群请求槽位全部覆盖,如果一个主库宕机且没有备库就会出现集群槽位不全,那么yes情况下redis集群槽位验证不全就不再对外提供服务,而no则可以继续使用但是会出现查询数据查不到的情况(因为有数据丢失)。建议为no
cluster-replica-no-failover no #如果为yes,此选项阻止在主服务器发生故障时尝试对其主服务器进行故障转移。 但是,主服务器仍然可以执行手动强制故障转移,一般为no
#Slow log 是 Redis 用来记录超过指定执行时间的日志系统, 执行时间不包括与客户端交谈,发送回复等I/O操作,而是实际执行命令所需的时间(在该阶段线程被阻塞并且不能同时为其它请求提供服务)slow log 保存在内存里面,读写速度非常快,因此可放心地使用,不必担心因为开启 slow log 而影响 Redis 的速度
slowlog-log-slower-than 10000 #以微秒为单位的慢日志记录,为负数会禁用慢日志,为0会记录每个命令操作。
slowlog-max-len 128 #最多记录多少条慢日志的保存队列长度,达到此长度后,记录新命令会将最旧的命令从命令队列中删除,以此滚动删除
5.1 生产环境配置文件
#生产环境的redis.conf配置信息
bind 10.4.61.63 127.0.0.1
protected-mode yes
port 9005
tcp-backlog 511
timeout 0
tcp-keepalive 300
daemonize yes
supervised no
pidfile /var/run/redis_9005.pid
loglevel notice
logfile /var/log/redis_9005.log
databases 16
always-show-logo yes
save ""
stop-writes-on-bgsave-error no
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis/9005
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
maxmemory 4294967296
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
lua-time-limit 5000
cluster-enabled yes
cluster-config-file nodes-9005.conf
cluster-node-timeout 15000
cluster-require-full-coverage no
slowlog-log-slower-than 10000
slowlog-max-len 128
latency-monitor-threshold 0
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
rename-command FLUSHALL SUPER_FLUSHALL
rename-command FLUSHDB SUPER_FLUSHDB
配置好文件之后,我们重启redis:
[root@mysql-65 6379]# ss -lntp|grep redis
LISTEN 0 128 *:6379 *:* users:(("redis-server",pid=8990,fd=7))
LISTEN 0 128 [::]:6379 [::]:* users:(("redis-server",pid=8990,fd=6))
[root@mysql-65 ~]# systemctl restart redis
[root@mysql-65 ~]# systemctl status redis
● redis.service - Redis
Loaded: loaded (/usr/lib/systemd/system/redis.service; enabled; vendor preset: disabled)
Active: active (running) since 四 2020-12-10 11:18:29 CST; 4s ago
Process: 17555 ExecStop=/usr/local/redis/src/redis-cli shutdown (code=exited, status=0/SUCCESS)
Process: 17558 ExecStart=/usr/local/redis/src/redis-server /data/6379/redis.conf (code=exited, status=0/SUCCESS)
Main PID: 17559 (redis-server)
CGroup: /system.slice/redis.service
└─17559 /usr/local/redis/src/redis-server 127.0.0.1:6379
12月 10 11:18:29 mysql-65 systemd[1]: Stopped Redis.
12月 10 11:18:29 mysql-65 systemd[1]: Starting Redis...
12月 10 11:18:29 mysql-65 systemd[1]: Started Redis.
6 Redis安全配置
- redis没有用户概念,只能设置密码
- redis默认工作在保护模式下,不允许远程用户登录
所以为了可以实现远程访问,我们需要加入以下几个配置:
[root@mysql-65 redis]# cat /data/6379/redis.conf
bind 192.168.1.65 127.0.0.1 #绑定远程服务器IP地址
requirepass 123456 #设置密码
#配置好之后,重启我们的redis
[root@mysql-65 6379]# systemctl restart redis
--------------- 验证 ---------------
[root@mysql-65 redis]# ./src/redis-cli -h 192.168.1.65 -a 123456
192.168.1.65:6379> set name zhangsan
OK
192.168.1.65:6379> get name
"zhangsan"
192.168.1.65:6379>
7 Redis的单线程架构
Redis使用了单线程架构和I/O多路复用模型来实现高性能的内存数据库服务
7.1 单线程模型
现在开启三个redis-cli客户端同时执行命令
客户端1设置一个字符串键值对:
10.4.60.96:7001> set hello world
客户端2对counter做自增操作:
10.4.60.96:7001> incr counter
客户端3对counter做自增操作:
10.4.60.96:7001> incr counter
Redis客户端与服务端的模型如下图所示,每次客户端调用都经历了发送命令、执行命令、返回结果三个过程。
其中第二步是重点讨论的,因为Redis是单线程来处理命令的,所以一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。所以上面3个客户端命令的执行顺序是不确定的,如下图所示:
但是可以确定不会有两条命令被同时执行,如下图所示:
所以两条incr命令无论怎么执行最终结果都是2,不会产生并发问题,这就是Redis单线程的基本模型。
7.2 为什么单线程还能这么快
通常来说,单线程的处理能比要比多线程差,但是为什么Redis使用单线程模型会达到每秒万级别的处理能力呢?可以将其归结为三点:
- 纯内存访问,Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
- 非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间,如下图所示:
- 单线程避免了线程切换和竞态产生的消耗