Redis基础

基本概念

数据类型

数据类型 数据结构 说明 适用场景
STRING 自己封装了一个sds的类型 能存储任何形式的字符串,包括:
1. 二进制数据
2. 序列化后的数据
3. JSON化的对象
4. 图片(Base64)
1. 普通的key/value存储
2. 保持计数(整数或浮点数自增或自减、访问超过阀值封锁)
3. 结合过期时间缓存Session
LIST 双向链表 1. 有序
2. 可重复
1. 缓存数据库表
2. 消息队列
3. 服务器的监控程序(在尽可能短的时间内,并行地检查一组网站,确保它们的可访问性)
SET value永远为null的HashMap(通过计算hash的方式来快速排重) 1. 无序
2. 不重复
3. string类型元素集合
1. 集合去重
2. 求交集
3. 求并集
4. 求差集
HASH Hash对应Value内部实际就是一个HashMap。
1. 成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap。
2. 成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
string类型的field和value的映射表 1. 购物车
2.高效率查询(hash的时间复杂度是O(1))
3. 存储对象
ZSET 使用HashMap和跳跃表(SkipList)来保证数据的存储和有序。
HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员。
排序依据是HashMap里存的score。
使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。
1. 有序
2. 不重复
3. string类型元素集合
4. 通过分数(score)进行排序
1. 以某个条件为权重排序
2. Top n排行榜

字符串实现原理

1
2
3
4
5
6
7
8
9
10
struct sdshdr{
// 记录buf数组中已经使用字节的数量(等于SDSN所保存字符串的长度)
int len;

// 记录buf数组中未使用字节的数量
int free;

// 字节数组,用于保存字符串
char buf[];
};

SDS数据类型的优点:

  1. 获取字符串长度变为O(1)。
  2. 线程安全,不会造成缓冲区溢出
  3. 减少了字符串改变造成的空间分配次数

持久化

对比项 RDB AOF
说明 在指定的时间间隔能对你的数据进行快照存储。 记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。
安全性 较差 更安全
文件大小 较小 较大
恢复速度 较快 较慢

缓存淘汰策略

缓存淘汰策略 说明 适用场景
voltile-lru 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 希望一些数据能长期被保存,而一些数据可以被淘汰掉
volatile-random 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 希望一些数据能长期被保存,而一些数据可以被淘汰掉
volatile-ttl 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 需要通过设置不同的ttl来判断数据过期的先后顺序
allkeys-lru 从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 1. 数据有一部分访问频率较高,其余部分访问频率较低
2. 无法预测数据的使用频率时
allkeys-random 从数据集(server.db[i].dict)中任意选择数据淘汰 所有数据访问概率大致相等
no-enviction 禁止驱逐数据,达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。 数据量较少,有充足的内存

事务

介绍

功能/特点 说明
非严格的ACID事务 比如一串用EXEC提交执行的命令,在执行中服务器宕机,那么会有一部分命令执行了,剩下的没执行。
基本的命令打包执行的功能 在服务器不出问题的情况下,可以保证一连串的命令是顺序在一起执行的,中间有会有其它客户端命令插进来执行。
Watch 你可以对一个key进行Watch,然后再执行事务,在这过程中,如果这个Watched的值进行了修改,那么这个Transactions会发现并拒绝执行。

执行事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
########### 执行事务 ##########
# 开启事务
$ multi

# 第一条命令进入等待队列
$ sadd 'user1' 2
# 第二条命令进入等待队列
$ sadd 'user2' 1

# 提交事务
$ exec

# 放弃事务
$ discard

基于watch机制实现乐观锁

监视一个(或多个)key,如果在事务exec执行之前这个(或这些)key被其它命令所改动,那么事务将被打断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
##########  ##########

# 设置k1的值为1
$ set k1 1

# 监视k1(客户端1执行)
# 当已经开始监视k1,则其它客户端不能修改k1的值
$ watch k1

# 开始事务(客户端1执行)
$ multi

# 设置k1的值为2(客户端2执行)
$ set k1 2

# 修改k1的值为3(客户端1执行)
$ set k1 3

# 提交事务(客户端1执行)
# k1值不会被修改为3,k1的值仍然是2,因为在事务开启之前k1的值被修改了
$ exec

发布订阅

说明
发布订阅 在Redis中,可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。
适用场景 实时消息系统,比如普通的即时聊天,群聊等功能。

命令行实现发布订阅示例

配置Redis订阅端

1
2
3
4
5
# 订阅channel频道主题
$ subscribe channel
baidu
# 订阅匹配模式的主题(匹配以chan开头的频道主题)
$ subscribe chan*

配置Redis发布端

1
$ publish channel message

Jedis实现发布订阅示例

参见:https://github.com/iamwlb/redisbasic 中pubsub模块

常用命令

通用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 测试redis服务的可用性
$ ping

# 获得Redis的所有配置
$ config get *

# 查看Redis服务器的统计信息
$ info

# 切换库
$ select db

# 删除当前库
$ flushdb

# 删除所有库
$ flushall

# 退出当前Redis连接
$ exit
$ quit

# 查看当前数据库中key的数据
$ dbsize

操作Key命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 列出所有key
# 注意:线上环境慎用
$ keys *

# 列出以xx开头的key
$ keys xx*

# 检查某个key是否存在
$ exists key

# 将当前库的key移动到给定的库中
$ move k1 2

# 设置key的值的过期时间
$ expire key seconds

# 查看key还有多少秒过期。
# -1:永不过期
# -2:已过期或key存在
$ ttl key

# 查看key所存储的值的类型
$ type key

# 删除key
$ del key

操作String命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 将字符串值value设置到key中
$ set key value

# 获得key的值
$ get key

# 将key中存储的数字值加1。
# 注意:只能对数字类型的数据操作。如果key不存在,则key的值先初始化为0,再执行incr操作。
$ incr key

# 将key中存储的数字值减1。
# 注意:只能对数字类型的数据操作。如果key不存在,则key的值先初始化为0,再执行incr操作。
$ decr key

# set expire的简写,设置key的值,并将key的生存时间设为seconds(以秒为单位)
$ setex key seconds value

# set if not exists的简写,如果key不存在,则set值,存在则不设置值
$ setnx key value

# 设置key的值为value,并返回key的旧值
$ getset key value

# 返回key所存储的字符串值的长度
$ strlen

# 如果key存在且是字符串,则将value追加到key原来旧值的末尾。
# 如果key不存在,则将key值设置为value
$ append

# 将key所存储的值加上增量值,如果key不存在,则key的值先被初始化为0再执行incrby命令
$ incrby key increment

# 将key所存储的值减去增量值,如果key不存在,则key的值先被初始化为0再执行decrby命令
$ decrby

# 获取key中字符串值从start开始到end结束的子字符串
$ getrange key start end

# 从指定的位置开始将key的值替换为新的字符串,比如旧值helloworld,setrange key 5 redis,将从第5个下标位置往后开始替换为新的字符串redis,最终结果:helloredis
$ setrange key offset value

# 同时设置一个或多个key-value对
$ mset key value [key,value ...]

# 获取所有(一个或多个)给定key的值
$ mget key [key ...]

# 同时设置一个或多个key-value对,当且仅当所有给定key都不存在时才能设置成功,否则只要有一个key存在都会失败
$ msetnx

操作Hash命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 将哈希表key中的域filed的值设置为value
$ hset key field value

# 获取哈希表key中给定域field的值
$ hget key field

# 同时将多个field-value(域-值)对设置到哈希表key中
$ hmset

# 获取哈希表key中一个或多个给定域的值
$ hmget

# 获取哈希表key中所有的域和值
$ hgetall key

# 删除哈希表中一个或多个指定域field
$ hdel

# 查看哈希表key中的所有field域
$ hkeys key

# 查看哈希表key中所有域的值
$ hvals

# 获取哈希表key中域field的个数
$ hlen

# 查看哈希表key中,给定域field是否存在
$ hexists

# 为哈希表key中的域field的值加上增量increment
$ hincrby

# 为哈希表中的域field加上浮点数增量increment
$ hincrbyfloat
# 将哈希表key中的域field的值设置为value,当且仅当域field不存在的时候才设置,否则不设置
$ hsetnx

操作list命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 将一个或多个值value插入到列表key的表头(最左边)
$ lpush key value [value ...]

# 将一个或多个值value插入到列表key的表尾(最右边)
$ rpush

# 获取列表key中指定区间内的元素。
# 0:表示列表的第一个元素
# 1:表示列表的第二个元素
# -1:表示列表的最后一个元素
# -2:表示列表的倒数第二个元素
# 一次类推
$ lrange

# 从左边获取列表key的一个元素,并将该元素移除
$ lpop

# 从右边获取列表key的一个元素,并将该元素移除
$ rpop

# 获取列表key中下标为指定index的元素
$ lindex

# 获取列表key的长度
$ llen

# 从左到右删除列表中指定个数的并与指定value值相等的value
$ lrem

# 删除指定区域外的元素,比如TRIM list 0 2,表示只保留列表list的前3个元素,其余元素全部删除
$ ltrim key start stop

# 将列表source中的最后一个元素(尾元素)弹出插入到列表destination,作为destination列表的头元素
$ rpoplpush

# 将列表key下标为index的元素的值设置为value
$ lset

# 将值value插入到列表key当中位于值pivot之前或之后的位置
$ linsert key BEFORE|AFTER pivot value

操作set命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 
$ sadd key member [member ...]

# 获取集合key中的所有成员元素
$ smembers

# 判断member元素是否是集合key的成员
$ sismember

# 获取集合里面的元素个数
$ scard

# 删除集合key中的一个或多个member元素
$ srem

# 随机返回集合中的一个元素
$ srandmember

# 随机从集合中删除一个元素
$ spop

# 将member元素从一个集合移动到另一个集合
$ smove

操作zset命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 将一个或多个member元素及其score值加入到有序集合key中
$ zadd key score value [score value ...]

# 删除有序集合key中的一个或多个成员
$ zrem

# 获取有序集合key的元素成员的个数
$ zcard

# 获取有序集合key中成员member的排名,有序集合成员按score值从小到大顺序排列
$ zrank

# 获取有序集合key中成员member的排名,有序集合成员按score值从大到小顺序排列
$ zrevrank

# 获取有序集合key中所有score值介于min和max之间的成员
$ zrangebyscore

# 获取有序集合key中所有score值介于max和min之间的所有成员
$ zrevrangebyscore

# 获取有序集合key中所有score值介于min和max之间的成员的个数
$ zcount

# 获取有序集合key中,指定区间内的成员,按score值从小到大排序
$ zrange key start stop [WITHSCORES]

# 获取有序集合key中,指定区间内的成员,按score值从大到小排序
$ zrevrange

主从复制

介绍

说明
主从复制介绍 复制(replication)可以让其它服务器拥有一个不断更新的数据副本,从而使得拥有数据副本的服务器可以用于处理客户端发送的读请求。
主从复制用途 1. 从Redis主服务器向从服务器同步数据
2. 利用从库分担主库的读压力
主从复制同步触发时间 客户端每次向主服务器进行写入时,从服务器都会实时地得到更新。

工作原理

  1. 如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个SYNC命令给master请求复制数据。
  2. master收到SYNC命令后,会在后台进行数据持久化,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份数据集发送给slave,slave会把接收到的数据进行持久化,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
  3. 当master与slave之间的连接由于某些原因而断开时,slave能够自动重连master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。
  4. 当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,支持部分复制。

类型

复制类型 说明
全部复制 如果是全部复制的话,Master必须开启数据持久化功能,如果是降低开销关闭数据持久化,那也需要禁用master的自动重启功能,因为一旦master因为故障自动重启的话,主数据丢失,从节点会将空数据复制到从节点上,从而导致数据的丢失。
部分复制 主服务器会为复制流提供一个缓冲区,主从服务器都会维护一个复制偏移量和一个master run id,如果服务器断开,从服务器会向主服务器发送一个psync命令,并且将上次复制偏移量和run id发送给主服务器,主服务器验证run id和偏倚量,如果验证通过且偏移量相同,则继续从上次中断地方开始复制,如果验证不通过和偏移量不同,则选择全量同步。
无磁盘复制 主从复制主要是通过传输RDB文件到从服务器,从服务器加载RDB文件实现数据同步,但是文件的读取是很耗机器性能,所以从2.8.18开始尝试无磁盘复制,主要是将RDB文件直接传输到从服务器加载,不经过硬盘做中间的存储。

命令

1
2
3
4
5
6
7
8
9
# 查看replication状态
$ info replication

# 在从服务器上配置主服务器的ip、port
$ slaveof ip port

# 在主服务器失败的时候,将从属服务器用作新的主服务器,从而实现无间断运行。
# 注意:SLAVEOF NO ONE 不会丢弃同步所得数据集
$ slaveof no one

配置主从复制

环境说明

主机名 IP 操作系统
node1 192.168.100.101 CentOS7
node2 192.168.100.102 CentOS7
node3 192.168.100.103 CentOS7
node4 192.168.100.104 CentOS7

配置主服务器(node1)

1
2
3
# 设置密码
requirepass 123456
masterauth 123456

方法一:配置redis.conf(node2、node3、node4)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# 设置密码
requirepass 123456
masterauth 123456

# 配置主服务器的地址
slaveof 192.168.100.101 6379

# 配置从服务器只读
slave-read-only yes

########## 可选项 ##########

# 当slave丢失master或者同步正在进行时,如果发生对slave的服务请求,是否正常提供服务
# 可选项:
# yes:slave依然正常提供服务
# no:slave返回client错误:"SYNC with master in progress"
slave-serve-stale-data yes

# 是否使用无硬盘复制功能
# 默认值:no
# 可选项:
# yes:无磁盘复制(socket)
# no:磁盘复制(disk)
repl-diskless-sync no

# 无硬盘复制功能延迟时间
# 默认值:5
repl-diskless-sync-delay 5

# 从服务器发送PING命令给主服务器的周期(秒)
# 默认值:10
repl-ping-slave-period 10

# 设置主库批量数据传输时间或者ping回复时间间隔(秒)
# 默认值:60
repl-timeout 60

# 是否关闭无延迟同步
# 默认值:no,即采用无延迟同步
# 可选项
# no:则发送数据到 slave 端的延迟会降低,但将使用更多的带宽用于复制
# yes:Redis将使用一个较小的数字TCP数据包和更少的带宽将数据发送到slave,但是这可能导致数据发送到 slave 端会有延迟 , 如果是 Linux kernel 的默认配置,会达到 40 毫秒
repl-disable-tcp-nodelay no

# 设置主从复制容量大小,这个backlog 是一个用来在 slaves 被断开连接时存放 slave 数据的 buffer。
# 1、复制的后台日志越大, slave 断开连接及后来可能执行部分复制花的时间就越长。
# 2、后台日志在至少有一个 slave 连接时,仅仅分配一次。
repl-backlog-size 1mb

# 配置定义从最后一个 slave 断开连接后需要释放的时间(秒)。在 master 不再连接 slave 后,后台日志将被释放。
# 可选项:
# 0:不释放后台日志
repl-backlog-ttl 3600

# 作用:设置slave的优先级
# 1. 如果 master 不能再正常工作,那么会在多个 slave 中,选择优先值最小的一个 slave 提升为master
# 2. 优先值为 0 表示不能提升为 master 。
# 3. 数值越小,优先级越高
slave-priority 100

# 限制有N个以上从服务器才允许写入
# 最少包含的从服务器
min-slaves-to-write 3
# 延迟值
min-slaves-max-lag 10

方法二:启动时指定参数(node2、node3、node4)

1
$ ./redis-server --slaveof <master-ip> <master-port>

容灾处理

冷处理

当Master服务出现故障,需手动将slave中的一个提升为master,剩下的slave挂至新的master上

1
2
3
4
5
# 将从属服务器用作新的主服务器
$ slaveof no one

# 将slave挂至新的master上
$ slave of ip port

哨兵

功能

sentinel功能 说明
监控(Monitoring) Redis Sentinel实时监控主服务器和从服务器运行状态,并且实现自动切换。
提醒(Notification) 当被监控的某个 Redis 服务器出现问题时, Redis Sentinel 可以向系统管理员发送通知, 也可以通过 API 向其他程序发送通知。
自动故障转移(Automatic failover) 当一个主服务器不能正常工作时,Redis Sentinel 可以将一个从服务器升级为主服务器, 并对其他从服务器进行配置,让它们使用新的主服务器。当应用程序连接Redis 服务器时, Redis Sentinel会告之新的主服务器地址和端口。

流程

  1. Sentinel会监视一系列主服务器以及这些主服务器的从服务器,通过向主服务器发送PUBLISH命令和SUBSCRIBE命令,并向主服务器发送PING命令,各个Sentinel进程可以自主识别可用的从服务器和其它Sentinel。
  2. 当主服务器失效的时候,监视这个主服务器的所有Sentinel就会基于彼此共有的信息选出一个Sentinel,并从现有的从服务器当中选出一个新的主服务器。
  3. 当被选中的从服务器转换成主服务器之后,那个被选中的Sentinel就会让剩余的其它从服务器去复制这个新的主服务器(在默认设置下,Sentinel会一个接一个地迁移从服务器,但这个数量可以通过配置选项进行修改。)

命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# sentinel的基本状态信息
$ info sentinel

# 列出所有被监视的主服务器,以及这些主服务器的当前状态
$ SENTINEL masters

# 列出给定主服务器的所有从服务器,以及这些从服务器的当前状态
$ SENTINEL slaves <master name>

# 返回给定名字的主服务器的 IP 地址和端口号
$ SENTINEL get-master-addr-by-name <master name>

# 重置所有名字和给定模式 pattern 相匹配的主服务器。重置操作清除主服务器目前的所有状态, 包括正在执行中的故障转移, 并移除目前已经发现和关联的, 主服务器的所有从服务器和 Sentinel 。
$ SENTINEL reset <pattern>

# 当主服务器失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移,但是它会给其他sentinel发送一个最新的配置,其他sentinel会根据这个配置进行更新
$ SENTINEL failover <master name>

哨兵配置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 哨兵sentinel实例运行的端口,默认26379  
port 26379
# 哨兵sentinel的工作目录
dir ./

# 哨兵sentinel监控的redis主节点的
## ip:主机ip地址
## port:哨兵端口号
## master-name:可以自己命名的主节点名字(只能由字母A-z、数字0-9 、这三个字符".-_"组成。)
## quorum:当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass <foobared>,所有连接Redis实例的客户端都要提供密码。
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456

# 指定主节点应答哨兵sentinel的最大时间间隔,超过这个时间,哨兵主观上认为主节点下线,默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

# 指定了在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步。这个数字越小,完成failover所需的时间就越长;反之,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为1,来保证每次只有一个slave,处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

# 故障转移的超时时间failover-timeout,默认三分钟,可以用在以下这些方面:
## 1. 同一个sentinel对同一个master两次failover之间的间隔时间。
## 2. 当一个slave从一个错误的master那里同步数据时开始,直到slave被纠正为从正确的master那里同步数据时结束。
## 3. 当想要取消一个正在进行的failover时所需要的时间。
## 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来同步数据了
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

# 当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本。一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
# 对于脚本的运行结果有以下规则:
## 1. 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10。
## 2. 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
## 3. 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh

# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

配置哨兵

环境说明

主机名 IP 操作系统
node1 192.168.100.101 CentOS7
node2 192.168.100.102 CentOS7
node3 192.168.100.103 CentOS7

配置主节点(node1)

1
2


配置从节点(node2、node3)

1
2


java操作sentinel

参见:https://github.com/iamwlb/redisbasic

集群

Codis

Redis-Cluster

常见问题

应用场景

Redis应用场景 说明
手机验证码 基于过期时间
数据库缓存 基于list实现
队列 基于list实现
存储配置(配置文件信息、统一Session存储)
搜索
对搜索结果进行排序
自动补全
分布式锁 基于watch机制实现乐观锁
最新列表 基于ZSET实现
排行榜 基于ZSET实现
消息通知 基于消息发布订阅+任务队列(List类型的LPUSH和RPOP(左进右出))实现
用户Timeline/Feeds 关注的人、主题、品牌的动态。
反垃圾系统 制定一系列反垃圾规则,其中有些规则可以利用redis做实时分析,譬如:1分钟评论不得超过2次、5分钟评论少于5次等采用sorted set将最近一天用户操作记录起来。
存储社交关系 譬如将用戶的好友/粉丝/关注,可以存在一个sorted set中,score可以是timestamp,这样求两个人的共同好友的操作,可能就只需要用求交集命令即可。
限制访客访问频率 进行各种数据统计的用途是非常广泛的,比如想知道什么时候封锁一个IP地址。INCRBY命令让这些变得很容易,通过原子递增保持计数;GETSET用来重置计数器;过期属性expire用来确认一个关键字什么时候应该删除。

缓存更新策略

场景 说明
读取 先读缓存,缓存没有,再读数据库
更新 先删除缓存,再更新数据库
删除 先删除缓存,再删除数据库
添加 先添加数据库,再添加缓存

缓存穿透

缓存穿透介绍

名词 原因 经过 结果
缓存穿透 先查缓存,缓存不存在,再查询数据库返回查询结果
查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询DB 缓存就失去了意义,在流量大时,可能DB就挂掉了。

解决缓存穿透问题?

方案 说明
布隆过滤器 使用布隆过滤器提前拦截,不合法就不让这个请求到数据库层。

缓存雪崩

缓存雪崩介绍

名词 原因 经过 结果
缓存雪崩 设置缓存时采用了相同的过期时间 缓存在某一时刻同时失效,请求全部转发到DB DB瞬时压力过重雪崩

解决缓存雪崩问题

方案 说明
设置不同过期时间 简单方案就时讲缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
定时更新 定时更新,假如缓存过期时间为60分钟,则单独设置一个线程每59分钟去负责更新缓存。

缓存击穿

缓存击穿介绍

名词 原因 经过 结果
缓存击穿 缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来 这些请求从后端DB加载数据并回设到缓存 瞬间把后端DB压垮

解决缓存击穿问题

方案 说明
互斥锁 使用互斥锁,同一时刻只允许一个线程去构建缓存,其他线程等待构建完毕后去缓存取。
定时更新 定时更新,假如缓存过期时间为60分钟,则单独设置一个线程每59分钟去负责更新缓存。

双写一致性问题

双写一致性问题介绍

名词 原因 经过 结果
双写一致问题 1. 缓存服务器挂了
2. 网络问题或网络延迟
3. ...
修改了数据库,没有及时修改缓存 缓存跟数据数据不一致

如何保证数据库与Redis双写一致性?

标题 说明
业务层实现 1. 在修改数据库后,无法修改缓存,这时候可以将这条数据放到数据库或队列中
2. 同时启动一个异步任务定时去检测缓存服务器是否连接成功,一旦连接成功则从数据库中按顺序取出修改数据,依次进行缓存最新值的修改。
基于binlog订阅方式实现 使用binlog订阅组件。
如:canal。

选择持久化方式

快照(snapshotting)、只追加文件(AOF)两种持久化方式既可以同时使用,又可以单独使用,在某些情况下甚至可以两种方法都不使用,具体选择哪种持久化方式需要根据用户的数据以及应用来决定。 AOF的数据丢失保护级别比快照方式要高。

持久化方式 适用场景
快照(snapshotting) 1. 数据量较少
2. 对数据丢失容忍度相对较高
只追加文件(AOF) 1. 数据量较大
2. 对数据丢失容忍度相对较低

慎用发布订阅模式

慎用发布订阅模式的原因 说明
Redis系统稳定性 1. 对于旧版Redis来说,如果一个客户端订阅了某个或某些频道,但它读取消息的速度却不够快的话,那么不断积压的消息就会使得Redis输出缓冲区的体积变得越来越大,这可能导致Redis的速度变慢,甚至直接崩溃。也可能导致Redis被操作系统强制杀死,甚至导致操作系统本身不可用。
2. 新版的Redis不会出现这种问题,因为它会自动断开不符合client-output-buffer-limit pubsub配置选项要求的订阅客户端。
数据传输的可靠性 任何网络系统在执行操作时都可能会遇上断线情况,而断线产生的连接错误通常会使得网络连接两端中的其中一端进行重新连接。
但是,如果客户端在执行订阅操作的过程中断线,那么客户端将丢失在断线期间发送的所有消息。
因此依靠频道来接收消息的用户可能会对Redis提供的PUBLISH命令和SUBSCRIBE命令的语义感到失望。

配置主从复制注意事项

配置主从复制注意事项 说明
内存分配 最好让主服务器只使用50%~65%的内存,留下一部分内存用于执行BGSAVE命令和创建记录写命令的缓冲区。
数据丢失 从服务器在进行初始连接时,数据库中原有的所有数据都将丢失,并被替换成主服务器发来的数据。
不适用场景 Redis不支持主主复制。

Redis与Memcached对比

对比项 Redis Memcached
数据存储类型 1. String
2. List
3. Set
4. Hash
5. ZSet
1.文本型
2. 二进制(新版本支持)
网络IO模型 单进程模式(Reactor模型,反应炉) 多线程、非阻塞IO模式
事件库 自封装简易AeEvent LibEvent事件库
持久化支持 1. RDB
2.AOF
不支持

Redis HA 方案

方案 说明
keepalived 通过 keepalived 的虚拟 IP,提供主从的统一访问,在主出现问题时, 通过 keepalived 运行脚本将从提升为主,待主恢复后先同步后自动变为主,该方案的好处是主从切换后,应用程序不需要知道(因为访问的虚拟 IP 不变),坏处是引入 keepalived 增加部署复杂性,在有些情况下会导致数据丢失
zookeeper 通过 zookeeper 来监控主从实例, 维护最新有效的 IP, 应用通过 zookeeper 取得 IP,对 Redis 进行访问,该方案需要编写大量的监控代码
sentinel 通过 Sentinel 监控主从实例,自动进行故障恢复,该方案有个缺陷:因为主从实例地址( IP & PORT )是不同的,当故障发生进行主从切换后,应用程序无法知道新地址,故在 Jedis2.2.2 中新增了对 Sentinel 的支持,应用通过 redis.clients.jedis.JedisSentinelPool.getResource() 取得的 Jedis 实例会及时更新到新的主实例地址

性能优化

修改Linux内存分配策略

1
2
3
4
5
$ vi /etc/sysctl.conf
# 添加如下内容,并重启:
vm.overcommit_memory = 1
# 或者(立即生效)
$ sysctl vm.overcommit_memory=1
overcommit_memory 说明
0 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
1 不管需要多少内存,都允许申请。
2 只允许分配物理内存和交换内存的大小。(交换内存一般是物理内存的一半)

关闭Transparent Huge Pages(THP)

THP会造成内存锁影响redis性能,建议关闭。

1
2
3
4
5
6
7
8
9
10
$ vi /etc/rc.local
# 添加如下内容:
if test -f /sys/kernel/mm/transparent_hugepage/enabled; then
 echo never > /sys/kernel/mm/transparent_hugepage/enabled
fi
if test -f /sys/kernel/mm/transparent_hugepage/defrag; then
 echo never > /sys/kernel/mm/transparent_hugepage/defrag
fi
# 给rc.local添加可执行权限
$ chmod +x /etc/rc.d/rc.local

修改linux中TCP 监听的最大容纳数量

在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。 注意Linux内核默默地将这个值减小到/proc/sys/net/core/somaxconn的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的效果。 如果想限制redis的最大连接数需要修改maxclients,默认最大连接数为10000。

限制Redis的内存大小

标题 说明
限制Redis的内存大小 如果不限制内存,当物理内存使用完之后,会使用swap分区,这样性能较低,如果限制了内存,当到达指定内存之后就不能添加数据了,否则会报OOM错误。可以设置maxmemory-policy,内存不足时删除数据。
知识点 - 通过redis的info命令查看内存使用情况
- 如果不设置maxmemory或者设置为0,64位系统不限制内存,32位系统最多使用3GB内存。

修改配置文件

1
2
maxmemory:最大内存
maxmemory-policy:内存不足时,数据清除策略

使用管道进行批量化操作

Redis是个单线程模型,客户端过来的命令是按照顺序执行的,所以想要一次添加多条数据的时候可以使用管道,或者使用一次可以添加多条数据的命令。

降低Redis的内存占用

降低Redis的内存占用事项 说明
短结构 Redis为列表、集合、散列和有序集合提供了一组配置选项,这些选项可以让Redis以更节约空间的方式存储长度较短的结构。
分片结构 分片(sharding)是一种广为人知的技术,它基于某些简单的规则将数据分为更小的部分,然后根据数据所属的部分来决定将数据发送到哪个位置上面。
打包存储二进制位和字节 如果被存储的是一些简短并且长度固定的连续ID,那么还有比使用分片散列更为节约内存的数据存储方法可用,这时可以将数据打包存储在字符串键里面。

源码阅读笔记

哈希表

哈希表的查询效率非常高,复杂度为O(1),通常关注性能的地方都会用到这个东西。 缓存系统,就是一个哈希表。只是通常哈希表的场景都是在本机,把哈希表放到远程的机器上,本机通过网络访问哈希表,就变成现在的缓存系统了。

开发

RedisTemplate

RedisTemplate介绍

spring 封装了 RedisTemplate 对象来进行对redis的各种操作,它支持所有的 redis 原生的 api。

RedisTemplate对数据结构操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//操作字符串
redisTemplate.opsForValue();

//操作hash
redisTemplate.opsForHash();

//操作list
redisTemplate.opsForList();

//操作set
redisTemplate.opsForSet();

//操作有序set
redisTemplate.opsForZSet();

序列化策略

序列化策略 说明
JdkSerializationRedisSerializer POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。
是目前最常用的序列化策略。
StringRedisSerializer Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。
是最轻量级和高效的策略。
JacksonJsonRedisSerializer jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。
【需要jackson-mapper-asl工具支持】
OxmSerializer 提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。
【需要spring-oxm模块的支持】

RedisTemplate与StringRedisTemplate对比

比较项 说明
继承关系 StringRedisTemplate继承自RedisTemplate。
数据互通性 两者的数据是不共通的。
1. StringRedisTemplate只能管理StringRedisTemplate里面的数据
2. RedisTemplate只能管理RedisTemplate中的数据。
序列化策略 1. StringRedisTemplate默认采用的是String的序列化策略
2. RedisTemplate默认采用的是JDK的序列化策略。
使用场景 1. RedisTemplate使用的序列类在在操作数据的时候,比如说存入数据会将数据先序列化成字节数组,然后在存入Redis数据库,这个时候打开Redis查看的时候,你会看到你的数据不是以可读的形式。
2. 如果你的数据需要被第三方工具解析,那么数据应该使用StringRedisSerializer而不是JdkSerializationRedisSerializer。
3. 当你的redis数据库里面本来存的是字符串数据或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可,但是如果你的数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。

总结

  1. 针对“序列化和发序列化”中JdkSerializationRedisSerializer和StringRedisSerializer是最基础的策略,原则上,我们可以将数据存储为任何格式以便应用程序存取和解析(其中应用包括app,hadoop等其他工具),不过在设计时仍然不推荐直接使用“JacksonJsonRedisSerializer”和“OxmSerializer”,因为无论是json还是xml,他们本身仍然是String。

  2. 如果你的数据需要被第三方工具解析,那么数据应该使用StringRedisSerializer而不是JdkSerializationRedisSerializer。

  3. 如果你的数据格式必须为json或者xml,那么在编程级别,在redisTemplate配置中仍然使用StringRedisSerializer,在存储之前或者读取之后,使用“SerializationUtils”工具转换转换成json或者xml。

Redis 执行 Lua

Redis执行Lua基本命令

命令 说明
EVAL 执行Lua脚本。
EVALSHA 执行Lua脚本的sha1。
SCRIPT FLUSH 清空Redis Script。
SCRIPT LOAD 将一个脚本装入脚本缓存,但并不立即运行它
SCRIPT EXISTS 根据给定的脚本校验和,检查指定的脚本是否存在于脚本缓存
SCRIPT KILL 杀死当前正在运行的脚本

Redis Lua scripts debugger

介绍

Redis 执行Lua脚本的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
发送命令请求
EVAL "return redis.call('DBSIZE')" 0
Caller ------------------------------------------> Redis

为脚本 "return redis.call('DBSIZE')"
创建 Lua 函数
Redis ------------------------------------------> Lua

绑定超时处理钩子
Redis ------------------------------------------> Lua

执行脚本函数
Redis ------------------------------------------> Lua

执行 redis.call('DBSIZE')
Fake Client <------------------------------------- Lua

伪客户端向服务器发送
DBSIZE 命令请求
Fake Client -------------------------------------> Redis

服务器将 DBSIZE 的结果
(Redis 回复)返回给伪客户端
Fake Client <------------------------------------- Redis

将命令回复转换为 Lua 值
并返回给 Lua 环境
Fake Client -------------------------------------> Lua

返回函数执行结果(一个 Lua 值)
Redis <------------------------------------------ Lua

将 Lua 值转换为 Redis 回复
并将该回复返回给客户端
Caller <------------------------------------------ Redis

开启调试会话

1
2
$ redis-cli --ldb --eval /tmp/script.lua
$ redis-cli --ldb --eval /tmp/script.lua mykey somekey , arg1 arg2

LDB 在默认情况下将使用子进程来创建调试会话, 并且在调试完成之后, 脚本对数据库进行的任何修改都将会被回滚。

开启同步模式

在一些特殊情况下, 为了追踪特定的 bug , 用户可以会想要保留每个调试会话对数据库所做的修改。 想要这么做的用户可以在启动调试器时, 向 redis-cli 客户端给定 ldb-sync-mode 选项:

1
$ redis-cli --ldb-sync-mode --eval /tmp/script.lua

错误处理

说明
redis.call() 当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因。
redis.pcall() redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误

Redis使用Lua的注意事项

Redis使用Lua的注意事项 说明
不要使用全局变量 如果Lua脚本中包括全局变量时,会报错。定义变量时一定使用local关键字。
evalsha执行位置 一定要确保Lua脚本已经通过load script或eval加载或执行脚本之后再调用evalsha。
设定超时时间 在一主多从的环境中,一定要注意使用evalsha时,从服务有可能会出现未成功加载lua脚本的情况。
redis配置文件中一定要设置lua超时钩子lua-time-limit 5000。

运维

安装

程序说明

路径 说明
/usr/local/bin/redis-server Redis服务器
/usr/local/bin/redis-cli 命令行客户端
/usr/local/bin/redis-benchmark Redis的性能测试工具
/usr/local/bin/redis-check-aof AOF文件修复工具
/usr/local/bin/redis-check-dump RDB文件检测工具
/usr/local/bin/redis-sentinel 启动redis-sentinel
/usr/local/bin/redis.conf Redis的配置文件

Mac中安装 Redis

brew方式安装 Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装Redis
$ brew install redis
# 运行 Redis
$ redis-server
# 后台运行 Redis
$ brew services start redis
# 停止后台运行 Redis
$ brew services stop redis
# 非后台运行 Redis
$ redis-server /usr/local/etc/redis.conf
# 检查 Redis 是否在运行
$ lsof -i tcp:6379
# 运行客户端
$ redis-cli

编译安装

1
2
3
4
$ $ wget http://download.redis.io/releases/redis-4.0.10.tar.gz
$ tar xzf redis-4.0.10.tar.gz
$ cd redis-4.0.10
$ make

CentOS7中安装Redis

yum安装

1
2
3
4
5
6
7
8
9
10
$ yum update
$ yum install epel-release
$ yum install redis
$ systemctl start redis
$ systemctl enable redis
# 验证是否安装成功
$ redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>

解压安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 安装依赖
$ yum install gcc gcc-c++ -y
$ cd /usr/local
$ wget http://download.redis.io/releases/redis-5.0.5.tar.gz
$ tar zxvf redis-5.0.5.tar.gz
$ cd redis-5.0.5
$ make
$ make install
# 设置开机启动
$ ./utils/install_server.sh
# 测试安装
$ netstat -nltp|grep redis
$ ./redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
# 常用管理命令
$ service redis_6379 start
$ service redis_6379 stop
$ service redis_6379 restart
$ chkconfig redis_6379 on
$ chkconfig redis_6379 off

默认配置如下:

配置项 配置值
Port 6379
Config file /etc/redis/6379.conf
Log file /var/log/redis_6379.log
Data dir /var/lib/redis/6379
Executable /usr/local/bin/redis-server
Cli Executable /usr/local/bin/redis-cli

Ubuntu中安装Redis

方式1

1
2
3
4
5
# 安装
$ sudo apt-get update
$ sudo apt-get install redis-server
# 启动
$ sudo /etc/init.d/redis-server start

配置

Redis配置文件样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
# 启用守护进程后,Redis会把pid写到一个pidfile中,在/var/run/redis.pid
daemonize no

# 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid

# 指定Redis监听端口,默认端口为6379
# 如果指定0端口,表示Redis不监听TCP连接
port 6379

# 绑定的主机地址
# 你可以绑定单一接口,如果没有绑定,所有接口都会监听到来的连接
# bind 127.0.0.1

# 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 0

# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug (很多信息, 对开发/测试比较有用)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel verbose

# 日志记录方式,默认为标准输出,如果配置为redis为守护进程方式运行,而这里又配置为标准输出,则日志将会发送给/dev/null
logfile stdout

# 设置数据库的数量,默认数据库为0,可以使用select <dbid>命令在连接上指定数据库id
# dbid是从0到‘databases’-1的数目
databases 16

################################ SNAPSHOTTING #################################
# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# 满足以下条件将会同步数据:
# 900秒(15分钟)内有1个更改
# 300秒(5分钟)内有10个更改
# 60秒内有10000个更改
# Note: 可以把所有“save”行注释掉,这样就取消同步操作了

save 900 1
save 300 10
save 60 10000

# 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

# 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb

# 工作目录.
# 指定本地数据库存放目录,文件名由上一个dbfilename配置项指定
#
# Also the Append Only File will be created inside this directory.
#
# 注意,这里只能指定一个目录,不能指定文件名
dir ./

################################# REPLICATION #################################
slave-serve-stale-data yes

# Slaves send PINGs to server in a predefined interval. It's possible to change
# this interval with the repl_ping_slave_period option. The default value is 10
# seconds.
#
# repl-ping-slave-period 10

# The following option sets a timeout for both Bulk transfer I/O timeout and
# master data or ping response timeout. The default value is 60 seconds.
#
# It is important to make sure that this value is greater than the value
# specified for repl-ping-slave-period otherwise a timeout will be detected
# every time there is low traffic between the master and the slave.
#
# repl-timeout 60

############################## APPEND ONLY MODE ###############################

#
# Note that you can have both the async dumps and the append only file if you
# like (you have to comment the "save" statements above to disable the dumps).
# Still if append only mode is enabled Redis will load the data from the
# log file at startup ignoring the dump.rdb file.
# 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。
# 因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
# log file in background when it gets too big.

appendonly no

# 指定更新日志条件,共有3个可选值:
# no:表示等操作系统进行数据缓存同步到磁盘(快)
# always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
# everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

################################## SLOW LOG ###################################
slowlog-max-len 1024

################################ VIRTUAL MEMORY ###############################

# 把vm-enabled设置为yes,根据需要设置好接下来的三个VM参数,就可以启动VM了
vm-enabled no

# Redis交换文件最好的存储是SSD(固态硬盘)
# 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
# *** WARNING *** if you are using a shared hosting the default of putting
# the swap file under /tmp is not secure. Create a dir with access granted
# only to Redis user and configure Redis to create the swap file there.
vm-swap-file /tmp/redis.swap

# With vm-max-memory 0 the system will swap everything it can. Not a good
# default, just specify the max amount of RAM you can in bytes, but it's
# better to leave some margin. For instance specify an amount of RAM
# that's more or less between 60 and 80% of your free RAM.
# 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多少,所有索引数据都是内存存储的(Redis的索引数据就是keys)
# 也就是说当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
vm-max-memory 0

# Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的数据大小来设定的。
# 建议如果存储很多小对象,page大小最后设置为32或64bytes;如果存储很大的对象,则可以使用更大的page,如果不确定,就使用默认值
vm-page-size 32

# 设置swap文件中的page数量由于页表(一种表示页面空闲或使用的bitmap)是存放在内存中的,在磁盘上每8个pages将消耗1byte的内存
# swap空间总容量为 vm-page-size * vm-pages
vm-pages 134217728

# 设置访问swap文件的I/O线程数,最后不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟,默认值为4
vm-max-threads 4

############################### ADVANCED CONFIG ###############################
# 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 512
hash-max-zipmap-value 64

list-max-ziplist-entries 512
list-max-ziplist-value 64

set-max-intset-entries 512

zset-max-ziplist-entries 128
zset-max-ziplist-value 64

# 指定是否激活重置哈希,默认为开启
activerehashing yes

配置RDB持久化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
################################ 快照  #################################  
#
# Save the DB on disk:保存数据库到磁盘
#
# save <秒> <更新>
#
# 如果指定的秒数和数据库写操作次数都满足了就将数据库保存。
#
# 下面是保存操作的实例:
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
#
# 注释:注释掉“save”这一行配置项就可以让保存数据库功能失效。
#
# 你也可以通过增加一个只有一个空字符串的配置项(如下面的实例)来去掉前面的“save”配置。
#
# save ""

# 作用:15分钟内至少有一个键被更改
save 900 1

# 作用:5分钟内至少有10个键被更改
save 300 10

# 作用:1分钟内至少有10000个键被更改
save 60 10000

# rdb文件存储路径
dir ./

# rdb文件名
dbfilename dump.rdb



#在默认情况下,如果RDB快照持久化操作被激活(至少一个条件被激活)并且持久化操作失败,Redis则会停止接受更新操作。
#这样会让用户了解到数据没有被正确的存储到磁盘上。否则没人会注意到这个问题,可能会造成灾难。
#
#如果后台存储(持久化)操作进程再次工作,Redis会自动允许更新操作。
#
#然而,如果你已经恰当的配置了对Redis服务器的监视和备份,你也许想关掉这项功能。
#如此一来即使后台保存操作出错,redis也仍然可以继续像平常一样工作。
stop-writes-on-bgsave-error yes

#是否在导出.rdb数据库文件的时候采用LZF压缩字符串和对象?
#默认情况下总是设置成‘yes’, 他看起来是一把双刃剑。
#如果你想在存储的子进程中节省一些CPU就设置成'no',
#但是这样如果你的kye/value是可压缩的,你的到处数据接就会很大。
rdbcompression yes

#从版本RDB版本5开始,一个CRC64的校验就被放在了文件末尾。
#这会让格式更加耐攻击,但是当存储或者加载rbd文件的时候会有一个10%左右的性能下降,
#所以,为了达到性能的最大化,你可以关掉这个配置项。
#
#没有校验的RDB文件会有一个0校验位,来告诉加载代码跳过校验检查。
rdbchecksum yes

配置AOF持久化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 作用:是否开启AOF
# 默认值:关闭(no)
appendonly yes

# 设置AOF持久化策略
# 可选值:
# always:每次都同步(最安全但是最慢,不推荐)
# everysec:每秒同步(默认的同步策略,推荐)
# no:不主动同步,由操作系统来决定(最快但是不安全,不推荐)
appendfsync everysec

# 指定 AOF 文件名
appendfilename appendonly.aof

# 作用:在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
# 默认值:no
# 可选项:
# yes:表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no
no-appendfsync-on-rewrite no

# 作用:当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100

# 作用:当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。
auto-aof-rewrite-min-size 64mb

配置登陆密码

1
requirepass 密码

配置允许远程访问

  • 将bind 127.0.0.1注释掉,或修改成允许访问的远程机器的ip地址。
  • 在redis3.2之后,redis增加了protected-mode,将 protected-mode yes 修改为 protected-mode no

配置后端模式启动

1
daemonize yes

系统调优

方法1:立即生效

1
$ sysctl vm.overcommit_memory=1

方法2:重启生效

1
$ vi /etc/sysctl.config

增加如下配置:

1
vm.overcommit_memory = 1

管理Redis

查看Redis版本

1
$ redis-cli --version

查看Redis的信息和状态

1
> info

查看Redis是否在运行

1
> ping

如果返回PONG,则说明Redis正在运行。 或者

1
$ ps -ef |grep redis

将Redis做成服务

启动脚本在cd /usr/local/redis/utils/redis_init_script,根据启动脚本要求,需要将改好的配置文件复制一份到指定目录,不过这一步可以省略,系统安装时已经自动帮我们做了。

1
2
$ mkdir /etc/redis
$ cp redis.conf /etc/redis/6379.conf

将启动脚本复制到/etc/init.d目录

1
2
$ cd /usr/local/redis/utils/
$ cp redis_init_script /etc/init.d/redisd

在启动脚本开头加入注视,修改启动脚本的运行级别

1
$ vi /etc/init.d/redisd

修改如下:

1
2
3
4
#!/bin/sh
# chkconfig: 2345 90 10

# description: Redis is a persistent key-value database

启动Redis

1
2
$ cd /usr/local/bin
$ ./redis-server & #加上“&”表示以后台服务运行。

1
$ redis-server ./redis.conf   #这种方式可以为Redis指定配置文件运行。

1
$ service redisd start  #需要将Redis做成服务才可执行此命令。

停止Redis

1
$ redis-cli shutdown

或者

1
$ service redisd stop  #需要将Redis做成服务才可执行此命令。

开机启动

1
$ chkconfig redisd on  #需要将Redis做成服务才可执行此命令。

取消开机启动

1
$ chkconfig redisd off  #需要将Redis做成服务才可执行此命令。

取消开机启动

1
$ chkconfig redisd off

连接 Redis

1
2
$ redis-cli -h 127.0.0.1 -p 6379
$ auth "password"

备份

1
2
3
127.0.0.1:6379> SAVE
# 获取redis的安装目录
127.0.0.1:6379> CONFIG GET dir

备份完成后会在安装目录下(默认/var/lib/redis/)创建dump.rdb文件

恢复

将备份文件(dump.rdb)移动到redis安装目录,并启动redis服务即可,redis会自动加载并恢复dumop.rdb中的数据。

使用Redis-Dump备份数据

Mac 中安装Redis-Dump

安装RVM

1
2
3
4
5
6
$ curl -L get.rvm.io | bash -s stable
$ source ~/.bashrc
$ source ~/.bash_profile
$ rvm -v
# 列出已知ruby的版本
$ rvm list known

安装 Ryby2.2.4

1
2
$ rvm install 2.2.4
$ ryby -v

安装 Redis-Dump

1
$ gem install redis-dump

导出数据

1
$ redis-dump -u ip:port -a 'password' -d 0 > redisA.json

导入数据

1
$ cat redisA.json | redis-load -u 127.0.0.1:6379 -a 'pwssword'

图形客户端

Ubuntu中安装redis-desktop-manager

1
$ sudo snap install redis-desktop-manager

Redis监控

Monitor

1
$ redis-cli monitor

RedisMonitor

试用了一下,就是一个空的东西,什么都没有,什么功能也没有。

安装redis-monitor 1.0.3

1
2
3
4
5
$ pip install redis-monitor
$ redis-monitor init
$ redis-monitor createdb
$ redis-monitor start
$ curl http://localhost:9527

代码示例

https://github.com/iamwlb/redisbasic

常见错误

error: jemalloc/jemalloc.h: No such file or directory

问题分析

libc 并不是默认的 分配器, 默认的是 jemalloc, 因为 jemalloc 被证明 有更少的 fragmentation problems 比libc。

解决办法

1
$ make MALLOC=libc

LOADING Redis is loading the dataset in memory

错误描述

Redis报错:

1
2
3
$ edis-cli
127.0.0.1:6379> ping
(error) LOADING Redis is loading the dataset in memory

原因分析

redis.conf中maxmemory默认是3G,当redis中dump.rdb文件到达3G时,所有redis的操作都会抛出此异常。

解决办法

  1. redis.conf中maxmemoy设置大一些
    1
    maxmemory <byte>

换算参考 在线文件大小(bit,bytes,KB,MB,GB,TB)转换换算

  1. 删除 dump.rdb文件。

第三方推荐

坚持原创技术分享,您的支持将鼓励我继续创作!
0%