简介

非关系型(NoSQL)内存键值数据库,使用C语言实现,可以存储键和五种不同类型的值之间的映射。
Redis数据库保存的的类型只能为字符串对象,而可以是字符串对象、列表对象、集合对象、散列对象、有序集合对象的其中一种。

数据类型

数据结构

跳表

跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。

整数集合

整数集合是集合键的底层实现之一,当一个集合只包含整数元素并且元素数量不多时,redis就会使用整数集合作为底层实现。

struct intset{
	//编码方式
	uint32_t encoding;
	//元素数量
	uint32_t length;
	//保存元素数组
	int8_t contents[];
}

string

struct sdshdr{
	//记录buf数组中已经使用字节的数量
	//等于SDS所保存字符串的长度
	int len;

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

	//字节组数,用于保存字符串
	char buf[]
}
  1. 常用命令: set,get,strlen,exists,decr,incr,setex
  2. 应用场景: 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等
  3. 结构优点: 常数复杂度获取字符串长度,杜绝缓冲区溢出,减少修改字符串时带来的内存重分配次数
//普通字符串的基本操作:
127.0.0.1:6379> set key value #设置 key-value 类型的值
OK
127.0.0.1:6379> get key # 根据 key 获得对应的 value
"value"
127.0.0.1:6379> exists key  # 判断某个 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key # 返回 key 所储存的字符串值的长度。
(integer) 5
127.0.0.1:6379> del key # 删除某个 key 对应的值
(integer) 1
127.0.0.1:6379> get key
(nil)
//向字符串的后面追加字符,如果有就补在后面,如果没有就新建
127.0.0.1:6379> append key value

//批量设置 
127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
OK
127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
1) "value1"
2) "value2"

//增长指令,只有当value为数字时才能增长
incr key  
incrby key increment  
incrbyfloat key increment 

//减少指令,有当value为数字时才能减少
decr key  
decrby key incremen
  • string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
  • redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。
  • 注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错。 9223372036854775807(java中long型数据最大值,Long.MAX_VALUE)

指定生命周期

//设置数据的生命周期,单位 秒
setex key seconds value
//设置数据的生命周期,单位 毫秒
psetex key milliseconds value

list

  1. 常用命令: rpush,lpop,lpush,rpop,lrange,llen
  2. 应用场景: 发布与订阅或者说消息队列、慢查询、微信文章订阅公众号
  3. 底层使用双向链表存储结构实现
//添加修改数据,lpush为从左边添加,rpush为从右边添加
lpush key value1 value2 value3...
rpush key value1 value2 value3...

//查看数据, 从左边开始向右查看. 如果不知道list有多少个元素,end的值可以为-1,代表倒数第一个元素
//lpush先进的元素放在最后,rpush先进的元素放在最前面
lrange key start end
//得到长度
llen key
//取出对应索引的元素
lindex key index

//获取并移除元素(从list左边或者右边移除)
lpop key
rpop key

//规定时间内获取并移除数据,b=block,给定一个时间,如果在指定时间内放入了元素,就移除
blpop key1 key2... timeout
brpop key1 key2... timeout

//移除指定元素 count:移除的个数 value:移除的值。 移除多个相同元素时,从左边开始移除
lrem key count value

注意事项:

  • list中保存的数据都是string类型的,数据总容量是有限的,最多2^32 - 1 个元素 (4294967295)。
  • list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队(rpush, rpop)操作,或以栈的形式进行入栈出栈(lpush, lpop)操作
  • 获取全部数据操作结束索引设置为-1 (倒数第一个元素)
  • list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载

set(无序且不重复)

  1. 常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion
  2. 应用场景: 微信抽奖小程序、朋友圈点赞、微博好友关注社交关系(set交并集运算)
  3. 与hash存储结构完全相同,仅存储键,不存储值(nil),并且是不允许重复的
//添加元素
sadd key member1 member2...

//查看元素
smembers key

//移除元素
srem key member

//查看元素个数
scard key

//查看某个元素是否存在
sismember key member

//从set中任意选出count个元素
srandmember key count

//从set中任意选出count个元素并移除
spop key count

//求两个集合的交集、并集、差集
sinter key1 key2...
sunion key1 key2...
sdiff key1 key2...

//求两个set的交集、并集、差集,并放入另一个set中
sinterstore destination key1 key2...
sunionstore destination key1 key2...
sdiffstore destination key1 key2...

//求指定元素从原集合放入目标集合中
smove source destination key

hash

  1. 常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals
  2. 应用场景: 系统中对象数据的存储(早期购物车)
  3. 底层使用哈希表结构实现数据存储

Map<string,Map<Object,Object>>

//插入(如果已存在同名的field,会被覆盖)
hset key field value
hmset key field1 value1 field2 value2...
//插入(如果已存在同名的field,不会被覆盖)
hsetnx key field value

//取出
hget key field
hgetall key

//删除
hdel key field1 field2...

//获取field数量
hlen key

//查看是否存在
hexists key field

//获取哈希表中所有的字段名或字段值 
hkeys key
hvals key

//设置指定字段的数值数据增加指定范围的值 
hincrby key field increment 
hdecrby key field increment

hash 类型数据操作的注意事项:

  • hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到, 对应的值为(nil)
  • 每个 hash 可以存储 2^32 - 1 个键值
  • hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
  • hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈

zset

  1. *新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
  2. 常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem
  3. **应用场景:**销量榜、热搜榜
  4. 需要的存储结构:新的存储模型,可以保存可排序的数据
  5. sorted_set类型:在set的存储结构基础上添加可排序字段

zset存储空间.png

//插入元素, 需要指定score(用于排序)
zadd key score1 member1 score2 member2

//查看元素(score升序), 当末尾添加withscore时,会将元素的score一起打印出来
zrange key start end (withscore)
//查看元素(score降序), 当末尾添加withscore时,会将元素的score一起打印出来
zrevrange key start end (withscore)

//移除元素
zrem key member1 member2...

//按条件获取数据, 其中offset为索引开始位置,count为获取的数目
zrangebyscore key min max [withscore] [limit offset count]
zrevrangebyscore key max min [withscore] [limit offset count]

//按条件移除元素
zremrangebyrank key start end
zremrangebysocre key min max
//按照从大到小的顺序移除count个值
zpopmax key [count]
//按照从小到大的顺序移除count个值
zpopmin key [count]

//获得元素个数
zcard key

//获得元素在范围内的个数
zcount min max

//求交集、并集并放入destination中, 其中numkey1为要去交集或并集集合的数目
zinterstore destination numkeys key1 key2...
zunionstore destination numkeys key1 key2...

注意事项:

  • score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
  • score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重
  • sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果

通用指令

Key的操作

//查看key是否存在
exists key

//删除key
del key

//查看key的类型
type key

//设置生命周期
expire key seconds
pexpire key milliseconds

//查看有效时间, 如果有有效时间则返回剩余有效时间, 如果为永久有效,则返回-1, 如果Key不存在则返回-2
ttl key
pttl key

//将有时限的数据设置为永久有效
persist key

//根据key查询符合条件的数据
keys pattern

Jedis

TODO

持久化

利用永久性存储介质将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化。主要是防止数据的意外丢失,确保数据安全性。

快照持久化(RDB)

Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。

快照持久化是 Redis 默认采用的持久化方式

命令:

//save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用
save
//手动启动后台保存操作,但不是立即执行
bgsave

注意:save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用。

RDB配置相关命令

  • dbfilename dump.rdb
    说明:设置本地数据库文件名,默认值为 dump.rdb
    经验:通常设置为dump-端口号.rdb
  • dir
    说明:设置存储.rdb文件的路径
    经验:通常设置成存储空间较大的目录中,目录名称data
  • rdbcompression yes
    说明:设置存储至本地数据库时是否压缩数据,默认为 yes,采用 LZF 压缩
    经验:通常默认为开启状态,如果设置为no,可以节省 CPU 运行时间,但会使存储的文件变大(巨大)
  • rdbchecksum yes
    说明:设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行
    经验:通常默认为开启状态,如果设置为no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险

RDB优缺点

  • 优点
    • RDB是一个紧凑压缩的二进制文件,节省磁盘空间
    • RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
    • RDB恢复数据速度快
    • 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复
  • 缺点
    • RDB方式宕机时可能会丢失最后一次备份后的所有修改
    • 每次运行要执行fork操作创建子进程,要牺牲掉一些性能
    • Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象

AOF持久化

以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令,以达到恢复数据的目的。
AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式(默认不开启)

//是否开启AOF持久化功能,默认为不开启状态
appendonly yes|no
//AOF写数据策略
appendfsync always|everysec|no

AOF写数据三种策略(appendfsync)

  1. always:每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
  2. everysec:每秒钟同步一次,数据准确性较高,性能较高
  3. no:让操作系统决定何时进行同步

AOF重写

AOF文件重写是将redis进程内的数据转化为写命令同步到新AOF文件的过程。简单来说就是将对同一个数据的若干条命令执行结果转化成最终结果数据对应的指令进行记录。

作用:

  • 降低磁盘占用量,提高磁盘利用率
  • 提高持久化效率,降低持久化写时间,提高IO性能
  • 降低数据恢复用时,提高数据恢复效率

规则:

  • 进程内已超时的数据不再写入文件
  • 忽略无效指令,重写时使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令
    • 如del key1、 hdel key2、srem key3、set key4 111、set key4 222等
  • 对同一数据的多条写命令合并为一条命令
    • 如lpush list1 a、lpush list1 b、 lpush list1 c 可以转化为:lpush list1 a b c
    • 为防止数据量过大造成客户端缓冲区溢出,对list、set、hash、zset等类型,每条指令最多写入64个元素

AOF优缺点

  • 优点
    • 备份机制更稳健,丢失数据概率更低
    • 可读的日志文本,通过操作AOF文件,可以处理误操作
  • 缺点
    • 需要占用更多的磁盘空间
    • 恢复备份速度更慢
    • 如果每次读写都同步,会造成一定的性能压力
    • 会存在个别bug

对比

RDB VS AOF.png

  • 对数据非常敏感,建议使用默认的AOF持久化方案
    • AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出现问题时,最多丢失0-1秒内的数据。
    • 注意:由于AOF文件存储体积较大,且恢复速度较慢
  • 数据呈现阶段有效性,建议使用RDB持久化方案
    • 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段点数据恢复通常采用RDB方案
    • 注意:利用RDB实现紧凑的数据持久化会使Redis降的很低
  • 综合比对
    • RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
    • 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
    • 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
    • 灾难恢复选用RDB
    • 双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据

Redis事务

redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次性按照添加顺序依次执行,中间不会被打断或者干扰

事务基本操作

//作设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
multi
//终止当前事务的定义,发生在multi之后,exec之前
discard
//设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
exec

//对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行
watch key1, key2....
//取消对所有key的监视
unwatch

//上锁,利用setnx命令的返回值特征,有值(被上锁了)则返回设置失败,无值(没被上锁)则返回设置成功
setnx lock-key value
//释放锁
del lock-key

//使用 expire 为锁key添加时间限定,到时不释放,放弃锁
expire lock-key seconds
pexpire lock-key milliseconds

内存淘汰机制

常用的过期数据的删除策略:

  1. 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但是可能会造成太多过期 key 没有被删除**
  2. 定期删除 :每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
  3. 定时删除:创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作(不会全部key都检查)。CPU压力很大,用处理器性能换取存储空间
配置文件
//最大可使用内存
maxmemory
//每次选取待删除数据的个数
maxmemory-samples
//达到最大内存后的,对被挑选出来的数据进行删除的策略
maxmemory-policy

数据淘汰策略:

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)

  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

使用INFO命令输出监控信息,查询缓存 hit 和 miss 的次数,根据业务需求调优Redis配置

高级数据类型

Bitmaps

//获取指定key对应偏移量上的bit值
getbit key offset
//设置指定key对应偏移量上的bit值,value只能是1或0
setbit key offset value

//对指定key按位进行交、并、非、异或操作,并将结果保存到destKey中
bitop and destKey key1 [key2...]
/*
and:交
or:并
not:非
xor:异或
*/

//统计指定key中1的数量
bitcount key [start end]

HyperLogLog

HyperLogLog 是用来做基数统计的,运用了LogLog的算法,基数是数据集去重后元素个数

//添加数据
pfadd key element1, element2...
//统计数据
pfcount key1 key2....
//合并数据
pfmerge destkey sourcekey [sourcekey...]
  • 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
  • 核心是基数估算算法,最终数值存在一定误差
  • 误差范围:基数估计的结果是一个带有 0.81% 标准错误的近似值
  • 耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数
  • pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大
  • Pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少

GEO

//添加坐标点
geoadd key longitude latitude member [longitude latitude member ...] 
//获取坐标点
geopos key member [member ...] 
//计算坐标点距离
geodist key member1 member2 [unit]

//根据坐标求范围内的数据
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
//根据点求范围内数据
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
//获取指定点对应的坐标hash值
geohash key member [member ...]

集群

主从复制

主从复制即将master中的数据即时、有效的复制到slave中
主从复制.png

  • 读写分离:master写、slave读,提高服务器的读写负载能力
  • 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
  • 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
  • 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
  • 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案

工作流程

主从复制过程.png

1. 建立连接

建立slave到master的连接,使master能够识别slave,并保存slave端口号
主从复制建立连接.png
slave断开连接后,不会删除已有数据,只是不再接受master发送的数据

2. 数据同步

主从复制数据同步.png

  • 全量复制:将master执行bgsave之前,master中所有的数据同步到slave中
  • 部分复制(增量复制):将master执行bgsave操作过程中,新加入的数据(复制缓冲区中的数据)传给slave,slave通过bgrewriteaof指令来恢复数据

数据同步阶段master说明:

  1. 如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行
  2. 复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态
  3. master单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执 行bgsave命令和创建复制缓冲区

数据同步阶段slave说明:

  1. 为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
  2. 数据同步阶段,master发送给slave信息可以理解master是slave的一个客户端,主动向slave发送命令
  3. 多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果master带宽不足,因此数据同步需要根据业务需求,适量错峰
  4. slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是 slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大,数据一致性变差,应谨慎选择

3. 命令传播

当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播

服务器运行ID:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id
复制缓冲区:是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区

心跳机制

进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线

  • master心跳:
    • 作用:判断slave是否在线
    • 查询:INFO replication 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常
  • slave心跳:
    • 作用:汇报slave自己的复制偏移量,获取最新的数据变更指令;判断master是否在线

当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作

主从复制完整流程.png

哨兵

哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。

作用

  • 监控:不断的检查master和slave是否正常运行。 master存活检测、master与slave运行情况检测
  • 通知:当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知
  • 自动故障转移:断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址

哨兵也是一台redis服务器,只是不提供数据服务 通常哨兵配置数量为单数

使用场景

1. 缓存

2. 分布式锁

3. 消息队列

Q.E.D.