`
OneAPM_Official
  • 浏览: 25128 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

    博客分类:
  • java
阅读更多

【编者按】本文作者为 Xinyu Liu,文章的第一部分重点概述了 Redis 方方面面的特性。在第二部分,将介绍详细的用例。文章系国内 ITOM 管理平台 OneAPM 编译呈现。

 

把 Redis 当作数据库的用例

现在我们来看看在服务器端 Java 企业版系统中把 Redis 当作数据库的各种用法吧。无论用例的简繁,Redis 都能帮助用户优化性能、处理能力和延迟,让常规 Java 企业版技术栈望而却步。

 

1. 全局唯一增量计数器

我们先从一个相对简单的用例开始吧:一个增量计数器,可显示某网站受到多少次点击。Spring Data Redis 有两个适用于这一实用程序的类:RedisAtomicInteger 和RedisAtomicLong。和 Java 并发包中的 AtomicInteger 和 AtomicLong 不同的是,这些 Spring 类能在多个 JVM 中发挥作用。

列表 3:全局唯一增量计数器

RedisAtomicLong counter = 
    new RedisAtomicLong("UNIQUE_COUNTER_NAME", redisTemplate.getConnectionFactory()); 
Long myCounter = counter.incrementAndGet();// return the incremented value

请注意整型溢出并谨记,在这两个类上进行操作需要付出相对较高的代价。

 

2. 全局悲观锁

时不时的,用户就得应对服务器集群的争用。假设你从一个服务器集群运行一个预定作业。在没有全局锁的情况下,集群中的节点会发起冗余作业实例。假设某个聊天室分区可容纳 50 人。如果聊天室已满,就需要创建新的聊天室实例来容纳另外 50 人。

如果检测到聊天室已满但没有全局锁,集群中的各个节点就会创建自有的聊天室实例,为整个系统带来不可预知的因素。列表 4 介绍了应当如何充分利用 SETNXSET if **N**ot e**X**ists:如果不存在,则设置)这一 Redis 命令来执行全局悲观锁。

列表4:全局悲观锁

public String aquirePessimisticLockWithTimeout(String lockName,            int acquireTimeout, int lockTimeout) {        

  if (StringUtils.isBlank(lockName) || lockTimeout <= 0)            
      return null;        
      final String lockKey = lockName;
        String identifier = UUID.randomUUID().toString(); 
        Calendar atoCal = Calendar.getInstance();
        atoCal.add(Calendar.SECOND, acquireTimeout);
        Date atoTime = atoCal.getTime();        

        while (true) {            
           // try to acquire the lock            
           if (redisTemplate.execute(new RedisCallback<Boolean>() {                @Override                
           public Boolean doInRedis(RedisConnection connection)                        throws DataAccessException {                    
           return connection.setNX(
redisTemplate.getStringSerializer().serialize(lockKey), redisTemplate.getStringSerializer().serialize(identifier));
                }
            })) {   // successfully acquired the lock, set expiration of the lock
             redisTemplate.execute(new RedisCallback<Boolean>() {                      @Override                    
             public Boolean doInRedis(RedisConnection connection)                            throws DataAccessException {                        
              return connection.expire(redisTemplate
                                .getStringSerializer().serialize(lockKey),
                                lockTimeout);
                    }
                });                
                return identifier;
            } else { // fail to acquire the lock                
            // set expiration of the lock in case ttl is not set yet.                if (null == redisTemplate.execute(new RedisCallback<Long>() {                    @Override                    
            public Long 
      doInRedis(RedisConnection connection)                            
         throws DataAccessException 
         {                        
              return connection.ttl(redisTemplate
                                .getStringSerializer().serialize(lockKey));
                    }
                })) {                    // set expiration of the lock
                    redisTemplate.execute(new RedisCallback<Boolean>() 
                    {                        
                    @Override                        
                    public Boolean 

           doInRedis(RedisConnection connection)                                        throws DataAccessException {                            
           return connection.expire(redisTemplate
                                .getStringSerializer().serialize(lockKey),
                                    lockTimeout);
                        }
                    }); 
}                if (acquireTimeout < 0) // no wait                    
                 return null;                
                 else {                    
                     try {
                        Thread.sleep(100l); // wait 100 milliseconds before retry
                    } catch (InterruptedException ex) {
                    }
                }                if (new Date().after(atoTime))                    break;
            }
        }        return null;
    }    


    public void 
releasePessimisticLockWithTimeout(String lockName, String identifier) {        if (StringUtils.isBlank(lockName) || StringUtils.isBlank(identifier))            return;        

     final String lockKey = lockName;

        redisTemplate.execute(new RedisCallback<Void>() {                          @Override                    
        public Void doInRedis(RedisConnection connection)                            throws DataAccessException {                        
        byte[] ctn = connection.get(redisTemplate
                                .getStringSerializer().serialize(lockKey));                        if(ctn!=null && identifier.equals(redisTemplate.getStringSerializer().deserialize(ctn)))
                            connection.del(redisTemplate.getStringSerializer().serialize(lockKey));                        return null;
                    }
                });
    }

如果使用关系数据库,一旦最先生成锁的程序意外退出,锁就可能永远得不到释放。Redis 的 EXPIRE 设置可确保在任何情况下释放锁。

 

3. 位屏蔽(Bit Mask)

假设 web 客户端需要轮询一台 web 服务器,针对某个数据库中的多个表查询客户指定更新内容。如果盲目地查询所有相应的表以寻找潜在更新,成本较高。为了避免这一做法,可以尝试在 Redis 中给每个客户端保存一个整型作为脏指标,整型的每个数位表示一个表。该表中存在客户所需更新时,设置数位。轮询期间,不会触发对表的查询,除非设置了相应数位。就获取并将这样的位屏蔽设置为 STRING 而言,Redis 非常高效。

 

4. 排行榜(Leaderboard)

Redis 的 ZSET 数据结构为游戏玩家排行榜提供了简洁的解决方案。ZSET 的工作方式有些类似于 Java 中的 PriorityQueue,各个对象均为经过排序的数据结构,井井有条。可以按照分数排出游戏玩家在排行榜上的位置。Redis 的 ZSET 定义了一份内容丰富的命令列表,支持灵活有效的查询。例如,ZRANGE(包括 ZREVRANGE)可返回有序集内的指定范围要素。

你可以使用这一命令列出排行榜前 100 名玩家。ZRANGEBYSCORE 返回指定分数范围内的要素(例如列出得分为 1000 至 2000 之间的玩家),ZRNK 则返回有序集内的要素的排名,诸如此类。

 

5. 布隆(Bloom)过滤器

布隆过滤器 (Bloom filter) 是一种空间利用率较高的概率数据结构,用来测试某元素是否某个集的一员。可能会出现误报匹配,但不会漏报。查询可返回“可能在集内”或“肯定不在集内”。

就在线服务和离线服务包括大数据分析等方面,布隆过滤器数据结构都能派上很多用场。Facebook 利用布隆过滤器进行输入提示搜索,为用户输入的查询提取朋友和朋友的朋友。Apache HBase 则利用布隆过滤器过滤掉不包含特殊行或列的 HFile 块磁盘读取,使读取速度得到明显提升。Bitly 用布隆过滤器来避免将用户重定向到恶意网站,而 Quara 则在订阅后端执行了一个切分的布隆过滤器,用来过滤掉之前查看过的内容。在我自己的项目里,我用布隆过滤器追踪用户对各个主题的投票情况。

借助出色的速度和处理能力,Redis 极好地融合了布隆过滤器。搜索 GitHub,就能发现很多 Redis 布隆过滤器项目,其中一些还支持可调谐精度。

 

6. 高效的全局通知:发布/订阅渠道

Redis 发布/订阅渠道的工作方式类似于一个扇出消息传递系统,或 JMS 语义中的一个主题。JMS 主题和 Redis 发布/订阅渠道的一个区别是,通过 Redis 发布的消息并不持久。消息被推送给所有相连的客户端后,Redis 上就会删除这一消息。换句话说,订阅者必须一直在线才能接收新消息。Redis 发布/订阅渠道的典型用例包括实时配置分布、简单的聊天服务器等。

在 web 服务器集群中,每个节点都可以是 Redis 发布/订阅渠道的一个订阅者。发布到渠道上的消息也会被即时推送到所有相连节点。这一消息可以是某种配置更改,也可以是针对所有在线用户的全局通知。和恒定轮询相比,这种推送沟通模式显然极为高效。

 

Redis 性能优化

Redis 非常强大,但也可以从整体上和根据特定编程场景做出进一步优化。可以考虑以下技巧。

 

存活时间

所有 Redis 数据结构都具备存活时间 (TTL) 属性。当你设置这一属性时,数据结构会在过期后自动删除。充分利用这一功能,可以让 Redis 保持较低的内存损耗。

 

管道技术

在一条请求中向 Redis 发送多个命令,这种方法叫做管道技术。这一技术节省了网络往返的成本,这一点非常重要,因为网络延迟可能比 Redis 延迟要高上好几个量级。但这里存在一个陷阱:管道中的 Redis 命令列表必须预先确定,并且应当彼此独立。如果一个命令的参数是由先前命令的结果计算得出,管道技术就不起作用。列表 5 给出了 Redis 管道技术的一个示例。

列表 5:管道技术

@Override
public List<LeaderboardEntry> fetchLeaderboard(String key, String... playerIds) {    
   final List<LeaderboardEntry> entries = new ArrayList<>();
    redisTemplate.executePipelined(new RedisCallback<Object>() {    // enable Redis Pipeline        
    @Override 
        public Object doInRedis(RedisConnection connection) throws DataAccessException { 
            for(String playerId : playerIds) {
                Long rank = connection.zRevRank(key.getBytes(), playerId.getBytes());
                Double score = connection.zScore(key.getBytes(), playerId.getBytes());
                LeaderboardEntry entry = new LeaderboardEntry(playerId, 
                score!=null?score.intValue():-1, rank!=null?rank.intValue():-1);
                entries.add(entry);
            }        
            return null; 
        }
    }); 
    return entries; 
}
 

副本集和切分

Redis 支持主从副本配置。和 MongoDB 一样,副本集也是不对称的,因为从节点是只读的,以便共享读取工作量。我在文章开头提到过,也可以执行切分来横向扩展 Redis 的处理能力和存储容量。事实上,Redis 非常强大,据亚马逊公司的内部基准显示,类型 r3.4xlarge 的一个 EC2 实例每秒可轻松处理 100000 次请求。传说还有把每秒 700000 次请求作为基准的。对于中小型应用程序,通常无需考虑 Redis 切分。(请参见这篇非常出色的文章《运行中的 Redis》,进一步了解 Redis 的性能优化和切分。)

 

Redis 中的事务

Redis 并不像关系数据库管理系统那样能支持全面的 ACID 事务,但其自有的事务也非常有效。从本质上来说,Redis 事务是管道、乐观锁、确定提交和回滚的结合。其思想是执行一个管道中的一个命令列表,然后观察某一关键记录的潜在更新(乐观锁)。根据所观察的记录是否会被另一个进程更新,该命令列表或整体确定提交,或完全回滚。

下面以某个拍卖网站上的卖方库存为例。买方试图从卖方处购买某件商品时,你负责观察 Redis 事务内的卖方库存变化。同时,你要从同一个库存中删除此商品。事务关闭前,如果库存被一个以上进程触及(例如,如果两个买方同时购买了同一件商品),事务将回滚,否则事务会确定提交。回滚后可开始重试。

 

Spring Data Redis 中的事务陷阱

我在 Spring 的 RedisTemplate 类redisTemplate.setEnableTransactionSupport(true); 中启用 Redis 事务时得到一个惨痛的教训:Redis 会在运行几天后开始返回垃圾数据,导致数据严重损坏。StackOverflow 上也报道了类似情况。

在运行一个 monitor 命令后,我的团队发现,在进行 Redis 操作或 RedisCallback后,Spring 并没有自动关闭 Redis 连接,而事实上它是应该关闭的。如果再次使用未关闭的连接,可能会从意想不到的 Redis 密钥返回垃圾数据。有意思的是,如果在RedisTemplate 中把事务支持设为 false,这一问题就不会出现了。

我们发现,我们可以先在 Spring 语境里配置一个 PlatformTransactionManager(例如DataSourceTransactionManager),然后再用 @Transactional 注释来声明 Redis 事务的范围,让 Spring 自动关闭 Redis 连接。

根据这一经验,我们相信,在 Spring 语境里配置两个单独的 RedisTemplate 是很好的做法:其中一个 RedisTemplates 的事务设为 false,用于大多数 Redis 操作,另一个 RedisTemplates 的事务已激活,仅用于 Redis 事务。当然必须要声明PlatformTransactionManager 和 @Transactional,以防返回垃圾数值。

另外,我们还发现了 Redis 事务和关系数据库事务(在本例中,即 JDBC)相结合的不利之处。混合型事务的表现和预想的不太一样。

 

结论

我希望通过这篇文章向其他 Java 企业开发师介绍 Redis 的强大之处,尤其是将 Redis 用作远程数据缓存和用于易挥发数据时。在这里我介绍了 Redis 的六个有效用例,分享了一些性能优化技巧,还说明了我的 Glu Mobile 团队怎样解决了 Spring Data Redis 事务配置不当造成的垃圾数据问题。我希望这篇文章能够激发你对 Redis NoSQL 的好奇心,让你能够受到启发,在自己的 Java 企业版系统里创造出一番天地。

本文系 OneAPM 工程师编译整理。OneAPM 能为您提供端到端的 Java 应用性能解决方案,我们支持所有常见的 Java 框架及应用服务器,助您快速发现系统瓶颈,定位异常根本原因。分钟级部署,即刻体验,Java 监控从来没有如此简单。想阅读更多技术文章,请访问 OneAPM 官方技术博客

本文转自 OneAPM 官方博客

原文地址:http://www.javaworld.com/article/3062899/big-data/lightning-fast-nosql-with-spring-data-redis.html?page=2

 

 
分享到:
评论

相关推荐

    spring data redis api jar

    Spring Data项目旨在简化对各种数据存储技术的访问,包括关系数据库、NoSQL数据库、图数据库等,而Spring Data Redis API则专注于提供与Redis的无缝连接。 Spring Data Redis API的核心特性包括: 1. **连接池支持...

    spring data redis 小例子

    首先,Spring Data Redis是Spring Data项目的一部分,该项目致力于简化各种数据存储的访问,包括关系型数据库、NoSQL存储以及缓存系统如Redis。通过Spring Data Redis,你可以使用Java或Groovy API来方便地执行Redis...

    spring data redis 官方文档

    1. **连接 Redis**:Spring Data Redis 提供了多种方式来配置 Redis 连接,如 Jedis、JRedis(已废弃)、SRP(已废弃)和 Lettuce。 - **Jedis Connector**:使用广泛且稳定的 Java 客户端 Jedis 进行连接配置。 -...

    Spring Data Redis中文参考文档

    ### Spring Data Redis中文参考文档知识点总结 #### 一、Spring Data Redis概述 **Spring Data Redis** 是Spring Data项目的一部分,它为开发人员提供了利用Redis这一高性能键值存储数据库的能力。该文档介绍了...

    Spring-data-redis使用指南

    - **了解 NoSQL 和键值存储**: Redis 是一种键值存储系统,理解其基本原理对于有效使用 Spring Data Redis 至关重要。 - **尝试示例代码**: 通过官方文档提供的示例代码来快速上手。 #### 六、获取帮助 - **社区...

    spring-data + jedis + redis代码

    4. 使用Spring Data的注解(如@Repository、@EnableRedisRepositories)来定义仓库接口,并让Spring自动实现这些接口,提供对Redis的访问。 在压缩包中的"redis+spring-data"文件可能包含以下内容: - spring配置...

    spring-data-redis-1.0.4.RELEASE-dist.zip

    Spring Data提供了一种统一的编程模型,适用于多种数据存储系统,包括关系数据库、NoSQL数据库和键值存储,如Redis。 2. **Redis介绍** Redis是一个高性能的键值存储系统,特别适合于处理大量实时数据。它支持多种...

    spring-data-redis-tools-master.zip

    2. **数据类型支持**:Spring Data Redis支持Redis的所有基础数据类型,如字符串(Strings)、哈希(Hashes)、列表(Lists)、集合(Sets)和有序集合(Sorted Sets),并提供了丰富的操作方法,如设置、获取、删除、更新等。...

    spring--redis所需要的所有jar

    Spring提供了对多种数据源的支持,包括关系型数据库、NoSQL数据库以及缓存系统如Redis。本篇文章将详细阐述Spring与Redis集成的相关知识点,以及如何利用提供的jar包进行配置与使用。 首先,Redis是一个开源的、...

    spring与Redis的入门Demo

    Spring 还提供了一个数据访问层,可以方便地与各种数据库进行交互,包括 NoSQL 数据库如 Redis。 **Redis** 是一个高性能的键值对数据存储系统,通常作为缓存或消息代理使用。它支持多种数据结构,如字符串、哈希、...

    springboot整合Redis

    Spring Data Redis 是 Spring 的一部分,提供了在 Spring 应用中通过简单的配置就可以访问 Redis 服务,对 Redis 底层开发包进行了高度封装。在 Spring 项目中,可以使用Spring Data Redis来简化 Redis 操作。 ...

    SpringDataRedisDemo

    Redis是一个高性能的键值对数据库,常用于缓存和消息代理,而SpringDataRedis是Spring Framework的一个模块,它提供了与Redis交互的高级抽象。 在**单机Redis-Demo**中,项目会展示如何配置Spring Data Redis来连接...

    尚硅谷SpringData视频观看下载链接

    SpringData Redis提供了对Redis内存数据存储的访问,用于缓存、消息队列等功能。关键特性有: - 提供RedisTemplate和ReactiveRedisTemplate,用于同步和响应式操作。 - 支持多种数据结构,如字符串、列表、集合、...

    Spring Redis操作手册

    - **环境配置:** 在正式学习Spring Data Redis前,建议先完成环境配置,确保开发环境支持Redis。 - **资源链接:** - **Redis入门教程:** [Redis入门教程]...

    Spring Data:Spring Data Modern Data Access for Enterprise Java

    2. Repositories: Convenient Data Access Layers 3. Type-Safe Querying Using Querydsl . . Part II. Relational Databases 4. JPA Repositories 5. Type-Safe JDBC Programming with Querydsl SQL Part III. ...

    Redis快速入门ppt(1)

    SpringDataRedis 是一个基于 Spring 框架的 Redis 客户端,提供了对 Redis 的高级操作。 学习目标 本课程的学习目标是: 1. 了解 NoSQL 与关系型数据库的区别 2. 熟悉 Redis 的常用五种数据结构 3. 熟悉 Redis 的...

    SpringData.pdf

    对于NoSQL数据库,SpringData支持MongoDB(文档数据库)、Neo4j(图形数据库)、Redis(键/值存储)和Hbase(列族数据库)。对于关系型数据库,SpringData主要支持JDBC和JPA技术。 SpringDataJPA是SpringData项目中...

    redis nosql java

    3. **Spring Data Redis**:作为Spring框架的一部分,Spring Data Redis提供了一套完整的解决方案来简化Redis与Java应用的集成。 - **RedisTemplate**:用于执行CRUD操作,支持各种数据结构的操作。 - **Redis配置...

    Spring Data实战

    Spring Data主要分为多个模块,如JPA、MongoDB、Neo4j、Redis等,分别对应不同的数据存储技术。每个模块都提供了与特定存储系统交互的API和工具。 3. **Repository抽象** Spring Data的核心是Repository抽象,它...

Global site tag (gtag.js) - Google Analytics