我试着更深入地理解Redis和缓存
由于在工作中接触到与现金有关的部分,我有机会深入了解了现金。以下是我的总结。
Redis是什么?
Redis是“REmote DIctionary Server”的缩写。在GitHub上的仓库是指Redis的仓库。
最近,开发者发表了他们打算退出Redis开发的帖子。因此,Redis的代码库已经从作者的个人代码库迁移到redis-io代码库中。
Redis是一种内存数据库,因此可以实现快速访问,它采用了客户端/服务器模型,因此可以从多个客户端访问。此外,Redis还具有可扩展性特征,例如支持复制。
在本篇投稿中,我们将讨论以下两个方面。
-
- in-memory database
- remote (client / server)
内存数据库
通过使用内存数据库,数据可以在主内存中存储,而无需使用硬盘驱动器(如HDD或SSD)。这样可以消除繁琐的磁盘输入/输出操作,从而提供更好的性能。
Redis被称为“内存数据库且速度快”。实际上,Redis相比于MySQL等数据库可以实现快速访问。然而需要注意的是,Redis之所以快速,并不是因为与MySQL相比,它不需要进行磁盘IO而仅需通过内存访问来获取数据。如果是这样的话,只要在MySQL中使用页面缓存(buffer pool)将数据缓存到内存中,即使是基于磁盘的关系型数据库,只要有足够的内存,也应该能够实现快速访问。
在内存数据库中,实际上,“通过去除硬盘约束”,可以实现高速操作。硬盘存在各种约束。以硬盘驱动器(HDD)为例,为了进行数据的读写操作,需要旋转磁头。因此,与RAM不同,必须使用基于顺序I/O的数据结构来处理数据,而不是随机I/O,才能进行优化。(例如,为了进行优化,使用B树索引等方法。)
换句话说,由于有Disk的限制,我们需要将之前在内存中使用的数据结构进行编码/解码,转换为Disk优化的数据结构。这样一来,就能够消除额外的开销,从而实现对内存数据库的快速访问。
关于可使用的最大内存量,您可以在redis.conf文件中使用maxmemory参数进行指定。为了处理超过指定限制值的情况,Redis提供了淘汰策略(Eviction Policy)。其中之一就是最近最少使用(LRU)策略。Redis通过使用LRU策略来考虑不过度使用内存。
此外,由于数据保存在内存中,可靠性(Durability)会丧失。然而,与作为缓存目的而创建的memcached不同,Redis可以通过AOF日志或RDB快照进行备份。需要注意的是,从最后备份之后开始的数据写入将会丢失。因此,对于重要数据而言,仅将其写入缓存数据库Redis是危险的。应该将Redis视为临时数据存储,并限制其仅用于可以容忍丢失的数据,如会话数据。
顺便提一下,就我个人而言,一听到内存数据库这个词我会想到Redis和Memcached。不过,根据内存数据库列表显示,还有其他各种数据库也被列为内存数据库。例如,SQLite也被标记为内存数据库。根据SQLite的官方文档(In-Memory Databases)所述,通过适当的设置,可以将SQLite用作内存数据库。
客户端/服务器模型
Redis使用了客户端/服务器模型。应用程序使用Redis客户端库通过TCP/IP发送请求到Redis服务器。Redis服务器从RAM中获取数据,并将其作为响应返回给应用程序。
MySQL等也采用了这种客户端服务器模型。而SQLite则采用了与之相对应的结构,并被称为无服务器(serverless)模式。
以下的图片清楚地表明了它们之间的区别。SQlite提供了一种不需经过网络就可以访问数据库的方法。
虽然《SQLite适用的场景》提到,无服务器(serverless)适合作为本地缓存和本地数据存储使用,但如果需要扩展性,应采用客户端-服务器模型。
线程模型
如果选择使用客户端/服务器模型,则需要决定如何处理来自客户端的连接。对于来自客户端的连接,Redis采用了单线程架构。
Node.js和nginx同样使用事件循环(Event Loop),在内部同时存在IO多路复用和线程池。Nginx采用这个模型的原因是为了解决c10k问题,而Redis采用单线程架构的原因是为了保持简便。正如Redis的宣言第六条所述,“我们反对复杂性。”,Redis注重简单性。
如果采用多线程架构,线程将共享内存,这就需要消除对共享数据的竞争条件。处理并行化会使代码变得复杂。
通过将其设为单线程,意味着在某个时刻只执行一个处理,因此不会发生竞争条件。然而,由于是单线程,无论是CPU密集型还是I/O密集型,执行阻塞操作时都需要注意。
Single Thread Model不利之處在於無法利用多核處理器。Redis 是單線程的。如何利用多個 CPU / 核心?即便如此,在平常的 Redis 中,CPU 不會成為瓶頸,因此當需要擴展性時,推薦的做法是使用多個 Redis 實例來建立 Redis Cluster 或 Redis Proxy,而不是使用多核處理器 + 多線程。(見第七條宣言)
多线程
memcached的架构采用了多线程模式。然而,正如ConfiguringServer中所述,与apache等模型不同,它更倾向于采用nginx的模型。也就是说,它像Redis一样使用事件循环。
默认情况下,有四个线程被分配,并使用名为libevent的库来执行事件循环。因此,即使是多线程,也不会面临C10K问题。
通过采用多线程模型,无法避免出现竞态条件。为了避免竞态条件,在Memcached中提供了一种乐观的并发控制方法,即cas命令。
检查并设置(或比较并交换)。一种操作,用于存储数据,但仅在您上次读取该数据后没有其他人更新数据时。用于解决更新缓存数据的竞争条件。【引用】指令
Memcached 将数据访问分散到线程上,而 Redis 仅将 I/O 分散到线程上。
键DB
存在的KeyDB是一个基于Redis Fork的多线程版本的数据库,对于Redis采用单线程模型的争议观点不一。
KeyDB通过在多个线程上运行正常的Redis事件循环来工作。网络IO和查询解析是并发完成的。每个连接在accept()时被分配一个线程。核心哈希表的访问受到自旋锁的保护。由于哈希表的访问非常快速,所以该锁的争用较低。事务会在EXEC命令的持续时间内持有锁。模块与GIL协同工作,只有在所有服务器线程都暂停时才会获取GIL。这保证了模块所期望的原子性保证。【引用】一个比Redis快5倍的多线程Redis衍生版本。
根据Multi Thread的结构,KeyDB可能会出现竞态条件。据推测,它通过使用自旋锁来实现对Hash Table的访问的互斥保护。而KeyDB-Pro则引入了MVCC来实现快照隔离。
现金
缓存指的是只执行一次耗时操作,然后将结果保存在内存中。例如,在进行消耗CPU的处理时,通过重复执行该处理不是高效的方法,所以我们应该将处理结果保存在RAM中以便重复使用。
RedisやMemcachedのような、キャッシュによく使われるデータベースは、パフォーマンスを得ることを主目的として導入されることもあれば、DBに対するreadの負荷分散を主目的として導入されることもあります。
以下の画像が示すように、キャッシュは様々なレイヤで利用することができます。
在本篇投稿中,我们将参考数据库缓存的命名方式,讨论以下两个内容。
-
- Local Caches (client-side caching / application cache)
- Remote Caches
本地缓存
在Redis中,它被介绍为客户端缓存。请注意,客户端是指Redis中的应用程序服务器。
+-------------+ +----------+
| | | |
| Application | ( No chat needed ) | Database |
| | | |
+-------------+ +----------+
| Local cache |
| |
| user:1234 = |
| username |
| Alice |
+-------------+
【引用】Redis服务端辅助的客户端缓存
通过在客户端进行缓存,可以实现快速的数据访问,无需通过网络,并且节省了磁盘I/O。
一方面,需要注意的是将缓存放在与应用服务器相同的内存中的本地缓存。当将应用服务器分为多个实例进行负载均衡时,可能会出现数据不一致的情况,因为在应用服务器的缓存之间,根据扩展来说缩放可能会导致不一致。
キャッシュがアプリケーションやキャッシュを使用しているシステムと同じノードにある場合、スケーリングがキャッシュの整合性に影響を与える場合があります。また、ローカルキャッシュを使用する場合、データを消費するローカルアプリケーションのみがローカルキャッシュを活用できます。分散キャッシュ環境では、データが複数のキャッシュサーバーにまたがる場合があるため、データのコンシューマーすべてに活用されるように、一元的な場所に保存されます。
【引用】キャッシュの概要
远程缓存
对于应用程序缓存,可以设置独立于应用程序和数据库的缓存方式。
尽管通过网络连接可能导致数据访问变慢,但由于可以从多个应用服务器实例引用同一个缓存服务器,因此不会发生类似本地缓存中数据不一致的情况。
缓存策略
如果考虑使用缓存,需要考虑到写入数据库的频率是否过高,以及读取等操作是否频繁使用缓存等各种因素。关于是否本质上应该使用缓存,我们将暂不讨论。假设我们使用缓存。
无论是使用本地缓存还是远程缓存,都需要考虑数据库和缓存数据库之间的一致性。此外,为了防止缓存未命中导致性能下降,需要考虑在何时将哪些数据保存到缓存中,以及将数据保存在缓存中的时间。因此,在引入缓存时,缓存策略即缓存和数据库之间的系统设计变得重要起来。
从这里开始,我们将看两种典型的缓存策略。
阅读/懒缓存/除缓存之外(反应性方法)
在上图中,需要注意的是,如果在缓存中存在数据,则不存在”Process / Format data”的过程。通过提前处理/格式化数据并将其写入缓存,可以在有数据存在于缓存中时直接使用数据而无需进行处理/格式化。
在读取时发生缓存错误时,会从数据库中获取数据并将其写入缓存中。因此,需要注意如果发生缓存错误,读取性能会下降。
另外,实际请求的数据才会被缓存,因此是高效的。但是,仅靠这种策略无法将更新同步到数据库,导致缓存的值可能变得陈旧。因此,为了防止缓存值过旧,需要正确设置TTL,或者与下文介绍的写回策略相结合。
以主动的方式书写 (積極方法)
在数据更新时,将数据存储到缓存中,这就是写入透明性。
通过采用write through策略,当对数据库进行写入时,也会对缓存进行写入,以确保缓存不包含旧值。此外,可以将对缓存的写入操作从读取时移至写入时。对于用户来说,写入操作在直觉上被认为是耗时的,因此可以提高用户的可用性(读取较快,写入较慢)。
通过采用写通过方式,可以始终更新缓存的最新数据,但是由于可能会出现应用程序逻辑遗漏等情况,缓存可能不会被更新。因此,始终设置TTL是很重要的,以应对这种情况。
TTLを設定するというのは簡単なのですが、実際は何秒/分でexpireさせるのか難しいと思います。データがどのくらいの頻度で変更されるのか、また、古い値を返してしまった場合のリスクなどを考慮して設定する必要があります。また、それぞれのデータのTTLを少しずらしておくことで、TTLがexpireした時のバックエンドの負荷を下げるようにすることも重要なようです。(Cache Thundering Herd問題)
参考
书籍
-
- Redis in Action
-
- https://redislabs.com/redis-in-action/
-
- Database Internals: A Deep Dive into How Distributed Data Systems Work
-
- https://www.amazon.co.jp/dp/B07XW76VHZ/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
-
- Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems
- https://www.amazon.co.jp/Designing-Data-Intensive-Applications-Reliable-Maintainable-ebook/dp/B06XPJML5D
Redis 是一种开源的内存数据库系统。
-
- Redisの特徴と活用方法について
-
- https://www.slideshare.net/yujiotani16/redis-76504393
-
- アルゴリズムとデータ構造から理解するRedis / Learn Redis from Internal Algorithms and Data Structures
-
- https://speakerdeck.com/kawasy/learn-redis-from-internal-algorithms-and-data-structures
-
- Redis作者自身によるRedisとMemcachedの比較
-
- https://yakst.com/ja/posts/3243
-
- http://antirez.com/news/94
Dive Deep Redis ~ 入門から実装の確認まで ~
Thread
Why isn’t Redis designed to benefit from multi-threading?
https://www.quora.com/Why-isnt-Redis-designed-to-benefit-from-multi-threading
How can Redis give multiple responses for multiple users with a single thread mechanism?
https://www.quora.com/How-can-Redis-give-multiple-responses-for-multiple-users-with-a-single-thread-mechanism
Concurrency vs Event Loop vs Event Loop + Concurrency
https://medium.com/@tigranbs/concurrency-vs-event-loop-vs-event-loop-concurrency-eb542ad4067b
内存缓存
-
- Hardware
-
- https://github.com/memcached/memcached/wiki/Hardware
-
- Home
- https://github.com/memcached/memcached/wiki
主要数据库
-
- KeyDB
-
- https://keydb.dev
-
- MultiVersion Concurrency Control (MVCC)
-
- https://docs.keydb.dev/docs/pro-mvcc/
-
- A Multithreaded Fork of Redis That’s 5X Faster Than Redis
- https://docs.keydb.dev/blog/2019/10/07/blog-post/
SQLite是一种嵌入式关系型数据库管理系统。
-
- Distinctive Features Of SQLite
-
- https://www.sqlite.org/different.html
-
- In-Memory Databases
- https://www.sqlite.org/inmemorydb.html
缓存
-
- キャッシュ
-
- https://docs.microsoft.com/ja-jp/azure/architecture/best-practices/caching
-
- Webアプリケーションにおける正しいキャッシュ戦略
-
- https://buildersbox.corp-sansan.com/entry/2019/03/25/150000
-
- Database Caching
-
- https://aws.amazon.com/jp/caching/database-caching/
-
- Caching Best Practices
-
- https://aws.amazon.com/jp/caching/best-practices/
-
- キャッシュの概要
-
- https://aws.amazon.com/jp/caching/
-
- Cache Me If You Can Minimizing Latency While Optimizing Cost Through Advanced Caching Strategies – ATC303 – re:Invent 2017
- https://www.youtube.com/watch?v=WFRIivS2mpo&feature=emb_title
其他
-
- Modern Main-Memory Database Systems
-
- http://www.vldb.org/pvldb/vol9/p1609-larson.pdf
-
- Redis vs. Memcached: In-Memory Data Storage Systems
- https://medium.com/@Alibaba_Cloud/redis-vs-memcached-in-memory-data-storage-systems-3395279b0941