天天看點

Spring資料和Redis

本文是我們學院課程的一部分,标題為Redis NoSQL鍵值存儲 。

這是Redis的速成班。 您将學習如何安裝Redis并啟動伺服器。 此外,您将在Redis指令行中亂七八糟。 接下來是更進階的主題,例如複制,分片和叢集,同時還介紹了Redis與Spring Data的內建。 在這裡檢視 !

目錄

1.簡介 2.先決條件 3.選擇Redis Java用戶端 4. Java應用程式依賴性和Eclipse 5.在Spring資料Redis上配置獨立Redis 6.配置與分片(分區)Redis的連接配接 7.配置與Redis群集的連接配接 8.使用Spring Data Redis通路Redis 9.使用Spring Data Redis進行事務 10.使用Spring Data Redis進行流水線 11.使用Spring Data Redis釋出/訂閱 12.結論

1.簡介

建立Redis是為了解決實際軟體系統的實際問題。 到目前為止,我們已經探索了非常豐富的Redis功能集,但實際上并沒有在實際的應用程式中使用它們。 為了填補這一空白,本教程的最後一部分專門介紹了此主題。 我們将建構一個使用Redis和出色的Spring Data Redis ( http://projects.spring.io/spring-data-redis/ )項目以及Spring Framework ( http://projects.spring.io )的簡單Java應用程式。 / spring-framework / ),請參閱Spring項目組合( http://spring.io/ )。 撰寫本文時, Spring Data Redis和Spring Framework的最新釋出版本分别為1.2.0和4.0.2 。

在深入探讨細節之前,值得一提的是,Redis支援各種應用程式架構和程式設計語言。 用戶端的完整清單可在此處找到: http : //redis.io/clients 。

本教程的其餘部分假定讀者能夠使用Java進行程式設計,并且對Spring Framework ( http://projects.spring.io/spring-framework/ )具有基本的了解。

2.先決條件

Java開發人員的常用工具集包括JDK(Java開發工具包)和諸如Eclipse或Intellij IDEA之類的IDE,以促進應用程式開發過程。 我們将使用的最新JDK版本是1.7_51 ,可以從http://www.oracle.com/technetwork/java/javase/downloads/index.html下載下傳。

IDE的選擇是

Eclipse

,它的最新版本是4.3.2 ,可以從https://www.eclipse.org/downloads/下載下傳( 适用于Java開發人員的 Eclipse IDE,适用于Java EE開發人員的Eclipse IDE或Spring Tool Suite版本很好)。

3.選擇Redis Java用戶端

有幾種Java用戶端可用于從Java應用程式通路Redis( Spring Data Redis也支援下面列出的所有用戶端):

  • 傑迪斯: https : //github.com/xetorthio/jedis
  • JRedis: https : //github.com/alphazero/jredis
  • 生菜: https : //github.com/wg/lettuce

從功能的角度來看,它們都很相似,但是Jedis已獲得越來越多的普及并被廣泛使用。 話雖如此, Jedis也是我們應用程式的選擇。

4. Java應用程式依賴性和Eclipse

我們将要建構的項目将介紹到目前為止已經讨論過的所有重要Redis功能,但這些功能來自應用程式開發人員。 我們将從一個基本示例開始,假設有一個獨立的Redis執行個體并在某個地方運作(讓我們将其稱為

redis-host

)。 大多數方案将以一個小的JUnit ( http://junit.org/ )測試片段的形式呈現。

Java世界中事實上的建構和依賴性管理工具仍然是Apache Maven ( http://maven.apache.org/ ),我們将使用的版本是3.1.1,可以從http://maven.apache下載下傳。 org / docs / 3.1.1 / release-notes.html 。 實際上,我們不會使用太多的Apache Maven來讓Eclipse代表我們完成這項工作,但是我們将研究基本的依賴項描述檔案,該檔案通常稱為

pom.xml

<project 
    xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://maven.apache.org/POM/4.0.0 
        http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.javacodegeeks</groupId>
    <artifactId>redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.2.0.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.4.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.0.13</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-all</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>

        <dependency>
              <groupId>com.jayway.awaitility</groupId>
              <artifactId>awaitility</artifactId>
              <version>1.5.0</version>
              <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
           

如果您是經驗豐富的Java開發人員,那麼

pom.xml

檔案應該非常熟悉。 但是對于新手來說,一些評論可能會有所幫助。 基本上,我們聲明名為

com.javacodegeeks.redis

的項目取決于:

  • 傑迪斯(

    redis.clients.jedis

  • Spring Data Redis(

    org.springframework.data.spring-data-redis

  • Spring架構(

    org.springframework.spring-core

    org.springframework.spring-context

    org.springframework.spring-tx

    org.springframework.spring-test

  • JUnit和随附的測試腳手架(

    junit.junit

    org.hamcrest.hamcrest-all

    com.jayway.awaitility.awaitility

至此,我們可以使用Existing Maven Project功能的Import(菜單檔案-> Import…)将

pom.xml

導入Eclipse 。

Spring資料和Redis

圖1:将現有的Maven項目導入Eclipse

導入完成後,名為

com.javacodegeeks.redis

的項目(或隻是

redis

,具體取決于您的導入設定)應出現在Eclipse Project Explorer視圖中。

Spring資料和Redis

圖2. Eclipse Project視圖中的

com.javacodegeeks.redis

5.在Spring資料Redis上配置獨立Redis

Jedis的Redis配置從定義

JedisConnectionFactory

開始。 預設情況下,Jedis使用連接配接池( http://en.wikipedia.org/wiki/Connection_pool ),以便每次都不建立到Redis伺服器的連接配接,而是從可用連接配接池中借用它們。 總的來說,這被認為是一種好的做法,因為建立網絡連接配接的過程是一個相對昂貴的操作。

讓我們将連接配接池和連接配接工廠定義為單獨的Spring配置bean,以便可以由不同的應用程式配置獨立地導入它。

package com.javacodegeeks.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;

import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Protocol;

@Configuration
public class ConnectionConfiguration  {
    @Bean
    public JedisPoolConfig poolConfig() {
        final JedisPoolConfig jedisPoolConfig =  new JedisPoolConfig();         
        jedisPoolConfig.setTestOnBorrow( true );
        jedisPoolConfig.setMaxTotal( 10 );
        return jedisPoolConfig;
    }
    
    @Bean
    public JedisConnectionFactory connectionFactory() {
        final JedisConnectionFactory connectionFactory = 
            new JedisConnectionFactory( poolConfig() );     
        connectionFactory.setHostName( "redis-host" );
        connectionFactory.setDatabase( Protocol.DEFAULT_DATABASE );
        connectionFactory.setPort( Protocol.DEFAULT_PORT );        
        return connectionFactory;
    }
}
           

在此代碼段中,我們将連接配接工廠配置為在具有最多10個連接配接池的

redis-host

上運作的Redis執行個體。

test on borrow

設定實際上確定從池借用的連接配接仍然有效并且可以使用(否則将重新建立連接配接)。

6.配置與分片(分區)Redis的連接配接

在第4部分 Redis Sharding中 ,我們讨論了用戶端分區。 實際上,Jedis提供了此功能,但不幸的是Spring Data Redis尚不支援此功能。

7.配置與Redis群集的連接配接

在第5部分“ Redis群集”中 ,我們發現了Redis的群集功能,并提到用戶端應該支援和識别Redis協定中的更改,以便向正确的節點發出指令。 Jedis已經提供了對Redis群集的支援,但是不幸的是Spring Data Redis尚不支援此功能。

8.使用Spring Data Redis通路Redis

Spring Data Redis在不同的Redis用戶端上提供一緻而簡潔的程式設計抽象(請參閱選擇Redis Java用戶端)。 這種抽象的核心是模闆的概念:最簡單的方法提供對所需功能的通路,而無需花費大量時間編寫樣闆代碼。 如果是Redis,則為

RedisTemplate

從本教程的前面的部分中,我們知道Redis支援多種原始資料類型:字元串和數字。 但是Java類型系統比這要豐富得多,這就是為什麼

RedisTemplate

要求鍵的類型和值的類型(以及這些類型的序列化器)進行操作的原因。 我們将從一個簡單的示例開始,在該示例中,鍵和值隻是字元串(實際上, Spring Data Redis已經包含了一個名為

StringRedisTemplate

的類,但盡管如此,讓我們看一下一般概念)。

package com.javacodegeeks.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Import( value = ConnectionConfiguration.class )
public class ApplicationConfiguration {
    @Bean @Autowired
    public RedisTemplate< String, String > redisTemplate( 
            final JedisConnectionFactory connectionFactory ) {        
        final RedisTemplate< String, String > template = 
            new RedisTemplate< String, String >();        
        template.setConnectionFactory( connectionFactory );
        template.setKeySerializer( new StringRedisSerializer() );
        template.setHashValueSerializer( new StringRedisSerializer() );
        template.setHashKeySerializer( new StringRedisSerializer() );
        template.setValueSerializer( new StringRedisSerializer() );
        template.setStringSerializer( new StringRedisSerializer() );
        return template;
    }
}
           

這樣,我們就可以編寫我們的第一個測試了。 我們将要建立的所有測試用例的容器是一個名為

RedisStringsTestCase

的類,該類大量使用Spring Test腳手架提供的測試功能。

package com.javacodegeeks.redis;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;

import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( classes = ApplicationConfiguration.class )
public class RedisStringsTestCase {
    @Autowired private RedisTemplate< String, String > template;

    // Out tests are going to be there    
}
           

通常,對于Redis指令的每種類别(請參閱第2部分 , Redis指令–使用Redis指令行 ),

RedisTemplate

具有從“ ops ”(opsForValue,opsForList,opsForHash等)開始的façade方法,該方法從字面上傳回一對一的一種類别特定指令到Java方法調用的映射。 第一個測試使用

SET

指令存儲一些鍵/值,并立即使用

GET

指令來驗證它是否正确存儲。

@Test
public void testSetAndGet() {
    template.opsForValue().set( "mykey", "myvalue" );
    assertThat( template.opsForValue().get( "mykey"), equalTo( "myvalue" ) );
}
           

為了確定測試不會失敗,我們可以使用

redis-cli

工具并連接配接到

redis-host

執行個體來檢查

mykey

值是否為“ myvalue ”。

Spring資料和Redis

圖3.驗證

mykey

值是否确實存儲在

redis-host

執行個體上

在轉到下一個測試用例之前,需要考慮一個問題:上一個測試完成執行後,

mykey

将保留在Redis中,直到有人将其删除為止。 這種行為可能會導緻其他測試用例失敗,通常被認為是不良做法。 最好使用幹淨的資料庫開始每個測試,實際上我們将使它像這樣工作。 清除目前Redis資料庫的指令是

FLUSHDB

。 它不是直接由

RedisTemplate

公開的,但是可以通過使用

execute()

方法并在基礎

RedisConnection

上調用

flushDb()

來輕松通路和觸發。

@Before
public void setUp() {
    template.execute( new RedisCallback< Void >() {
        @Override
        public Void doInRedis( RedisConnection connection ) throws DataAccessException {
            connection.flushDb();
            return null;
        }
    } );
}
           

我們将要探索的下一個操作是

INCREMENT

指令,該指令示範了字元串和包含數字的字元串之間的透明操作是如何進行的。

@Test
public void testSetAndIncrementAndGet() {
    template.opsForValue().set( "mykey", "10" );
    template.opsForValue().increment( "mykey", 5 );
    assertThat( template.opsForValue().get( "mykey"), equalTo( "15" ) );
}
           

足夠簡單,讓我們進入更複雜的資料結構:清單,集合和排序集合。 第一個測試用例建立一個值清單“ a ”,“ b ”,“ c ”,“ d ”(使用

RPUSH

指令)。 然後它檢查清單(大小

LLEN

指令),請求由索引(該清單的最後一個元素

LINDEX

指令),最後從清單(彈出所述第一進制件

LPOP

指令)。

@Test
public void testPushToListAndGetElementByIndexAndPopFirstElement() {
    template.opsForList().rightPushAll( "mykey", "a", "b", "c", "d" );
    assertThat( template.opsForList().size( "mykey" ), equalTo( 4L ) );
    assertThat( template.opsForList().index( "mykey", 3 ), equalTo( "d" ) );
    assertThat( template.opsForList().leftPop( "mykey" ), equalTo( "a" ) );
    assertThat( template.opsForList().size( "mykey"), equalTo( 3L ) );
}
           

該代碼看起來非常緊湊且易讀。 讓我們從清單轉到集,然後下一個測試用例建立一組值“ a ”,“ b ”,“ c ”,“ d ”(使用

SADD

指令)。 然後,它檢查集合的大小(使用

SCARD

指令),并詢問“ c ”和“ e ”是否是該集合的成員(使用

SISMEMBER

指令)。

@Test
public void testAddToSetAndCheckElementExists() {
    template.opsForSet().add( "mykey", "a", "b", "c", "d" );
    assertThat( template.opsForSet().size( "mykey" ), equalTo( 4L ) );
    assertThat( template.opsForSet().isMember( "mykey", "c" ), equalTo( true ) );
    assertThat( template.opsForSet().isMember( "mykey", "e" ), equalTo( false ) );
}
           

組示出了全功率向上在幀間的一組操作:交叉點(

SINTER

指令),接頭(

SUNION

指令)和差(

SDIFF

指令)。 下面的測試案例通過将這些操作應用于兩組來示範該操作。

@Test
public void testIntersetOperations() {
    template.opsForSet().add( "mykey1", "a", "b", "c", "d" );
    template.opsForSet().add( "mykey2", "c", "d", "e", "f" );
       
    assertThat( template.opsForSet().intersect( "mykey1", "mykey2" ), 
         equalTo( set( "c", "d" ) ) );
        
    assertThat( template.opsForSet().union( "mykey1", "mykey2" ), 
        equalTo( set( "a", "b", "c", "d",  "e", "f" ) ) );

    assertThat( template.opsForSet().difference( "mykey1", "mykey2" ), 
        equalTo( set( "a", "b" ) ) );
}
           

為了完成資料收集類型,我們将介紹Spring Data Redis稱為ZSets的排序集。 下面的測試用例建立一個排序集(

ZADD

指令),然後要求Redis傳回按分數從高到低排序的所有成員(帶有

WITHSCORE

選項的

ZREVRANGEBYSCORE

指令)。

@Test
public void testAddToSortedSetAndCheckElementsAreSortedByScore() {
    template.opsForZSet().add( "mykey", "a", 6.15d );
    template.opsForZSet().add( "mykey", "b", 9.95d );
    template.opsForZSet().add( "mykey", "c", 8.45d );
        
    assertThat( template.opsForZSet().reverseRangeByScoreWithScores( "mykey", 0d, 10d ),
        equalTo( 
           set( 
                ( TypedTuple< String > )new DefaultTypedTuple< String >( "b", 9.95d ), 
                ( TypedTuple< String > )new DefaultTypedTuple< String >( "a", 6.15d ), 
                ( TypedTuple< String > )new DefaultTypedTuple< String >( "c", 8.45d ) 
           ) 
        ) 
     );
}
           

由于需要進行通用類型轉換,是以代碼有點冗長,但通常也很簡單且可讀性強。

最後,我們将把注意力轉移到哈希上。 Redis哈希可以被認為是Java中的資料對象:屬性(或字段)及其值的容器。 下一個測試用例(使用

HSET

指令)建立具有兩個屬性(或字段),“ prop1 ”和“ prop2 ”的哈希 。 然後,它驗證所有屬性及其值是否正确存儲(使用

HGETALL

指令),從哈希中删除所有屬性(字段)(使用

HDEL

指令),并檢查它們是否确實被删除(使用

HGET

指令)。

@Test
public void testHashOperations() {
    template.opsForHash().put( "mykey", "prop1", "value1"  );
    template.opsForHash().put( "mykey", "prop2", "value2" );        
        
    assertThat( template.opsForHash().entries( "mykey" ), 
        equalTo( map( "prop1", "value1", "prop2", "value2" ) ) );

    assertThat( template.opsForHash().get( "mykey", "prop1" ), 
        equalTo( ( Object )"value1" ) );
        
    template.opsForHash().delete( "mykey", "prop1", "prop2" );
    assertThat( template.opsForHash().get( "mykey", "prop1" ), 
        equalTo( null ) );
}
           

在本節中,我們介紹了Spring Data Redis的一些基礎知識,并很好地了解了Redis指令如何映射到其API。 我們開發的測試用例的數量隻是瞥了一眼豐富的Spring Data Redis功能集。 在接下來的三個部分中,我們将研究進階API模式:事務,管道和釋出/訂閱。

9.使用Spring Data Redis進行事務

盡管Redis交易支援在一定程度上受到限制,但是在需要時它仍然是一個非常有用的功能。 為了展示Spring Data Redis如何支援Redis事務語義,我們将建立一個測試用例:

  • 為兩個鍵配置設定一些值:

    mykey1

    SET

    指令)和

    mykey2

    SADD

    指令)
  • 驗證成員“ a ”不在

    mykey2

    集中(

    SISMEMBER

    指令)
  • 開始監視密鑰

    mykey1

    WATCH

    指令)
  • 啟動事務(

    MULTI

    指令)
  • 遞增

    mykey1

    INCREMENT

    指令)
  • 将新成員“ b ”添加到集合

    mykey2

    SADD

    指令)
  • 通過發出

    EXEC

    指令來完成事務
@Test
public void testTransaction() {
    template.opsForValue().set( "mykey1", "10" );                
        
    template.opsForSet().add( "mykey2", "a" );        
    assertThat( template.opsForSet().isMember( "mykey2", "b"), equalTo( false ) );
        
    template.execute( new SessionCallback< List< Object > >() {
        @SuppressWarnings("unchecked")
        @Override
        public< K, V > List<Object> execute( final RedisOperations< K, V > operations )
                throws DataAccessException {
                
            operations.watch( ( K )"mykey1" );
            operations.multi();                          
                
            operations.opsForValue().increment( ( K )"mykey1", 5 );
            operations.opsForSet().add( ( K )"mykey2", ( V )"b" );
                
            return operations.exec();
        }
    } );
        
    assertThat( template.opsForValue().get( "mykey1"), equalTo( "15" ) );
    assertThat( template.opsForSet().isMember( "mykey2", "b"), equalTo( true ) );
}
           

但是,我們忽略了template.execute()方法調用的傳回值,它傳回每個指令的結果。 在我們的測試案例中,

INCREMENT

的結果為15 ,

SADD

的結果為1 。

10.使用Spring Data Redis進行流水線

我們大部分時間通路Redis的方式是單個指令/響應序列:為了發送新指令,用戶端應等待Redis伺服器傳回上一個指令的結果。 但是,有一種方法可以将多個指令發送到伺服器,而無需等待任何響應,最後一步即可讀取所有響應。 這種技術稱為

pipelining

Redis從很早的發行版開始就支援流水線化,是以無論您運作的是哪個版本,都可以在Redis中使用流水線化(有關更多詳細資訊,請參見http://redis.io/topics/pipelining )。

流水線可通過減少網絡延遲來顯着提高應用程式的性能。 但是有一個陷阱:當任何用戶端使用流水線技術發送指令時,伺服器将被迫将響應排隊在記憶體中。 如果需要通過管道傳遞大量指令,則最好将這些指令發送到給定的合理數量(以便将它們拆分為多個管道)。 性能将幾乎相同,但使用的額外記憶體将受到限制。

以下測試案例示範了如何使用Spring Data Redis進行流水線操作。 我們将使用流水線發送100條指令,并通過将計數器的值與預期值進行比較來驗證是否已發送并處理了所有指令。

@Test
public void testPipelining() {
    template.opsForValue().set( "mykey1", "10" );                
                 
    template.executePipelined( new RedisCallback< Object >() {
        @Override
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
            for( int i = 0; i < 100; ++i ) {
                template.opsForValue().increment( "mykey1", 1 );
            }
            return null;
        }
    } );
        
    assertThat( template.opsForValue().get( "mykey1"), equalTo( "110" ) );
}
           

與Redis事務測試用例一樣,我們忽略了

template.executePipelined()

方法調用的傳回值,但它傳回每個指令的結果(總共100個結果)。 另外,如果您想知道為什麼我們從

RedisCallback

傳回null ,則是有原因的:此傳回值将被響應中的實際值(收到時)覆寫,是以該回調不允許傳回非null值(請參閱請通路http://docs.spring.io/spring-data/data-redis/docs/1.2.0.RELEASE/reference/html/redis.html#pipeline了解更多資訊)。

11.使用Spring Data Redis釋出/訂閱

Redis支援釋出/訂閱消息傳遞範例, Spring Data Redis也提供對此功能的全面支援。

從本質上講,釋出/訂閱消息傳遞至少涉及兩個參與者:釋出消息的釋出者和偵聽釋出者的消息的訂閱者(通常是多對多關系,但我們已将其簡化為單個釋出者/訂閱者模型)。

為此開發一個健壯的測試用例可能會有些棘手。 釋出是很容易的部分,但是在特定管道(或模式)上偵聽消息需要做一些工作。 我們首先定義稱為

RedisMessageListener

的訂戶類。 它不會做很多,但會計算到目前為止已收到的所有消息。

package com.javacodegeeks.redis;

import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;

public class RedisMessageListener implements MessageListener {
    private AtomicInteger count = new AtomicInteger( 0 ); 
    
    @Override
    public void onMessage(Message message, byte[] pattern) {
        count.incrementAndGet();
    }
    
    public int getCount() {
        return count.get();
    }
}
           

接下來,我們應該使用

RedisMessageListener

RedisMessageListenerContainer

其他bean擴充配置。 後者的作用非常重要:它将聽衆和他們正在收聽的頻道粘合在一起。

PubsubConfiguration

我們開始所需的最低配置(

PubsubConfiguration

)(請注意,我們正在導入之前建立的

ApplicationConfiguration

)。

package com.javacodegeeks.redis;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;

@Configuration
@Import( value = ApplicationConfiguration.class )
public class PubsubConfiguration  {
    @Bean @Autowired
    public RedisMessageListenerContainer container( 
            final JedisConnectionFactory connectionFactory ) {        

        final RedisMessageListenerContainer container = 
            new RedisMessageListenerContainer();
        
        container.setConnectionFactory( connectionFactory );
        container.addMessageListener( listener(), new ChannelTopic( "test-channel" ) );
        
        return container;
    }
    
    @Bean
    public MessageListener listener() {
        return new RedisMessageListener();
    }
}
           

為了提供一些背景資訊,我們将偵聽器附加到名為“ test-channel ” 的通道 ,這就是我們要向其釋出消息的通道。

釋出/訂閱消息傳遞通信本質上是異步的,這是在開發這種功能的測試用例時的又一個複雜問題。 訂戶将不會立即接收消息,但是會有些延遲。 如果要連接配接到本地計算機上運作的Redis伺服器,則可能需要花費毫秒,但是如果要通路雲中的某個執行個體,則可能需要一段時間。 一種可能的處理方法是引入某種合理的延遲,以便給訂戶一些時間來接收所有消息(這也是我們在測試案例中使用的技巧)。

package com.javacodegeeks.redis;

import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;


import java.util.concurrent.Callable;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration( classes = PubsubConfiguration.class )
public class RedisPublishSubscriberTestCase {
    @Autowired private RedisTemplate< String, String > template;
    @Autowired private RedisMessageListener listener;
    
    @Test
    public void testPublishSubscribe() {
        assertThat( listener.getCount(), equalTo( 0 ) );
        
        template.convertAndSend( "test-channel", "Test Message 1!" );
        template.convertAndSend( "test-channel", "Test Message 2!" );
        template.convertAndSend( "test-channel", "Test Message 3!" );
        
        await().atMost( 1, SECONDS ).until(
            new Callable< Integer >() {            
                @Override
                public Integer call() throws Exception {
                    return listener.getCount();
                }
            }, 
            equalTo( 3 ) 
        );
    }
}
           

這個新的測試案例使用我們的

PubsubConfiguration

類執行個體化Spring測試上下文。 在測試用例本身中,我們将三個消息釋出到Redis伺服器上的“ test-channel ” 通道 。 然後,我們給消息偵聽器一些時間(但不超過一秒),以消耗通道上釋出的所有消息。 之後,我們期望偵聽器收到所有3條消息,這就是我們最後要驗證的内容。

12.結論

Redis的發展非常Swift。 跟上它的所有新功能和指令非常困難。 這樣,您可能會發現Spring Data Redis尚不支援最新Redis發行版中提供的某些最新功能(甚至Java用戶端也需要一些時間來添加對它的支援)。 一個很好的例子是Redis Cluster, Spring Data Redis尚不支援它。

這是Redis教程的最後一部分,但這隻是通往Redis世界的開始。 如果您想關注Redis的最新發展,可以參考以下兩個資源:

  • http://antirez.com/:Redis的建立者Salvatore Sanfilippo的部落格
  • http://aphyr.com/posts/283-call-me-maybe-redis :關于Redis群集可用性的非常有用的見解
翻譯自: https://www.javacodegeeks.com/2015/09/spring-data-and-redis.html