Redis详解
什么是Redis
Redis是一个开源的,内存数据库,它可以作为数据库、缓存和消息中间件。它支持多种类型的数据结构。如字符串string、散列hash、列表list、集合set、有序集合zset。Redsi还支持事务和不同级别的持久化操作。
各种数据类型 应用场景
# 五种数据类型
这里的数据类型我们统一认为是值得value,因为key都是string
## string
这个类型是最常用的一个类型,Redis的String类型每个key可以存储512MB的数据。不要看type为String,其实它还可以存储数字等各种java类型,甚至是序列化的对象、图片、json字符串等。
通过string类型,我们可以设计出计数器、点赞、分布式锁、对象存储(序列化或者json)等。
## hash
存储一个一个map,可以存在多个key-value。可以通过这个存储一些没有序列化的对象。可以实现保存用户状态,设置一个过期时间即可。
## list
List是一个基本类型,根据它的lpush、rpush,等命令可以实现出队列、栈等数据结构。利用这些数据结构的特点帮我我们解决很多问题。
## set
这就类似于我们java中的set,它的内容不会重复,可以关注列表等。
## zset
比set多了一个排序规则,可以使用它做排行榜,它有一个top命令,可以取出前N个。
# 三大特殊类型
## 坐标geospatial
记录一组经纬度,可以方便的计算两个经纬度之间的位置,附近的人等等。
## 并集Hyperloglog
可以存储2^64个元素的集合,可以很方便的计算并集
## 位图bitmaps
存储二进制位0和1 可以存储多个0 1 ,结合数据字典可以实现很多业务。例如统计每周打卡天数,每月签到次数等
Redis为什么是单线程 单线程还这么快?
# 为啥是单线程的?
因为Redsi官方说,Redis的性能瓶颈不在CPU,它对CPU的需求比较低。另外使用多线程还会涉及到很多的问题。Redis的瓶颈在网络带宽和内存大小上。
# 单线程还这么快?
1、纯内存操作
2、非阻塞IO+多路复用机制
3、单线程避免频繁切换上下文
Redis中的事务
# redis中的事务
Redis事务的本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。
**redis中的单条事务是有原子性的,但是redis中的事务是不保证原子性的。**
**redis中的事务没有隔离级别的概念,所有的命令在事务中,并没有被执行,只有发起命令时,才会被执行**
## redis的事务:
----- 队列 set set set 执行 -----
- 开启事务(multi)
- 命令入队(set key val... set key2 val2...)
- 执行事务(exec) OR - 取消事务(discard)
当执行exec后,事务中的操作将有序的进行。取消事务后队列中的代码都不会被执行。当事务exec或者discard后就代表此次事务执行完毕了。
## 事务何时会失败?
当队列中存在编译时异常, 事务中所有的命令都不会执行(例如:使用了错误的命令导致redis报错)。如果出现运行时异常,除了出错部分,其他的仍然会被执行(例如:给字符串incr)。
## 乐观锁和悲观锁
所谓悲观锁,就是认为无论什么时候都会出问题,任何操作都会加锁。所谓乐观锁就是认为任何情况都不会出问题。所有操作都不会加锁。
但是乐观锁会在更新之前查询一下version字段的值,修改的时候判断一下在此期间是否有人修改过数据。
## watch监控版本
redis可以实现乐观锁,通过watch来监控。
在redis中实现乐观锁的方法非常的简单,在事务之前加上watch key即可
例如:
version : 10
watch version
multi
set version 11
set key val...
exec
此时可以正常执行成功,
但是如果在此期间,有其他线程修改了version就无法执行事务。
RDB AOF
# Redis持久化
redis是一个内存数据库,意味着断电丢失。所以它的持久化是非常有必要的。Redis中提供了两种持久化方式,RDB和AOF。默认的配置会保存在redis的data目录中,rdb保存在dump.rdb中,aof存在appendonly.aof中
## RDB快照
所谓RDB就是在一段时间内将内存中的数据快照写入磁盘,也就是所谓的snapshot(快照)。恢复时,从快照中将数据恢复至内存中。
在redis的配置文件中存在如下配置
save 900 1
save 300 10
save 60 100000
也就是说,在900S内只要有一次修改操作,300秒内发生10次修改操作,60秒内发生10000此修改该做就会触发RDB操作。
RDB的优势:文件保存的完整、恢复快。
## AOF日志
每次操作都会被立即记录到磁盘中,性能差但是数据保存的十分完整。它有三种触发机制,主要是两种吧,一个是每秒保存,另一种是每次保存。优点:AOP比RDB更加可靠,一般AOF每隔一秒通过一个后台线程执行一次fsync操作,最多丢失1秒的数据。而且AOP还可以进行灾难恢复,如果不小写使用了flushall清空了所有数据,也可以通过AOF来恢复。缺点就是AOF的文件会很大。
穿透 雪崩
# 为什么要用缓存?
当一个数据的访问量非常高频的时候,会对数据库造成巨大的压力。所以我们引入了缓存的概念,当用户需要操作DB之前,先判断缓存中是否存在,如果存在就直接从缓存拿,没有的话再查数据库,再放入缓存一份。
那么此时如果缓存挂掉了,用户的请求都直接访问DB这将是灾难性的。
# 什么是雪崩?
内存的价格是比较昂贵的,所以缓存中的数据会根据某些策略来设置过期时间,主要是惰性删除和定期删除两种。如果缓存设置的过期时间是相同的,这段时间内会有大量的key过期,所有的请求都会跳过redis直接访问DB,对DB造成大量的访问。甚至会导致整个服务的瘫痪。这种现象就是Redis的雪崩。
## 如何防止雪崩?
上面我们说到,雪崩产生的原因和过期时间有关,那么我们解决了过期时间集中就可以了.
1、给缓存的过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
2、对于Redis瘫痪可以利用集群+哨兵
# 什么是穿透?
再来聊一种场景,例如现在缓存了100个用户的信息,他们的id是1~100,那么当黑客请求id为-1的接口,就会导致直接去请求数据库,并且再访问数据库之后不会将数据缓存到redis中。如果穿透发生了同样会使得整个服务瘫痪。
## 如何防止穿透?
解决缓存穿透也有两种方案
1、对于不合法的请求,可以使用过滤器提前拦截。不合法就不让这个请求到达DAO层。
2、当数据不存在时,在缓存中缓存一个空对象。下次请求时,就可以从缓存中获取啦,不过使用这种方法,一般都会设置一个比较短的过期时间。
如何保证Redis中数据和MySQL(OtherDB)的一致性
# 更新操作
一般来说,对于更新操作,我们会有两种选择:
1、先操作数据库,再操作缓存
2、先操作缓存,在操作数据库
首先我们要明确的时,无论我们先选择哪个,我们都希望这两个要么同时成功, 要么同时失败,所以这就演变为了一个分布式事务的问题。
如果原子性被破坏了,可能会有以下的情况:
数据库成功了,缓存操作失败了
缓存操作成功了,数据库操作失败了
## 操作缓存
更新时,更新缓存也有两种方案
1、更新缓存
2、删除缓存
我们一般都是采用删除缓存的策略,原因是更新更容易导致缓存与数据库不一致的问题。
先删除缓存,咋更新数据库在高并发的情况下表现不是很好,先更新数据库,再删除缓存在高并发情况下表现更好,但是原子性容易被破坏。
集群 哨兵
所谓集群,就是通过增加服务器的数量,使得服务器达到一个稳定的、高可用的状态。
单机的redis虽然提供了RDB+AOF的策略。但是如果Redis宕机,也是灾难性的。为了提高redis的可用性,我们必须对他进行集群。
# 集群
部署多台Redis,有三种方案。twemproxy、codis、redis cluster3.0。这里不进行深入研究了。
## 主从复制
redis的集群中,每一个redis都称之为节点
节点有两种类型,主节点master和从节点slave
redis集群是基于redis主从复制实现的
只要网络连接正常,master都会将自己的数据同步到slaves中,保持主从同步
## 节点特点
主节点可读、可写从节点只读
## 哨兵
主从有一个弊端,就是当主节点挂了以后,整个集群就没有主节点了。所以在这种设计模式下,又出现了哨兵的机制,哨兵的作用就是对一个或者多个Redis集群进行监控和提醒,当哨兵监控到主节点出现故障之后,就会通知其他节点开会,由这些节点投票选取票数最高者作为主节点。当选的从节点自动成为主节点。原master修复重新上线后会自动转换为从节点。
分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 SETNX 不做任何动作
解锁:使用 del key 命令就能释放锁
解决死锁:
1)通过Redis中expire()给锁设定最大持有时间,如果超过,则Redis来帮我们释放锁。
2) 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。
SpringBoot中使用Redis
1、引入启动器
2、配置类中初始化redisTemplate属性
3、创建Redis工具类,封装常用操作。由于工具类还是需要使用2,所以其也需要被Spring扫描。
4、注入工具类,使用~