Redis缓存问题怎么解决

LevelDB 来了!

这是一个由 Google 开源的 NOSQL 存储引擎库,是现代分布式存储领域不可或缺的利器。在它的基础之上,Facebook 开发出了另一个 NOSQL 存储引擎库 RocksDB,沿用了 LevelDB 的先进技术架构的同时还解决了 LevelDB 的一些短板。你可以将 RocksDB 比喻成氢弹,它比 LevelDB 的威力更大一些。许多现代开源数据库采用了 RocksDB 作为底层存储引擎,TiDB 就是其中一个著名的例子。

但是为什么我要讲 LevelDB 而不是 RocksDB 呢?其原因在于 LevelDB 技术架构更加简单清晰易于理解。如果我们先把 LevelDB 吃透了再去啃一啃 RocksDB 就会非常好懂了,RocksDB 也只是在 LevelDB 的基础上添砖加瓦进行了一系列优化而已。当我们打破 RocksDB 这个难题时,TiDB 的核动力宇宙飞船已经在前方不远处迎接我们了。

Redis 缓存有什么问题?

当我们使用 Redis 作为缓存时,通常仍会有一个持久性数据库记录完整的冷、热数据。Redis 和持久层数据库之间的数据一致性是由应用程序自己来控制的。当缓存中没有数据时,应用程序需要从持久层中加载数据并将其放入缓存中。当数据更新发生时,需要将缓存置为失效。

<code class="hljs javascript">function getUser(String userId) User {<br>  User user = redis.get(userId);<br>  if user == null {<br>    user = db.get(userId);<br>    if user != null {<br>      redis.set(userId, user);<br>    }<br>  }<br>  return user;<br>}<br><br>function updateUser(String userId, User user) {<br>  db.update(userId, user);<br>  redis.expire(userId);<br>}<br></code>


有过这方面开发经验的朋友们就知道写这样的代码还是挺繁琐的,所有的涉及到缓存的业务代码都需要加上这一部分逻辑。

严格来说我们还需要仔细考虑缓存一致性问题,比如在 updateUser 方法中,数据库正确执行了更新,但是缓存 redis 因为网络抖动等原因置为失效没有成功,那么缓存中的数据就成了过期数据。读者可以考虑,即使反转设置缓存和更新持久存的顺序,仍可能出现其他问题。

在多进程高并发场合也会导致缓存不一致,比如一个进程对某个 userId 调用 getUser() 方法,因为缓存里没有,它需要从数据库里加载。结果刚刚加载出来,正准备要设置缓存,这时候发生了内存 fullgc 代码暂停了一会,而正在此时另一个进程调用了 updateUser 方法更新了数据库,将缓存置为失效(其实缓存里本来就没有数据)。然后前面那个进程终于 fullgc 结束要开始设置缓存了,这时候进缓存的就是过期的数据。

LevelDB 是如何解决的?

LevelDB 将 Redis 缓存和持久层合二为一,一次性帮你搞定缓存和持久层。有了 LevelDB,你的代码可以简化成下面这样

<code class="hljs javascript">function getUser(String userId) User {<br>  return leveldb.get(userId);<br>}<br><br>function updateUser(String userId, User user) {<br>  leveldb.set(userId, user);<br>}<br></code>


而且你再也不用当心缓存一致性问题了,LevelDB 的数据更新要么成功要么不成功,不存在中间薛定谔状态。用户无需关注数据一致性的细节,因为LevelDB内部集成了内存缓存和持久层磁盘文件。

LevelDB 具体是什么?

我们之前提到它是一种非关系型存储引擎,和 Redis 的概念不同。Redis 是一个完备的数据库,而 LevelDB 它只是一个引擎。如果将数据库比作一辆高级跑车,则存储引擎便是其发动机,也即是它的核心和心脏。有了这个发动机,我们再给它包装上一系列的配件和装饰,就可以成为数据库。不过也不要小瞧了配件和装饰,做到极致那也是非常困难,将 LevelDB 包装成一个简单易用的数据库需要加上太多太多精致的配件。LevelDB 和 RocksDB 出来这么多年,能够在它的基础上做出非常一个完备的生产级数据库寥寥无几。

LevelDB可以被视为一个内存中的键值数据库。它提供了基础的 Get/Set API,我们在代码里可以通过这个 API 来读写数据。你还可以将它看成一个无限大小的高级 HashMap,我们可以往里面塞入无限条 Key/Value 数据,只要磁盘可以装下。

由于它只能视为一种内存数据库,其中储存的数据无法在进程或机器间共享。在分布式领域,LevelDB 要如何大显身手呢?

要实现这一点,需要将网络 API 包装在 LevelDB 内存数据库之上。当多个进程位于不同的机器上并希望访问该资源时,它们都必须通过网络 API 接口进行统一访问。这样就形成了一个简易的数据库。应用 Redis 协议封装网络层,即可使用 Redis 客户端对该数据库进行读写操作。

如果要考虑数据库的高可用性,我们在上面这个单机数据库的基础上再加上主从复制功能就可以变身成为一个主从结构的分布式 NOSQL 数据库。在主从数据库前面加一层转发代理(负载均衡器如 LVS、F5 等),就可以实现主从的实时切换。

如果你需要的数据容量特别大以至于单个机器的硬盘都容不下,这时候就需要数据分片机制将整个数据库的数据分散到多台机器上,每台机器只负责一部分数据的读写工作。数据分片的方案非常多,可以像 Codis 那样通过转发代理来分片,也可以像 Redis-Cluster 那样使用客户端转发机制来分片,还可以使用 TiDB 的 Raft 分布式一致性算法来分组管理分片。最简单最易于理解的还是要数 Codis 的转发代理分片。

当数据量继续增长需要新增节点时,就必须将老节点上的数据部分迁移到新节点上,管理数据的均衡和迁移的又是一个新的高级配件 —— 数据均衡器。

以上就是Redis缓存问题怎么解决的详细内容,更多请关注其它相关文章!