关于java:Redis-常见问题总结

38次阅读

共计 15382 个字符,预计需要花费 39 分钟才能阅读完成。

1. 简略介绍一下 Redis 呗!

简略来说 Redis 就是一个应用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的,也就是它是内存数据库,所以读写速度十分快,因而 Redis 被广泛应用于缓存方向。

另外,Redis 除了做缓存之外,Redis 也常常用来做分布式锁,甚至是音讯队列。

Redis 提供了多种数据类型来反对不同的业务场景。Redis 还反对事务、长久化、Lua 脚本、多种集群计划。

2. 分布式缓存常见的技术选型计划有哪些?

分布式缓存的话,应用的比拟多的次要是 Memcached 和 Redis。不过,当初根本没有看过还有我的项目应用 Memcached 来做缓存,都是间接用 Redis

Memcached 是分布式缓存最开始衰亡的那会,比拟罕用的。起初,随着 Redis 的倒退,大家缓缓都转而应用更加弱小的 Redis 了。

分布式缓存次要解决的是单机缓存的容量受服务器限度并且无奈保留通用的信息。因为,本地缓存只在以后服务里无效,比方如果你部署了两个雷同的服务,他们两者之间的缓存数据是无奈独特的。

3. 说一下 Redis 和 Memcached 的区别和共同点

当初公司个别都是用 Redis 来实现缓存,而且 Redis 本身也越来越弱小了!不过,理解 Redis 和 Memcached 的区别和共同点,有助于咱们在做相应的技术选型的时候,可能做到有理有据!

共同点

  1. 都是基于内存的数据库,个别都用来当做缓存应用。
  2. 都有过期策略。
  3. 两者的性能都十分高。

区别

  1. Redis 反对更丰盛的数据类型(反对更简单的利用场景)。Redis 不仅仅反对简略的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只反对最简略的 k/v 数据类型。
  2. Redis 反对数据的长久化,能够将内存中的数据放弃在磁盘中,重启的时候能够再次加载进行应用, 而 Memecache 把数据全副存在内存之中。
  3. Redis 有劫难复原机制。 因为能够把缓存中的数据长久化到磁盘上。
  4. Redis 在服务器内存应用完之后,能够将不必的数据放到磁盘上。然而,Memcached 在服务器内存应用完之后,就会间接报异样。
  5. Memcached 没有原生的集群模式,须要依附客户端来实现往集群中分片写入数据;然而 Redis 目前是原生反对 cluster 模式的.
  6. Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 应用单线程的多路 IO 复用模型。(Redis 6.0 引入了多线程 IO)
  7. Redis 反对公布订阅模型、Lua 脚本、事务等性能,而 Memcached 不反对。并且,Redis 反对更多的编程语言。
  8. Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时应用了惰性删除与定期删除。

置信看了下面的比照之后,咱们曾经没有什么理由能够抉择应用 Memcached 来作为本人我的项目的分布式缓存了。

4. 缓存数据的解决流程是怎么的?

作为暖男一号,我给大家画了一个草图。

简略来说就是:

  1. 如果用户申请的数据在缓存中就间接返回。
  2. 缓存中不存在的话就看数据库中是否存在。
  3. 数据库中存在的话就更新缓存中的数据。
  4. 数据库中不存在的话就返回空数据。

5. 为什么要用 Redis/ 为什么要用缓存?

简略,来说应用缓存次要是为了晋升用户体验以及应答更多的用户。

上面咱们次要从“高性能”和“高并发”这两点来对待这个问题。

高性能

对照下面我画的图。咱们构想这样的场景:

如果用户第一次拜访数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。然而,如果说,用户拜访的数据属于高频数据并且不会常常扭转的话,那么咱们就能够很释怀地将该用户拜访的数据存在缓存中。

这样有什么益处呢? 那就是保障用户下一次再拜访这些数据的时候就能够间接从缓存中获取了。操作缓存就是间接操作内存,所以速度相当快。

不过,要放弃数据库和缓存中的数据的一致性。如果数据库中的对应数据扭转的之后,同步扭转缓存中相应的数据即可!

高并发:

个别像 MySQL 这类的数据库的 QPS 大略都在 1w 左右(4 核 8g),然而应用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的状况,redis 集群的话会更高)。

QPS(Query Per Second):服务器每秒能够执行的查问次数;

所以,间接操作缓存可能接受的数据库申请数量是远远大于间接拜访数据库的,所以咱们能够思考把数据库中的局部数据转移到缓存中去,这样用户的一部分申请会间接到缓存这里而不必通过数据库。进而,咱们也就进步的零碎整体的并发。

6. Redis 常见数据结构以及应用场景剖析

你能够本人本机装置 redis 或者通过 redis 官网提供的在线 redis 环境。

6.1. string

  1. 介绍 :string 数据结构是简略的 key-value 类型。尽管 Redis 是用 C 语言写的,然而 Redis 并没有应用 C 的字符串示意,而是本人构建了一种  简略动静字符串(simple dynamic string,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光能够保留文本数据还能够保留二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)), 除此之外,Redis 的 SDS API 是平安的,不会造成缓冲区溢出。
  2. 常用命令: set,get,strlen,exists,dect,incr,setex 等等。
  3. 利用场景:个别罕用在须要计数的场景,比方用户的拜访次数、热点文章的点赞转发数量等等。

上面咱们简略看看它的应用!

一般字符串的基本操作:

127.0.0.1:6379> set key value #设置 key-value 类型的值
OK
127.0.0.1:6379> get key # 依据 key 取得对应的 value
"value"
127.0.0.1:6379> exists key  # 判断某个 key 是否存在
(integer) 1
127.0.0.1:6379> strlen key # 返回 key 所贮存的字符串值的长度。(integer) 5
127.0.0.1:6379> del key # 删除某个 key 对应的值
(integer) 1
127.0.0.1:6379> get key
(nil)

批量设置 :

127.0.0.1:6379> mset key1 value1 key2 value2 # 批量设置 key-value 类型的值
OK
127.0.0.1:6379> mget key1 key2 # 批量获取多个 key 对应的 value
1) "value1"
2) "value2"

计数器(字符串的内容为整数的时候能够应用):

127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> incr number # 将 key 中贮存的数字值增一
(integer) 2
127.0.0.1:6379> get number
"2"
127.0.0.1:6379> decr number # 将 key 中贮存的数字值减一
(integer) 1
127.0.0.1:6379> get number
"1"

过期

127.0.0.1:6379> expire key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56

6.2. list

  1. 介绍 list 即是  链表 。链表是一种十分常见的数据结构,特点是易于数据元素的插入和删除并且且能够灵便调整链表长度,然而链表的随机拜访艰难。许多高级编程语言都内置了链表的实现比方 Java 中的 LinkedList,然而 C 语言并没有实现链表,所以 Redis 实现了本人的链表数据结构。Redis 的 list 的实现为一个  双向链表,即能够反对反向查找和遍历,更不便操作,不过带来了局部额定的内存开销。
  2. 常用命令: rpush,lpop,lpush,rpop,lrange、llen 等。
  3. 利用场景: 公布与订阅或者说音讯队列、慢查问。

上面咱们简略看看它的应用!

通过 rpush/lpop 实现队列:

127.0.0.1:6379> rpush myList value1 # 向 list 的头部(左边)增加元素
(integer) 1
127.0.0.1:6379> rpush myList value2 value3 # 向 list 的头部(最左边)增加多个元素
(integer) 3
127.0.0.1:6379> lpop myList # 将 list 的尾部 (最右边) 元素取出
"value1"
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的 list 列表,0 为 start,1 为 end
1) "value2"
2) "value3"
127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,- 1 示意倒数第一
1) "value2"
2) "value3"

通过 rpush/rpop 实现栈:

127.0.0.1:6379> rpush myList2 value1 value2 value3
(integer) 3
127.0.0.1:6379> rpop myList2 # 将 list 的头部 (最左边) 元素取出
"value3"

我专门花了一个图不便小伙伴们来了解:

通过 lrange 查看对应下标范畴的列表元素:

127.0.0.1:6379> rpush myList value1 value2 value3
(integer) 3
127.0.0.1:6379> lrange myList 0 1 # 查看对应下标的 list 列表,0 为 start,1 为 end
1) "value1"
2) "value2"
127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,- 1 示意倒数第一
1) "value1"
2) "value2"
3) "value3"

通过 lrange 命令,你能够基于 list 实现分页查问,性能十分高!

通过 llen 查看链表长度:

127.0.0.1:6379> llen myList
(integer) 3

6.3. hash

  1. 介绍 :hash 相似于 JDK1.8 前的 HashMap,外部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表, 特地适宜用于存储对象,后续操作的时候,你能够间接仅仅批改这个对象中的某个字段的值。比方咱们能够 hash 数据结构来存储用户信息,商品信息等等。
  2. 常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。
  3. 利用场景: 零碎中对象数据的存储。

上面咱们简略看看它的应用!

127.0.0.1:6379> hset userInfoKey name "guide" description "dev" age "24"
OK
127.0.0.1:6379> hexists userInfoKey name # 查看 key 对应的 value 中指定的字段是否存在。(integer) 1
127.0.0.1:6379> hget userInfoKey name # 获取存储在哈希表中指定字段的值。"guide"
127.0.0.1:6379> hget userInfoKey age
"24"
127.0.0.1:6379> hgetall userInfoKey # 获取在哈希表中指定 key 的所有字段和值
1) "name"
2) "guide"
3) "description"
4) "dev"
5) "age"
6) "24"
127.0.0.1:6379> hkeys userInfoKey # 获取 key 列表
1) "name"
2) "description"
3) "age"
127.0.0.1:6379> hvals userInfoKey # 获取 value 列表
1) "guide"
2) "dev"
3) "24"
127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 批改某个字段对应的值
127.0.0.1:6379> hget userInfoKey name
"GuideGeGe"

6.4. set

  1. 介绍: set 相似于 Java 中的 HashSet。Redis 中的 set 类型是一种无序汇合,汇合中的元素没有先后顺序。当你须要存储一个列表数据,又不心愿呈现反复数据时,set 是一个很好的抉择,并且 set 提供了判断某个成员是否在一个 set 汇合内的重要接口,这个也是 list 所不能提供的。能够基于 set 轻易实现交加、并集、差集的操作。比方:你能够将一个用户所有的关注人存在一个汇合中,将其所有粉丝存在一个汇合。Redis 能够十分不便的实现如独特关注、独特粉丝、独特爱好等性能。这个过程也就是求交加的过程。
  2. 常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。
  3. 利用场景: 须要寄存的数据不能反复以及须要获取多个数据源交加和并集等场景

上面咱们简略看看它的应用!

127.0.0.1:6379> sadd mySet value1 value2 # 增加元素进去
(integer) 2
127.0.0.1:6379> sadd mySet value1 # 不容许有反复元素
(integer) 0
127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
1) "value1"
2) "value2"
127.0.0.1:6379> scard mySet # 查看 set 的长度
(integer) 2
127.0.0.1:6379> sismember mySet value1 # 查看某个元素是否存在 set 中,只能接管单个元素
(integer) 1
127.0.0.1:6379> sadd mySet2 value2 value3
(integer) 2
127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 获取 mySet 和 mySet2 的交加并存放在 mySet3 中
(integer) 1
127.0.0.1:6379> smembers mySet3
1) "value2"

6.5. sorted set

  1. 介绍: 和 set 相比,sorted set 减少了一个权重参数 score,使得汇合中的元素可能按 score 进行有序排列,还能够通过 score 的范畴来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
  2. 常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
  3. 利用场景: 须要对数据依据某个权重进行排序的场景。比方在直播零碎中,实时排行信息蕴含直播间在线用户列表,各种礼物排行榜,弹幕音讯(能够了解为按音讯维度的音讯排行榜)等信息。
127.0.0.1:6379> zadd myZset 3.0 value1 # 增加元素到 sorted set 中 3.0 为权重
(integer) 1
127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次增加多个元素
(integer) 2
127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素数量
(integer) 3
127.0.0.1:6379> zscore myZset value1 # 查看某个 value 的权重
"3"
127.0.0.1:6379> zrange  myZset 0 -1 # 程序输入某个范畴区间的元素,0 -1 示意输入所有元素
1) "value3"
2) "value2"
3) "value1"
127.0.0.1:6379> zrange  myZset 0 1 # 程序输入某个范畴区间的元素,0 为 start  1 为 stop
1) "value3"
2) "value2"
127.0.0.1:6379> zrevrange  myZset 0 1 # 逆序输入某个范畴区间的元素,0 为 start  1 为 stop
1) "value1"
2) "value2"

7. Redis 单线程模型详解

Redis 基于 Reactor 模式来设计开发了本人的一套高效的事件处理模型(Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。因为文件事件处理器(file event handler)是单线程形式运行的,所以咱们个别都说 Redis 是单线程模型。

既然是单线程,那怎么监听大量的客户端连贯呢?

Redis 通过IO 多路复用程序 来监听来自客户端的大量连贯(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否产生。

这样的益处非常明显:I/O 多路复用技术的应用让 Redis 不须要额定创立多余的线程来监听客户端的大量连贯,升高了资源的耗费(和 NIO 中的 Selector 组件很像)。

另外,Redis 服务器是一个事件驱动程序,服务器须要解决两类事件:1\. 文件事件; 2\. 工夫事件。

工夫事件不须要多花工夫理解,咱们接触最多的还是 文件事件(客户端进行读取写入等操作,波及一系列网络通信)。

《Redis 设计与实现》有一段话是如是介绍文件事件的,我感觉写得挺不错。

Redis 基于 Reactor 模式开发了本人的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器应用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并依据 套接字目前执行的工作来为套接字关联不同的事件处理器。

当被监听的套接字筹备好执行连贯应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作绝对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来解决这些事件。

尽管文件事件处理器以单线程形式运行,但通过应用 I/O 多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又能够很好地与 Redis 服务器中其余同样以单线程形式运行的模块进行对接,这放弃了 Redis 外部单线程设计的简略性。

能够看出,文件事件处理器(file event handler)次要是蕴含 4 个局部:

  • 多个 socket(客户端连贯)
  • IO 多路复用程序(反对多个客户端连贯的要害)
  • 文件事件分派器(将 socket 关联到相应的事件处理器)
  • 事件处理器(连贯应答处理器、命令申请处理器、命令回复处理器)


《Redis 设计与实现:12 章》

8. Redis 没有应用多线程?为什么不应用多线程?

尽管说 Redis 是单线程模型,然而,实际上,Redis 在 4.0 之后的版本中就曾经退出了对多线程的反对。

不过,Redis 4.0 减少的多线程次要是针对一些大键值对的删除操作的命令,应用这些命令就会应用主解决之外的其余线程来“异步解决”。

大体上来说,Redis 6.0 之前次要还是单线程解决。

那,Redis6.0 之前 为什么不应用多线程?

我感觉次要起因有上面 3 个:

  1. 单线程编程容易并且更容易保护;
  2. Redis 的性能瓶颈不再 CPU,次要在内存和网络;
  3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。

9. Redis6.0 之后为何引入了多线程?

Redis6.0 引入多线程次要是为了进步网络 IO 读写性能,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈次要受限于内存和网络)。

尽管,Redis6.0 引入了多线程,然而 Redis 的多线程只是在网络数据的读写这类耗时操作上应用了,执行命令依然是单线程程序执行。因而,你也不须要放心线程平安问题。

Redis6.0 的多线程默认是禁用的,只应用主线程。如需开启须要批改 redis 配置文件 redis.conf

io-threads-do-reads yes

开启多线程后,还须要设置线程数,否则是不失效的。同样须要批改 redis 配置文件 redis.conf :

io-threads 4 #官网倡议 4 核的机器倡议设置为 2 或 3 个线程,8 核的倡议设置为 6 个线程

举荐浏览:

  1. Redis 6.0 新个性 - 多线程连环 13 问!
  2. 为什么 Redis 抉择单线程模型

10. Redis 给缓存数据设置过期工夫有啥用?

个别状况下,咱们设置保留的缓存数据的时候都会设置一个过期工夫。为什么呢?

因为内存是无限的,如果缓存中的所有数据都是始终保留的话,分分钟间接 Out of memory。

Redis 自带了给缓存数据设置过期工夫的性能,比方:

127.0.0.1:6379> exp key  60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
127.0.0.1:6379> ttl key # 查看数据还有多久过期
(integer) 56

留神:Redis 中除了字符串类型有本人独有设置过期工夫的命令 setex 外,其余办法都须要依附 expire 命令来设置过期工夫。另外,persist 命令能够移除一个键的过期工夫:

过期工夫除了有助于缓解内存的耗费,还有什么其余用么?

很多时候,咱们的业务场景就是须要某个数据只在某一时间段内存在,比方咱们的短信验证码可能只在 1 分钟内无效,用户登录的 token 可能只在 1 天内无效。

如果应用传统的数据库来解决的话,个别都是本人判断过期,这样更麻烦并且性能要差很多。

11. Redis 是如何判断数据是否过期的呢?

Redis 通过一个叫做过期字典(能够看作是 hash 表)来保留数据过期的工夫。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保留了 key 所指向的数据库键的过期工夫(毫秒精度的 UNIX 工夫戳)。

过期字典是存储在 redisDb 这个构造里的:

typedef struct redisDb {
    ...

    dict *dict;     // 数据库键空间, 保留着数据库中所有键值对
    dict *expires   // 过期字典, 保留着键的过期工夫
    ...
} redisDb;

12. 过期的数据的删除策略理解么?

如果假如你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?

罕用的过期数据的删除策略就两个(重要!本人造缓存轮子的时候须要分外思考的货色):

  1. 惰性删除:只会在取出 key 的时候才对数据进行过期查看。这样对 CPU 最敌对,然而可能会造成太多过期 key 没有被删除。
  2. 定期删除:每隔一段时间抽取一批 key 执行删除过期 key 操作。并且,Redis 底层会通过限度删除操作执行的时长和频率来缩小删除操作对 CPU 工夫的影响。

定期删除对内存更加敌对,惰性删除对 CPU 更加敌对。两者各有千秋,所以 Redis 采纳的是 定期删除 + 惰性 / 懒汉式删除

然而,仅仅通过给 key 设置过期工夫还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的状况。这样就导致大量过期 key 沉积在内存里,而后就 Out of memory 了。

怎么解决这个问题呢?答案就是:Redis 内存淘汰机制。

13. Redis 内存淘汰机制理解么?

相干问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保障 Redis 中的数据都是热点数据?

Redis 提供 6 种数据淘汰策略:

  1. volatile-lru(least recently used):从已设置过期工夫的数据集(server.db[i].expires)中筛选最近起码应用的数据淘汰
  2. volatile-ttl:从已设置过期工夫的数据集(server.db[i].expires)中筛选将要过期的数据淘汰
  3. volatile-random:从已设置过期工夫的数据集(server.db[i].expires)中任意抉择数据淘汰
  4. allkeys-lru(least recently used):当内存不足以包容新写入数据时,在键空间中,移除最近起码应用的 key(这个是最罕用的)
  5. allkeys-random:从数据集(server.db[i].dict)中任意抉择数据淘汰
  6. no-eviction:禁止驱赶数据,也就是说当内存不足以包容新写入数据时,新写入操作会报错。这个应该没人应用吧!

4.0 版本后减少以下两种:

  1. volatile-lfu(least frequently used):从已设置过期工夫的数据集 (server.db[i].expires) 中筛选最不常常应用的数据淘汰
  2. allkeys-lfu(least frequently used):当内存不足以包容新写入数据时,在键空间中,移除最不常常应用的 key

14. Redis 长久化机制(怎么保障 Redis 挂掉之后再重启数据能够进行复原)

很多时候咱们须要长久化数据也就是将内存中的数据写入到硬盘外面,大部分起因是为了之后重用数据(比方重启机器、机器故障之后复原数据),或者是为了避免系统故障而将数据备份到一个近程地位。

Redis 不同于 Memcached 的很重要一点就是,Redis 反对长久化,而且反对两种不同的长久化操作。Redis 的一种长久化形式叫快照(snapshotting,RDB),另一种形式是只追加文件(append-only file, AOF)。这两种办法各有千秋,上面我会具体这两种长久化办法是什么,怎么用,如何抉择适宜本人的长久化办法。

快照(snapshotting)长久化(RDB)

Redis 能够通过创立快照来取得存储在内存外面的数据在某个工夫点上的正本。Redis 创立快照之后,能够对快照进行备份,能够将快照复制到其余服务器从而创立具备雷同数据的服务器正本(Redis 主从构造,次要用来进步 Redis 性能),还能够将快照留在原地以便重启服务器的时候应用。

快照长久化是 Redis 默认采纳的长久化形式,在 Redis.conf 配置文件中默认有此下配置:

save 900 1           #在 900 秒 (15 分钟) 之后,如果至多有 1 个 key 发生变化,Redis 就会主动触发 BGSAVE 命令创立快照。save 300 10          #在 300 秒 (5 分钟) 之后,如果至多有 10 个 key 发生变化,Redis 就会主动触发 BGSAVE 命令创立快照。save 60 10000        #在 60 秒 (1 分钟) 之后,如果至多有 10000 个 key 发生变化,Redis 就会主动触发 BGSAVE 命令创立快照。

AOF(append-only file)长久化

与快照长久化相比,AOF 长久化 的实时性更好,因而已成为支流的长久化计划。默认状况下 Redis 没有开启 AOF(append only file)形式的长久化,能够通过 appendonly 参数开启:

appendonly yes

开启 AOF 长久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保留地位和 RDB 文件的地位雷同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。

在 Redis 的配置文件中存在三种不同的 AOF 长久化形式,它们别离是:

appendfsync always    #每次有数据批改产生时都会写入 AOF 文件, 这样会重大升高 Redis 的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步

为了兼顾数据和写入性能,用户能够思考 appendfsync everysec 选项,让 Redis 每秒同步一次 AOF 文件,Redis 性能简直没受到任何影响。而且这样即便呈现零碎解体,用户最多只会失落一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的加快本人的速度以便适应硬盘的最大写入速度。

相干 issue:783:Redis 的 AOF 形式

拓展:Redis 4.0 对于长久化机制的优化

Redis 4.0 开始反对 RDB 和 AOF 的混合长久化(默认敞开,能够通过配置项 aof-use-rdb-preamble 开启)。

如果把混合长久化关上,AOF 重写的时候就间接把 RDB 的内容写到 AOF 文件结尾。这样做的益处是能够联合 RDB 和 AOF 的长处, 疾速加载同时防止失落过多的数据。当然毛病也是有的,AOF 外面的 RDB 局部是压缩格局不再是 AOF 格局,可读性较差。

补充内容:AOF 重写

AOF 重写能够产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保留的数据库状态一样,但体积更小。

AOF 重写是一个有歧义的名字,该性能是通过读取数据库中的键值对来实现的,程序毋庸对现有 AOF 文件进行任何读入、剖析或者写入操作。

在执行 BGREWRITEAOF 命令时,Redis 服务器会保护一个 AOF 重写缓冲区,该缓冲区会在子过程创立新 AOF 文件期间,记录服务器执行的所有写命令。当子过程实现创立新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的开端,使得新旧两个 AOF 文件所保留的数据库状态统一。最初,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来实现 AOF 文件重写操作

15. Redis 事务

Redis 能够通过 MULTI,EXEC,DISCARD 和 WATCH 等命令来实现事务 (transaction) 性能。

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

应用 MULTI 命令后能够输出多个命令。Redis 不会立刻执行这些命令,而是将它们放到队列,当调用了 EXEC 命令将执行所有命令。

Redis 官网相干介绍 https://redis.io/topics/transactions 如下:

然而,Redis 的事务和咱们平时了解的关系型数据库的事务不同。咱们晓得事务具备四大个性:1\. 原子性2\. 隔离性3\. 持久性4\. 一致性

  1. 原子性(Atomicity): 事务是最小的执行单位,不容许宰割。事务的原子性确保动作要么全副实现,要么齐全不起作用;
  2. 隔离性(Isolation): 并发拜访数据库时,一个用户的事务不被其余事务所烦扰,各并发事务之间数据库是独立的;
  3. 持久性(Durability): 一个事务被提交之后。它对数据库中数据的扭转是长久的,即便数据库产生故障也不应该对其有任何影响。
  4. 一致性(Consistency): 执行事务前后,数据保持一致,多个事务对同一个数据读取的后果是雷同的;

Redis 是不反对 roll back 的,因此不满足原子性的(而且不满足持久性)。

Redis 官网也解释了本人为啥不反对回滚。简略来说就是 Redis 开发者们感觉没必要反对回滚,这样更简略便捷并且性能更好。Redis 开发者感觉即便命令执行谬误也应该在开发过程中就被发现而不是生产过程中。

你能够将 Redis 中的事务就了解为:Redis 事务提供了一种将多个命令申请打包的性能。而后,再按程序执行打包的所有命令,并且不会被中途打断。

相干 issue :issue452: 对于 Redis 事务不满足原子性的问题,
举荐浏览:https://zhuanlan.zhihu.com/p/43897838。

16. 缓存穿透

16.1. 什么是缓存穿透?

缓存穿透说简略点就是大量申请的 key 基本不存在于缓存中,导致申请间接到了数据库上,基本没有通过缓存这一层。举个例子:某个黑客成心制作咱们缓存中不存在的 key 发动大量申请,导致大量申请落到数据库。

16.2. 缓存穿透状况的解决流程是怎么的?

如下图所示,用户的申请最终都要跑到数据库中查问一遍。

16.3. 有哪些解决办法?

最根本的就是首先做好参数校验,一些不非法的参数申请间接抛出异样信息返回给客户端。比方查问的数据库 id 不能小于 0、传入的邮箱格局不对的时候间接返回谬误音讯给客户端等等。

1)缓存有效 key

如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期工夫,具体命令如下:SET key value EX 10086。这种形式能够解决申请的 key 变动不频繁的状况,如果黑客歹意攻打,每次构建不同的申请 key,会导致 Redis 中缓存大量有效的 key。很显著,这种计划并不能从根本上解决此问题。如果非要用这种形式来解决穿透问题的话,尽量将有效的 key 的过期工夫设置短一点比方 1 分钟。

另外,这里多说一嘴,个别状况下咱们是这样设计 key 的:表名: 列名: 主键名: 主键值

如果用 Java 代码展现的话,差不多是上面这样的:

public Object getObjectInclNullById(Integer id) {
    // 从缓存中获取数据
    Object cacheValue = cache.get(id);
    // 缓存为空
    if (cacheValue == null) {
        // 从数据库中获取
        Object storageValue = storage.get(key);
        // 缓存空对象
        cache.set(key, storageValue);
        // 如果存储数据为空,须要设置一个过期工夫(300 秒)
        if (storageValue == null) {
            // 必须设置过期工夫,否则有被攻打的危险
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    }
    return cacheValue;
}

2)布隆过滤器

布隆过滤器是一个十分神奇的数据结构,通过它咱们能够十分不便地判断一个给定数据是否存在于海量数据中。咱们须要的就是判断 key 是否非法,有没有感觉布隆过滤器就是咱们想要找的那个“人”。

具体是这样做的:把所有可能存在的申请的值都寄存在布隆过滤器中,当用户申请过去,先判断用户发来的申请的值是否存在于布隆过滤器中。不存在的话,间接返回申请参数错误信息给客户端,存在的话才会走上面的流程。

退出布隆过滤器之后的缓存解决流程图如下。

然而,须要留神的是布隆过滤器可能会存在误判的状况。总结来说就是:布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素肯定不在。

为什么会呈现误判的状况呢? 咱们还要从布隆过滤器的原理来说!

咱们先来看一下,当一个元素退出布隆过滤器中的时候,会进行哪些操作:

  1. 应用布隆过滤器中的哈希函数对元素值进行计算,失去哈希值(有几个哈希函数失去几个哈希值)。
  2. 依据失去的哈希值,在位数组中把对应下标的值置为 1。

咱们再来看一下,当咱们须要判断一个元素是否存在于布隆过滤器的时候,会进行哪些操作:

  1. 对给定元素再次进行雷同的哈希计算;
  2. 失去值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么阐明这个值在布隆过滤器中,如果存在一个值不为 1,阐明该元素不在布隆过滤器中。

而后,肯定会呈现这样一种状况:不同的字符串可能哈希进去的地位雷同。(能够适当减少位数组大小或者调整咱们的哈希函数来升高概率)

更多对于布隆过滤器的内容能够看我的这篇原创:《不理解布隆过滤器?一文给你整的明明白白!》,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。

17. 缓存雪崩

17.1. 什么是缓存雪崩?

我发现缓存雪崩这名字起的有点意思,哈哈。

实际上,缓存雪崩形容的就是这样一个简略的场景:缓存在同一时间大面积的生效,前面的申请都间接落到了数据库上,造成数据库短时间内接受大量申请。 这就好比雪崩一样,不堪一击之势,数据库的压力可想而知,可能间接就被这么多申请弄宕机了。

举个例子:零碎的缓存模块出了问题比方宕机导致不可用。造成零碎的所有拜访,都要走数据库。

还有一种缓存雪崩的场景是:有一些被大量拜访数据(热点缓存)在某一时刻大面积生效,导致对应的申请间接落到了数据库上。 这样的状况,有上面几种解决办法:

举个例子:秒杀开始 12 个小时之前,咱们对立寄存了一批商品到 Redis 中,设置的缓存过期工夫也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的拜访间接就生效了。导致的状况就是,相应的申请间接就落到了数据库上,就像雪崩一样可怕。

17.2. 有哪些解决办法?

针对 Redis 服务不可用的状况:

  1. 采纳 Redis 集群,防止单机呈现问题整个缓存服务都没方法应用。
  2. 限流,防止同时解决大量的申请。

针对热点缓存生效的状况:

  1. 设置不同的生效工夫比方随机设置缓存的生效工夫。
  2. 缓存永不生效。

18. 如何保障缓存和数据库数据的一致性?

细说的话能够扯很多,然而我感觉其实没太大必要(小声 BB:很多解决方案我也没太弄明确)。我集体感觉引入缓存之后,如果为了短时间的不一致性问题,抉择让零碎设计变得更加简单的话,齐全没必要。

上面独自对 Cache Aside Pattern(旁路缓存模式) 来聊聊。

Cache Aside Pattern 中遇到写申请是这样的:更新 DB,而后间接删除 cache。

如果更新数据库胜利,而删除缓存这一步失败的状况的话,简略说两个解决方案:

  1. 缓存生效工夫变短(不举荐,治标不治本):咱们让缓存数据的过期工夫变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不实用。
  2. 减少 cache 更新重试机制(罕用):如果 cache 服务以后不可用导致缓存删除失败的话,咱们就隔一段时间进行重试,重试次数能够本人定。如果多次重试还是失败的话,咱们能够把以后更新失败的 key 存入队列中,等缓存服务可用之后,再将 缓存中对应的 key 删除即可。

19. 参考

  • 《Redis 开发与运维》
  • 《Redis 设计与实现》
  • Redis 命令总结:http://Redisdoc.com/string/set.html
  • 通俗易懂的 Redis 数据结构基础教程:https://juejin.im/post/5b53ee7e5188251aaa2d2e16
  • WHY Redis choose single thread (vs multi threads): https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153
  • 《2020 最新 Java 根底精讲视频教程和学习路线!》

正文完
 0