Redis一些基础知识

本文详细介绍了Redis的高性能原因,包括基于内存操作、简单数据结构、单线程执行和非阻塞I/O。同时,讨论了Redis的持久化机制,如RDB和AOF的优缺点。此外,还涵盖了Redis的过期策略和内存淘汰策略,以及如何应对缓存雪崩、穿透和击穿问题。最后,提出了分布式锁的实现方法和确保缓存与数据库数据一致性的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis 为什么这么快

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在內存中。
  2. 数据结构简单,对数据的操作也简单。
  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
  4. 使用多路I/O复用模型,非阻塞IO;

ps:6.0之后加入多线程,注意:
Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。

数据类型

数据类型存储的值应用场景
String字符串做简单的键值对缓存
List列表存储一些列表型的数据结构,类似文章评论等
Set无序集合交集并集差集的操作
Hash包含键值对的无序散列表结构化的数据,比如一个对象
Zset有序集合去重但可以排序,如获取排名前几的用户

持久化

Redis有两种持久化RDB(默认)AOF

RDB

RDB是 Redis默认的持久化方式。按照一定的时间将内存的数据以快照的开式保存到硬盘中,对应产生的数据文件为 dump.rabe通过配置文件中的save参数来定义快照的周期。
优点:

  1. 只有一个文件 dump. rdb,方便持久化
  2. 容灾性好,一个文件可以保存到安全的磁盘。
  3. 性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis的高性能
  4. 相对于数据集大时,比AOF的启动效率更高。

缺点:
5. 数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间 redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)

AOF

AOF持久化(即 Append Only File持久化),则是将Redis执行的每次写命令记录到单独的日志文件中,当重启Redis会重新将持久化的日志中文件恢复数据。
优点:

  1. 数据安全,AOF持久化可以配置 appendfsync属性,有 always,每进行一次命令操作就记录到aof文件中一次。
  2. 通过 append模式写文件,即使中途服务器宕机,可以通过 redis-check-aof工具解决数据一致性问题。
  3. AOF机制的 rewrite模式。AOF文件没被 rewrite之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(flushall)

缺点:

  1. AOF文件比RDB文件大,且恢复速度慢。
  2. 数据集大的时候,比rdb启动效率低。

过期策略

过期策略通常有三种

  • 定期过期
    毎个设置过期时间的key都需要创建一个定时器,到过期时间就会立即凊除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期
    只有当访向key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能岀现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期抽查
    每隔一定的时间,会扫描一定数量的数据库的 expires字典中一定数量的key,并清除其中已过期的keγ。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和毎次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到优的平衡效果。
    redis 目前同时使用了前两种方案

内存淘汰策略

Redis的内存淘汰策略是指在 Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
全局的键空间选择性移除

  • eviction:当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)。
  • allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

缓存异常

缓存雪崩

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  1. 缓存数据的过期时间设置随机,防止同时间大量数据过期现象发生。
  2. 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
  3. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。

缓存穿透

緩存穿透是指緩存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  1. 接口层增加校验,如用户鉴权校验,i做基础校验,id<=0的直接拦截;
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将 key-value对写为key-nu,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  3. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap中,一个定不存在的数据会被这个 bitmap拦截掉,从而避免了对底层存储系统的查询压力。

缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读緩存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:

  1. 设置热点数据(长时间)
  2. 加互斥锁

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决方案:

  • 直接写个缓存刷新页面/接口,上线时手动触发一下
  • 数据量不大,可以在项目启动时自动加载
  • 定时刷新缓存

redis 分布式锁

/**
* 尝试获取分布式锁
*
* @param lockKey 锁
* @param expireTime 超期时间 300(秒)
* @return 是否获取成功
*/
public boolean lock(String lockKey, long expireTime) {
String uuid = UUID.randomUUID().toString();
String value = jedisCmd.set(lockKey, uuid, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (value != null && value.equals("OK")) {
threadLocal.set(uuid);
return true;
}
return false;
}

/**
* 释放分布式锁
*git
* @param lockKey 锁
* @return 是否释放成功
*/
public boolean unLock(String lockKey) {
String key = jedisCmd.get(lockKey);
//可能会存在锁失效的时候
if (key == null) {
return true;
}
//保持原子性 (Get —>判断 -> del )操作
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end ";
Object object = jedisCmd.eval(script, Collections.singletonList(lockKey), Collections.singletonList(threadLocal.get()));
if (RELEASE_SUCCESS.equals(object)) {
return true;
}
return false;
}

注意点:
lock:

  1. 这里使用的不是setnx,需要使用set增加过期时间
  2. 对于lock来说如果说没有获取到锁,有以下几种情况:
    2.1 自旋来循环获取锁(对于并发量较大的请求不适用)
    2.2 直接丢弃

unlock:

  1. 注意这里需要原子性操作(使用lua脚本):
    (Get —>判断 -> del )
    if redis.call(‘get’,KEYS[1]) == ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end

如何保证缓存与数据库数据一致?

读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后再删除缓存(这里不是更新缓存,因为有可能缓存值是计算得来的,这里不用再次计算,只有查询是才是,且如果更新频繁,还会涉及内存的多次修改)。

最初级的缓存不一致问题及解决方案:
先删除缓存,再修改数据库。如果数据库修改失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。因为读的时候缓存没有,则读数据库中旧数据,然后更新到缓存中。

高并发下会出问题:
并发量大时,可能导致更新缓存时,另一个请求已经将新值写入数据库,那么可能造成数据库与缓存数据不一致。
解决方法:
转载

跳跃表

转载

### Redis 基础知识入门教程 Redis(Remote Dictionary Server)是一个使用 C 语言编写的高性能非关系型键值对数据库[^1]。与传统的关系型数据库不同,Redis 的数据存储在内存中,因此具有极高的读写速度,并广泛应用于缓存场景。此外,Redis 提供了多种持久化机制,能够将内存中的数据定期保存到磁盘中,确保数据的安全性[^3]。 #### Redis 的核心特性 - **高性能**:由于 Redis 的数据存储在内存中,其性能可以达到每秒 110000 次读取和 81000 次写入操作。 - **丰富的数据结构**:除了基本的字符串类型外,Redis 还支持列表、集合、哈希表、有序集合等多种数据结构[^4]。 - **持久化**:Redis 提供了 RDB(快照)和 AOF(追加日志)两种持久化方式,以保证数据在服务重启后不会丢失。 - **事务支持**:Redis 支持简单的事务功能,允许多个命令在一个原子操作中执行。 - **主从复制**:Redis 支持主从复制,可以通过配置多个从节点来提升系统的可用性和扩展性。 - **哨兵和集群**:Redis 提供了哨兵模式用于高可用部署,以及集群模式用于大规模分布式存储[^3]。 #### Redis 的基本操作 以下是一个简单的 Redis 操作示例,演示如何设置和获取一个键值对: ```bash 127.0.0.1:6379> SET runoob "菜鸟教程" OK 127.0.0.1:6379> GET runoob "菜鸟教程" ``` 上述代码展示了如何通过 `SET` 命令设置一个键值对,以及通过 `GET` 命令获取对应的值[^2]。 #### 安装 Redis 在 Windows 系统上安装 Redis 可以通过以下步骤完成: 1. 下载适用于 Windows 的 Redis 版本(通常为社区提供的移植版本)。 2. 解压文件并运行 Redis 服务器和客户端程序。 3. 使用命令行工具连接到 Redis 并进行测试。 对于 Linux 或 macOS 系统,可以直接通过包管理器安装 Redis,例如: ```bash sudo apt-get install redis-server # Ubuntu/Debian brew install redis # macOS ``` #### Redis 的应用场景 Redis 因其高性能和灵活性,被广泛应用于以下场景: - **缓存**:利用 Redis 的内存存储特性,作为应用系统的缓存层。 - **消息队列**:Redis 的列表数据结构可以实现高效的队列操作。 - **计数器**:通过原子操作实现高并发环境下的计数功能。 - **实时分析**:处理实时数据流,例如用户行为分析或系统监控。 ### 注意事项 在学习 Redis 的过程中,建议先掌握其基本命令和数据结构,然后逐步了解持久化、事务、主从复制等高级功能。同时,可以根据实际需求选择合适的部署模式(单机、主从、集群等)[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值