基础
数据库
Redis中的数据库概念与MySQL中类似,但Redis中的数据库不支持自定义数据库的名字,每个数据库以编号命名。Redis不支持为每个数据库设置不同的访问密码,一个客户端可以访问到所有的数据库。所以对于Redis来说数据库更像是一种命名空间,且不适宜存储不同应用程序的数据。
比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据。
Redis默认有16个数据库,可以通过redis.conf文件中的databases进行修改。
客户端与Redis建立连接后默认选择0号数据库,使用select [数据库编号]可以选择使用第几个数据库(集群不支持,集群模式下只有一个db0)。
常用指令
中文官方文档(5.0.4):redis命令手册
- SET、MSET
SET命令用于存储一个或多个字符串值到一个键中。如果该键不存在,则会创建一个新键。
1 | SET key value [EX seconds] [PX milliseconds] [NX|XX] |
key是要存储的键名,value是要存储的值。EX和PX参数可选,用于设置键的过期时间,单位分别为秒和毫秒。NX和XX参数也可选,用于控制键的创建行为,NX表示只在键不存在时创建,XX表示只在键已存在时执行操作。
MSET命令可用于一次设置多个键值对。
1 | MSET key1 value1 [key2 value2 ...] |
- GET、MGET
GET命令用户返回给定key的值。如果key不存在返回null。
1 | MGET key1 [key2 value2 ...] |
- EXPIRE、PEXPIRE
EXPIRE命令用于设置键的过期时间,单位为秒,PEXPIRE类似,单位为毫秒。
1 | EXPIRE key seconds |
key是要设置过期时间的键名,seconds是过期时间,单位为秒。
- SETEX、SETNX、MSETNX
SETEX命令可用于一次设置带有过期时间的键值对。
1 | SETEX key seconds value |
key是要存储的键名,seconds是过期时间,单位为秒,value是要存储的值。
SETNX命令判断key是否存在,不存在才设置。常用来实现分布式锁。
1 | SETNX key value |
MSETNX用来批量设置key,但该操作是原子的,在设置多个键的时候,如果其中一个key存在,那么其他未存在的key也会设置失败。
- KEYS
KEYS 用来查看key是否存在或有哪些key
1 | KEYS keyname/* |
*用来查看所有key
- FLUSHDB、FLUSHALL
FLUSHDB用来清空当前数据库中的所有key。
FLUSHALL用来清空所有数据库中的key。
- MOVE
MOVE用来移除数据库中对应的key和value
1 | MOVE key |
- TTL
TTL用来查看某个key的过期时间。
1 | TTL key |
- EXISTS
EXISTS用来判断某个key是否存在数据库中。
1 | EXISTS key |
如果存在返回1,不存在返回0
- APPEND
APPEND对某个key的value后追加内容。
1 | APPEND key "value" |
如果当前追加的key不存在,相当于set key。APPEND追加成功会返回value的长度。
- STRLEN
STRLEN用来获得某个key对应value的长度。
1 | STRLEN key |
- INCR、DECR、INCRBY、DECRBY
INCR用来使key对应的value值+1,DECR使key对应的value值-1。
1 | INCR key |
INCRBY和DECRBY可以按照指定的步长增加或减少,返回value的长度。
1 | INCRBY key steps |
steps代表增加的步长,返回value的长度。
- GETRANGE、SETRANGE
GETRANGE用来获取一个左右闭合区间内的值,下标从0开始。
1 | GETRANGE key start end |
start代表起始位置,end代表终止位置,注意这个区间是左右闭合,start和end的值都能获取到。end为-1时表示到最后一个字符。
SETRANGE用来替换一个区间内的值。
1 | SETRANGE key start value |
start表示开始替换的起点,value为需要替换的值。替换时从起点开始(起点也会被替换)替换长度为value的内容。
数据类型
5种基础数据类型
String(字符串)
String使用场景:
- 计数器
- 统计多单位的数量(统计粉丝数量) uid:xxxxx:follow 0
- 对象缓存存储 user:{id}:name zhangsan user:{id}:age 18
String最长可以存512MB数据。
List(列表)
List底层是一个链表,如果key不存在创建新的链表,如果存在新增内容。在两边插入效率最高,在中间插入效率较低。
List常用命令如下:
LPUSH key value [key, value, …]
LPUSH用来将value放入一个list中。放入后的顺序是倒着的,相当于头插。
RPUSH key value [key, value, …]
RPUSH以尾插的方式将value放入list中。
LRANGE key start end
LRANGE用来获得list中[start, end]的value。
LPOP key
LPOP弹出头部第一个元素
RPOP key
RPOP弹出尾部第一个元素
LINDEX key index
LINDEX通过下标获得list中的某一个值
LLEN key
LLEN返回list的长度
LREM key count value
LREM移除list中count个value
LTRIM key start length
LTRIM从下标为start开始(包括start),保留length个元素
RPOPLPUSH curlist newlist
将当前curlist中的最后一个元素弹出,并放入到新的列表newlist中
LSET key index value
LSET将指定下标的值替换为value,若该下标不存在或列表不存在则会报错
LINSERT key BEFORE|AFTER pivot value
LINSERT将value插入到列表中某个元素pivot前面或者后面
Set(集合)
Set常用命令:
SADD key value [value2, value3, …]
给set中添加value
SMEMBERS key
查看set中的所有value
SISMEMBER key value
查看set中的value是否存在,存在返回1,不存在返回0
SCARD key
获取set中的元素个数
SREM key value
移除set中指定的value
SRANDMEMBER key count
随机选出count个元素
SPOP key
随即删除一个元素
SMOVE key1 key2 value
将key1中的value移动到key2
SDIFF key1 key2
查看key1中与key2的差集
SINTER key1 key2
查看key1和key2的交集
SUNION key1 key2
查看key1和key2的并集
应用场景:
共同关注,共同爱好,二度好友(六度分割理论)
Hash(哈希)
常用命令:
HSET key hashkey hashvalue
存入一个hash值
HGET key hashkey
获取对应hashkey的value
HGETALL key
获取hash中的所有值
HDEL key hashkey [hashkey2, …]
删除hash中指定的key-value
HLEN key
获取hash中的value的数量
HKEYS key
获取hash中所有的key
HVALS key
获取hash中所有的value
hash更适合对象的存储,尤其是经常变动的信息
Zset(有序集合)
底层采用跳表实现。
常用命令:
ZADD key [NX|XX] [CH] [INCR] score member [score member]
将指定成员添加到zset中。XX表示仅更新存在成员,不添加新成员。NX表示不更新存在成员,仅添加新成员。CH修改返回值为发生变化的成员总数,原始返回值是新添加成员的总数。INCR与ZINCRBY相似,对成员分数进行递增。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
升序返回score在[min, max]之间的value,withscores返回时会带上scores
ZREVRANGE key start stop [WITHSCORES]
start=0,stop=-1,从大到小进行排序
ZREM key member
移除zset中的成员
ZCARD key
获取zset中的成员个数
ZCOUNT key min max
获取[min, max]之间的成员数量
3种特殊数据类型
geospatial(地理位置)
用于朋友的定位、附近的人,打车距离计算等等。
常用命令如下:
GEOADD key 经度 维度 member
两极无法添加。
GEOPOS key member
获取指定member的经纬度
GEODIST key member1 member2 [unit]
查看两个成员之间的距离,unit指定单位:m、km、mi(英里)、ft(英尺)
GEORADIUS key 经度 维度 半径 [withdist] [withcoord] count
以经纬度为中心,查找半径内的成员,withdist返回距离,withcoord返回经纬度,count返回的个数
GEORADIUSBYMEMBER key member radius [unit]
找出位于指定元素周围的其他元素。
GEO底层实现原理是Zset,可以使用zset的命令操作geo。
Hyperloglog
Hyperloglog用来进行基数统计,可以用来实现网页的UV统计,但无法记录在线用户名。
基数是一个集合中不重复的元素,例如集合A={1,2,2,3,4},基数=4。
官方介绍具有0.81%错误率,在UV统计中可以忽略不计。
优点:占用的内存是固定的,2^64的元素仅需要12KB内存。从内存角度考虑首选Hyperloglog
常用指令:
PFADD key element [element2…]
添加元素
PFCOUNT key
统计元素数量
PFMERGE newkey sourcekey1 sourcekey2 [sourcekey3…]
合并(取并集)sourcekey1和sourcekey2中的元素到新的newkey中。
Bitmap
位数组,统计用户信息,是否活跃,是否登录,是否打卡等。两个状态的都可以使用
常用命令:
SETBIT key offset value
设置第offset位的值为value,value只能为0或1
GETBIT key offset
获取第offset位的值
BITCOUNT key [start end]
统计从start到end值为1的数量
事务
Redis事务本质是一组命令的集合,一个事务中的所有命令会被序列化,在事务执行过程中,会按照顺序执行。一次性、顺序性、排他性。因此Redis事务没有隔离级别的概念。
Redis单条命令保证原子性,但是事务不保证原子性。
事务使用:
- 开启事务(multi)
- 命令入队(……)
- 执行事务(exec)
放弃事务:
- 开启事务(multi)
- 命令入队(……)
- 放弃事务(discard)
编译型异常(代码有问题),事务中的所有命令都不会被执行。
运行时异常,执行时错误命令会抛出异常,其他命令正常执行。
Redis实现乐观锁
使用watch命令监控某个值,若该值被修改则事务执行失败。
1 | # 线程A |
1 | # 线程B |
若线程A声明事务后还未执行,此时线程B修改了money的值,那么线程A开始事务后,事务执行失败。
可以使用unwatch解锁,然后再重新使用watch加锁,然后再执行事务。
该过程与乐观锁的Version实现类似。
Jedis
在Java中使用Redis需要使用中间加Jedis。
导入依赖
1 | <dependencies> |
连接数据库
1 | public static void main(String[] args) { |
连接成功输出PONG。
连接失败参考:idea连接云服务器上的redis报错:Failed to connect to any host resolved for DNS name.【已解决】-CSDN博客
Jedis中的命令与直接在Redis中操作命令相同。
Java实现Redis事务
1 | public static void main(String[] args) { |
SpringBoot整合Redis
在SpringBoot2.x之后,原来底层使用Jedis被替换成了lettuce。
jedis:采用的直连,多个线程操作的话,是不安全的,想要避免需要使用jedis pool连接池。类似BIO模式。
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况。可以减少线程数量。类似NIO模型。
源码分析:
1 |
|
使用步骤:
- 导入依赖
1 | <dependency> |
- 配置连接
1 | spring.redis.host=172.29.1.122 |
- 使用
1 | void contextLoad(){ |
自定义RedisTemplate
1 | package com.fjz.redis02springboot.config; |
持久化
Redis是内存数据库,如果不将数据持久化到磁盘中,那么服务器一旦退出或者挂掉,那么服务器中的数据也会丢失。Redis提供了RDB(Redis DataBase)和AOF(Append Only File)两种持久化方式。
RDB
在指定的时间间隔内将内存中的数据快照写入磁盘,恢复时将快照文件直接读到内存中。默认采用RDB持久化。
Redis会单独创建(fork)一个子进程来进行持久化,先将数据写入到一个临时文件中,等待持久化过程结束,再用这个临时文件替换上次持久化好的文件。在整个过程中,主进程不进行任何IO操作,保证不影响主线程的性能。
如果需要进行大规模数据恢复,并且对于数据完整性不是特别敏感,使用RDB方式比AOF方式更加高效。
RDB的缺点是最后一次持久化后的数据可能丢失。
RDB触发机制
- save规则满足的情况下,会自动触发RDB规则
- 执行flushall命令,也会触发RDB规则
- 默认情况下退出Redis时也会产生RDB文件
- 手动使用
SAVE
或BGSAVE
命令(SAVE
阻塞主进程直接调用rdbSave,BGSAVE
使用子进程调用rdbSave) - 主从复制时,从节点要从主节点进行全量复制也会触发bgsave操作,生成当时的快照发送给从节点
- 执行debug reload命令重新加载redis时也会触发bgsave
备份会自动生成一个dump.rdb文件(该文件名可在redis.conf中进行配置)
如何使用RDB恢复数据
只需要将rdb文件放在Redis启动目录即可,Redis启动时会自动检查dump.rdb并恢复其中的数据。
使用以下命令可以查看dump.rdb需要存在的位置。
1 | 127.0.0.1:6379> config get dir |
优缺点
优点
- RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景
- Redis加载RDB文件恢复数据要远远快于AOF方式
缺点
- RDB方式实时性不够,无法做到秒级的持久化
- 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高
- RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全
- 版本兼容RDB文件问题
AOF
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(不记录读操作),只允许追加文件但不可以改写文件,Redis启动的时候会读取AOF文件重新构建数据,即Redis重启后根据日志文件的内容将写指令全部执行一次,完成数据恢复。
同步步骤
- 命令传播: Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中
- 缓存追加: AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加到服务器的 AOF 缓存中
- 文件写入和保存: AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF 保存条件被满足的话,
fsync
函数或者fdatasync
函数会被调用,将写入的内容真正地保存到磁盘中
AOF保存模式
Redis 目前支持三种 AOF 保存模式,可以在redis.conf文件中选择。
Always
,同步写回:每个写命令执行完,立马同步地将日志写回磁盘Everysec
,每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘No
,操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
AOF后台重写
AOF 后台重写,是为了避免主进程被阻塞,无法处理请求,所以采用主进程fork
出子进程,用于 AOF 重写。为了避免在 AOF 重写期间新命令对现有数据的修改导致的不一致问题,Redis 增加了一个AOF 重写缓存,这个缓存在fork
出子进程之后开始启用,Redis 主进程在接到新的写命令之后,除了会将这个写命令的协议内容追加到现有的 AOF 文件之外,还会追加到这个缓存中。
当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:
- 处理命令请求
- 将写命令追加到现有的 AOF 文件中
- 将写命令追加到 AOF 重写缓存中
当子进程完成 AOF 重写之后,它会向主进程发送一个完成信号,主进程在接到完成信号之后,会调用一个信号处理函数,并完成以下工作:
- 将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。
- 对新的 AOF 文件进行改名,覆盖原有的 AOF 文件
AOF优缺点
优点
- 拥有不同的
fsync
策略,fsync
是使用后台线程执行的,写入性能很好 - AOF是一个仅追加日志,没有查找和断电时的损坏问题。即使由于某种原因(磁盘已满或其他原因)日志以写入一半的命令结束,
redis-check-aof
工具也能够轻松修复它 - 当 AOF 变得太大时,Redis 能够在后台自动重写 AOF
- AOF 以易于理解和解析的格式包含一个接一个地记录所有操作的日志,使得导出和恢复十分简单
- 拥有不同的
缺点
对于相同的数据集,AOF 文件通常比等效的 RDB 文件大
根据确切的 fsync 策略,AOF 可能比 RDB 慢
(Redis < 7.0)
如果在重写期间有对数据库的写入,AOF 会使用大量内存
重写期间到达的所有写入命令都会写入磁盘两次
Redis 可能会在重写结束时冻结写入并将这些写入命令同步到新的 AOF 文件
Redis主从复制
基本概念
主从复制是将一台Redis服务器的数据复制到其他Redis服务器。数据的复制是单向的,只能由主节点到从节点。主节点以写为主,从节点以读为主。
默认情况下,每台Redis服务器都是主节点,且一个弓主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的主要作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的另一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载压力。
- 高可用:主从复制是哨兵和集群的基础。
环境配置
- 查看当前数据库的信息
1 | 127.0.0.1:6379> info replication # 查看当前库的信息 |
- 修改每个数据库的配置文件
1 | port 6379 # 为每个数据库指定不同的端口 |
- 启动Redis并配置从库
默认情况下,启动后的Redis数据库都是主库,此时需要对从库进行配置。
1 | SLAVEOF host port |
在从库中输入以上命令,host为主机地址,port为主机端口号。
以上配置是通过命令配置的,只是暂时的主从,实际生产中应该在配置文件中配置,保证永久主从。
- 配置文件配置主从
在配置文件中的replication部分添加以下配置:
1 | replicaof <masterip> <masterport> # 分别代表主机的ip和port |
通过以上配置后,Redis数据库启动后默认为从库。
复制原理
- Slave启动成功后连接到master后会发送一个sync同步命令。
- master接收到命令后,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次完全同步。
- slave接收到数据库文件数据后,将其存盘并加载到内存中(全量复制)。
- master继续将新的所有收集到的修改命令依次传给slave,完成同步(增量复制)。
slave重新连接master后,会自动执行一次全量复制。
哨兵模式
哨兵配置
创建一个sentinel.conf文件,在文件中输入以下内容。
1 | sentinel monitor <master-name> <ip> <redis-port> <quorum> # quorum配置多少个哨兵统一认为master节点失联,那么客观上就认为主节点失联了 |
redisname为该哨兵监控的redis的名字,host为监控redis的ip,port为端口,1代表当监控的redis挂掉后,哨兵可以发起投票,选举新的主节点。
如果主机重新上线了,会被当做从节点。
优缺点
- 优点:
- 主从可以切换,故障可以转移,系统可用性高
- 哨兵模式是主从模式的升级,更加健壮
- 缺点:
- 实现哨兵模式的配置较为麻烦