编程知识 编程知识CODING KNOWLEDGE
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

redis

redis

企业缓存产品介绍(key-value)

# Memcached
优点:高性能读写、支持客户端式分布式集群、一致性hash、多核结构、多线程读写性能高
缺点:单一数据类型、无持久化、节点故障可能出现缓存穿透、分布式需要客户端实现、跨机房数据同步困难、架构扩容复杂度高# Redis:
优点:高性能读写、多数据类型支持、数据持久化、高可用架构、支持自定义虚拟内存、支持分布式分片集群、单线程读写性能极高  
缺点:多线程读写较Memcached慢
应用厂家:新浪、京东、直播类平台、网页游戏# Tair:
优点:高性能读写、支持三种存储引擎(ddb、rdb、ldb)、支持高可用、支持分布式分片集群、支撑了几乎所有淘宝业务的缓存
缺点:单机情况下,读写性能较其他两种产品较慢# memcache与redis在读写性能的对比
memcached适合:多用户(高并发)访问,每个用户少量的rw
redis适合:少用户(低并发)访问,每个用户大量rw

# redis使用场景介绍
Memcached:多核的缓存服务,更加适合于多用户并发访问次数较少的应用场景
Redis:单核的缓存服务,单节点情况下,更加适合于少量用户,多次访问的应用场景。
Redis一般是单机多实例架构,配合redis集群出现。

Redis功能介绍

数据类型丰富 (string 、hash、list、set、sortset、stream)
支持持久化 (rdb、AOF)
多种内存分配及回收策略(LRU、lFU)
支持弱事务
消息队列、消息订阅
支持高可用
支持分布式分片集群
Redis 支持丰富API

redis安装部署

# 下载redis6.0并解压
wget http://download.redis.io/releases/redis-6.0.6.tar.gz
[root@web02 ~]# mkdir /data
[root@web02 ~]# tar xzf redis-6.0.6.tar.gz -C /data/
[root@web02 ~]# mv /data/redis-6.0.6 /data/redis# 安装依赖(centos7在安装6.0版本前,需要更新gcc编译环境)    
[root@web02 ~]# yum install -y gcc gcc-c++ jemalloc centos-release-scl devtoolset-9-gcc \
devtoolset-9-gcc-c++ devtoolset-9-binutils \
scl enable devtoolset-9 bash# 安装并启动redis
[root@web02 ~]# cd /data/redis
[root@web02 /data/redis]# make
[root@web02 /data/redis]# src/redis-server &# 配置环境变量
[root@web02 ~]# tail -n 1 /etc/profile
export PATH=/root/redis/src:$PATH
[root@web02 ~]# source /etc/profile

 

Redis基本管理操作

[root@web02 ~]# mkdir -p /data/6379
[root@web02 ~]# vim /data/6379/redis.conf 
[root@web02 ~]# cat /data/6379/redis.conf 
daemonize yes  # 以后台守护进程的方式运行(是否后台运行)
port 6379  # 监听端口
logfile /data/6379/redis.log  # 日志输出文件
dir /data/6379  # 持久化文件存储位置(RDB文件、AOF文件都写在这里)
dbfilename dump.rdb  # RDB快照文件名
[root@web02 ~]# redis-cli shutdown
[root@web02 ~]# redis-server /data/6379/redis.conf
[root@web02 ~]# netstat -lntup |grep redis

 

redis安全配置

redis默认开启了保护模式,只允许本地回环地址登录并访问数据库
vim /data//redis.conf
bind 10.0.0.8 127.0.0.1 # 只在这两张网卡上监听(Redis 在哪些本地网卡地址上监听 TCP 端口) requirepass 123456 # 客户端连接密码

 

# 方法一:
[root@web02 ~]# redis-cli -a 123456
127.0.0.1:6379>

[root@web02 ~]# redis-cli -a 123456 -h 10.0.0.8
10.0.0.8:6379>

# 方法二:
[root@web02 ~]# redis-cli
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> set a b

 

在线查看和修改配置 

CONFIG GET *
CONFIG GET requirepass
CONFIG GET r*
CONFIG SET requirepass 123

 

redis持久化

save:在主线程里同步生成 RDB 快照,期间阻塞所有客户端请求,生产环境禁用。

bgsave:fork() 出子进程在后台生成 RDB,父进程继续对外服务,只在 fork 那一刻有短暂阻塞,是生产的正确姿势。

# 介绍
将内存数据保存到磁盘。redis默认没有开启持久化功能,需要配置。
redis可以支持两种持久化功能:RDB、AOF。
# 区别
redis 持久化方式有哪些?有什么区别?
rdb:基于快照的持久化,速度更快,一般用作备份,主从复制也是依赖于rdb持久化功能
aof:以追加的方式记录redis操作日志的文件。可以最大程度的保证redis数据安全,类似mysql的binlog# RDB 持久化
vim /data/6379/redis.conf
dir /data/6379
dbfilename dump.rdb
save 900 1
save 300 10
save 60 10000
配置分别表示:(三条同时生效,任一满足就触发)
900秒(15分钟)内有1个更改
300秒(5分钟)内有10个更改
60秒内有10000个更改
# AOF 持久化(append-only log file)
AOF持久化配置
appendonly yes  # 开启AOF(默认为no)
appendfsync always  # 每个命令都fsync到磁盘
appendfsync everysec  # 后台线程每秒fsync一次到磁盘
appendfsync no  # 只写入内核缓冲区,由操作系统决定何时刷盘(Linux通常30秒)
vim /data/6379/redis.conf
appendonly yes
appendfsync everysec

 

Redis数据类型

string:字符类型
Hash:字典类型
List:列表
Set:集合
Sorted set:有序集合
STREAM

 

127.0.0.1:6379> set a 1
127.0.0.1:6379> set b 2
127.0.0.1:6379> keys *
1) "a"
2) "b"
127.0.0.1:6379> type a
string
127.0.0.1:6379> expire a 60
(integer) 1
127.0.0.1:6379> ttl a
(integer) 55
127.0.0.1:6379> persist a
(integer) 1
127.0.0.1:6379> ttl a
(integer) -1
127.0.0.1:6379> del b
(integer) 1
127.0.0.1:6379> exists b
(integer) 0
127.0.0.1:6379> rename a aaa
OK
127.0.0.1:6379> keys *
1) "aaa"

 

strings

应用场景:微博数,粉丝数,礼物

127.0.0.1:6379> mset id 101 name xiaojiang age 18 gender m
OK
127.0.0.1:6379> set aaa 111
OK
127.0.0.1:6379> keys *
1) "gender"
2) "name"
3) "age"
4) "id"
5) "aaa"

 

计数器

127.0.0.1:6379> set num 0
OK
127.0.0.1:6379> incr num  # 每点一次关注,都执行一下命令一次 
(integer) 1
127.0.0.1:6379> get num  # 显示粉丝数量
"1"# 暗箱操作
127.0.0.1:6379> incrby num 10000
(integer) 10001
127.0.0.1:6379> get num 
"10001"
127.0.0.1:6379> decrby num 5000
(integer) 5001
127.0.0.1:6379> get num
"5001"

 

增:
set mykey "test"
为键设置新值,并覆盖原有值
getset mycounter 0
setex mykey 10 "hello"
setnx mykey "hello"
设置值,取值同时进行
设置指定 Key 的过期时间为10秒,在存活时间可以获取value
若该键不存在,则为键设置新值mset key3 "zyx" key4 "xyz"
批量设置键
删除已有键
删:
del mykey
改:
append mykey "hello"
若该键并不存在,返回当前 Value 的长度
该键已经存在,返回追加后 Value的长度
值增加1,若该key不存在,创建key,初始值设为0,增加后结果为1
值减少5
incr mykey
decrby mykey 5
setrange mykey 20 dd
把第21和22个字节,替换为dd, 超过value长度,自动补0
查:
exists mykey
get mykey
判断该键是否存在,存在返回 1,否则返回0
获取Key对应的value
strlen mykey
ttl mykey
获取指定 Key 的字符长度
查看一下指定 Key 的剩余存活时间(秒数)
获取第2到第20个字节,若20超过value长度,则截取第2个和后面
getrange mykey 1 20
所有的
mget key3 key4
批量获取键

hash类型(字典类型)

应用场景:
存储部分变更的数据,如用户信息等。
最接近mysql表结构的一种类型# 存数据
127.0.0.1:6379> hmset stu1 id 101 name yuan age 18 gender m
OK
127.0.0.1:6379> hmset stu2 id 102 name xiaojiang age 20 gender f
OK
127.0.0.1:6379> keys stu*
1) "stu1"
2) "stu2"# 取数据
127.0.0.1:6379> hget stu1 id
"101"
127.0.0.1:6379> hmget stu1 id age gender
1) "101"
2) "18"
3) "m"
更多的例子:
增
hset myhash field1 "s"
若字段field1不存在,创建该键及与其关联的Hashes, Hashes中,key为field1 ,并设value为s ,若存
在会覆盖原value
hsetnx myhash field1 s
若字段field1不存在,创建该键及与其关联的Hashes, Hashes中,key为field1 ,并设value为s, 若字
段field1存在,则无效
hmset myhash field1 "hello" field2 "world
一次性设置多个字段
删
hdel myhash field1
del myhash
删除 myhash 键中字段名为 field1 的字段
删除键改
hincrby myhash field 1
给field的值加1
查
hget myhash field1
hlen myhash
获取键值为 myhash,字段为 field1 的值
获取myhash键的字段数量
hexists myhash field1
段
判断 myhash 键中是否存在字段名为 field1 的字
hmget myhash field1 field2 field3
hgetall myhash
hkeys myhash
一次性获取多个字段
返回 myhash 键的所有字段及其值
获取myhash 键中所有字段的名字
获取 myhash 键中所有字段的值

 

LIST(列表)

消息队列系统
比如sina微博:在Redis中我们的最新微博ID使用了常驻缓存,这是一直更新的。
但是做了限制不能超过5000个ID,因此获取ID的函数会一直询问Redis。
只有在start/count参数超出了这个范围的时候,才需要去访问数据库。
系统不会像传统方式那样“刷新”缓存,Redis实例中的信息永远是一致的。
SQL数据库(或是硬盘上的其他类型数据库)只是在用户需要获取“很远”的数据时才会被触发,
而主页或第一个评论页是不会麻烦到硬盘上的数据库了。微信朋友圈:
127.0.0.1:6379> LPUSH wechat "today is nice day !"
127.0.0.1:6379> LPUSH wechat "today is bad day !" "today is rainy day !" "today is good day !"
127.0.0.1:6379> lrange wechat 0 0
1) "today is friday !"
127.0.0.1:6379> lrange wechat 0 1
1) "today is friday !"
2) "today is rainy day !"
127.0.0.1:6379> lrange wechat 0 2
1) "today is friday !"
2) "today is rainy day !"
3) "today is good day !"

 

SET集合类型(join union)

应用场景:
案例:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。
Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,
对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
sadd \ smembers \ SUNION \ SINTER \ SDIFF \ sdiffstore \ sinterstore \
sunionstore

 

127.0.0.1:6379> SADD guanzhu1 xiaoyangge dayangge xuezhiqian linjunjie
(integer) 4
127.0.0.1:6379> smembers guanzhu1
1) "xuezhiqian"
2) "linjunjie"
3) "dayangge"
4) "xiaoyangge"
127.0.0.1:6379> sadd guanzhu2 xuezhiqian linjunjie zhangjie
(integer) 3
127.0.0.1:6379> sunion guanzhu1 guanzhu2
1) "zhangjie"
2) "dayangge"
3) "xiaoyangge"
4) "xuezhiqian"
5) "linjunjie"
127.0.0.1:6379> sinter guanzhu1 guanzhu2
1) "xuezhiqian"
2) "linjunjie"
127.0.0.1:6379> sdiff guanzhu1 guanzhu2
1) "xiaoyangge"
2) "dayangge"
127.0.0.1:6379> sdiff guanzhu2 guanzhu1
1) "zhangjie"

 

SortedSet(有序集合)

应用场景:
排行榜应用,取TOP N操作
这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排
序,
这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置
成相应的value,
每次只需要执行一条ZADD命令即可。
--------------
127.0.0.1:6379> zadd topN 0 smlt 0 fskl 0 fshkl 0 lzlsfs 0 wdhbx 0 wxg
(integer) 6
127.0.0.1:6379> ZINCRBY topN 100000 smlt
"100000"
127.0.0.1:6379> ZINCRBY topN 10000 fskl
"10000"
127.0.0.1:6379> ZINCRBY topN 1000000 fshkl
"1000000"
127.0.0.1:6379> ZINCRBY topN 100 lzlsfs
"100"
127.0.0.1:6379> ZINCRBY topN 10 wdhbx
"10"
127.0.0.1:6379> ZINCRBY topN 100000000 wxg
"100000000"
127.0.0.1:6379> ZREVRANGE topN 0 2
1) "wxg"
2) "fshkl"
3) "smlt"
127.0.0.1:6379> ZREVRANGE topN 0 2 withscores
1) "wxg"
2) "100000000"
3) "fshkl"
4) "1000000"
5) "smlt"
6) "100000"

 

发布订阅

PUBLISH channel msg
将信息 message 发送到指定的频道 channel
SUBSCRIBE channel [channel ...]
订阅频道,可以同时订阅多个频道
UNSUBSCRIBE [channel ...]
取消订阅指定的频道, 如果不指定频道,则会取消订阅所有频道
PSUBSCRIBE pattern [pattern ...]
订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,比如 it* 匹配所
有以 it 开头
的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有 以 news. 开头的频道(
news.it 、 news.global.today 等等),诸如此类
PUNSUBSCRIBE [pattern [pattern ...]]
退订指定的规则, 如果没有参数则会退订所有规则
PUBSUB subcommand [argument [argument ...]]
查看订阅与发布系统状态
注意:使用发布订阅模式实现的消息队列,当有客户端订阅channel后只能收到后续发布到该频道的消息,之
前发送的不会缓存,必须Provider和Consumer同时在线。
发布订阅例子:
窗口1:
127.0.0.1:6379> SUBSCRIBE baodi
窗口2:
127.0.0.1:6379> PUBLISH baodi "jin tian zhen kaixin!"
订阅多频道:
窗口1:
127.0.0.1:6379> PSUBSCRIBE wang*
窗口2:
127.0.0.1:6379> PUBLISH wangbaoqiang "jintian zhennanshou "

 

Redis事务

redis的事务是基于队列实现的。
mysql的事务是基于事务日志实现的。
开启事务功能时(multi)
multi
command1
command2
command3
command4
exec
discard4条语句作为一个组,并没有真正执行,而是被放入同一队列中。
如果,这是执行discard,会直接丢弃队列中所有的命令,而不是做回滚。
exec
当执行exec时,对列中所有操作,要么全成功要么全失败

 

image

 服务器管理

info:用于查看 Redis 服务器的运行状态和统计信息127.0.0.1:6379> info  # 显示全部信息127.0.0.1:6379> info replication  # 显示某一节的信息
client:查看当前连接数,阻塞中的连接127.0.0.1:6379> client list  # 显示所有的连接数信息127.0.0.1:6379> client kill ip:port  # 主动断开指定 IP 和端口的客户端连接CLIENT KILL ID 3 —— 按客户端 IDCLIENT KILL 只是让服务端断开了那条 TCP 连接,而你本地的 redis-cli 会自动重连生成一条新连接,所以你仍停留在命令行中——想真正退出需要用 quitCONFIG GET * —— 查看全部配置
CONFIG SET —— 在线动态改配置重启就失效。​ CONFIG SET 只改内存里的值,不动配置文件不是所有配置都能动态改。​ 有些是启动只读
CONFIG RESETSTAT —— 统计清零(重置 INFO stats 里的累计计数器)
DBSIZE —— 当前库的 key 数量
FLUSHALL 清空所有数据(所有库)
select 1(切换数据库,默认有16个库)
FLUSHDB 清空当前库monitor 监控实时指令
shutdown关闭服务器

image

redis主从复制

原理

1. 副本库通过slaveof 10.0.0.8 6379命令,连接主库,并发送SYNC给主库
2. 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库
3. 副本库接收后会应用RDB快照
4. 主库会陆续将中间产生的新的操作,保存并发送给副本库
5. 到此,我们主复制集就正常工作了
6. 再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库.
7. 所有复制相关信息,从info信息中都可以查到.即使重启任何节点,他的主从关系依然都在.
8. 如果发生主从关系临时,从库数据没有任何损坏,在下次重连之后,从库发送PSYN给主库
9. 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的

 

主从数据一致性保证

min-slaves-to-write 1
主节点要求至少有 1 个从节点(replica)保持正常连接时才接受写操作;否则拒绝写入,客户端会收到错误min-slaves-max-lag 3
从节点的复制延迟不能超过 3 秒。lag 是主从最后一次心跳(REPLCONF ACK)的时间差,超过 3 秒就认为该从节点"不健康",不计入上面的数量统计。
从节点每秒会给主节点发一次心跳包(REPLCONF ACK),相当于打卡报平安。主节点记下"最后一次收到打卡的时间"。
lag = 当前时间 − 最后一次收到心跳的时间合起来的含义:主节点只有在"至少 1 个从节点延迟 ≤ 3 秒"时才允许写入;一旦从节点全部断连或延迟超 3 秒,写请求全部被拒。

 

主库是否要开启持久化

主库不开持久化时,重启后内存是空的,而它会把这份""通过全量复制强塞给所有从库——从库无条件清空自身旧数据去加载这个空快照,于是主库和所有从库一起归零。假设主库关闭了 RDB 和 AOF,主库有 10GB 数据,从库也同步了这 10GB:
① 主库进程重启(进程崩溃被拉起 / 运维重启 / 服务器重启)↓
② 主库内存空空如也,且磁盘上没有任何持久化文件可加载↓
③ 从库发现连接断了,重连后向主库发起 PSYNC 请求同步↓
④ 主库重启后 replid 变了、复制积压缓冲区(backlog)也清空了→ 无法做增量同步 → 只能返回 +FULLRESYNC,走【全量复制】↓
⑤ 主库执行 BGSAVE,生成一份【空的 RDB】发给从库↓
⑥ 从库收到 RDB 后,标准流程是:先清空自己全部旧数据,再加载 RDB↓
⑦ 从库加载空 RDB → 从库的 10GB 数据也没了↓
⑧ 最终结果:主库空 + 所有从库空 = 数据 100% 丢失,无法恢复

主从复制实现

requirepass  入站​  别人要连我,得先报密码

masterauth  出站​  我要去连主库,得带上密码

loglevel notice 是 Redis 的日志详细程度开关

[root@web02 /data]# mkdir /data/638{0..2}
[root@web02 /data]# cat >> /data/6380/redis.conf <<EOF
port 6380
daemonize yes
pidfile /data/6380/redis.pid
loglevel notice
logfile "/data/6380/redis.log"
dbfilename dump.rdb
dir /data/6380
requirepass 123
masterauth 123
EOF
[root@web02 /data]# cat >> /data/6381/redis.conf <<EOF
port 6381
daemonize yes
pidfile /data/6381/redis.pid
loglevel notice
logfile "/data/6381/redis.log"
dbfilename dump.rdb
dir /data/6381
requirepass 123
masterauth 123 
EOF
[root@web02 /data]# cat >> /data/6382/redis.conf <<EOF
port 6382
daemonize yes
pidfile /data/6382/redis.pid
loglevel notice
logfile "/data/6382/redis.log"
dbfilename dump.rdb
dir /data/6382
requirepass 123
masterauth 123
EOFredis-server /data/6380/redis.conf
redis-server /data/6381/redis.conf
redis-server /data/6382/redis.conf

开启主从

主节点:6380

从节点:6381、6382

6381/6382命令行:

redis-cli -p 6381 -a 123 SLAVEOF 127.0.0.1 6380

redis-cli -p 6382 -a 123 SLAVEOF 127.0.0.1 6380

redis-cli -p 6380 -a 123 info replication

redis-cli -p 6381 -a 123 info replication

redis-cli -p 6382 -a 123 info replication

从库切换为主库

slaveof no one:让从库断开与主库的复制关系,停止数据同步(此时从库自动变成主库)# 模拟主库故障
[root@web02 ~]# redis-cli -p 6380 -a 123 shutdown# 将从库转换为主库
[root@web02 ~]# redis-cli -p 6381 -a 123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6381> slaveof no one
OK
127.0.0.1:6381> info replication
# Replication
role:master# 6382连接到6381
[root@web02 ~]# redis-cli -p 6382 -a 123
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
127.0.0.1:6382> slaveof no one
OK
127.0.0.1:6382> slaveof 127.0.0.1 6381
OK
127.0.0.1:6382> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6381

 

redis哨兵(redis-sentinel)

Sentinel 是一个独立运行的特殊 Redis 进程(不是 Redis 服务器本身,不能存数据),默认监听 26379​ 端口。它以分布式集群方式部署,专门负责:盯梢主从结构 → 发现主库挂了 → 自动完成故障转移 → 通知客户端新主地址

1、监控
2、自动选主,切换(6381 slaveof no one)
3、2号从库(6382)指向新主库(63814、应用透明
5、多sentinel防止脑裂

image

 多 Sentinel 通过法定票数(quorum 判定客观下线 + majority 授权 Leader 切换),确保只有多数派分区才有权发起故障转移,少数派分区凑不够票数而无法选新主,从而保证同一时刻全局只会有一个合法主库,从根源上避免双主脑裂。

sentinel搭建过程

mkdir /data/26380
cd /data/26380
vim sentinel.conf
port 26380
dir "/data/26380"
sentinel monitor mymaster 127.0.0.1 6381 1
# 通用格式:sentinel monitor <主库别名> <主库IP> <主库端口> <quorum>
主库别名:给这套主从结构起的名字,后续所有配置都靠它关联;客户端连接时也要用这个名字
主库 IP:只填主库,从库不用写
主库端口:只填写主库,从库不用写
quorum:判定需要多少个sentinel同意票数(类似于mysql中的选主) sentinel down
-after-milliseconds mymaster 5000
Sentinel 每秒向主库发一次 PING,如果连续 5000 毫秒(5 秒)内都没有收到有效回复,就判定该实例主观下线(sdown)
sentinel auth-pass mymaster 123
告诉 Sentinel,它要去监控/操作的那个主库(mymaster),密码是 123
启动: redis-sentinel /data/26380/sentinel.conf & 如果有问题: 1、重新准备1主2从环境 2、kill掉sentinel进程 3、删除sentinel目录下的所有文件 4、重新搭建sentinel

 

PING :返回 PONG 。
SENTINEL masters :列出所有被监视的主服务器
SENTINEL slaves <master name>
SENTINEL get-master-addr-by-name <master name> : 返回给定名字的主服务器的 IP 地址和端
口号。
SENTINEL reset <pattern> : 重置所有名字和给定模式 pattern 相匹配的主服务器。
SENTINEL failover <master name> : 当主服务器失效时, 在不询问其他 Sentinel 意见的情况
下, 强制开始一次自动故障迁移。

 

redis cluster

 

返回列表