Redis小记
# 在 redis 中,存储序列化后的对象,为什么 String 相比较于 Hash 存储更节省内存?
在内存存储方面,String 相比于 Hash 存储更节省内存的原因是因为 String 使用了字符数组(char[])来存储字符串的内容,而 Hash 存储通常是使用一个哈希表来存储键值对。
- String 使用字符数组存储:在 Java 中,String 对象使用字符数组来存储字符串的内容,每个字符占用两个字节(16 位)。对于较短的字符串,字符数组可以直接存储整个字符串内容,没有额外的开销。
- Hash 存储使用哈希表:哈希表是一种数据结构,它由一系列的桶(bucket)组成,每个桶中存储一个键值对。对于较小的哈希表,每个桶可能只存储一个键值对,这样会有一定的空间开销。而对于较大的哈希表,可能会有较多的桶未被使用,也会造成一定的空间浪费。
因此,对于存储较短的字符串或者只包含单个字符的字符串,使用 String 存储相对更节省内存。而对于存储大量的键值对,使用 Hash 存储可能更合适,因为它可以支持快速的查找和插入操作。
# 利用 SETNX key value 命令可以实现一个最简易的分布式锁(存在一些缺陷,通常不建议这样实现分布式锁)。为什么?
- 还应该加个过期时间,比如
SET lock_key unique_value NX PX 10000
- 使用更成熟、稳定的分布式锁算法,比如基于 Redlock(红锁)或者 Redisson 等开源分布式锁实现库。
红锁算法要求满足两个条件:如果有超过半数的 Redis 节点成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功
# 三种缓存读写(更新)策略
# 旁路缓存策略
写:
- 先更新 db
- 然后删除 cache
读:
- 先从 cache 中读取数据,命中就返回
- cache 中读取不到的话,就从 db 中读取数据返回,然后(db)再把数据写入 cache 中
问题:
1、在写数据的过程中,可以先删除 cache,后更新 db 么?
答:不可以,因为这样会导致数据库和缓存数据不一致的问题。
举例:请求 1 先写数据 A,请求 2 随后读数据 A 的情况。
假设请求 1 先把 cache 中的 A 数据删除 -> 请求 2 从 db 中读取数据并写入 cache 中 -> 请求 1 再把 db 中的 A 数据更新,此时就会产生数据库和缓存数据不一致的问题。
db 的是新数据,cache 的是旧数据。
2、在写数据的过程中,先更新 db,后删除 cache 就没有问题了吗?
答:可能会有问题,但出现的概率非常小,因为【缓存】的写入速度比【数据库】的写入速度快很多。
举例:
请求 A 更新完数据库,但还未删除缓存,此时请求 B 命中了缓存并返回,就导致了数据不一致。
出现的概率低:是因为缓存的写入非常快,中间的时间差非常短,通常只有几毫秒或者几十毫秒。
缺陷:
1、首次请求数据一定不在 cache 的问题
解决方法:将热点数据提前放入 cache 中。
2、写操作比较频繁的话导致 cache 中的数据会被频繁被删除,会影响缓存命中率
解决方法:
- 更新数据的时候同样更新缓存,只是在更新缓存前需要先加一个分布式锁,从而保证线程安全。(强一致场景)
- 给缓存加一个较短的过期时间。(可以短暂允许数据不一致场景)
为什么会线程不安全?
因为在多线程或分布式环境下,对数据的并发更新可能会导致线程不安全的情况。当多个线程同时尝试更新缓存的数据时,如果没有进行同步控制,就会产生以下问题:
- 竞态条件:多个线程同时读取缓存数据,并在此基础上进行更新,但由于读取和写入操作不是原子性的,可能导致数据的不一致性。
- 脏数据:在并发情况下,多个线程同时更新缓存,其中一个线程的更新可能会覆盖其他线程的更新,导致数据出现错误。
- 缓存不一致:由于缓存的失效机制或其他原因,缓存中的数据可能落后于数据库中的数据,这样读取到的数据就会是旧数据。
# 读写穿透策略
原则:应用程序只和缓存交互,不再和数据库交互。
比较少见,因为常用的分布式缓存组件都不提供【写入数据库】和【自动加载数据库中的数据】的功能。
写:
- 先查 cache,如果数据不存在,(缓存组件)直接更新 db,然后返回。
- 如果数据存在,则先更新 cache,然后缓存组件同步更新 db。
读:
- 从 cache 中读取数据,读取到就返回。
- 读取不到,就由缓存组件先从数据库查数据并写入缓存组件,最后返回响应。
# 写回策略(异步写入)
读写的情况
与【读写穿透策略】相似,都是由 cache 服务(组件)来负责 cache 和 db 的读写。
不同点
原则:只更新缓存,不直接更新 db,而是改为异步批量的方式来更新 db。
Write Back(写回)策略:在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的,然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行。
适合【写多】的场景。(比如浏览量,点赞量)
# 三者谁的线程安全度更高?
读写穿透策略。
Write Through 策略在写操作时,会先更新数据库,然后再更新缓存。这样做的好处是确保了数据库和缓存的数据始终保持一致,避免了数据不一致的问题。在并发情况下,当多个线程同时写入数据时,由于先更新数据库再更新缓存,可以保证不会出现数据不一致的情况。
另一方面,Write Back(写回)策略在写操作时,只更新缓存而不更新数据库,然后通过批量异步更新的方式来更新数据库。虽然 Write Back 策略在高并发场景下可以提高写入性能,但是由于在更新缓存时不更新数据库,可能会导致数据库和缓存之间的数据不一致。这种情况下,如果多个线程同时写入数据,可能会出现数据覆盖或者丢失的情况,从而降低了线程安全度。
# Redis 符合 ACID 吗?
Redis 不完全符合 ACID(原子性、一致性、隔离性、持久性)这一传统的数据库事务特性。
下面是 Redis 在 ACID 特性方面的情况:
- 原子性(Atomicity):Redis 支持多个操作的原子性,这些操作可以一次性执行。但是,Redis 并不是严格意义上的 ACID 原子性,因为 Redis 命令的执行不会在单个操作内部细分。例如,MSET 命令在一次调用中设置多个键值对,但如果其中某个键值设置失败,已经设置成功的键值对不会被回滚。Redis 还提供了 MULTI/EXEC/WATCH 命令来支持事务,但 Redis 的事务不是严格的 ACID 事务。
- 一致性(Consistency):Redis 在某些情况下不会提供严格的一致性。例如,当多个客户端同时设置相同的键值对时,最后一个客户端设置的键值会被保留,其他客户端的设置会被覆盖。这种情况下可能会导致数据的覆盖,从而导致数据的不一致性。
- 隔离性(Isolation):Redis 是单线程的,它使用事件循环来处理客户端请求。由于单线程的特性,Redis 不需要考虑传统数据库中的并发控制和隔离性问题。然而,在某些情况下,多个客户端的操作可能会出现竞争条件,影响数据的隔离性。
- 持久性(Durability):Redis 默认情况下将数据存储在内存中,因此不满足持久性的要求。但 Redis 提供了持久化选项,可以将内存中的数据定期写入磁盘,从而实现一定程度的持久性。Redis 的持久化有两种方式:RDB 持久化和 AOF 持久化。RDB 持久化在指定的时间间隔内将数据集快照写入磁盘,AOF 持久化则记录每个写操作的日志,可以保证数据的持久性。
总之,Redis 的设计目标是追求高性能和低延迟,因此在一些 ACID 特性上做了妥协。如果应用场景需要严格的 ACID 事务特性,传统的关系型数据库可能更适合。
# Jedis 和 redis 是什么关系
Jedis 是一个 Java 客户端库,用于与 Redis 数据库进行交互。它提供了一组丰富的方法和功能,用于连接、执行命令和操作 Redis 数据库。
Redis 是一个开源的高性能键值存储数据库,支持持久化、数据类型丰富、分布式和高可用性等特性。它以内存中的数据结构存储数据,并通过网络提供对数据的访问。
Jedis 是 Redis 客户端库之一,专门为 Java 语言设计和实现。它通过与 Redis 服务器建立连接,发送命令并接收响应来实现与 Redis 的交互。Jedis 提供了简单、直观的 API,使 Java 开发人员可以方便地使用 Redis 数据库。它支持与 Redis 的各种操作,包括字符串、哈希、列表、集合、有序集合等数据结构的读写操作。
在使用 Jedis 时,你需要将 Jedis 客户端库添加到你的项目中,并在代码中创建 Jedis 对象来与 Redis 进行通信。通过 Jedis,你可以执行各种 Redis 命令,如 SET、GET、HSET、HGET、LPUSH、LRANGE 等。
除了 Jedis,还有其他编程语言和平台的 Redis 客户端库,如 StackExchange.Redis(C#)、redis-py(Python)、redis-rb(Ruby)等。这些客户端库都是用于在不同编程语言中与 Redis 进行交互的工具。
因此,Jedis 是 Redis 的 Java 客户端库之一,用于在 Java 应用程序中使用 Redis 数据库。它提供了方便的方法和功能,使 Java 开发人员能够使用 Redis 的各种功能和数据结构。
# 如果 redis 数据库宕机了咋办
如果 Redis 数据库宕机了,你可以采取以下步骤来应对这种情况:
- 检查 Redis 服务器状态:首先,确认 Redis 服务器是否真的宕机了。你可以尝试连接到 Redis 服务器并执行一些基本操作来验证其状态。
- 恢复 Redis 服务器:如果确认 Redis 服务器宕机了,你需要采取相应的措施来恢复它。具体的步骤可能因你的环境和配置而有所不同。通常,可以尝试重启 Redis 服务器,或者检查日志文件以了解可能的错误原因。
- 备份和恢复数据:如果 Redis 数据库的数据没有进行持久化保存(如配置了 RDB 持久化或 AOF 持久化),那么在 Redis 宕机后,数据可能会丢失。如果你有 Redis 数据库的备份,可以尝试使用备份数据来进行恢复。否则,你需要考虑重新构建丢失的数据。
- 容错和高可用性:为了避免单点故障,你可以在 Redis 架构中引入容错和高可用性机制。例如,使用 Redis Sentinel 或 Redis Cluster 来实现故障转移和自动故障恢复。
- 监控和预警:建立监控系统来实时监测 Redis 服务器的状态。这样,你可以及时收到警报并采取措施来应对宕机或其他问题。