1 분 소요

Springboot Lettuce기반 Lua를 호출하여 결과값을 List 받는 방법을 공유합니다. Lua를 를 이용해서 Boolean, Long 타입은 별다른 어려움없이 응답을 받을 수 있습니다. 그러나 String이나 List같은 경우는 RedisTemplate 기본 설정으로는 결과를 받을 수 없고 다음과 같은 오류를 만나게 될 것입니다. 이것은 GenericJackson2JsonRedisSerializer에서 byte[]를 deserialization을 하지 못해서 발생하는 에러입니다.

com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'test': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')
 at [Source: (byte[])"test"; line: 1, column: 5]
	at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:2391) ~[jackson-core-2.13.5.jar!/:2.13.5]

그래서 다음과 같은 방법으로 해결이 가능하며, Lua를 통해서 JSON, String, List등을 반환값으로 받을 수 있습니다.

  • GenericJackson2JsonRedisSerializer에서 extends
  • deserialize를 overriding

GenericJackson2JsonRedisSerializer를 extention하여 overriding하는 소스는 다음과 같습니다. GenericJackson2JsonRedisSerializer를 CustomJackson2JsonRedisSerializer로 변경하면 Lua에서 반환값을 다양하게 받을 수 있습니다.

import java.util.Objects;

import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CustomJackson2JsonRedisSerializer extends GenericJackson2JsonRedisSerializer {
	ObjectMapper objectMapper = new ObjectMapper();
	/**
	 * @param source can be {@literal null}.
	 * @param type must not be {@literal null}.
	 * @return {@literal null} for empty source.
	 * @throws SerializationException
	 */
	@Nullable
	public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws SerializationException {
		T clazz = null;
		try {
			
			Assert.notNull(type,
					"Deserialization type must not be null! Please provide Object.class to make use of Jackson2 default typing.");

			if (Objects.isNull(source)) {
				return null;
			}

			log.info("Source:{}, class:{}", new String(source), type);
			clazz = objectMapper.readValue(new String(source), type);
			return clazz;
		} catch (Exception ex) {
			throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
		}
	}
}

Redis Configuration

RedisTemplate Value Serializer에 CustomJackson2JsonRedisSerializer를 설정합니다.

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
    		CustomJackson2JsonRedisSerializer customJackson2JsonRedisSerializer = new CustomJackson2JsonRedisSerializer();
    	
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(customJackson2JsonRedisSerializer);
        redisTemplate.setDefaultSerializer(customJackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(customJackson2JsonRedisSerializer);   
        
        return redisTemplate;
    } 

leaderboard Lua

Lua 예제는 다음과 같습니다.

-- leaderboard.lua 
local rank = redis.call('zrank', KEYS[1], KEYS[2])
local min = math.max(rank - ARGV[1], 0)
local max = rank + ARGV[1]
local ldb = redis.call('zrange', KEYS[1], min, max)
local results = {}

results['rank'] = tostring(rank + 1)
results['item'] = ldb

local vars = cjson.encode(results)

return vars

redis에 적용하기

cat leaderboard.lua | redis-cli -x script load
"60e0429ac3aed8dbb3abfeb4fb2d13dde103d575"

 127.0.0.1:6379> evalsha "60e0429ac3aed8dbb3abfeb4fb2d13dde103d575" 2 leaderboard Heather 3
"{\"item\":[\"Andy\",\"Dolly\",\"Cathy\",\"Heather\",\"Gilbert\",\"Lilly\",\"Dinesh\"],\"rank\":\"6\"}"

RedisTemplate & Lettuce를 이용

    public Object retrieveLeaderBoard(LeaderBoardReqVO leaderBoardReqVO) {
    	DefaultRedisScript<Object> redisScript = new DefaultRedisScript<>();
    	Resource resource = new ClassPathResource(leaderBoard);
    	redisScript.setScriptSource(new ResourceScriptSource(resource));
    	redisScript.setResultType(Object.class);
    	log.info("retrieveLeaderBoard:{}", leaderBoardReqVO);
    	Object[] args = new Object[] {leaderBoardReqVO.getUser()};
    	Object ret = redisTemplate.execute(redisScript,  Arrays.asList(leaderBoardReqVO.getKey(), leaderBoardReqVO.getUser()), leaderBoardReqVO.getCount());
    	
    	log.info("retrieveLeaderBoard ret:{}", ret);
    	
    	return ret;
    }

Swagger UI에서 호출

m1s1-r1s2-r3s3

[수행결과과]
m1s1-r1s2-r3s3

태그: ,

카테고리:

업데이트:

댓글남기기