Redis学习笔记
Redis学习笔记
学习视频:狂神说Java 学习时间:2021年2月2日
基础知识
Redis默认有16个数据库,默认使用第0个数据库。默认端口为6379;
1 | select 3 #切换数据库 |
1 | flushdb #清空当前数据库的信息 |
Redis是单线程的
Redis是基于内存操作的,CPU不是其性能瓶颈。Redis的瓶颈是机器的内存和网络带宽,所以使用单线程。
Redis是C语言写的。性能完全不比memcache差。
下载安装
window下载安装
Linux下载安装
基本命令
1 | keys * # 列出所有的键 |
五大基本数据类型
1.String
基本命令
1 | append key "hello" # 向键key的值后追加字符串,如果当前key不存在则新建,相当于set |
使用场景
value除了字符串,数字也可以。使用场景有计数器,多单位的统计数量 uid:234123:follow 322
2.List
3.Set
基本指令
set集合中无序不可重复
1 | ##################################################### |
使用场景
共同关注
4.Hash
key-Map集合,逻辑形式:key-
基本指令
1 | ##################################################### |
使用场景
hash用于经常变动的数据。更加适用于对象的存储。
5.Zset
有序集合,在set的基础上增加了一个排序
三大特殊数据类型
geospatial
hyperloglog
bitmaps
Redis事务
概念
Redis事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,会按照顺序执行。一次性,顺序性,排他性。
Redis单条命令是保证原子性的,而事务是不保证原子性的。
Redis事务没有隔离级别的概念。
所有的命令在事务中并没有被执行。只有发起事务执行命令的时候才会被执行。
Redis事务的三个阶段:
- 开启事务 multi
- 命令入队 …
- 执行事务 exec
锁:Redis能够实现乐观锁。
基本指令
正常执行事务
1 | multi # 开启事务 |
放弃事务
1 | multi |
编译型异常
事务中所有的命令都不会执行
1 | multi |
运行时异常
除异常指令,其它指令都可以正常执行(没有原子性),异常指令抛出异常
1 | set k1 "v1" |
监控
悲观锁
- 无论什么时候都加锁
乐观锁
- 只在更新数据的时候,判断在此期间是否有人修改过这个数据
- 获取version
- 更新的时候比较version
监控测试
1 | set money 100 |
以上是正常情况。
线程1
1 | set money 100 |
线程2
1 | get money |
Jredis
通过Jedis操作Redis
什么是Jedis
Jedis是Redis官方推荐的Java连接开发工具,是使用Java操作Redis的中间件,相当于用Java操作数据库的JDBC
1.导入依赖
1 | <dependency> |
2.编码测试
(1)连接测试
1 | public class TestPing { |
(2)部分指令测试
1 | public class TestPing { |
(3)事务控制
1 | public class TestPing { |
SpringBoot整合Redis
1.maven依赖
1 | <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis --> |
2.说明
SpringBoot 2.x之后,原来使用的Jedis被替换为了Lettuce
Jedis:采用的直连,多个线程操作的话是不安全的, BIO
Lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的问题,NIO
3.RedisTemplate源码
SpringBoot所有的配置类,都有一个自动配置类 RedisAutoConfiguration.class
自动配置类又会绑定一个properties配置文件 RedisProperties.class
1 |
|
4.整合测试
(1)导入依赖
(2)配置连接
1 | # 配置Redis |
(3)测试
1 |
|
5.自定义RedisTemplate
(1)json依赖
1 | <!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-mapper-asl --> |
(2)测试
1 | public void test() throws IOException { |
直接传递对象会报错,利用jdk方式序列化后可正常传输
1 | //将其序列化 |
(3)自定义RedisTemplate类(略)
Redis持久化
redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以Redis提供了持久化功能。
RDB(Redis Database)
什么是RDB
在指定的时间间隔内将内存的数据集快照写入磁盘,也就是snapshot快照,它恢复时是将快照文件直接读入内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化的文件。整个过程中,父(主)进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是特别敏感,那么RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。Redis默认的就是采用RDB方式(redis.conf),一般情况下不需要修改这个配置。
在生产环境有时会将这个dump.rdb文件进行备份。
==redis保存的持久化文件是dump.rdb==
redis.conf里设置save规则:
1 | save 60 5 # 如果60s内操作了5次以上操作,则会生成dump.rdb文件 |
RDB触发机制
1.save的规则满足的情况下
2.执行flushall命令
3.退出redis时
备份就会自动生成dump.rdb文件
几乎Redis默认的rdb配置就够用了。
优缺点
1.优点
(1)适合大规模的数据恢复
(2)对数据的完整性要求不高
2.缺点
(1)需要一定的时间间隔进行操作。如果Redis意外宕机了,那么最后一次的数据就会丢失,例如在59s时宕机,则这0~59秒间产生的数据都会丢失。
(2)fork子进程的时候会占用一定的内存空间
AOF(Append Only File)
什么是AOF
AOF将我们的所有命令都记录下来。
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件,不许修改文件,Redis重启后,会根据日志文件的内容将写指令从前到后依次执行一次完成数据的恢复工作。默认是不开启的,需手动开启(改为yes即可)。save规则是每秒生成(每秒都在记录写操作)。
1 | appendonly no |
==AOF保存的是appendonly.aof文件==
如果aof文件被破坏了,这时候redis将无法启动,需要修复aof文件。redis提供了工具redis-check-aof --fix appendonly.aof 进行修复。
触发机制
重启Redis。
重写规则
aof默认就是文件的无限追加,文件会越来越大。
1 | auto-aof-rewrite-percentage 100 |
如果aof文件大于64mb,则fork一个新进程将我们的文件进行重写。
优缺点
1 | appendfsync always #每次修改都会同步,消耗性能 |
1.优点
(1)每一次修改都同步,文件完整性会更好。
(2)每秒同步一次,可能会丢失一秒的数据。
(3)从不同步则效率最高
2.缺点
(1)相对于数据文件来说,aof远大于rdb,且修复的速度小于rdb
(2)aof运行效率也比rdb慢
Redis发布订阅
Redis发布订阅(pub and sub)是一种==消息通信模式==:发送者发送消息,订阅者接受消息。
Redis客户端可以订阅任意数量的频道。
指令
发布订阅测试
订阅频道
1 | redis 127.0.0.1:6379> SUBSCRIBE runoobChat #订阅一个频道,没有则创建 |
发布消息
1 | redis 127.0.0.1:6379> PUBLISH runoobChat "Redis PUBLISH test" #发布消息到指定频道 |
使用场景
1.实时消息系统
2.订阅/关注系统
3.实时聊天
稍微复杂的场景会使用消息中间件MQ
Redis主从复制
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。主节点以写为主,从节点以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。最低配置一主二从。
作用
1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2.故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
4.读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
5.高可用基石(集群):除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
环境配置
1.只配置从库,不用配置主库。查看库的信息
1 | info replication # 查看当前库的信息 |
2.从机配置
1 | SLAVEOF 127.0.0.1 6379 #以127.0.0.1:6379的主机为主库 |
真实的配置应在配置文件中进行,这样才是永久的。采用指令的方式是暂时的配置,如果从机断开再重启,则又会变回主机,丢失以前所属主机的信息。
一些细节
1.主机可以写,从机只能读。从机一旦连接上主机,主机的所有信息都能被从机自动保存。
2.主机断开连接,从机依旧连接到主机的。
3.层层链路:上一个M链接下一个S。如果主机断开了连接,可以使用SLAVEOF no one让自己成为主机。
如果主机修复了,只能手动配置主从关系。自动化->哨兵模式
哨兵模式
概述
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
哨兵测试
1.对sentinel.conf进行核心配置
1 | #sentinel monitor 被监控的名称(自己设置) 监控主机 端口 1 |
最后面的1代表主机宕机,让从机投票,看让谁接替成为主机,票数最多的成为主机
2.启动哨兵
1 | redis-sentinel kconfig/sentinel.conf |
优点
1.哨兵集群基于主从复制模式,所有的主从配置的优点,它都具有。
2.系统可用性更好
3.哨兵模式是主从模式的升级,是其自动的实现,更加健壮
缺点
1.在线扩容非常麻烦
2.哨兵模式的配置复杂
Redis缓存穿透和雪崩
服务的高可用问题
缓存穿透
1.概念
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层数据库。这会给持久层数据库造成很大压力,这时候相当于出现了缓存穿透。
2.解决方案
(1)布隆过滤器 Bloom Filter
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先校验,不符合则丢弃,从而避免了对底层存储系统的压力。
使用场景
原本有10亿个号码,现在又来了10万个号码,要快速准确判断这10万个号码是否在10亿个号码库中?
解决办法一:将10亿个号码存入数据库中,进行数据库查询,准确性有了,但是速度会比较慢。
解决办法二:将10亿号码放入内存中,比如Redis缓存中,这里我们算一下占用内存大小:10亿*8字节=8GB,通过内存查询,准确性和速度都有了,但是大约8gb的内存空间,挺浪费内存空间的。
接触过爬虫的,应该有这么一个需求,需要爬虫的网站千千万万,对于一个新的网站url,我们如何判断这个url我们是否已经爬过了?解决办法还是上面的两种,很显然,都不太好。
同理还有垃圾邮箱的过滤。
那么对于类似这种,大数据量集合,如何准确快速的判断某个数据是否在大数据量集合中,并且不占用内存,布隆过滤器应运而生了。
原理
核心实现是一个超大的位数组和几个哈希函数。假设位数组的长度为m,哈希函数的个数为k。
以上图为例,具体的操作流程:假设集合里面有3个元素{x, y, z},哈希函数的个数为3。首先将位数组进行初始化,将里面每个位都设置位0。对于集合里面的每一个元素,将元素依次通过3个哈希函数进行映射,每次映射都会产生一个哈希值,这个值对应位数组上面的一个点,然后将位数组对应的位置标记为1。查询W元素是否存在集合中的时候,同样的方法将W通过哈希映射到位数组上的3个点。如果3个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。反之,如果3个点都为1,则该元素可能存在集合中。注意:此处不能判断该元素是否一定存在集合中,可能存在一定的误判率。可以从图中可以看到:假设某个元素通过映射对应下标为4,5,6这3个点。虽然这3个点都为1,但是很明显这3个点是不同元素经过哈希得到的位置,因此这种情况说明元素虽然不在集合中,也可能对应的都是1,这是误判率存在的原因。
添加元素
将要添加的元素给k个哈希函数;得到对应于位数组上的k个位置;将这k个位置设为1。
查询元素
将要查询的元素给k个哈希函数;得到对应于位数组上的k个位置;如果k个位置有一个为0,则肯定不在集合中;如果k个位置全部为1,则可能在集合中
优缺点
优点:优点很明显,二进制组成的数组,占用内存极少,并且插入和查询速度都足够快。
缺点:随着数据的增加,误判率会增加;还有无法判断数据一定存在;另外还有一个重要缺点,无法删除数据。
实现
Redis 实现布隆过滤器的底层就是通过 bitmap 这种数据结构,至于如何实现,这里就不重复造轮子了,介绍业界比较好用的一个客户端工具Redisson。Redisson 是用于在 Java 程序中操作 Redis 的库,利用Redisson 我们可以在程序中轻松地使用 Redis。
1 | import org.redisson.Redisson; |
(2)缓存空对象
当存储层不命中时,即使返回的空对象也将其存储起来,同时会设置一个过期时间,之后访问这个数据将会从缓存中获取,保护了后端数据源。
存在的问题:
1.如果空值能够被缓存起来,这就意味着缓存需要更多的空间来存储更多的键。
2.即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿
1.概念
缓存击穿是一个key非常热点,在不停地扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。
这类数据一般时热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且写回缓存,会导致数据库压力瞬间过大。
2.解决方案
(1)设置热点数据永不过期
(2)加互斥锁
使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁上,对其的考验很大。
缓存雪崩
1.概念
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
2.解决方案
(1)redis高可用:搭建redis服务集群
(2)限流降级:在缓存失效后,通过加锁或队列来控制读数据库写缓存的线程数量。
(3)数据预热:在正式部署之前,把可能的数据预先访问一遍,这样部分可能大量访问的数据会加载到缓存中。在即将发生并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。











