Redis 学习笔记

一、Redis入门
二、基础知识
三、五大基本数据类型
四、三大特殊类型
五、事务
六、Jedis
七、Springboot 整合
八、Redis.conf 详解
九、持久化
十、Redis 发布订阅
十一、Redis 主从复制
十二、缓存击穿、穿透与雪崩

一、Redis入门

Redis是什么

Redis(Remote Dictionary Server ),即远程字典服务。

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis能干什么?

  1. 内存存储、持久化,内存是断电即失的,所以需要持久化(RDB、AOF)
  2. 高效率、用于高速缓冲
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器(eg:浏览量)
  6. 。。。

特性

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

环境搭建(略)

#启动服务:
redis-server /usr/local/bin/Xconfig/redis.conf

#启动客户端:
redis-cli -p 6379

关闭redis:

在这里插入图片描述

查看Redis进程是否开启:

ps -ef|grep redis

性能测试

**redis-benchmark:**Redis官方提供的性能测试工具,参数选项如下:

请添加图片描述

#本机测试  100个连接 共100000个请求:
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

可以了解到Redis的快

请添加图片描述

Top↑

二、基础知识

安全

127.0.0.1:6379> config set requirepass "010507"		#设置 redis 密码
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass			#获取 redis 密码
1) "requirepass"
2) "010507"
127.0.0.1:6379> quit
[root@iZf8zi9i30yumlfpgkhceqZ Xconfig]# redis-cli -p 6379
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 010507					#需要配合密码才能登录使用
OK
127.0.0.1:6379> 

redis默认有16个数据库

默认使用的第0个;

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

#切换数据库
127.0.0.1:6379> select 3
OK

#查看数据库大小
127.0.0.1:6379[3]> dbsize
(integer) 0

#清除当前数据库
127.0.0.1:6379> flushdb
OK

#清空所有的数据库
flushall


Redis 端口号 6379

Redis 为单线程(Redis6之后为多线程)

Top↑

三、五大数据类型

String
List
Set
Hash
Zset

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库高速缓存消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存和消息代理。Redis提供数据结构,如字符串、哈希、列表、集合、带范围查询的排序集合、位图、hyperloglogs、地理空间索引和流。Redis具有内置复制、Lua脚本、LRU逐出、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster的自动分区提供高可用性。

Redis-Key

#设置key
127.0.0.1:6379[3]> set name "as"
OK

#查询key
127.0.0.1:6379[3]> get name
"as"

#判断是否存在某key  存在为1,反之为0
127.0.0.1:6379> exists name
(integer) 1

#移动当前键到其他数据库 如1号数据库	
127.0.0.1:6379> move name 1
(integer) 1

#查看数据库所有的key
127.0.0.1:6379[3]> keys *
1) "name"

#设置key的过期时间,单位为秒
127.0.0.1:6379> expire name 10
(integer) 1

#查询当前key的过期时间:
127.0.0.1:6379> ttl name
(integer) 6

#查看当前key的类型
127.0.0.1:6379> type name
string



String

#1.
#追加内容,返回的是总长度,不存在则新建
127.0.0.1:6379> append hw World
(integer) 10

#查看当前字符串长度
127.0.0.1:6379> strlen hw
(integer) 10

---------------------------------------------------------------------
#2.
127.0.0.1:6379> get views 
"0"

#字符串记录数值自增
127.0.0.1:6379> incr views 
(integer) 1
127.0.0.1:6379> get views
"1"

#自减
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> get views
"0"

#步长自增,自减则为 decrby
127.0.0.1:6379> incrby views 10
(integer) 10
127.0.0.1:6379> get views
"10"
---------------------------------------------------------------------

---------------------------------------------------------------------
#3.
127.0.0.1:6379> get k1
"hello-World"

#获取某范围字符串 getrange ,起始位置,可负数(0,-1 可获取全部字符串)
127.0.0.1:6379> getrange k1 0 5
"hello-"
127.0.0.1:6379> getrange k1 0 -2
"hello-Worl"

#更改字符串,选好位置会进行等长度替换
127.0.0.1:6379> setrange k1 0 HELLO		#起始位置 替换内容
(integer) 11
127.0.0.1:6379> get k1
"HELLO-world"

---------------------------------------------------------------------


---------------------------------------------------------------------
#4.
#setex (set with expire)   设置过期时间
#setnx (set if not exist)  不存在设置,设置之后该值将不能被覆盖	(分布式锁中常用)

127.0.0.1:6379> setex k2 30 kkkkk		#过期时间   键值
OK

127.0.0.1:6379> setnx k4 redis			
(integer) 1
127.0.0.1:6379> setnx k4 mysql			#若当前设置的键已存在则不可以被创建
(integer) 0
127.0.0.1:6379> get k4
"redis"

--------------------------------------------------------------------

--------------------------------------------------------------------
#5.
#批量设置:
mset
mget

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 
1) "v1"
2) "v2"

#原子性操作,要么同时成功要么同时失败
127.0.0.1:6379> msetnx k1 v0 k4 v4
(integer) 0

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


#效果一样:
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 23
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "23"


#同理:
set article:10000:like 10  #第10000篇文章点赞数为1w

--------------------------------------------------------------------

---------------------------------------------------------------------
#6.
#getset 先get后set

127.0.0.1:6379> getset db redis
"mongodb"
127.0.0.1:6379> get db
"redis"


---------------------------------------------------------------------

使用场景

value 除了是字符串类型还可以是数字

✝ 计数器

☮ 统计多单位的数量

☪对象缓存存储

List

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

🧡 所有的list命令都是以l开头的

💛 list 中的值可以相同

---------------------------------------------------------------------
#1.
#将一个值或多个值插入到列表的头部 (左,头插)
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
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"
127.0.0.1:6379> 


---------------------------------------------------------------------

---------------------------------------------------------------------
#2.
#从左移除和从右开始移除
127.0.0.1:6379> lpop list
"three"
127.0.0.1:6379> rpop list
"right"
 
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"

---------------------------------------------------------------------

---------------------------------------------------------------------
#3.
#根据索引获得值
127.0.0.1:6379> lindex list 0
"two"

127.0.0.1:6379> lrange list 0 -1
1) "fore"
2) "fore"
3) "fore"
4) "fore"
5) "three"
6) "two"
7) "one"


#长度获取
127.0.0.1:6379> llen list
(integer) 7

#移除(可指定个数批量移除,根据值移除)
127.0.0.1:6379> lrem list 2 fore		#数量  要移除的值
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "fore"
2) "fore"
3) "three"
4) "two"
5) "one"

---------------------------------------------------------------------

---------------------------------------------------------------------
#4.
#截取指定范围的字符串,截取后保留为
127.0.0.1:6379> lrange list 0 -1
1) "hello0"
2) "hello1"
3) "hello2"
4) "hello3"
5) "hello4"
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello1"
2) "hello2"


---------------------------------------------------------------------

---------------------------------------------------------------------
#5.
127.0.0.1:6379> lrange list 0 -1
1) "helllo0"
2) "helllo1"
3) "helllo2"

# rpoplpush  移除列表之中的最后一个元素,并且将之放在其他的列表之中
127.0.0.1:6379> rpoplpush list otherlist		#要移除的列表,目标列表
"helllo2"
127.0.0.1:6379> lrange list 0 -1
1) "helllo0"
2) "helllo1"
127.0.0.1:6379> lrange otherlist 0 -1
1) "helllo2"

---------------------------------------------------------------------

---------------------------------------------------------------------
#6.
#将列表中指定索引的值替换为相应值:lset
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lset list 0 substitute		#替换的索引  替换为的值
OK
127.0.0.1:6379> lrange list 0 0			
1) "substitute"

#将某个具体的值插入到列表中某个元素的前面或后面:

127.0.0.1:6379> LRANGE list 0 -1
1) "hello0"
2) "hello1"
127.0.0.1:6379> linsert list before hello1 what			#before/after  插入的值
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello0"
2) "what"
3) "hello1"


---------------------------------------------------------------------

总结

🤎 list 实际上是一个链表, before Node after ,它的 left 和 right 都可以插入值

🖤 如果 key 不存在,则创建一个新的列表

🤍 如果 key 存在,则新增内容

💓 如果移除了所有值,变成空链表,即不存在

💖 在两边插入或修改值,效率最高。中间元素相对效率会低点(与java类似)

💘 消息排队!消息队列(Lpush Rpop) 栈(Lpush Lpop)♥

Set(集合)

💥 set中的值不可以重复

---------------------------------------------------------------------
#1.
#存值 sadd
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "yes"
(integer) 1
 
#查看所有的元素  smembers
127.0.0.1:6379> smembers myset
1) "yes"
2) "hello"
3) "world"

#判断集合中是否有指定元素
127.0.0.1:6379> sismember myset yes
(integer) 1

---------------------------------------------------------------------

---------------------------------------------------------------------
#2.
#查看 set 中的元素个数  scard
127.0.0.1:6379> scard myset
(integer) 3

#移除元素  srem
127.0.0.1:6379> srem myset yes
(integer) 1
 
127.0.0.1:6379> smembers myset
1) "hello"
2) "world"

#随机抽选 set 中的元素  srandmember
127.0.0.1:6379>  srandmember myset
"hello"

#随机删除 set 中的元素  spop
127.0.0.1:6379> spop myset
"hello"

#移动指定的元素到指定的 set 中   smove
127.0.0.1:6379> smove myset newset yes		#源集合  目标集合  指定元素
(integer) 1

---------------------------------------------------------------------

---------------------------------------------------------------------
#3.
#共同关注  共同好友:
#数学集合: 	并集、交集、差集

127.0.0.1:6379> smembers set1
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> smembers set2
1) "d"
2) "e"
3) "c"

#差集
127.0.0.1:6379> sdiff set1 set2
1) "a"
2) "b"

#交集
127.0.0.1:6379> sinter set1 set2
1) "c"

#并集
127.0.0.1:6379> sunion set1 set2
1) "c"
2) "b"
3) "d"
4) "a"
5) "e"


---------------------------------------------------------------------

Hash

💨 Map集合 前面的都是 key-value 型,而哈希为 key-map 型,这时候这个值是一个map集合,相当于 key-(key-value)

本质与 String 没多大区别

用法

---------------------------------------------------------------------
#1.
#设置一个具体的 key-value 的哈希值   hset 
127.0.0.1:6379> hset hash f1 hello
(integer) 1

#获取字段值 hget
127.0.0.1:6379> hget hash f1
"hello"

#批量设置,可覆盖 	hmset
127.0.0.1:6379> hmset hash f1 hello f2 world
OK

#批量获取	hmget
127.0.0.1:6379> hmget hash f1 f2
1) "hello"
2) "world"

#获取全部 hgetall
127.0.0.1:6379> hgetall hash
1) "f1"
2) "hello"
3) "f2"
4) "world"

#删除指定的 key ,其对应的 value 也消失了
127.0.0.1:6379>  hdel hash f1
(integer) 1
127.0.0.1:6379> hgetall hash
1) "f2"
2) "world"



---------------------------------------------------------------------

---------------------------------------------------------------------
#2.
# 获取哈希表的字段长度  hlen 
127.0.0.1:6379> hlen hash
(integer) 2

#判断哈希中的指定字段是否存在  hexists
127.0.0.1:6379> HEXISTS hash f2
(integer) 1


#查看所有的键  hkeys
127.0.0.1:6379> hkeys hash
1) "f2"
2) "f1"

#查看所有的值	 hvals
127.0.0.1:6379> hvals hash
1) "world"
2) "hello"

---------------------------------------------------------------------

---------------------------------------------------------------------
#3.
#自增与自减: hincreby  增则参数为正数,反之为负数 

#hsetnx	:如果不存在则可以设置


#设置对象:
127.0.0.1:6379>  hset user:1 name xx
(integer) 1
127.0.0.1:6379> hget user:1 name
"xx"

---------------------------------------------------------------------

🕳 哈希常用于存储用户的信息,尤其是常常变更的信息

💫 哈希更适合对象的存储,String 更适合字符串的存储(太多冒号不好 )

Zset(有序集合)

🈶 在 set 的基础上,增加了一个值,相当于优先级。zset key score value


---------------------------------------------------------------------
#1.
#添加值,可一次添加多个值。  zadd
127.0.0.1:6379> zadd myset 1 one
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three
(integer) 2

#查看所有的值,会自动按照优先级进行排序
127.0.0.1:6379>  zrange myset 0 -1
1) "one"
2) "two"
3) "three"

---------------------------------------------------------------------

---------------------------------------------------------------------
#2.
#排序	zrangescore

127.0.0.1:6379> zrangebyscore salary -inf inf		#从负无穷到正无穷按照 score 进行升序排序
1) "a"
2) "c"
3) "b"
127.0.0.1:6379> zrangebyscore salary -inf inf withscores		# 显示数值
1) "a"
2) "100"
3) "c"
4) "150"
5) "b"
6) "300"

127.0.0.1:6379> zrangebyscore salary -inf 200 withscores		#还可以自动筛选指定范围内的进行排序显示 
1) "a"
2) "100"
3) "c"
4) "150"

#降序排列	zrevrangebyscore
127.0.0.1:6379> zrevrangebyscore salary inf -inf
1) "b"
2) "c"
3) "a"

---------------------------------------------------------------------

---------------------------------------------------------------------
#3.
#移除指定的元素
127.0.0.1:6379> zrem salary a
(integer) 1
127.0.0.1:6379>  zrange salary 0 -1
1) "c"
2) "b"

#获取有序集合中的个数
127.0.0.1:6379> zcard salary
(integer) 2


---------------------------------------------------------------------

🉑 可到官方文档查询所需要使用的 API

用处:工资表排序、评分、带权重排序

Top↑

四、三大特殊类型

geospatial
Hyperloglog
Bitmap

geospatial(地理位置)

☢ 定位、距离运算、附近的人等……

☣ Redis 的 Geo 在 Redis3.2 版本就已经推出了,此功能可以推算出地理位置的信息,如半径内的人,计算距离等

⚠ 可以查询一些测试数据 http://www.hao828.com/chaxun/zhongguochengshijingweidu/

geoadd


#1.
#添加地理位置:	geoadd
#规则:两级的地理位置无法添加,一般会下载城市数据通过 java 添加
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing		#key  经度	纬度	城市名(属性名)
(integer) 1

GEOPOS

#获取指定元素(城市) 的经度和纬度(坐标值)
127.0.0.1:6379> GEOpos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"

GEODIST

查看两个位置之间的直线距离

单位:

千米 km,米 m,mi 英尺,ft 英里 ()

127.0.0.1:6379> GEODIST china:city shanghai beijing	     #默认是米
"1067378.7564" 
127.0.0.1:6379> GEODIST china:city shanghai beijing km		#可更改单位
"1067.3788"

GEORADIUS

查看指定坐标半径内的元素(查看附近的人)

#(查看 (110,30)方圆1000km的城市)
127.0.0.1:6379> GEORADIUs china:city 110 30 1000 km		#元素  经度 纬度  距离 单位	
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"

127.0.0.1:6379> GEORADIUs china:city 110 30 500 km
1) "chongqing"
2) "xian"

#withcoord 并显示经纬度
127.0.0.1:6379> GEORADIUs china:city 110 30 1000 km withcoord		
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
3) 1) "shenzhen"
   2) 1) "114.04999762773513794"
      2) "22.5200000879503861"
4) 1) "hangzhou"
   2) 1) "120.1600000262260437"
      2) "30.2400003229490224"

#withdist 显示距离
127.0.0.1:6379> GEORADIUs china:city 110 30 1000 km withdist 
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
3) 1) "shenzhen"
   2) "924.6408"
4) 1) "hangzhou"
   2) "977.5143"


# count 还可以限定查询的数量
127.0.0.1:6379> GEORADIUs china:city 110 30 1000 km withcoord withdist count 2
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"


GEORADIUSBYMEMBER

以元素的定位查找周边的其他元素

127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
2) "xian"

🚸 GEO 的底层实现原理起始就是 Zset ,可以运用 Zset 来操作GEO

127.0.0.1:6379> zrange china:city 0 -1		#查看
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city chongqing	#移除
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "xian"
2) "shenzhen"
3) "hangzhou"
4) "shanghai"
5) "beijing"

Hyperloglog

什么是基数:

A{1,3,5,7,9,9} B{1,3,5,6,8}

基数(集合的不重复数)= 5,可接受误差

简介

是一种数据结构,做基数统计的算法!

💤 网页的 UV (一个人访问多次网站,但还是只算做一个人)

传统的方式:利用 set 来保存用户的 id ,然后就可以统计 set 中的元素数量为判断标准(保存大量的用户 id 会比较麻烦(占内存))

💦 优点:占用固定的内存

测试使用:

#创建key 并添加元素:
127.0.0.1:6379> pfadd key a b  c d e f g h i j 
(integer) 1

#统计所有不重复元素数量
127.0.0.1:6379> pfcount key
(integer) 10
127.0.0.1:6379> pfadd key2 q w e r t y u
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 7

#进行并集
127.0.0.1:6379> pfmerge key3 key key2		#目的  源 源
OK
127.0.0.1:6379> pfcount key3
(integer) 15

💢 如果允许容错,可以使用 hyperloglog(容错率0.81%),反之则使用 set 或者自订的数据类型

Bitmap

位储存

⛔ 统计用户信息。(存储 0 与 1 )状态信息等

Bitmaps 位图,是一种数据结构。

使用:

setbit 设置位图与其状态值

例:记录一周的打卡状态:

请添加图片描述


bitcount 统计

#统计状态为 1 的数量
127.0.0.1:6379> bitcount sign
(integer) 4

Top↑

事务

MySQL (关系型数据库)中事务的概念:

MySQL:ACID。**事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功 **(原子性)。

  • 原子性

    原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败

  • 一致性

    事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。

  • 隔离性

    事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。

  • 持久性

    持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

#️⃣ 隔离级别:多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确

​ 若没有隔离性:

  • 脏读指一个事务读取了另外一个事务未提交的数据
  • 不可重复读指在一个事务内读取表中的某一行数据,多次读取结果不同。
  • 虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致

但 Redis 单条命令保证原子性,事务不保证原子性

Redis 没有隔离级别的概念,既不可能出现幻读、脏读等

Redis 事务本质:一组命令的集合!

--------- 队列  命令  命令  命令  执行-----------

🛴 所有的命令都会被序列化,在事务的执行过程总中,会按照顺序执行 :一次性、顺序性、排他性

所有的命令并没有被执行,只有在发起执行命令的时候才会被执行

redis 的事务:

  • 开启事务 (multi)
  • 命令入队 (…)
  • 执行事务 (exec)
  • 放弃事务 (discard)
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)> keys *
QUEUED

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



127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1 
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> keys *
QUEUED

#放弃事务
127.0.0.1:6379(TX)> DISCARD
OK
#队列中的事务都不会被执行
127.0.0.1:6379> get k5
(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> multi
OK
127.0.0.1:6379(TX)> incr k1		#字符串不能+1,代码结构没错,但逻辑错误了
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)> get k3
QUEUED

#除了异常的都会执行
127.0.0.1:6379(TX)> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k3
"v3"

❕ 单条命令保证原子性,事务不保证原子性!

以上情况在 MySQL 中是不可能执行的。

监控:Watch

乐观锁

💖 乐观,认为什么时候都不会出问题,所以不会上锁,更新时会判断一下,在此期间是否有人修改过这个数据。

  • 获取 version

  • 更新时比较 version

悲观锁

💔 悲观,认为什么时候都会出问题,无论干什么都会加锁


Redis 监视测试

正常执行成功:

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK

#监视 money
127.0.0.1:6379> watch money 
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED

#事务正常结束,事务期间没有发生数据变化,这个时候就执行成功!
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

✴ 多线程测试:修改值,使用watch可以当作乐观锁操作

#线程1:
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi 
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED

#此时,线程2 对money进行修改:
127.0.0.1:6379> set money 1000
OK

#线程1事务便执行失败
127.0.0.1:6379(TX)> EXEC
(nil)

♈ 解锁:unwantch

Top↑

Jedis

我们要使用 java 来操作 Redis

简介

Jedis 是 Redis 官方推荐的 Java 连接开发工具!使用 java 开发 redis 中间件。

使用

  • 导入对应的依赖:

<!-- 导入 jedis 的包-->
    <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
   <dependencies>
       <dependency>
           <groupId>redis.clients</groupId>
           <artifactId>jedis</artifactId>
           <version>3.3.0</version>
       </dependency>

<!--       fastonjason-->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.62</version>
       </dependency>
   </dependencies>

  • 编码测试 :

    • 连接数据库

    • 操作命令

    • 断开连接。

代码示例

public class TestTX {
    public static void main(String[] args) {
        //连接远程 Redis
        Jedis jedis = new Jedis("47.113.205.23",6379);
        //测试是否连接成功
        System.out.println(jedis.ping());

        //关闭连接
        jedis.close();

    }
}

jedis 事务测试:

public static void main(String[] args) {
        //连接远程 Redis
        Jedis jedis = new Jedis("47.113.205.23",6379);
        //测试是否连接成功
        System.out.println(jedis.ping());

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");        //添加 json 键值对
        jsonObject.put("name","xx");

        String jsonString = jsonObject.toJSONString();
        //开启事务:
        Transaction multi = jedis.multi();
        try {
            multi.set("user1",jsonString);
            multi.set("user2",jsonString);
            multi.exec();
            Set set = jedis.keys("*");
            Iterator iter = set.iterator();
            while (iter.hasNext()){
                Object o = iter.next();
                System.out.println(o);
                System.out.println(jedis.get(o.toString()));
            }
        } catch (Exception e) {
            multi.discard();     //放弃事务
            e.printStackTrace();
        }finally {
            jedis.close();
        }
}

Top↑

Spring Boot 整合

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

‼💥 在springboot 2.x 之后,原来的 jedis 换成了 lettuce

  • jedis:采用的直连,多个线程操作的话,是不安全的,若要避免,则需要使用 redis 线程池。像BIO模式

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

源码分析:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    
   //我们可以自定义一个 redisTemplate 去替换这一个默认的
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        //默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化
        //两个泛型都是 Object 类型,后面使用需要进行强制转换,期望值为:<String,Object>
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)		//String 类型是最常使用的类型所以单独提出来一个 bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

整合测试

  • 导入依赖
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置连接
#Spring boot的所有配置类,都会有一个自动配置类
#自动配置类都会绑定一个 properties 文件
#   RedisAutoConfiguration  RedisProperties


#配置Redis:
spring.redis.host=47.113.205.23
spring.redis.port=6379


  • 测试
	//自动导入 进行操作
	@Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        //redisTemplate 操作不同的数据类型
        //opsForValue:操作字符串,后接其相应方法
        //opsForList :  操作 list
        //opsForSet  :  操作 set
        //…………………………………………


        //除了基本的操作,常用的方法可以直接通过 redisTemplate 操作,比如事务和基本的 CRUD

        //获取连接对象:
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushAll();
//        connection.flushDb();

        redisTemplate.opsForValue().set("hello","滴滴滴");
        System.out.println(redisTemplate.opsForValue().get("hello"));
    }

分析

⚠ 但是通过以上方法所设置的键值会乱码:

请添加图片描述

🎋 RedisTemplate 分析:

请添加图片描述

请添加图片描述

因此,我们需要自己常见一个 redis 配置类,真实开发中很少使用默认的 redisTemplate

🧨 所有的 pojo 类对象需要序列化!但默认使用了 jdk 序列化

public class User implements Serializable {
    private String name;
    private int age;

}

💝💝 编写自己的 redisTemplate ,直接使用:

package com.xerxes.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Created on 2022/3/8.
 *
 * @author WEZARD
 */

@Configuration
public class redisConfig {
    //编写我们自己的 redisTemplate:固定模板,直接使用

    //注册到 bean 中
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {     //首先改成 <String, Object>

        RedisTemplate<String, Object> template = new RedisTemplate();
        //连接工厂
        template.setConnectionFactory(redisConnectionFactory);

        //配置的重点是 serializable 序列化配置

        //使用 Jackson 的 Serializer 对象    (已过时)
//        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>();

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

        //key 的序列化
        template.setKeySerializer(stringRedisSerializer);        //需要接受一个 Serializer 对象,自行 new
        //hash 的 key 的序列化
        template.setHashKeySerializer(stringRedisSerializer);
        //value 的序列化
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        //hash 的 value  的序列化
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());         //新一代 Serializer 对象

        template.afterPropertiesSet();
        return template;
    }
}

✴ 开发过程中,80%情况都不会使用原生的代码(诸如opsForVlue……)去开发,一般使用:redisUtil (自己编写工具类)

Top↑

Redis.cof 详解

启动的时候通过此配置文件启动

网络配置:

bind 127.0.0.1 -::1		#指定可访问 Redis 的 ip,可以直接注释掉
protected-mode no		#保护模式,
port 6379				#端口设置
	
	

通用配置 General:

daemonize yes		#以守护进程的方式运行,默认是 no ,需要 yes

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

快照:

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

🔆 redis 是内存数据库,如果没有持久化,那么数据断电就会丢失

# save 3600 1		#如果 3600 秒内,至少有 1 个 key 修改,则将会进行持久化操作 
# save 300 100		#如果 300 秒内,至少有 10 个 key 修改,则将会进行持久化操作 
# save 60 10000		#如果 60 秒内,至少有 10000 个 key 修改,则将会进行持久化操作 
#可以自行定义

stop-writes-on-bgsave-error yes		#如果持久化出错了,是否还需要继续工作

rdbcompression yes					#是否压缩 rdb 文件(持久化文件),需要消耗cpu

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

dir ./						#rdb 文件保存的目录

限制

maxclients 10000		#设置能连接上 redis 客户端的最大数量
maxmemory <bytes>		#redis 配置的最大内存容量
maxmemory-policy noeviction   #内存达到上限之后采取的策略
#移除一些 key
#报错
#……………………

APPEND ONLY MODE (aof 模式的配置)

appendonly no		#默认是不开启 aof 模式的,默认是采用 rdb 模式进行持久化的。大部分的情况下 rdb 够用
appendfilename "appendonly.aof"		#持久化文件的名字

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

Top↑

持久化

RDB
AOF

redis是一个内存数据库,数据保存在内存中,但是我们都知道内存的数据变化是很快的,也容易发生丢失。幸好Redis还为我们提供了持久化的机制。

RDB(Redis database)

RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。

请添加图片描述

在指定的时间间隔内将内存中的数据集快照写入到硬盘中,也就是 Snapshot 快照,他恢复是将快照文件直接导入内存里

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

🔱 默认就是 RDB 文件,一般不需要修改这个配置

生产环境中,进行备份

RDB 保存的文件: dump.rdb

触发条件

请添加图片描述

  • save 命令:该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。

  • bgsave 命令:执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。

  • save 规则满足的情况下,也会触发 RDB

  • 执行 FLUSHALL 命令,会触发 RDB

  • 退出 redis ,会触发 RDB

请添加图片描述

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

恢复 RDB 文件

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

  • 查看需要存放的位置:

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin/Xconfig"			#若存在  dump.rdb 则会自动恢复

优点:

  • RDB文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。
  • 适合大规模的数据恢复,速度快(fork)
  • 对数据的完整性要求不高

缺点:

  • 若 redis 意外宕机,则最后一次修改的数据将会丢失 ->子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。

  • 占用存储空间

AOF (Append Only File)

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

请添加图片描述

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

aof 保存的文件是 :Appendonly.aof 文件

append

默认是不开启的,我们需要手动进行配置!我们只需要将 appendonly 改为 yes 就开启了 aof

当同时开启的时候,aof 的优先级大于 rdb

重启 redis 就可以生效

🕉 如果这个 aof 文件有错位(损坏),这时候 redis 是启动不起来的,我们需要进行修复 aof 文件

redis 给我们提供了一个工具:redis-check-aof --fix

redis-check-aof --fix appendonly.aof

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

重写规则说明

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

请添加图片描述

如果 aof 文件大于 64m ,太大了。fork一个新的进程来将文件进行 重写

AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写。

优点和缺点:

优点:

  • everysc 每一次修改都同步,文件的完整性会更好!
  • 每秒同步一次,可能会丢失一秒的数据
  • no 从不同步,效率最高
  • AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。
  • AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没有发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据

缺点:

  • 相对于数据文件来说,aof 远远大于 rdb,修复的速度也比 rdb 慢
  • aof 的运行效率也要比 rdb 慢

扩展:

1、RDB 持久化方式能够在指定的时间间隔内对你的数据进行快照存储
2、AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
3、只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
4、同时开启两种持久化方式

  • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
  • RDB 的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。

5、性能建议

  • 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
  • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
  • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

Top↑

Redis 发布订阅

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

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

模式:消息发布者、频道、消息订阅者

请添加图片描述

频道1 以及订阅这个频道的三个客户端之间的关系:

请添加图片描述

当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会发送给订阅他的三个客户:

请添加图片描述

命令

命令描述
PSUBSCRIBE pattern [pattern…]订阅一个或多个符合给定模式的频道。
PUNSUBSCRIBE pattern [pattern…]退订一个或多个符合给定模式的频道。
PUBSUB subcommand [argument[argument]]查看订阅与发布系统状态。
PUBLISH channel message向指定频道发布消息
SUBSCRIBE channel [channel…]订阅给定的一个或多个频道。
UNSUBSCRIBE channel [channel…]退订一个或多个频道

测试

订阅端:

127.0.0.1:6379> subscribe xx		#订阅频道 “xx”
Reading messages... (press Ctrl-C to quit)		#等待信息
1) "subscribe"
2) "xx"
3) (integer) 1

#消息
1) "message"		#消息
2) "xx"				#来源于那个频道
3) "hello"			#消息内容

1) "message"
2) "xx"
3) "hi"

发布端:

127.0.0.1:6379> publish xx "hello"		#发布者发布消息到频道中
(integer) 1
127.0.0.1:6379> publish xx "hi"
(integer) 1

原理

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

请添加图片描述

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

缺点

  • 如果一个客户端订阅了频道,但自己读取消息的速度却不够快的话,那么不断积压的消息会使redis输出缓冲区的体积变得越来越大,这可能使得redis本身的速度变慢,甚至直接崩溃。
  • 这和数据传输可靠性有关,如果在订阅方断线,那么他将会丢失所有在短线期间发布者发布的消息。

Top↑

Redis 主从复制

环境配置
集群
哨兵模式

概念:

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

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

🛐 主从复制的作用主要包括:

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
  • 负载均衡:再主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器均衡;尤其是再写少读多的情况下,通过多个从节点分担负载,可以大大提高 Redis 服务器的并发量。
  • 高可用(集群)基石:除上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础。

⛎ 一般来说,要将 Redis 运用于工程项目应中,只使用一台 Redis 是万万不能的(意外宕机)原因:

  • 从结构上,单个 Redis 服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大。
  • 从容量上,单个 Redis 服务器内存容量有限,就算一台 Redis 服务器的容量为256G,也不能将所有内存作 Redis 存储内存,一般来说,单台 Redis 最大内存使用量不应该超过20G。

♈ 电商网站上的商品,一般都是一次上传,无数次浏览的,即多读少些。

对于此场景。我们可以使用如下架构:

请添加图片描述

主从复制,读写分离!80%的情况下都是在进行读操作。减缓服务器的压力。

最低配:一主二从。真实开发中,不可能使用单机,一定使用集群。

环境配置

只配置从库,不配置主库。Redis 默认当前库为主库

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

♉ 一主二从,复制3个配置文件(redis.conf),然后修改相应的信息

  1. 端口

  2. pid 名字

  3. 日志名字

  4. dump.rdb 名字、

    例如:6381从机文件:

port 6379 改成:port 6381

pidfile /var/run/redis_6379.pid  改成: pidfile /var/run/redis_6381.pid

logfile "" 	改成:  logfile "6381.log"


dbfilename dump.rdb  改成: dbfilename dump6381.rdb


单机多集群:启动三个服务:

redis-server redis79.conf 
redis-server redis80.conf 
redis-server redis81.conf 

查看进程信息:

请添加图片描述

一主二从

默认情况下,每台主机都是主节点,所以一般情况下只用配置从机。主机79

#从机配置:	SLAVEOF (主机)主机名 端口号
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK

#可以看到从机的信息
127.0.0.1:6381> info replication
# Replication
role:slave					#角色
master_host:127.0.0.1		#主机 ip
master_port:6379			#主机端口号
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_read_repl_offset:182
slave_repl_offset:182
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:0fdf28881bd48720fac26140ce5924f15cba3ab7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:182
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:182

#可以看主机的信息:
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2												#两台从机
slave0:ip=127.0.0.1,port=6381,state=online,offset=98,lag=0		#从机信息
slave1:ip=127.0.0.1,port=6380,state=online,offset=98,lag=0
master_failover_state:no-failover
master_replid:0fdf28881bd48720fac26140ce5924f15cba3ab7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:98
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:98

❗ 如果主机有密码就会绑定失败 :master_link_status:down
vim 从机的配置文件,找到主从配置 REPLICATION 这个地方
把这个:# masterauth
改成:masterauth 主机的主机密码 例如: masterauth 123456

♊ 真实的主从配置是应该从配置文件中配置的,这样子才是永久的。通过命令行配置是暂时的,关闭就没有。

请添加图片描述

细节

主机可以写,从机不可以。主机中的所有信息和数据,都会被从机保存

127.0.0.1:6380> get k1		#从机可以读主机写的内容
"v1"	
127.0.0.1:6380> set k2 v2		#从机不可以写
(error) READONLY You can't write against a read only replica.

♋ 主机断了,从机依旧保持连接。重新连接后仍可正常运行

♌ 如果使用命令行配置的主从,当从机断开重连之后,从机就变成了主机,不能正常运行。只有当再次成为从机才能再次从主机之中获取值。

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

复制原理

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

♍ 全量复制:slave 服务在接收到数据库文件数据后,将其存盘加载到内存中。(连接后全部同步)

♎ 增量复制:master 继续将新的所有收集到的修改命令依次传给 slave,完成同步。(后来开发之中新增)

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

Redis 蜈蚣

请添加图片描述

♏ 原先的80端口由从机变成即是主机又是从机,但实质上仍是从机,仍不能进行写操作

80 的信息:

127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_read_repl_offset:12708
slave_repl_offset:12708
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=12708,lag=1
master_failover_state:no-failover
master_replid:0fdf28881bd48720fac26140ce5924f15cba3ab7
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:12708
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:99
repl_backlog_histlen:12610

苍天已死,黄巾当立

♐ 当主机挂了的时候,从机可以手动配置成为主机:

SLAVEOF NO ONE

主机回来时只有再次修改才能恢复。

哨兵模式

(自动选举老大的模式)

概述

主从切换的方法是:当主服务器宕机后,需要手动把一台从服务器转换成主服务器,这时候需要人工干预,费时费力,还会造成一段时间内服务不可用。这时候我们有限考虑哨兵模式。Redis 从2.8开始正式提供了 Sentinel (哨兵)架构来解决这个问题。

♑ 哨兵模式,能够自动后台监控主机是否故障,如果故障了根据投票数自动将从库转换成主库。

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

请添加图片描述

♓ 这里哨兵的作用:

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

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

请添加图片描述

多个哨兵可以进行哨兵之间的互相监督,以防有一方挂了。可以立刻通知。

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

♾ 当后面的哨兵也检测到主服务器不可用,当数量到达一定值时,那么哨兵之间将会进行一次投票,投票的结果由一个哨兵发起,进行 failover 故障转移操作。切换成功后,就会通过发布订阅模式,让各个哨兵把主机监控的从服务器实现切换主机,这个给过程册灰姑娘为客观下线。

测试

目前状态:一主二从。

  1. 在 redis.conf 同级目录下创建哨兵配置文件 sentinel.conf
#sentinel monitor 自己写的主机的名字 host port
sentinel monitor myredis host 127.0.0.2 6379 1

#当设置了 redis 密码的时候要设置上。
sentinel auth-pass 自己写的主机的名字 自己设置的密码
sentinel auth-pass myredis 010507

后面这个1代表着主机挂了,从机投票谁来成为主机,票数最多的成为主机。

​ 2.启动哨兵

#启动哨兵
[root@iZf8zi9i30yumlfpgkhceqZ Xconfig]# redis-sentinel sentinel.conf 	
4239:X 10 Mar 2022 22:21:02.457 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
4239:X 10 Mar 2022 22:21:02.457 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=4239, just started
4239:X 10 Mar 2022 22:21:02.457 # Configuration loaded
4239:X 10 Mar 2022 22:21:02.458 * monotonic clock: POSIX clock_gettime
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.2.6 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                  
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379						#哨兵进程端口号
 |    `-._   `._    /     _.-'    |     PID: 4239
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           https://redis.io       
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

4239:X 10 Mar 2022 22:21:02.458 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
4239:X 10 Mar 2022 22:21:02.461 # Sentinel ID is d6ab204aaaf4dafab8a6d8924a808010a20d7119
4239:X 10 Mar 2022 22:21:02.461 # +monitor master myredis 127.0.0.2 6379 quorum 1							#主机有1票是老大
4239:X 10 Mar 2022 22:21:02.461 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.2 6379		#两个从机 80 和 81
4239:X 10 Mar 2022 22:21:02.464 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.2 6379

⚛ 如果 master 主机宕机了,那么将会随机的从 slave 从机中选择一个作为新的主机(有具体的投票算法)。

请添加图片描述

哨兵日志:

请添加图片描述

朕的大清亡了

若是原本主机回来了,那么只能归并到新的主机之中成为从机。

127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_read_repl_offset:61591
slave_repl_offset:61591
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:729b2821f4590892a906f0143b0d2e28ea278563
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:61591
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:60221
repl_backlog_histlen:1371

哨兵模式

优点:

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

缺点:

  • Redis 难以再现扩容,集群容量一旦到达上限,在线扩容就会非常麻烦。
  • 实现哨兵模式的配置很麻烦,太多选择。

哨兵模式全部配置:

# Example sentinel.conf
 
# 哨兵sentinel实例运行的端口 默认26379		
# 如果有哨兵集群,我们还需要配置每个哨兵端口。需要像redis集群那样复制多份配置文件
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

Top↑

Redis 缓存穿透与雪崩

穿透
击穿
雪崩

服务器的高可用问题

Redis 缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,也带来了一些问题。其中,最坏的问题就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。

缓存穿透(查不到)

概念

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

查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

在系统层面来看,像是直接穿透了缓存层而直接向数据库查询数据。

解决方案

布隆过滤器:

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

请添加图片描述

缓存空对象:

🚫 当存储层不命中后,即使返回的空兑现也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取(空值),这样就不用再到数据库,保护了后端数据源。

请添加图片描述

但这种方法会有问题:

  • 如果空值能被缓存起来,那就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多空的键。
  • 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间的窗口不一致,这对于需要保存一致性的业务会有影响。

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

子弹全都打在一个点上,墙也可能被攻破。

微博服务器宕机:所有的流量都在一个点上(极短时间)。

概述

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

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

解决方案

🔇 设置热点数据永不过期

从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。

🔕 加互斥锁

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

请添加图片描述

缓存雪崩

概念

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

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

请添加图片描述

雪崩发生时,没有一片雪花是无辜的!

🚷 起始集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或者断网。因为自然形成的缓存雪崩,一定是在某个时间段集种创建缓存。这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对服务器造成的压力是不可预知的,很有可能瞬间就把服务器压垮。

双十一:停掉一些服务(退款),保证主要的服务可用!

解决方案

🚯 Redis 高可用

含义:既然 Redis 可能挂掉,那就多增设几台 redis ,一台挂掉其他还可以工作,其实就是搭建的集群。(异地多活、异地备灾)

**🚳 **限流降级

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

**🚱 **数据预热

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值