Redis目录

  • Redis
  • NOSQL
  • 什么是NOSQL
  • Redis
  • 安装redis
  • redis-benchmark的性能测试
  • redis的配置文件解读
  • redis基本知识(可忽略)
  • 五大数据类型
  • Redis-key命令
  • String(字符串)
  • List(列表)
  • Sets(集合)
  • Hash(哈希)
  • sorted set(有序集合)
  • 三大特殊数据类型
  • GEO
  • HyperLogLog
  • Bitmap(位图)
  • redis的事务
  • 事务
  • 监控
  • Jedis
  • jedis远程连接redis准备
  • 使用jedis的api
  • jedis的**事务**
  • jedis开启监控
  • SpringBoot整合Redis
  • 简单使用RedisTemplate
  • 自定义RedisTemplate
  • Redis的持久化
  • RDB(Redis DataBase)
  • AOF(Append Only File)
  • Redis的订阅发布
  • Redis集群
  • Redis的主从复制
  • 主从库的配置
  • 哨兵模式
  • 简单了解Redis的缓存穿透、击穿与雪崩
  • 缓存穿透
  • 缓存击穿
  • 缓存雪崩



Redis

根据“狂神说”的redis教程学习。他有b站号和微信公众号,如果想要学习,可自行去搜索。

NOSQL

什么是NOSQL

NoSQL,泛指非关系型的数据库,区别于关系数据库。

随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

例如当前各种类型用户的个人信息,社交网络,地理位置等……这些数据的存储不需要一个具体的类型,并且要求可以简易的拓展。就可以通过key-value形式来进行数据存储,Map<String,Object>。

优点:

  • 易拓展:NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。数据之间无关系,这样就非常容易扩展。无形之间,在架构的层面上带来了可扩展的能力。
  • 高性能:NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。NoSQL的缓存记录级是细粒度的,所以性能高很多。
  • 灵活的数据模型:不需要事先设计数据库,随时可以存储自定义的数据格式。在大数据时代,如果是关系型数据库,想给一个有大量数据的表增加一个字段,那真是难为人……

NOSQL的四大分类

NOSQL分类

数据库

应用场景

数据模型

优点

缺点

键值(key-value)

Redis、Oracle BDB

内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。

Key 指向 Value 的键值对,通常用hash table来实现

查找速度快

数据无结构化,通常只被当作字符串或者二进制数据

列存储数据库

HBase

分布式的文件系统

以列簇式存储,将同一列数据存在一起

查找速度快,可扩展性强,更容易进行分布式扩展

功能相对局限

文档型数据库

MongoDb、CouchDB

Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容)

Key-Value对应的键值对,Value为结构化数据

数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构

查询性能不高,而且缺乏统一的查询语法。

图像数据库

Neo4J、InfoGrid

社交网络,推荐系统等。专注于构建关系图谱

图结构

利用图结构相关算法。比如最短路径寻址,N度关系查找等

很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。

  • 键值:必须掌握redis,目前主流。
  • 列存储数据库:HBase,更容易拓展列。
  • 文档型数据库:
  • MongoDb:一个基于分布式文件存储的数据库,C++编写,可用于存储大量文档,例如商品的评论等。是一个介于NOSQL和关系型数据库中间的产品,MongoDb是功能最丰富,最像关系型数据库的NOSQL
  • CouchDB
  • 图像数据库 :并不是指用于存储图品的数据库,而是用于存储关系,例如一个人社交等。可用于广告推荐等。

Redis

安装redis

以linux(centos7)为例。redis版本5.0.10.如果redis为6版本,则需要时间升级环境。

  1. 下载redis安装包。rediso-5.0.10.tar.gz
  2. 解压进 /opt目录下
  3. redis 的decrement方法 redis的increment_redis

  4. 进入解压的文件,则可以看到配置文件redis.conf
  5. redis 的decrement方法 redis的increment_nosql_02

  6. 安装redis所需要的基本环境
yum install gcc-c++   #安装c语言环境

make	#安装

make install
遇到的问题,如果安装的时redis6版本,则linuc(centos7)默认的gcc版本过低,需要升级

redis 的decrement方法 redis的increment_redis_03

gcc版本需要升级到5.3以上

#升级到 5.3及以上版本 yum -y install centos-release-scl yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils #注意:scl命令启用只是临时的,推出xshell或者重启就会恢复到原来的gcc版本。 scl enable devtoolset-9 bash #如果要长期生效的话,执行如下: echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile

但是因为升级下载太慢,我放弃了,才选回用5.0.10的redis。

  1. redis的默认安装路径:/usr/local/bin
  2. 将redis的配置文件,复制到安装目录下(我创建了一个config文件夹,将配文件放入其中)
  3. vim命令修改配置文件,将daemonize改为yes。

daemonize:yes 当redis.conf中选项daemonize设置成yes时,代表开启守护进程模式。在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。

  1. 启动redis服务,并指定配置文件
  2. redis 的decrement方法 redis的increment_nosql_04

  3. 连接redis
    redis-cli -h host -p port -a password
    host:远程redis服务器host(连接本机可默认不屑)
    port:redis服务端口
    password:远程redis服务密码(无密码的的话就不需要-a参数了)
  4. 关闭redis服务,以及退出连接
  5. redis 的decrement方法 redis的increment_nosql_05


redis-benchmark的性能测试

redis-benchmark是一个redis官方自带的压力测试工具

redis-benchmark [option] [option value]

注意:该命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。

参数:

redis 的decrement方法 redis的increment_redis_06

实例:100个并发,10万个请求

redis-benchmark -h localhost -p 6379 -c 100 -n 100000

测试结果解析:以SET请求为例

redis 的decrement方法 redis的increment_redis 的decrement方法_07


redis的配置文件解读

  1. 单位
  2. 导入其他配置文件
  3. 网络配置部分
  4. 通用配置
  5. 快照,将操作持久化未rdb文件或aof文件。
  6. 主从复制
  7. 安全

    也可以通过命令的形式操作密码
config set requirepass 密码  #设置密码
config get requirepass 密码  #获取密码
auth 密码	#通过密码登入
#或者在连接redis的时候直接使用	-a 密码	来输入密码
redis-cli -p 6379 -a 密码
  1. 客户端规则
  2. 内存管理

    八种内存淘汰方式解释:

序号

淘汰策略

意义

1

noeviction

无法继续写入(默认)

2

allkeys-lru

所有key中,至今最长时间未被访问的

3

allkeys-lfu

所有key中,最近一段时间内访问次数最少的

4

allkeys-random

所有key中,随机选择

5

volatile-ttl

过期的key以及即将快要到期的

6

volatile-lru

过期的key中至今最长时间未被访问的

7

volatile-lfu

过期的key中最近一段时间内访问次数最少的

8

volatile-random

过期的key中随机选择

  1. APPEND ONLY模式(aof配置)

redis基本知识(可忽略)

  1. redis默认有16个数据库。
    查看redis.conf配置文件,可以看到

    使用select num命令可以切换指定num数据库,默认连接数据库编号为0(第一个数据库)

flushdb命令可以清空当前库,flushall则会清空全部数据库。

redis 的decrement方法 redis的increment_redis 的decrement方法_08

  1. redis是单线程的(redis6之前,redis6默认不开启)
    redis是基于内存操作的,cpu并不是redis的性能瓶颈,redis的瓶颈是机器的内存和网络带宽。

redis6已经支持多线程了。

  1. 虽然支持,默认不开启
  2. 配置线程数要小于CPU数
  3. 虽然开了多线程,但其实还是单线程处理逻辑,不用担心线程安全问题
  4. 多线程主要解决I/O读写瓶颈问题,针对业务量达到亿级的应用,常规项目没有必要使用

五大数据类型

Redis-key命令

Redis-key命令用于管理 redis 的键。

以下各种数据类型的命令不必强记,只需要了解经常用的。需要使用命令时多查官方文档。

中文官网命令查询地址:http://www.redis.cn/commands.html

命令

描述

RENAME key newkey

修改 key 的名称为newkey

TYPE key

返回 key 所储存的值的类型。

DEL key

用于在 key 存在时删除 key。

EXISTS key

检查给定 key 是否存在。

MOVE key db

将当前数据库的 key 移动到给定的数据库 db 当中。

EXPIRE key seconds

为给定 key 设置过期时间,以秒计。

TTL key

以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)。

PERSIST key

移除 key 的过期时间,key 将持久保持。


String(字符串)

Redis 字符串数据类型的相关命令用于管理 redis 字符串值

命令

描述

SET key value

设置指定 key 的值

SETEX key seconds value

设置指定 key 的,并指定过期时间seconds秒。

SETNX key value

只有在 key 不存在时设置 key 的值。

GET key

获取指定 key 的值。

GETRANGE key start end

返回 key 中字符串值的子字符,截取范围为[start,end]。

SETRANGE key offset value

用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始。

APPEND key value

如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾。

如果key不存在,相当于set命令。

INCR key

将 key 中储存的数字值增一。比如应用于浏览量。

DECR key

将 key 中储存的数字值减一

INCRBY key num

将 key 所储存的值加上给定的增量(num) 。

DECRBY key num

key 所储存的值减去给定的减量(num) 。

mset key1 value1 k2 v2 …[kn vn]

同时指定多个key的value值

msetnx k1 v1 … kn vn

与mset不同在于,该命令是一个原子性操作,同时成功或失败

mget k1 k2 k3 … kn

同时获取多个key的值

案例:使用命令来存取一个对象

127.0.0.1:6379> set student:001 {name:rem,age:18}		#使用json格式来保存student:001对象的值
OK
127.0.0.1:6379> get student:001
"{name:rem,age:18}"
127.0.0.1:6379> msetnx student:002:name sixu student:002:age 22		#另一种设计模式 			
(integer) 1
127.0.0.1:6379> mget student:002:name student:002:age 
1) "sixu"
2) "22"

List(列表)

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

一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。

l开头的命令,表示从列表左边开始操作;以r开头的命令,表示从列表右端开始操作

命令

描述

lpush key value1 … valuen

向一个名为key的列表添加元素,可添加多个。从左边开始添加

rpush key value1 … valuen

lrange key start stop

获取列表指定范围内的元素,范围是[start,stop]。后添加的先获取,即获取第0个元素是最后添加的元素。

lpop key

移除列表key的一个元素并返回。以l开头的命令,从列表的最左边开始移除。

rpop key

lindex key index

获取下标为index的值

lrem key count value

精准的移除列表key中的value元素,count表示移除的个数,因为可能有多个重复的元素。

ltrim key start stop

对一个列表进行修剪(trim),列表只保留[start,stop]区间的元素,其他的全部删除。

lset key index newvalue

通过索引index更新列表key元素的值为newvalue。注意:表必须存在,并且索引也必须存在。

linsert key before/after pivot value

在元素pivot,前/后插入元素value。元素pivot不存在则不执行。


Sets(集合)

Redis 的 Set 是 String 类型的无序集合集合成员是唯一的,这就意味着集合中不能出现重复的数据。

Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

命令

描述

sadd ket v1 … vn

向集合添加一个或多个成员

sismember key member

判断 member 元素是否是集合 key 的成员

scard key

获取集合key的成员数

srem key v1 [ … vn]

移除集合中一个或多个成员

srandmember key [count]

从集合中随机获取1个或count个元素

smembers key

返回集合中的所有成员

spop key

移除并返回集合中的一个随机元素

smove key1 key2 value

将value从key1集合移动到key2集合

sdiff k1 [k2]

返回集合k1与其他集合的差集

sinter k1 [k2]

返回集合k1与其他集合的交集

sunion k1 [k2]

返回集合k1与其他集合的合集


Hash(哈希)

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

命令

描述

hset key field value

将哈希表 key 中的字段 field 的值设为 value 。

hget key field

获取存储在哈希表中指定字段的值。

hgetall key

获取在哈希表中指定 key 的所有字段和值

hmset key field1 v1 field2 v2 …

同时将多个 field-value 对设置到哈希表 key 中。

hmget key field1 field2 …

获取所有给定字段的值

hdel key field1 field2 …

删除一个或多个哈希表字段

hlen key

获取哈希表中字段的数量

hexists key filed

查看哈希表 key 中,指定的字段是否存在。

hkeys key

获取所有哈希表中的字段

hvals key

获取哈希表中所有值。

hincrby key filed increment

为哈希表 key 中的指定字段的整数值加上增量 increment 。

哈希数据类型更适合用于存放对象信息,例如用户信息等。string更适合字符串的存储。

实例:

127.0.0.1:6379> hmset student:001 name sixu age 18  
OK
127.0.0.1:6379> hkeys student:001
1) "name"
2) "age"
127.0.0.1:6379> hmget student:001 name age 
1) "sixu"
2) "18"

sorted set(有序集合)

有set数据类型相似,不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。

命令

描述

zadd ket source1 v1 … sourcen vn

添加多个元素

zrem key v1 [ … vn]

移除有序集合中的一个或多个成员

zremrangebysource key min max

移除有序集合中给定的分数区间的所有成员

zrange key start stop [withscore]

通过索引区间返回有序集合指定区间内的成员

zrangebyscore key min max [withscore] [limit]

通过分数返回有序集合指定区间内的成员,从小到大

zrevrangebyscore key max min [withscore]

通过分数返回有序集合指定区间内的成员,从大到小

zrank key value

返回有序集合中指定成员的索引,从小到大排序

zrevrank key value

返回有序集合中指定成员的索引,从大到小排序

zcard key

获取有序集合的成员数

zcount key min max

计算在有序集合中指定区间[min,max]分数的成员数

zincrby key num value

有序集合中对指定成员value的分数加上增量 num


三大特殊数据类型

GEO

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

命令

描述

geoadd key longitude latitude member [longitude latitude member …]

添加地理位置的坐标。经度(longitude)、纬度(latitude)、位置名称(member)

geopos key member [member …]

根据地理名称获取经度(longitude)、纬度(latitude)。若名称不存在则为nil。

geodist key member1 member2 [m|km|mi|ft]

返回两个给定位置之间的距离。指定单位米|千米|英里|英尺。

georadius key longitude latitude range [m|km|mi|ft] [WITHDIST] [WITHCOORD] [withcount]

根据用户给定的经纬度坐标来获取指定范围内的地理位置集合

longitude latitude range表示指定的经度、纬度、半径

m|km|mi|ft 表示距离单位

WITHDIST 表示返回结果包括与指定坐标的距离

WITHCOORD 返回结果包括经纬度

withcount 限定返回结果最大条数

georadiusbymember keys member range [m|km|mi|ft]

根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合,与georadius的区别在于:

georadius的中心位置是根据经纬度自定义的

而georadiusbymember的中心位置要在位置集合中选取一个。

geohash key member [member …]

返回一个或多个位置对象的 geohash 值

实例:

  • geoadd和geopos :导入地理信息,一般会下载城市数据,直接通过java程序导入。
127.0.0.1:6379> geoadd china 120.864608 32.016212 nanTongShi		#将南通市的地理位置添加进china地图
(integer) 1
127.0.0.1:6379> geoadd china 118.767413 32.041544 nanJingShi 120.29975 31.58002 WuXiShi		#将南京市和无锡市的地理位置添加进china地图
(integer) 2
127.0.0.1:6379> geopos china nanTongShi nanJingShi beiJingShi		#查询南通市、南京市和北京市的经纬度
1) 1) "120.8646056056022644"
   2) "32.01621124479810732"
2) 1) "118.76741319894790649"
   2) "32.04154324806454923"
3) (nil)		#空数据
  • geodist
127.0.0.1:6379> geodist china nanTongShi nanJingShi km
"197.7728"
  • georadius
127.0.0.1:6379> georadius china 119 32.025 200 km withdist withcoord
1) 1) "nanJingShi"		#显示名称
   2) "22.0079"			#显示距离,km
   3) 1) "118.76741319894790649"		#经度
      2) "32.04154324806454923"			#纬度
2) 1) "nanTongShi"
   2) "175.8404"
   3) 1) "120.8646056056022644"
      2) "32.01621124479810732"
3) 1) "WuXiShi"
   2) "132.4554"
   3) 1) "120.29974848031997681"
      2) "31.5800212193721066"
  • georadiusbymember
127.0.0.1:6379> georadiusbymember china WuXiShi 100 km		#以china中的一个城市为中心点
1) "WuXiShi"
2) "nanTongShi"

GEO的底层原理起始就是有序集合,可以使用有序集合的命令对GEO数据操作。


HyperLogLog

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

命令

描述

pfadd key element [element …]

添加指定元素到HyperLogLog中

pfcount key [key …]

返回给定 HyperLogLog 的基数估算值。

pfmerge key0 [keys …]

将多个 HyperLogLog 合并为一个 HyperLogLog

实例:

127.0.0.1:6379> pfadd key01 a d g a a a s d f x c  #添加元素到key01
(integer) 1
127.0.0.1:6379> pfcount key01						#key01基数统计
(integer) 7
127.0.0.1:6379> pfadd key02 a a a a d  f x z r t c	#添加元素到key02
(integer) 1
127.0.0.1:6379> pfcount key02						#key02基数统计
(integer) 8
127.0.0.1:6379> pfcount key01 key02					#对key01和key02进行基数统计
(integer) 9
127.0.0.1:6379> pfmerge key01 key02					#将key02合并到key01
OK	
127.0.0.1:6379> pfcount key01						#key01基数统计
(integer) 9

应用场景:

UV统计,一个网站同一个用户多次访问,视为一次。

0.81的错误率,对于uv统计任务来说可以忽略不记。


Bitmap(位图)

如果有一件事物,只有两个状态,可以用0和1表示,那么就可以使用bitmap。

情景假设:假如需要记录一个人再一个月内每天是否上课。如果使用数据库,则需要学号,姓名,时间,以及是否上课状态字段,这太消耗资源。

但是如果使用位图,上课使用1表示,没上课使用0表示。如下图:

127.0.0.1:6379> setbit 070717000-sixu-2017/11 0 1		#070717000-sixu-2017/11表示 学号-姓名-时间 ,11月1号上课
(integer) 0
127.0.0.1:6379> setbit 070717000-sixu-2017/11 1 1		#11月2号上课
(integer) 0
127.0.0.1:6379> setbit 070717000-sixu-2017/11 2 0		#11月3号上课
(integer) 0
127.0.0.1:6379> setbit 070717000-sixu-2017/11 3 0		
(integer) 0
127.0.0.1:6379> setbit 070717000-sixu-2017/11 4 1
(integer) 0
127.0.0.1:6379> getbit 070717000-sixu-2017/11 2			#查询sixu在11月3号是否上课
(integer) 0
127.0.0.1:6379> getbit 070717000-sixu-2017/11 4			#查询sixu在11月4号是否上课
(integer) 1
127.0.0.1:6379> bitcount 070717000-sixu-2017/11			#查询sixu在11月上课有几天
(integer) 3

redis的事务

事务

Redis 事务的本质是一组命令的集合。

  • 一次性:事务支持一次执行多个命令,一个事务中所有命令都会被序列化。
  • 顺序性:在事务执行过程,会按照顺序串行化执行队列中的命令
  • 排他性:其他客户端提交的命令请求不会插入到事务执行命令序列中。

Redis事务没有隔离级别的概念:

批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。

Redis不保证原子性:

Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。

命令

描述

multi

开启事务

exec

执行事务块内所有的命令

discard

取消事务

watch key [ key …]

监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

unwatch

取消 WATCH 命令对所有 key 的监视。

redis事务的步骤

  1. 开启事务(multi)
  2. 命令入队
  3. 执行事务(exec)

实例:

127.0.0.1:6379> multi		#开启事务
OK
127.0.0.1:6379> set k1 v1	
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> sadd k2 k2v1 k2v2 k2v3
QUEUED
127.0.0.1:6379> get k2		#故意出现语法性错误,类似于java中的1/0.
QUEUED
127.0.0.1:6379> exec	#提交事务
1) OK
2) "v1"
3) (integer) 3
4) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> smembers k2		#正确的获取到k2集合的全部元素,由此可知事务并没有原子性
1) "k2v3"
2) "k2v1"
3) "k2v2"

如果事务中存在命令型错误(就是redis中根本就没有这种命令,redis一眼就看出来这个命令是你瞎编的),类似于java中的编译型错误,则exec后所有的命令也都不会执行。

127.0.0.1:6379> multi		#开启事务
OK
127.0.0.1:6379> set key3 sss		
QUEUED
127.0.0.1:6379> geet key3		#出现命令型错误,根本没有这个命令
(error) ERR unknown command `geet`, with args beginning with: `key3`, 
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.		#所有命令都不执行
127.0.0.1:6379> get key3
(nil)

监控

悲观锁:悲观,认为别人只要碰到数据就会修改。所以只要自己需要使用数据时,先提前上锁,怕自己使用途中有人修改数据,自己用完别人才能用。

乐观锁:乐观,任务别人全都不会乱修改数据。所以不会上锁,只要在使用前记住记住数据的值,自己用好数据时,再和数据比较一下看看数据有没有发生变动即可。

redis的watch就类似于乐观锁。

别人不修改值都没问题,但是当其他人修改了值,本机事务就被打断。

实例:例如,我开启两个客户端窗口模拟

  1. 客户端A先watch了一个数据,然后开启事务,命令入列,但还没有提交。
  2. 客户端B把数据给改了。
  3. 客户端A执行事务。发现事务执行失败,因为客户端B改动了数据。

redis 的decrement方法 redis的increment_redis_09


Jedis

什么是Jedis?

jedis是官方推荐的java连接开发工具,使用java操作redis的中间件。应该对其熟练掌握。

jedis远程连接redis准备

**遇到的坑:**远程连接centos的redis。如果直接使用jedis连接服务器deredis,可能连接不上。

  1. 开启防火墙的6379端口。
    firewall-cmd --query-port=6379/tcp:查看6379端口是否开启
    firewall-cmd --add-port=6379/tcp:将6379端口开启,返回success。
    永久的开启6379端口
    firewall-cmd --zone=public --add-port=6379/tcp --permanent (–permanent永久生效,没有此参数重启后失效)
    firewall-cmd --reload 重新载入
    firewall-cmd --zone= public --query-port=6379/tcp 查看
  2. 修改redis的配置文件
  • 在配置文件redis.conf中,默认的bind 接口是127.0.0.1。这样的话,访问redis服务只能通过本机的客户端连接,而无法通过远程连接,
    如果bind选项为空的话,那会接受所有来自于可用网络接口的连接。所以这里直接将bind注释掉。
  • 关闭保护模式(不建议
    直接关闭保护模式比较省事,但出于安全考虑,给redis设置密码即可。
    在配置文件中找到requirepass(默认被注释),为其取消注释,设置一个密码。
使用jedis的api

使用jedis连接步骤:

  1. 导入jedis的依赖或jar包
<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
  1. 编写测试类,使用jedisAPI。与前面学的redis命令大致无二。
public class JedisTest {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("外网ip",6379);		//设置ip和端口号
        jedis.auth(密码);		//输入密码
        System.out.println("redis的ping命令:"+jedis.ping());
        
        //字符串结构
        System.out.println("redis的set命令:"+jedis.set("key01","value01"));
        System.out.println("redis的get命令:"+jedis.get("key01"));

        //哈希结构
        System.out.println("redis的hset命令:"+jedis.hset("key","field1","value1"));
        System.out.println("redis的hset命令:"+jedis.hset("key","field2","value2"));
        System.out.println("redis的hset命令:"+jedis.hset("key","field3","value3"));
        System.out.println("redis的hgetAll命令:"+jedis.hgetAll("key"));

        System.out.println("redis的quit命令:"+jedis.flushDB());
        System.out.println("redis的quit命令:"+jedis.quit());

    }
}
  1. 结果:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DHzneCEO-1605356685686)(/image-20201113150933971.png)]
  2. 根据测试结果,会发现这些api与之前学的redis命令一模一样。

jedis的事务

实例:

//测试redis的事务
public class test02 {
    public static void main(String[] args) {

        Jedis jedis = new Jedis(IP地址, 6379);
        jedis.auth(密码);

        jedis.set("money","10");

        Transaction multi = jedis.multi();      //开启事务
        try {
            multi.incr("money");
            int i=1/0;
            multi.exec();
        }catch (Exception e){
            multi.discard();       //出现异常,放弃事务
        }finally {
            System.out.println("money="+jedis.get("money"));		//结果:money=10
            jedis.flushDB();
            jedis.quit();
        }
    }
}

结果:可知由于出现异常,事务被取消了。


jedis开启监控

实例:

public class test01 {
    public static void main(String[] args) {
        Jedis jedisA = new Jedis(外网IP, 6379);       //模拟客户端A
        jedisA.auth(密码);


        Jedis jedisB = new Jedis(外网IP, 6379);      //模拟客户端B
        jedisB.auth(密码);

        System.out.println(jedisA.ping("A客户端---正常开启"));
        System.out.println(jedisB.ping("B客户端---正常开启"));

        jedisA.set("money", "100");
        System.out.println("money=" + jedisB.get("money"));         //money=100

        jedisA.watch("money");
        Transaction multi = jedisA.multi();         //A开启事务
        multi.incrBy("money", 10);      //A的命令入列
        multi.get("money");         //A的命令入列

        jedisB.incrBy("money", 10);      //B修改了值

        multi.exec();       //A执行事务。


        System.out.println("money=" + jedisA.get("money"));
        //如果事务执行成功,那么money=120
        //如果事务执行失败,那么money=110

        jedisA.flushDB();
        jedisA.quit();
        jedisB.quit();

    }
}

结果:由此可知,将监控数值被其他用户改变时,会导致事务失败。

redis 的decrement方法 redis的increment_数据库_10


SpringBoot整合Redis

简单使用RedisTemplate

说明:springboot 2.x之后的版本,底层不再使用jedis,而是使用lettuce。

jedis:采用直连,多线程不安全。或者是使用jedis的连接池

lettuce:采用netty,实例可以在多个线程之间共享。

redis 的decrement方法 redis的increment_数据库_11

步骤:

  1. 导入rediso的启动器:spring-boot-starter-data-redis
<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
  1. 配置文件。springboot导入的依赖,都有一个自动配置类,找到自动配置类,查看需要配置哪些信息。

    由此得知,我们最起码需要配置哪些信息。
spring.redis.host=Ip地址
spring.redis.port=6379
spring.redis.password=密码
#配置连接类型,2.x默认使用lettuce连接
#spring.redis.client-type=jedis
  1. 使用RedisTemplate操作redis数据库。
    首先了解一下:
    Springboot整合Redis有两种方式,分别是Jedis和RedisTemplate,这两者有何区别?
  • Jedis是Redis官方推荐的面向Java的操作Redis的客户端,
  • 而RedisTemplate是SpringDataRedis中对JedisApi的高度封装。其实在Springboot的官网上我们也能看到,官方现在推荐的是SpringDataRedis形式,相对于Jedis来说可以方便地更换Redis的Java客户端,其比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache。

图解:还是要起源于redis的自动配置类。

redis 的decrement方法 redis的increment_redis 的decrement方法_12

通过源码可以看出,SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。这个RedisTemplate的泛型是<Object,Object>。而上面我们提到,可以使用RedisTemplate来操作redis。

那接下来看看RedisTemplate中有哪些东西。

redis 的decrement方法 redis的increment_redis 的decrement方法_13

有上图可知(上图并不是全部,只是简单列举),RedisTemplate中有操作不同redis数据类型的方法,再以字符串为例,深入opsForValue方法,点进**DefaultValueOperations(字符串)**这个类中去看看。

redis 的decrement方法 redis的increment_redis_14

由此可知,RedisTemplate可以使用不同的方法来操作不同的数据类型,每种数据类型的具体的操作方法可能名称有所改变(例如字符串数据类型,通过重载increment方法让其兼具了incr和incrBy方法的功能),但是具体功能都一样。

OK,编写测试类,使用RedisTemplate操作redis数据库。

@SpringBootTest
class RedisSpringbootApplicationTests {

	@Autowired
	private RedisTemplate<Object,Object> redisTemplate;

	@Test
	void contextLoads() {
		ValueOperations<Object, Object> valueOperations = redisTemplate.opsForValue();
		valueOperations.set("name","sixu");
		valueOperations.set("age",18);
		System.out.println("name:"+valueOperations.get("name"));
		System.out.println("age:"+valueOperations.get("age"));
	}
}

结果:

redis 的decrement方法 redis的increment_数据库_15


自定义RedisTemplate

首先编写一个pojo用于测试

//记得实现序列化
public class User implements Serializable {
    private String name;
    private int age;
	//构造方法,get,set,toString
}

RedisTemplate默认的序列化方式

RedisTemplate默认使用的序列化是Jdk的。

redis 的decrement方法 redis的increment_nosql_16

而使用jdk序列化化会出现一个问题。

@Test
	void test01() throws JsonProcessingException {
		User user001 = new User("思绪", 18);
		redisTemplate.opsForValue().set("user",user001);
		System.out.println(redisTemplate.opsForValue().get("user"));
	}

从控制台查看:会出前缀

redis 的decrement方法 redis的increment_redis 的decrement方法_17

自定义RedisTemplate指定序列化方式

则可以自己编写一个redisConfig类,用于自定义redis配置。自定义RedisTemplate的序列化方式。

首先可以看RedisTemplate有关于Serializer的方法

redis 的decrement方法 redis的increment_redis 的decrement方法_18

点进setKeySerializer()进行查看,则可以看到有不同的序列化方法,例如默认的jdk,jackson等。

redis 的decrement方法 redis的increment_数据库_19

则我们自定义一个RedisTemplate,key使用String的序列化方式,value使用string的序列化方式。

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        //json序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);        //value,所有类型都可以序列化
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        //string序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //设置RedisTremplate所使用的序列化方式。
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);


        //载入设置
        template.afterPropertiesSet();
        return template;
    }
}

再测试一下序列化方法。

redis 的decrement方法 redis的increment_redis_20

在公司真是开发中,一般不会使用原生的RedisTremplate的Api,而是会自己封装一个redis工具类。


Redis的持久化

为何要持久化?
redis是内存数据库,所有的数据都保存在内存中。但是一旦服务器进程退出,或者断电等情况,那内存中的数据就会丢失。因此redis需要持久化功能,将一些重要的数据保存在硬盘中。

RDB(Redis DataBase)

默认的RDB模式,配置文件一般不需要修改。

保存的文件名叫dump.rdb

redis 的decrement方法 redis的increment_redis_21

在指定间隔时间内,将内存中的数据写入磁盘中(快照);恢复时直接将磁盘的文件读到内存中去。

redis会单独创建一个子进程进行持久化,父进程不进行IO操作的。子进程先将内存的所有内容都持久化到一个临时文件中,当快照操作完成后,在替换原来的文件。

rdb的触发机制

  1. 满足save的规则
  2. 执行flushall
  3. 退出redis

当出发机制时,就会生成一个dump.rdb的备份文件

redis 的decrement方法 redis的increment_nosql_22

恢复rdb文件
只需要将rdb文件放在redis的启动目录即可,redis启动时会自动扫描dump.rdb并恢复其中的数据。

rdb文件如果出现问题,可以使用redis-check-rdb进行修复

优点就是rdb比aof要更高效,在对数据完整度要求不高的情况下,适合对大规模数据的备份与恢复。

缺点就是在最后一次写入文件时,如果服务器凑巧出现问题,可能导致最后一次要写入的内容丢失。子进程会占用一定的内存空间。


AOF(Append Only File)

保存的文件名叫appendnoly.aof

redis 的decrement方法 redis的increment_nosql_23

以日志的形式记录每一次写的操作,将所有的写操作记录下来,只能追加文件,不能修改文件。恢复的时候就将文件内的所有指令执行一遍,达到恢复数据的目的。

aof默认的就是文件的追加,会导致文件越来越大。

aof文件如果出现问题,可以使用redis-check-aof进行修复。

redis-check-aof --fix appendnoly.aof		#修复aof文件

Redis的订阅发布

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

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

订阅/发布消息图:

  1. 发布者
  2. 频道
  3. 订阅者

redis 的decrement方法 redis的increment_数据库_24

redis发布订阅命令

命令

描述

PUBLISH channel message

将信息发送到指定的频道。

SUBSCRIBE channel [channel …]

订阅给定的一个或多个频道的信息。

UNSUBSCRIBE [channel [channel …]]

指退订给定的频道。

PUNSUBSCRIBE [pattern [pattern …]]

退订所有给定模式的频道。

PUBSUB subcommand [argument [argument …]]

查看订阅与发布系统状态。

PSUBSCRIBE pattern [pattern …]

订阅一个或多个符合给定模式的频道。

实例:

  1. 订阅者1订阅了sixu频道
127.0.0.1:6379> SUBSCRIBE sixu	#订阅消息
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "sixu"
3) (integer) 1
  1. 发布者在sixu频道中发布消息
127.0.0.1:6379> PUBLISH sixu hello,sixu		#发布消息
(integer) 1

则此时订阅者页面发成了变化,即使的收到了发布者所发布的消息。

127.0.0.1:6379> SUBSCRIBE sixu
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "sixu"
3) (integer) 1
1) "message"		#订阅者所售到的消息
2) "sixu"
3) "hello,sixu"

使用场景:

  1. 实时聊天系统
  2. 实时订阅
  3. 订阅关注系统等

复杂的场景就将使用到消息中间件MQ


Redis集群

Redis的主从复制

概念:主从复制是指将一个主节点(master/leader)的数据复制到其余从节点(slave/flowwer)中。复制只能是单向的,从主到从

默认情况下,每一个redis的服务器都是一个主节点;主节点可以有任意和从节点(0~n);从节点只能由一个父节点。

作用:

  1. 数据冗余:主从复制实现了数据的热备份,是除了持久化外的一种数据冗余方式。
  2. 故障恢复:当主节点故障时,可以暂时由从节点提供服务。实现快速的故障恢复。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以大大提高redis服务器的并发量。例如主节点负责写服务,而读服务则由从节点负责。这种方式在读多写少的情景下效果尤为明显,从节点可以大大减缓主节点的压力。
  4. 高可用基石:主从复制是哨兵以及集群实施的基础,所以是redis高可用的基础。

一般来说,要将redis运用于工程中,只有一台redis是不行的,原因:

  1. 从结构上说:单个redis服务器会发生单点故障,并且一台服务器需要处理全部请求,负载过大。
  2. 从容量上来说,单个redis的内存容量有限,哪怕一台redis服务器的总内存容量有256g,也不能将全部的内存给redis使用。一般来说,单台redis的内存不超过20g。

因此,在公司中,主从复用基本上是必用的。

redis 的decrement方法 redis的increment_分布式_25

主从库的配置

主库不需要配置,一个redis服务默认就是主节点。主需要改动从节点的配置即可。

127.0.0.1:6379> info replication		#查看redis服务信息
# Replication		
role:master		#角色:主节点
connected_slaves:0		#从节点:0
master_replid:35114f95bcf41ed65121c0015c2b88b88f81bc40
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集群。

  1. 复制三份redis配置文件,分别为redis-master-6379.conf(主节点)、redis-slave-6380.conf(从节点)、redis-slave-6381.conf(从节点)。
  2. 修改配置文件的端口,pidfile,logfiel,rdb文件名。(由于演示的是单机多服务,为了防止冲突)
    **注意:**如果主库有密码的话,需要再从库的配置文件再设置masterauth ,让其可以连接上主库。
  • 主库的配置文件为redis-master-6379.conf。端口为6379(不变);pidflie为redis_master_6379.pid;logfile为master_6379.log;rdb文件名为dump_master_6379.rdb。
  • 从库以redis-slave-6380.conf为例。端口为6380;pidflie为redis_slave_6380.pid;logfile为slave_6380.log;rdb文件名为dump_slave_6380.rdb。
    **注意:**如果主库有密码的话,需要再从库的配置文件再设置masterauth ,让其可以连接上主库。
  1. 将主从redis服务器全部启动。
    以从库6380为例,此时它还默认为主库
  2. 所有从库执行命令:slaveof 127.0.0.1 6379 ;指定6379端口为自己的主节点
    以从库6380为例
127.0.0.1:6380> slaveof 127.0.0.1 6379		#指定主库
OK
127.0.0.1:6380> info replication
# Replication
role:slave		#角色:从节点
master_host:127.0.0.1		#主节点ip
master_port:6379		#主节点端口
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1
master_link_down_since_seconds:1605337294
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:70be422893a68747cf6a5954c7e800a61d0746ea
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

此时再看主库信息:

127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2		#从库数量
slave0:ip=127.0.0.1,port=6380,state=online,offset=392,lag=0		#从库信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=392,lag=0		#从库信息
master_replid:61640415a80956e63504327743f3886f5e3636a5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:392
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:392

但使用slaveof 命令只是暂时的主从关系,下次重启这种关系就不复存在了。可以直接配置从库的配置文件,配置replicaof,这样就是永久的了。

所以以后可以直接配置从机的配置文件,来永久的定义主从关系。

主从复制的简单过程

slave连接到master后,发送一个sync同步命令;

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

全量复制:master将全部的数据给slave后,slave将其存盘并加载到内存中。当slave第一次连接或者从连master,就会进行全量复制。

增量复制:只slave和master在连接状态中,如果有人在master那里修改了数据,那会立马告诉salve,salve完成同步。


哨兵模式

上面,简单的演绎了一下主从复制的流程。

发现问题:

但是有一个问题,由于从节点只能读,不能写。当主节点挂了之后,该怎么办?

主节点挂了,意味着写服务是没办法了,只能从节点的读服务还能正常运行。

假如主节点一时半会活不过来,那就需要让一个从节点变主节点,必须得有节点扛起写服务这个责任。

手动:

  1. 选一个从节点,执行 slaveof no one 命令,恢复主节点的身份。
  2. 让其他的从节点重新认一个主节点。

但是,手动太麻烦。因此,自动从子节点中选出一个担当主节点,哨兵模式的作用就体现出来了

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

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

redis 的decrement方法 redis的increment_redis 的decrement方法_26

哨兵有两个作用:

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

上面的那个图只有一个哨兵,但是哨兵也可能出现问题,因此可多哨兵监听。当某个服务出现问题时,哨兵投票是否选举新的主节点。

redis 的decrement方法 redis的increment_nosql_27

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

实例:配置一主二从,一个哨兵。

一主二从在上面主从库配置的基础上进行测试,主库配置需要添加masterauth ,这是因为当主库挂掉之后,从从库选取一个当主库,但是假如原来的主库死掉后又活过来了,他变成一个从库需要去连接新继任的主库,所以需要配置连接主库的密码。

  1. 定义一个基本的sentinel.conf文件
#entinel monitor代表监控,redis_master代表服务器的名称,127.0.0.1代表监控的主服务器,6379代表端口,1代表只有1个或1个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
# sentinel author-pass定义服务的密码,mymaster是服务名称,后面是密码是Redis服务器密码
# sentinel auth-pass <master-name> <password>
sentinel monitor redis_master 127.0.0.1 6379 1
sentinel auth-pass redis_master 6379
  1. 先启动3个redis服务,再启动哨兵。
    此时,刚启动时,6381是主节点
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=0,lag=1
slave1:ip=127.0.0.1,port=6379,state=online,offset=0,lag=1
master_replid:a73ec7cdd73071c97000a98cf49a776cdfc03644
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0

启动哨兵

redis 的decrement方法 redis的increment_nosql_28

  1. 将主库(6381)停下,哨兵就会检测到主库以及挂了,会从两个从库之间选一个当主库。
    如图,此时6380变为主库了
127.0.0.1:6380> info replication
# Replication
role:master		#6380变成主库了
connected_slaves:1
slave0:ip=127.0.0.1,port=6379,state=online,offset=9399,lag=0
master_replid:4facfe86dd97927b14dec82598fd22043ed753b6
master_replid2:a814b69fefb0fa195d6bd53f4631c6b3876a312e
master_repl_offset:9399
second_repl_offset:6170
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:9399
  1. 这时再将6381恢复
    就会发现6381变成一个从库,它的主库是6380
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380		#6381的主库是6380
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:0
master_link_down_since_seconds:1605349289
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:f065f6076a52c396af9f446bd42d63a07f14df1b
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
127.0.0.1:6380> info replication	
# Replication
role:master
connected_slaves:2		#6380下有两个从库
slave0:ip=127.0.0.1,port=6379,state=online,offset=19633,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=19633,lag=1
master_replid:4facfe86dd97927b14dec82598fd22043ed753b6
master_replid2:a814b69fefb0fa195d6bd53f4631c6b3876a312e
master_repl_offset:19633
second_repl_offset:6170
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:19633

简单了解Redis的缓存穿透、击穿与雪崩

缓存穿透

缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果命中,直接返回结果;如果key不存在或者key已经过期,再对数据库进行查询。并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。

图解:

redis 的decrement方法 redis的increment_redis_29

但是假如一个客户端,不停的查询一个不存在的key,就会导致不停的访问数据库,给服务器造成巨大的压力,这就是缓存穿透。

缓存穿透解决方案:

  1. 布隆过滤器
    是一种数据结构,对所有可能查询的参数以hash形式存储,先在控制层进行校验,不符合规定的请求直接求其,避免对服务器造成查询压力。
  2. 将查询为null的key也假如缓存,过期时间可以设置短一些。
    这样即使频繁的查询不存在的key,也可以减少查询压力。
    缺点:要花费放多的空间去存储没有意义的空值;即使过期时间设置的短,也会导致缓存层和数据层的数据有一段时间并不同步。

缓存击穿

场景:假如某个热搜,很多人都正在搜索。但是再某一瞬间,缓存中该热搜的key过期了。仅仅在那一瞬间,可能有超级巨大的访问直接去访问数据库。

缓存击穿解决方案:

  1. 设置热点数据永不过期
  2. 加互斥锁
    分布式锁:使用分布式锁,让同一时间只能有一个线程能去数据库访问key。当这个线程访问数据库后,会重新将该key加入缓存。
    这种方式将高并发的压力转移到了分布式锁上,对锁的压力很大。

缓存雪崩

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

产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。

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

缓存雪崩解决方案:

  1. redis高可用
    多搭建几台服务器
  2. 服务降级
    缓存失效后,使用流控,来限制同一时间的访问量或者线程数量。
  3. 数据预热