按照Redis官网定义,Redis是一个开源的基于BSD协议的强大的K-V内存数据库。它提供了五种数据结构:strings、hashes、lists、sets和sorted sets。Redis完全基于ANSI C编写。
Redis通常被用作缓存目的,而在Java界流行的缓存框架还有Memcached、EhCache。于是我想探究下Redis和Memcached的区别,如下:
- Memcached的所有值都是简单的字符串,而Redis支持更丰富的数据类型,更丰富的数据类型常常意味着能以简单的方式支持复杂的功能
- 通常Redis比Memcached快得多
- Redis可以将缓存数据持久化到磁盘,而Memcached不可以。Memcached是一个更纯粹的缓存(Cache),而Redis不那么纯粹,可以用作数据库(Database)=> 基于这点,可以用Redis的list做高可用性队列,因为队列可被持久化到磁盘
- Memcached号称分布式缓存,但其实只是Shard,并未涉及各个实例的协作
- Redis的所有操作都是原子操作
我比较关注上面第3条,于是我特地更深入地就这条查看了几篇文章。对Redis作为数据库(Database)的描述如下:
1
2
3
4
|
Redis虽然是一个内存数据库,但也可以将数据持久化到硬盘,有两种持久化方式:RDB和AOF。 RDB持久化方式定时将数据快照写入磁盘。这种方式并不是非常可靠,因为可能丢失数据,但非常快速。 AOF持久化方式更加可靠,将服务端收到的每个写操作都写入磁盘。在服务器重启时,这些写操作被重新执行来 重新构建数据集。 |
相比之下Memcached没有提供这种持久化机制,为什么Memcached不提供呢?原因应该有很多条(见仁见智),这里列举两条:
- Memcached的设计初衷就是一个对象缓存框架,而不是一个数据库
- 将缓存Dump到磁盘可能是危险的:
12345678910
考察下面的场景:
A. 用户更新账户信息,账户状态为A
B. 你的代码更新数据库(比如MySQL数据库),数据库中的账户状态为A
C. 你的代码更新缓存,缓存中的账户状态为A
D. 你将缓存Dump到磁盘并开始在部署了缓存的主机上工作,磁盘和缓存中的账户状态都为A
E. 用户再次更新账户信息,账户状态为B
F. 你的代码更新数据库,数据库中的账户状态为B
G. 你的代码未能更新缓存(因为这个时候缓存服务器宕机了)
H. 你从磁盘恢复缓存,缓存中的账户状态为A
I. 用户查看账户信息,得到的账户状态为A
针对上面第2条提到的问题,有人可能会建议将F、G更换顺序,先更新缓存,再更新数据库,只有缓存更新成功了才更新数据库。这在某些情况下的确可以解决这种写缓存失败导致的读缓存数据不一致问题。但现在写入性能下降了。使用缓存框架的理想状态是:不对写性能造成影响,同时极大提高读性能,并且保证比较高的数据一致性(或准确性、数据时新性)。因此写缓存的推荐方式是异步写,将F、G更换顺序的建议并不太妥当。
这里体现了几个使用缓存框架时的折衷:
- 写缓存性能(写缓存):同步写入还是异步写入
- 缓存数据时新性(读缓存):缓存的数据是否需要实时与数据库数据同步
- 缓存是否不仅仅是缓存(缓存持久化):是否还用作数据库,是否需要定时Dump缓存到磁盘
没有万金油式的解决方案,因为技术服务于业务,而业务场景又是千变万化的。