'Cluster'에 해당되는 글 3건

데이터베이스/Nosql

redis cluster로 구성하여 실행 시켜보기

redis를 사용하면서 cluste로 구성해봐야하는 일이 있었다.

그래서 찾아보던 중 redis문서에서 방법을 찾았다. https://redis.io/topics/cluster-tutorial

 

Redis cluster tutorial – Redis

*Redis cluster tutorial This document is a gentle introduction to Redis Cluster, that does not use complex to understand distributed systems concepts. It provides instructions about how to setup a cluster, test, and operate it, without going into the detai

redis.io

따라서 구성해보자.

 

redis 다운로드

https://redis.io/download

 

다운 받고 압축을 풀고 make, make install 명령어를 사용해서 빌드한다.

tar xvfz redis-5.0.4
cd redis-5.0.4
make
make install

 

redis 클러스터 생성

redis 클러스터 구성을 위해서는 다음과 같이 6개의 노드가 필요하다. 이유는 아래의 내용과 같다.

** ERROR: Invalid configuration for cluster creation.** Redis Cluster requires at least 3 master nodes.
** This is not possible with 2 nodes and 1 replicas per node.** At least 6 nodes are required.

 

그럼 각 노드 생성을 위해서 필요한 설정을 담을 cluster-test 폴더를 먼저 만들자.

그리고 6700 ~ 6705까지의 폴더를 만든다.

jeongcheol-ui-MacBookPro:redis-5.0.4 wedul$ mkdir cluster-test
jeongcheol-ui-MacBookPro:redis-5.0.4 wedul$ cd cluster-test
jeongcheol-ui-MacBookPro:cluster-test wedul$ mkdir $(seq 6700 6705)
jeongcheol-ui-MacBookPro:cluster-test wedul$ ls
6700	6701	6702	6703	6704	6705

그리고 src 폴더에 make를 통해 생성된 redis-server 파일을 모든 폴더에 넣어주고 다음 설정이 들어간 redis.conf 파일까지 같이 넣어준다.

port 6700
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

그럼 모든 서버를 실행시켜보자.

./redis-server redis.conf &

이제 6개의 인스턴스가 실행중인것을 확인 할 수 있다. 그럼 이 인스턴스를 특별한 설정을 통해 cluster를 구성해야한다.

cluster설정은 redis 3, 4에서는 redis-trib.rb를 사용했지만 5부터는 redis-cli에 다 포함되어 있다.

그럼 아래 명령어를 사용해서 cluster를 구성해보자.

redis-cli --cluster create 127.0.0.1:6700 127.0.0.1:6701 127.0.0.1:6702 127.0.0.1:6703 127.0.0.1:6704 127.0.0.1:6705 --cluster-replicas 1

여기서 사용된 cluster-replicas는 각 인스턴스의 replica 노드를 하나씩 구성하겠다는 뜻으로 마스터노드 3개 slave노드 3개가 만들어진다.

그러면 아래 보는것과 같이 서버가 3대 3대로 마스터와 슬레이브가 구성이되고 yes를 누르면 그에 맞게 클러스터가 구성된다.

 

그럼 이렇게 구성된 클러스터를 사용해서 redis에 접속해보자.

cluster모드에 redis 접속을 위해서는 -c 옵션을 달아서 실행시킨다.

 

이렇게 레디스의 클러스터를 구성해봤다.

이를 사용해서 이제 업무에 적용해보자.

데이터베이스/Nosql

[번역] Redis partitioning

파티셔닝 공부를 위해 아래 페이지의 내용을 번역하며 정리해봤다.

https://redis.io/topics/partitioning


Redis Partitioning: 여러 레디스 인스턴스로 데이터 분배하기


파티셔닝은 데이터를 여러 레디스 인스턴스로 분할하여 모든 인스턴스가 자기가 소유한 키의 집합들만 소유하도록 하는 프로세스이다. 먼저 파티셔닝 개념에 대해 설명하고 레디스 파티셔닝에 대한 대안을 소개한다.

파티셔닝이 효율적인 이유

레디스에서 파티셔닝을 하기는 다음 두개의 이점이 있다.
1. 하나의 컴퓨터로 메모리의 양이 제한되는 경우에 파티셔닝을 사용하여 더 큰 데이터베이스와 메모리를 가질 수 있다.
2. 여러 개의 코어와 여러 대의 컴퓨터에 연산 능력을 확장하고 네트워크 대역폭을 여러 대의 컴퓨터와 네트워크 어댑터로 확장할 수 있다.

기본 파티셔닝 방법 (range partitioning)

파티셔닝에는 여러 기준이 있다. Redis 인스턴스 R0, R1, R2, R3 그리고 많은 사용자를 대표하는 키인 user:1, user:2와 같은 존재한다고 가정해보자. 이때 해당 키들을 어느 인스턴스에 어떻게 넣어야 하는지에 대한 여러 방법을 가지고 있다. 다른말로 말하면 주어진 키들을 주어진 인스턴스에 어떻게 매핑 할것인지에 대한 여러 방법이 있다.

가장 간단한 방법으로 range partitoning이 있다. 이 방법은 특정 범위에 있는 데이터는 특정 인스턴스에 매핑시켜서 데이터를 분배한다. 예를 들면 1 ~ 10000 까지의 데이터는 R0, 10001 ~ 20000 까지는 R1 식으로 저장 할 수 있다. 이 방식은 어떤 범위에 키를 어느 인스턴스로 매핑할지에 대한 정리가 되어있는 테이블이 필요하다.

이 테이블은 관리가 필요하고 모든 번위에대한 정리가 되어있어야 한다. 그래서 매우 불편하여 다른 파티셔닝 기법을 사용하여 이 번거로움을 대체한다.

해시 파티셔닝 Hash Partitioning

이 방식은 키와 함께 동작하고 object_name:<id>형식으로 키를 만들어서 사용하지 않아도 된다. 동작방식은 간단하다. 우선 키 이름을 crc32 해시 함수를 이용해서 숫자로 변경한다. 예를 들면 foobar라는 키는 93024922로 변경한다. 그리고 인스터스 개수 만큼 % 연산을 진행한다. 만약 인스턴스가 4개라면 93024922 % 4는 2이기 때문에 2번째 인스턴스에 들어간다.

다른 파티셔닝 종류

몇몇의 레디스 클라이언트와 프록시로 부터 hash function을 향상시켜서 만든 파티셔닝으로 consistent hashing라고 불린다.

Client side 파티셔닝
- 클라이언트에서 직접적으로 키를 가지고 읽고 기록할 노드를 선택한다. 많은 레디스 클라이언트는 이 파티셔닝을 구현한다.

Proxy assisted 파티셔닝
- 레디스 클라이언트가 바로 레디스 인스턴스에 요청을 보내지 않고 프록시에게 전송한다.
이 프록시는 적절하게 설정된 파티셔닝 스키마 대로 레디스 인스턴스에 저장하고 클라이언트에게 응답한다. 레디스와 Memcached에 대표적으로 Twemproxy가 존재한다.

Query 라우팅
- 임의의 인스턴스로 전달된 쿼리가 올바른 노드로 리다이렉션 되는 것을 말한다. redis cluster는 클라이언트에 도움을 받아서 하이브리드 형태의 쿼리 라우팅을 구현한다.

여러 파티셔닝이 있지만 기본 베이스는 기폰 파티셔닝과 해시 파티셔닝에서 구현된것이기 때문에 이 두가지가 기본이다.

Data store or Cache?

레디스에서 파티셔닝은 개념적으로 데이터 스토어와 캐시로 사용할 때 동일하지만 사실 데이터스토어로써 파티셔닝을 사용할 때는 약간의 제약이 존재한다. 레디스가 데이터 스토어로 사용될 때 키는 항상 같은 레디스 인스턴스에 있어야한다. 하지만 레디스가 캐시로 사용될 때 주어진 노드를 사용할 수 없을 때 다른 노드를 사용한다고 해서 큰문제가 되지 않는다. 이 경우에는 인스턴스 맵을 변경하여 수정할 수 있다. 위에서 제시되었던 파티셔닝에서 기존에 가야할 노드가 사용불가능할 경우 다른 노드로 저장될 수 있다. 비슷하게 만약 새로운 노드가 추가되면 새로운 키의 일부는 새로운 노드에 저장될 수 있다.

정리된 컨셉은 다음과 같다.
- 레디스를 캐시로 사용할 경우 scaling up and down이 자유롭다.
- 레디스를 데이터 ㅈ장소로 써 사용할 경우에는 고정된 키-인스턴스 맵이 존재해야하고 인스턴수의 개수는 그렇게 크지 않게 고정되어 있어야 한다. 그렇지 않으면 인스턴스가 추가되거나 제거 될 때 인스턴스간에 키를 리밸런싱 할 수 있는 시스템이 필요하다. 현재는 redis cluster만 이 기능을 제공한다.


PreSharding

위에 본거와 같이 레디스를 캐시로써 사용하지 않는이상 파티셔닝에 단점이 있느 것을 확인 할 수 있다.

하지만 데이터 스토어는 매일 많이 사용된다. 오늘 10개의 레디스 인스턴스 노드를 사용한다고 해도 다음날 50개가 필요할 수도 있다. 그렇기 때문에 고정된 인스턴스로 키-인스턴스 맵으로 관리하는 방식으로는 데이터 스토어로써 레디스를 사용하는데 어려움이 있다.

레디스가 필요 리소스가 적기 때문에 이 문제에 대한 간단한 접근방법은 애초에 많이 생성하는 것 입니다. 만약 하나의 서버로 서비스를 시작한다면 하나의 서버안에서 파티셔닝을 통해 여러 레디스를 구동할 수 있다. 그래서 처음부터 32개 또는 64개의 인스턴스를 만들어서 충분하게 사용자들이 사용할 수 있도록 설계할 수 있다. 이러한 방식으로 인스턴스를 크게 늘리고 만약 데이터 저장소가 더 필요하고 레디스 서버가 더 필요하다면 간단하게 인스턴스를 다른 서버로 이동 시킬 수 있따. 만약 부가적인 서버가 추가된다면 레디스 인스턴스 반을 추가된 서버로 이동 시킬 수 있다.
Redis 복제를 사용하면 사용자를위한 중단 시간이 거의 없거나 전혀 없을 때 이동을 수행 할 수 있다.


레디스 파티셔닝 사용

이론을 공부했다. 이제 어떻게 사용해야하는지 보자.

Redis Cluster
redis cluster는 자동으로 샤딩을 하고 높은 가용성을 가지는것을 선호한다. 2015년 4월 1일 부터 redis cluster를 사용할 수 있다. redis cluster는 query routing과 client side 파티셔닝을 섞어놓은 방식으로 진행된다.

Twemproxy
Twemproxy는 memcached ASCII와 redis 프로토콜을 위해서 트위터에서 개발된 프록시 이다. 싱글스레드이고 C로 개발되어 전적으로 빠르다. 여러 레디스 인스턴스에서 자동으로 샤딩이 되는 것을 지원하며 하나의 인스턴스가 사용이 불가능하면 다른 인스턴스로 전환되는 것을 지원한다.


주의사항
http://www.zdnet.co.kr/view/?no=20131119174125
여기에 보면 주의사항이 나오는데 핵심은 redis의 경우 싱글 스레드로 돌아가기 때문에 작업이 오래 발생되는 keys나 flushall은 사용하지 말아라. 1만건 이하에 데이터를 조작하는 경우에는 사용해도 되는데 그 이상 사용하는 경우에는 주의하라는 뜻.

web/Spring

Spring Boot에서 6.4 Elasticsearch 연결 및 간단 CRUD

Elasticsearch를 Spring Boot에서 작업을 하는 간단한 정리를 해보자.


1. Library 추가

Elasticsearch를 사용하기 위해서는 spring-data-elasticsearch 라이브러리가 추가되어야 한다. 

gradle에 추가해보자.

1
2
3
4
5
6
7
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    compileOnly "org.projectlombok:lombok:1.16.16"
}
 
cs


spring-data-elasticsearch 버전별로 호환되는 elasticsearch가 상이하니 참고

spring data elasticsearchelasticsearch
3.2.x6.5.0
3.1.x6.2.2
3.0.x5.5.0
2.1.x2.4.0
2.0.x2.2.0
1.3.x1.5.2


2. Configuration

Elasticsearch에 접속하기 위한 Configuration을 정의해준다.

Elasticsearch  접속을 위해서는 host, port, cluster name이 필요하다. cluster name을 알아야 하는데 docker에 설치 한 경우 여기서 확인하면 된다.

우선 docker exec -it elastic bash로 콘솔에 접속한 후에 elasticsearch.yml에 적혀있는 cluster name을 확인한다.

그리고 application.properties에 설정 내용을 적어준다.

1
2
3
4
elasticsearch.host=127.0.0.1
elasticsearch.port=9300
elasticsearch.cluster_name=docker-cluster
spring.main.allow-bean-definition-overriding=true
cs

그리고 EnableElasticsearchRepositories 애노테이션을 설정한 Configuration 클래스를 만들어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.elasticsearch.study.configuration;
 
import org.springframework.beans.factory.annotation.Value;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
 
import java.net.InetAddress;
 
/**
 * Elasticsearch Configuration
 *
 * @author wedul
 * @since 2019-02-09
 **/
@EnableElasticsearchRepositories
@Configuration
public class ElasticConfiguration {
 
  @Value("${elasticsearch.host}")
  private String host;
 
  @Value("${elasticsearch.port}")
  private int port;
 
  @Value("${elasticsearch.cluster_name")
  private String clusterName;
 
  @Bean
  public Client client() throws Exception {
    Settings settings = Settings.builder().put("cluster.name", clusterName).build();
 
    TransportClient client = new PreBuiltTransportClient(settings);
    client.addTransportAddress(new TransportAddress(InetAddress.getByName(host), port));
    return client;
  }
 
  @Bean
  public ElasticsearchOperations elasticsearchTemplate() throws Exception {
    return new ElasticsearchTemplate(client());
  }
 
}
 
cs


3. DTO 생성

Elasticsearch에서 Document 내용을 담을 DTO를 만들어주고 @Document 애노테이션을 달고 index name과 type을 정의해준다.

@Id 어노테이션이 붙은 필드는 각 Doucument에 붙어있는 _id 값이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.elasticsearch.study.dto;
 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
 
/**
 * studyFor
 *
 * @author wedul
 * @since 2019-02-09
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Document(indexName = "wedul_play", type = "story")
public class WedulPlay {
 
  @Id
  private String id;
  private String title;
  private String user;
  private long startAt;
  private long endAt;
 
}
 
cs


4. Repository

JPA를 사용하면 익숙할 패턴으로 Elasticsearch에서도 ElasticsearchRepository가 존재한다. 사용방법은 JPA와 동일하게 저장할 때는 save, 조회할 때는 find(), findByUser()등으로 사용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.elasticsearch.study.repository;
 
import com.elasticsearch.study.dto.WedulPlay;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
 
/**
 * study
 *
 * @author wedul
 * @since 2019-02-09
 **/
@Repository("wedulPlayRepository")
public interface WedulPlayRepository extends ElasticsearchRepository<WedulPlay, String> {
 
  WedulPlay findByUser(String user);
  
}
 
cs


5. Service

지금 테스트 하는 부분에서는 크게 비즈니스 로직에 들어갈 소스가 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.elasticsearch.study.service;
 
import com.elasticsearch.study.dto.WedulPlay;
import com.elasticsearch.study.repository.WedulPlayRepository;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
 
import java.util.List;
 
/**
 * study
 *
 * @author wedul
 * @since 2019-02-09
 **/
@AllArgsConstructor
@NoArgsConstructor
@Service
public class WedulPlayService {
 
  private WedulPlayRepository wedulPlayRepository;
 
  public void save(WedulPlay play) {
    wedulPlayRepository.save(play);
  }
 
  public List<WedulPlay> findAll() {
    return Lists.newArrayList(wedulPlayRepository.findAll());
  }
 
  public WedulPlay findByUser(String user) {
    return wedulPlayRepository.findByUser(user);
  }
 
}
 
cs


6. Test 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.elasticsearch.study.wedulplay;
 
import com.elasticsearch.study.dto.WedulPlay;
import com.elasticsearch.study.repository.WedulPlayRepository;
import com.elasticsearch.study.service.WedulPlayService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
import java.util.List;
 
import org.hamcrest.core.IsNull;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
 
/**
 * wedul play document 조회
 *
 * @author wedul
 * @since 2019-02-09
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
public class WedulPlayTest {
 
  WedulPlayService wedulPlayService;
 
  @Autowired
  @Qualifier("wedulPlayRepository")
  WedulPlayRepository wedulPlayRepository;
 
  @Before
  public void setup() {
    wedulPlayService = new WedulPlayService(wedulPlayRepository);
  }
 
  @Test
  public void whenValidParameter_thenSuccessFind() {
    List<WedulPlay> list = wedulPlayService.findAll();
 
    assertNotNull(list);
  }
 
  @Test
  public void whenValidParameter_thenSuccessSave() {
    Exception ex = null;
 
    try {
      wedulPlayService.save(WedulPlay.builder().title("안녕 이건 테스트야").user("위들").startAt(1242421424).endAt(23214124).build());
    } catch (Exception exception) {
      ex = exception;
    }
 
    assertTrue(null == ex);
  }
 
  @Test
  public void whenValidParameter_thenSuccessFindByUser() {
    Exception ex = null;
 
    try {
      WedulPlay play = wedulPlayService.findByUser("위들");
 
      assertThat(play, is(IsNull.notNullValue()));
    } catch (Exception exception) {
      ex = exception;
    }
 
    assertTrue(null == ex);
  }
 
 
}
 
cs


설정이 간단하다. 

나중에 이용해 먹어야지


자세한 소스코드는 여기 참조

https://github.com/weduls/spring_elastic

푸터바

알림

이 블로그는 구글에서 제공한 크롬에 최적화 되어있고, 네이버에서 제공한 나눔글꼴이 적용되어 있습니다.

카운터

  • Today : 0
  • Yesterday : 460
  • Total : 82,691