目录
1.NoSQL简介
2.NoSQL与关系型数据库的区别及其特点
3.NoSQL的四大分类
4.Redis简介
5.Redis入门知识
5.1 Windows下安装Redis
5.2 Linux下安装和启动Redis
5.3 Redis性能测试
5.4 Redis基础知识
6.Redis中的数据类型
6.1 Redis的五大数据类型:String,List,Set,Hash,Zset
6.2 Redis的三种特殊数据类型:geospatial,hyperloglog,bitmaps
7.Redis的基本事务操作
8.Jedis的使用
9.SpringBoot集成Redis
10.Redis.conf配置文件详解
11.Redis持久化
11.1 RDB(Redis DataBase)
11.2 AOF(Append Only File)
12.Redis发布订阅
13.Redis主从复制
14.Redis缓存穿透、击穿和雪崩处理
1.NoSQL简介
NoSQL(Not Only SQL),泛指非关系型的数据库。在我们现在所处的大数据时代,一般的关系型数据库已经无法再继续胜任分析处理的任务了。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的社交网站类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
2.NoSQL与关系型数据库的区别及其特点
1.存储方式的区别:
关系型数据库是表格的固定形式的,存储在表的行和列中,行列之间关联协作存储,提取数据很方便。而NoSQL数据库则相反,它是由各种数据块组合在一起,便于横向扩展,例如社交网络的图结构和地理位置等这些数据不需要一个固定的格式。
2.存储结构的区别:
关系型数据库是结构化组织的,数据表都预先定义了结构(列的定义),结构描述了数据的形式和内容。这一点对数据建模至关重要,虽然预定义结构带来了可靠性和稳定性,但是修改这些数据会比较困难。而Nosql数据库基于动态结构,不需要事先设计好,可以随取随用。因为Nosql数据库是动态结构,可以很容易适应各种数据类型和结构的变化。就像JAVA中的Map<String, Object>
结构,可以存储任何类型的数据,这就是典型的一种NoSQL的表现。
3.存储规范的区别:
关系型数据库的数据存储为了更高的规范性,把数据分割为最小的关系表以避免重复,获得精简的空间利用。虽然管理起来很清晰,但是单个操作设计到多张表的时候,数据管理就显得有点麻烦。而Nosql数据存储在平面数据集中,数据经常可能会重复。单个数据库很少被分隔开,而是存储成了一个整体,这样整块数据更加便于读写。
4.存储扩展的区别:
这可能是两者之间最大的区别,关系型数据库是纵向扩展,也就是说想要提高处理能力,要使用速度更快的计算机。因为数据存储在关系表中,操作的性能瓶颈可能涉及到多个表,需要通过提升计算机性能来克服。虽然有很大的扩展空间,但是最终会达到纵向扩展的上限。而Nosql数据库是横向扩展的,它的存储天然就是分布式的,可以通过给资源池添加更多的普通数据库服务器来分担负载。横向扩展具体是指扩展服务器的数量进行高并发的处理(增强处理业务的能力)。
5.查询方式的区别:
关系型数据库通过结构化查询语言来操作数据库(就是我们通常说的SQL)。SQL支持数据库CURD操作的功能非常强大,是业界的标准用法。而Nosql查询以块为单元操作数据,使用的是非结构化查询语言(UnQl),它是没有标准的。关系型数据库表中主键的概念对应Nosql中存储数据的Key。关系型数据库使用预定义优化方式(比如索引)来加快查询操作,而Nosql采用更简单更精确的数据访问模式。
6.事务方面的区别:
关系型数据库遵循ACID规则,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),而Nosql数据库遵循BASE原则,即基本可用性(Basically Availble)、软/柔性事务(Soft-state)、最终一致性(Eventual Consistency) 。由于关系型数据库的数据强一致性,所以对事务的支持很好。关系型数据库支持对事务原子性细粒度控制,并且易于回滚事务。而Nosql数据库是在CAP(一致性、可用性、分区容忍度)中任选两项,因为基于节点的分布式系统中,很难全部满足,所以对事务的支持不是很好,虽然也可以使用事务,但是并不是Nosql的闪光点。
综上可以总结一些NoSQL的特点:
- 方便扩展,数据之间没有关系,很容易扩展;
- 大数据量下高性能,官方的Redis测试数据,一秒可以写8万次,读取11万次,NoSQL的缓存是记录级的,是一种细粒度的缓存,性能高;
- 支持多种数据结构,而且不需要事先设计数据库表,可以随取随用。通常情况下如果数据量十分大的表,也很难设计;
- 支持持久化操作,可以进行aof及rdb数据持久化到磁盘,从而进行数据备份或数据恢复等操作,较好的防止了数据丢失;
- 单线程请求,所有命令串行执行,并发情况下不需要考虑数据一致性问题;
- 支持pub/sub消息订阅机制,可以用来进行消息订阅与通知;
- 支持简单的事务需求,但一般业界使用场景很少,并不成熟。
另一方面,关系型数据库和非关系型数据库各有优势,所以在实际应用中,通常会两者结合着一起使用。
3.NoSQL的四大分类
1.key-value键值对数据库
2.文档型数据库(bson格式,类似json只是二进制的)
3.列存储数据库
4.图形关系数据库(不存图片,存图关系,如朋友圈社交网络、广告推荐等)
4.Redis简介
Redis(Remote Dictionary Server ),即远程字典服务。是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis免费和开源,是当下最热门的NoSQL技术之一,也被人们称为结构化数据库。
Redis的一些功能和特性:
常用功能:
- 内存中的数据断电即失,Redis支持数据持久化;
- 速度快,效率高,可以用作高速缓存;
- 发布订阅系统
- 地图信息分析
- 计时器、计数器,如浏览量等;
特性:
- 支持存储多种数据类型;
- 持久化
- 集群
- 事务
5.Redis入门知识
Redis的官网:https://redis.io/;中文网:https://www.redis.net.cn/;通过官网下载即可,并推荐在Linux服务器上搭建。
5.1 Windows下安装Redis
Windows下安装需要去Github下载:https://github.com/MSOpenTech/redis/releases ,选择版本号后直接下载、解压即可。其目录结构如下:
双击Redis的服务器程序即可启动Redis,然后双击启动Redis的客户端通过命令ping
,如果响应PONG
则连接成功!并添加一条korbin:cool
的键值对记录:
5.2 Linux下安装Redis
安装步骤:
- 前往Redis官网直接下载安装包,下载完成后上传到Linux系统,然后解压
tar -zxvf redis-6.0.6.tar.gz
,生成redis的一个解压目录; - 进入到解压的Redis根目录,首先需要安装gcc和tcl的编译环境:
yum install gcc -y
、yum install tcl -y
; - 执行
make
命令,进行编译;
这里遇到一个问题:在安装6.0.1版本make时会遇到这样一个错误,server.c:xxxx:xx: error: ‘xxxxxxxx’ has no member named ‘xxxxx。
原因:gcc编译工具版本的问题,centos7默认安装的版本是4.8.5,但是要求对应版本要在5.3以上,查看gcc版本命令。gcc -v
解决方法:升级到5.3以上版本,依次执行命令:
1.yum -y install centos-release-scl
2.yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
3.scl enable devtoolset-9 bash
4.echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
--使永久生效
- 然后安装Redis到指定目录:
make PREFIX=/usr/local/redis install
; - 安装完成后,将刚才Redis解压目录中的
redis.conf
拷贝一份到Redis的安装目录/usr/local/redis/
下:cp redis.conf /usr/local/redis/
; - 进入安装目录,查看Redis的程序,在
/usr/local/redis/bin
目录下。至此完成安装。
启动Redis服务器和客户端:
执行命令./redis-server
即可启动Redis数据库服务器,同样,可以另起一个窗口启动客户端然后ping
一下进行测试,另外运行客户端默认连接本机的6379端口,如果你连接的是另一台Linux服务器的话命令需要追加ip地址和端口:./redis-cli -h 127.0.0.1 -p 6379
。
后台启动Redis:
上述的启动方式是前台的启动方式,想要后台启动需要修改之前拷贝过来的redis.conf
配置文件中的daemonize
值为yes
。然后带着配置文件启动服务器:./redis-server ../redis.conf
。此时可以查看后台进程,Redis已经启动成功:ps -ef | grep redis
。
关闭Redis:
另外,如果是前台启动则直接可以Ctrl+C
关闭。如果是后台启动,即上面客户端连接成功后,首先输入exit
退出redis客户端,然后输入./redis-cli shutdown
即可关闭Redis服务器。或者可以直接在客户端内输入shutdown
命令关闭服务器和客户端,然后再exit
退出。
5.3 Redis性能测试
可以看到在Redis服务器和客户端启动程序的同一目录下,还有一个redis-benchmark
工具,这是Redis的压力测试工具,是官方自带的性能测试工具。
redis-benchmark 命令参数:
示例:
向本地的6379默认端口测试100个并发连接,每个连接发送10万次请求:./redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 100000
5.4 Redis基础知识
Redis默认有16
个数据库,这个值可以在redis.conf
配置文件中的databases
属性进行修改。并且默认使用的是第0
个数据库。
常用基础命令:
select 数据库序号
:数据库切换
dbsize
:查看数据库大小,即存储的数据数量
flushall
:清空所有数据库
flushdb
:清空当前数据库
Redis-Key相关命令:
set 键 值
:存储键值对数据
get 键
:读取数据
keys *
:查看所有键的列表
exists 键
:是否存在该键
move 键 数据库序号
:将键值对数据移动到指定序号的数据库
del 键
:删除键值对
expire 键 时间秒数
:设置键的过期时间,并可以通过ttl 键
命令查看还剩多少秒过期(ttl:time to live)
更多详细的命令可以在Redis中文官网查看:http://www.redis.cn/commands.html 。
Redis是单线程的,并且所有的数据都存储在内存中,由于CPU进行多线程的上下文切换会消耗资源,所有内存服务器单线程就是效率最高的方式和最佳方案。
6.Redis中的数据类型
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
查看键值对数据类型命令:type 键
!
6.1 Redis的五大数据类型
String字符串类型
Redis中的String类型和Java中的String数据类型非常相似,Redis提供了一些操作字符串的方法:
incr 键
:如果键对应的值为数字,该命令使数字加1;同理decr 键
数字减1;
incrby 键 步长
:数字加上指定的步长,如步长为2则加2,就不是默认的加1;同理decrby 键 步长
减去步长;
所如果字符串是数字,可以应用做计数器、统计多单位的数量等功能。
append 键 值
:命令可以在键原来对应字符串值的基础上追加字符串,如果键不存在则相当于set 键 值
;
strlen 键
:命令获取字符串的长度;
getrange 键 开始位置 结束位置
:开始位置从0开始,截取任意范围的字符串。0~-1则表示截取全部字符串;类似Java中substring;
setrange 键 开始位置 替换的值
:修改从指定位置开始的后面的字符串,替换的字符串的长度就是原字符串中待替换字符串的长度;类似Java中replace;
setex 键 时间秒数 值
:在添加键值对的同时设置过期时间,单位秒;
setnx 键 值
:如果该键不存在,则成功添加键值对数据,如果键存在,则添加失败;setnx
与set
的区别在于set
如果键已经存在则会直接覆盖值;该命令常在分布式锁中使用。
mset 键1 值1 键2 值2 ...
:批量添加键值对;
mget 键1 键2 ...
:同时查询多个值;
msetnx 键1 值1 键2 值2 ...
:该命令是mset
与setnx
的结合,同时具有两者的特点;但其属于原子性的操作,只要其中一个键值对添加失败,则所有的添加失败,即要么一起添加成功,要么一起添加失败;
另外我们可以通过字符串的形式进行对象存储,将Key(键)设计成这种形式:对象名:对象id:对象属性值
,如mset user:10001:name Korbin user:10001:age:22
,本质是使用的mset
命令,但是实际这个键值对可以表示存储了一个id为10001的User对象,这个对象有name和age两个属性,值分别为Korbin和22。
getset 键 值
:该命令表示先取出该键原先对应的值,然后把新值设置给该键。如果键不存在,即键对应值为空相当于直接set,键不为空则是取出原值并设置新值;可以用来做更新操作。
List类型
在Redis中,对于List类型,我们可以同时把它当成多种和线性表结构相关的数据结构来用,包括栈、队列、阻塞队列等;另外,同Java中的List集合,可以存储多个相同的元素。
List的大多数命令都是以L(不区分大小写)
开头开头,一些List的常见操作命令如下:
lpush 键 元素值
:该命令表示向名字为该键的List的左边插入一个元素;该命令对线性表的这种操作方式可以用来模拟栈的数据结构,后插入的元素还在表的前面,即后进先出;
lrange 键 开始位置 结束位置
:同getrange
命令类似,获取List中指定范围的所有值,从左至右标号; 并且Redis没有提供从右开始标号读取的方式(如rrange);
rpush 键 元素值
:该命令与lpush
命令相反,从表的右边插入元素;该命令对线性表的操作方式可以用来模拟队列的数据结构,后进后出;
可以看出List更像一个双端队列,可以从两边插入元素。
与插入类似,删除List中的元素也可以从左右两边删除,即lpop 键
和rpop 键
命令,分别表示删除List的第一个元素和最后一个元素。
如果想要删除特定的元素,可以通过命令lrem 键 数量 元素值
来实现,该命令表示删除List中的多个相同的元素,如一个键位list的List中有1,2,2,2,3,4这几个元素,则lrem list 2 2
表示删除list中的两个值为2的元素,删除后list中还有1,2,3,4这些元素。
lindex 键 下标
:该命令表示通过下标的获取List中的元素值;
llen 键
:获取List的长度,即元素数量;
ltrim 键 开始位置 结束位置
:在List中截取从开始位置下标到结束位置下标的元素,截取下来的子List会替换原List;
rpoplpush 源List键 目标List键
:移除源List中的最右边的一个元素,并把该元素添加到目标List中的最左边;目标List可以是自己,相当于把最右边的元素移到最左边;
lset 键 元素下标 新元素值
:修改List中指定下标元素的值;如果List不存在或者下标越界则修改失败,报错;
linsert 键 before/after 元素值 新元素值
:该命令表示在List的指定元素的前面或者后面插入新的元素;例如linsert list before "v2" "v1"
表示在list列表的v2字符串元素前面插入v1字符串,linsert list after "v2" "v1"
表示在后面插入;
通常可以应用在消息队列等情况下。
Set
Set集合与List的主要区别是元素不允许重复,且无序。Set的大多数命令都是以S(不区分大小写)
开头开头,常见操作命令如下:
sadd 键 元素值 ...
:向Set中批量添加元素;重复元素则添加失败;
smembers 键
:获取Set中的所有元素;
sismember 键 元素值
:判断在Set中该元素是否存在;
scard 键
:获取Set的长度,即元素个数;
srem 键 元素值
:删除Set中指定的元素;
srandmember 键 数量(可选)
:从Set中随机获取指定数量的元素,如果不指定数量,则默认随机取一个;
spop 键
:随机删除Set中的一个元素;
smove 源Set键 目标Set键 元素值
:将源Set中的指定元素移动到目标Set中;
sinter 源Set键 目标Set键
:获取源Set和目标Set两个Set的交集;
sdiff 源Set键 目标Set键
:获取在源Set中去除目标Set中两者共有的元素后剩下的元素,即去除上述的交集后剩下的元素;
sunion 源Set键 目标Set键
:获取两个集合的并集;
通常可以应用在推荐好友(共同好友)等情况下;
Hash
Redis中Hash这种数据类型类似于Java中的Map集合,也是键值对的形式,只不过值也是一个Map(键-值对)。或者可以这样看,String类型的值是字符串,多个String键-值对组成了一个String的表;而Hash的值为多个键-值对,即这个String的表,所以Hash就是一个存放多个键值对的表。
Hash的大多数命令都是以H(不区分大小写)
开头开头,常见操作命令如下:
hset 哈希键 键 值
:向哈希中添加一个键-值对;
hget 哈希键 键
:获取哈希中指定键对应的值;
hmset 哈希键 键1 值1 键2 值2 ...
:向哈希中批量添加键值对元素;
hgetall 哈希键
:获取指定哈希中所有的键-值对元素;获取结果的列表依次为键1 值1 键2 值2 ...
;
hdel 哈希键 键
:删除指定哈希中的指定键-值对;
hlen 哈希键
:获取指定哈希中键值对元素的数量;
hexists 哈希键 键
:判断指定哈希中指定字段是否存在;
hkeys 哈希键
:获取哈希中所有元素的键;
hvals 哈希键
:获取哈希中所有元素的值;
hincr 哈希键 键 自增值
:指定哈希中的指定键的值自增指定的值;
hdecr 哈希键 键 自减值
:自减指定的值;
hsetnx 哈希键 键 值
:同String中的相同,存在则创建成功,不存在则失败;与set的区别是set会覆盖;
通常可以应用为存储对象,比String的方式结构更加清晰,String还是更加适合存储字符串。
Zset
Zset是在Set的基础上为每个元素增加了一个标识,实现分组。根据该结构很容易想到我们可以通过Zset可以实现有序Set,并进行排序。Hash的大多数命令都是以Z(不区分大小写)
开头开头,常见操作命令如下:
zadd 键 标识1 值1 标识2 值2 ...
:批量添加多个带有标识的元素值,如zadd 1 one 2 two 3 three
则表示按照1、2、3的顺序有序添加one、two、three三个字符串;
zrange 键 开始位置 结束位置
:获取指定范围元素,升序;zrevrange
为降序;
zrangebyscore 键 最小值 最大值
:获取标识在最小值~最大值范围内的元素,且按照标识升序排序zrevrangebyscore 键 最大值 最小值
为降序;
zrem 键 值
:删除指定元素;
zcount 键 开始标识 结束标识
:获取指定标识区间内的元素数量;
Zset的很明操作命令后面可以跟参数,并且与Set几乎相同,具体的使用可以参考官方文档。
一般可以应用于排行榜,比如微博粉丝排行榜,标识值为粉丝数,元素值为用户id。
6.2 Redis的三种特殊数据类型
geospatial
geospatial为地理位置类型数据,其提供了推算两地之间距离等功能。geospatial大多数命令都是以geo(不区分大小写)
开头开头,总共只有6个命令:
geoadd 地理空间键 经度 纬度 地名
:向指定的地理空间中添加地理位置信息;添加规则:地球两极无法直接添加,且一般通过程序直接快速导入地理位置信息;geopos 地理空间键 地名
:获取地理位置信息;geodist 地理空间键 地名1 地名2
:获取两地的直线距离;georedius 地理空间键 经度 纬度 范围 距离单位
:查询在指定地理空间中,指定一个具体位置为圆心,以指定范围为半径,在这个圆内的所有地名;后面可以跟参数withdist
表示查询结果带上距离、withcoord
查询结果带上经度纬度、count 数量
表示最多只查询出指定数量的结果;georediusbymember 地理空间键 地名 范围 距离单位
:以已有的地名为中心点查询范围内的所有地名;geohash 地理空间键 地名1 地名2 ...
:返回一个或者多个位置元素的geohash(一个11个字符的字符串)表示。
geospatial的底层实现其实是Zset,所以我们同样可以用和Zset一样的命令来操作geospatial的增删查改。
hyperloglog
hyperloglog是用来作基数统计的算法,通常应用在UV(用户访问统计)的计数中,但存在0.81%的错误率。并且能够很大程度上优化这些数据在内存中存储的大小。常见命令如下:
pfadd 键 值1 值2 ...
:存储多个元素;
pfcount 键
:统计不重复元素的个数;
pfmerge 键 源键1 源键2
:合并两组数据到新的数据集合中,并集;
bitmaps
bitmaps为位图数据结构,按位存储数据,数据中只有0和1两个状态,用于统计只有两种状态的大量数据,如疫情感染人数,网站用户活跃人数,因为对于数据中的每一项,只有感染或者不感染,活跃或者不活跃这两个状态;还有像打卡、登录用户等类似这些场景。该存储结构在这些应用场景下,很大程度上节约了存储空间。常用命令:
setbit bit键 键 值(可选值:0和1)
:添加数据;如用bitmaps记录员工korbin周一到周日的打开,则添加setbit korbin sunday 1
、setbit korbin monday 0
等7条数据,1表示签到,0表示未签到。
getbit bit键 键
:查询指定的值;
bitcount bit键
:统计值为1的数量;
7.Redis的基本事务操作
事务的本质是一组命令的集合,并且事务中的所有命令都会被序列化,在事务的执行过程中顺序进行;由于这些命令在一个事务中会按照顺序依次执行完,并且事务不允许其他命令在这些命令执行过程中进行干扰,所以事务有一致性、顺序性和排他性这个三个特性。
在Redis中,的单条命令是保证原子性的,要么同时成功,要么同时失败;但Redis的事务是不保证原子性的。并且由于Redis中一次事务中的所有命令并不会提前执行,而是先入队,然后在事务执行时所有命令再一起依次执行,所以Redis的事务实际上是没有隔离级别的概念的。
Redis的事务操作:
- 开启事务:
multi
;- 写命令操作数据,命令入队;
- 执行事务:
exec
或者取消事务:discard
。
如果事务发生错误,Redis事务发生错误的处理方式和Java的异常处理机制非常相似,分编译性出错和运行时出错:
- 编译性出错指命令代码都写错了,比如写了根本就没有的命令等。则会直接报错,事务中的所有命令都不会执行;
- 而运行时的错误指如果事务的命令队列中只存在逻辑错误,如字母组成的字符串执行数字的自增操作。则其他命令是可以正常执行的,只有出错的命令执行失败。
Redis中的监控:
Redis中的监控,即相当于给数据加锁,首先介绍锁🔒的概念:
-
悲观锁:顾名思义很悲观,认为总是会出错,所以无论做什么都会加锁。
-
乐观锁:与悲观锁相反,很乐观,认为总是不会出错,所以不会上锁!但是它会在自己做更新数据操作的时候,判断它取数据到更新数据的期间原数据有没有没被其他线程抢先修改过,通过一个version字段来实现。
监控操作命令:watch 键
,表示监控该字段,然后再执行开启事务操作 👇
如果事务正常执行结束,则一切正常;但如果事务出错,发生了如上述介绍乐观锁时发生了被别的线程抢先修改了数据的情况,则事务会提交失败,事务中的所有命令都不执行。所以相当于起到了加了乐观锁的作用。 另外,虽然事务由于加锁失败,但如果想要继续完成操作的话,需要先解除监视,即给该字段解锁unwatch
,然后再执行上述的监视命令后执行事务操作则正常进行。
8.Jedis的使用
Jedis是Redis官方推荐的使用Java来操作Redis的连接开发工具!
导入Jedis依赖:
<!-- jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
Jedis的使用非常简单,所有接口与Redis中的命令完全对应。示例:
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());
jedis.set("name","Korbin");
jedis.set("age","12");
jedis.incr("age");
System.out.println(jedis.get("age"));
jedis.close();
在Jedis中管理Redis事务:
Jedis jedis = new Jedis("127.0.0.1",6379);
Transaction transaction = jedis.multi();
try {
transaction.set("name","Korbin");
transaction.set("age","12");
int i = 1/0;
transaction.incr("age");
transaction.exec();
}catch (Exception e){
transaction.discard();
e.printStackTrace();
}finally {
jedis.close();
}
Jedis对事务的管理相当于不仅默认会使用Redis的事务错误处理机制,而且还通过Java的运行时异常来在Java出错时取消事务。
9.SpringBoot整合Redis
请参照我的另一篇博客 :SpringBoot整合Redis 。
10.Redis.conf配置文件详解
在Linux环境下启动Redis服务器时,会带上redis.conf
配置文件,并且在之前修改过daemonize
值为yes
让Redis服务器后台运行。使用好配置文件,将有助于我们更加灵活的按照自己的需求配置和使用Redis。一些常用的配置介绍如下(修改配置文件后请重启Redis服务):
基本信息介绍部分
配置文件一开始介绍了一些基础信息、进制单位信息和大小写不敏感等信息:
INCLUDES
配置可以去包含别的配置文件,即将多个redis.conf配置文件组合到一起配置,默认被注释:
include /path/to/local.conf
NETWORK(网络配置)
bind 127.0.0.1
:绑定可以访问的ip地址,默认绑定本机地址。如果想要远程访问则可以用*
通配或者指定要远程访问的ip。protected-mode yes
:保护模式,默认开启,保证Redis数据库的安全性。port 6379
:端口号,默认6379,可根据需求自行更改。
GENERAL(通用配置)
daemonize yes
:是否以守护进程的方式开启Redis服务,即是否后台运行,默认为no。supervised no
:管理守护进程,默认为no,一般不使用。pidfile /var/run/redis_6379.pid
:指定以守护进程模式启动服务情况下的pid文件,即进程文件。loglevel notice
:指定日志的级别。有debug、verbose、verbose和warning这几种日志级别,详细参照配置文件中注释的介绍。logfile ""
:日志的输出文件,默认为空,即为标准终端输出。databases 16
:数据库数量,默认16个。always-show-logo yes
:开启服务时是否显示LOGO,默认开启。
SNAPSHOTTING(持久化快照)
save 300 10
:配置持久化规则,表示在300秒内至少执行了10次数据库操作(10个key进行了数据修改),则触发rdb数据持久化方式。Redis为内存数据库,断电即失,所以需要做数据持久化。stop-writes-on-bgsave-error yes
:持久化出现错误后Redis数据库是否还继续工作,默认开始。rdbcompression yes
:是否压缩rdb持久化文件,默认开启。压缩代表会多消耗CPU资源,但节省了硬盘空间,可根据需求自行配置。rdbchecksum yes
:持久化时是否校验rdb文件,检查文件是否有错误。dbfilename dump.rdb
:持久化的rdb文件名。dir ./
:持久化文件保存目录,默认当前目录。
REPLICATION(主从复制)
slaveof <masterip> <masterport>
:配置主机的ip和port,默认被注释。masterauth <master-password>
:配置主机密码。
SECURITY(安全)
requirepass foobared
:开启服务器密码,默认被注释。配置密码requirepass 密码
即可,另外还可以在客户端中通过命令来设置密码:config set requirepass 密码
,验证密码命令:auth 密码
。
CLIENTS(客户端限制)
maxclients 10000
:设置能连接上Redis服务器的最大客户端数量,默认被注释。
MEMORY MANAGEMENT(内存管理)
maxmemory <bytes>
:Redis设置最大的内存容量,默认被注释。maxmemory-policy noeviction
:配置内存达到上限之后的处理策略,6中策略方式如下:
- volatile-lru:只对设置了过期时间的key进行LRU(默认值)
- allkeys-lru : 删除lru算法的key
- volatile-random:随机删除即将过期key
- allkeys-random:随机删除
- volatile-ttl : 删除即将过期的
- noeviction : 永不过期,返回错误
APPEND ONLY MODE(aof配置)
appendonly no
:默认不开启aof持久化(追加)模式,即默认使用rdb方式做持久化,因为实际大多数应用场景下rdb完全够用。appendfilename "appendonly.aof"
:aof持久化的文件名。appendfsync everysec
:持久化同步的策略,默认每秒同步一次,另外还有always
和no
分别表示每次修改数据都会同步和永不同步,由操作系统自己同步数据。no-appendfsync-on-rewrite no
:是否开启aof重写机制,即在aof持久化文件超过配置的大小时,超出限制的持久化数据大小,则会重写覆盖。默认关闭,无限追加。
其他还有更多的配置内容有兴趣的朋友可以去自行了解。
11.Redis持久化
上述提到过,Redis是基于内存的数据库,由于内存中的数据断电即失,所以一旦Redis服务器进程退出,则Redis服务器数据库中的数据也就丢失了,所以必须做数据的持久化工作。当然,如果希望数据只做缓存,也可以不使用任何持久化。Redis中提供了两种持久化方式。
如果持久化文件破损了,可以通过Redis提供的redis-check-aof
和redis-check-rdb
工具进行对应的修复(删除破损的数据,正常数据保留),命令:./redis-check-aof --fix appendonly.aof
。
11.1 RDB(Redis DataBase)
RDB持久化方式会在指定的时间间隔内,将Redis在内存中的数据集快照写入磁盘;在恢复数据时则是将持久化的快照文件即以rdb为后缀的文件dump.rdb
,再读入内存中。
RDB的原理:
Redis会单独创建(fork)一个子进程来对数据进行持久化。首先将数据读取后写入一个临时的文件中,待持久化过程结束了,用这个新的持久化文件去替换上一次的持久化文件,至此子进程结束。整个过程中,主进程不会进行任何IO的操作,这就在很大程度上确保了Redis的性能问题。如果需要进行大规模的数据恢复,且对于数据恢复的完整性不是非常的敏感,则RDB方式比另一种持久化方式AOF更加有效。但RDB的缺点是最后一次持久化后的数据可能丢失。所以一般情况下在上述的配置文件中默认就是使用的RDB。
RDB的触发机制:
- 配置文件中配置的save规则满足的情况下会触发RDB规则进行数据持久化,生成dump.rdb文件。
- 执行
flushall
命令,同样会触发RDB,生成持久化文件。- 退出Redis,进行关机时,也会触发生成持久化文件。
RDB数据恢复方式:
RDB文件是默认生成在Redis服务启动目录,即安装时生成的
bin
目录,所以如果我们要做数据的恢复,只需要将RDB文件放到该目录即可,在Redis启动时会自动检查该文件,然后恢复里面备份的数据到内存中的Redis数据库中。
RDB的优缺点:
优点:
1.由于其使用子进程的形式处理持久化,所以适合大规模的数据恢复。
2.RDB更适合对数据完整性要求不高的情况。
缺点:
1.由于save的规则不同,可能需要一定的时间间隔才会进行子进程持久化操作,所以如果Redis意外宕机,最后几次修改的数据就丢失了。
2.开启子进程进行持久化时需要占用内存空间,需要考虑内存空间的消耗及分配问题。
11.2 AOF(Append Only File)
AOF持久化方式是以日志的形式来记录每个写操作,并将Redis执行过的所有指令记录下来(读操作不记录),只允许追加文件内容,不允许修改。文件名appendonly.aof
。
AOF原理:
Redis服务在启动之初会开启子进程读取该持久化文件重新构建数据,换言之,Redis重启的话就根据日志文件的内容将写指令从前到后再执行一遍,以完成数据的恢复工作。
AOF的优缺点:
优点:
每一次写操作都会被记录,所以持久化的数据完整性更好。
缺点:
1.相对于RDB持久化数据文件,AOF大得多,恢复数据也更慢。
2.AOF的运行效率也较RDB更慢,所以Redis默认采用RDB持久化。
如果同时开启了RDB和AOF两种持久化方式,当Redis重启时会优先载入AOF的文件来恢复原始数据,因为在通常情况下AOF文件保存的数据集要比RDB保存的完整,而且RDB的数据不实时。但是通常情况下还是推荐两种持久化方式同时开启,因为RDB更适合用于数据库的备份,快速重启,然后AOF可能存在一些潜在的BUG,两个配合,作一个数据的完全备份手段。
12.Redis发布订阅
Redis的发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,接收者(sub)接收消息。例如微信公众号每天推送文章,然后微信用户收到消息。
订阅/发布消息图如下,有三个角色,消息发送者、频道和消息订阅者:
操作命令, 这些命令被广泛的应用于构建即时通讯应用,比如网络聊天室(chatroom)和实时广播、实时提醒等。
示例:
Redis的该功能在实际应用中使用得比较少,稍微复杂一点的场景一般使用消息中间件MQ。
13.Redis主从复制
主从复制,是指将一台Redis服务器中的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能从主节点到子节点。主节点以写为主,从节点以读为主。
默认情况下, 每台Redis服务器都是主节点,且一个主节点可以有多个从节点或者没有从节点,但一个从节点只能有一个主节点。
主从复制的作用
- 数据冗余:主从复制实现了数据的热备份,是持久化方式之外的另一种数据冗余的方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复。实际上是一种服务的冗余。
- 负载均衡:在主从复制基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时连接主节点,读Redis数据时连接从节点),以分担服务器的负载。尤其是在大多数场景是写少读多的情况下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用(集群)基石:除了上述作用之外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
一般来说,在将Redis运用于工程项目中时,不会使用单机Redis服务器,通常是一主二从这样的模式,原因如下:
- 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力太大。
- 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储数据。并且单台Redis服务器的最大内存不应该超过20G。
主从复制的配置
只配置从库,不配置主库。首先启动第一台Redis服务器作为主机,查看主从复制信息的Redis命令
info replication
:
在单机下模拟搭建集群,分别以3个不同的端口号再启动三个子节点,然后再分别配置三个子节点,命令slaveof 主机ip 端口
,分别指定他们的主机为第一台开启的Redis服务器。完成配置后三个子节点的角色变为了salve:
上述通过命令来进行主从配置只是暂时的,服务重启后则失效,而采用上述配置文件中的主从复制配置则是永久性的。
上述搭建的集群中,只有主节点可以写数据,而子节点只能读数据。主机中所有的信息和数据都会被从机保存。并且当主机宕机了,从机中的数据仍然还在。
并且当主机再次启动后,可以从从机中获取数据,所以之前的数据仍然存在。当从机宕机后,再次启动连接上来,也是仍然可以读取到主机共享到从机的数据。
复制原理
- slave每次启动成功连接到master后会发送一个sync同步命令;
- master接收到命令,启动后台的存盘子进程,同时收集所有接收到的用于修改数据集的命令,在该子进程执行完毕后,master将传送整个数据文件到slave,完成一次同步任务。
全量复制:上述完成的两步为全量复制,slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:在新增数据修改操作后,master会继续将新的所有收集到的修改命令依次传给slave,完成同步。
所以相当于从机连接至主机时,一定会先执行一次全量复制;在主机进行写操作时,会执行一次增量复制操作,即从机会一直同步主机的数据。
主从复制模式
另外,除了可以以上述的模式进行主从复制,还可以在将从机作为另一个从机的主机,虽然它是其他从机的主机,但是它的角色依旧是从机,即是角色为master的从机。这种主从复制模式很像一个火车:
但是,上述的两种模式实际上在工程项目中都很少使用,因为在第一种主从复制模式中,主机宕机后,从机就群龙无首了,没有服务器再提供写服务了。所以如果在主机宕机时有从机能够站出来代替主机的位置,并且当主机重新启动回来时,只能作为从机来使用了,因为主机位置已经被接管了,很类似于谋朝篡位。这样这个Redis集群就能够继续提供完整的服务,这就是第三种主从复制模式:
如果主机宕机,则选择一个从机执行命令:slaveof no one
,将其提升为主机;然后其他从机再重新指定主机为刚才的新主机。
但是上述通过命令的方式是手动切换主从关系的,需要人工干预,费时费力,还会造成一段时间内服务不可用,因此并不是一种推荐方式。所以就有一种智能的主从切换技术: 哨兵模式 ,可以自动重新指定主机(自动选举老大),Redis从2.8以后正式提供Sentinel(哨兵)架构来解决该问题。
哨兵模式
哨兵架构能够自动监控Redis主机是否故障,如果故障了则会根据投票数自动将从机转换为主机。
哨兵模式是一种特殊的模式,Redis提供了哨兵的命令,并且哨兵是一个独立的进程。哨兵模式的基本原理是哨兵通过发送请求命令,等待Redis服务器响应,从而监控运行的多个Redis实例(很像TCP的三次握手)。
哨兵的作用:
1.通过发送请求命令,监控Redis服务器的运行状态,包括主服务器和从服务器。
2.当哨兵检测到master宕机时,会自动将slave切换成master,然后通过“发布订阅模式”通知其他的从服务器,修改配置文件,实现自动切换指定的主机。
然而如果只有一个哨兵进程对Redis服务器进行监控,当这个哨兵进程也没了该怎么办呢?为此,一般我们可以使用多个哨兵对Redis服务器进行监控,并且各个哨兵之间同时还相互监控状态,相当又组成了一个哨兵集群,哨兵集群监控服务集群,这样就形成了完整的哨兵模式。(如果全部哨兵都死了,那么可以考虑机房是不是被炸了?😄)
假设主服务器宕机,哨兵1会首先检测到这个结果,但系统并不会马上进行failover,因为可能仅仅是哨兵1主观的认为主服务器不可用,这个现象称为 主观下线(即哨兵进程判断错了),然后当其他的哨兵也检测到主服务器不可用时,并且数量达到一定值时,这时其中一个哨兵会发起投票,哨兵之间进行投票,投票的结果由其中一个哨兵公布发送,然后才进行failover(故障转移)操作;主从关系切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器将主机切换为新主机,这个过程称之为 客观下线。
哨兵模式配置文件sentinel.conf
,在安装Redis时的Redis解压目录中,像拷贝redis.conf一样将哨兵模式的配置文件拷贝到同样的目录下。配置文件的详细配置请自行参考Redis官方文档介绍,其中一个关键配置内容如下:# 被监控的主机名称:mymaster # ip:127.0.0.1 # 端口号:6379 # 一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效:2 sentinel monitor mymaster 127.0.0.1 6379 2
启动哨兵程序:
./redis-sentinel ../sentinel.conf(配置文件位置)
。
sentinel.conf
部分其他配置
# Example sentinel.conf
# port <sentinel-port>
port 8001
# 守护进程模式
daemonize yes
# 指明日志文件名
logfile "./sentinel1.log"
# 工作路径,sentinel一般指定/tmp比较简单
dir ./
# 哨兵监控这个master,在至少quorum个哨兵实例都认为master down后把master标记为odown
# (objective down客观down;相对应的存在sdown,subjective down,主观down)状态。
# slaves是自动发现,所以你没必要明确指定slaves。
sentinel monitor MyMaster 127.0.0.1 7001 1
# master或slave多长时间(默认30秒)不能使用后标记为s_down状态。
sentinel down-after-milliseconds MyMaster 1500
# 若sentinel在该配置值内未能完成failover操作(即故障时master/slave自动切换),则认为本次failover失败。
sentinel failover-timeout TestMaster 10000
# 设置master和slaves验证密码
sentinel auth-pass TestMaster testmaster123
sentinel config-epoch TestMaster 15
#除了当前哨兵, 还有哪些在监控这个master的哨兵
sentinel known-sentinel TestMaster 127.0.0.1 8002 0aca3a57038e2907c8a07be2b3c0d15171e44da5
sentinel known-sentinel TestMaster 127.0.0.1 8003 ac1ef015411583d4b9f3d81cee830060b2f29862
14.Redis缓存穿透、击穿和雪崩处理
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题,其中最要害的问题就是数据的一致性,从严格意义上来讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。
另外的一些典型问题就是缓存穿透、缓存击穿和缓存雪崩。目前,业界已经有比较流行的解决方案。
缓存穿透
缓存穿透,指用户想要查询一个数据,但是Redis中却没有,也就是缓存没有命中,于是便向持久层数据库查询,但如果持久层数据库也没有查询到,则此次查询失败。如果当非常多的用户查询时缓存都没有命中,都去请求持久层数据库时,这就会给持久层的数据库造成很大的压力。这种情况就是出现了缓存穿透。甚至,还可能被坏人恶意攻击,疯狂请求查询该不存在的数据,将可能直接导致持久层数据库服务器崩溃宕机。
缓存穿透解决方案:
- 布隆过滤器:一种数据结构,对所有可能查询的参数以hash的形式存储,在控制层就先进行检验,不符合则丢弃,从而避免了对底层存储系统的查询压力。
- 缓存空对象:当存储层(持久层)不命中后,即使返回的是空对象也将其缓存起来,并与此同时设置一个过期时间,之后如果再请求查询该数据则将从缓存中读取,保护了底层数据源。
但是缓存空对象会存在两个问题:1.如果空值能够被缓存起来,就意味着缓存需要更多的空间来存储更多的键,因为当空值的键过多时,会导致消耗很多的空间来存储无用的数据。2.即使对空值设置了过期时间,依旧会使缓存层和存储层的数据发生窗口的不一致,这对于需要保持高度一致性的业务会有影响。
缓存击穿
注意区分缓存击穿和缓存穿透不是同一个概念,缓存击穿是指一个键(Key)非常热点,在不停的扛着高并发,高并发集中对这个点进行访问,当这个key在失效的一瞬间,持续的高并发就穿破了缓存,转而直接请求持久层数据库。这久像是在屏障上凿开了一个洞,所以叫做缓存击穿。
当某个Key在过期的一瞬间,大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,虽然会很快会写至缓存,但是会在会写回缓存这个期间导致持久层数据库压力太大,进而可能导致服务器崩溃。如微博热搜。
所以相当于缓存穿透是太多数据查不到,而缓存击穿是缓存过期时同时查询太多了。
缓存击穿解决方案:
- 设置热点数据永不过期:从缓存层面来看,没有设置过期时间,所以不会出现热点Key过期产生的问题。
- 加互斥锁:通过使用分布式锁,保证对于每个key同时只允许有一个线程去查询底层持久层的数据,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩
缓存雪崩是指在某一个时间段内,缓存中的数据集中过期失效,Redis缓存服务器宕机。
缓存雪崩的原因:
- 比如马上就要到双十一零点,这时很快就会迎来一波抢购,然后这批商品数据在比较集中的时间放入了缓存,假设缓存过期时间设置的是一个小时。那么到陵城一点钟的时候,这批商品的缓存将在一个很短的时间段内集体过期。而对这批商品的访问查询,其负载全都落在了数据库身上,对于数据库而言,就会产生周期性的压力波峰。
- 其实缓存集中过期都还不是非常致命,因为自然形成的缓存雪崩,一定是在某个时间段集中创建部分缓存,但这种压力数据库一般是能够承受的,无非是对数据库产生周期性的压力罢了;但最危险的缓存雪崩情况是,缓存服务器在某个时间节点上宕机或者断网等其他因素导致的缓存停运,这时所有的数据请求都会涌向数据库,将对数据库服务器造成的压力将不可预知,很有可能瞬间就把数据库压垮。
像双十一的时候,淘宝就可能会停掉一些其他服务,以保证主要服务的正常运行,比如在这个期间就没法去退款之类的。
缓存雪崩解决方案:
- 保证Redis高可用:该思想的含义是,既然Redis有可能挂掉,那我就多增设几台Redis服务器,这样一台服务器挂掉之后其他的还可以继续工作,其实就是搭建集群。
- 限流降流:这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库和写缓存的线程数量。比如对某个Key只允许一个线程查询数据和写缓存,其他线程只能等待。
- 数据预热:数据加热的含义是指在正式部署之前,我可以先把可能的数据都预先访问一遍,这样部分可能会被大量访问的数据就会提前加载到缓存中。在即将发生大并发访问前手动触发加载不同的Key到缓存,设置不同的过期时间,以使缓存的过期时间点尽可能的均匀一点。