'sharding'에 해당되는 글 1건

[Redis-Jedis] Spring을 이용한 샤드(Shard) 설정 2013.11.19

[Redis-Jedis] Spring을 이용한 샤드(Shard) 설정

Published on: 2013. 11. 19. 20:55 by louis.dev

업무에 Redis를 이용할 기회가 있어서 설정 내용을 포스팅해봅니다. Redis의 Java 라이브러리인 Jedis와 Spring FrameWork를 통해서 세팅해보겠습니다.


Spring에서는 spring-data라는 프로젝트를 통해 각종 Database, NoSQL의 사용을 스프링프레임워크를 기반으로한 프로젝트에서 쉽게 사용할수있도록 라이브러리를 제공하고 있습니다.


물론 Redis도 spring-data-redis라는 이름으로 제공하고 있습니다. 그런데 spring-data-redis 프로젝트가 시작된지 얼마 안되서 그런지(현재 안정화 버전은 1.1.0) 샤딩을 지원하고 있지 않습니다.


여기서 샤딩이란 간단하게 말하자면 여러대의 레디스 서버에 데이터를 분산하여 저장하는 것을 의미합니다. 

Redis는 데이터를 메모리에 올려놓는 In Memory 방식이기 때문에 SSD나 HDD에 저장하는 기존의 DB들 보다 저장공간이 부족합니다(하드디스크는 테라단위로 설치하긴 쉽지만 램은 하드디스크 보다 용량 확장이 쉽지 않기 때문입니다).

따라서 레디스 서버 한대로 서비스를 시작한 후에 데이터량이 점점 증가하여 메모리 용량이 모자르게 되면 서버를 추가하여 데이터를 해당 서버 수만큼 쪼개어 저장할수 있는데 이것을 샤딩이라고 합니다.


아무튼 spring-data-redis는 Sharding을 지원해 주지 않기 때문에 Jedis를 이용하여 직접 샤드 설정을 해야합니다.


샤드 설정은 생각보다 간단합니다.


1. Jedis를 다운받은 후 프로젝트에 라이브러리를 추가합니다.

2. xml에 다음과 같이 bean을 등록합니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:c="http://www.springframework.org/schema/c"
	xmlns:p="http://www.springframework.org/schema/p"
	xmlns:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    
	<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" p:maxActive="500" p:maxIdle="300" p:maxWait="10000" p:testOnBorrow="true" />
	<util:list id="jedisShardInfos">
		<bean class="redis.clients.jedis.JedisShardInfo" c:host="${redis1.host.name}" c:port="${redis.port}" c:timeout="10000" c:weight="1" />
		<bean class="redis.clients.jedis.JedisShardInfo" c:host="${redis2.host.name}" c:port="${redis.port}" c:timeout="10000" c:weight="1" />
	</util:list>
	<!-- 해쉬 알고리즘 MURMUR와 MD5가 있음 -->
	<util:constant id="jedisAlgo" static-field="redis.clients.util.Hashing.MURMUR_HASH" />
	<util:constant id="jedisKeyTagPattern" static-field="redis.clients.util.Sharded.DEFAULT_KEY_TAG_PATTERN" />
	<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" c:poolConfig-ref="jedisPoolConfig" c:shards-ref="jedisShardInfos" c:algo-ref="jedisAlgo" c:keyTagPattern-ref="jedisKeyTagPattern" destroy-method="destroy" />	
</beans>

3. Jedis를 이용해 실제 Redis Command를 수행할 Template Class를 생성합니다.

package net.krespo.template;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPipeline;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.exceptions.JedisConnectionException;

@Component
public class RedisCacheTemplate {
	
	@Autowired protected ShardedJedisPool redisPool;
	public void set(String key, String value) {
		ShardedJedis jedis = null;
		try {
		    jedis = redisPool.getResource();
		    jedis.set(key,value)
		} catch (JedisConnectionException e) {
			if (jedis != null) redisPool.returnBrokenResource(jedis); // 접속이 끊긴 리소스 반환
		} finally {
		    if (jedis != null) redisPool.returnResource(jedis);
		}
	}
}

위의 코드에서 눈여겨 볼부분은 24번째 줄입니다. 

JedisConnectionException이 발생하면 redisPool의 returnBrokenResource를 호출하고 있는데요. 

샤딩을 구성하는 여러대의 서버중 한대가 장애가 발생하면, 커넥션 풀에는 장애나기 전 서버와의 커넥션이 그대로 존재하게 됩니다. 따라서 어플리케이션에서 커넥션풀에서 커넥션을 꺼내올것을 요청했을때(redisPool.getResource) 장애가 발생한 서버의 커넥션을 리턴해주고, 해당커넥션으로 레디스 커맨드를 실행하려고 하면 JedisConnectionException이 발생하게 됩니다. 따라서 해당 익셉션 발생시 풀에서 해당 커넥션을 제거해주는 작업을 해주기 위함입니다.


RedisCacheTemplate 클래스는 간단하게 jedis를 이용한 set메소드를 구현하였습니다. 위와 같이 Template 클래스를 만든 이유는 레디스 커맨드를 사용하기 전에 항상

  1. 커넥션 풀에서 커넥션 획득
  2. 커맨드 실행후 해당 커넥션 반환

의 작업을 항상 해주어야 하는데요. 이 번거러운 작업을 미리 작업해 두고 다른 persistance 레이어의 클래스에서는 해당 Template 클래스를 주입시켜 사용만 하면 되기 때문입니다.

ShardedJedis jedis = null;
try {
	jedis = redisPool.getResource();
	//레디스 커맨드 실행
} catch (JedisConnectionException e) {
	if (jedis != null) redisPool.returnBrokenResource(jedis); // 접속이 끊긴 리소스 반환
} finally {
	if (jedis != null) redisPool.returnResource(jedis);
}

위의 예제에서는 set메소드만 구현하였지만 상황에 따라 필요한 메소드를 구현하면 됩니다. 

그리고 redisPool.getResource와 redisPool.returnResource 같은 부분이 계속나와 신경쓰이신다면 AOP로 빼서 한꺼번에 적용하셔도 좋을것 같네요~


이상 스프링기반의 프로젝트에서 Jedis를 이용한 샤딩 설정을 마치겠습니다