Redis 内存数据结构存储
Redis是一种开放源代码
(BSD许可)的内存中数据结构存储,用作数据库
,缓存
和消息代理
。
Redis提供数据结构,例如字符串
,哈希
,列表
,集合
,带范围查询的排序集合,位图
,超日志
,地理空间索引
和流
。
Redis具有内置的复制
,Lua脚本
,LRU驱逐
,事务
和不同级别的磁盘持久性
,并通过Redis Sentinel
和Redis Cluster
自动分区提供了高可用性
。
关注博主不迷路,获取更多干货资源
1 安装
1.1 下载redis安装包
1 |
|
1.2 解压redis压缩包到指定目录
1 |
|
1.3 安装C程序运行环境
1 |
|
1.4 安装较新版本的tcl
1.4.1 使用压缩包进行安装
1 |
|
1.4.2 在线安装tcl(推荐)
1 |
|
1.5 编译redis
1 |
|
1.5.1 修改redis配置文件
1 |
|
1.5.2 启动redis
1 |
|
1.5.3 关闭redis
1 |
|
1.5.4 连接redis客户端
1 |
|
1.6 GUI工具
1 |
|
2 数据类型
1 |
|
2.1 String
一个键最大能存储512M
序号 | 命令及描述 | 示例 |
---|---|---|
1 | SET key value 设置指定 key 的值 |
SET hello world |
2 | GET key 获取指定 key 的值 |
GET hello |
3 | GETSET key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value) |
GETSET hello world2 |
4 | MGET key1 [key2..] 获取所有(一个或多个)给定 key 的值 |
MGET hello world |
5 | SETEX key seconds value 将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位) |
SETEX hello 10 world3 |
6 | SETNX key value 只有在 key 不存在时设置 key 的值 |
SETNX xiaoma redisvalue |
7 | STRLEN key 返回 key 所储存的字符串值的长度 |
STRLEN xiaoma |
8 | MSET key value [key value ...] 同时设置一个或多个 key-value 对 |
MSET xiaoma2 xiaomavalue2 xiaoma3 xiaomavalue3 |
9 | MSETNX key value [key value ...] 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 |
MSETNX xiaoma4 xiaomavalue4 xiaoma5 xiaomavalue5 |
10 | PSETEX key milliseconds value 这个命令和 SETEX 命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像 SETEX 命令那样,以秒为单位 |
PSETEX xiaoma6 6000 xiaoma6value |
11 | INCR key 将 key 中储存的数字值增一 |
set xiaoma7 1 INCR xiaoma7 GET xiaoma7 |
12 | INCRBY key increment 将 key 所储存的值加上给定的增量值(increment) |
INCRBY xiaoma7 2 get xiaoma7 |
13 | INCRBYFLOAT key increment 将 key 所储存的值加上给定的浮点增量值(increment) |
INCRBYFLOAT xiaoma7 0.8 |
14 | DECR key 将 key 中储存的数字值减一 |
set xiaoma8 1 DECR xiaoma8 GET xiaoma8 |
15 | DECRBY key decrement key 所储存的值减去给定的减量值(decrement) |
DECRBY xiaoma8 3 |
16 | APPEND key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾 |
APPEND xiaoma8 hello |
2.2 Hash
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Redis 中每个 hash 可以存储 232- 1 键值对(40多亿)
序号 | 命令及描述 | 示例 |
---|---|---|
1 | HSET key field value 将哈希表 key 中的字段 field 的值设为 value |
HSET key1 field1 value1 |
2 | HSETNX key field value 只有在字段 field 不存在时,设置哈希表字段的值 |
HSETNX key1 field2 value2 |
3 | HMSET key field1 value1 [field2 value2 ] 同时将多个 field-value (域-值)对设置到哈希表 key 中 |
HMSET key1 field3 value3 field4 value4 |
4 | HEXISTS key field 查看哈希表 key 中,指定的字段是否存在 |
HEXISTS key1 field4 HEXISTS key1 field6 |
5 | HGET key field 获取存储在哈希表中指定字段的值 |
HGET key1 field4 |
6 | HGETALL key 获取在哈希表中指定 key 的所有字段和值 |
HGETALL key1 |
7 | HKEYS key 获取所有哈希表中的字段 |
HKEYS key1 |
8 | HLEN key 获取哈希表中字段的数量 |
HLEN key1 |
9 | HMGET key field1 [field2] 获取所有给定字段的值 |
HMGET key1 field3 field4 |
10 | HINCRBY key field increment 为哈希表 key 中的指定字段的整数值加上增量 increment |
HSET key2 field1 1 HINCRBY key2 field1 1 HGET key2 field1 |
11 | HINCRBYFLOAT key field increment 为哈希表 key 中的指定字段的浮点数值加上增量 increment |
HINCRBYFLOAT key2 field1 0.8 |
12 | HVALS key 获取哈希表中所有值 |
HVALS key1 |
13 | HDEL key field1 [field2] 删除一个或多个哈希表字段 |
HDEL key1 field3 HVALS key1 |
2.3 List
list列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232- 1 个元素 (4294967295, 每个列表超过40亿个元素)。
序号 | 命令及描述 | 示例 |
---|---|---|
1 | LPUSH key value1 [value2] 将一个或多个值插入到列表头部 |
LPUSH list1 value1 value2 |
2 | LRANGE key start stop 查看list当中所有的数据 |
LRANGE list1 0 -1 |
3 | LPUSHX key value 将一个值插入到已存在的列表头部 |
LPUSHX list1 value3 |
4 | RPUSH key value1 [value2] 在列表中添加一个或多个值到尾部 |
RPUSH list1 value4 value5 LRANGE list1 0 -1 |
5 | RPUSHX key value 为已存在的列表添加单个值到尾部 |
RPUSHX list1 value6 |
6 | LINSERT key BEFORE 或 AFTER pivot value 在列表的元素前或者后插入元素 |
LINSERT list1 BEFORE value3 beforevalue3 |
7 | LINDEX key index 通过索引获取列表中的元素 |
LINDEX list1 0 |
8 | LSET key index value 通过索引设置列表元素的值 |
LSET list1 0 hello |
9 | LLEN key 获取列表长度 |
LLEN list1 |
10 | LPOP key 移出并获取列表的第一个元素 |
LPOP list1 |
11 | RPOP key 移除列表的最后一个元素,返回值为移除的元素 |
RPOP list1 |
12 | BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
BLPOP list1 2000 |
13 | BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
BRPOP list1 2000 |
14 | RPOPLPUSH source destination 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
RPOPLPUSH list1 list2 |
15 | BRPOPLPUSH source destination timeout 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止 |
BRPOPLPUSH list1 list2 2000 |
16 | LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 |
LTRIM list1 0 2 |
17 | DEL key1 key2 删除指定key的列表 |
DEL list2 |
2.4 Set
Redis 的 Set 是
String 类型的无序集合
。集合成员是唯一的
,这就意味着集合中不能出现重复的数据
Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)[最低时空复杂度,耗时与输入数据大小无关]。
集合中最大的成员数为 232- 1 (4294967295, 每个集合可存储40多亿个成员)。
序号 | 命令及描述 | 示例 |
---|---|---|
1 | SADD key member1 [member2] 向集合添加一个或多个成员 |
SADD set1 setvalue1 setvalue2 |
2 | SMEMBERS key 返回集合中的所有成员 |
SMEMBERS set1 |
3 | SCARD key 获取集合的成员数量 |
SCARD set1 |
4 | SDIFF key1 [key2] 返回给定所有集合的差集 |
SADD set2 setvalue2 setvalue3 SDIFF set1 set2 |
5 | SDIFFSTORE destination key1 [key2] 返回给定所有集合的差集并存储在 destination 中 |
SDIFFSTORE set3 set1 set2 |
6 | SINTER key1 [key2] 返回给定所有集合的交集 |
SINTER set1 set2 |
7 | SINTERSTORE destination key1 [key2] 返回给定所有集合的交集并存储在 destination 中 |
SINTERSTORE set4 set1 set2 |
8 | SISMEMBER key member 判断 member 元素是否是集合 key 的成员 |
SISMEMBER set1 setvalue1 |
9 | SMOVE source destination member 将 member 元素从 source 集合移动到 destination 集合 |
SMOVE set1 set2 setvalue1 |
10 | SPOP key 移除并返回集合中的一个随机元素 |
SPOP set2 |
11 | SRANDMEMBER key [count] 返回集合中一个或多个随机数 |
SRANDMEMBER set2 2 |
12 | SREM key member1 [member2] 移除集合中一个或多个成员 |
SREM set2 setvalue1 |
13 | SUNION key1 [key2] 返回所有给定集合的并集 |
SUNION set1 set2 |
14 | SUNIONSTORE destination key1 [key2] 所有给定集合的并集存储在 destination 集合中 |
SUNIONSTORE set5 set1 set2 |
2.5 ZSet
Redis
有序集合
和集合一样也是string类型元素的集合,且不允许重复
的成员
它用来
保存需要排序的数据
,例如排行榜,一个班的语文成绩,一个公司的员工工资,一个论坛的帖子等。
有序集合中,每个元素都带有score(权重),以此来对元素进行排序
它有三个元素:key、member和score。以语文成绩为例,key是考试名称(期中考试、期末考试等),member是学生名字,score是成绩。
序号 | 命令及描述 | 示例 |
---|---|---|
1 | ZADD key score1 member1 [score2 member2] 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
向ZSet中添加页面的PV值ZADD pv_zset 120 page1.html 100 page2.html 140 page3.html |
2 | ZCARD key 获取有序集合的成员数 |
获取所有的统计PV页面数量ZCARD pv_zset |
3 | ZCOUNT key min max 计算在有序集合中指定区间分数的成员数 |
获取PV在120-140在之间的页面数量ZCOUNT pv_zset 120 140 |
4 | ZINCRBY key increment member 有序集合中对指定成员的分数加上增量 increment |
给page1.html的PV值+1ZINCRBY pv_zset 1 page1.html |
5 | ZINTERSTORE destination numkeys key [key ...] 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
创建两个保存PV的ZSET:ZADD pv_zset1 10 page1.html 20 page2.html ZADD pv_zset2 5 page1.html 10 page2.html ZINTERSTORE pv_zset_result 2 pv_zset1 pv_zset2 |
6 | ZRANGE key start stop [WITHSCORES] 通过索引区间返回有序集合指定区间内的成员 |
获取所有的元素,并可以返回每个key对一个的scoreZRANGE pv_zset_result 0 -1 WITHSCORES |
7 | ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 通过分数返回有序集合指定区间内的成员 |
获取ZSET中120-140之间的所有元素ZRANGEBYSCORE pv_zset 120 140 |
8 | ZRANK key member 返回有序集合中指定成员的索引 |
获取page1.html的pv排名(升序)ZRANK pv_zset page3.html |
9 | ZREM key member [member ...] 移除有序集合中的一个或多个成员 |
移除page1.htmlZREM pv_zset page1.html |
10 | ZREVRANGE key start stop [WITHSCORES] 返回有序集中指定区间内的成员,通过索引,分数从高到低 |
按照PV降序获取页面ZREVRANGE pv_zset 0 -1 |
11 | ZREVRANK key member 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
获取page2.html的pv排名(降序)ZREVRANK pv_zset page2.html |
12 | ZSCORE key member 返回有序集中,成员的分数值 |
获取page3.html的分数值ZSCORE pv_zset page3.html |
2.6 Key操作
序号 | 命令及描述 | 示例 |
---|---|---|
1 | DEL key 该命令用于在 key 存在时删除 key |
del xiaoma5 |
2 | DUMP key 序列化给定 key ,并返回被序列化的值 |
DUMP key1 |
3 | EXISTS key 检查给定 key 是否存在 |
exists xiaoma |
4 | EXPIRE key seconds 为给定 key 设置过期时间,以秒计 |
expire xiaoma 5 |
5 | PEXPIRE key milliseconds 设置 key 的过期时间以毫秒计 |
PEXPIRE set3 3000 |
6 | KEYS pattern 查找所有符合给定模式( pattern)的 key |
keys * |
7 | PERSIST key 移除 key 的过期时间,key 将持久保持 |
persist set2 |
8 | PTTL key 以毫秒为单位返回 key 的剩余的过期时间 |
pttl set2 |
9 | TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live) |
ttl set2 |
10 | RANDOMKEY 从当前数据库中随机返回一个 key |
randomkey |
11 | RENAME key newkey 修改 key 的名称 |
rename set5 set8 |
12 | RENAMENX key newkey 仅当 newkey 不存在时,将 key 改名为 newkey |
renamenx set8 set10 |
13 | TYPE key 返回 key 所储存的值的类型 |
type set10 |
2.7 BitMaps位图
计算机最小的存储单位是位bit,Bitmaps是针对位的操作的,相较于String、Hash、Set等存储方式
更加节省空间
Bitmaps不是一种数据结构,操作是基于String结构的,一个String最大可以存储512M,那么一个Bitmaps则可以设置2^32个位
Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个
存储0、1值的数组
,数组的每个单元值只能存储0和1
,数组的下标在Bitmaps中叫做偏移量
BitMaps 命令说明:将每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id 。
2.7.1 设置值
1 |
|
setbit命令设置的vlaue只能是
0
或1
两个值
设置键的第offset个位的值(从0算起),假设现在有20个用户,uid=0,5,11,15,19的用户对网站进行了访问, 那么当前Bitmaps初始化结果如图所示
具体操作过程如下, unique:users:2016-04-05代表2016-04-05这天的独立访问用户的Bitmaps
1 |
|
很多应用的用户id以一个指定数字(例如10000) 开头, 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字。
在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞。
2.7.2 获取值
1 |
|
获取键的第offset位的值(从0开始算),例:下面操作获取id=8的用户是否在2016-04-05这天访问过, 返回0说明没有访问过
1 |
|
2.7.3 获取Bitmaps指定范围值为1的个数
1 |
|
例:下面操作计算2016-04-05这天的独立访问用户数量:
1 |
|
2.7.4 Bitmaps间的运算
1 |
|
bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。 假设2016-04-04访问网站的userid=1, 2, 5, 9, 如图3-13所示:
1 |
|
例1:下面操作计算出2016-04-04和2016-04-05两天都访问过网站的用户数量, 如下所示。
1 |
|
例2:如果想算出2016-04-04和2016-04-03任意一天都访问过网站的用户数量(例如月活跃就是类似这种) , 可以使用or求并集, 具体命令如下:
1 |
|
2.8 HyperLogLog结构
2.8.1 应用场景
HyperLogLog常用于
大数据量
的去重
统计,比如页面访问量统计或者用户访问量统计。
要统计一个页面的访问量(PV),可以直接用redis计数器或者直接存数据库都可以实现,如果要统计一个页面的用户访问量(UV),一个用户一天内如果访问多次的话,也只能算一次,这样,我们可以使用SET集合
来做,因为SET集合是有去重
功能的,key存储页面对应的关键字,value存储对应的userid,这种方法是可行的。但如果访问量较多,假如有几千万的访问量,这就麻烦了。为了统计访问量,要频繁创建SET集合对象。
Redis实现HyperLogLog算法,HyperLogLog 这个数据结构的发明人 是Philippe Flajolet(菲利普·弗拉若莱)教授。Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
2.8.2 UV计算示例
1 |
|
Redis集成的HyperLogLog使用语法主要有pfadd和pfcount,顾名思义,一个是来添加数据,一个是来统计的。为什么用
pf
?是因为HyperLogLog 这个数据结构的发明人 是Philippe Flajolet教授 ,所以用发明人的英文缩写,这样容易记住这个语法了。
下面我们通过一个示例,来演示如何计算uv。
1 |
|
HyperLogLog算法一开始就是为了大数据量的统计而发明的,所以很适合那种数据量很大,然后又没要求不能有一点误差的计算,HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,不过这对于页面用户访问量是没影响的,因为这种统计可能是访问量非常巨大,但是又没必要做到绝对准确,访问量对准确率要求没那么高,但是性能存储方面要求就比较高了,而HyperLogLog正好符合这种要求,不会占用太多存储空间,同时性能不错
pfadd
和pfcount
常用于统计,需求:假如两个页面很相近,现在想统计这两个页面的用户访问量呢?这里就可以用pfmerge
合并统计了,语法如例子:
1 |
|
2.8.3 HyperLogLog为什么适合做大量数据的统计
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
什么是基数?
比如:数据集{1, 3, 5, 7, 5, 7, 8},那么这个数据集的基数集{1, 3, 5, 7, 8},基数(不重复元素)为5。基数估计就是在误差可接受的范围内,快速计算基数。
3 Redis的持久化
由于redis是一个内存数据库,所有的数据都是保存在内存当中的,内存当中的数据极易丢失,所以redis的数据持久化就显得尤为重要,在redis当中,提供了两种数据持久化的方式,分别为RDB以及AOF,且Redis默认开启的数据持久化方式为RDB方式。
3.1 RDB持久化方案
3.1.1 介绍
Redis
会定期保存数据快照至一个rbd
文件中,并在启动时自动加载rdb
文件,恢复之前保存的数据。可以在配置文件中配置Redis
进行快照保存的时机:
1 |
|
意为在
seconds
秒内如果发生了changes
次数据修改,则进行一次RDB
快照保存,例如
1 |
|
会让
Redis
每60秒
检查一次数据变更情况,如果发生了100次
或以上的数据变更,则进行RDB
快照保存。可以配置多条save
指令,让Redis
执行多级的快照保存策略。Redis
默认开启RDB
快照。也可以通过SAVE
或者BGSAVE
命令手动触发RDB快照保存。SAVE
和BGSAVE
两个命令都会调用rdbSave
函数,但它们调用的方式各有不同:
SAVE
直接调用rdbSave
,阻塞Redis
主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
BGSAVE
则fork
出一个子进程,子进程负责调用rdbSave
,并在保存完成之后向主进程发送信号,通知保存已完成。Redis
服务器在BGSAVE
执行期间仍然可以继续处理客户端的请求。
3.1.2 RDB方案优点
对性能影响最小。如前文所述,Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率。
每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段。
使用RDB文件进行数据恢复比使用AOF要快很多
3.1.3 RBD方案缺点
快照是定期生成的,所以在Redis crash时或多或少会丢失一部分数据
如果数据集非常大且CPU不够强(比如单核CPU),Redis在fork子进程时可能会消耗相对较长的时间,影响Redis对外提供服务的能力
3.1.4 RDB配置
修改redis的配置文件
1 |
|
这三个选项是redis的配置文件默认自带的存储机制。表示每隔多少秒,有多少个key发生变化就生成一份dump.rdb文件,作为redis的快照文件
例如:save 60 10000 表示在60秒内,有10000个key发生变化,就会生成一份redis的快照
重新启动redis服务
每次生成新的dump.rdb都会覆盖掉之前的老的快照
1 |
|
3.2 AOF持久化方案
3.2.1 介绍
采用
AOF
持久方式时,Redis
会把每一个写请求都记录在一个日志文件里。在Redis
重启时,会把AOF
文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新。
3.2.2 开启AOF
AOF默认是关闭的,如要开启,进行如下配置:
1 |
|
3.2.3 配置AOF
AOF提供了三种fsync配置:always/everysec/no,通过配置项[appendfsync]指定:
1.
appendfsync no
:不进行fsync,将flush文件的时机交给OS决定,速度最快
2.appendfsync always
:每写入一条日志就进行一次fsync操作,数据安全性最高,但速度最慢
3.appendfsync everysec
:折中的做法,交由后台线程每秒fsync一次
3.2.4 AOF rewrite
随着AOF不断地记录写操作日志,因为所有的写操作都会记录,所以必定会出现一些无用的日志。大量无用的日志会让AOF文件过大,也会让数据恢复的时间过长。不过Redis提供了AOF rewrite功能,可以重写AOF文件,只保留能够把数据恢复到最新状态的最小写操作集。
AOF rewrite可以通过BGREWRITEAOF命令触发,也可以配置Redis定期自动进行:
1 |
|
Redis在每次AOF rewrite时,会记录完成rewrite后的AOF日志大小,当AOF日志大小在该基础上增长了100%后,自动进行AOF rewrite
auto-aof-rewrite-min-size最开始的AOF文件必须要触发这个文件才触发,后面的每次重写就不会根据这个变量了。该变量仅初始化启动Redis有效。
3.2.5 AOF优点
最安全,在启用appendfsync为always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据
AOF文件在发生断电等问题时也不会损坏,即使出现了某条日志只写入了一半的情况,也可以使用redis-check-aof工具轻松修复
AOF文件易读,可修改,在进行某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
3.2.6 AOF缺点
1.AOF文件通常比RDB文件更大
2.性能消耗比RDB高
3.数据恢复速度比RDB慢
Redis的数据持久化工作本身就会带来延迟,需要根据数据的安全级别和性能要求制定合理的持久化策略:
AOF + fsync always
的设置虽然能够绝对确保数据安全,但每个操作都会触发一次fsync,会对Redis的性能有比较明显的影响AOF + fsync every second
是比较好的折中方案,每秒fsync一次AOF + fsync never
会提供AOF持久化方案下的最优性能
使用RDB持久化通常会提供比使用AOF更高的性能,但需要注意RDB的策略配置
3.3 RDB or AOF
每一次RDB快照和AOF Rewrite都需要Redis主进程进行fork操作。fork操作本身可能会产生较高的耗时,与CPU和Redis占用的内存大小有关。根据具体的情况合理配置RDB快照和AOF Rewrite时机,避免过于频繁的fork带来的延迟
Redis在fork子进程时需要将内存分页表拷贝至子进程,以占用了24GB内存的Redis实例为例,共需要拷贝48MB的数据。在使用单Xeon 2.27Ghz的物理机上,这一fork操作耗时216ms。
4 Redis 高级使用
4.1 Redis事务
4.1.1 Redis事务简介
Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
总结说:Redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令
Redis事务没有隔离级别的概念
批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。
Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
一个事务从开始到执行会经历以下三个阶段:第一阶段:开始事务
第二阶段:命令入队
第三阶段、执行事务。
Redis事务相关命令:
1 |
|
4.1.2 Redis事务演示
MULTI开始一个事务:给k1、k2分别赋值,在事务中修改k1、k2,执行事务后,查看k1、k2值都被修改。
1 |
|
事务失败处理:语法错误(编译器错误),在开启事务后,修改k1值为11,k2值为22,但k2语法错误,最终导致事务提交失败,k1、k2保留原值。
1 |
|
Redis类型错误(运行时错误),在开启事务后,修改k1值为11,k2值为22,但将k2的类型作为List,在运行时检测类型错误,最终导致事务提交失败,此时事务并没有回滚,而是跳过错误命令继续执行, 结果k1值改变、k2保留原值。
1 |
|
DISCARD取消事务
1 |
|
4.1.3 为什么Rdis不支持事务回滚
多数事务失败是由语法错误或者数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。Redis之所以保持这样简易的事务,完全是为了保证高并发下的核心问题——
性能
。
4.2 Redis过期策略
Redis是key-value数据库,可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种:
定时过期
每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,
对内存很友好
;但是会占用大量的CPU资源
去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期
只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以
最大化地节省CPU资源,却对内存非常不友好
。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期
每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
4.3 内存淘汰策略
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据,在Redis的配置文件中描述如下:
1 |
|
实际项目中设置内存淘汰策略:maxmemory-policy allkeys-lru,移除最近最少使用的key。
5 Redis的主从复制架构
5.1 简介
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为
主节点(master)
,后者称为从节点(slave)
,数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
5.1.1 一主一从
如下图所示左边是Master节点,右边是slave节点,即主节点和从节点。从节点也是可以对外提供服务的,主节点是有数据的,从节点可以通过复制操作将主节点的数据同步过来,并且随着主节点数据不断写入,从节点数据也会做同步的更新。
从节点起到的就是数据备份的效果。
5.1.2 一主多从
除了一主一从模型之外,Redis还提供了一主多从的模型,也就是一个master可以有多个slave,也就相当于有了多份的数据副本。
可以做一个更加高可用的选择,例如一个master和一个slave挂掉了,还能有其他的slave数据备份。
5.2 主从复制原理
- 当从数据库启动后,会向主数据库发送SYNC命令
- 主数据库接收到SYNC命令后开始在后台保存快照(RDB持久化),并将保存快照期间接收到的命令缓存再来
- 快照完成后,Redis(Master)将快照文件和所有缓存的命令发送给从数据库
- Redis(Slave)接收到RDB和缓存命令时,会开始载入快照文件并执行接收到的缓存的命令
- 后续,每当主数据库接收到写命令时,就会将命令同步给从数据库。所以3和4只会在初始化的时候执行
5.3 主从复制的应用的场景
5.3.1 读写分离
- 通过主从复制可以实现读写分离,以提高服务器的负载能力
- 在常见的场景中(例如:电商网站),读的频率大于写
- 当单机Redis无法应付大量的读请求时(尤其是消耗资源的请求),就可以通过主从复制功能来建立多个从数据库节点,主数据库只进行写操作,从数据库负责读操作
- 这种主从复制,比较适合用来处理读多写少的场景,而当单个主数据库不能满足需求时,就需要使用Redis 3.0后推出的集群功能
5.3.2 从数据库持久化
- Redis中相对耗时的操作就是持久化,为了提高性能,可以通过主从复制创建一个或多个从数据库,并在从数据库中启用持久化,同时在主数据库中禁用持久化(例如:禁用AOF)
- 当从数据库崩溃重启后主数据库会自动将数据同步过来,无需担心数据丢失
- 而当主数据库崩溃时,后续我们可以通过哨兵(Sentinel)来解决
5.4 另外两台服务器安装Redis
5.4.1 安装Redis依赖环境
5.4.2 上传Redis压缩包
5.4.3 服务器安装tcl
5.4.4 编译redis
5.4.5 修改redis配置文件
5.4.5.1 node2.itast.cn服务器修改配置文件
执行以下命令修改Redis配置文件
1 |
|
5.4.5.2 node3.xiaoma.cn服务器修改配置文件
执行以下命令修改redis配置文件
1 |
|
5.5 启动Redis服务
node2.xiaoma.cn执行以下命令启动Redis服务
1 |
|
node3.xiaoma.cn执行以下命令启动Redis服务
1 |
|
启动成功便可以实现redis的主从复制,node1.xiaoma.cn可以读写操作,node2.xiaoma.cn与node3.xiaoma.cn只支持读取操作。
6 Redis中的Sentinel架构
6.1 Sentinel介绍
Sentinel(哨兵)是Redis的高可用性解决方案:由一个或多个Sentinel实例 组成的Sentinel系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。
例如:
在Server1 掉线后:
升级Server2 为新的主服务器:
6.2 配置哨兵
6.2.1 三台机器修改哨兵配置文件
三台机器执行以下命令修改redis的哨兵配置文件
1 |
|
配置监听的主服务器
1.修改node1.xiaoma.cn的sentinel.conf文件
1 |
|
参数说明
- sentinel monitor代表监控
- mymaster代表服务器的名称,可以自定义
- node1.xiaoma.cn代表监控的主服务器,6379代表端口
- 2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
如果Redis是有密码的,需要指定密码
1 |
|
2.分发到node2.xiaoma.cn和node3.xiaoma.cn
1 |
|
3.分别修改配置中bind的服务器主机名
node2.xiaoma.cn
1 |
|
node3.xiaoma.cn
1 |
|
6.2.2 三台机器启动哨兵服务
1 |
|
三台服务器的进程信息:
1 |
|
6.2.3 node1服务器杀死redis服务进程
1 |
|
使用kill -9命令杀死redis服务进程,模拟redis故障宕机情况
过一段时间之后,就会在node2.xiaoma.cn与node3.xiaoma.cn服务器选择一台服务器来切换为主节点
1 |
|
6.3 Redis的sentinel模式代码开发连接
通过哨兵连接,要指定哨兵的地址,并使用JedisSentinelPool来创建连接池。
实现步骤:
1.在 cn.xiaoma.redis.api_test 包下创建一个新的类 ReidsSentinelTest
2.构建JedisPoolConfig配置对象
3.创建一个HashSet,用来保存哨兵节点配置信息(记得一定要写端口号)
4.构建JedisSentinelPool连接池
5.使用sentinelPool连接池获取连接
1 |
|
7 Redis 集群
Redis最开始使用
主从模式做集群
,若master宕机需要手动配置slave转为master;后来为了高可用提出来哨兵模式
,该模式下有一个哨兵监视master和slave,若master宕机可自动将slave转为master,但它也有一个问题,就是不能动态扩充
;所以在Redis 3.x提出cluster集群
模式。
7.1 引言
Redis Cluster是Redis官方提供的Redis集群功能,为什么要实现Redis Cluster?
- 主从复制不能实现高可用
- 随着公司发展,用户数量增多,并发越来越多,业务需要更高的QPS,而主从复制中单机的QPS可能无法满足业务需求;
- 数据量的考虑,现有服务器内存不能满足业务数据的需要时,单纯向服务器添加内存不能达到要求,此时需要考虑分布式需求,把数据分布到不同服务器上;
- 网络流量需求,业务的流量已经超过服务器的网卡的上限值,可考虑使用分布式来进行分流;
- 离线计算,需要中间环节缓冲等其他需求;
在存储引擎框架(MySQL、HDFS、HBase、Redis、Elasticsearch等)中,只要数据量很大时,单机无法承受压力,最好的方式就是:数据分布进行存储管理。
对Redis 内存数据库来说:全量数据,单机Redis节点无法满足要求,按照分区规则把数据分到若干个子集当中。
Redis集群数据分布方式:
虚拟槽分区
:虚拟槽分区是Redis Cluster采用的分区方式,预设虚拟槽,每个槽就相当于一个数字,有一定范围。每个槽映射一个数据子集,一般比节点数大。Redis Cluster中预设虚拟槽的范围为0到16383。
7.2 Redis Cluster 设计
Redis Cluster是分布式架构,有多个节点,每个节点都负责进行数据读写操作,每个节点之间会进行通信。Redis Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
结构特点:
- 所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽;
- 节点的fail是通过集群中超过半数的节点检测失效时才生效;
- 客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;
- redis-cluster 把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster 负责维护node<->slot<->value;
- Redis集群预分好16384个桶(Slot),当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) & 16384的值,决定将一个key放到哪个桶中;
Redis 集群的优势:
- 缓存永不宕机:启动集群,永远让集群的一部分起作用。主节点失效了子节点能迅速改变角色成为主节点,整个集群的部分节点失败或者不可达的情况下能够继续处理命令;
- 迅速恢复数据:持久化数据,能在宕机后迅速解决数据丢失的问题;
- Redis可以使用所有机器的内存,变相扩展性能;
- 使Redis的计算能力通过简单地增加服务器得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长;
- Redis集群没有中心节点,不会因为某个节点成为整个集群的性能瓶颈;
- 异步处理数据,实现快速读写;
Redis 3.0以后,节点之间通过去中心化的方式提供了完整的sharding(数据分片)、replication(复制机制、Cluster具备感知准备的能力)、failover解决方案。
7.3 Redis Cluster 搭建
Redis3.0及以上版本实现,集群中至少应该有奇数个节点,所以至少有三个节点,官方推荐
三主三从
的配置方式。Redis 3.x和Redis4.x 搭建集群是需要手动安装ruby
组件的,比较麻烦。
2018年十月 Redis 发布了稳定版本的 5.0 版本,推出了各种新特性,其中一点是放弃 Ruby的集群方式,改为 使用 C语言编写的redis-cli的方式,是集群的构建方式复杂度大大降低。
Redis cluster tutorial
:https://redis.io/topics/cluster-tutorial 。
基于Redis-5.0.8版本,在三台机器上搭建6个节点的Redis集群:三主三从架构。
7.3.1 环境准备
关闭以前Redis主从复制和哨兵模式监控的所有服务,备注:如果以前没有安装过Redis服务,不用执行此步骤操作。
1 |
|
安装Redis编译环境:GCC和TCL。
1 |
|
7.3.2 上传和解压
将Redis-5.0.8软件安装包上传至 /export/software 目录,并解压与安装。
1 |
|
7.3.3 编译安装
编译Redis 源码,并安装至【/export/server/redis-5.0.8-bin】目录。
1 |
|
配置环境变量(如果以前安装过Redis,配置过环境变量,就不用配置)。
1 |
|
7.3.4 拷贝配置文件
从Redis-5.0.8源码目录下拷贝配置文件:redis.conf至Redis 安装目录。
1 |
|
7.3.5 修改配置文件
每台机器上启动2个Redis服务,一个主节点服务:7001,一个从节点服务:7002,如下图所示:
在Redis安装目录下创建7001和7002目录,分别存储Redis服务配置文件、日志及数据文件。
1 |
|
拷贝配置文件:redis.conf至7001目录,并重命名为redis_7001.conf。
1 |
|
编辑配置文件:redis_7001.conf,内容如下:
1 |
|
创建日志目录和数据目录:
1 |
|
配置7002端口号启动Redis服务,操作命令如下:
1 |
|
7.3.6 发送安装包
将node1.xiaoma.cn上配置好的Redis安装包,发送至node2.xiaoma.cn和node3.xiaoma.cn,每台机器运行2个Redis服务,端口号分别为7001和7002,具体命令如下:
1 |
|
7.4 启动Redis服务
在三台机器node01、node02和node03,分别启动6个Redis服务,命令如下:
1 |
|
Redis服务启动完成以后,查看如下:
7.4.1 启动集群
Redis5.x版本之后,通过redis-cli客户端命令来进行创建集群,注意:Redis对主机名解析不友好,使用IP地址。
1 |
|
启动集群日志信息如下:
1 |
|
7.4.2 启动关闭集群
编写脚本,方便启动和关闭Redis集群:redis-cluster-start.sh和redis-cluster-stop.sh。
- 进入Redis安装目录中bin目录,创建脚本文件
1 |
|
- 启动集群:redis-cluster-start.sh
1 |
|
- 关闭集群:redis-cluster-stop.sh
1 |
|
7.4.3 测试集群
在任意一台机器,使用redis-cli客户端命令连接Redis服务:
1 |
|
输入命令:cluster nodes(查看集群信息)和info replication(主从信息):
1 |
|
测试数据,设置Key值和查询Key的值。
1 |
|
7.4.4 主从切换
测试Redis Cluster中主从服务切换,首先查看集群各个服务状态:
在node3上将7001端口Redis 服务关掉:SHUTDOWN
../redis-cli -h node3.xiaoma.cn -p 7001 SHUTDOWN
1 |
|
再次查看集群状态信息:
重新启动node03上7001端口Redis服务,查看集群状态信息
1 |
|
7.5 Redis Cluster 管理
redis-cli集群命令帮助:
1 |
|
在实际项目中可能由于Redis Cluster中节点宕机或者增加新节点,需要操作命令管理,主要操作如下。
7.6 JavaAPI操作redis集群
连接Redis集群,需要使用JedisCluster来获取Redis连接。
实现步骤:
- 在cn.xiaoma.redis.api_test包下创建一个新的类:RedisClusterTest
- 创建一个HashSet
,用于保存集群中所有节点的机器名和端口号 - 创建JedisPoolConfig对象,用于配置Redis连接池配置
- 创建JedisCluster对象
- 使用JedisCluster对象设置一个key,然后获取key对应的值
1 |
|
8 Redis面试题
在应用程序和MySQL数据库中建立一个中间层:Redis缓存,通过Redis缓存可以有效减少查询数据库的时间消耗,但是引入redis又有可能出现
缓存穿透
、缓存击穿
、缓存雪崩
等问题。
8.1 缓存穿透
缓存穿透
:key对应的数据在数据源并不存在
,每次针对此key的请求从缓存获取不到
,请求都会到数据源,从而可能压垮数据源。一言以蔽之:查询Key,缓存和数据源都没有,频繁查询数据源
比如用一个不存在的用户id获取用户信息,无论论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
- 如何解决缓存穿透:当查询不存在时,也将结果保存在缓存中。[PS:布隆过滤器虽快,但不能准确判断key值是否已存在,不推荐]
8.2 缓存击穿
缓存击穿
:key对应的数据库存在
,但在redis中过期
,此时若有大量并发请求
过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
一言以蔽之:查询Key,缓存过期,大量并发,频繁查询数据源
业界比较常用的做法:使用互斥锁
。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db(查询数据库),而是先使用Redis的SETNX操作去set一个mutex key【此key作为互斥锁,在指定的 key 不存在时,为key设置指定的值,返回1;key存在时返回0
】,只让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据。
1 |
|
8.3 缓存雪崩
缓存雪崩
:当缓存服务器重启
或者大量缓存集中在某一个时间段失效
,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
一言以蔽之:缓存不可用(服务器重启或缓存失效),频繁查询数据源
与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key。
缓存正常从Redis中获取,示意图如下:
缓存失效瞬间示意图如下:
缓存失效时的雪崩效应对底层系统的冲击非常可怕!
大多数系统设计者考虑用
加锁或者队列
的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。
还有一个简单方案就时
将缓存失效时间分散开
,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
8.4 Redis的命名规范是?
使用统一的命名规范
- 一般使用业务名(或数据库名)为前缀,用冒号分隔,例如,业务名:表名:id。
- 例如:shop:usr:msg_code(电商:用户:验证码)
控制key名称的长度,不要使用过长的key
- 在保证语义清晰的情况下,尽量减少Key的长度。有些常用单词可使用缩写,例如,user缩写为u,messages缩写为msg。
名称中不要包含特殊字符
- 包含空格、单双引号以及其他转义字符
8.5 集群
问题一:Redis的多数据库机制,了解多少
问题二:懂Redis的批量操作么?
问题三:Redis集群机制中,你觉得有什么不足的地方吗?
问题四:在Redis集群模式下,如何进行批量操作?
问题五:懂Redis事务么?
关注博主不迷路
本博客所有文章除特别声明外,均为原创。版权归博主小马所有。任何团体、机构、媒体、网站、公众号及个人不得转载。如需转载,请联系博主(关于页面)。如其他团体、机构、媒体、网站、博客或个人未经博主允许擅自转载使用,请自负版权等法律责任!