Spring Data Redis 让 NoSQL 快如闪电
《Spring Data Redis 让 NoSQL 快如闪电》要点: 【编者按】本文作者为 Xinyu Liu,详细介绍了 Redis 的特性,并辅之以丰富的用例.在本文的第一部分,将重点概述 Redis 的方方面面.文章系国内 ITOM 管理平台 OneAPM 编译呈现. 建立在 Java 企业版之上的多层体系结构是强大的服务器端编程解决方案.作为一名从业多年的 Java 企业版开发人员,我最满意的就是三层企业开发法:最下方是 JPA/Hibernate 持久层,中间是 Spring 或 EJB 应用层,最上方则是 web 层.对于较为复杂的用例,我用 BPM(业务流程管理)、一个类似于 Drools 的规则引擎和一个集成框架(例如 Camel)集成了一个工作流驱动的解决方案. 但是,笔者最近接到一个任务,要设计一个拥有亚秒级响应延迟并能支持成千上万名并发用户的系统.我立即发现了自己常用的 Java 企业版栈区的局限性.基于关系数据库管理系统的传统型 web 应用程序,包括在 Hibernate/JPA 之上构建的应用程序,都有二阶延迟,扩展效果不佳.传统的 Java 企业版持久性体系结构无法满足我当时设计的系统的性能和处理能力要求.然后我转而尝试 NoSQL,最后发现了 Redis. 作为一种内存键值数据库,Redis 打破了数据库的传统定义(将数据保存在硬盘上).反之,使用 Redis 时可结合持久性的 NoSQL 数据库,比如 MongoDB、HBase、Cassandra 或 DynamoDB.Redis 以远程缓存服务器见长,对易挥发数据来说是极快型数据库. 在本文中,笔者会介绍一些有关 Redis 的简单用例和进阶用例以及性能调优情况.当然,我还会做个简单概述,但我相信各位基本都了解 NoSQL 及其各种解决方案.
Redis 概述 和大多数 NoSQL 数据库一样,Redis 舍弃了表格、行列的关系概念.而事实上,Redis 是一种键值数据库,利用独特的字符串键值来存储和检索每条记录.Redis 支持把以下内置数据结构作为所有记录的值:
不同于关系数据库管理系统中的表,Redis 数据结构是即时实例化的.如果用户查询的内容不存在于 Redis 中,系统只会返回空值.虽然 Redis 不允许嵌套结构,但用户可以执行自定义的 Java 或 JSON 串行器/解串器,从而将 POJO 映射到字符串.通过这种方式,就可以把任意 Java bean 保存为 性能和可扩展性 对于 Redis,人们注意到的第一个特点可能就是它的速度极快.根据记录的大小和连接的数量,性能基准会有所不同,但延迟通常为单数位毫秒.在大多数用例中,Redis 每秒最多可支持 50000 次哀求.如果用户使用较高端的硬件,处理能力更可高达每秒 700000 次哀求(但这一数值可能会被网卡带宽扼制). 作为一种内存数据库,Redis 的存储容量有限; AWS EC2 中的最大实例为 r3.8xlarge,内存 244 GB.由于数据结构的索引和性能都经过优化,Redis 消耗的内存比所存储的数据量大得多.切分 Redis 有助于克服这一局限性.要把内存数据备份到硬盘上,可以在预定作业中进行时间点转储,也可以根据需要运行 用 Spring 进行远程数据缓存 要想提升应用程序服务器的性能,数据缓存可能是性价比最高的办法了.利用 Spring 的缓存抽象注释( Encache 通常被配置成本地缓存层,具有嵌套结构,在应用的 JVM 上运行. Memcached 和 Redis 都能作为独立的缓存服务器运行.要想把 Redis 缓存集成到基于 Spring 的应用中,需要使用 Spring Data Redis的 RedisTemplate 和 RedisCacheManager. 在 Redis 中拜访已缓存的对象,耗时通常不到数毫秒,和关系数据库查询相比,这大幅提升了应用程序的性能.
本地缓存与远程缓存 在没有网络开销的系统中,本地缓存快于远程缓存.本地缓存的缺点是,同一个对象的多个拷贝在服务器集群中的各个不同节点之中会同步得更快.正因如此,本地缓存仅适用于静态数据,例如可容忍短期滞后和不一致现象的系统级设置.如果为易挥发的业务数据(例如用户数据和交易数据)使用本地缓存,很有可能会以运行应用程序服务器的单个实例而告终. 远程缓存服务器就没有这一局限性.在同一个键的情况下,可保证缓存服务器上的对象只有一个拷贝.只要用户让缓存中的对象及其数据库值彼此保持同步,就无需处理过期数据. 列表 1 给出了一个 Spring 数据缓存的示例. 列表 1:在基于 Spring 的应用中启用缓存 @Cacheable(value="User_CACHE_REPOSITORY",key = "#id") public User get(Long id) { return em.find(User.class,id); } @Caching(put = {@CachePut(value="USER_CACHE_REPOSITORY",key = "#user.getId()")}) public User update(User user) { em.merge(user); return user; } @Caching(evict = {@CacheEvict(value="USER_CACHE_REPOSITORY",key = "#user.getId()")}) public void delete(User user) { em.remove(user); } @Caching(evict = {@CacheEvict(value="USER_CACHE_REPOSITORY",key = "#user.getId()")}) public void evictCache(User user) { } 这里的读取操作被 Spring 的 但如果另一个过程(例如另一个服务器节点)甚至同一个 JVM 中的另一个线程在数据库中更新了同一个对象,又会怎样呢?如果只运用 为了防止发生这种情况,可以给所有数据库更新操作添加一个 容错 听起来简直完美,对吧?事实当然不是这样.利用列表 1 中的配置,负载较低时可能不会遇到任何问题,但随着服务器集群上的负载逐渐增加,远程缓存上就会出现过期数据.要做好准备应对服务器节点争用甚至更糟的情况.即使成功写入数据库,最后也可能会因为网络故障而使得缓存服务器 在收到 列表 2:解决缓存中的过期对象 try{ User user = userDao.get(id); // user fetched in cache server userDao.update(user,oldname,newname); }catch(ConcurrentModificationException ex) { // cached user object may be stale userDao.evictCache(user); user = userDao.get(id); // refresh user object userDao.update(user,newname); // retry the same operation. Note it may still throw legitimate ConcurrentModificationException.}
在本文的第二部分,将介绍 Redis 的6大用例,敬请期待. 本文系 OneAPM 工程师编译整理.OneAPM 能为您提供端到端的 Java 应用性能解决方案,我们支持所有常见的 Java 框架及应用服务器,助您快速发现系统瓶颈,定位异常根本原因.分钟级部署,即刻体验,Java 监控从来没有如此简单.想阅读更多技术文章,请拜访 OneAPM 官方技术博客. 编程之家PHP培训学院每天发布《Spring Data Redis 让 NoSQL 快如闪电》等实战技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培养人才。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |