天天看点

Spring Data Redis 让 NoSQL 快如闪电 (1)

spring data redis

和大多数 nosql 数据库一样,redis 舍弃了表格、行列的关系概念。而事实上,redis 是一种键值数据库,利用独特的字符串键值来存储和检索每条记录。redis 支持把以下内置数据结构作为所有记录的值:

<code>string</code> 保有单个字符串值。

<code>list</code>、<code>set</code> 和 <code>hash</code> 从语义上来说与 java 中的相同数据结构相一致。

<code>zset</code> 是由浮点分数安排的字符串列表,类似于 java 中的 <code>priorityqueue</code>。

不同于关系数据库管理系统中的表,redis 数据结构是即时实例化的。如果用户查询的内容不存在于 redis 中,系统只会返回空值。虽然 redis 不允许嵌套结构,但用户可以执行自定义的 java 或 json 串行器/解串器,从而将 pojo 映射到字符串。通过这种方式,就可以把任意 java bean 保存为 <code>string</code>,或者将其放置在 <code>list</code>、<code>set</code> 中,等等。

对于 redis,人们注意到的第一个特点可能就是它的速度极快。根据记录的大小和连接的数量,性能基准会有所不同,但延迟通常为单数位毫秒。在大多数用例中,redis 每秒最多可支持 50000 次请求。如果用户使用较高端的硬件,处理能力更可高达每秒 700000 次请求(但这一数值可能会被网卡带宽扼制)。

作为一种内存数据库,redis 的存储容量有限; aws ec2 中的最大实例为 r3.8xlarge,内存 244 gb。由于数据结构的索引和性能都经过优化,redis 消耗的内存比所存储的数据量大得多。切分 redis 有助于克服这一局限性。要把内存数据备份到硬盘上,可以在预定作业中进行时间点转储,也可以根据需要运行 <code>dump</code> 命令。

在 redis 中访问已缓存的对象,耗时通常不到数毫秒,和关系数据库查询相比,这大幅提升了应用程序的性能。

延迟和收益

在没有网络开销的系统中,本地缓存快于远程缓存。本地缓存的缺点是,同一个对象的多个拷贝在服务器集群中的各个不同节点之中会同步得更快。正因如此,本地缓存仅适用于静态数据,例如可容忍短期滞后和不一致现象的系统级设置。如果为易挥发的业务数据(例如用户数据和交易数据)使用本地缓存,很有可能会以运行应用程序服务器的单个实例而告终。

远程缓存服务器就没有这一局限性。在同一个键的情况下,可保证缓存服务器上的对象只有一个拷贝。只要用户让缓存中的对象及其数据库值彼此保持同步,就无需处理过期数据。

列表 1 给出了一个 spring 数据缓存的示例。

列表 1:在基于 spring 的应用中启用缓存

这里的读取操作被 spring 的 <code>@cacheable</code> 注释围绕,作为 aop 幕僚而执行。spring 中的存活时间设置也规定了这些对象可在缓存中停留的时间。调用 <code>get()</code> 方法后,spring 就会试着先从远程缓存读取和返回对象。如果未找到对象,spring 会执行方法主体,然后将数据库结果放在远程缓存中,之后再返回结果。

但如果另一个过程(例如另一个服务器节点)甚至同一个 jvm 中的另一个线程在数据库中更新了同一个对象,又会怎样呢?如果只运用 <code>@cacheable</code> 注释,你可能会从远程缓存服务器收到过期拷贝。

为了防止发生这种情况,可以给所有数据库更新操作添加一个 <code>@cacheput</code> 注释。每次调用这些方法时,返回值就会替换掉远程缓存中原先的对象。在数据库读取和写入上都更新缓存,可以让缓存服务器和后台数据之间的记录保持同步。

听起来简直完美,对吧?事实当然不是这样。利用列表 1 中的配置,负载较低时可能不会遇到任何问题,但随着服务器集群上的负载逐渐增加,远程缓存上就会出现过期数据。要做好准备应对服务器节点争用甚至更糟的情况。即使成功写入数据库,最后也可能会因为网络故障而使得缓存服务器 <code>put</code> 以失败告终。另外,nosql 通常不支持在关系数据库中存在完整事务语义,因为这会导致部分提交。为了让代码容错,可以考虑给数据模型增加版本号,实现乐观锁。

在收到 <code>optimisticlockingfailureexception</code> 或 <code>currentmodificationexception</code>(具体取决于持久性解决方案)时,可以调用带有 <code>@cacheevict</code> 注释的方法,从缓存中清除过期拷贝,然后重试同一个操作:

列表 2:解决缓存中的过期对象

结合 elasticache 使用 redis amazon elasticache 是一款内存缓存服务,可结合 memcached 或 redis 作为缓存服务器使用。虽然 elasticache 不在本文介绍范围内,但笔者还是想给各位开发人员介绍一个结合 redis 使用 elasticache 的技巧。对于大多数 redis 参数,使用其默认值并无大碍,但 <code>tcp-keepalive</code> 和 <code>timeout</code> 的默认 redis 设置并不会移除已无效的客户连接,最后还会耗尽缓存服务器上的套接口。结合 elasticache 使用 redis 时,务必每次都明确设置这两个值。

在本文的第二部分,将介绍 redis 的6大用例,敬请期待。