文章目录
redis在互联网界使用如此之广泛,不仅让人赞叹不已,下面博主来和大家一块研究一下redis的雪崩、击穿、穿透等问题。
1. SpringBoot整合Redis方式
- 直接通过RedisTemplate来使用
@RequestMapping("/cachetest")
public void test() {
redisCacheTemplate.opsForValue().set("userkey", new User(1, "张三", 25));
User user = (User) redisCacheTemplate.opsForValue().get("userkey");
logger.info("当前获取的:{}", user.toString());
}
2.spring cache注解方式
//一般有三种操作方式:
//@Cacheable:在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;没有则调用方法并将方法返回值放进缓存。
//@CachePut:将方法的返回值放到缓存中。
//@CacheEvict:删除缓存中的数据。
@CachePut(value ="user", key = "#user.id")
public User save(User user) {
userMap.put(user.getId(), user);
logger.info("进入save方法,当前存储对象:{}", user.toString());
return user;
}
@CacheEvict(value="user", key = "#id")
public void delete(int id) {
userMap.remove(id);
logger.info("进入delete方法,删除成功");
}
@Cacheable(value = "user", key = "#id")
public User get(Integer id) {
logger.info("进入get方法,当前获取对象:{}", userMap.get(id)==null?null:userMap.get(id).toString());
return userMap.get(id);
}
Redis三种缓存问题
1. 缓存雪崩
假设现在我有一个电商系统,我一般会采取springboot的定时任务去刷新缓存,或者查不到之后去更新缓存。那么定时任务刷新就有一个问题。比如:如果首页所有Key的失效时间都是12小时,中午12点刷新的,我零点有个大型促活动大量用户涌入,假设每秒6000个请求,本来缓存可以抗住这些请求,但是缓存中所有Key都失效了,正好处在失效时间。此时6000个/秒的请求全部落在了DB,DB必然扛不住,好的情况是页面报错,500等,次之就是服务器宕机(在校开发的亲身经历),假如该服务器存在重要数据且尚未缓存,那么造成的损失便是不可估量的,网传之前有一家企业经历过此次事件,该企业损失了几千万
2. 缓存穿透
缓存穿透看上去和缓存穿透字面意思差不多,但其实这是两种概念
假设现在有一个不知名黑客A,不断发起标记有id=-1或者id特别大(即不存在的数据)的请求,我们数据库的id都是从1自增的,这样的不断攻击导致数据库压力很大,严重会击垮数据库,大量的请求也回落到DB上。
3. 缓存击穿
接下来是缓存击穿,这个概念其实跟缓存雪崩有点像,但是又有一点不一样,雪崩是因为大面积的缓存失效,打崩了DB
假设我的系统存在一个热点数据,大量并发集中访问这个点,但是当这个Key在失效的瞬间,持续的大并发直接落到了数据库上,就在这个Key的点上击穿了缓存。这就是缓存击穿
缓存问题解决方案:
1. 缓存雪崩
(1)不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。可以用随机数生成的方式设置缓存失效时间。可以自定义一个annotation(推荐),也可以setRedis(key, value, time+Math.random()*10000);
(2)通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2. 缓存穿透
(1)BloomFilter ,这是一个避免缓存穿透的一个利器
布隆过滤器的原理是,当一个元素被加入集合时,通过K个散列函数将这个元素映射成一个位数组中的K个点,把它们置为1。检索时,我们只要看看这些点是不是都是1就(大约)知道集合中有没有它了:如果这些点有任何一个0,则被检元素一定不在;如果都是1,则被检元素很可能在。这就是布隆过滤器的基本思想。
(2)在代码中加入判断,在前期写死(不建议,判断id等容易引起缓存穿透的点,给他堵死!)
(3)如果一个查询返回的数据为空,仍把这个空结果缓存。这样请求就落到了Redis上
3. 缓存击穿
(1)其实我们对于这种往往可以不设置过期时间的方式,简单粗暴
上线之后可以写个测试页面手动添加所有的想要缓存的数据,热加载以下。可以是一个空白页等等(个人之拙见)
例如我的个人博客网站其中有一段就是我热加载我的所有数据到Redis中,假如涉及到增删改等操作直接 @CacheEvict(value=“user”, key = “#id”)
然后在加载到Redis中
Redis数据类型
结构类型 | 结构存储方式 | 结构的读写方式 | 常见使用场景 |
---|---|---|---|
String | 可以是字符串、整数或者浮点数等基本类型 | 对整个字符串或者字符串的其中一部分执行操对 | 存储 json字符串,生成自增 id |
List | 链表上的每个节点都包含了一个字符串 | 从链表的两端推入或者弹出元素:根据偏移量对链表进行修剪。读取单个或者多个元素;根据值来查找或者移除元素 | 消息队列、最新内容 |
Hash | 包含键值对的无序散列表 | 添加、获取、移除单个键值对:获取所有键值对 | 对象类数据 |
Set | 据值来查找或者移除元素并且被包含的每个字符事都是独一无的、各不相同 | 添加、获取、移除单个元素;检查-一个元素是否存在于某个集合中;计算交集、并集、差集;从集合里卖弄随机获取元素 | 共同好友列表、附近的人 |
Zset | 字符串成员(member)与浮点数分值(score)之间的有序映射,元素的排列顺序由分值的大小决定 | 添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素 | 排行榜 |
1. string底层实现:
字符串对象底层数据结构实现为简单动态字符串(SDS)和直接存储,但其编码方式可以是int、raw或者embstr,区别在于内存结构的不同。
SDS结构图如下
struct sdshdr {
// 用于记录buf数组中使用的字节的数目
// 和SDS存储的字符串的长度相等
int len;
// 用于记录buf数组中没有使用的字节的数目
int free;
// 字节数组,用于储存字符串
char buf[]; //buf的大小等于len+free+1,其中多余的1个字节是用来存储’\0’的。
};
图示:
2. hash底层实现
redis的hash架构就是标准的hashtab的结构,通过挂链解决冲突问题。redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable。当hash对象可以同时满足一下两个条件时,哈希对象使用ziplist编码。
Redis是什么
REmote DIctionary Server(Redis) 是一个由Salvatore
Sanfilippo写的key-value存储系统。 Redis是一个开源的使用ANSI
C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets)
和 有序集合(sorted sets)等类型。
Redis为何这么快
- 完全基于内存
- redis的数据结构是经过单独设计的,对操作非常简单,类似于HashMap
- 采用单线程,避免了不必要的上下文竞争和切换
- 使用多路复用IO模型,非阻塞IO
Redis过期策略
-
定时删除
含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
优点:保证内存被尽快释放
缺点:
若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
没人用 -
惰性删除
含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存) -
定期删除
含义:每隔一段时间执行一次删除过期key操作
优点:
通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用–处理"定时删除"的缺点
定期删除过期key–处理"惰性删除"的缺点
缺点
在内存友好方面,不如"定时删除"
在CPU时间友好方面,不如"惰性删除"
持久化
- AOF,对修改的命令都保存到一个文件里
- RDB,内存快照,保存到一个dump的文件中(默认方式)
- AOF(append only file)持久化:
以独立日志的方式记录每次写命令, 重启时再重新执行AOF文件中的命令达到恢复数据的目的。AOF的主要作用 是解决了数据持久化的实时性,
缺点: 文件比较大
优点: 能够及时恢复数据 - RDB:
RDB持久化是把当前进程数据生成快照保存到硬盘的过程,即内存快照
RDB的优点:
·RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据 快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份, 并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复。
优点:Redis加载RDB恢复数据远远快于AOF的方式。
缺点:RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运 行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高。