코딩마을방범대

Redis - (5) Java에서 Redis 사용하기(Lettuce) 본문

💡 백엔드/Redis

Redis - (5) Java에서 Redis 사용하기(Lettuce)

신짱구 5세 2023. 6. 19. 17:21

 

 

 

Redis - (1) 기본 개념

Redis - (2) 우분투 서버에 Redis 세팅하기

Redis - (4) Java에서 Redis 사용하기(Jedis)


 

 

 

 

Jedis보단 Lettuce가 선호되는 추세이니 Lettuce를 사용하도록 하자!

다만 Jedis보다 사용이 조금 어렵다는 단점이 있다.

 

 

레터스는 레디스 서버와 단일 커넥션으로 멀티 스레드 요청에 대해 처리가 가능하다.

( 내부적으로 논-블럭킹 + 비동기로 구현되어 있으며 스레드 세이프하다. )

 

레디스 서버가 어차피 싱글 스레드 기반이기 때문에 어차피 다중 커넥션이 단일 커넥션에 비해 성능상 이점이 있는 것도 아니다.

따라서 레터스를 사용한다면 굳이 커넥션 풀을 만들지 않고 단일 커넥션을 공유하도록 하는 것이 좋다.

 

그럼에도 불구하고 커넥션을 공유하면 안되는 경우에는 다중 커넥션을 이용하게 될 수 있다.

(  블로킹(차단) API사용이나 레디스 트랜잭션(multi/exec)의 경우 )

 

블로킹의 경우는 spring에서는 어차피 레터스API를 직접 사용할 일이 거의 없기 때문에 무시하면 되고 레디스 트랜잭션의 경우는 spring에서도 사용할 일이 있다.

레디스 트랜잭션을 사용할 경우에는 새 커넥션이나 커넥션 풀을 이용해 전용 커넥션(연결)을 사용하도록 해야 한다.

 

 

 

 


 

 

shareNativeConnection

 

org.springframework.data.redis의 LettuceConnectionFactory에 보면 shareNativeConnection옵션이 있는데, 커넥션 공유 여부에 대한 설정이며 기본값은 true이다.

 

true일 경우 여러 명령어 수행 시 동일한 커넥션으로 수행된다.

dispatching command AsyncCommand [type=SET, outp
[channel=0xdb7ad8fe, /127.0.0.1:1464 -> /127.0.0.1:6378, epid=0x1] write() writeAndF
....
dispatching command AsyncCommand [type=GET, output=ValueO [
channel=0xdb7ad8fe, /127.0.0.1:1464 -> /127.0.0.1:6378, epid=0x1] write() writeAndFl

false일 경우 여러 명령어 수행 시 다른 커넥션으로 수행된다.

[channel=0x2d1bff4a, [id:0x3e56a857] (inactive), chid=0x2] channelRegistered()
Connecting to Redis at 127.0.0.1:6378: Success...
...
dispatching command AsyncCommand [type=SET, outp....
[channel=0x2d1bff4a, /127.0.0.1:1535-> /127.0.0.1:6378, epid=0x2] write() ...
...
Closing Redis Connection...
[channel=0x2d1bff4a, /127.0.0.1:1535-> /127.0.0.1:6378, epid=0x2] closeAsync()
...
dispatching command AsyncCommand [type=GET, output=ValueO...
[channel=0x72087637, /127.0.0.1:1536-> /127.0.0.1:6378, epid=0x3] write() ....

그리고 명령어 호출 전후로 커넥션을 얻고, 닫는 로그도 확인 할 수 있다.

 

shareNativeConnection옵션은 레디스 데이터베이스를 사용하는 경우 커넥션을 공유할 수 없어 사용한다.

만약 레디스 데이터베이스 명령어를 (select) 공유 커넥션에서 사용하면 UnsupportedOperationException가 발생한다.

 

 

 


 

 

레터스 커넥션 풀

전용 커넥션을 획득하기 위해 매번 커넥션을 획득.반환하게 되면 비용이 많이 발생하기 때문에 커넥션 풀을 사용하여 비용을 절감할 수 있다.

 

아래와 같이 커넥션 풀 옵션이 있으면 커넥션 풀이 생성되며, 전용 커넥션이 필요할 때 커넥션 풀이 사용된다.

spring:
  redis:
    ...
    pool:
      max-idle: 8
      min-idle: 0
      max-active: 8
      max-wait: -1

 

레터스 커넥션 풀을 사용하려면 apache common-pool2이 필요하므로 의존성 추가가 필요하다.

( gradle 링크 )

// https://mvnrepository.com/artifact/org.apache.commons/commons-pool2
implementation 'org.apache.commons:commons-pool2:2.2'

 

shareNativeConnection를 false, 커넥션 풀 옵션을 주고 실행하면 매번 커넥션을 생성하지 않고 커넥션 풀에서 커넥션을 획득하기 때문에 동일한 커넥션에서 명령어가 실행된 걸 확인 할 수 있다.

dispatching command AsyncCommand [type=SET, output=Statu
[channel=0xfb0dd9e3, /127.0.0.1:3838-> /127.0.0.1:6378,
....
ispatching command AsyncCommand [type=GET, output=Value
[channel=0xfb0dd9e3, /127.0.0.1:3838-> /127.0.0.1:6378

 

 

 


 

 

 

정리

1. 특별한 이유가 없다면 shareNativeConnection옵션은 건들지 말자.

2. 트랜잭션 명령어를 사용하지 않는다면 커넥션 풀을 쓸데없이 만들지 말자.

 

 

 

 


 

 

 

 

 

세팅하기

 

build.gradle

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

 

 

application.yml

!! 트랜잭션을 사용하지 않는 경우 pool 옵션은 줄 필요 없음 !!

spring:
  redis:
    # 레디스 서버 주소 (local일 경우 localhost)
    host: ip주소
    # 비밀번호
    password: 비밀번호
    # 레디스 서버 포트
    port: 포트번호
    # 커넥션 타임아웃 (단위 밀리세컨드)
    timeout: 1000
    pool:
      # 풀에서 관리하는 idle 커넥션의 쵀소수 대상 (양수일 때만 유효)
      max-idle: 8
      # 풀에서 관리하는 idle 커넥션의 최소수 대상 (양수일 때만 유효)
      min-idle: 0
      # pool에 할당될 수 있는 커넥션 최대수 (음수로 하면 무제한)
      max-active: 8
      # pool이 바닥났을 때 예외 발생 전, 커넥션 할당 차단 최대 시간(단위 밀리세컨드, 음수는 무제한 차단)
      max-wait: -1

 

 

RedisProperties

@Data
@ConfigurationProperties(prefix = "spring.redis")
@Component
public class RedisProperties {
    private String host;
    private int port;
    private String password;
}

 

 

Configuration

spring-boot 2.2 버전 이상부터는 autoConfiguration에 의해 restTemplate과 redisConnectionFactory가 자동 생성된다.
(redis template Bean 4개)

redisTemplate와 stringRedisTemplate의 차이는 Serializer(직렬화)

redisTemplateJdkSerializationRedisSerializer방식

stringRedisTemplateStringRedisSerializer방식

@Configuration
@RequiredArgsConstructor
public class RedisConfiguration {
    private final RedisProperties redisProperties;
    private String host;
    private int port;
    private String password;
    
    @PostConstruct
    protected void init() {
        host = redisProperties.getHost();
        port = redisProperties.getPort();
        password = redisProperties.getPassword();
    }

    // TCP 통신
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        // 패스워드 있을 경우
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
        return lettuceConnectionFactory;
        
        // 패스워드 없을 경우
        return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port));
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(connectionFactory);

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // JSON 포맷으로 저장

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());

        return redisTemplate;
    }

    @Bean
    StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}
직렬화          : Java 내부의 데이터들을 외부에서 사용 가능하도록 바이트 형태로 데이터를 변환하는 기술
역직렬화      : 바이트 데이터를 원래 Object로 변환하는 기술

LettuceConnectionFactory

  • Lettuce로 Redis와의 연결을 진행
  • Redis의 주소와 포트를 파라미터로 넣어줌

RedisTemplate

  • 데이터의 추가 조회 작업 수행

SpringRedisTemplate 

  • 데이터의 추가 조회 작업 수행
  • 문자열에 특화된 메소드

 

 

 

 

 


 

 

 

 

 

사용하기

 

public GetUserInfoResponseDTO getUserInfo(String userKey) {
	// Redis와 연결
	ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();

	// 데이터 추가 시 set
	valueOperations.set(키, 값);
    ...
}

 

 

RedisTemplate 를 이용한 데이터 다루기

메소드 리턴값 예시 설명
keys( pattern ) Set<K> redisTemplate.keys("*") 패턴에 맞는 키들을 리턴
hasKey( key ) Boolean redisTemplate.hasKey(key) 해당 키값이 있는지 확인
delete( key ) Boolean redisTemplate.delete(key) 해당 키값을 지움
expire( key, 시간, TimeUnit ) Boolean redisTemplate.expire(key, authLimitSettings.getOtpTime(), TimeUnit.SECONDS) 해당 키값에 만료 기한을 정함
!! Keys 로 뽑은후 Set 활용법
Map으로 활용
 new HashMap<String, String>(){{
	for(String key: set){
		put(key, valueOperations.members(key).toString());
	}
}};

 


List로 활용
new ArrayList<>( redisTemplate.keys("*"));​

 

 

 


 

 

 

RedisTemplate 사용 타입

메소드 타입
opsForValue ValueOperations Strings
opsForList ListOperations List
opsForSet SetOperations Set
opsForZSet ZSetOperations ZSet
opsForHash HashOperations Hash

 

 

Strings ( opsForValue )

( 삭제 시 getAndDelete 라는 옵션이 있다고 하는데 버전 문제인지 확인되지 않아 탬플릿을 이용해 지웠다 )

ValueOperations<String, Object> valOpt = redisTemplate.opsForValue();

// 입력
// set(키값, 값)
valOpt.set(key, "1");

// 출력
// get(키값)
valOpt.get(key); // 1

// 증감
// increment(키값)
valOpt.increment(key) // 2

// 삭제
// delete(키값)
redisTemplate.delete(key);

 

 


 

 

List ( opsForList )

ListOperations<String, String> valOpt = redisTemplate.opsForList();

// 단일 입력
// rightPush(키값, 값)
valOpt.rightPush(key, "H");
valOpt.rightPush(key, "i");

// 다중 입력
// rightPushAll(키값, ...값)
valOpt.rightPushAll(key, " ", "s", "a", "b", "a"); // [H, i,  , s, a, b, a]

// 단일 조회
// index(키값, 인덱스)
valOpt.index(key, 1);

// 사이즈 조회
// size(키값)
valOpt.size(key);

// 다중 조회
// range(키값, start, end)
List<String> ResultRange = valOpt.range(key, 0, 9);

// 삭제
// remove(키값, 인덱스, 값)
valOpt.remove(key, longId, value);

 


 

 

Set ( opsForSet )

SetOperations<String, String> varOpt = redisTemplate.opsForSet();

// 입력
// add(키값, 값)
varOpt.add(key, "o");

// 출력
// members(키값)
Set<String> setMem = varOpt.members(key);

// 사이즈 조회
// size(키값)
varOpt.size(key);

// 삭제
// remove(키값)
varOpt.remove(key);

 


 

Hash ( opsForHash )

HashOperations<String, Object, Object> varOpt = redisTemplate.opsForHash();

// 입력
// put(키값, 필드명, 값)
varOpt.put(key, "Hello1", "sabarada3");

// 출력
// get(키값, 필드명)
varOpt.get(key, "Hello");

// 키에 해당하는 필드들 전부 가져오기
// entries(키값)
Map<Object, Object> entries = varOpt.entries(key);

// 사이즈 조회
// size(키값)
Long size = varOpt.size(key);

// 삭제
// delete(키값)
varOpt.delete(key);

 

 

 


 

 

만료 시간 설정하기

expire를 사용하여 key에 만료 시간을 설정할 수 있다

valueOperations.set(key, value);
redisTemplate.expire(key, authLimitSettings.getOtpTime(), TimeUnit.SECONDS);

 

 

 


참고사이트

Spring Boot Redis 적용기 (With lettuce)

[Redis] SpringBoot + Redis

[Spring boot]Redis - Lettuce 설정

[Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisTemplate 편

 

 

 

SMALL