学习参考视频:【狂神说Java】Redis
Redis
一、redis相关概述
redis属于非关系型数据库。
1.关于非关系型数据库的介绍
非关系型的数据库,也就是NoSQL(Not Only SQL)。
关系型数据库:列+行,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。
NoSQL泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。
NoSQL的特点
- 方便扩展(数据之间没有关系,很好扩展!)
- 大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
- 数据类型是多样型的!(不需要事先设计数据库,随取随用)
NoSQL的分类
类型 | 部分代表 | 特点 |
---|---|---|
列存储 | Hbase Cassandra HypertableHypertable | 顾名思义,是按列存储数据的。 最大的特点是方便存储结构化和半结构化数据,方便做数据压缩,对针对某一列或者某几列的查询有非常大的IO优势。 |
文档存储 | MongoDB CouchDB | 文档存储一般用类似json的格式存储,存储的内容是文档型的。 这样也就有有机会对某些字段建立索引,实现关系数据库的某些功能。 |
key-value存储 | Tokyo Cabinet/Tyrant Berkeley DB MemcacheDB Redis | 可以通过key快速查询到其value。 一般来说,存储不管value的格式,照单全收。(Redis包含了其他功能) |
图存储 | Neo4J FlockDB | 图形关系的最佳存储。 使用传统关系数据库来解决的话性能低下,而且设计使用不方便。 |
对象存储 | db4o Versant | 通过类似面向对象语言的语法操作数据库,通过对象的方式存取数据。 |
xml数据库 | Berkeley DB XML BaseX | 高效的存储XML数据,并支持XML的内部查询语法, |
2.什么是redis
Redis是一款内存高速缓存数据库。Redis全称为:Remote Dictionary Server (远程数据服务),使用C语言编写,Redis是一个key-value存储系统(键值存储系统),支持丰富的数据类型,如:String、list、set、zset、hash。
3.redis的应用场景
众多语言都支持Redis,因为Redis交换数据快,在服务器中常用来存储一些需要频繁调取的数据,节省内存开销,也极大的提升了速度。
将一些热点数据存储到Redis中,要用的时候,直接从内存取,极大的提高了速度和节约了服务器的开销。
1、会话缓存(最常用)
2、消息队列(支付)
3、活动排行榜或计数
4、发布,订阅消息(消息通知)
5、商品列表,评论列表
二、五大数据类型
在介绍五大数据类型之前,我们先来了解一下关于key操作的基础命令
命令 | 说明 |
---|---|
keys * | 查看当前数据库所有的key |
exists | 判断指定key是否存在 |
del | 删除指定key |
expire | 设置key的过期时间 |
type | 查看key的类型 |
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> type name
string
127.0.0.1:6379> expire name 10 #设置10s到期时间
(integer) 1
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name #10s后过期了
(integer) 0
127.0.0.1:6379> keys * #在查看当前数据库所有的key,发现无了
(empty list or set)
String(字符串)
由于redis输入命令后面会有提示,我这里就写他的关键词了
命令 | 描述 |
---|---|
APPEND | 向指定的key的value后追加字符串 |
DECR/INCR | 将指定key的value数值进行+1/-1(仅对于数字) |
INCRBY/DECRBY | 按指定的步长对数值进行加减 |
INCRBYFLOAT | 为数值加上浮点型数值 |
STRLEN | 获取key保存值的字符串长度 |
GETRANGE | 按起止位置获取字符串(闭区间,起止位置都取) |
SETRANGE | 用指定的value 替换key中 offset开始的值 |
GETSET | 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。 |
SETNX | 仅当key不存在时进行set |
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"thenie"
127.0.0.1:6379> get age
"18"
127.0.0.1:6379> append name Shuai #向指定字符串后加Shuai
(integer) 11
127.0.0.1:6379> get name
"thenieShuai"
127.0.0.1:6379> DECR age #自减
(integer) 17
127.0.0.1:6379> INCR age #自增
(integer) 18
127.0.0.1:6379> DECRBY age 5 #指定减少
(integer) 13
127.0.0.1:6379> INCRBY age 5
(integer) 18
127.0.0.1:6379> STRLEN age #返回字符串长度
(integer) 2
127.0.0.1:6379> getrange name 0 -1 #返回完整字符串
"thenieShuai"
127.0.0.1:6379> getrange name 0 6 #返回下标为0~6的字符串
"thenieS"
127.0.0.1:6379> setrange name 6 Haokan #从6的位置开始用Haokan进行覆盖
(integer) 12
127.0.0.1:6379> get name
"thenieHaokan"
127.0.0.1:6379> GETSET name kuaijieshule #返回name的值,并进行指定的修改
"thenieHaokan"
127.0.0.1:6379> GET name
"kuaijieshule"
127.0.0.1:6379> setnx name , #创建,失败,因为存在key为name
(integer) 0
127.0.0.1:6379> get name
"kuaijieshule"
String类似的使用场景:value除了是字符串还可以是数字,用途举例:
- 计数器
- 统计多单位的数量:uid:123666:follow 0
- 粉丝数
- 对象存储缓存
Hash(哈希)
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
命令 | 描述 |
---|---|
HSET | 将哈希表 key 中的字段 field 的值设为 value 。重复设置同一个field会覆盖,返回0 |
HMSET | 同时将多个 field-value (域-值)对设置到哈希表 key 中。 |
HSETNX | 只有在字段 field 不存在时,设置哈希表字段的值。 |
HEXISTS | 查看哈希表 key 中,指定的字段是否存在。 |
HGET | 获取存储在哈希表中指定字段的值 |
HMGET | 获取所有给定字段的值 |
HGETALL | 获取在哈希表key 的所有字段和值 |
HKEYS | 获取哈希表key中所有的字段 |
HLEN | 获取哈希表中字段的数量 |
HVALS | 获取哈希表中所有值 |
HDEL | 删除哈希表key中一个/多个field字段 |
HINCRBY | 为哈希表 key 中的指定字段的整数值加上增量n,并返回增量后结果 一样只适用于整数型字段 |
HINCRBYFLOAT | 为哈希表 key 中的指定字段的浮点数值加上增量 n。 |
127.0.0.1:6379> HSET mymap k1 v1 #初始化一个key为mymap的集合
(integer) 1
127.0.0.1:6379> HSET mymap k2 v2
(integer) 1
127.0.0.1:6379> HMSET mymap k3 v3 k4 v4
OK
127.0.0.1:6379> HEXISTS mymap k1 #判断k1在mymap中是否存在
(integer) 1
127.0.0.1:6379> HEXISTS mymap k5
(integer) 0
127.0.0.1:6379> HGETALL mymap #返回mymap中所有的key和value
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
7) "k4"
8) "v4"
127.0.0.1:6379> HGET mymap k1
"v1"
127.0.0.1:6379> HKEYS mymap #返回mymap中所有的key
1) "k1"
2) "k2"
3) "k3"
4) "k4"
127.0.0.1:6379> HVALS mymap
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> HDEL mymap k2 #删除mymap中的k2
(integer) 1
127.0.0.1:6379> HKEYS mymap
1) "k1"
2) "k3"
3) "k4"
127.0.0.1:6379> HSET mymap number 10
(integer) 1
127.0.0.1:6379> hget mymap number
"10"
127.0.0.1:6379> HINCRBY mymap number 10
(integer) 20
Hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!Hash更适合于对象的存储,Sring更加适合字符串存储!
List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。
首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等
正如图Redis中List是可以进行双端操作的,所以命令也就分为了LXXX和RLLL两类,有时候L也表示List例如LLEN
命令 | 描述 |
---|---|
LPUSH/RPUSH | 从左边/右边向列表中PUSH值(一个或者多个)。 |
LRANGE | 获取list 起止元素==(索引从左往右 递增)== |
LPUSHX/RPUSHX | 向已存在的列名中push值(一个或者多个) |
LINSERT key BEFORE|AFTER pivot value | 在指定列表元素的前/后 插入value |
LLEN | 查看列表长度 |
LINDEX | 通过索引获取列表元素 |
LSET | 通过索引为元素设值 |
LPOP/RPOP | 从最左边/最右边移除值 并返回 |
RPOPLPUSH source destination | 将列表的尾部(右)最后一个值弹出,并返回,然后加到另一个列表的头部 |
LTRIM | 通过下标截取指定范围内的列表 |
LREM key count value | List中是允许value重复的 count > 0 :从头部开始搜索 然后删除指定的value 至多删除count个 count < 0 :从尾部开始搜索… count = 0 :删除列表中所有的指定value。 |
BLPOP/BRPOP | 移出并获取列表的第一个/最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 |
127.0.0.1:6379> rpush list a b c d e f #初始化key为list的列表
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1 #从左至右返回list
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
127.0.0.1:6379> LPUSH list 1 #向左插入1
(integer) 7
127.0.0.1:6379> LRANGE list 0 -1
1) "1"
2) "a"
3) "b"
4) "c"
5) "d"
6) "e"
7) "f"
127.0.0.1:6379> RPUSH list 2 #向右插入2
(integer) 8
127.0.0.1:6379> LRANGE list 0 -1 #从左至右返回list
1) "1"
2) "a"
3) "b"
4) "c"
5) "d"
6) "e"
7) "f"
8) "2"
127.0.0.1:6379> llen list
(integer) 8
127.0.0.1:6379> LINDEX list 2
"b"
127.0.0.1:6379> LPUSH list1 1 #初始化一个list1的列表
(integer) 1
127.0.0.1:6379> RPOPLPUSH list list1 #从list的右边弹出一个值并向list的左边插入
"2"
127.0.0.1:6379> LRANGE list1 0 -1
1) "2"
2) "1"
127.0.0.1:6379> LPOP list1
"2"
127.0.0.1:6379> LPOP list1
"1"
127.0.0.1:6379> LTRIM list 1 8
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
6) "f"
小结
- list实际上是一个链表,before Node after , left, right 都可以插入值
- 如果key不存在,则创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
应用:
消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)
Set(集合)
Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。
命令 | 描述 |
---|---|
SADD | 向集合中无序增加一个/多个成员 |
SCARD | 获取集合的成员数 |
SMEMBERS | 返回集合中所有的成员 |
SISMEMBER | 查询member元素是否是集合的成员,结果是无序的 |
SRANDMEMBER | 随机返回集合中count个成员,count缺省值为1 |
SPOP | 随机移除并返回集合中count个成员,count缺省值为1 |
SMOVE source destination member | 将source集合的成员member移动到destination集合 |
SREM | 移除集合中一个/多个成员 |
SDIFF | 返回所有集合的差集 key1- key2 - … |
SDIFFSTORE destination key1[key2..] | 在SDIFF的基础上,将结果保存到集合中==(覆盖)==。不能保存到其他类型key噢! |
SINTER key1 [key2..] | 返回所有集合的交集 |
SINTERSTORE destination key1[key2..] | 在SINTER的基础上,存储结果到集合中。覆盖 |
SUNION key1 [key2..] | 返回所有集合的并集 |
SUNIONSTORE destination key1 [key2..] | 在SUNION的基础上,存储结果到及和张。覆盖 |
SSCAN KEY [MATCH pattern] [COUNT count] | 在大量数据环境下,使用此命令遍历集合中元素,每次遍历部分 |
127.0.0.1:6379> sadd set a b c #初始化key为set的集合
(integer) 3
127.0.0.1:6379> sadd set1 d e f #初始化key为set1的集合
(integer) 3
127.0.0.1:6379> SMEMBERS set #查看指定集合的全部成员
1) "b"
2) "c"
3) "a"
127.0.0.1:6379> SISMEMBER set b #判断b是否存在于集合中
(integer) 1
127.0.0.1:6379> SISMEMBER set c
(integer) 1
127.0.0.1:6379> SISMEMBER set e
(integer) 0
127.0.0.1:6379> SRANDMEMBER set 2 #随机返回两个成员
1) "b"
2) "c"
127.0.0.1:6379> SRANDMEMBER set 2
1) "a"
2) "c"
127.0.0.1:6379> SADD set d
(integer) 1
127.0.0.1:6379> SMOVE set set1 a #将set中的a移入set1中
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "b"
2) "c"
3) "d"
127.0.0.1:6379> SMEMBERS set1
1) "a"
2) "f"
3) "e"
4) "d"
127.0.0.1:6379> SREM set1 a #删除set1中的a元素
(integer) 1
127.0.0.1:6379> SMEMBERS set1
1) "f"
2) "e"
3) "d"
ZSet(sorted set 有序集合)
不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
score相同:按字典顺序排序
有序集合的成员是唯一的,但分数(score)却可以重复。
命令 | 描述 |
---|---|
ZADD key score member1 [score2 member2] | 向有序集合添加一个或多个成员,或者更新已存在成员的分数 |
ZCARD key | 获取有序集合的成员数 |
ZCOUNT key min max | 计算在有序集合中指定区间score的成员数 |
ZINCRBY key n member | 有序集合中对指定成员的分数加上增量 n |
ZSCORE key member | 返回有序集中,成员的分数值 |
ZRANK key member | 返回有序集合中指定成员的索引 |
ZRANGE key start end | 通过索引区间返回有序集合成指定区间内的成员 |
ZRANGEBYLEX key min max | 通过字典区间返回有序集合的成员 |
ZRANGEBYSCORE key min max | 通过分数返回有序集合指定区间内的成员==-inf 和 +inf分别表示最小最大值,只支持开区间()== |
ZLEXCOUNT key min max | 在有序集合中计算指定字典区间内成员数量 |
ZREM key member1 [member2..] | 移除有序集合中一个/多个成员 |
ZREMRANGEBYLEX key min max | 移除有序集合中给定的字典区间的所有成员 |
ZREMRANGEBYRANK key start stop | 移除有序集合中给定的排名区间的所有成员 |
ZREMRANGEBYSCORE key min max | 移除有序集合中给定的分数区间的所有成员 |
ZREVRANGE key start end | 返回有序集中指定区间内的成员,通过索引,分数从高到底 |
ZREVRANGEBYSCORRE key max min | 返回有序集中指定分数区间内的成员,分数从高到低排序 |
ZREVRANGEBYLEX key max min | 返回有序集中指定字典区间内的成员,按字典顺序倒序 |
ZREVRANK key member | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
ZINTERSTORE destination numkeys key1 [key2 ..] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中,numkeys:表示参与运算的集合数,将score相加作为结果的score |
ZUNIONSTORE destination numkeys key1 [key2..] | 计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中 |
ZSCAN key cursor [MATCH pattern\] [COUNT count] | 迭代有序集合中的元素(包括元素成员和元素分值) |
-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 3 m3 # 向有序集合myzset中添加成员m1 score=1 以及成员m2 score=2..
(integer) 2
127.0.0.1:6379> ZCARD myzset # 获取有序集合的成员数
(integer) 2
127.0.0.1:6379> ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量
(integer) 1
127.0.0.1:6379> ZCOUNT myzset 0 2
(integer) 2
----------------ZINCRBY--ZSCORE--------------------------
127.0.0.1:6379> ZINCRBY myzset 5 m2 # 将成员m2的score +5
"7"
127.0.0.1:6379> ZSCORE myzset m1 # 获取成员m1的score
"1"
127.0.0.1:6379> ZSCORE myzset m2
"7"
--------------ZRANK--ZRANGE-----------------------------------
127.0.0.1:6379> ZRANK myzset m1 # 获取成员m1的索引,索引按照score排序,score相同索引值按字典顺序顺序增加
(integer) 0
127.0.0.1:6379> ZRANK myzset m2
(integer) 2
127.0.0.1:6379> ZRANGE myzset 0 1 # 获取索引在 0~1的成员
1) "m1"
2) "m3"
127.0.0.1:6379> ZRANGE myzset 0 -1 # 获取全部成员
1) "m1"
2) "m3"
3) "m2"
#testset=>{abc,add,amaze,apple,back,java,redis} score均为0
------------------ZRANGEBYLEX---------------------------------
127.0.0.1:6379> ZRANGEBYLEX testset - + # 返回所有成员
1) "abc"
2) "add"
3) "amaze"
4) "apple"
5) "back"
6) "java"
7) "redis"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 0 3 # 分页 按索引显示查询结果的 0,1,2条记录
1) "abc"
2) "add"
3) "amaze"
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录
1) "apple"
2) "back"
3) "java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员
1) "abc"
2) "add"
3) "amaze"
4) "apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员
1) "apple"
2) "back"
3) "java"
-----------------------ZRANGEBYSCORE---------------------
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员
1) "m1"
2) "m3"
3) "m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
1) "m1"
2) "m3"
--------------------ZLEXCOUNT-----------------------------
127.0.0.1:6379> ZLEXCOUNT testset - +
(integer) 7
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3
------------------ZREM--ZREMRANGEBYLEX--ZREMRANGBYRANK--ZREMRANGEBYSCORE--------------------------------
127.0.0.1:6379> ZREM testset abc # 移除成员abc
(integer) 1
127.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成员
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成员
(integer) 2
# testset=> {abc,add,apple,amaze,back,java,redis} score均为0
# myzset=> {(m1,1),(m2,2),(m3,3),(m4,4),(m7,7),(m9,9)}
----------------ZREVRANGE--ZREVRANGEBYSCORE--ZREVRANGEBYLEX-----------
127.0.0.1:6379> ZREVRANGE myzset 0 3 # 按score递减排序,然后按索引,返回结果的 0~3
1) "m9"
2) "m7"
3) "m4"
4) "m3"
127.0.0.1:6379> ZREVRANGE myzset 2 4 # 返回排序结果的 索引的2~4
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYSCORE myzset 6 2 # 按score递减顺序 返回集合中分数在[2,6]之间的成员
1) "m4"
2) "m3"
3) "m2"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
1) "java"
2) "back"
3) "apple"
4) "amaze"
-------------------------ZREVRANK------------------------------
127.0.0.1:6379> ZREVRANK myzset m7 # 按score递减顺序,返回成员m7索引
(integer) 1
127.0.0.1:6379> ZREVRANK myzset m2
(integer) 4
# mathscore=>{(xm,90),(xh,95),(xg,87)} 小明、小红、小刚的数学成绩
# enscore=>{(xm,70),(xh,93),(xg,90)} 小明、小红、小刚的英语成绩
-------------------ZINTERSTORE--ZUNIONSTORE-----------------------------------
127.0.0.1:6379> ZINTERSTORE sumscore 2 mathscore enscore # 将mathscore enscore进行合并 结果存放到sumscore
(integer) 3
127.0.0.1:6379> ZRANGE sumscore 0 -1 withscores # 合并后的score是之前集合中所有score的和
1) "xm"
2) "160"
3) "xg"
4) "177"
5) "xh"
6) "188"
127.0.0.1:6379> ZUNIONSTORE lowestscore 2 mathscore enscore AGGREGATE MIN # 取两个集合的成员score最小值作为结果的
(integer) 3
127.0.0.1:6379> ZRANGE lowestscore 0 -1 withscores
1) "xm"
2) "70"
3) "xg"
4) "87"
5) "xh"
6) "93"
应用案例:
- set排序 存储班级成绩表 工资表排序!
- 普通消息,1.重要消息 2.带权重进行判断
- 排行榜应用实现,取Top N测试
三、事务
Redis的单条命令是保证原子性的,而Redis事务不保证原子性。
1.如何使用事务
-
开启事务(
multi
) -
命令入队
-
执行事务(
exec
)开启事务后的命令不会立即执行,而是
exec
输入后一起执行127.0.0.1:6379> keys * (empty list or set) 127.0.0.1:6379> MULTI #开启 OK 127.0.0.1:6379> set name thenie #命令入队 QUEUED 127.0.0.1:6379> set age 18 QUEUED 127.0.0.1:6379> get name QUEUED 127.0.0.1:6379> get name QUEUED 127.0.0.1:6379> get age QUEUED 127.0.0.1:6379> EXEC #结束--执行 1) OK 2) OK 3) "thenie" 4) "thenie" 5) "18"
在redis与其称作事务,不如叫批处理更为合适。
如果在开启事务中途不想执行了,输入
DISCARD
放弃。
2.事务中的命令错误会怎么样
代码语法错误(编译时异常)所有的命令都不执行
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> MULTI #开启
OK
127.0.0.1:6379> set name thenie
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> setset name er #错误的命令
(error) ERR unknown command `setset`, with args beginning with: `name`, `er`,
127.0.0.1:6379> get name
QUEUED
127.0.0.1:6379> EXEC #结束后全部没有执行
(error) EXECABORT Transaction discarded because of previous errors.
代码逻辑错误 (运行时异常) **其他命令可以正常执行 ** >>> 所以不保证事务原子性
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set name thenie
QUEUED
127.0.0.1:6379> set age 18
QUEUED
127.0.0.1:6379> INCR name #逻辑错误,最字符串进行了自增
QUEUED
127.0.0.1:6379> INCR age
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) OK
3) (error) ERR value is not an integer or out of range #第三条命令错误,但是其他命令也都执行了
4) (integer) 19
四、SpringBoot整合reids
可以看我的这篇博客SpringBoot整合reids
五、持久化——RDB
redis中的数据是缓存在内存中的,而内存中的数据是断电即失的,这就需要我们在指定时间间隔后将内存中的数据写入到硬盘中
这个过程就叫做持久化,RDB(Redis Databases)是其中的一种方式。
什么是RDB
在指定时间间隔后,将内存中的数据集快照写入数据库 ;在恢复时候,直接读取快照文件,进行数据的恢复 ;
默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。文件名可以在配置文件中进行自定义。
如何触发
-
触发持久化规则
持久化规则配置在名为redis.conf配置文件中:
-
flushall命令
当执行此命令时,也会默认触发持久化。
重启redis数据库后,数据复原。
-
save
使用
save
命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;由于
save
命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save
命令执行速度会非常慢,阻塞所有客户端的请求。 -
bgsave
bgsave
是异步进行,进行持久化的时候,redis
还可以将继续响应客户端请求 ;也就是fork()一个子进程去进行持久化,而主进程继续工作。
工作原理
在进行 RDB
的时候,redis
的主线程是不会做 io
操作的,主线程会 fork
一个子线程来完成该操作;
- Redis 调用forks。同时拥有父进程和子进程。
- 子进程将数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)
优缺点
优点:
- 适合大规模的数据恢复
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了。
- fork进程的时候,会占用一定的内容空间。
六、持久化——AOF
什么是AOF
将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
如何开启
默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!
如果要使用AOF,需要修改配置文件redis.conf:
appendonly no yes
则表示启用AOF
转存文件出问题
如果这个aof文件有错位,这时候redis是启动不起来的,我需要修改这个aof文件
redis给我们提供了一个工具redis-check-aof --fix
会将这个文件中出问题的命令剔除掉
优缺点
优点
- 每一次修改都会同步,文件的完整性会更加好
- 没秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
- Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
七、redis主从复制
概念
在redis主从架构中,Master节点负责处理写请求,Slave节点只处理读请求。对于写请求少,读请求多的场景,例如电商详情页,通过这种读写分离的操作可以大幅提高并发量,通过增加redis从节点的数量可以使得redis的QPS达到10W+。
当Master结点写入数据后会同步到Slave结点,简易版架构图如下:
在一台服务器上模拟一主二从(redis)
-
设置三个redis服务进程,并进行配置
cp三个配置文件:
#分别修改三个redis的配置文件 --------------主(redis.conf)------------- #端口号 port 6379 #pid文件名 pidfile /var/run/redis_6379.pid #日志文件名 logfile "6379.log" #rdb文件名 dbfilename dump.rdb --------------从(slave1.conf)------------- port 6380 pidfile /var/run/redis_6380.pid logfile "6380.log" dbfilename dump6380.rdb --------------从(slave2.conf)------------- port 6381 pidfile /var/run/redis_6381.pid logfile "6381.log" dbfilename dump6381.rdb
启动服务成功:
-
建立联系
默认情况下每台redis都是主机点,所以我们只用配置从机就好了
使用
SLAVEOF host port
就可以为从机配置主机了。配置成功并用
info replication
查看主从状态信息:使用规则
-
从机只能进行读操作,主机可以读但是多用于写
-
当主机断电宕机后,默认情况下从机的角色不会发生变化 ,集群中只是失去了写操作,当主机恢复以后,又会连接上从机恢复原状。
-
当从机断电宕机后,若不是使用配置文件配置的从机,再次启动后作为主机是无法获取之前主机的数据的,若此时重新配置称为从机,又可以获取到主机的所有数据。这里就要提到一个同步原理。
-
第二条中提到,默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:
- 从机手动执行命令
slaveof no one
,这样执行以后从机会独立出来成为一个主机 - 使用哨兵模式(自动选举)
- 从机手动执行命令
-
八、哨兵模式
当主服务器宕机后,如果手动切换主服务器显然是不太现实的,更多时候采用哨兵模式。
概述
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里哨兵的作用:
- 通过发送命令,监控redis,让其返回运行状态(包括主服务器和从服务器)
- 当哨兵检测到master宕机后,会自动将一台slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
故障切换(failover)的过程
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
九、缓存穿透与雪崩
缓存穿透
概念
在默认情况下,用户请求数据时,会先去缓存中去查找,未命中再去数据库中查找。但如果访问量过大,并且缓存未命中,这时都会转移到数据库中,导致压力过大,就可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
解决方案
布隆过滤器
顾名思义,布隆,在前方举起一个盾牌。
最所有可能查询的参数进行Hash存储,以便快速判断是否有该数据;在控制层进行拦截校验,如果不存在直接返回。
缓存空对象
若某次请求在缓存和数据库中没有找到,就在缓存中放一个空对象,便于后续处理。
这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间;
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿
概念
相较于缓存穿透,缓存击穿目的性更强,当某个key在缓存中过期,同时有多个请求同时访问此key,这些请求都会击穿到DB中,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
解决方案
-
设置热点数据永不过期
这样就不会出现热点数据过期的情况,但是这样会占用空间,一旦热点数据过多占用空间就会更多。当redis内存空间满的时候也会清理部分数据。
-
加互斥锁(分布式锁)
在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
缓存雪崩
概念
大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。
解决方案
-
redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
-
限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
-
数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
如果文章对您有帮助,还请赏个赞,加个关~~