坚持学习,坚持写博客,努力向大佬前进! QAQ ………
redis 概述
在web应用发展初期,web站点访问量不高,与用户交予较少,因此关系型数据库受到广泛的应用。但是随着互联网的发展,web站点的访问量提升,使用关系型数据库(基于磁盘的读写操作)在性能上出现了I/O上的瓶颈。在一瞬间大量请求同时命中数据库时,复杂的I/O操作使得数据库很难承受这样高速读写的压力,此时必须有一个更高效的中间件,来缓解数据库的压力。
redis 是什么
为解决数据库在高并发情况下的压力,非关系型数据(NoSQL)也就进入我们的视野。Redis是现在最受欢迎的NoSQL数据库之一
redis 的自有数据结构
- String
- 它是一个二进制安全的字符串,不仅能够存储字符串、还能存储图片、视频等。单个最大512M
- Hash
- 该类型是由field和关联的value组成的map。其中,field和value都是字符串类型的。
- List
- 插入顺序排序的字符串元素集合,基于双向链表
- Set
- 无序集合,数据唯一,可用于去重
- Zset
- 有序集合类型,每个元素都会关联一个double类型的分数权值,通过这个权值来为集合中的成员进行从小到大的排序。
更多请查看redis中文网站 - 数据类型// keyName 用作主键id // orderNum1 用作就是分数id 用于排序 zadd keyName orderNum1 v1 orderNum1 v2 orderNum1 v3
- 有序集合类型,每个元素都会关联一个double类型的分数权值,通过这个权值来为集合中的成员进行从小到大的排序。
redis 的使用场景
- 缓存系统热点数据
- 如:系统中频繁被请求的静态数据
- 计数器
- 如:基于redis的主键生成策略、分布式计数。
- 排行榜,消息队列
- 如:计算日排行、月排行等,可以通过zset类型添加。
- 消息队列(订阅发布模式):
发布: publish channel message 例如:publish msg1 发送内容 订阅: subscribe channel [.....] 例如:subscribe msg1
- 分布式锁,共享session
- 分布式锁:通过setnx实现
setnx(key,value,time,'时间类型【时/分/秒/毫秒/微秒等】')
- 分布式锁:通过setnx实现
redis 的持久化
Redis持久化分为RDB持久化和AOF持久化
RDB持久化
RDB 其实就是在指定时间间隔中将内存中的数据集快照写入磁盘。这是redis 默认开启的持久化方式。
RDB 持久化过程
当 Redis 需要保存 dump.rdb 文件时, 服务器执行以下操作:
- Redis 调用forks. 同时拥有父进程和子进程。
- 子进程将数据集写入到一个临时 RDB 文件中。
- 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。
RDB的优点
- rdb 是一个非常紧凑的单一文件,,非常适用于数据集的备份(比如每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据),这样即使出现问题也可以根据某时刻的备份恢复到之前的版本。
- 由于rdb生成的文件时单一的,这也很方便用于同步数据(比如,集群情况下,某台redis主机宕机,可以通过rdb文件恢复)
- rdb 同步时是从父线程fork一个子线程来完成这个操作,这意味着父线程不需要做其他额外的I/O操作,最大的发挥redis的性能。
- 和AOF相比,在大数据集的保存上,RDB 有明显的性能优势。
*RDB的缺点
- 由于RDB 保存的数据是某个时刻的快照,这意味着在保存快照操作开始到下次保存快照之间新增的数据集,是有可能丢失的,比如在下次保存之前意外断电或宕机。
- 由于RDB是保存某一时刻的全量数据,即使是从父线程 fork 一个子线程,也难免消耗性能。
AOF持久化
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.
如何开启AOF
在配置文件中打开AOF方式
appendonly yes
持久化策略
- 每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全
- 每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
- 从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。
- 推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。
*AOF持久化过程
- Redis 执行 fork() ,同时拥有父进程和子进程。
- 子进程开始将新 AOF 文件的内容写入到临时文件。
- 对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
- 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
- Redis 原子地用新文件替换旧文件,之后所有命令都会直接追加到新 AOF 文件的末尾。
AOF的优点
- AOF 文件是一个只进行追加的日志文件,不需要写入seek,,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
- Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写,整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
- AOF 文件有序地保存了对数据库执行的所有写入操作
- AOF 提供了多种同步策略:无fsync,每秒fsync,每次写的时候fsync。默认使用每秒同步。这样即使出现异常(如断电、宕机),也只丢失一秒的数据。
- 通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
*AOF的缺点
- 相同的数据集,AOF文件的体积要比RDB更大。
- 即使选择每秒同步,AOF的速度还是比RDB慢
在RDB和AOF同时开启的情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据
redis 在sprinBoot中的使用
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置
spring.redis.cluster.max-redirects=3
spring.redis.cluster.nodes=127.0.0.1:6590,127.0.0.1:6591
spring.redis.password=Pass_123
spring.redis.timeout=5000
代码中的使用
@Resource
private StringRedisTemplate stringRedisTemplate;
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
// 分布式锁
ops.setIfAbsent(msgId,msgId,1800L,TimeUnit.SECONDS)
redis 常见面试题
- redis为什么可以快速执行?
- 绝大多数请求都是基于内存操作的
- 采用单线程,避免不必要的上下文切换,不需要各种锁的性能消耗。
- 非阻塞IO-IO多路复用(多网络连接,复用一个线程)
- Redis采用自己实现的事件分离器
- 缓存雪崩
- 缓存雪崩是指在某台缓存主机宕机时,所有请求直接访问数据库,导致数据库宕机的请况。
- 解决方案:
- 确保redis的高可用:多台 redis 主机同时工作,互为主备,及搭建 redis 集群。
- 限流降级:只允许一个线程同时访问数据库(分布式锁),当一个线程访问时,其他线程等待。
- 数据预热:在正式上线之前,先对可能大量访问的数据进行预先访问,使其存在缓存中,同时设置超时时间,使缓存失效时间尽量分布均匀
- 缓存穿透
- 是直接访问一个 redis 中不存在的key,缓存没有命中,直接访问数据库。在这种请求下大量访问类似的key,也会出现数据库宕机的请况。
- 解决方案:
- 布隆过滤器:将数据库可以查询的key 以hash的类型缓存到 bitmap中,当一个一定不存在的key访问时,会被直接拦截
- 缓存空对象:如果key值在缓存和数据库都没有命中的情况下,直接缓存一个空对象(需要设置超时时间),下次访问这个key就不会命中数据库。
- 缓存击穿
- 指某个热点key,在高并发的情况下突然失效,并发直接请求数据库,导致数据库宕机的请求
- 解决方案:
- 互斥锁(mutex key):在出现热点key 失效时,通过redis的setnx(key,value,timeout)方法,实现单一线程进行读写操作。
```java//设置一个缓存(存在超时时间)表示,目前有一个线程正在读写操作 if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex);//热点key设置完成后,将之前key_mutex删除,防止下次击穿时,无法进行读取db } else { //这个时候代表同时期的线程进入等待,等待结束再次进行读取操作 sleep(50); get(key); //重试 }
- 互斥锁(mutex key):在出现热点key 失效时,通过redis的setnx(key,value,timeout)方法,实现单一线程进行读写操作。
- 永远不过期:
物理不过期:不设置超时时间,也就不存在过期问题。但是会出现数据不自动更新,无法保证一致性。
异步不过期:设置超时时间,同时添加异步线程,发现热点key快过期的时候,重新设置热点key,但是会出现异步线程更新key时,其他线程访问的还是老数据。
```
- 过期机制
- 在内存充足的情况下,redis 对已经过期的 key 进行清理
- 常见的过期机制有三种:
- 定时过期:设置对每个设置过期时间的 key 创建一个定时器,再key一到过期时间,就会立即删除。该策略对内存友好,但是对CPU消耗极大,影响redis的响应时间和吞吐量。
- 惰性过期:访问 key 的时候,采取校验是否过期,如果过期则删除。该策略对CPU友好,但是对内存极不友好。
- 定期过期:每隔一段时间,检查数据库 expires字典中的 key ,是否过期,过期则删除。
- redis采用惰性过期和定期过期,优化内存和CPU的性能,使其达到平衡。
- 淘汰机制
- 是指再redis 缓存内存不足时的,如何处理新入库的数据的解决方案。
- 淘汰机制分6种:
- noeviction:不删除策略,达到最大内存限制时,如果需要更多内存,直接返回错误信息。
- allkeys-lru:所有key通用,优先删除最近最少使用的(less recently used,LRU)key.
- allkeys-random:所有key通用,随机删除一部分key.
- volatile-random:只限于设置了expire的部分,删除一部分expire的key.
- volatile-ttl:只限于设置了expire的部分,优先删除剩余时间(time to live,TTL)短的key.
- volatile-lru:只限于设置过期时间的数据集,优先删除最近最少使用的数据淘汰
- 如何配置 淘汰策略:redis.conf 中maxmemory 设置最大内存,超出时,自动触发 maxmemory-policy 中配置的策略