Spring BootアプリからAzure Cache for Redisに接続する

はじめに

先日からSpring Bootで作ったWebアプリに対してAzureの各種サービスを接続することをやっています。今回はAzure Cache for Redisに接続してみることにします。セッション情報をアプリケーションからRedisなどに保存することでスケーラブルなアプリケーションが実現できます。

azure.microsoft.com

前提

以下のバージョンで実装しました。

  • Spring Boot 2.3.1.RELEASE

Spring Bootの設定

サンプルが公開されているので、それを参考に。

docs.spring.io

また、少しバージョンは古いですが、Qiitaに分かりやすい記事が乗っていましたのでこちらも参考にしました。

qiita.com

build.gradleの編集

まず、build.gradleに以下の記述します。

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.session:spring-session-data-redis'

セッション情報を保存するクラスを作成する

次にセッション情報を保存するクラスを作成します。こんな感じになりました。

package com.example.demo;

// import省略

@SessionScope
@Component
public class SessionInfo implements Serializable {
    private static final long serialVersionUID = 1L;

    private long id;
    private String name;
    private Date createdAt;
  // getter/setter省略

}

セッションに情報を保存する

適当なControllerにセッションに情報を保存する処理を実装します。こんな感じの実装になりました。

package com.example.demo;

// import省略

@RestController
public class GreetingController {
    private static final String template = "Hello, %s!";
    private final AtomicLong counter = new AtomicLong();
    private final SessionInfo sessionInfo;

    public GreetingController(SessionInfo sessionInfo) {
        this.sessionInfo = sessionInfo;
    }

    @GetMapping("/")
    public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
        if (sessionInfo.getName() == null) {
            sessionInfo.setId(counter.incrementAndGet());
            sessionInfo.setName(name);
            sessionInfo.setCreatedAt(new Date());
        }
        return new Greeting(
                sessionInfo.getId(),
                String.format(template, sessionInfo.getName()),
                sessionInfo.getCreatedAt()
                );
    }

    @GetMapping("/goodbye")
    public String goodbye(HttpSession session) {
        Optional<String> name = Optional.ofNullable(sessionInfo.getName());
        session.invalidate();
        return "Goodbye " + name.orElse("anonymous");
    }
}

あわせてGreetingクラスの内容を適宜修正しています。

application.propertiesにRedisの接続先を記述する

application.propertiesにRedisの接続先を記述します。各項目をそのまま記述するのではなく、環境変数を介する感じのほうが良いかなと思います。

spring.session.store-type=redis
spring.redis.host=${REDIS_HOST:cache}
spring.redis.port=${REDIS_PORT:6379}
spring.redis.password=${REDIS_PASSWORD:password}

docker-compose.ymlにRedisを追加する

ローカルでテストしやすいようにdocker-compose.ymlにRedisの設定を追加します。こんな感じ。

# 省略
  cache:
    image: redis:6.0.5-alpine
    command: redis-server --requirepass password
    ports:
      - 6379:6379
  app:
    image: demo:0.0.1-SNAPSHOT
    depends_on:
      - db
      - cache
    ports:
      - 8080:8080
    environment:
      REDIS_HOST: cache
      REDIS_PORT: 6379
      REDIS_PASSWORD: password
# 省略

以上の記述で動くようになります。

Azure Cache for Redisを作成する

ようやく本論のAzure Cache for Redisを作成します。私は東日本リージョンで作成しましたが、作成に20分ほどかかりました。VMなどの他のサービスと異なり結構待たされました。

「非SSLポート(6379)」をどうするかは後で変更できるので作成時にあまり悩まなくても良いかなと思います。

f:id:miyohide:20200723165546p:plain

接続してみる

作成したAzure Cache for Redisに対して接続してみます。docker-compose.ymlに追加したREDIS_HOSTREDIS_PORTREDIS_PASSWORDにそれぞれの値を設定します。REDIS_PASSWORDはアクセスキーを表示させてコピペしてください。

f:id:miyohide:20200723165940p:plain

...ですが、このままだとうまく接続できません。以下の例外が発生します。

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'enableRedisKeyspaceNotificationsInitializer' defined in class path resource [org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: ERR unknown command `CONFIG`, with args beginning with: `GET`, `notify-keyspace-events`,

この問題については @setoazusa さんが以下のブログにて解決方法を示されています。

blog.fieldnotes.jp

バージョンが上がっていくつかのメソッドが非推奨になっていたのでそれを解決するために私は以下の実装にしました。

package com.example.demo;

// import文省略

@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
    @Bean
    public static ConfigureRedisAction configureRedisAction() {
        return ConfigureRedisAction.NO_OP;
    }

    @Bean
    public LettuceConnectionFactory connectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(System.getenv("REDIS_HOST"));
        redisStandaloneConfiguration.setPassword(System.getenv("REDIS_PASSWORD"));
        redisStandaloneConfiguration.setPort(Integer.parseInt(System.getenv("REDIS_PORT")));
        LettuceClientConfiguration lettuceClientConfiguration = LettuceClientConfiguration.builder().useSsl().build();
        return new LettuceConnectionFactory(redisStandaloneConfiguration, lettuceClientConfiguration);
    }
}

この記述をすると例外が発生せずにうまく動きました。Azure PortalからAzure Cache for Redisへのコンソール機能にアクセスできるので中身を確認してみるとよいかと思います。

f:id:miyohide:20200723170921p:plain

ソース

以下に上げました。省略したものもあるかと思うので、あわせてみてください。

github.com