読者です 読者をやめる 読者になる 読者になる

rakugakibox.net

技術ノート。兼JS/CSS実験場。 ♡:Java, Spring, AWS.

Java: Spring Boot で Redis を使う!

Java Spring Boot Spring Framework Redis Programming

バックエンドに Redis を置いた、
Spring Boot アプリケーションを作りました。
そのときに調べた実装方法のメモです。

Redisに格納するデータフォーマットは

  • 文字列
  • Javaシリアライズ
  • JSON

をまとめました。

Redis とは

  • Key-Valueストア (KVS)
  • インメモリDBなので高速
  • データに型がある (文字列, リスト, セット, ソート済セット, ハッシュ)

環境

  • CentOS 6.6
  • Redis 2.8.19
  • Java 1.8.0 update 51
  • Maven 3.3.3
  • Spring Boot 1.2.5
  • Lombok 1.16.4 (Getter/Setter作成に使ってます, 本題ではないです)

依存関係

pom.xmldependencies
spring-boot-starter-redis を追加します。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-redis</artifactId>
</dependency>

接続設定

Redisサーバへ接続するための設定です。

ここでは application.yml に書きました。
.properties にするとか、
Java実行パラメータ (-D) で渡すとかはお好みで。

spring.redis:
  host: localhost
  port: 6379
  password: null
  database: 0
  pool:
    max-idle: 8
    min-idle: 0
    max-active: 8
    max-wait: -1

ちなみに上記はデフォルト値。
この値のまま使うなら敢えて書く必要はないです(^^;

spring.redis.database

Redis ではデータベースを複数持つことが出来るようで、
0から順に番号付けされてるようです。
この設定値には、どのデータベースに繋ぐかの番号を指定します。

僕はテスト時は spring.redis.database=1 にして、
アプリケーション実行に使うエリアと、
テストコードが使うエリアとに分けて利用してます :)

spring.redis.pool.*

デフォルトでは Apache Commons Pool によって
コネクションをプールして使いまわすっぽいです。
どれくらいプールするかの設定を
このプロパティでいじれるようになってました。

RedisTemplate について

Java から Redisアクセスするには、
RedisTemplate 系のクラスを使います。

Spring Boot の自動設定では、
StringRedisTemplate がDIコンテナに登録されてました。

文字列で保存/取得してみる

RESTコントローラで、RedisへPUT/GETしてみます。

Redisアクセスには StringRedisTemplate を使います。
データ型には、文字列, リスト, ハッシュを使ってみました。

@Data
public class Hoge {
    private String string;
    private List<String> list;
    private Map<String, String> map;
}
@RestController @RequestMapping(value = "/hoge-string")
public class HogeStringController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @RequestMapping(method = RequestMethod.PUT)
    public void put(@RequestBody Hoge value) throws Exception {
        redisTemplate.opsForValue()
                .set("hoge-string:string", value.getString());
        redisTemplate.delete("hoge-string:list");
        redisTemplate.opsForList()
                .rightPushAll("hoge-string:list",
                        value.getList().toArray(new String[0]));
        redisTemplate.delete("hoge-string:map");
        redisTemplate.opsForHash()
                .putAll("hoge-string:map", value.getMap());
    }

    @RequestMapping(method = RequestMethod.GET)
    public Hoge get() throws Exception {
        Hoge hoge = new Hoge();
        hoge.setString(
            redisTemplate.opsForValue()
                    .get("hoge-string:string")
        );
        hoge.setList(
            redisTemplate.opsForList()
                    .range("hoge-string:list", 0, -1)
        );
        hoge.setMap(
            redisTemplate.<String, String>opsForHash()
                    .entries("hoge-string:map")
        );
        return hoge;
    }

}

実行すると。

$ curl -X PUT 'http://localhost:8080/hoge-string' \
    -H 'Content-Type:application/json' \
    -d '{
          "string": "hoge",
          "list": [
            "hoge",
            "fuga"
          ],
          "map": {
            "hoge": "fuga",
            "fuga": "piyo"
          }
        }'

$ curl 'http://localhost:8080/hoge-string'
{"string":"hoge","list":["hoge","fuga"],"map":{"fuga":"piyo","hoge":"fuga"}}

Redisの登録内容は。

$ redis-cli

> get hoge-string:string
"hoge"

> lrange hoge-string:list 0 -1
1) "hoge"
2) "fuga"

> hgetall hoge-string:map
1) "hoge"
2) "fuga"
3) "fuga"
4) "piyo"

Javaのシリアライズで保存/取得してみる

さっきの Hoge クラスをJavaのシリアライズで
バイナリにして、1件のKey-Valueとして登録します。

まず自前の RedisTemplate を作って、DIコンテナに登録してやります。
このとき、シリアライザに JdkSerializationRedisSerializer をセットします。
キーまでバイナリになると確認しにくいので、
キーのシリアライザには StringRedisSerializer をセットしました。

用意されてるシリアライザを組み合わせるだけなので楽ちんです。

@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate<String, Hoge> serialRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Hoge> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setHashKeySerializer(redisTemplate.getKeySerializer());
        redisTemplate.setHashValueSerializer(redisTemplate.getValueSerializer());
        return redisTemplate;
    }
}

Hoge クラスは Serializable にしておきます。

import java.io.Serializable;

public class Hoge implements Serializable {
    // ...
}

さっきとは別のRESTコントローラを作って、RedisへPUT/GETしてみます。
RedisTemplate は新たに作ったものをインジェクション。
Hoge クラスを丸っとRedisへ投げてます。

@RestController @RequestMapping(value = "/hoge-serial")
public class HogeSerialController {

    @Autowired @Qualifier("serialRedisTemplate")
    private RedisTemplate<String, Hoge> redisTemplate;

    @RequestMapping(method = RequestMethod.PUT)
    public void put(@RequestBody Hoge value) throws Exception {
        redisTemplate.opsForValue().set("hoge-serial", value);
    }

    @RequestMapping(method = RequestMethod.GET)
    public Hoge get() throws Exception {
        return redisTemplate.opsForValue().get("hoge-serial");
    }

}

実行すると。

$ curl -X PUT 'http://localhost:8080/hoge-serial' \
    -H 'Content-Type:application/json' \
    -d '{
          "string": "hoge",
          "list": [
            "hoge",
            "fuga"
          ],
          "map": {
            "hoge": "fuga",
            "fuga": "piyo"
          }
        }'

$ curl 'http://localhost:8080/hoge-serial'
{"string":"hoge","list":["hoge","fuga"],"map":{"hoge":"fuga","fuga":"piyo"}}

Redisの登録内容は。

$ redis-cli

> get hoge-serial
"\xac\xed\x00\x05sr\x00#akihyro.tryspringbootwithredis.Hoge\xa2[\xd9\xb5?\xa0\xcb=\x02\x00\x03L\x00\x04listt\x00\x10Ljava/util/List;L\x00\x03mapt\x00\x0fLjava/util/Map;L\x00\x06stringt\x00\x12Ljava/lang/String;xpsr\x00\x13java.util.ArrayListx\x81\xd2\x1d\x99\xc7a\x9d\x03\x00\x01I\x00\x04sizexp\x00\x00\x00\x02w\x04\x00\x00\x00\x02t\x00\x04hoget\x00\x04fugaxsr\x00\x17java.util.LinkedHashMap4\xc0N\\\x10l\xc0\xfb\x02\x00\x01Z\x00\x0baccessOrderxr\x00\x11java.util.HashMap\x05\a\xda\xc1\xc3\x16`\xd1\x03\x00\x02F\x00\nloadFactorI\x00\tthresholdxp?@\x00\x00\x00\x00\x00\x0cw\b\x00\x00\x00\x10\x00\x00\x00\x02t\x00\x04hoget\x00\x04fugat\x00\x04fugat\x00\x04piyox\x00t\x00\x04hoge"

・・・やはりバイナリは見にくい(^^;

JSONで保存/取得してみる

さっきの Hoge クラスをJSONにして、
1件のKey-Valueとして登録します。

まずはJSON版の RedisTemplate をDIコンテナに登録。
シリアライザには Jackson2JsonRedisSerializer をセットします。

@Configuration
public class RedisConfiguration {

    // ...

    @Bean
    public RedisTemplate<String, Hoge> jsonRedisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Hoge> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Hoge.class));
        redisTemplate.setHashKeySerializer(redisTemplate.getKeySerializer());
        redisTemplate.setHashValueSerializer(redisTemplate.getValueSerializer());
        return redisTemplate;
    }

}

また新しいRESTコントローラを作って、RedisへPUT/GETしてみます。

@RestController @RequestMapping(value = "/hoge-json")
public class HogeJsonController {

    @Autowired @Qualifier("jsonRedisTemplate")
    private RedisTemplate<String, Hoge> redisTemplate;

    @RequestMapping(method = RequestMethod.PUT)
    public void put(@RequestBody Hoge value) throws Exception {
        redisTemplate.opsForValue().set("hoge-json", value);
    }

    @RequestMapping(method = RequestMethod.GET)
    public Hoge get() throws Exception {
        return redisTemplate.opsForValue().get("hoge-json");
    }

}

実行すると。

$ curl -X PUT 'http://localhost:8080/hoge-json' \
    -H 'Content-Type:application/json' \
    -d '{
          "string": "hoge",
          "list": [
            "hoge",
            "fuga"
          ],
          "map": {
            "hoge": "fuga",
            "fuga": "piyo"
          }
        }'

$ curl 'http://localhost:8080/hoge-json'
{"string":"hoge","list":["hoge","fuga"],"map":{"hoge":"fuga","fuga":"piyo"}}

Redisの登録内容は。

$ redis-cli

> get hoge-json
"{\"string\":\"hoge\",\"list\":[\"hoge\",\"fuga\"],\"map\":{\"hoge\":\"fuga\",\"fuga\":\"piyo\"}}"

Redisに格納するフォーマットで好きなもの

アプリの特性にもよるだろうけど、
僕は今のところJSONで格納が好きです。

  • リストやハッシュの構造を持てる。
  • 値を人間が確認できる。
    • redis-cli だけでは見にくいけど…
    • Redis Desktop Manager を使うと見やすい。
  • 項目を追加しやすい。
    • 前記のコードで言うと、 Hoge クラスにフィールドを追加しやすい。
    • JdkSerializationRedisSerializer では、フィールド追加前に保存したエントリを読み込もうとすると SerializationException が発生する。
    • Jackson2JsonRedisSerializer では、新たに追加したフィールドは null で返してくれる。

おまけ: クライアントは Redis Desktop Manager が便利

Redis Desktop Manager
http://redisdesktop.com

GUIでコロン区切りでいい感じにツリー構造にしてくれるのと、
保存形式がJSONなら見やすい形にも出来ました :)

f:id:akihyrox:20150724203639p:plain

おまけ: AWS では ElastiCache が便利

もしアプリケーションをAWSで動かすなら、
Amazon ElastiCache の Redis が便利でした。

キャッシュノードを複数のAZに立てれば、
レプリケーショングループのオプション1つで
マルチAZ+レプリケーションしてくれて、SPOFを回避できます。

最初、 memcached と Redis どちらを使うか悩みましたが、
この楽ちんさで Redis を採用しました :)

コード (GitHub)

参考

関連記事