Redis学习笔记

Redis 是一个高性能的键值存储系统,支持五大数据类型:字符串、哈希、列表、集合、有序集合。在持久化方面,Redis 提供 RDB 和 AOF 两种方式。此外,Redis 的主从复制能实现数据备份,哨兵模式则可以监控和自动故障转移,确保服务高可用。事务支持多条命令原子执行,保证数据一致性。
摘要由CSDN通过智能技术生成

Redis学习笔记

学习狂神Redis的详细笔记,个人手写,注意点赞收藏哦!

基础知识

redis默认有16个数据库,默认使用的数据库索引为0,切换数据库可以使用select,DBSIZE可以查看数据库大小

keys *查看数据库所有key

清除当前数据库 flushdb,清除全部数据库的内容flushall

redis是单线程的,官方表示,Redis是基于内存操作的

五大基本数据类型

redis-key

redis中key区分大小写

redis-cli -p 6379	#连接redis

EXISTS [KEY] 判断当前的key是否存在

move [key] 1移除当前的key

EXPIRE [KEY] X 设置当前key过期时间,单位:X秒,类似cookie

ttl [key] 查看当前key的过期时间

type [key]查看key对应value的类型

APPEND [KEY] “*” 追加字符串如多当前key不存在就相当于set key

127.0.0.1:6379> keys *
1) "k2"
2) "mysay"
3) "views"
4) "k1"
5) "k3"
6) "say2"
7) "say"
127.0.0.1:6379> flushdb	#清空数据库
OK
127.0.0.1:6379> keys *
(empty array)

增长

#i++操作
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views 	#自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> incr views
(integer) 3
127.0.0.1:6379> decr views	#自减1
(integer) 2
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> incrby views 10	#设置步长
(integer) 11
127.0.0.1:6379> decrby views 5
(integer) 6

String

#字符范围 rang
127.0.0.1:6379> set say "hello,redis"
OK
127.0.0.1:6379> get say
"hello,redis"
127.0.0.1:6379> GETRANGE say 0 3 #截取字符串[0,3]
"hell"
127.0.0.1:6379> GETRANGE say 0 -1 #截取全部字符串
"hello,redis"
127.0.0.1:6379> set say2 qwertyu
OK
127.0.0.1:6379> get say2
"qwertyu"
127.0.0.1:6379> setrange say2 1 aa	#替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get say2
"qaartyu"
127.0.0.1:6379> setrange say2 0 xx
(integer) 7
127.0.0.1:6379> get say2
"xxartyu"




#####################################################################################
#	setex (set with expire) 设置过期时间
#	setnx (set if not exist) 不存在设置(分布式锁中会常常用)

127.0.0.1:6379> setex say3 30 "hello"	#设置say3 的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl say3	#查看剩余过期秒数
(integer) 20
127.0.0.1:6379> ttl say3
(integer) 17
127.0.0.1:6379> get say3
"hello"
127.0.0.1:6379> get say3
"hello"
127.0.0.1:6379> ttl say3
(integer) 4
127.0.0.1:6379> setnx mysay "redis"	#如果mysay不存在,创建mysay
(integer) 1	#1表示成功
127.0.0.1:6379> keys *
1) "say2"
2) "mysay"
3) "views"
4) "say"
127.0.0.1:6379> setnx mysay "MongoDB"	#如果mysay存在,创建失败!
(integer) 0	#0表示失败
127.0.0.1:6379> get mysay
"redis"
###################################################################################

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3	#同时设置多个键值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3	#同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4	#msetnx是原子性的操作,要么一起成功,要么一起失败。	
(integer) 0
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"

#####################################################################################

set user:1{name:zhangsan,age:12} #设置一个user:1对象 值为json字符来保存一个对象

#这里的key是一个巧妙的设计:user:{id}:{filed}

127.0.0.1:6379> mset user:1:name zhangsan user:1:age 12
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "12"

#####################################################################################
getset	#先get后set

127.0.0.1:6379> getset db mongodb	#如果不存在值,则返回nil,设置值
(nil)
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> getset db redis	#如果存在值,则返回值,设置新值
"mongodb"
127.0.0.1:6379> get db
"redis"


#####################################################################################

List

在redis中,我们可以将list玩成栈、队列、阻塞队列

所有的list命令都是用l开头的

#####################################################################################

127.0.0.1:6379> lpush list one	#将一个值或者多个值插入到列表的头部(上,左)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1	#获取list中的值
1) "three"	
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1	#获取指定区间的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list right	##将一个值或者多个值插入到列表的尾部(下,右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"

#####################################################################################

lpop
rpop

127.0.0.1:6379> lpop list	#移除队首的第一个
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> rpop list	#移除队尾的第一个
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"

#####################################################################################
lindex

127.0.0.1:6379> lindex list 0	#通过下标获取某一个值
"two"
127.0.0.1:6379> lindex list 1	
"one"

127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> llen list	#返回列表的长度
(integer) 2

#####################################################################################

移除指定的值

    127.0.0.1:6379> lrange list 0 -1	#将list中的值全部取出
1) "two"
2) "one"
127.0.0.1:6379> llen list	#查看list长度
(integer) 2
127.0.0.1:6379> lpush list three	#向list中添加值
(integer) 3
127.0.0.1:6379> lpush list three
(integer) 4
    127.0.0.1:6379> lrange list 0 -1	#将list中的值全部取出
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 three	#从list中移除一个值从上至下,精确匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lpush list three
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 2 three	#从list中移除两个值从上至下
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"

#####################################################################################
截取
127.0.0.1:6379> lpush mylist "hello" "redis" "MongoDB"
(integer) 3
127.0.0.1:6379> keys *
1) "mylist"
127.0.0.1:6379> lrange mylist 0 -1
1) "MongoDB"
2) "redis"
3) "hello"
127.0.0.1:6379> ltrim mylist 1 2	#通过下标截取指定区间长度的值,该list集合只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "redis"
2) "hello"

#####################################################################################

LSET

127.0.0.1:6379> lpush mylist "hello" "redis" "MongoDB"
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "MongoDB"
2) "redis"
3) "hello"
#组合
rpoplpush [list] [list]
rpoprpush [list] [list] 
lpoplpush [list] [list] 
lpoprpush [list] [list]

127.0.0.1:6379> rpoplpush mylist otherlist	#从mylist中移除值到otherlist(如果otherlist不存在则创建)
"hello"
127.0.0.1:6379> lrange mylist 0 -1	
1) "MongoDB"
2) "redis"
127.0.0.1:6379> lrange otherlist 0 -1
1) "hello"
127.0.0.1:6379> keys *
1) "mylist"
2) "otherlist"
127.0.0.1:6379> lset test 0 hello	#list将下标为0 的值设置为hello,如果list集合不存在则报错
(error) ERR no such key
127.0.0.1:6379> lset otherlist 0 xiaomi	#list将下标为X 的值设置为*,如果list集合存在则将原有值设置为*相当于更新操作
OK
127.0.0.1:6379> lrange otherlist 0 -1
1) "xiaomi"
127.0.0.1:6379> llen otherlist
(integer) 1
127.0.0.1:6379> lset otherlist 1 xiaomi
(error) ERR index out of range

#####################################################################################
#插入
linsert

127.0.0.1:6379>  LRANGE mylist 0 -1	#将mylist中的值全部取出
1) "MongoDB"
2) "redis"
127.0.0.1:6379> linsert mylist before redis hello	#在redis前插入hello
(integer) 3
127.0.0.1:6379>  LRANGE mylist 0 -1	#将mylist中的值全部取出
1) "MongoDB"
2) "hello"
3) "redis"
127.0.0.1:6379> linsert mylist after mongodb nb	#在MongoDB后插入nb,注意区分字段大小写
(integer) -1
127.0.0.1:6379>  LRANGE mylist 0 -1
1) "MongoDB"
2) "hello"
3) "redis"
127.0.0.1:6379> linsert mylist after MongoDB nb
(integer) 4
127.0.0.1:6379>  LRANGE mylist 0 -1
1) "MongoDB"
2) "nb"
3) "hello"
4) "redis"
127.0.0.1:6379> lpush mylist hello	#向集合中添加hello使得集合中有两个相同的值测试插入时插入到哪一值前或后
(integer) 5
127.0.0.1:6379> linsert mylist after hello lpx	#向hello后插入lpx
(integer) 6
127.0.0.1:6379>  LRANGE mylist 0 -1	#有两个相同value时使用linsert插入时选择的是顶端的value
1) "hello"
2) "lpx"
3) "MongoDB"
4) "nb"
5) "hello"
6) "redis"


#####################################################################################

小结

本质是一个链表,Node前后都可以插入,两边插入的效率比中间高一些。
应用场景:实现消息队列(左进右出,或反之)、栈(左进左出,或反之)。

set

#####################################################################################

127.0.0.1:6379> sadd myset hello redis MongoDB
(integer) 3
127.0.0.1:6379> smembers myset	#查看指定set的所有值
1) "hello"
2) "MongoDB"
3) "redis"
127.0.0.1:6379> sismember myset hello	#判断某一个值是不是在set集合中
(integer) 1
127.0.0.1:6379> sismember myset word
(integer) 0

#####################################################################################

127.0.0.1:6379> scard myset
(integer) 3

#####################################################################################

127.0.0.1:6379> sadd myset lpx	
(integer) 1
127.0.0.1:6379> smembers myset
1) "lpx"
2) "hello"
3) "MongoDB"
4) "redis"
127.0.0.1:6379> srem myset lpx	#移除set集合中指定的元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "MongoDB"
3) "redis"

#####################################################################################

set无序不重复集合

127.0.0.1:6379> srandmember myset	#随机抽选出一个元素
"redis"
127.0.0.1:6379> srandmember myset
"hello"
127.0.0.1:6379> srandmember myset 2	#随机抽选出两个个元素
1) "hello"
2) "MongoDB"
127.0.0.1:6379> srandmember myset 2
1) "redis"
2) "MongoDB"
127.0.0.1:6379> srandmember myset 2
1) "hello"
2) "MongoDB"
127.0.0.1:6379> srandmember myset 2
1) "hello"
2) "redis"

######################################################################################

删除指定key,随机y

127.0.0.1:6379> smembers myset
1) "hello"
2) "MongoDB"
3) "redis"
127.0.0.1:6379> spop myset	#随机删除set集合中的一些元素
"MongoDB"
127.0.0.1:6379> smembers myset
1) "hello"
2) "redis"
######################################################################################

移动
127.0.0.1:6379> smembers myset
1) "hello"
2) "redis"
127.0.0.1:6379> smove myset myset2 hello	#将一个集合中的元素移动到另一个集合中
(integer) 1
127.0.0.1:6379> smembers myset2
1) "hello"

######################################################################################

微博,B站,共同关注!(并集)
数字集合类
-差集
-交集
-并集

127.0.0.1:6379> sadd myset a b c
(integer) 3
127.0.0.1:6379> sadd myset2 c d e 
(integer) 3
127.0.0.1:6379> smembers myset
1) "c"
2) "a"
3) "b"
127.0.0.1:6379> smembers myset2
1) "c"
2) "e"
3) "d"
127.0.0.1:6379> sdiff myset myset2	#myset与myset2差集
1) "a"
2) "b"
127.0.0.1:6379> sdiff myset2 myset	#myset2与myset差集
1) "d"
2) "e"
127.0.0.1:6379> sinter myset myset2	#交集	共同好友可以这样实现
1) "c"
127.0.0.1:6379> sunion myset myset2	#并集
1) "c"
2) "e"
3) "a"
4) "b"
5) "d"

小结

微博将所有关注的人放在一个set集合中,将他的粉丝也放在一个集合中!

共同关注、共同爱好、推荐好友

Hash(哈希)

Map集合,key-map!这个值是一个map集合!本质和String类型没有太大区别 ,还是一个简单的key-value

######################################################################################

127.0.0.1:6379> hset myhash name lpx	#set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash lpx		#获取某个值
(nil)
127.0.0.1:6379> hget myhash name
"lpx"
127.0.0.1:6379> hmset myhash age 18 name liu	##set多个key-value
OK
127.0.0.1:6379> hmget myhash age name	#获取多个值
1) "18"
2) "liu"
127.0.0.1:6379> hgetall myhash	#获取全部值
1) "name"
2) "liu"
3) "age"
4) "18"

######################################################################################
127.0.0.1:6379> hgetall myhash
1) "name"
2) "liu"
3) "age"
4) "18"
5) "sex"
6) "man"
127.0.0.1:6379> hdel myhash sex	#删除指定hash表的指定key字段,对应的value值也就对应删除了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "liu"
3) "age"
4) "18"

######################################################################################

127.0.0.1:6379> hgetall myhash
1) "name"
2) "liu"
3) "age"
4) "18"
127.0.0.1:6379> hlen myhash	#获取某个hash表的字段数
(integer) 2
127.0.0.1:6379> hexists myhash name	#判断某个hash表的某个字段是否存在
(integer) 1

######################################################################################

#只获得所有field
#只获得所有value

    127.0.0.1:6379> hkeys myhash	#只获得所有field
1) "name"
2) "age"
127.0.0.1:6379> hvals myhash	#只获得所有value
1) "liu"
2) "18"

######################################################################################

incr decr

127.0.0.1:6379> hset myhash num 1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "liu"
3) "age"
4) "18"
5) "num"
6) "1"
127.0.0.1:6379> hincrby myhash num 1	#自增1
(integer) 2
127.0.0.1:6379> hincrby myhash num -1	#自增-1
(integer) 1
127.0.0.1:6379> hsetnx myhash birthday 2021-02-06
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "liu"
3) "age"
4) "18"
5) "num"
6) "1"
7) "birthday"
8) "2021-02-06"
127.0.0.1:6379> hsetnx myhash birthday 2021-02-06	#不存在创建
(integer) 0
127.0.0.1:6379> hsetnx myhash birthday 2021-02-07	#已存返回0,不操作
0
(integer) 0
######################################################################################

小结

hash变更的数据user name age ,尤其是用户信息之类的,经常变动的信息

hash更适合对象的存储,string更加适合字符串的存储

Zset(有序集合)

######################################################################################
127.0.0.1:6379> zadd myset 1 one 2 two	#添加多个值
(integer) 2
127.0.0.1:6379> zadd myset 3 three 4 four
(integer) 2
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
######################################################################################

#排序
127.0.0.1:6379> zadd salary 2500 xiaohong	#添加四个元素
(integer) 1
127.0.0.1:6379> zadd salary 2500 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 5000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 3500 wangwu
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf	#从低到高排序key做排序
1) "xiaohong"
2) "zhangsan"
3) "wangwu"
4) "lisi"
127.0.0.1:6379> zrangebyscore salary 0 +inf		#从低到高排序key做排序
1) "xiaohong"
2) "zhangsan"
3) "wangwu"
4) "lisi"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores	#从低到高排序,显示所有元素及值
1) "xiaohong"
2) "2500"
3) "zhangsan"
4) "2500"
5) "wangwu"
6) "3500"
7) "lisi"
8) "5000"
127.0.0.1:6379> zrangebyscore salary 3500 5000 withscores	#指定区间从低到高排序key做排序
1) "wangwu"
2) "3500"
3) "lisi"
4) "5000"
#从大到小排序
127.0.0.1:6379> zrevrange salary 0 -1
1) "lisi"
2) "wangwu"
3) "zhangsan"
4) "xiaohong"
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "lisi"
2) "5000"
3) "wangwu"
4) "3500"
5) "zhangsan"
6) "2500"
7) "xiaohong"
8) "2500"
######################################################################################

#移除

127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "lisi"
2) "5000"
3) "wangwu"
4) "3500"
5) "zhangsan"
6) "2500"
7) "xiaohong"
8) "2500"
127.0.0.1:6379> zrem salary xiaohong	#移除指定元素
(integer) 1
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "lisi"
2) "5000"
3) "wangwu"
4) "3500"
5) "zhangsan"
6) "2500"
######################################################################################

#统计区间中元素数量

127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "lisi"
2) "5000"
3) "wangwu"
4) "3500"
5) "zhangsan"
6) "2500"
127.0.0.1:6379> zcount salary 2500 4000
(integer) 2
127.0.0.1:6379> zcount salary 2500 5000
(integer) 3
127.0.0.1:6379> zcount salary 3000 5000
(integer) 2

三种特殊数据类型

geospatial地理位置

朋友的定位,附近的人,打车距离计算

######################################################################################
#geoadd
#两级是无法直接添加的,我们一般会下载城市数据,直接通过java程序一次性导入
#参数 key 值(纬度 经度 名称)
127.0.0.1:6379> geoadd china:city 115.07168 34.44539 suixian
(integer) 1
127.0.0.1:6379> geoadd china:city 115.65338 34.38804 suiyangqu

(integer) 1
127.0.0.1:6379> geoadd china:city 114.35708 36.09046 wenfengqu

(integer) 1
127.0.0.1:6379> geoadd china:city 113.81558 36.07804 linzhoushi 115.14621 34.64931 minquanxian
(integer) 2

######################################################################################

#获得当前定位:坐标值

127.0.0.1:6379> geopos china:city suixian	#获取指定的经度和纬度
1) 1) "115.07167786359786987"
   2) "34.44538914978334532"


######################################################################################

#geodist

127.0.0.1:6379> geodist china:city suixian suiyangqu	#查询睢县到睢阳区的距离默认m
"53754.2987"
127.0.0.1:6379> geodist china:city suixian suiyangqu km	#查询睢县到睢阳区的距离 km
"53.7543"

######################################################################################

#georadius

127.0.0.1:6379> georadius china:city 115.07168 34.44539 100 km	#查询100km(半径)以内的位置

1) "suixian"
2) "minquanxian"
3) "suiyangqu"

127.0.0.1:6379> georadius china:city 115.07168 34.44539 100 km withcoord withdist withhash count 2	#查询100km以内的位置,限定数量为2
1) 1) "suixian"
   2) "0.0002"						#距离 withdist
   3) (integer) 4064341297318991	#withhash
   4) 1) "115.07167786359786987"	#显示他人的定位信息 withcoord
      2) "34.44538914978334532"
2) 1) "minquanxian"
   2) "23.6867"	#距离
   3) (integer) 4065078118317270
   4) 1) "115.14621108770370483"
      2) "34.64931000177332265"


######################################################################################

#GEORADIUSBYMEMBER
#找出位于指定元素周围的其它元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city suixian 100 km	#
1) "suixian"
2) "minquanxian"
3) "suiyangqu"

######################################################################################

#geohash
#该命令返回11个字符串的geohash字符串
127.0.0.1:6379> geohash china:city suixian
1) "ww1gfw8dum0"	#将二维的字符串转换为一维的字符串,如果两个字符串越接近,距离就越近


######################################################################################

#GEO底层原理其实是Zset,我们可以使用zset命令来操作geo

127.0.0.1:6379> zrange china:city 0 -1	#查看地图中全部元素
1) "suixian"
2) "suiyangqu"
3) "minquanxian"
4) "linzhoushi"
5) "wenfengqu"
127.0.0.1:6379> zrem china:city minquanxian	#移除地图中某一个元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "suixian"
2) "suiyangqu"
3) "linzhoushi"
4) "wenfengqu"


######################################################################################

hyperloglog

redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

一般用来网页的访问量uv(一个人访问网站多次,但还是算作一个人)

传统的方式,set保存用户的id.然后就可以统计set中元素的数量作为标准

HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

测试使用

######################################################################################

127.0.0.1:6379> pfadd mykey a b c d e f g h i j k l m n	#创建一组元素
(integer) 1
127.0.0.1:6379> pfcount mykey	#统计元素基数
(integer) 14
127.0.0.1:6379> pfadd mykey2 a q w e r t z x c v b	#创建一组元素
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 11
127.0.0.1:6379> pfmerge mykey3 mykey mykey2	#将两组元素并到第三组
OK
127.0.0.1:6379> pfcount mykey3
(integer) 20

######################################################################################

Bitmaps 位图

位存储
Bitmaps是一种特殊的“数据结构”,实质上是一个字符串,操作单元是位。二进制位来操作进行记录,就只有0和1两个状态可用

统计疫情感染人数:0 0 0 0 0 0 1 0 0 …

统计用户信息,活跃,不活跃,登录,未登录,打卡等两个状态的都可以使用bitmaps

测试

使用bitmap来记录周一到周日的打卡

周一:1、周二:2、周三:3、周四:4、周五:5、周六:6、周日:0、

打卡状态:

​ -0:未打开

​ -1:已打卡

127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0

查看某一天是否已经打卡

127.0.0.1:6379> getbit sign 3
(integer) 0
127.0.0.1:6379> getbit sign 0
(integer) 1

统计打卡天数

127.0.0.1:6379> bitcount sign	#统计这周打卡记录
(integer) 1

事务

redis 事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行

一次性、顺序性、排他性,执行一系列的命令

------队列 set set set...执行-----

redis事务没有隔离级别的概念

所有的命令在事务中并没有直接被执行,只有发起执行命令的时候才会执行!Exec

redis单条命令是保证原子性的,但是事务是不保证原子性的

redis的事务:

​ -开启事务(multi)

​ -命令入队 (…)

​ -执行事务

锁:Redis可以实现乐观锁,watch

正常执行事务
127.0.0.1:6379> multi	#开启事务
OK
# 命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED

#	执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v2"
4) OK

放弃事务
127.0.0.1:6379> multi	#开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> discard	#取消事务,事务中的命令都不会执行
OK
127.0.0.1:6379> get k4
(nil)

编译时异常(代码有问题),事务中所有的命令都不会被执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3	#错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec	#执行事务
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4		#所有的命令都不会执行
(nil)

运行时异常(1/0),如果事务队列中存在语法性,那么执行命令时,其他命令时可以正常执行的,错误命令抛异常
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1	#会执行的时候失败
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range	#虽然这条命令报错了,但是依旧正常执行成功了
2) OK
3) "v2"

监控 watch

悲观锁

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

乐观锁

​ -很乐观,认为什么时候都不会出现问题,所以不会加锁,更新数据的时候去判断一下,在此器件是否有人修改过这个数据

​ -获取version

​ -更新的时候比较version

redis监视测试

正常执行成功

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money		#监控money对象
OK
127.0.0.1:6379> multi			#事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
OK
127.0.0.1:6379(TX)> decrby money 30
QUEUED
127.0.0.1:6379(TX)> incrby out 30
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 70
2) (integer) 30

测试多线程修改值,使用watch当作redis的乐观锁操作

127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec	#执行之前另外一个线程修改了监控的值,这时候就会导致事务执行失败
(nil)

#-----------------------另一线程-------------------------
127.0.0.1:6379> clear
127.0.0.1:6379> set money 200 
OK


###########################################################################################

127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379> unwatch		#事务执行失败,先解锁,解除监控
OK
127.0.0.1:6379> watch money	#获取新的值,再次监视

Jedis

Jedis是Redis官方推荐的Java连接开发工具。

1、导入依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

2、编码测试:

​ -连接数据库

​ -操作命令

​ -断开连接

Springboot整合Redis

springboot操作数据:spring-data jpa jdbc mongodb redis

SpringData也是和spring boot齐名的项目

说明:在spring boot2.x之后,原来使用的jedis被替换为了lettuce

jedis:采用的是直连的多个线程操作是不安全的,使用jedis pool连接池,更像BIO

lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数量,更像NIO

源码分析:

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")	//可以自定义redisTemplate来替换这个默认的
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
        
        //默认的RedisTemplate没有过多设置,redis都是需要序列化的
        //两个泛型都是Object类型,后使用需要强制转换<String,Object>
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean	//由于String是redis中最常使用的类型,所以单独提出一个bean
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
			throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

整合测试

1、导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置连接

#配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379

3、测试

User user = new User("lpx",18);
String jsonUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",jsonUser);

编写一个自己的RedisTemplate

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){

        //为了方便,一般直接使用<String, Object>的泛型
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        //连接默认的
        template.setConnectionFactory(redisConnectionFactory);


        //Jackson序列化配置
        Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

        //指定序列化输入的类型 enableDefaultTyping已过期官方推荐使用activateDefaultTyping
//        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
        jsonRedisSerializer.setObjectMapper(objectMapper);

        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


//        key使用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);

//        hash的key也采用String的序列化
        template.setHashKeySerializer(stringRedisSerializer);

//        value序列化方式采用Jackson
        template.setValueSerializer(jsonRedisSerializer);

//        hash的value序列化方式采用jackson
        template.setHashKeySerializer(jsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }
}

redis.config详解

单位

# Redis configuration file example.
#
# Note that in order to read the configuration file, Redis must be
# started with the file path as first argument:
#
# ./redis-server /path/to/redis.conf

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#配置文件对大小写不敏感
# units are case insensitive so 1GB 1Gb 1gB are all the same.

包含

可以将多个配置文件都配置过来

################################## INCLUDES ###################################

# Include one or more other config files here.  This is useful if you
# have a standard template that goes to all Redis servers but also need
# to customize a few per-server settings.  Include files can include
# other files, so use this wisely.
#
# Notice option "include" won't be rewritten by command "CONFIG REWRITE"
# from admin or Redis Sentinel. Since Redis always uses the last processed
# line as value of a configuration directive, you'd better put includes
# at the beginning of this file to avoid overwriting config change at runtime.
#
# If instead you are interested in using includes to override configuration
# options, it is better to use include as the last line.
#
# include /path/to/local.conf
# include /path/to/other.conf

网络

bind 127.0.0.1
protected-mode yes
port 6379

通用 GENERAL

daemonize yes #以守护进程的方式运行

pidfile /www/server/redis/redis.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

# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile "/www/server/redis/redis.log"	#日志生成文件名

databases 16	#默认数据库的数量

always-show-logo yes	#是否总是显示自己的logo

快照 SNAPSHOTTING

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

redis是内存数据库,如果没有持久化,那么数据断电即失!

#如果900秒内,如果至少有1个key进行了修改,我们即进行持久化操作
save 900 1
#如果300秒内,如果至少10个key进行了修改,我们即进行持久化操作
save 300 10
#如果60秒内,如果至少10000个key进行了修改,我们即进行持久化操作
save 60 10000

stop-writes-on-bgsave-error yes	#持久化数据出问题了就拒绝新的写入

rdbcompression yes	#是否压缩rdb文件

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

dir /www/server/redis/	#rdb保存的目录

复制 REPLICATION,关于主从复制


安全 SECURITY

可以设置密码,默认没有密码

# requirepass foobared

#获取redis密码
config get requirepass
#命令设置密码
config set requirepass "******"
#验证密码
auth ******

限制客户端 CLIENTS

# maxclients 10000	#最大客户端的数量

# maxmemory <bytes>	#配置最大内存容量


# maxmemory-policy noeviction	#内存达到上限之后的处理策略
    # maxmemory-policy 六种方式
    # 1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
    # 2、allkeys-lru : 删除lru算法的key   
    # 3、volatile-random:随机删除即将过期key   
    # 4、allkeys-random:随机删除   
    # 5、volatile-ttl : 删除即将过期的   
    # 6、noeviction : 永不过期,返回错误

APPEND ONLY 模式

appendonly no	#默认 是不开启aof模式的,默认使用的是rdb方式持久化的,在大部分所有的情况下,rdb完全够用

# The name of the append only file (default: "appendonly.aof")	

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


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

Redis持久化

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

RDB(Redis DataBase)

什么是RDB

在这里插入图片描述

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。

也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中

默认的文件名为dump.rdb,恢复时是将快照文件直接读到内存里

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

触发机制
#   save ""

save 900 1
save 300 10
save 60 10000

1、save规则满足的情况下,会自动触发rdb规则

2、执行flushall也会触发rdb规则

3、退出redis,也会产生rdb文件

备份就会自动生成一个dump.rdb

如何恢复rdb文件

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

2、查看需要存在的位置,如果这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据

优点

1、适合大规模的数据恢复

2、对数据完整性要求不高

缺点

1、需要一定的时间间隔进行操作,如果redis意外宕机,这个最后一次修改的数据就没有了

2、fork进程运行的时候需要占用一定的内存空间

AOF(Append Only File)

将我们的所有命令都记录下来,恢复的时候就把这个文件全部执行一遍

什么是aof

在这里插入图片描述

以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),只需追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据库,换言之,redis重启的话就根据日志wen’jian的内容将写指令从前到后执行一次以完成数据的恢复工作。

AOF保存的是 appendonly.aof文件

# Please check http://redis.io/topics/persistence for more information.

appendonly no	#默认是不开启的,如果开启AOF改为yes即可

# The name of the append only file (default: "appendonly.aof")

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

如果aof文件有错误,这时候redis是启动不起来的,我们需要修复aof文件

redis提供了一个一个工具,redis-check-aop --fix

在这里插入图片描述

如果文件正常,重启就可以恢复了!

在这里插入图片描述

重写规则说明

默认的是文件的无限追加,文件会越来越大

# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.

no-appendfsync-on-rewrite no

# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size grows by the specified percentage.
#
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (if no rewrite has happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb


#如果aof文件大于64mb,就会fork一个进程来将我们的文件进行重写
aof优点缺点

优点:

1、每一次修改都同步,文件完整性会更好

2、每秒同步一次,可能会丢失一秒数据

3、从不同步,效率最高

缺点:

1、相对于数据文件来说,aof远大于rdb,修复的速度也比rdb慢

2、aof运行效率比rdb慢所以redis默认配置是rdb持久化

扩展

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8Gf5eZQ-1627477765766)(C:\Users\51545\Desktop\MyPractice\笔记\redis\拓展1.PNG)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ptsud0u-1627477765767)(C:\Users\51545\Desktop\MyPractice\笔记\redis\拓展2.PNG)]

Redis发布订阅

redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息

Redis客户端可以订阅任意数量的频道

订阅/发布消息图

在这里插入图片描述

下图展示了频道channel1,以及订阅这是三个频道的三个客户端–client2、client5、和client1之间的关系

在这里插入图片描述

当有新消息通过publish命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端

在这里插入图片描述

命令

这些命令被广泛用于构建即时通信应用,比如聊天室(chatroom)和实时广播、实时提醒等

在这里插入图片描述

测试

订阅端:

127.0.0.1:6379> SUBSCRIBE lpx				#订阅一个频道	lpx
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "lpx"
3) (integer) 1
#等待读取信息
1) "message"	#消息
2) "lpx"		#频道
3) "hello,lpx"	#内容
1) "message"
2) "lpx"
3) "hello,redis"

发送端:

127.0.0.1:6379> PUBLISH lpx "hello,lpx"		#发布者发布消息到频道
(integer) 1
127.0.0.1:6379> PUBLISH lpx "hello,redis"	#发布者发布消息到频道
(integer) 1
原理

Redis是使用C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅的底层实现,其次加深对redis的理解。

Redsi通过PUBLISH、SUBSCRIBE和PSUBSCRIBE等命令实现发布和订阅功能。

通过SUBSCRIBE命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个channel(频道),而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSVRIBE命令的关键,就是将客户端添加到给定channel的订阅链表中。

通过PUBLISH命令向订阅者发布消息,redis-server会使用给定的频道作为键,在它维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。

PUB/SUB从字面上理解就是发布(publish)与订阅(subscrible),在Redis中,你可以设定对某一个key值进行消息发布及订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

使用场景

1、实时消息系统

2、实时聊天(频道作为聊天室,将信息回显给所有人即可)

3、订阅、关注系统

稍微复杂的场景就会使用消息中间件来实现例如:MQ

Redis主从复制

概念

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

默认情况下,每台 Redis 服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。主从复制的作用主要包括:

1 、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。

2 、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。

3 、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 R edis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis服务器的并发量。

4 、高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础

一般来说,要将 Redis 运用于工程项目中,只使用一台 Redis 是万万不能的,原因如下:

1 、从结构上,单个 Redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;

2 、从容量上,单个 R edis 服务器内存容量有限,就算一台 RediS 服务器内存容量为 256G ,也不能将所有内存用作 Redis 存储内存一般来说,单台 R edis 最大使用内存不应该超过 ZOG 。

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

对于这种场景,我们可以使如下这种架构:

在这里插入图片描述

环境配置

只配置从库,不配置主库!

127.0.0.1:6379> info replication	#查看当前库的信息
# Replication
role:master			#角色
connected_slaves:0	#从机数
master_failover_state:no-failover
master_replid:0fd5a5793a63b5dbe6e16fb29732bc6c7d536e2f
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

复制三个配置文件,然后修改对应信息

1、端口

2、pid名称

3、log文件名称

4、dump.rdb名字

修改完成之后,启动三个redis服务器,可以通过进程信息查看

在这里插入图片描述

一主二从

默认情况下,每台redis服务器都是主节点;我们一般情况下只需要配置从机就好了!

类似认老大,一主(6379)二从(6380,6381)

命令:SLAVEOF host port

在这里插入图片描述

在这里插入图片描述

如果两个从机都配置完了,就是有两个从机的

在这里插入图片描述

真实的主从配置应该是在配置文件中配置,这样的话是永久的

细节

主机可以写。

在这里插入图片描述

从机不能写只能读!

在这里插入图片描述

主机中的所有信息和数据,都会自动被从机保存!

测试:主机断开连接,从机依旧连接到主机的,但是没有写操作了,这个时候,主机如果重新连接了,从机依旧可以直接获取到主机写的信息!

如果是使用命令行,来配置的主从,这个时候如果重启了,就会变回主机!只要变回了从机,立马就会从主机中获取值!

复制原理

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

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

全量复制:而Slave 服务在接收到数据库文件数据后,将其存盘并加载到内存中。

增量复制: Master 继续将新的所有收集到的修改命令依次传给 Slave ,完成同步

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

一带一路

上一个主节点(M)连接下一个从节点(S)

在这里插入图片描述

这时候也可以完成主从复制

如果没有了主机,这个时候可以选择一个作为主机,哨兵模式出现之前是手动!

如果主机断开链接了,可以使用SLAVEOF no one选择某从机让其变为主机,其他节点可以手动连接到最新的主节点(手动),如果主机重新连接了,那就只能重新连接切换为原来的主节点。

哨兵模式(自动选举模式)

概述

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用.这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式. Redis 从 2 . 8 开始正式提供了 Sentinel (哨兵)架构来解决这个问题。

谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。

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

在这里插入图片描述

这里的哨兵有两个作用:

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

然而一个哨兵进程对 R edis 服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

在这里插入图片描述

假设主服务器宕机,哨兵 1 先检测到随个结果,系统并不会马上进行 failove过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover 「故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。

测试

1、配置哨兵配置文件sentinel.conf

#sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1

后面的数字1,代表主机挂了,slave投票看让谁接替成为主机,票数最多的就会成为主机

2、启动哨兵

在这里插入图片描述

如果Master节点断开了,这个时候就会从从机中随机选择一个服务器作为主机(这里有一个投票算法)

哨兵日志

在这里插入图片描述

哨兵模式

如果原作为Master的主机重新连接了,只能归并到新Master主机下当作从机,这就是哨兵模式规则

优点:

  1. 哨兵集群,基于主从复制,所有主从复制优点他都有
  2. 主从可以切换,故障可以转移,系统可用性会更好
  3. 哨兵模式就是主从模式升级,手动到自动,更加健壮

缺点:

  1. Redis不好在线扩容,集群容量到达上限,在线扩容就十分麻烦
  2. 实现哨兵模式的配置其实是很麻烦的,里面有很多选择
哨兵模式的全部配置

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Redis缓存穿透和雪崩

Redis 缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。

缓存穿透

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现 redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

解决方案

布隆过滤器

布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。

在这里插入图片描述

缓存空对象

当存储层不命中,即返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;

在这里插入图片描述

但是这种方法会存在两个问题:

1、如果空值能够被缓存起来,这就意会着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;

2、即使设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

缓存击穿

概述

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

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

解决方案

设置热点数据永不过期

从缓存层面看,mei’you’she’zhi过期时间,所以不会出现热点key过期后产生的问题。

加互斥锁

分布式锁:使用分布式锁,保证对于每个ket同时只有一个线程去查询后端服务。其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

缓存雪崩

概念

缓存雪崩,是指在某一个时间段,缓存集中过期失效。 Redis 宕机!

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰.于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

在这里插入图片描述

解决方案
redis 高可用

这个思想的含义是,既然 redis 有可能挂掉,那我多增设几台 redis ,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。

限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。

数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的 key ,设置不同的过期时间,让缓存失效的时间点尽量均匀。

特别鸣谢:@狂神说JAVA->哔哩哔哩

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值