Redis是一个支持网络、基于内存、可选持久性的NoSql数据库,目前在很多的系统中都使用了Redis,尤其是在实现缓存功能的时候应用的尤其广泛(缓存功能也是很多人对Redis的认识),那么Redis到底有哪些优点和缺点,为什么会被广泛应用呢?
Redis的第一个优点就是速度快,Redis使用C语言实现,基于内存,数据的读写效率非常的高,这也是为什么很多系统的缓存功能使用Redis来实现,但是需要明确的是Redis是一个数据库,缓存只是它的一项应用而已。
Redis的第二个优点是单线程模型,所谓单线程模型就是每一个请求都会有一个全新的线程来进行处理,这一点类似于Struts2,每一个请求都会有一个新的线程来进行处理。这样做的好处就是避免了线程频繁切换带来的系统开销,同时也避免了让人头疼的多线程问题。
Redis的第三个优点就是使用了非阻塞I/O (NIO),不在网络上浪费时间,进一步提高了效率。
Redis的第四个优点就是支持多种的数据类型,并且每一种数据类型都提供了丰富的操作命令,适用于很多特殊的场景,并且支持自定义命令创建个性化的操作命令。
redis服务器宕机,内存数据是会丢失了,为了保证数据不丢失需要对数据做备份,所备份就是持久化,Redis的持久化即将内存中的数据同步到硬盘,主要包括两种方式RDB、AOF。
RDB持久化机制,(默认使用):做当前内存数据的全本快照,
将内存中的数据以快照的方式写入到二进制文件dump.rdb,
在redis.conf中可以设置发起快照保存的条件。在指定的时间内如果有超过指定数量的key被修改,则会发起快照保存。
这种方式在数据的实时性上不高,在突然断电的情况下,可能会出现部分数据的丢失,即最后一次快照之后在内存中发生修改的数据。
简单来说:RDB就是将redis上的所有数据做个备份,存储的是二进制的数据。
AOF持久化机制,(默认是关闭):
AOF是将Redis内存数据库中更改的数据都记录到指定的文件appendonly.aof。在redis.conf中可以进行写磁盘的相关设置。
在突然断电的情况下,由于在appendonly.aof中保存了最后一次写磁盘之后redis内存发生数据修改的指令,所以在这个Redis重启后,基本不会发生数据丢失,比RDB具有更好的数据安全性。
appendfsync always 接收到更改数据的命令,立即将其记录到appendonly.aof中,能保证数据持久化,数据完全不丢失,但效率相对最低。
appendfsync everysec 每秒钟将redis内存数据修改的命令记录到appendonly.aof中,在性能和持久化上做了折中。因频繁执行磁盘操作,在仅存在单个Master执行写操作时,效率可能存在问题。但在多个Master执行写操作的Redis集群中,效率会提升。
appendfsync no 依赖于操作系统,因不会频繁执行磁盘操作而性能最好,但redis内存数据修改持久化没有保证,无法保证数据可靠性
如何开启AOF持久化:
将redis.conf文件中 appendonly 改成 yes ,自动创建appendonly.aof,该文件存储的客户端执行过增删改操作的命令
redis数据库存储数据使用的key-value,键值对方式存储
key是string类型 value的数据结构支持5个string、set、sorted_set、list、hash
可以实现原子性的自增(数据安全)
这是最基本的类型了,没啥可说的,就是普通的set和get,做简单的k-v缓存
这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以就操作hash里的某个字段。
key=150
value={
“id”: 150,
“name”: “zhangsan”,
“age”: 20
}
hash类的数据结构,主要是用来存放一些对象,把一些简单的对象给缓存起来,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值
value={
“id”: 150,
“name”: “zhangsan”,
“age”: 21
}
有序列表,这个是可以玩儿出很多花样的
微博,某个大v的粉丝,就可以以list的格式放在redis里去缓存
key=某大v
value=[zhangsan, lisi, wangwu]
key=书名
value=[评论1, 评论2, 评论3]
比如可以通过list存储一些列表型的数据结构,类似粉丝列表了、文章的评论列表了之类的东西
比如可以通过lrange命令,就是从某个元素开始读取多少个元素,可以基于list实现分页查询,这个很棒的一个功能,基于redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走
比如可以搞个简单的消息队列,从list头怼进去,从list尾巴那里弄出来
无序集合,自动去重
直接基于set将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于jvm内存里的HashSet进行去重,但是如果你的某个系统部署在多台机器上呢?
得基于redis进行全局的set去重
可以基于set玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧
把两个大v的粉丝都放在两个set中,对两个set做交集
排序的set,去重但是可以排序,写进去的时候给一个分数,自动根据分数排序,这个可以玩儿很多的花样,最大的特点是有个分数可以自定义排序规则
比如说你要是想根据时间对数据排序,那么可以写入进去的时候用某个时间作为分数,人家自动给你按照时间排序
排行榜:将每个用户以及其对应的什么分数写入进去,zadd board score username,接着zrevrange board 0 99,就可以获取排名前100的用户;zrank board username,可以看到用户在排行榜里的排名
总结:优先掌握 String 即可
场景:
手机验证码登录 手机验证码注册
验证码后台生成 Redis
验证码
key = 手机号
value = 验证码
设置3分钟过期
Redis中可以设置数据的存活时间
命令
expire key 存活时间的秒
ttl key 查看key对应的数据的存活时间.
pexipre key 存活时间的毫秒
pttl key 查看key对应的数据的存活时间,毫秒单位
expire key 存活时间的秒
失效的原理
定期随机删除+惰性删除
key
1 1分钟
2 1分钟
3 1分钟
4 1分钟
5
6
redis 每过100ms 随机抽取一定数量的设置了失效时间的key 将过期的删除
有些key过期了 每次都没有随机到 就一直删不掉 怎么办?
惰性删除 get key 的时候 先判断 key是否过期 如果过期 返回数据为空
定期随机删除 例如100ms
查询的时候 先检查key
内存淘汰机制
如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:
redis 10个key,现在已经满了,redis需要删除掉5个key
1个key,最近1分钟被查询了100次
1个key,最近10分钟被查询了50次
1个key,最近1个小时被查询了1次
LRU算法 扩展视野
为什么要做缓存
缓存的选择(二级缓存)
二级缓存的基本概念
二级缓存的缺点
使用Redis集成二级缓存的步骤
缓存的作用
MyBatis二级缓存机制(开启)
机制:
Java本地缓存空间.(jar)
mybaits事务提交后操作缓存
Mybatis根据Mapper文件的namespace划分多个缓存空间.
mybatis会将查询语句执行结果,缓存在 sql所在mapper文件对应的namespace对应的缓存空间中.
会将执行的查询sql(对sql处理后产生的对象)作为key.
MyBatis执行DML,在事务提交之后,默认清空当前sql所在的mapper文件对应的namespace对应的缓存空间中
A Mapper User表 脏读
B Mapper User表 删除 缓存清空
缓存空间融合
MyBatis缓存实现原理(源码)
org.apache.ibatis.cache.impl.PerpetualCache.class
根据namepace划分缓存空间(id)
MyBatis二级缓存本质是一个Map结构
key :和执行的sql先关
value:查询结果相关
存放数据的功能: select语句(key)----查询结果(value)
获得数据的功能: 根据key
清空缓存的功能: clear
MyBatis管理每个缓存,使用Map管理 key:id(namespace) value:PerpetualCache
MyBatis缓存的问题?(缓存数据量不能太多)
解决办法:
将Mybatis的二级缓存空间转移到Redis数据库中
Mybatis二级缓存空间划分
Redis缓存空间的划分设计
核心:
方案:
自定义Redis缓存实现
自定义缓存实现类
自定义MyBatis二级缓存
根据namepace划分缓存空间(id)
MyBatis二级缓存本质是一个Map结构
key :和执行的sql先关
value:查询结果相关
存放数据的功能: select语句(key)----查询结果(value)
获得数据的功能: 根据key
清空缓存的功能: clear
使用自定义的缓存
为何要实现session共享?
nginx负载均衡,希望兼顾权重的按照硬件性能分配访问压力的优势,又想保证多个tomcat使用同一个session应该怎么解决?
解决方案
使用redis管理负载均衡中多个tomcat的session.(Redis共享session)
如何实现Redis管理Session
配置步骤
将jar拷贝tomcat中lib
1. tomcat 配置文件context.xml
<Manager className="session管理工具全类名" host="redis的ip地址" port="端口" maxInactiveInterval="session存活时间 秒 1800" 秒 />
<Valve className="RedisSessionHandlerValve在tomcat中使用session管理工具"/>
重启两个tomcat
缓存穿透
缓存雪崩
User
根据主键查询
key
1
2
3
4
5
6
7
8
9
1W -id 缓存中都没有
2000个查询 每秒 可以认为是安全的
缓存击穿 大量不存在的key攻击
只需要极少的空间就可以判断一个元素是不是在一个集合之内,这正好是我们所需要的场景啊:判断key是否存在
解决方案
空值缓存 (非恶意攻击)
key value
-1 null
布隆过滤器
可以判断key是否在数据库中存在
缺点:可能会判断出错 概率不高 但是会
微博
key = 鹿晗微博1 value = 相关信息 评论 追评 点赞 等
key = 鹿晗微博2 value = 相关信息 评论 追评 点赞 等
key = 鹿晗微博3 value = 相关信息 评论 追评 点赞 等
key = 鹿晗微博4 value = 相关信息 评论 追评 点赞 等
key = 鹿晗微博5 value = 相关信息 评论 追评 点赞 等
key = 吃瓜群众1
key = 吃瓜群众2
key = 吃瓜群众3
key = 吃瓜群众4
key = 吃瓜群众5
上千万key 这些key一定会设置失效 失效时间设置的不合理 同一时间大量key过期了(500W) 如果发生在平时 无所谓
不巧的是 上热搜了 突然间 大量的用户来访问 相关的信息
大量key同一时间失效 将导致不存在的数据每次请求都要到存储层去查询,失去了缓存保护后端存储的意义
大量流量 且数据失效 导致不存在的数据每次请求都要到存储层去查询 一模一样的SQL 数据库崩溃
分布式的锁,谁获得了这把锁,谁就可以访问数据库
大型项目中
如果说用户查不到数据 降级服务
一种逻辑处理
分布式锁本质上要实现的目标就是在 Redis 里面占一个“茅坑”,当别的进程也要来占时,发现已经有人蹲在那里了,就只好放弃或者稍后再试。
占坑一般是使用 setnx(set if not exists) 指令,只允许被一个客户端占坑。先来先占, 用完了,再调用 del 指令释放茅坑。
// 这里的冒号:就是一个普通的字符,没特别含义,它可以是任意其它字符,不要误解
> setnx lock:codehole true
OK
... do something critical ...
> del lock:codehole
(integer) 1
但是有个问题,如果逻辑执行到中间出现异常了,可能会导致 del 指令没有被调用,这样就会陷入死锁,锁永远得不到释放。
于是我们在拿到锁之后,再给锁加上一个过期时间,比如 5s,这样即使中间出现异常也可以保证 5 秒之后锁会自动释放。
> setnx lock:codehole true
OK
> expire lock:codehole 5
... do something critical ...
> del lock:codehole
(integer) 1
但是以上逻辑还有问题。如果在 setnx 和 expire 之间服务器进程突然挂掉了,可能是因为机器掉电或者是被人为杀掉的,就会导致 expire 得不到执行,也会造成死锁。
这种问题的根源就在于 setnx 和 expire 是两条指令而不是原子指令。如果这两条指令可以一起执行就不会出现问题。也许你会想到用 Redis 事务来解决。但是这里不行,因为 expire 是依赖于 setnx 的执行结果的,如果 setnx 没抢到锁,expire 是不应该执行的。事务里没有 if-else 分支逻辑,事务的特点是一口气执行,要么全部执行要么一个都不执行。
为了解决这个疑难,Redis 开源社区涌现了一堆分布式锁的 library,专门用来解决这个问题。实现方法极为复杂,小白用户一般要费很大的精力才可以搞懂。如果你需要使用分布式锁,意味着你不能仅仅使用 Jedis 或者 redis-py 就行了,还得引入分布式锁的 library。
为了治理这个乱象,Redis 2.8 版本中作者加入了 set 指令的扩展参数,使得 setnx 和 expire 指令可以一起执行,彻底解决了分布式锁的乱象。从此以后所有的第三方分布式锁 library 可以休息了。
> set lock:codehole true ex 5 nx
OK
... do something critical ...
> del lock:codehole
上面这个指令就是 setnx 和 expire 组合在一起的原子指令,它就是分布式锁的奥义所在。