狂神redis笔记

Redis入门

  • Redis是什么?

Redis(Remote Dictionary Server ),即远程字典服务,数据都是缓存在内存中。
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步

  • 特性

      多样的数据类型
      持久化
      集群
      事务
    
  • 基础知识

redis默认有16个数据库默认使用的第0;
16个数据库为:DB 0~DB 15
默认使用DB 0 
可以使用select n切换到DB n
dbsize可以查看当前数据库的大小,与key数量相关。

127.0.0.1:6379> config get databases # 命令行查看数据库数量databases
1) "databases"
2) "16"

127.0.0.1:6379> select 8 # 切换数据库 DB 8
OK
127.0.0.1:6379[8]> dbsize # 查看数据库大小
(integer) 0

# 不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
127.0.0.1:6379> set name sakura 
OK
127.0.0.1:6379> SELECT 8
OK
127.0.0.1:6379[8]> get name # db8中并不能获取db0中的键值对。
(nil)
127.0.0.1:6379[8]> DBSIZE
(integer) 0
127.0.0.1:6379[8]> SELECT 0
OK
127.0.0.1:6379> keys *
1) "counter:__rand_int__"
2) "mylist"
3) "name"
4) "key:__rand_int__"
5) "myset:__rand_int__"
127.0.0.1:6379> DBSIZE # size和key个数相关
(integer) 5
	keys * :查看当前数据库中所有的key。
	flushdb:清空当前数据库中的键值对。
	flushall:清空所有数据库的键值对。
  • Redis是单线程的,Redis是基于内存操作的。

      所以Redis的性能瓶颈不是CPU,而是机器内存和网络带宽。
    
  • Redis为什么单线程还这么快?

      对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的。
      在内存存储数据情况下,单线程就是最佳的方案。
    

五大数据类型

它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) , bitmaps, hyperloglogs 和 地理空间(geospatial) 。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

  • key命令:

      exists key:判断键是否存在
      del key:删除键值对
      move key db:将键值对移动到指定数据库
      expire key second:设置键值对的过期时间
      type key:查看value的数据类型
    
# 基础语法:
# SET key value	          设置一个key
# GET key                 获取一个key对应value
# EXISTS key              查询key是否存在
# MOVE key n(数字)	     将当前key移动到指定的几号数据库中
# KEYS *                  查询当前数据库中全部的key
# EXPIRE key time         设置当前key的过期时间
# TTL key                 查询当前key的存活时间
# TYPE key                查看key的数据类型
————————————————

127.0.0.1:6379> keys * # 查看当前数据库所有key
(empty list or set)
127.0.0.1:6379> set name qinjiang # set key
OK
127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> move age 1 # 将键值对移动到指定数据库
(integer) 1
127.0.0.1:6379> EXISTS age # 判断键是否存在
(integer) 0 # 不存在
127.0.0.1:6379> EXISTS name
(integer) 1 # 存在
127.0.0.1:6379> SELECT 1
OK
127.0.0.1:6379[1]> keys *
1) "age"
127.0.0.1:6379[1]> del age # 删除键值对
(integer) 1 # 删除个数


127.0.0.1:6379> set age 20
OK
127.0.0.1:6379> EXPIRE age 15 # 设置键值对的过期时间,15s

(integer) 1 # 设置成功 开始计数
127.0.0.1:6379> ttl age # 查看key的过期剩余时间
(integer) 13
127.0.0.1:6379> ttl age
(integer) 11
127.0.0.1:6379> ttl age
(integer) 9
127.0.0.1:6379> ttl age
(integer) -2 # -2 表示key过期,-1表示key未设置过期时间

127.0.0.1:6379> get age # 过期的key 会被自动delete
(nil)
127.0.0.1:6379> keys *
1) "name"

127.0.0.1:6379> type name # 查看value的数据类型
string

  • 关于TTL命令
    Redis的key,通过TTL命令返回key的过期时间,一般来说有3种:

      当前key没有设置过期时间,所以会返回-1.
      当前key有设置过期时间,而且key已经过期,所以会返回-2.
      当前key有设置过期时间,且key还没有过期,故会返回key的正常剩余时间.
    
  • 关于重命名RENAME和RENAMENX

      RENAME key newkey修改 key 的名称
      RENAMENX key newkey仅当 newkey 不存在时,将 key 改名为 newkey 。
    

String(字符串)

普通的set、get直接略过。

# 语法:
# APPEND key appendValue            对指定key实现字符串拼接,如果key不存在,等同于se
# STRLEN key                        查看指定key的长度
# INCR key                          对指定key进行自增,类似于Java中的i++
# DECR key                          自减,类似于Java的i--
# INCRBY key n                      对指定key按照指定的步长值进行自增
# DECRBY key n						按照指定的步长值自减
# SETRANGE key index value          替换指定位置的字符串,从指定key的索引开始,插入指定的value值。
# 									如果key不存在且索引>1,那么当前的索引之前的数据,会用\x00代替并占用一个索引位置,相当于ASCII码中的null
# GETRANGE key startIndex endIndex	将指定key按照索引的开始和结束范围进行截取
# SETEX key time value				设置一个有存活时间的key
# SETNX key value                   如果这个key不存在,即创建,返回0失败,返回1成功(分布式锁中运用较多)
# MSET key1 value1 key2 value2...	设置多个key value
# MGET key ...						获取多个key指定的value
# GETSET key value                  先获取指定的key,然后再设置指定的value
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> KEYS * # 获取当前数据库中所有的key
1) "k1"
127.0.0.1:6379> APPEND k1 hello #给k1再继续追加value值
(integer) 7
127.0.0.1:6379> get k1
"v1hello"
127.0.0.1:6379> APPEND k1 ,xiaohuang
(integer) 17
127.0.0.1:6379> STRLEN k1 # 查看当前k1的长度
(integer) 17
127.0.0.1:6379> get k1
"v1hello,xiaohuang"
127.0.0.1:6379> KEYS * 
1) "k1"
127.0.0.1:6379> APPEND name xiaohuang
(integer) 9
127.0.0.1:6379> get name
"xiaohuang"
127.0.0.1:6379> KEYS *
1) "k1"
2) "name"
###################实现自增自减效果################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
0
127.0.0.1:6379> INCR views # 设置value的自增效果
(integer) 1
127.0.0.1:6379> INCR views 
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> DECR views # 设置value的自减效果
(integer) 1
127.0.0.1:6379> DECR views
(integer) 0
127.0.0.1:6379> get views
"0"
############可以在自增自减时设置步长##############
127.0.0.1:6379> INCRBY views 2 # 自增,设置步长为2
(integer) 2
127.0.0.1:6379> INCRBY views 2
(integer) 4
127.0.0.1:6379> get views
"4"
127.0.0.1:6379> DECRBY views 3 # 自减,设置步长为3
(integer) 1
127.0.0.1:6379> DECRBY views 3
(integer) -2
127.0.0.1:6379> get views
"-2"
# 注意:value的自增和自减只适用于Integer类型
127.0.0.1:6379> incr name 
(error) ERR value is not an integer or out of range
#################实现字符串截取效果#################
127.0.0.1:6379> set k1 hello,xiaohuang
OK
127.0.0.1:6379> get k1
"hello,xiaohuang"
127.0.0.1:6379> GETRANGE k1 0 3 # 实现字符串截取,有起始索引和结束索引,相当于Java中的subString()
"hell"
# 如果结束索引为-1,则表示当前截取的字符串为全部
127.0.0.1:6379> GETRANGE k1 0 -1
"hello,xiaohuang"
###############实现字符串的替换效果#################
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 2 hello # 实现字符串的替换效果,命令中的数字“2”表示从索引2的位置开始将其替换为指定字符串
(integer) 7
127.0.0.1:6379> get key2
"abhello"
##################################################
# setex(set with expire) # 设置过期时间
# setnx(set with not exist) # 如果key不存在,创建(分布式锁中常用)
127.0.0.1:6379> setex k3 10 v3 # 设置一个k3,过期时间为10秒
OK
127.0.0.1:6379> keys *
1) "k1"
2) "key2"
3) "name"
4) "views"
5) "k3"
# 10秒之后会自动删除
127.0.0.1:6379> keys *
1) "k1"
2) "key2"
3) "name"
4) "views"
127.0.0.1:6379> setnx lan redis # 如果key不存在,即创建
(integer) 1 
127.0.0.1:6379> setnx lan mongodb
(integer) 0 # 0表示没有设置成功,也可理解为“有0行受到影响”
127.0.0.1:6379> get lan
"redis"
######################一次性设置(获取)多个键值对#####################
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> KEYS * 
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
# 也可以在这边的语法前面加上一个m,代表设置多个
127.0.0.1:6379> msetnx k1 vv1 k4 v4 
(integer) 0
# 但是这边同时设置多个值,如果有一个key已经存在,那么这一条设置语句便不会执行成功,
# 因为Redis单条语句是原子操作,要么同时成功,要么同时失败
127.0.0.1:6379> keys * 
1) "k2"
2) "k1"
3) "k3"
# 在Redis中,还推荐了一个比较有意思的东西
# 这是Redis中关于key的命名,可以用“:”来代表层次结构,可以对指定的key进行分类存储
127.0.0.1:6379> mset user:1:name xiaohuang user:1:age 21
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "xiaohuang"
2) "21"
127.0.0.1:6379> getset sqlan redis # 先获取当前key指定的value,如果不存在,会返回nil(null),然后设置新值
(nil)
127.0.0.1:6379> get sqlan
"redis"
127.0.0.1:6379> getset sqlan hbase
"redis"
127.0.0.1:6379> get sqlan
"hbase"
####################################################################

String类似的使用场景:value除了是字符串还可以是数字,用途举例:

	计数器
	统计多单位的数量:uid:123666:follow 0
	粉丝数
	对象存储缓存

List(列表)

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

首先我们列表,可以经过规则定义将其变为队列、栈、双端队列等。

在这里插入图片描述
头插法:

	头部插入值,然后将原有的值向右挤过去

尾插法:

	尾部插入值,然后将原有的值向左挤过去
# 命令:
# LPUSH key value1 value2 ...             设置一个key,从头部插入数据(头插法)
# RPUSH key value1 value2 ...			  设置一个key,从尾部插入数据(尾插法)
# LRANGE key startIndex endIndex          返回列表中从开始索引到结束索引位置的value值
# LPOP key                                从key头部弹出一个value(如:移除list第一个元素)
# RPOP key                                从尾部弹出一个value
# LINDEX index                            返回key中指定索引的value值
# LREM key n value                        删除key中的指定的value值,n代表删除几个
# LLEN key                                返回key的长度
# LTRIM key startIndex endIndex           截取key,截取的范围从开始索引到结束索引
# LSET key index value                    从当前key的索引开始插入指定的value值
# RPOPLPUSH key1 key2                     从key1的尾部弹出一个元素,将此元素从key2的头部插入
# LINSERT key BEFORE|AFTER oldValue newValue 从指定key中已存在的value的前面或者后面插入一个指定的value
————————————————

---------------------------LPUSH---RPUSH---LRANGE--------------------------------

127.0.0.1:6379> LPUSH mylist k1 # LPUSH mylist=>{1}
(integer) 1
127.0.0.1:6379> LPUSH mylist k2 # LPUSH mylist=>{2,1}
(integer) 2
127.0.0.1:6379> RPUSH mylist k3 # RPUSH mylist=>{2,1,3}
(integer) 3
127.0.0.1:6379> get mylist # 普通的get是无法获取list值的
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> LRANGE mylist 0 4 # LRANGE 获取起止位置范围内的元素(从左边开始拿)
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 2
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> LRANGE mylist 0 1
1) "k2"
2) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1 # 获取全部元素
1) "k2"
2) "k1"
3) "k3"

---------------------------LPUSHX---RPUSHX-----------------------------------

127.0.0.1:6379> LPUSHX list v1 # list不存在 LPUSHX失败
(integer) 0
127.0.0.1:6379> LPUSHX list v1 v2  
(integer) 0
127.0.0.1:6379> LPUSHX mylist k4 k5 # 向mylist中 左边 PUSH k4 k5
(integer) 5
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k1"
5) "k3"

---------------------------LINSERT--LLEN--LINDEX--LSET----------------------------

127.0.0.1:6379> LINSERT mylist after k2 ins_key1 # 在k2元素后 插入ins_key1
(integer) 6
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "ins_key1"
5) "k1"
6) "k3"
127.0.0.1:6379> LLEN mylist # 查看mylist的长度
(integer) 6
127.0.0.1:6379> LINDEX mylist 3 # 获取下标为3的元素
"ins_key1"
127.0.0.1:6379> LINDEX mylist 0
"k5"
127.0.0.1:6379> LSET mylist 3 k6 # 将下标3的元素 set值为k6
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k5"
2) "k4"
3) "k2"
4) "k6"
5) "k1"
6) "k3"

---------------------------LPOP--RPOP--------------------------

127.0.0.1:6379> LPOP mylist # 左侧(头部)弹出
"k5"
127.0.0.1:6379> RPOP mylist # 右侧(尾部)弹出
"k3"

---------------------------RPOPLPUSH--------------------------

127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
3) "k6"
4) "k1"
127.0.0.1:6379> RPOPLPUSH mylist newlist # 将mylist的最后一个值(k1)弹出,加入到newlist的头部
"k1"
127.0.0.1:6379> LRANGE newlist 0 -1
1) "k1"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"
3) "k6"

---------------------------LTRIM--------------------------

127.0.0.1:6379> LTRIM mylist 0 1 # 截取mylist中的 0~1部分
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "k4"
2) "k2"

# 初始 mylist: k2,k2,k2,k2,k2,k2,k4,k2,k2,k2,k2
---------------------------LREM--------------------------

127.0.0.1:6379> LREM mylist 3 k2 # 从头部开始搜索 至多删除3个 k2
(integer) 3
# 删除后:mylist: k2,k2,k2,k4,k2,k2,k2,k2

127.0.0.1:6379> LREM mylist -2 k2 #从尾部开始搜索 至多删除2个 k2
(integer) 2
# 删除后:mylist: k2,k2,k2,k4,k2,k2


---------------------------BLPOP--BRPOP--------------------------

mylist: k2,k2,k2,k4,k2,k2
newlist: k1

127.0.0.1:6379> BLPOP newlist mylist 30 # 从newlist中弹出第一个值,mylist作为候选
1) "newlist" # 弹出
2) "k1"
127.0.0.1:6379> BLPOP newlist mylist 30
1) "mylist" # 由于newlist空了 从mylist中弹出
2) "k2"
127.0.0.1:6379> BLPOP newlist 30
(30.10s) # 超时了

127.0.0.1:6379> BLPOP newlist 30 # 我们连接另一个客户端向newlist中push了test, 阻塞被解决。
1) "newlist"
2) "test"
(12.54s)

  • 总结:

      list实际上是一个链表,before Node after , left, right 都可以插入值
      如果key不存在,则创建新的链表
      如果key存在,新增内容
      如果移除了所有值,空链表,也代表不存在
      在两边插入或者改动值,效率最高!修改中间元素,效率相对较低(结构原因,本质是一个链表)
    
  • 应用:

      消息排队
      消息队列(Lpush Rpop)
      栈(Lpush Lpop)
    

Set(集合)

	Redis的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
	Redis 中 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
# 命令:
# SADD key value1 value2 ...          设置一个key
# SMEMBERS key                        查看当前key
# SISMEMBER key value                 查看key中指定的value是否存在
# SCARD key                           查看key的长度
# SREM key value                      删除key中的指定value
# SPOP key                            随机删除key中的一个value
# SRANDMEMBER key [n]                 随机查看当前key中的一个或者多个value
# SMOVE key1 key2 key1的Value         将key1中的value移动到key2中
# SDIFF key1 key2                     两个key相交,求第一个key的补集
# SINTER key1 key2                    两个key相交,求交集
# SUNION key1 key2                    两个key相交,求并集
————————————————

---------------SADD--SCARD--SMEMBERS--SISMEMBER--------------------

127.0.0.1:6379> SADD myset m1 m2 m3 m4 # 向myset中增加成员 m1~m4
(integer) 4
127.0.0.1:6379> SCARD myset # 获取集合的成员数目
(integer) 4
127.0.0.1:6379> smembers myset # 获取集合中所有成员
1) "m4"
2) "m3"
3) "m2"
4) "m1"
127.0.0.1:6379> SISMEMBER myset m5 # 查询m5是否是myset的成员
(integer) 0 # 不是,返回0
127.0.0.1:6379> SISMEMBER myset m2
(integer) 1 # 是,返回1
127.0.0.1:6379> SISMEMBER myset m3
(integer) 1

---------------------SRANDMEMBER--SPOP----------------------------------

127.0.0.1:6379> SRANDMEMBER myset 3 # 随机返回3个成员
1) "m2"
2) "m3"
3) "m4"
127.0.0.1:6379> SRANDMEMBER myset # 随机返回1个成员
"m3"
127.0.0.1:6379> SPOP myset 2 # 随机移除并返回2个成员
1) "m1"
2) "m4"
# 将set还原到{m1,m2,m3,m4}

---------------------SMOVE--SREM----------------------------------------

127.0.0.1:6379> SMOVE myset newset m3 # 将myset中m3成员移动到newset集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "m4"
2) "m2"
3) "m1"
127.0.0.1:6379> SMEMBERS newset
1) "m3"
127.0.0.1:6379> SREM newset m3 # 从newset中移除m3元素
(integer) 1
127.0.0.1:6379> SMEMBERS newset
(empty list or set)

# 下面开始是多集合操作,多集合操作中若只有一个参数默认和自身进行运算
# setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}

-----------------------------SDIFF------------------------------------

127.0.0.1:6379> SDIFF setx sety setz # 等价于setx-sety-setz
1) "m4"
127.0.0.1:6379> SDIFF setx sety # setx - sety
1) "m4"
2) "m1"
127.0.0.1:6379> SDIFF sety setx # sety - setx
1) "m5"


-------------------------SINTER---------------------------------------
# 共同关注(交集)
# setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}
127.0.0.1:6379> SINTER setx sety setz # 求 setx、sety、setx的交集
1) "m6"
127.0.0.1:6379> SINTER setx sety # 求setx sety的交集
1) "m2"
2) "m6"

-------------------------SUNION---------------------------------------
# setx=>{m1,m2,m4,m6}, sety=>{m2,m5,m6}, setz=>{m1,m3,m6}
127.0.0.1:6379> SUNION setx sety setz # setx sety setz的并集
1) "m4"
2) "m6"
3) "m3"
4) "m2"
5) "m1"
6) "m5"
127.0.0.1:6379> SUNION setx sety # setx sety 并集
1) "m4"
2) "m6"
3) "m2"
4) "m1"
5) "m5"

Hash(哈希)

Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
Set就是一种简化的Hash,只变动key,而value使用默认值填充。可以将一个Hash表作为一个对象进行存储,表中存放对象的信息。

# 命令:
# HSET key field value                 设置单个hash
# HSETNX key field value			   如果不存在可以设置,如果存在则不能设置
# HGET key field                       获取单个
# HMSET key field1 v1 field2 v2        设置多个
# HMGET key field1 field2              获取多个
# HGETALL key                          获取hash中全部的field-value
# HLEN key                             获取hash长度
# HEXISTS key field                    查询hash中指定的field是否存在
# HKEYS key                            只获取hash中的field
# HVALS key                            只获取hash中value
# HINCRBY key field n                  对hash中指定的field设置自增自减
————————————————

------------------------HSET--HMSET--HSETNX----------------
127.0.0.1:6379> HSET studentx name sakura # 将studentx哈希表作为一个对象,设置name为sakura
(integer) 1
127.0.0.1:6379> HSET studentx name gyc # 重复设置field进行覆盖,并返回0
(integer) 0
127.0.0.1:6379> HSET studentx age 20 # 设置studentx的age为20
(integer) 1
127.0.0.1:6379> HMSET studentx sex 1 tel 15623667886 # 设置sex为1,tel为15623667886
OK
127.0.0.1:6379> HSETNX studentx name gyc # HSETNX 设置已存在的field
(integer) 0 # 失败
127.0.0.1:6379> HSETNX studentx email 12345@qq.com
(integer) 1 # 成功

----------------------HEXISTS--------------------------------
127.0.0.1:6379> HEXISTS studentx name # name字段在studentx中是否存在
(integer) 1 # 存在
127.0.0.1:6379> HEXISTS studentx addr
(integer) 0 # 不存在

-------------------HGET--HMGET--HGETALL-----------
127.0.0.1:6379> HGET studentx name # 获取studentx中name字段的value
"gyc"
127.0.0.1:6379> HMGET studentx name age tel # 获取studentx中name、age、tel字段的value
1) "gyc"
2) "20"
3) "15623667886"
127.0.0.1:6379> HGETALL studentx # 获取studentx中所有的field及其value
 1) "name"
 2) "gyc"
 3) "age"
 4) "20"
 5) "sex"
 6) "1"
 7) "tel"
 8) "15623667886"
 9) "email"
10) "12345@qq.com"


--------------------HKEYS--HLEN--HVALS--------------
127.0.0.1:6379> HKEYS studentx # 查看studentx中所有的field
1) "name"
2) "age"
3) "sex"
4) "tel"
5) "email"
127.0.0.1:6379> HLEN studentx # 查看studentx中的字段数量
(integer) 5
127.0.0.1:6379> HVALS studentx # 查看studentx中所有的value
1) "gyc"
2) "20"
3) "1"
4) "15623667886"
5) "12345@qq.com"

-------------------------HDEL--------------------------
127.0.0.1:6379> HDEL studentx sex tel # 删除studentx 中的sex、tel字段
(integer) 2
127.0.0.1:6379> HKEYS studentx
1) "name"
2) "age"
3) "email"

-------------HINCRBY--HINCRBYFLOAT------------------------
127.0.0.1:6379> HINCRBY studentx age 1 # studentx的age字段数值+1
(integer) 21
127.0.0.1:6379> HINCRBY studentx name 1 # 非整数字型字段不可用
(error) ERR hash value is not an integer
127.0.0.1:6379> HINCRBYFLOAT studentx weight 0.6 # weight字段增加0.6
"90.8"

	Hash适合存变更的数据user name age,尤其是用户信息之类的,经常变动的信息!
	Hash更适合于对象的存储,Sring更加适合字符串存储!

Zset(有序集合)

	不同的是每个元素都会关联一个double类型的分数(score)。redis正是通过分数来为集合中的成员进行从小到大的排序。
	score相同:按字典顺序排序
	有序集合的成员是唯一的,但分数(score)却可以重复。
# 命令:
# ZADD key score1 value1 score2 value2 ...        zset中添加一个或多个元素
# ZRANGE key startIndex endIndex                  查询从开始到结束索引的zset集合
# ZRANGEBYSCORE key min max [WITHSCORES]          对hash中按照指定数值进行升序排列
# ZREVRANGE key startIndex endIndex               对指定开始和结束索引进行降序排列
# ZREM key field                                  删除hash中指定的field
# ZCARD key                                       查询hash长度
# ZCOUNT key [min max]       查询hash数量,还可以增加最大值和最小值的范围
————————————————

-------------------ZADD--ZCARD--ZCOUNT--------------
127.0.0.1:6379> ZADD myzset 1 m1 2 m2 # 向有序集合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测试
    

三种特殊数据类型

Geospatial(地理位置)

使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用

  • 有效经纬度

      有效的经度从-180度到180度。 有效的纬度从-85.05112878度到85.05112878度
    
  • 指定的单位

      m 表示单位为米。
      km 表示单位为千米。
      mi 表示单位为英里。
      ft 表示单位为英尺。
    
  • 关于GEORADIUS的参数

      通过georadius就可以完成 附近的人功能
      withcoord:带上坐标
      withdist:带上距离,单位与半径单位相同
      COUNT n : 只显示前n个(按距离递增排序)
    
----------------georadius---------------------
127.0.0.1:6379> GEORADIUS china:city 120 30 500 km withcoord withdist # 查询经纬度(120,30)坐标500km半径内的成员
1) 1) "hangzhou"
   2) "29.4151"
   3) 1) "120.20000249147415"
      2) "30.199999888333501"
2) 1) "shanghai"
   2) "205.3611"
   3) 1) "121.40000134706497"
      2) "31.400000253193539"
     
------------geohash---------------------------
127.0.0.1:6379> geohash china:city yichang shanghai # 获取成员经纬坐标的geohash表示
1) "wmrjwbr5250"
2) "wtw6ds0y300"

  • GEOADD
# geoadd 添加地理位置
# 语法:GEOADD key 经度 纬度 城市名称 ...
# 注意:南北极无法直接添加。用添加城市数据来说,一般都会使用Java的Jedis来操作,而这些城市数据都是被下载下来通过JavaAPI调用
# 有效经度从-180180度
# 有效纬度从-85.0511287885.05112878 度。超过范围会出现(error) ERR invalid longitude,latitude pair
127.0.0.1:6379> GEOADD china:city 116.40 39.90 beijing 121.47 31.23 shanghai
(integer) 2
127.0.0.1:6379> GEOADD china:city 113.28 23.12 guangzhou 114.08 22.54 shenzhen
(integer) 2
127.0.0.1:6379> GEOADD china:city 119.30 26.07 fuzhou 118.11 24.49 xiamen
(integer) 2

  • GEOPOS
# 语法:GEOPOS key member1 member2 ...
127.0.0.1:6379> GEOPOS china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city shanghai guangzhou # 获取一个或多个地理信息
1) 1) "121.47000163793563843"
   2) "31.22999903975783553"
2) 1) "113.27999979257583618"
   2) "23.1199990030198208"

  • GEODIST
    GEODIST命令表示指定两个位置的距离
# 语法:GEODIST key member1 member2 [unit]
# 后面的unit加了中括号表示可选操作,即当前这个命令计算出来的结果为m(米)
127.0.0.1:6379> GEODIST china:city beijing shanghai # 查看beijing和shanghai两个位置的直线距离
"1067378.7564"
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing fuzhou km
"1561.6135"
127.0.0.1:6379> 

  • GEORADIUS
    对于社交软件来说,附近的人,就相当于,你现在所在的地址,再加上一定的半径来进行查找

位置找!

# 语法:GEORADIUS key 经度 纬度 半径 [单位] [WITHCOORD(搜寻到的目标的经纬度)] [WITHDIST(直线距离)] [count] 
127.0.0.1:6379> GEORADIUS china:city 111 31 1000 km 
# 以111经度31纬度为中心,1000km为半径搜寻在器范围之内的城市
1) "shenzhen"
2) "guangzhou"
3) "fuzhou"
4) "shanghai"
127.0.0.1:6379> GEORADIUS china:city 111 31 1000 km WITHCOORD WITHDIST # 追加参数,目标经纬度,直线距离
1) 1) "shenzhen"
   2) "989.2821"
   3) 1) "114.08000081777572632"
      2) "22.53999903789756587"
2) 1) "guangzhou"
   2) "905.0108"
   3) 1) "113.27999979257583618"
      2) "23.1199990030198208"
3) 1) "fuzhou"
   2) "978.4847"
   3) 1) "119.29999798536300659"
      2) "26.06999873822022806"
4) 1) "shanghai"
   2) "996.9549"
   3) 1) "121.47000163793563843"
      2) "31.22999903975783553"
127.0.0.1:6379> GEORADIUS china:city 111 31 1000 km WITHCOORD WITHDIST count 2 # 还可以限制查询的结果条数,只显示两条
1) 1) "guangzhou"
   2) "905.0108"
   3) 1) "113.27999979257583618"
      2) "23.1199990030198208"
2) 1) "fuzhou"
   2) "978.4847"
   3) 1) "119.29999798536300659"
      2) "26.06999873822022806"

  • GEORADIUSBYMEMBER
    找出指定元素周围的其他元素,就是以城市为中心,一定长度为半径搜索member
# 语法:GEORADIUSBYMEMBER key member 长度 [unit]单位
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 2500 km 
1) "shenzhen"
2) "guangzhou"
3) "xiamen"
4) "fuzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 1000 km # 找出以shanghai为中心,1000km为半径搜索
1) "xiamen"
2) "fuzhou"
3) "shanghai"
127.0.0.1:6379> 

  • GEOHASH
    返回一个或多个元素的GeoHash表示,该命令返回11个字符组成的GeoHash字符串
# GEOHASH key member
127.0.0.1:6379> GEOHASH china:city beijing 
1) "wx4fbxxfke0"#这个就是经纬度字符串
# 多聊一嘴GeoHash,Redis原先是将二维的经纬度通过一定的策略转换为一维的52位的字符串编码,这个时候的编码描述的地理位置是准确的,这种操作俗称“降维打击”
# 当前命令相比于52位的字符串来说,它砍掉了右边的大多数字符串,这也意味着它失去了一定的精度,但是地理位置的指向不变

  • Geospatial的底层
    实际上它就是一个zset集合,geospatial数据类型是zset的一层封装,故也可使用zset命令去操作
127.0.0.1:6379> ZRANGE china:city 0 -1 # 使用zset命令查看geospatial
1) "shenzhen"
2) "guangzhou"
3) "xiamen"
4) "fuzhou"
5) "shanghai"
6) "beijing" 

并且在这个特殊的数据类型中,并没有删除操作,因为使用zset的基本命令即可删除

127.0.0.1:6379> ZREM china:city beijing
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "shenzhen"
2) "guangzhou"
3) "xiamen"
4) "fuzhou"
5) "shanghai"

Hyperloglog(基数统计)

	Redis HyperLogLog 是用来做基数统计的算法
	HyperLogLog的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
	花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。
	因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog不能像集合那样,返回输入的各个元素。
	其底层使用string数据类型
  • 什么是基数

      如现在有两个数据集
      A{1,3,5,7,8},
      B{1,3,5,7,8,7}
      数据集中不重复的元素5个
      基数就是5
    
  • 应用场景

      网页的访问量(UV):一个用户多次访问,也只	能算作一个人。
      传统实现,存储用户的id,然后每次进行比较。(通过用户的id,来计数访问量)当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,Hyperloglog就能帮助我们利用最小的空间完成。
    
----------PFADD--PFCOUNT---------------------
127.0.0.1:6379> PFADD myelemx a b c d e f g h i j k # 添加元素
(integer) 1
127.0.0.1:6379> type myelemx # hyperloglog底层使用String
string
127.0.0.1:6379> PFCOUNT myelemx # 估算myelemx的基数
(integer) 11
127.0.0.1:6379> PFADD myelemy i j k z m c b v p q s
(integer) 1
127.0.0.1:6379> PFCOUNT myelemy
(integer) 11

----------------PFMERGE-----------------------
127.0.0.1:6379> PFMERGE myelemz myelemx myelemy # 合并myelemx和myelemy 成为myelemz,
OK
127.0.0.1:6379> PFCOUNT myelemz # 估算基数
(integer) 17

	如果允许容错,那么一定可以使用Hyperloglog !
	如果不允许容错,就使用set或者自己的数据类型即可 !

BitMaps(位图)

	使用位存储,信息状态只有 0 和 1
	Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。
  • 应用场景

      签到统计、状态统计
      比如使用它来记录周一到周日的打卡,打了卡为1,没打卡为0
    
------------setbit--getbit--------------
127.0.0.1:6379> setbit sign 0 1 # 设置sign的第0位为 1 
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 设置sign的第2位为 1  不设置默认是0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> type sign
string

127.0.0.1:6379> getbit sign 2 # 获取第2位的数值
(integer) 1
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 4 # 未设置默认是0
(integer) 0

-----------bitcount----------------------------
127.0.0.1:6379> BITCOUNT sign # 统计sign中为1的位数
(integer) 4

事务

Redis事务本质:一组命令的集合。事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。一次性、顺序性、排他性。

	这里有三个set,顺序执行,在执行前入队,当发起执行命令才会执行
	----------------- 队列 set set set 执行 -------------------

Redis事务没有隔离级别的概念,也就不会出现关系型数据库幻读、脏读、不可重复读
Redis的单条命令是保证原子性的,但是redis事务不能保证原子性

Redis事务操作过程

  • 三步

      开启事务(multi)
      命令入队(…)
      执行事务(exec)
      
      所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性完成
    
127.0.0.1:6379> multi # 1.开启事务
OK
127.0.0.1:6379> set k1 v1 # 2.命令入队
QUEUED       			  #出现QUEUED,表示入队了
127.0.0.1:6379> set k2 v2 # ..
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec # 3.事务执行
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"
   2) "k2"
   3) "k1"

  • 取消事务(discurd)
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD # 放弃事务
OK
127.0.0.1:6379> EXEC 
(error) ERR EXEC without MULTI # 当前未开启事务
127.0.0.1:6379> get k1 # 被放弃事务中命令并未执行
(nil)

事务错误

  • 代码语法错误(编译时异常)所有的命令都不执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> error k1 # 这是一条语法错误命令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队 
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 执行报错
127.0.0.1:6379> get k1 
(nil) # 所有命令并没有被执行

  • 代码逻辑错误 (运行时异常) 其他命令可以正常执行,所以不保证事务原子性
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> INCR k1 # 这条命令逻辑错误(对字符串进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行

# 虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。
# 所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。

监控

  • 悲观锁

      很悲观,认为什么时候都会出现问题,无论做什么都会加锁
    
  • 乐观锁

      很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数
      获取version
      更新的时候比较version
    

正常执行

127.0.0.1:6379> set money 100 # 设置余额:100
OK
127.0.0.1:6379> set use 0 # 支出使用:0
OK
127.0.0.1:6379> watch money # 监视money (也就是上锁)
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
127.0.0.1:6379> exec # 监视值没有被中途修改,事务正常执行
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis的乐观锁操作(相当于getversion)

  • 启动另外一个客户端模拟插队线程

      我们在线程1里边使用watch来监视money,然后进行money减20,和use加20进行入队操作。
      然后我们在线程1未进行执行之前,使用线程2来对money进行修改
      最后回到线程1,执行失败
    

线程1:

127.0.0.1:6379> watch money # money上锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY use 20
QUEUED
127.0.0.1:6379> 	# 此时事务并没有执行

模拟线程插队,线程2:

127.0.0.1:6379> INCRBY money 500 # 修改了线程一中监视的money
(integer) 600

回到线程1,执行事务:

127.0.0.1:6379> EXEC # 执行之前,另一个线程修改了我们的值,这个时候就会导致事务执行失败
(nil) # 没有结果,说明事务执行失败

127.0.0.1:6379> get money # 线程2 修改生效
"600"
127.0.0.1:6379> get use # 线程1事务执行失败,数值没有被修改
"0"

解锁获取最新值,然后再加锁进行事务,就能重新执行。unwatch进行解锁。

127.0.0.1:6379> unwatch    #1.解锁
127.0.0.1:6379> watch money #2.再加锁,此时相当于刷新了money
#3.最后exec的时候再进行判断

  • 应用
    redis实现秒杀功能
    注意:每次提交执行exec后都会自动释放锁,不管是否成功

Redis.conf(重要)

Redis启动的时候,就通过配置文件来启动

  • 单位:单位大小写不敏感,1GB,1Gb,1gB是一样的
    在这里插入图片描述
  • Includes这里告诉我们可以将,多个redis.config组合成一个,类比jsp中的include
    在这里插入图片描述
  • 网络配置部分
    在这里插入图片描述
  • GENERAL部分,这里涉及较多就不使用图了,改为代码块笔记
daemonize yes #以守护进程的方式去运行,默认为no,需要自己改为yes,这样就可以使得redis后台运行

pidfile /var/run/redis_6379.pid   #配置文件的pid文件

# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# 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 notice			#日志,上方就是取值,有四种

logfile ""				#日志的输出,“”表示输出,写位置就可以生成日志文件

databases 16			#数据库的数量,默认16个

always-show-logo no		#是否显示logo

  • 快照SNAPSHOTTING部分

持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb和.aof

redis是内存数据库,如果不持久化就会丢数据,断个电就G了

# Unless specified otherwise, by default Redis will save the DB:
#   * After 3600 seconds (an hour) if at least 1 key changed
#   * After 300 seconds (5 minutes) if at least 100 keys changed
#   * After 60 seconds if at least 10000 keys changed
#
# You can set these explicitly by uncommenting the three following lines.
#
#注意上方源代码中的英文
#如果3600s内有1个key进行了修改就,进行持久化操作
save 3600 1
#如果300s内有100个key进行了修改就,进行持久化操作
save 300 100
#如果60s内有10000个key进行了修改就,进行持久化操作
save 60 10000



stop-writes-on-bgsave-error yes	#持久化出现错误之后是否继续工作

rdbcompression yes	#是否压缩rdb文件(rdb就是持久化的文件),需要消耗部分CPU资源

rdbchecksum yes		#保存rdb文件的时候,是否进行错误的检查校验

dir ./				#rdb文件保存的目录,默认是当前目录下

  • REPLICATION部分,复制部分,主从复制部分再讲
appendonly no	#默认不开启aof使用rdb方式持久化,改为yes即可启动

appendfilename "appendonly.aof"	#默认文件名字为appendonly.aof

# appendfsync always #每次修改都会snyc,比较消耗性能	
appendfsync everysec #每秒执行一次snyc,如果这一秒出现问题,可能会丢失这1s的数据
# appendfsync no	#不sync,这个时候操作系统自己同步数据,速度最快

  • SECURITY(安全)部分:可以对redis设置一下密码,默认没有密码
    可以直接在配置文件中写,这里写入的是123456
    在这里插入图片描述
  • CLIENTS部分
maxclients 10000  #最大客户端数量
  • MEMORY MANAGEMENT部分
maxmemory <bytes> #最大内存限制

maxmemory-policy noeviction # 内存达到最大值的处理策略
#处理策略如下:
#redis2.6+版本,redis.conf中的默认的策略是 noeviction ,一共有八种:
#1、volatile-lru:只对设置了过期时间的key进行LRU算法进行删除
#2、allkeys-lru : 对所有key执行LRU算法进行删除
#3、volatile-lfu:只对设置了过期时间的key进行LFU算法进行删除
#4、allkeys-lfu:对所有key执行LFU算法进行删除
#5、volatile-random:随机删除设置有过期时间的key
#6、allkeys-random:随机删除
#7、volatile-ttl : 删除即将过期的
#8、noeviction : 永不过期,返回错误

  • APPEND ONLY MODE部分:也就是我们的aof的一些配置,aof是第二种持久化的方式
appendonly no	#默认不开启,而是使用rdb持久化方式,大部分情况下rdb基本够用

appendfilename "appendonly.aof"	#持久化后文件的名字

# appendfsync always #每次修改都会snyc,比较消耗性能	
appendfsync everysec #每秒执行一次snyc,如果这一秒出现问题,可能会丢失这1s的数据
# appendfsync no	#不sync,这个时候操作系统自己同步数据,速度最快

持久化—RDB

Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能!

RDB:Redis Databases
在这里插入图片描述
在指定的时间间隔内将内存中的数据集快照写入磁盘,也是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。

	Redis会单独创建(fork)一个子进程来进行持久化会先将数据写入到一个临时文件中,待持久化过程结束了,在用这个临时文件替换上次持久化好的文件。
	整个过程中,主进程时不进行任何IO操作的。这就确保了极高的性能。如果需要进行大规模数据的回复,且对于数据恢复的完整性不是非常敏感,那RDB要比AOF方式更加高效。
	RDB的缺点是最后一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改这个配置!

有时候在生产环境我们会将这个文件进行备份!

rdb保存的文件是dump.rdb 都是在我们的配置文件中快照中进行配置的!

什么是RDB

  • 在指定时间间隔后,将内存中的数据集快照写入数据库 ;

  • 在恢复时候,直接读取快照文件,进行数据的恢复 ;
    在这里插入图片描述

      默认情况下, Redis 将数据库快照保存在名字为 dump.rdb的二进制文件中。
      文件名可以在配置文件中进行自定义。
    

工作原理

在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;

  • Redis 调用forks。同时拥有父进程和子进程。
  • 子进程将数据集写入到一个临时 RDB 文件中。
  • 子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求)

在这里插入图片描述

触发机制(生成rdb文件)

	save的规则满足的情况下,会自动触发rdb原则(创建rdb文件)
	执行flushall命令,也会触发我们的rdb原则(创建rdb文件)
	退出redis,也会自动产生rdb文件
  • save命令

      立刻对当前内存中的数据进行持久化 ,但是会**阻塞**,也就是不接受其他操作了
    

    在这里插入图片描述
    save命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求

  • bgsave命令
    bgsave 是异步进行,进行持久化的时候,redis 还可以将继续响应客户端请求(很不错)
    在这里插入图片描述

  • bgsave和save对比
    在这里插入图片描述
    SAVE 和 BGSAVE 两个命令都会调用 rdbSave 函数,但它们调用的方式各有不同:

      SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求。
      
      BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成。
      Redis服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求。
    
  • flushall命令
    flushall 命令也会触发持久化

  • 自动触发
    自动触发是由我们的配置文件来完成的。在redis.conf配置文件中,里面有如下配置,我们可以去设置,就是之前我们看的快照部分的N 秒内数据集至少有 M 个改动就自动生成一个rdb

在这里插入图片描述

恢复rdb文件

  • 只需要将rdb文件放在我们redis启动目录就可以, redis启动的时候会自动检查dump.rdb恢复其中的数据!

  • 查看我们的启动目录
    在这里插入图片描述

rdb的优缺点

优点:

	适合大规模的数据恢复
	对数据的完整性要求不高

缺点:

	需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了
	fork进程的时候,会占用一定的内存空间

持久化-AOF

AOF:Append Only File

将我们所有的命令都记录下来,history,恢复的时候就把这个文件全部再执行一遍
在这里插入图片描述
以日志的形式来记录每个写的操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

什么是AOF

  • rdb配置文件
appendonly no	#默认不开启aof使用rdb方式持久化,改为yes即可启动

appendfilename "appendonly.aof"	#默认文件名字为appendonly.aof

# appendfsync always #每次修改都会snyc,比较消耗性能	
appendfsync everysec #每秒执行一次snyc,如果这一秒出现问题,可能会丢失这1s的数据
# appendfsync no	#不sync,这个时候操作系统自己同步数据,速度最快

#rewrite 重写
no-appendfsync-on-rewrite no	#是否开启
auto-aof-rewrite-percentage 100	#当前写入日志文件的大小超过上一次rewrite之后的文件大小的百分之100时就是2倍时触发Rewrite
auto-aof-rewrite-min-size 64mb	#当超过64mb就fork一个新进程来将我们的文件进行重写

AOF本质是文件的无限制的追加,文件越来越大,所以,我们超过64m,就重新整一个

  • 如果appendonly.aof文件有错位,这时候redis是启动不起来的,我需要修改这个文件

redis给我们提供了一个工具redis-check-aof --fix
在这里插入图片描述

优点和缺点

优点

	每一次修改都会同步,文件的完整性会更加好
	没秒同步一次,可能会丢失一秒的数据
	从不同步,效率最高

缺点

	相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
	Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化

RDB 和 AOF 对比

在这里插入图片描述
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能。
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
有很多用户都只使用 AOF 持久化, 但并不推荐这种方式: 因为定时生成 RDB 快照(snapshot)非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快。

Redis发布与订阅

在这里插入图片描述

  • 示例
------------订阅端----------------------
127.0.0.1:6379> SUBSCRIBE sakura # 订阅sakura频道
Reading messages... (press Ctrl-C to quit) # 等待接收消息
1) "subscribe" # 订阅成功的消息
2) "sakura"
3) (integer) 1
#当发布端发布了消息后,这边自动的刷新接收到,下方就是发布端发送的消息
1) "message" # 接收到来自sakura频道的消息 "hello world"
2) "sakura"#哪个频道的信息
3) "hello world"#具体的内容
1) "message" # 接收到来自sakura频道的消息 "hello i am sakura"
2) "sakura"
3) "hello i am sakura"

--------------消息发布端-------------------
127.0.0.1:6379> PUBLISH sakura "hello world" # 发布消息到sakura频道
(integer) 1
127.0.0.1:6379> PUBLISH sakura "hello i am sakura" # 发布消息
(integer) 1

-----------------查看活跃的频道------------
127.0.0.1:6379> PUBSUB channels
1) "sakura"

  • 原理
    每个 Redis 服务器进程都维持着一个表示服务器状态的 redis.h/redisServer 结构, 结构的 pubsub_channels 属性是一个字典, 这个字典就用于保存订阅频道的信息,其中,字典的键为正在被订阅的频道, 而字典的值则是一个链表, 链表中保存了所有订阅这个频道的客户端。

在这里插入图片描述
图中就是channel1,有client2,5,1订阅

客户端订阅,就被链接到对应频道的链表的尾部,退订则就是将客户端节点从链表中移除

  • 使用场景

      消息订阅:公众号订阅,微博关注等等(其实更多是使用消息队列来进行实现)
      多人在线聊天室
    
      稍微复杂的场景,我们就会使用消息中间件MQ处理
    
  • 缺点

如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。

这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在断线期间发布者发布的消息。

Redis主从复制

概念

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。

默认情况下,每台Redis服务器都是主节点,一个主节点可以有0个或者多个从节点,但每个从节点只能由一个主节点。

作用

  • 数据冗余

      主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式。
    
  • 故障恢复

      当主节点故障时,从节点可以暂时替代主节点提供服务,是一种服务冗余的方式
    
  • 负载均衡

      在主从复制的基础上,配合读写分离,由主节点进行写操作,从节点进行读操作,分担服务器的负载;尤其是在多读少写的场景下。
      通过多个从节点分担负载,提高并发量。
    
  • 高可用(集群)基石

      主从复制还是哨兵和集群能够实施的基础。
    

为什么使用集群

单台服务器难以负载大量的请求
单台服务器故障率高,系统崩坏概率大
单台服务器内存容量有限。

电商网站上的商品一般都是一次上传,无数次浏览的,说专业点就是多读少写

  • 对于这种场景,我们可以使用如下这种架构
    在这里插入图片描述

  • 主从复制,读写分离!
    80%的情况下都是进行读操作!减缓服务器的压力!架构中经常使用!一主二从!(最低配,上图为一主三从)
    只要在公司中,主从复制就是必须要使用的,因为在真实的项目中不可能单机使用Redis!

环境配置

  • 们启动redis-server,然后启动redis-cli,可以查看目前的库信息
redis-server pengconfig/redis.conf #启动redis-server
redis-cli -p 6379	#启动redis-cli
127.0.0.1:6379> info replication	#查看当前库信息
# Replication
role:master	#角色,master,主库
connected_slaves:0	#连接的从机有几个,此为0个
master_failover_state:no-failover
master_replid:c7d1c5a78528f916bf9e4e739e87c56d4c598739
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

  • redis.config修改部分,以redis6380.config为例,修改三个地方
port 6380	#端口号
daemonize yes	#后台运行打开
pidfile /var/run/redis_6380.pid	#pid文件名
logfile "6380.log"		#日志文件名
dbfilename dump6380.rdb	#rdb文件名

  • 主要使用以下红框中的配置文件进行测试
    在这里插入图片描述
  • 开启四个服务,前三个就开数据库,第四个进行查看测试
  • 在这里插入图片描述
redis-server pengconfig/redis6379.conf
redis-server pengconfig/redis6380.conf
redis-server pengconfig/redis6381.conf

  • 可以看到,三个端口的服务开启完毕

一主二从

默认情况下,每台redis服务器都是主节点刚刚启动的3个redis服务都是master
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
所以说一般情况下我们配置从机就可

  • 搭建环境
#使用slaveof ip地址 主机端口号
127.0.0.1:6380> slaveof 127.0.0.1 6379 

在这里插入图片描述
由master变为slave,同理6381:
在这里插入图片描述

  • 我们去6379页面查看从机配置:

在这里插入图片描述
注意:我们这里是使用命令搭建,是暂时的,真实开发中应该在从机的配置文件中进行配置,这样的话是永久的。

  • 配置文件中主从复制
    在这里插入图片描述

细节介绍

1、主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机保存!

从机可以读到主机的东西,从机不能写入也就是不能set,只能get

2、主机如果断开,从机依旧是从机。从机依旧会执行它的读取功能,主机连回来从机读取功能依旧有效

3、只要是从机,就可以获取到主机的信息。只要变为从机,立马会从主机中获取值。不管什么时候变成的从机。

4、从机也可作为从机的主机
在这里插入图片描述
默认情况下,主机故障后,不会出现新的主机,有两种方式可以产生新的主机:

1、从机手动执行命令slaveof no one,这样执行以后从机会独立出来成为一个主机

	如果主机断了的话,可以这样成为主节点
	其他的节点可以手动的连到这个最新的主节点这里

2、使用哨兵模式(自动选举)

复制原理

  • Slave启动成功连接到master后会发送一个sync同步命令

  • Master接到命令,启动后台的存盘进程
    同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,,master将传送整个数据文件到slave ,并完成一次完全同步。

      全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
      增量复制:Master继续将新的所有收集到的修改命令依次传给slave ,完成同步
    

但是只要是重新连接master,一次完全同步(全量复制)将被自动执行

哨兵模式(自动选主机的模式)

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例.

单机单个哨兵

在这里插入图片描述
哨兵的作用:

	通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
	
	当哨兵监测到master宕机,会自动将slave切换成master
	然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

单哨兵也有局限性,也就是说这个哨兵死掉的话就完蛋了,所以有了下方的多哨兵模式,哨兵之间再监控,是否存活。

多哨兵模式

简介

在这里插入图片描述

1、假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线

2、当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票 ,投票的结果由一个哨兵发起,进行failover[故障转移]操作。

3、切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切
换主机,这个过程称为客观下线

哨兵之间相互监控

测试

1、在redis.conf配置文件目录下再建立一个名为sentinel.conf的文件
这个文件就是哨兵配置文件了

vim sentinel.conf

2、在sentinel.conf中写入以下内容,去监视谁

#1.sentinel monitor 
#2.被监控的名称(随意) 
#3.被监控的IP 
#4.被监控的端口 
#5.1表示当一个哨兵主观认为主机断开,就可以客观认为主机故障,然后从机投票开始选举新的主机。
sentinel monitor mymaster 127.0.0.1 6379 1

3、启动哨兵

redis-sentinel pengconfig/sentinel.conf
#这里只配置了一个配置文件,如果有哨兵集群就开启配置多个文件

在这里插入图片描述

4、现在咱们把主机(6379)关闭(模拟主机挂掉),过一会儿,我们再次查看从机的状态,发现6381变为主机了
在这里插入图片描述
如果Master节点断开了,这个时候就会从从机中随机选择一个服务器! ( 这里面有一个投票算法! )
在这里插入图片描述

5、如果此时主机又连回来了,因为redis哨兵比较智能会把它变成从机,归并到新的主机下变为从机

哨兵模式优缺点

  • 优点

      哨兵集群,基于主从复制模式,所有主从复制的优点,它都有
      主从可以切换,故障可以转移,系统的可用性更好
      哨兵模式是主从模式的升级,手动到自动,更加健壮	
    
  • 缺点:

      Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
      实现哨兵模式的配置其实是很麻烦的,里面有很多配置项
    

哨兵模式的全部配置

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379
port 26379
 
# 哨兵sentinel的工作目录
dir /tmp
 
# 哨兵sentinel监控的redis主节点的 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 1
 
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
 
 
# 指定多少毫秒之后 主节点没有应答哨兵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
 
# SCRIPTS EXECUTION
 
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
 
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
  sentinel notification-script mymaster /var/redis/notify.sh
 
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。 
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

缓存穿透与雪崩(重要)

缓存穿透

概念

在默认情况下,用户请求数据时,会先在缓存(Redis)中查找,若没找到即缓存未命中,再在数据库中进行查找,数量少可能问题不大,可是一旦大量的请求数据(例如秒杀场景)缓存都没有命中的话,就会全部转移到数据库上,造成数据库极大的压力,就有可能导致数据库崩溃。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击
在这里插入图片描述

解决方案

  • 布隆过滤器
    布隆过滤器是一种数据结构。对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验校验不通过直接打回,减轻了存储系统的压力。

在这里插入图片描述

  • 缓存空对象
    一次请求若在缓存和数据库中都没找到,就在缓存中放一个空对象用于处理后续这个请求。
    在这里插入图片描述

缓存击穿(量太大,缓存过期)

概念

  • 相较于缓存穿透,缓存击穿的目的性更强,一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。

  • 这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

  • 当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。

  • 比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。

解决方案

  • 设置热点数据永不过期:这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
  • 加互斥锁(分布式锁):在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
    给每一个key同时只有一个线程去查询服务,其他线程没有权限

在这里插入图片描述

缓存雪崩

概念

  • 大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。(停电了redis宕机)
  • 比如在双11,基本上热点的数据都是放在缓存中的,如果此时缓存崩了,所有访问直接请求mysql,mysql没有抗住就挂了。
    在这里插入图片描述
  • 其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
  • 所以说双11,一般会停掉一些服务,保证主要的服务可用。比如:双11,你去查历史订单是查不到的

解决方案

  • redis高可用:这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
  • 限流降级:这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
  • 数据预热:数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值