이 연재글은 Redis 알아보기의 3번째 글입니다.

SpringBoot2와 redis cluster를 연동하고, redis command에 대해 설명합니다. 다음을 참고하여 프로젝트를 하나 생성합니다.
>> Intellij Community 프로젝트생성

Boot를 실행하기 위한 Application Class 생성

package com.redis.cluster;

@SpringBootApplication
public class RedisClusterApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisClusterApplication.class, args);
    }
}

build.gradle 수정

spring-boot-starter-data-redis를 이용하여 redis cluster를 연동합니다.

plugins {
    id 'org.springframework.boot' version '2.1.4.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.rest'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-freemarker'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'com.google.code.gson:gson'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

application.yml 수정

h2, jpa, redis 설정을 추가합니다. cluster 노드 정보를 넣고, redis에 패스워드가 있으면 패스워드 정보도 추가합니다.

server:
  port: 8081

spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/test
    driver-class-name: org.h2.Driver
    username: sa
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    properties.hibernate.hbm2ddl.auto: update
    showSql: true
  redis:
    cluster:
      nodes:
        - 15.164.98.87:6300
        - 15.164.98.87:6301
        - 15.164.98.87:6302
        - 15.164.98.87:6400
        - 15.164.98.87:6401
        - 15.164.98.87:6402
    password: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

test/resources 아래에 logback_test.xml 생성

유닛 테스트에서 log를 사용하기 위해 test/resources 아래에 logback_test.xml를 생성합니다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                %-5level %d{HH:mm:ss} %logger{15}.%method:%line - %msg%n
            </pattern>
        </encoder>
    </appender>

    <logger name="com.redis.cluster" level="debug" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>
</configuration>

RedisClusterTest 생성

Redis가 제공하는 명령어를 실습합니다.

자주 사용하는 기본 명령어

@Test
public void commonCommand() {
    ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
    valueOps.set("key1", "key1value");
    valueOps.set("key2", "key2value");
    // Key 타입 조회.
    assertEquals(DataType.STRING, redisTemplate.type("key1"));
    // 존재하는 Key의 개수를 반환.
    assertSame(2L, redisTemplate.countExistingKeys(Arrays.asList("key1", "key2", "key3")));
    // Key가 존재하는지 확인
    assertTrue(redisTemplate.hasKey("key1"));
    // Key 만료 날짜 세팅
    assertTrue(redisTemplate.expireAt("key1", Date.from(LocalDateTime.now().plusDays(1L).atZone(ZoneId.systemDefault()).toInstant())));
    // Key 만료 시간 세팅
    assertTrue(redisTemplate.expire("key1", 60, TimeUnit.SECONDS));
    // Key 만료 시간 조회
    assertThat(redisTemplate.getExpire("key1"), greaterThan(0L));
    // Key 만료 시간 해제
    assertTrue(redisTemplate.persist("key1"));
    // Key 만료시간이 세팅 안되어있는경우 -1 반환
    assertSame(-1L, redisTemplate.getExpire("key1"));
    // Key 삭제
    assertTrue(redisTemplate.delete("key1"));
    // Key 일괄 삭제
    assertThat(redisTemplate.delete(Arrays.asList("key1", "key2", "key3")), greaterThan(0L));
}

String structure command

redis command set, get에 대한 내용입니다.

@Test
public void opsValue() {
ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
    Collection<String> cacheKeys = new ArrayList<>();
    String cacheKey = "value_";
    for (int i = 0; i < 10; i++) {
    cacheKeys.add(cacheKey + i);
    valueOps.set(cacheKey + i, String.valueOf(i), 60, TimeUnit.SECONDS);
    }
    List<String> values = valueOps.multiGet(cacheKeys);
    assertNotNull(values);
    assertEquals(10, values.size());
    log.info("##### opsValue #####");
    log.info("{}", values);
}
##### opsSortedSet #####
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Lists structure command – 순서 있음. value 중복 허용

redis command lpush, llen, lrange, lpop, rpop에 대한 내용입니다.

@Test
public void opsList() {
    ListOperations<String, String> listOps = redisTemplate.opsForList();
    String cacheKey = "valueList";
    for (int i = 0; i < 10; i++)
        listOps.leftPush(cacheKey, String.valueOf(i));

    assertSame(DataType.LIST, redisTemplate.type(cacheKey));
    assertSame(10L, listOps.size(cacheKey));
    log.info("##### opsList #####");
    log.info("{}", listOps.range(cacheKey, 0, 10));
    assertEquals("0", listOps.rightPop(cacheKey));
    assertEquals("9", listOps.leftPop(cacheKey));
    assertEquals(true, redisTemplate.delete(cacheKey));
}
##### opsList #####
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Hashes structure command – 순서 없음. key 중복 허용안함, value 중복 허용

redis command hset, hget, hlen, hdel에 대한 내용입니다.

@Test
public void opsHash() {
    HashOperations<String, String, String> hashOps = redisTemplate.opsForHash();
    String cacheKey = "valueHash";
    for (int i = 0; i < 10; i++)
        hashOps.put(cacheKey, "key_" + i, "value_" + i);

    assertSame(DataType.HASH, redisTemplate.type(cacheKey));
    assertSame(10L, hashOps.size(cacheKey));
    log.info("##### opsHash #####");
    Set<String> hkeys = hashOps.keys(cacheKey);
    for (String hkey : hkeys) {
       log.info("{} / {}", hkey, hashOps.get(cacheKey, hkey));
    }
    assertEquals("value_5", hashOps.get(cacheKey, "key_5"));
    assertSame(1L, hashOps.delete(cacheKey, "key_5"));
    assertSame(null, hashOps.get(cacheKey, "key_5"));
}
##### opsHash #####
key_0 / value_0
key_1 / value_1
key_2 / value_2
key_3 / value_3
key_4 / value_4
key_6 / value_6
key_7 / value_7
key_8 / value_8
key_9 / value_9
key_5 / value_5

Set structure command – 순서 없음, value 중복 허용안함

redis command sadd, scard, smembers, sismember에 대한 내용입니다.

@Test
public void opsSet() {
    SetOperations<String, String> setOps = redisTemplate.opsForSet();
    String cacheKey = "valueSet";
    for (int i = 0; i < 10; i++)
        setOps.add(cacheKey, String.valueOf(i));

    assertSame(DataType.SET, redisTemplate.type(cacheKey));     
    assertSame(10L, setOps.size(cacheKey));
    log.info("##### opsList #####");
    log.info("{}", setOps.members(cacheKey));
    assertEquals(true, setOps.isMember(cacheKey, "5"));
}
##### opsSet #####
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

SortedSet structure command – 순서 있음, value 중복 허용안함

rediscommand zadd, zcard, zrange, zrank에 대한 내용입니다.

@Test
public void opsSortedSet() {
    ZSetOperations<String, String> zsetOps = redisTemplate.opsForZSet();
    String cacheKey = "valueZSet";
    for (int i = 0; i < 10; i++)
        zsetOps.add(cacheKey, String.valueOf(i), i);

    assertSame(DataType.ZSET, redisTemplate.type(cacheKey));
    assertSame(10L, zsetOps.size(cacheKey));
    log.info("##### opsSortedSet #####");
    log.info("{}", zsetOps.range(cacheKey, 0, 10));
    assertSame(0L, zsetOps.reverseRank(cacheKey, "9"));
}
##### opsSortedSet #####
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Geo structure command – 좌표 정보 처리, 타입은 zset으로 저장.

redis command geoadd, geodist, geopos에 대한 내용입니다.

@Test
public void opsGeo() {
    GeoOperations<String, String> geoOps = redisTemplate.opsForGeo();
    String[] cities = {"서울", "부산"};
    String[][] gu = {{"강남구", "서초구", "관악구", "동작구", "마포구"}, {"사하구", "해운대구", "영도구", "동래구", "수영구"}};
    Point[][] pointGu = {{new Point(10, -10), new Point(11, -20), new Point(13, 10), new Point(14, 30), new Point(15, 40)}, {new Point(-100, 10), new Point(-110, 20), new Point(-130, 80), new Point(-140, 60), new Point(-150, 30)}};
    String cacheKey = "valueGeo";

    // previous key delete
    redisTemplate.delete(cacheKey);

    for (int x = 0; x < cities.length; x++) {
        for (int y = 0; y < 5; y++) {
            geoOps.add(cacheKey, pointGu[x][y], gu[x][y]);
        }
    }

    log.info("##### opsGeo #####");
    Distance distance = geoOps.distance(cacheKey, "강남구", "동작구");
    assertNotNull(distance);
    assertEquals(4469610.0767, distance.getValue(), 4);
    log.info("Distance : {}", distance.getValue());
    List<Point> position = geoOps.position(cacheKey, "동작구");
    assertNotNull(position);
    for (Point point : position) {
        assertEquals(14.000001847743988d, point.getX(), 4);
        assertEquals(30.000000249977013d, point.getY(), 4);
        log.info("Position : {} x {}", point.getX(), point.getY());
    }
}
##### opsGeo #####
Distance : 4469610.0767
Position : 14.000001847743988 x 30.000000249977013

HyperLogLog structure command – 집합의 원소의 개수 추정, 타입은 string으로 저장.

redis command pfadd, pfcount에 대한 내용입니다.

@Test
public void opsHyperLogLog() {
    HyperLogLogOperations<String, String> hyperLogLogOps = redisTemplate.opsForHyperLogLog();
    String cacheKey = "valueHyperLogLog";
    String[] arr1 = {"1", "2", "2", "3", "4", "5", "5", "5", "5", "6", "7", "7", "7"};
    hyperLogLogOps.add(cacheKey, arr1);
    log.info("##### opsHyperLogLog #####");
    log.info("count : {}", hyperLogLogOps.size(cacheKey));
    redisTemplate.delete(cacheKey);
}
##### opsHyperLogLog #####
count : 7

최신 소스는 GitHub 사이트를 참고해 주세요.
https://github.com/codej99/SpringRedisCluster/tree/feature/rediscluster

연재글 이동[이전글] Redis – cluster
[다음글] Redis – Spring-data-redis : @Cacheable, @CachePut, @CacheEvict, @RedisHash