728x90

 

본문 출처 : 코딩스타트 :: Redis - Cluster & Sentinel 차이점 및 Redis에 대해 (tistory.com)

 

요즘 챗봇 개발 중에 Redis와 같은 In-memory Cache를 이용할 필요가 생겨서 Redis를 공부 하고있다.

많은 블로그를 보는 도중에 Redis에 대한 기술적인 좋은 글과 설명이 있어서 포스팅한다.

 

▶︎▶︎▶︎레디스 클러스터, 센티넬 구성 및 레디스 동작 방식

 

RDBMS만큼의 정합성과 영속성을 보장할 필요가 없는 데이터들을 빠르게 처리하거나 일정 기간동안만 보관하고 있기 위한 용도로 레디스(Redis), memcached 등의 in-memory 기반 저장소가 많이 사용된다. 그중에서도 Redis는 빠른 성능을 유지하면서도 일정 수준 이상의 persistence를 제공하고, 다양한 native 데이터 구조들을 제공하여 효율적이고 편리한 사용이 가능하게 해주기 때문에 다양한 use-case들이 존재한다.

이 글은 실제 명령어를 날려서 레디스를 직접 사용해보는 것을 배우기 보다는 어떤 in-memory 저장소를 선택할지 고민하는 분들을 위해서 주요 운영 방식, 레디스의 내부 동작 방식 및 특징, 주요 클라이언트들에 대한 정보를 제공하는 쪽에 초점이 맞춰져 있다.

운영 모드 (Operation Modes)

레디스는 단일 인스턴스만으로도 충분히 운영이 가능하지만, 물리 머신이 가진 메모리의 한계를 초과하는 데이터를 저장하고 싶거나, failover에 대한 처리를 통해 HA를 보장하려면 센티넬이나 클러스터 등의 운영 모드를 선택해서 사용해야 한다. 각 모드들이 어떤 특성을 갖는지 좀더 자세히 알아보도록 하자.

  • 단일 인스턴스(Single instance)
    • HA(High availibilty) 지원 안됨.
  • 센티넬(Sentinel)
    • HA 지원
    • master/slave replication
    • sentinel process
      • redis와 별도의 process
      • 여러개의 독립적인 sentinel process들이 서로 협동하여 운영된다 (SPOF 아님)
      • 안정적 운영을 위해서는 최소 3개 이상의 sentinel instance 필요 (fail over를 위해 과반수 이상 vote 필요)
        • redis process가 실행되는 각 서버마다 각각 sentinel process를 띄워놓는 방법
        • redis process가 실행되는 서버와 별개로 redis에 액세스를 하는 application server들에 sentinel process를 띄워놓는것도 가능
        • 등등 다양한 구성이 가능
      • 지속적으로 master/slave 가 제대로 동작을 하고있는지 모니터링
      • master에 문제가 감지되면 자동으로 failover 수행
    • 클라이언트는 어떻게 redis server에 연결해서 데이터를 조회하나?
      • 먼저 sentinel에 연결해서 현재 master를 조회해야 한다.
  • 클러스터(Cluster)
    • HA, sharding 지원
      • Sentinel과 동시에 사용하는 것이 아님! 완전히 별도의 솔루션.
      • dataset을 자동으로 여러 노드들에 나눠서 저장해준다.
      • Redis Cluster 기능을 지원하는 client를 써야만 데이터 액세스 시에 올바른 노드로 redirect가 가능하다.
    • Cluster node들의 동작 방식
      • serve clients 6379 (data port)
      • cluster bus 16379 (data port + 10000)
        • 자체적인 바이너리 프로토콜을 통해 node-to-node 통신을 한다.
        • failure detection, configuration update, failover authorization 등을 수행
      • 각 노드들은 클러스터에 속한 다른 노드들에 대한 정보를 모두 갖고있다.
    • Sharding 방식
      • 최대 1000개의 노드로 샤딩해서 사용. 그 이상은 추천하지 않음
      • consistent hashing을 사용하지 않는대신 hashslot이라는 개념을 도입
      • hashslot
        • 결정방법 CRC16(key) mod 16384를
          • CRC16을 이용하면 16384개의 슬롯에 균일하게 잘 분배됨
        • 노드별로 자유롭게 hash slot을 할당 가능
        • 예)
          • Node A contains hash slots from 0 to 5500.
          • Node B contains hash slots from 5501 to 11000.
          • Node C contains hash slots from 11001 to 16383.
      • 운영 중단 없이 hash slots을 다른 노드로 이동시키는 것이 가능
        • add/remove nodes
        • 노드별 hashslot 할당량 조정
      • multiple key operations 을 수행하려면 모든 키값이 같은 hashslot에 들어와야 한다.
        • 이를 보장하기위해 hashtag 라는 개념 도입
          • {} 안에있는 값으로만 hash 계산
          • {foo}_my_key
          • {foo}_your_key
    • Replication & failover
      • failover를 위해 클러스터의 각 노드를 N대로 구성가능 
      • master(1대) / slave(N-1대)
      • async replication (master → slave replication 과정에서 ack을 받지 않음)
        • 데이터 손실 가능성 존재
        • master가 client요청을 받아서 ack을 완료한 후, 해당 요청에 대한 replication이 slave로 전파되기 전에 master가 죽는 경우 존재
    • 클라이언트는 클러스터에 어떻게 연결해서 데이터를 조회하나?
      • redis client는 클러스터 내의 어떤 노드에 쿼리를 날려도 된다(슬레이브에도 가능).
        • ex) GET my_key
      • 쿼리를 받은 노드가 해당 쿼리를 분석
        • 해당 키를 자기 자신이 갖고있다면 바로 찾아서 값을 리턴
        • 그렇지 않은경우 해당 키를 저장하고 있는 노드의 정보를 리턴 (클라이언트는 이 정보를 토대로 쿼리를 다시 보내야함)
        • ex) MOVED 3999 127.0.0.1:6381

메모리 동작 방식

  • key가 만료되거나 삭제되어 redis가 메모리를 해제하더라도, OS에서 해당 분량만큼 바로 메모리가 확보되진 않음
    • 꼭 redis에만 해당되는 이야기는 아님
    • 5GB중 3GB의 데이터를 메모리에서 해제 -> OS 메모리 사용량은 여전히 5GB
    • 하지만 다시 데이터를 add하면 logically freed된 영역에 잡히므로 실제 메모리 5GB를 넘지는 않는다.
  • 따라서 peak memory usage 기준으로 잡아야 한다.
  • 대부분 5GB 정도 사용하고 가끔 10GB 가 필요하더라도, 10GB 메모리를 이상의 머신이 필요.
  • maxmemory 설정을 해두는게 좋음 (하지 않으면 무한히 메모리 사용하다가 머신 전체가 죽을 가능성)
    • maxmemory 설정시의 eviction policy
      • no-eviction (추가 memory를 사용하는 write command에 대해 에러 리턴)
      • allkeys-lru (전체 아이템 중에서 LRU)
      • volatile-lru (expire 되는 아이템 중에서 LRU)
      • volatile-ttl (expire 되는 아이템 중 TTL이 얼마 안남은 녀석 순으로)
    • RDB persistence를 사용한다면 maxmemory를 실제 가용 메모리의 45%정도로 설정하는것을 추천. 스냅샷을 찍을때 현재 사용중인 메모리의 양만큼 필요하다. (5%는 오버헤드에 대비한 마진)
    • 사용하고 있지 않다면 가용 메모리의 95%정도로
  • 동일 key-value 데이터를 저장한다고 가정했을 때, Cluster Mode를 사용할 경우 Single instance 보다 1.5~2배 정도 메모리를 더 사용하는것에 주의해야 한다.
    • Redis cluster의 경우 내부적으로 cluster안에 저장된 key를 hashslot으로 맵핑하기 위한 테이블을 가지고 있기 때문에 추가적인 memory overhead가 발생한다.
    • 이때문에 key의 숫자가 많아질수록 이러한 현상이 더 두드러진다
    • 4.x 버전에 이와 관련한 메모리 최적화 기능이 들어가서 3.x 버전보다는 더 적게 메모리를 사용하지만, 여전히 Single instance보다는 많은 메모리를 필요로 한다.

데이터 영속성 (Data Persistence)

memcached의 경우 데이터가 메모리에만 저장되기 때문에 프로세스가 재기동되면 메모리상의 데이터는 모두 유실된다. 하지만 redis의 경우 기본적으로 disk persistence가 설정되어있기 때문에, 프로세스를 재시작 하더라도 셧다운 되기 전의 마지막 상태와 거의 동일한 (약간의 손실은 있을 수 있다) 상태로 돌려 놓을 수 있다.

  • RDB persistence
    • 일정 인터벌로 point-in-time snapshots을 생성
    • compact한 단일 파일로 저장됨 백업하기 좋음
    • 부모 프로세스는 자식 프로세스를 fork. fork된 프로세스에서 모든 persist I/O처리
    • restart 시간이 빠르다
      • H/W 사양에 따라 다르겠지만 보통 메모리 사용량 1GB당 10~20 초정도 로드타임
    • 멈췄을때 데이터 유실 가능성이 더 높다.
    • 몇 분 정도의 데이터 유실 가능성을 감내 할 수 있다면 RDB를 쓰는것을 추천
  • AOF (append only file)
    • 모든 write operation 을 log로 남기고 서버 재시작 시점에 replay
    • 데이터 양이 많아지면 해당 시점의 데이터셋을 만들어낼 수 있도록하는 minimum log들만 남기는 compaction을 진행
    • 읽기 쉬운 포멧이지만 RDB보다 용량이 크다
    • write 리퀘스트가 많을때 RDB보다 반응성이 느리다
  • 참고: http://oldblog.antirez.com/post/redis-persistence-demystified.html

라인 메신저의 메시징 시스템에서는 RDB또는 AOF 사용으로 인해

트랜잭션 모델(Transaction model)

  • Redis는 single threaded model이고 요청이 들어온 순서대로 처리한다
  • MULTI → commands → EXEC/DISCARD
  • MULTI 이후의 명령들은 queue에 넣어뒀다가 EXEC가 불린순간 순차적으로 진행
    • EXEC를 통해서 실행중일때는 다른 connection에서 중간에 끼어들지 못한다
  • command 실행중에 에러가 발생해도 롤백하지 않고 계속 진행한다.
    • command에 잘못된 명령어나, 잘못된 타입의 인자를 넣었을때 에러 발생 -> 거의 개발자의 실수
    • 롤백기능을 없앤 덕분에 훨씬 빠른 성능 제공 가능
  • optimistic locking (using CAS operation) 사용
    • 값의 변경을 모니터링하다가, 값이 변경되었다면 현재 트랜잭션을 취소하고 다시 처음부터 실행
    • WATCH
      • 특정 키값이 변경되지 않은 경우에만 EXEC를 수행, 변경된경우 transaction자체를 수행하지 않음
      • EXEC가 불리는 시점에 모든 key에 대한 WATCH가 자동으로 UNWATCH됨
      • client의 연결이 끝나는 시점에도 모든 key에 대해 UNWATCH됨
    • 예시)
      • WATCH mykey
      • val = GET mykey
      • val = val + 1
      • MULTI
      • SET mykey $val
      • EXEC

주요 특수 기능

  • 다양한 데이터 구조 지원
    • 단순히 key – value 문자열만 저장하는 것이 아니라 고수준의 데이터 구조를 사용 가능하다
    • ex) Hash, Set, List, SortedSet, etc.
    • Hash
      • HSET(key, fields, value), HGET(key, field)
      • web application에서 특정 유저 userId를 key로 두고 해당 유저의 세부 정보들(name, email 등)을 field로 둔다
      • 이렇게하면 특정 유저와 관련된 정보들을 한번에 삭제하는 등의 namespace처럼 사용하는것도 가능하다.
      • hash key당 1000개 정도의 field까지는 레디스가 zipmap을 이용해 압축해서 저장한다
  • Expiration 지정
    • key별로 TTL(Time-To-Live)을 정해두면 레디스가 알아서 해당 시점이 지날때 key 삭제
    • 설정된 max memory에 도달하면 expire되지 않은 key들도 eviction policy에 따라 삭제될 수 있다.
  • Pipelining
    • 여러 커맨드들을 한번에 모아서 보낸후, 실행 결과도 한번에 모아서 받는다.
    • 레이턴시가 큰 네트워크 환경이라면 명령어 하나당 한번의 request/response를 할 때보다 스루풋을 올릴 수 있음.
  • Pub/Sub (출판/구독 모델)
    • 하나의 클라이언트가 같은 채널에 연결된 다른 클라이언트들에게 메시지들을 보내는 것이 가능
    • 이를 이용하여 속도가 빠른 메시지 브로드캐스터 혹은 메시지 큐 처럼 사용하는것이 가능하다.
  • Lua scripting
    • 여러 명령어들이 사용되는 복잡한 작업을 할 때 (특히 트랜잭션이 사용되는 경우) 이를 하나의 lua script로 만들어서 사용할 수있다.
    • 스크립트는 atomic하게 실행되기 때문에 optimistic locking transactions 를 사용할 필요가 없다.

지표 모니터링 (Monitoring Metrics)

  • used_memory
  • total_commands_processed
    • 이 지표와 요청/응답 latency 로깅해두면 명령어 처리량 대비 latency를 알 수 있다.
    • redis-cli -h {host} -p {port} —latency
  • slow command
    • slowlog get 명령으로 확인
  • client connections
    • info clients 명령으로 확인
    • 대략 5000개 이상의 커넥션들이 존재한다면 응답 시간이 느려지고 있을 가능성이 있으니 주의깊게 모니터링
    • maxclients 값을 대략 예상되는 connection 숫자의 1.1배~1.5배정도로 설정해 두면 안전함
  • mem_fragmentation_ratio
    • 정의 = 실제 물리 메모리 사용량 / 레디스의 메모리 사용량
    • 1 이면 이상적인 상태, 커질수록 파편화 심화
    • 1.5가 넘는다면 서버 재시작 추천
    • 1보다 작은 경우
      • 레디스의 메모리 사용량이 실제 물리 메모리 사용량보다 큰 경우는 OS에 의해 메모리의 일부가 swap되어 디스크로 이동되었을때 나타남
      • 디스크 사용으로 인해 성능 저하 발생
      • 메모리 사용량을 줄이거나 램 용량 증설 필요
  • 참고: https://www.datadoghq.com/pdf/Understanding-the-Top-5-Redis-Performance-Metrics.pdf

클라이언트 핸들링(Client Handling)

  • maxclients 설정 가능
    • 설정된 maxclient 도달시에 
  • timeout default = forever
  • Redis 3.2 이상부터 TCP keep-alive 디폴트로 켜져있음(약 300초)

Redis Client library for Java

언어별로 레디스 클라이언트 라이브러리들이 다양하게 존재하고 있다. 그중에서 자바언어로 만들어진 가장 많이 사용되는 3가지를 뽑아서 비교해 보았다.
async style API를 사용하는 경우 처리량(throughput)을 높일 수 있지만 오히려 개별 요청에 대한 응답 지연(latency)은 sync API 보다 더 느려질 수 있으니 상황에 맞게 선택해서 사용하는 것이 좋다.

반응형
728x90

본문 출처 : 코딩스타트 :: Springboot,Redis - Springboot Redis Nodes Cluster !(레디스 클러스터) (tistory.com)

 

이전 포스팅에서는 Redis Server들의 고가용성을 위해 Redis Sentinel을 구성하여 Master-Slave 관계의 구성을 해보았습니다. 

 

▶︎▶︎▶︎Redis Sentinel 구성

 

Sentinel을 구성하여 Redis Server들의 고가용성을 키워주는 방법 이외에도 사실 Redis는 Cluster라는 좋은 기능을 지원해줍니다.

그럼 Sentinel은 무엇이고 Redis Cluster는 다른 것인가? 대답은 엄연히 다른 기능입니다. 

간단히 비교하면 Sentinel는 Master-Slave관계를 구성합니다.(Redis Server 끼리 구성을 갖춤).

하지만 Redis Server 이외에 Sentinel 인스턴스를 띄워주어야합니다. 그런 Sentinel 인스턴스들은 Redis Server들을 모니터링하고 고가용성을 위한 적당한 처리를 해줍니다. 그리고 Redis Server끼리의 데이터 동기화도 마춰줍니다. 이말은,

모든 Redis Server는 모두 같은 데이터들을 가지고 있는 것이죠.

하지만 Cluster를 이용하면 각 Redis Server들은 자신만의 HashSlot을 할당 받게 됩니다. 그리고 Cluster도 Master-Slave 관계를 구성하게 됩니다.

이말은 무엇이냐? 대략 16000개의 데이터 바구니를 나누어가지는 Redis Server들은 Master가 됩니다.

Sentinel과는 다르게 하나의 마스터만 갖는 것이 아닙니다. 그리고 각 마스터에 대한 Slave 서버를 가지게 되는 것입니다. 더 자세한 사항은 아래 링크를 참조해주세요.

 

▶︎▶︎▶︎Cluster&Sentinel 그리고 Redis

 

이제는 Redis Cluster 구성을 해보겠습니다. 오늘 구성해볼 아키텍쳐입니다.

혹시나 Redis를 설치와 간단한 사용법에 대해 모르신다면 아래링크를 참조해주세요.

 

▶︎▶︎▶︎Redis 설치와 사용법

 

3개의 Master와 3개의 Slave 입니다.(편의상 Redis 폴더의 루트 == $REDIS)

 

$REDIS 위치에 cluster라는 폴더를 하나 구성해줍니다. 

그리고 해당 $REDIS/redis.conf를 cluster 폴더에 6개를 복사해줍니다.(redis-cluster1~6.conf)

 

이제 각 redis-cluster 설정파일을 수정할 것입니다. 이번에 할 설정은 간단한 설정입니다. 프러덕환경에서는

더 세부적인 설정이 필요할 수 있습니다.

 

이번예제는 동일한 서버에 6개의 port를 나누어 진행합니다. 만약 서로 다른 서버에 구성을 하시기 위해서는

적절히 인스턴스들을 나누어주시고 각 서버에 대해 포트 개방이 필요합니다.

 

redis-cluster1.conf - port:6379

 

설정은 직관적으로 어떠한 설정에 대한 것인지 알수 있습니다. 해당 인스턴스의 포트는 6379를 사용하고

클러스터를 사용하겠다. 그리고 해당 인스턴스가 클러스터에 대한 정보를 남기기위해 nodes.conf를 사용한다.

또한 타임아웃은 5초로 하고 모든 데이터는 영속하기 위해 항상 write마다 기록한다 라는 설정입니다.(데이터 유실방지)

 

나머지 인스턴스들의 설정도 port와 cluster-config-file의 설정만 구분하고 동일하게 작성합니다.

 

ex) port 6380, cluster-config-file nodes2.conf

 

설정 파일작성이 끝나셨으면 6개의 터미널을 띄워줍니다.

 

>cd src

>./redis-server ../cluster/redis-clusterN.conf 

 

총 6개의 레디스 인스턴스를 실행시킵니다.

 

그리고 하나 추가적으로 작업을 해주어야할 것이 있습니다. 실행되고 있는 인스턴스에 대해

명시적으로 클러스터 구성을 생성해주는 작업입니다. 이 과정은 버젼에 따라 총 2가지의 방법이 있습니다.

 

1
2
3
redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6380 \
127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 \
--cluster-replicas 1
cs

 

$REDIS/src 의 redis-cli를 이용한 방법입니다. 클러스터 구성에 참여하는 인스턴스 정보를 모두 입력하고 마지막에 replicas 속성을

명시해줍니다. 마지막 속성은 마스터에 대한 슬레이브를 몇개를 둘것인가 라는 설정입니다.

 

1
2
./redis-trib.rb create --replicas 1 127.0.0.1:6379 127.0.0.1:6380 \
127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384
cs

 

동일한 속성에 대한 redis-trib.rb를 이용한 클러스터 구성방법입니다.

 

저는 첫번째 방법을 이용하였습니다. 명령어를 탁 치는 순간 3개는 마스터 3개는 슬레이브 노드를 임의로 

선택해 이렇게 클러스터를 구성하겠습니까? 라는 질문에 yes||no로 답변해주어야합니다. yes를 입력합니다.

 

이제는 클러스터 구성이 잘 되었는지 확인해볼까요?

잘 구성이 되었습니다 ! 여기서 한가지 집고 넘어가야 할 것이 있습니다. Redis Cluster 사용을 위해서는 그에 맞는 클라이언트가 필요합니다. 저는

그 클라이언트를 Springboot를 이용하여 구성해보았습니다. springboot의 Spring Redis 프로젝트를 생성해줍니다!

1
2
3
4
#Redis Cluster Config(마스터노드의 리스트)
spring.redis.cluster.nodes=127.0.0.1:6379,127.0.0.1:6380,127.0.0.1:6381
클러스터 노드간의 리다이렉션 숫자를 제한.
spring.redis.cluster.max-redirects=
cs

application.propeties 파일입니다. 클러스터에 참여하는 노드들을 나열해줍니다.

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
/**
 * Redis Cluster Config
 * @author yun-yeoseong
 *
 */
@Component
@ConfigurationProperties(prefix = "spring.redis.cluster")
public class RedisClusterConfigurationProperties {
    
    /**
     * spring.redis.cluster.nodes[0]=127.0.0.1:6379
     * spring.redis.cluster.nodes[1]=127.0.0.1:6380
     * spring.redis.cluster.nodes[2]=127.0.0.1:6381
     */
    List<String> nodes;
 
    public List<String> getNodes() {
        return nodes;
    }
 
    public void setNodes(List<String> nodes) {
        this.nodes = nodes;
    }
    
    
}
cs

properties에 나열한 노드들의 정보를 얻기위한 빈을 하나 띄워줍니다. 물론 @Value로 직접 주입시켜주어도 상관없습니다. 해당 방법은 Spring Redis Document에 나온데로 진행하고 있는 중입니다.

/**
 * Redis Configuration
 * @author yun-yeoseong
 *
 */
@Configuration
public class RedisConfig {
    
    
    /**
     * Redis Cluster 구성 설정
     */
    @Autowired
    private RedisClusterConfigurationProperties clusterProperties;
    
    /**
     * JedisPool관련 설정
     * @return
     */
    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        return new JedisPoolConfig();
    }
    
    
    /**
     * Redis Cluster 구성 설정
     */
    @Bean
    public RedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
        return new JedisConnectionFactory(new RedisClusterConfiguration(clusterProperties.getNodes()),jedisPoolConfig);
    }
    
    /**
     * RedisTemplate관련 설정
     * 
     * -Thread-safety Bean
     * @param jedisConnectionConfig - RedisTemplate에 설정할 JedisConnectionConfig
     * @return
     */
    @Bean(name="redisTemplate")
    public RedisTemplate redisTemplateConfig(JedisConnectionFactory jedisConnectionConfig) {
        
        RedisTemplate redisTemplate = new RedisTemplate<>();

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(jedisConnectionConfig);
        
        return redisTemplate;
        
    }
    
    /**
     * 문자열 중심 편의 RedisTemplate
     * 
     * @param jedisConnectionConfig
     * @return
     */
    @Bean(name="stringRedisTemplate")
    public StringRedisTemplate stringRedisTemplate(JedisConnectionFactory jedisConnectionConfig) {
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(jedisConnectionConfig);
        
        return stringRedisTemplate;
    }
    
}
 

Redis Config를 위한 자바클래스입니다. 이제 정말로 잘되는지 확인해볼까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
    
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Test
    public void testDataHandling() {
        
        redisTemplate.getConnectionFactory().getConnection().info().toString();
        
        String key = "yeoseong";
        String value = "yoon";
        redisTemplate.opsForValue().set(key, value);
        String returnValue = (String) redisTemplate.opsForValue().get(key);
        
        System.out.println(value);
    }
    
}
 
cs

결과값으로 "yoon"이라는 값을 얻어옵니다. 그러면 진짜로 클러스터된 노드들에서 얻어왔는지 확인해봐야겠습니다.

 

 

6379 포트의 인스턴스로 해당 값을 얻어오려고 하니 실제로는 6381에 해당 데이터가 있어 리다이렉트 됬다라는 로그와 함께 결과 데이터를 얻어왔습니다.

여기까지 Redis Cluster 구성이었습니다. 

반응형
728x90

본문 출처 : 코딩스타트 :: Redis - Redis 설치 및 설정, 간단한 사용방법 (tistory.com)

 

Redis란? 정말 단순하게 표현하면 key-value 형태의 값을 저장할 수 있는 하나의 In-Memory DB라고 생각하시면 됩니다. 

사용용도는 정말 많습니다. Springboot에서는 세션클러스터링을 위해 Redis를 사용하기도 하고, Cache를 위해서 사용하기도 하며, 여러 서버에서 공통으로 공유해야하는 정보를 담는 용도로 사용하기도 합니다. 필자는 챗봇을 개발중인데, 이중화된 서버에서 세션값을 공유하기위한 용도로 Redis를 사용하려고 합니다. 우선 이번 포스팅에서는 Redis 설치 및 설정, 그리고 간단한 사용법에 대한 포스팅입니다.

 

▶︎▶︎▶︎Redis Homepage

 

우선 위에서 Redis를 다운로드 받아줍니다.

 

그리고 다운로드 받아진 Redis Root 폴더로 들어가서

>make 

명령으로 컴파일을 해줍니다.

 

준비끝 !

 

첫번째 예제는 Redis Server를 Standard Alone으로 구성하는 예제입니다.

(편의상 Redis 폴더의 root를 $REDIS로 표현합니다.)

 

>$REDIS/src/redis-server 

 

위처럼 실행시켜줍니다. 그러면 Redis Server은 모든 기본설정으로 구동이됩니다.

localhost:6379

 

 

이런 화면이 보인다면 지금까지는 잘 따라오신겁니다. 다음은 cli를 이용하여 간단하게 데이터를 삽입,조회 해보겠습니다.

 

직관적으로 key-value 형태의 데이터를 삽입하고 조회하는 예제입니다.

 

현재까지의 구성으로 dev환경에서는 충분히 활용가치가 있습니다. 하지만 prod환경에서는 하나의 Redis Server로

운영한다는 것은 아주 위험한 선택일 것 같습니다. 그래서 다음 예제는 Zookeeper와 같은 소프트웨어에서도 이용하는

구성인 Master-Slave 관계를 구축해보겠습니다. Redis Server 구성은 하나의 로컬에서 구성한다는 가정입니다.

 

서버명  IP:PORT 
Master Node  127.0.0.1 : 6379 
Slave Node - 1  127.0.0.1 : 6380 
Slave Node - 2  127.0.0.1 : 6381 

위의 구성으로 진행해보도록 하겠습니다. 진행하기에 앞서 우리가 이번 포스팅에서 건들게되는 설정파일이 있습니다.

그것은 바로 redis.conf입니다. 해당 파일을 들어가보면 주석으로 친절하게 설정정보 설명이 되어있습니다.

이번에는 모든 설정정보를 다루기보다는 우리가 많이 다루고, 꼭 필요한 정보들에 대한 설명만 할 예정입니다.

 

우선 다중 Redis Server 구성을 위하여 $REDIS에 있는 redis.conf를 2개 복사해둡니다.(Slave 2개 설정파일)

>cp redis.conf ./redis-slave1.conf

>cp redis.conf ./redis-slave2.conf

 

설정값 설명 
daemonize [yes||no] default no  Redis는 기본적으로 daemon으로 실행하지 않습니다. 만약 daemon으로 실행하고 싶다면 'yes'를 사용하시면 됩니다. 만약 daemon으로 실행시 '/var/run/redis.pid' 파일에 pid를 디폴트로 기록할 것입니다.(파일경로 변경가능 - pidfile 설정) 
port [number] default 6379  Connection 요청을 받을 포트를 지정하는 설정입니다. 
bind [ip] default all interface Redis를 bind 할 특정 interface를 지정하는 설정입니다. 만약 명시하지 않으면 모든 interface로부터 들어오는 요청들을 listen합니다. 
unixsocket [path], unixsocketperm [number]
unixsocket /tmp/redis.sock / unixsocketperm 755
소켓으로 Redis Server를 붙게할 수 있는 설정입니다. 
timeout [second]  클라이언트의 idle 시간이 해당 초만큼 지속되면 connection을 닫습니다.
0으로 지정할시 항상 connection을 열어둡니다. 
loglevel [level - debug, verbose,notice,warning]  로그레벨지정 
logfile [file path]  로그파일이 쓰여질 파일 경로를 지정합니다. 
slaveof -> replicaof [master ip] [master port] 
최근 버젼은 slaveof에서 replicaof로 변경됨.(어느 버전부터 바뀐지는 확인필요)
Master-Slave 관계를 구성하기 위하여 Master의 replication Node가될 slave Node 설정파일에 Master Node의 ip와port 정보를 기입하는 설정이다.
즉, Master의 데이터가 slave에 replicate되게 된다. 
 masterauth [master-password] 만약 Master Node 설정파일에 Slave Node가 붙기 위한 암호요구를 하였다면(require pass [password]), Slave Node의 설정파일은 Master Node가 지정한 패스워드로 해당 설정을 해줘야한다. 
slave-server-stale-data [yes||no] default yes
-> replica-server-stale-data [yes||no] default yes 
만약 slave가 master와의 connection이 끊겼거나 replication 중일 때, 취할수 있는 행동 2가지가 있다.


1. [yes] - 유효하지 않은 데이터로 클라이언트 요청에 답한다.
2. [no] - 몇몇 명령을 제외하고 요청에 대해 error로 응답한다. 
repl-ping-slave-perid [seconds] default 10  Slave가 지정된 시간마다 ping을 날린다. 
repl-timeout [seconds] default 60  타임아웃 시간을 설정한다. 기본적으로 ping 주기보다 큰 시간을 주어야한다. 
 require pass [password] 클라이언트에게 패스워드를 요구한다.(Slave가 Master에 접근할때의 보안설정이기도하다) 
maxclient [size] 한번에 연결될 수 있는 클라이언트의 연결수이다. 
maxmemory [bytes]  Redis Server의 최대 메모리 사용양을 설정한다. 만약 memory에 한계가 도달했다면 Redis는 선택된 eviction policy(제거정책)에 따라 key들을 제거한다. 만약 제거에 실패한다면 read-only에 대한 명령만 수행할 수있게 된다. 
maxmemory-policy [policy]  max memory 도달시 취할 정책을 설정한다
1) volatile-ru : expire가 설정된 key들 중 LRU 알고리즘을 이용해 선택된 key를 제거한다.
2) alleys-lru :  모든 keys에 대해 LRU 알고리즘을 적용해 제거한다.
3) volatile-random : expire가 설정된 key들 중 임의의 key를 제거한다.
4) alleys-random : 모든 keys중 랜덤하게 제거한다.
5) volatile-ttl : expire time이 가장 적게 남은 key를 제거한다.
6) noeviction : 어떠한 key도 제거하지 않는다. read-only작업만 허용하고 write와 그 밖에 명령은 error를 내뿜는다.
appendonly [yes||no] Redis는 기본적으로 비동기로 disk에 dataset의 dump를 남긴다. 이 방법은 최근 데이터가 손실되어도 큰 문제가 없다면 사용해도 좋지만, 하나라도 데이터의 유실을 원치 않는 상황이라면 yes로 설정하여 요청받는 모든 write를 disk에 쓰도록하면 된다.(default file appendonly.aof) 
 appendfilename [file name] append only 파일 이름을 설정한다. 

여기까지 몇가지 설정들을 다루어봤다. 실제 위에 설정들 일부를 사용하여 Master-Slave 관계를 구성해볼 것이다.

 

우선 MasterNode의 설정파일로 사용할 redis.conf이다.

 

>bind 127.0.0.1

>port 6379

>requirepass yourpassword

>daemonize  yes

 

이정도 설정으로 나머지는 기본설정을 사용한다.

 

이제는 나머지 SlaveNode의 설정파일 2개이다.

 

>bind 127.0.0.1

>port 6380(slave -1) / port 6381(slave-2)

>replicaof 127.0.0.1 6379

>masterauth yourpassword

>daemonize yes

 

로 구성한다.

 

이제 순차적으로 실행시켜본다

 

>$REDIS/src/redis-server ../redis.conf

>$REDIS/src/redis-server ../redis-slave1.conf

>$REDIS/src/redis-server ../redis-slave2.conf

 

Master&Slave를 순차적으로 실행 시킨 로그이다. Connection이 맺어지고 있는 것이 보인다.

 

그리고 실제로 데이터의 복제가 잘 이루어지는지 보기 위해 몇개의 창을 더 띄워서

cli로 데이터를 삽입,조회해본다.

 

복사가 잘된것을 확인할 수 있다. 그렇다면 혹시나 Master와의 관계를 끊기 위해

Master 노드를 중지시켰다면 어떻게 될까?

 

SlaveNode들의 로그를 보면 connection refused 로그가 계속해서 떨어진다.

하지만 아까 보았던 설정에서 replica-server-stale-data의 설정에 따라 Slave들의 행동이 

달라질것이다.

 

마지막으로 다시 MasterNode를 띄워보면

Slave들이 다시 Master에 connection 된것을 볼수있다.

여기까지는 좋다. 데이터 복제를 위해 replica도 구성했고, 근데 하나찜찜한 것이있다. Master가 죽으면 알아서 Slave중에서 Master를 선출하면 좋을 텐데.... 방법이 있다!

바로 sentinel을 이용하여 클러스터를 구성해주는 것이다. sentinel은 아쉽게도 다음 포스팅에서 다뤄볼 예정이다.

 

반응형
728x90

 

본문 출처 : 코딩스타트 :: Redis - Sentinel 이란? 설정방법! Redis 고가용성을 위한 방법 (tistory.com)

 

이전 포스팅에서 Redis Server Replication 구성방법에 대해 알아봤습니다. 이번 포스팅은

Redis 고가용성을 위한 Sentinel 기능에 대해 알아보려고 합니다. 어떻게 보면 조금더 완벽한 클러스터링을

구성한다고 생각하시면 됩니다. 

 

만약 Redis 설치 및 설정 방법을 모르신다면 아래 링크를 통해 참조하시고 오셔도 좋을 것같습니다.

▶︎▶︎▶︎Redis 설치 및 설정, 간단한 사용법!

 

우선 Sentinel에 대한 기능에 대해 알아보겠습니다.

 

1) 모니터링

2) 알림기능

3) 페일오버

4) 환경 구성 프로바이더

 

이러한 기능을 제공해줍니다.

오늘 예제로 구성해볼 이미지입니다.

 

구성이 이해가 가십니까? 간단하게 설명을 하면 Master&Slave는 이전 포스팅과 동일하게 3개의 노드를 띄웁니다.

그리고 Sentinel도 동일하게 3개의 노드를 띄웁니다(3개의 의미가 있음). 이런 구성에서 Sentinel은 마스터를 지속적으로 

모니터링합니다. 그리고 장애가 있을시에 적당한 프로세스를 거칩니다. 여기서 Sentinel의 노드를 3개를 띄웠는데요. 이것은

의미가 있는 숫자입니다. 이전에 포스팅 중 Zookeeper관련된 글에서도 동일한 정책을 따랐는데요. 

 

"바로 홀수단위로 띄운다"

 

입니다. 이것은 만약 네트워크의 잠깐의 오버타임때문에 마스터가 죽었다고 생각하는 하나의 Sentinel이 있을 수 있습니다.

그럼 이것이 진짜로 죽은 거라고 판단을 해야할까요? 우리는 보통 선거를 하게되면 과반수의 원칙에 따르게 됩니다. 여기서도

동일하게 과반수의 원칙을 따르게 되는 겁니다. 과반수 이상의 Sentinel이 "OK" 해야 비로소 그 마스터 노드는

죽은 것이고, 그때서야 Slave 노드에서 마스터 노드를 선출하게 되는 것입니다. 그렇기 때문에 Sentinel은

3개이상의 홀수 인스턴스를 띄운다 원칙을 지켜주셔야합니다.

 

우리가 구성할 실제 구성도입니다.

진행하기 앞서, $REDIS(Redis 폴더 root)에 있는 sentinel.conf 파일을 2개 복사해줍니다.(총 3개의 Sentinel 설정파일 구성)

그리고 아래 설정을 port만 각기 분리해주고 나머지 설정을 동일하게 작성해줍니다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11001.conf ~ 11003.conf
 
# 센티널이 실행될 포트입니다. (이부분은 포트별로 다르게 설정)
port 11001
# 센티널이 감시할 레디스 Master 인스턴스 정보를 넣어줍니다.
# sentinel monitor mymaster <redis master host> <redis master port> <quorum>
sentinel monitor mymaster 127.0.0.1 10000 2
# 센티널이 Master 인스턴스에 접속하기 위한 패스워드를 넣어줍니다.
sentinel auth-pass mymaster foobared
# 센티널이 Master 인스턴스와 접속이 끊겼다는 것을 알기 위한 최소한의 시간입니다.
sentinel down-after-milliseconds mymaster 30000
# 페일오버 작업 시간의 타임오버 시간을 정합니다.
# 기본값은 3분입니다.
sentinel failover-timeout mymaster 180000
# 이 값은 Master로부터 동기화 할 수 있는 slave의 개수를 지정합니다.
# 값이 클수록 Master에 부하가 가중됩니다.
# 값이 1이라면 Slave는 한대씩 Master와 동기화를 진행합니다.
sentinel parallel-syncs mymaster 1
cs

 

여기서 하나 설명할 것이 있다면 설정중 quorum 입니다. 이것은 의사결정에 필요한 최소 Sentinel 노드수라고 생각하시면 됩니다.

즉, 지금 구성에서는 딱 과반수가되는 2개의 Sentinel이 동의하면 의사결정이 진행이 되는 것입니다.

 

SDOWN vs ODOWN

More advanced concepts이라는 페이지에서 SDOWN과 ODOWN이라는 단어가 나옵니다. SDOWN은 Subjectively Down condition의 축약어이고 ODOWN은 Objectively Down condition의 축약어입니다.

SDOWN은 센티널 인스턴스가 Master와 접속이 끊긴 경우 주관적인 다운 상태로 바뀝니다. 이것은 잠시 네트워크 순단 등으로 인해 일시적인 현상일 수 있으므로 우선 SDOWN 상태가 됩니다.

그러나 SDOWN 상태인 센티널들이 많아진다면 이는 ODOWN 상태(quorum), 즉 객관적인 다운 상태로 바뀝니다. 이때부터 실질적인 페일오버(failover) 작업이 시작됩니다.

 

위의 설정을 모두 완료하셨다면 이전 포스팅에서 진행한 redis.conf를 조금 변경해야합니다.

 

Redis Server 설정 파일에 마스터,슬래이브 관계없이 모두

 

masterauth yourpassword

requirepass yourpassword

 

두개의 설정을 3개의 redis conf에 설정해줍니다. 이유는 슬래이브 노드가 마스터 노드로 선출될 수도 있기에

모든 슬래이브는 require pass 설정을 가져야하고 마스터 노드도 슬래이브 노드가 될수 있기 때문에

masterauth설정을 해주어야합니다.

 

이제 실행을 해봅니다. Redis Server들은 이전 포스팅에서 진행한 데로 실행해주시면 됩니다.

 

>$REDIS/src/redis-sentinel ../sentinel.conf

>$REDIS/src/redis-sentinel ../sentinel2.conf

>$REDIS/src/redis-sentinel ../sentinel3.conf

 

명령으로 모든 센티널들을 실행시킵니다.

 

로그를 하나하나 설명안해도 읽어보시면 어떠한 로그인지 직관적으로 이해가갑니다.

 

여기에서 마스터 노드를 shutdown 시켜봅니다. 그리고 마스터 선출에 시간이 걸리니 대략

30초 정도 기다려봅니다. 아니? 슬래이브 노드가 마스터노드로 선출됬습니다.

 

기존에 마스터였던 6379는 슬래이브로, 기존에 슬래이브였던 6381이 마스터로 선출되었습니다.

위의 정보를 출력하기 위해서는 

 

>./redis-cli -p port -a password

>info

 

로 접속하셔야합니다. 패스워드를 작성안하시면 해당 명령어를 사용하실수 없습니다.

 

여기까지 Redis Server 고가용성을 위한 Sentinel 구성이었습니다. production 환경에서는 반드시

위와같이 장애에 대응할 수 있는 서버 구성을 가져야합니다. 

 

다음 포스팅은 실제 Redis를 Springboot에서 추상화된 객체로 사용해보는 예제를 진행 해볼 것입니다.

반응형
728x90

    <error-page>
        <error-code>400</error-code>
        <location>/error-40X.jsp</location>
    </error-page>
    <error-page>
        <error-code>401</error-code>
        <location>/error-40X.jsp</location>
    </error-page>
    <error-page>
        <error-code>403</error-code>
        <location>/error-40X.jsp</location>
    </error-page>
    <error-page>
        <error-code>404</error-code>
        <location>/error-40X.jsp</location>
    </error-page>
    <error-page>
        <error-code>500</error-code>
        <location>/error-50X.jsp</location>
    </error-page>
    <error-page>
        <exception-type>java.lang.Throwable</exception-type>
        <location>/error.jsp</location>
    </error-page>

반응형
728x90

출처 : NginX를 이용한 static 컨텐츠 서비스 와 캐시 설정 (joinc.co.kr)

1. NginX를 이용한 static 컨텐츠 서비스

NginX를 리버스 프락시 서버로 사용하는 이유는 다음과 같다.

  1. 로드밸런싱 : 유저의 요청을 웹 애플리케이션 서버(WAS)로 분산 할 수 있다.
  2. 유저 요청에 대한 선 처리 : 유저의 요청이 WAS에 도달하기 전에 다양한 처리를 할 수 있다. 웹 애플리케이션 방화벽(WAF)를 설치하거나, 유저의 요청을 다른 위치로 보내도록 제어 할 수 있다.
  3. 캐싱 : 웹 서비스는 이미지, CSS, 자바스크립트 같은 정적인 페이지를 가지고 있다. 이런 정적 컨텐츠들을 NginX에서 대신 처리하는 것으로 응답 속도를 높일 수 있으며, WAS에 대한 부담을 줄일 수 있다. 컨텐츠들을 메모리에 캐시할 경우 서비스 할 경우 고성능의 웹 서비스를 만들 수도 있다.

이중 3번, Nginx를 이용해서 스태틱 페이지를 캐싱해서 서비스하는 방법을 테스트한다.

2. Web Contents 캐시에 대해서

2.1. 서버 캐시

웹 서비스의 성능을 높일 수 있는 가장 확실하고 손쉬운 방법은 컨텐츠 캐시다. 요즘 웹 서버는 매우 바쁘다. 유저의 요청을 받아서 데이터베이스를 조회해서 즉석에서 HTML 페이지를 만들고 이미지와 CSS, 자바스크립트 등 다양한 오브젝트들을 함께 응답해야 한다. 웹 서버가 해야 하는 일이 늘어나면서 리버스 프락시를 이용, 두 개 이상의 WAS(Web Application Server)가 요청을 분산해서 처리하도록 구성을 한다.

유저가 요청을 내리면 WAS로 바로가지 않고, 리버스 프락시 서버를 한번 거치게 된다. 요청을 분산하기 위해서 프락시 서버를 한번 더 거쳐야 하는데, 모든 컨텐츠가 굳이 프락시 서버를 거칠 필요가 있을까 ? 변하지 않는 컨텐츠의 경우에는 그냥 프락시 서버에서 직접 응답을 하게 하면 더 빠른 서비스를 제공 할 수 있지 않을까 ? 요즘 왠만한 웹 서비스들은 동적으로 컨텐츠를 서비스 한다. 하지만 조금만 들여다 보면, 많은 컨텐츠가 정적(변하지 않음)이라는 것을 알 수 있다. 이미지, CSS, Javascript, 동영상, PDF 뿐만 아니라 HTML 문서의 상당수도 정적이다. 유저 정보를 표시하는 HTML 문서는 (데이터베이스 등을 조회해서) 동적으로 만들어져야 하겠으나, 메뉴얼, 사이트 소개, 메뉴 등은 변하지 않는 컨텐츠들이다. 이들 컨텐츠들을 프락시 서버에서 서비스 한다면 더 빠르고 효율적으로 서비스 할 수 있을 것이다. 아래 그림을 보자.

리버스 프락시에서 정적인 페이지를 대신 응답한다. 이렇게 구성해서 얻을 수 있는 성능상의 잇점을 살펴보자.

  1. 네트워크 홉 이 줄어든다. WAS까지 요청을 보내지 않아도 된다. 1만큼의 네트워크 홉을 줄일 수 있다.
  2. 파일처리에 최적화 할 수 있다. 캐시 서버는 파일에 대한 "읽기/쓰기" 연산, 그 중에서도 읽기 연산을 주로 한다. 목적이 특정되므로 이에 맞게 최적화 할 수 있다. 당연하지만 NginX나 Apache 같은 전용 웹서버는 다양한 일을 하는 WAS(Django, RoR, Node)보다 빠르고 효율적으로 작동한다.
  3. 최적화가 쉽다. 정적 컨텐츠와 동적 컨텐츠를 분리 함으로써, 그에 맞는 최적화 방법을 사용 할 수 있다. 정적 컨텐츠를 Redis나 memcache, 램디스크 등으로 캐시할 수 있다.

3. NginX 캐시 매커니즘

NginX 서버의 캐시 매커니즘은 아래와 같다.

  1. NginX 캐시 서버가 컨텐츠 요청을 받는다.
  2. 요청 URL을 Key로 컨텐츠가 캐시돼 있는지 확인한다.
  3. 처음 요청이므로 캐시 실패(MISS) 한다.
  4. WAS서버로 요청을 보낸다.
  5. WAS서버로 부터 응답이 오면 캐시를 할지를 검사한다.
  6. 응답을 디스크에 저장한다.
  7. 다음 번에 같은 URI로 요청이 오면, 캐시 적중(HIT)한다. 요청은 WAS로 전달되지 않으며, 캐시서버가 대신 응답한다.

4. 테스트 구성

아래와 같은 서비스를 구성하기로 했다.

WAS 서버는 CSS, 이미지, Javascript를 포함한 동적인 컨텐츠를 서비스 한다. WAS 앞에는 Nginx가 WAS 서버들을 로드밸런싱 한다. WAS는 로드밸런서의 역할 뿐만 아니라 static 페이지들을 캐시하고 있다가 대신 서비스하는 캐시서버 역할을 한다.

  • Nginx는 WAS(Web application Server)나 다른 웹 서버(Nginx나 apache 같은)의 앞에 배치 할 수 있다. 로드밸런서로서의 역할 과 캐시 서버로의 역할을 동시에 수행 할 수 있다.!
  • Nginx는 일반적인 HTTP 요청 뿐만 아니라 FastCGI uWSGI 요청의 결과를 캐시할 수 있다. 이 기능은 특히 CMS(Content Management System)의 결과물을 캐시하는데 유용하게 사용 할 수 있다.
  • 캐시서버를 둠으로써, 애플리케이션 서버의 부하를 분산 할 수 있다.

5. WAS 서버 구성

테스트를 위해서 WAS 서버를 구성했다. 이 서버는 HTML, CSS, 자바스크립트, 이미지등을 서비스한다. NginX로 만들기로 했다. 아래는 설정파일이다.

 
 
이 설정파일은 기본적인 캐시 설정을 포함하고 있다. manifest, appcache, html, xml, json 파일들은 캐시를 하지 않는다. jpg,gif,gz 과 같은 파일들은 한달의 만료기간동안 캐시를 한다. css와 js 파일들은 1년을 캐시한다.curl로 index.html을 요청해보자.
$ curl -XGET -I 192.168.56.30/index.html
HTTP/1.1 200 OK
Server: nginx/1.9.3 (Ubuntu)
Date: Mon, 09 May 2016 14:59:36 GMT
Content-Type: text/html
Content-Length: 7261
Last-Modified: Mon, 09 May 2016 14:54:30 GMT
Connection: keep-alive
ETag: "5730a4a6-1c5d"
Expires: Mon, 09 May 2016 14:59:35 GMT
Cache-Control: no-cache
Accept-Ranges: bytes

Expires Date가 같은 시간임을 알 수 있다. 요청시간과 동시에 컨텐츠가 만료됐다. 따라서 클라이언트는 요청 결과를 캐시하지 않는다. 또한 Cache-Control: no-cache로 이 컨텐츠를 캐시하지 말라고 지시하고 있다.css파일을 요청해 보자.

# curl -XGET -I 192.168.56.30/css/foundation.css
HTTP/1.1 200 OK
Server: nginx/1.9.3 (Ubuntu)
Date: Mon, 09 May 2016 15:10:52 GMT
Content-Type: text/css
Content-Length: 105995
Last-Modified: Fri, 15 Apr 2016 07:10:18 GMT
Connection: keep-alive
ETag: "571093da-19e0b"
Expires: Tue, 09 May 2017 15:10:52 GMT
Cache-Control: max-age=31536000
Cache-Control: public
Accept-Ranges: bytes
 
Expires시간이 현재시간으로 부터 1년 후임을 알 수 있다. 이 규칙에 의해서 foundation.css는 최대 1년 동안 캐시가 된다.

6. 캐시 서버 설정

WAS 서버 설정이 끝났다. 이제 캐시서버를 만든다. 이 캐시서버는 WAS 서버를 로드밸런싱하면서 동시에 주요 컨텐츠들을 캐싱하는 역할을 한다. 유저의 요청은 캐시서버를 거치는데, 요청 결과가 캐시되어 있다면 (WAS까지 요청을 보내지 않고)캐시서버가 즉시 결과를 응답한다.역시 NginX 서버를 설치하고 아래와 같이 설정했다.

# cat /etc/nginx/site-available/default
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {
        include proxy_params;
        proxy_pass http://192.168.56.30;
    }
}

우선은 간단한 reverse proxy로 설정했다. 캐시서버는 192.168.56.31:80에서 유저 요청을 기다린다. 만약 유저 요청이 도착하면, proxy_pass에 설정된 192.168.56.30으로 요청을 전달한다. curl을 이용해서 /css/foundation.css를 요청해 보자. 캐시서버가 컨텐츠를 캐시하지 않고 있기 때문에, 이 요청은 WAS까지 전달된다.

# curl -XGET -I 192.168.56.31/css/foundation.css
HTTP/1.1 200 OK
Server: nginx/1.9.3 (Ubuntu)
Date: Tue, 10 May 2016 14:05:56 GMT
Content-Type: text/css
Content-Length: 105995
Connection: keep-alive
Last-Modified: Fri, 15 Apr 2016 07:10:18 GMT
ETag: "571093da-19e0b"
Expires: Wed, 10 May 2017 14:05:56 GMT
Cache-Control: max-age=31536000
Cache-Control: public
Accept-Ranges: bytes
이제 NginX가 WAS의 컨텐츠를 캐시하도록 설정을 바꿔보자.
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=my_zone:10m inactive=60m;
proxy_cache_key "$scheme$request_method$host$request_uri";
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {
        proxy_cache my_zone;
        add_header X-Proxy-Cache $upstream_cache_status;

        include proxy_params;
        proxy_pass http://192.168.56.30;
    }
}

6.1. proxy_cache_path

캐시파일이 저장되는 위치와 저장 방식을 설정하기 위해서 사용한다. levels는 캐시파일을 어떻게 저장할지를 결정한다. 만약 levels를 설정하지 않는다면, 캐시파일은 현재 디렉토리에 저장이 된다. level을 설정할 경우, 서브디렉토리에 md5 해시된 파일이름으로 컨텐츠를 저장한다. level=1:2는 첫번째 단계의 디렉토리는 한글자, 두번째 단계의 디렉토리는 두 글자로 명명하라는 의미다. 예를 들어 /tmp/nginx/a/bc 디렉토리에 캐시 파일이 저장된다.keys_zone은 이 캐시를 가리키는 이름이다. my_zone은 캐시와 메타파일을 저장하기 위해서 10M의 공간을 할당 했다.

 

inactive지시어를 사용 하면, 일정 시간동안 접근이 없는 캐시파일을 할 수 있다. 위 설정에서는 한시간(60 minutes)동안 사용하지 않는 캐시파일은 삭제하도록 설정했다.

6.2. proxy_cache_key

각 캐시파일을 구분하기 위한 Key 규칙을 설정한다. 기본 설정 값은 $scheme$proxy_host$uri$is_args$args이다. "$host$request_uri $cookie_user"와 같이 사용 할 경우 유저 쿠키 별로 캐시를 구성할 수도 있다.

6.3. proxy_cache

location블럭내에서 사용한다. my_zone 캐시를 사용하도록 설정했다. add_header X-Proxy-Cache $upstream_cache_status 헤더를 추가했다. 이 헤더에는 HIT, MISS, MYPASS와 같은 캐시 적중 상태정보가 설정된다.curl를 이용해서 컨텐츠를 요청해 보자.

 
아직 캐시가 없어서 MISS가 설정된 걸 확인 할 수 있다. 캐시를 하기 위해서는 WAS로 부터 최소한 한 번 이상의 요청이 이루어져야 한다. 다시 한번 요청을 해보자.
 
캐시가 성공(HIT)한 것을 알 수 있다. 이 요청은 WAS로 전달되지 않고, 캐시 서버가 대신 응답한다. 캐시서버 디렉토리에서 캐시파일을 확인 할 수 있다.
# cd /tmp/nginx/c/6f
# ls
d34ee92169a4e9dbb366919f06d756fc
이제 Ngix는 클라이언트의 Cache-Control 헤더 요청을 무시한다. 만약 클라이언트의 Cache-Control 요청을 허용하고 싶다면, 아래와 같이 설정을 바꿔야 한다.
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {
        proxy_cache my_zone;
        proxy_cache_bypass $http_cache_control;
        add_header X-Proxy-Cache $upstream_cache_status;

        include proxy_params;
        proxy_pass http://192.168.56.30;
    }
}

curl로 Cache-Control을 테스트해보자.

 
X-Proxy-Cache가 BYPASS로 설정된 걸 알 수 있다. 이 요청은 WAS 서버로 직접 전달된다. tcpdump로 Cache-Control을 설정했을 때와 그렇지 않았을 때를 테스트 해보자.

6.4. 캐시 상태

앞서 X-Proxy-Cache를 이용해서 캐시의 상태를 반환하고 있다. MISS, HIT, BYPASS외에 몇 가지 상태들이 더 있다.

MISS 캐시를 찾을 수 없다. 요청은 WAS 까지 전달된다. WAS 응답이 끝나면 캐시가 만들어질 것이다.
BYPASS 캐시서버의 캐시를 무시하고, WAS까지 요청을 전달한다. 캐시서버는 아마도 캐시를 가지고 있을 것이다.
EXPIRED 캐시 만료시간이 초과했다. 캐시서버는 WAS로 요청을 전달해서 캐시를 업데이트 한다.
STALE 서버가 제대로 응답하지 않아서, 낡은(stale) 캐시를 서비스하고 있다.
UPDATING 정확한 목적을 모르겠다. 좀 더 살펴봐야 할 듯
REVALIDATED 캐시가 여전히 유효하다. 클라이언트가 if-modified-since를 설정했을 때 리턴한다.
HIT 성공적으로 캐시를 서비스 했다.

7. Joinc에 적용

Joinc는 Go로 직접 개발한 CMS(자칭 gowiki)로 운영하고 있다. 모니위키를 기반으로 하고 있기 때문에, 위키문법의 문서를 HTML로 변환하는데 상당한 자원이 들어간다. 성능을 올리기 위해서 gowiki 서버 앞에 nginx 캐시서버를 두기로 했다.

/w 로 시작하는 URL의 컨텐츠는 정적인 컨텐츠이므로 캐시하기로 했다. 예를 들어 처음 /w/FrontPage를 요청하면, Gowiki로 요청이 전달된다. Gowiki는 FrontPage 문서를 HTML로 변환해서 응답한다. 이 응답은 NginX 서버가 캐시를 하므로, 다음번 요청 부터는 캐시에 있는 데이터를 응답한다.동적인 컨텐츠는 /api로 요청을 한다. 코드 실행기가 대표적인 경우인데, 이런 컨텐츠는 캐시하면 안 된다. 그리고 css, js, png, jpg 등도 캐시하기로 했다. NginX의 설정은 다음과 같다.

# cat /etc/nginx/site-available/default
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=my_zone:100m inactive=1440m;
proxy_cache_key "$scheme$request_method$host$request_uri";
server {
    listen 80 default_server;
    root /var/www/;
    index index.html index.htm;

    charset utf-8;

    location / {

        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }

    location /w/ {
        proxy_cache my_zone;
        proxy_cache_valid any 30m;
        add_header X-Proxy-Cache $upstream_cache_status;
        add_header Cache-Control "public";
        expires 1y;

        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }

    location ~* \.(?:css|js|html)$ {
        proxy_cache my_zone;
        proxy_cache_valid any 30m;
        add_header X-Proxy-Cache $upstream_cache_status;
        add_header Cache-Control "public";
        expires 1y;

        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }
}

7.1. 캐시 삭제

일단 캐시가된 컨텐츠의 경우, 컨텐츠의 내용이 바뀌더라도 여전히 캐시의 내용을 읽어오는 문제가 있다. 위키 특성상 문서를 자주 수정하게 되는데, 수정한 내용이 보이지 않는 것은 심각한 문제가 될 수 있다. 문제를 해결하기 위해서는 문서의 수정 후 해당 컨텐츠의 캐시를 찾아서 삭제하는 코드를 만들어야 했다. 직접 만들기 귀찮아서 nginx-cache-purge라는 프로그램을 다운로드 해서 사용했다. 그럭저럭 잘 작동하는 것 같다. 예를 들어서 /w/man/12/nginx 문서를 수정했다면 ./nginx-cache-purge /w/man/12/nginx /tmp/nginx 수행하면 된다.혹은 컨텐츠의 key를 md5 한 값을 데이터베이스에 저장해서 문서 수정시 삭제하는 방법이 있다. 위 설정에 의하면 /w/man/12/nginx의 key는 "httpGETwww.joinc.co.kr/w/man/12/nginx/static"이다. 이걸 md5 한 값은 아래와 같다.

# echo -n "httpGETwww.joinc.co.kr/w/man/12/nginx/static" | md5sum
0c9aa652815494e45573c077c9015c60 -
 
이 캐시파일의 정확한 위치는 "/tmp/nginx/0/c6/0c9aa652815494e45573c077c9015c60"다. 디렉토리는 md5 해시의 마지막 값으로 알아낼 수 있다.

7.2. 성능 테스트

크롬 개발자도구를 이용해서 응답속도만 테스트 했다. static 의 응답시간을 확인하자.캐시 적용 전

캐시 적용 후

캐시 적용 된 후 47msec 에서 5msec로 줄어든 걸 확인 할 수 있다.

7.3. 하나의 HTML 문서에 정적 컨텐츠와 동적 컨텐츠가 함께 있을 때

Joinc 사이트는 아래와 같이 구성된다.

모든 페이지들이 메뉴와 wiki 본문 으로 구성이 된다. 메뉴는 유저의 상태에 따라서 구성이 달라진다. 로그인 하기 전이라면 로그인 관련 메뉴를 출력하고,로그인 후라면 문서 편집과 같은 메뉴들이 출력된다. 컨텐츠가 동적으로 바뀌는 셈이다. 반면 Wiki 문서 내용은 정적 컨텐츠다. 만약 페이지 전체를 캐시를 해버리면, 메뉴 출력에 문제가 생길 것이다. 이 문제를 해결해야 했다.나는 쿠기(cookie)를 이용해서 이 문제를 해결 했다. 아래와 같이 proxy_cache_key에 cookie_login을 설정했다.

proxy_cache_key "$scheme$request_method$host$request_uri$cookie_login";
 
유저가 로그인에 성공하면 SetCookie("login", "true")로 login 쿠키를 설정한다. 로그아웃 하면 SetCookie("login","")로 쿠키를 삭제한다. 이렇게 하면 동일한 페이지라고 하더라도 로그인과 로그아웃 상태별로 캐시파일을 만들 수 있다. 간단하기는 하지만 캐시파일이 두배가 된다는 단점이 있다. 메뉴부분을 모두 자바스크립트로 처리하는 방법도 있는데, "개인 사이트에서 캐시공간이 늘어나 봤자 얼마나 늘어나겠나 ?"라는 생각에 패스하기로 했다(귀찮아서 패스했다는 얘기다).

 

반응형

'WEB & WAS > nginx' 카테고리의 다른 글

nginx와 Tomcat 구성  (0) 2016.08.22
728x90

[Tomcat] 톰캣 오류 처리하기 만들기 ErrorReportValve Custom Tomcat Valve

프로그램 자료/Java & Spring 2019. 10. 31. 11:31

출처1 : http://jagadesh4java.blogspot.com/2014/09/custom-error-page-in-tomcat.html

출처2 : https://aspiresoftware.in/blog/catalinatomcat-custom-error-report-valve-to-handle-errors-exceptions/

출처3 : https://github.com/theand/til-by-heesang/blob/master/md/java/tomcat_invalid_character_found_in_request.md

출처4 : https://server0.tistory.com/39

 

얼마전에 납품한 솔루션의 웹 보안 리포트를 받았다. 

특정 url에서 페이지 없음 오류가 떠야하는데 톰캣 에러가 예쁘게 뜨더라.

 

대충 이렇게?? 

 

좀 찾다보니까 톰캣 8.5.31(?) 이상, 9.0.8(?) 이상, 8.0.52(?) 버전부터는 RFC 7230, RFC 3986에 의해 특수문자를 받지 않는다고 하더라.

이미 톰캣 설정에 relaxedQueryChars를 사용하고 있어서 당당히 [] 문자를 넣었다. 

 

그런데 다음과 같은 url로 접속하면 여전히 톰캣 오류가 뜨는 증상이 있었다.

http://127.0.0.1:8080/[

 

어떻게 해야하나 고민했었는데 톰캣에서는 Valve라는 것을 자바 클래스로 만들고, 이걸 상속받아서 구현하면 가져다가 쓸 수 있다고 하더라.

 

package com.motolies.config;

 

import java.io.BufferedWriter;

import java.io.IOException;

import java.io.OutputStreamWriter;

import java.util.logging.Logger;

 

import org.apache.catalina.connector.Request;

import org.apache.catalina.connector.Response;

import org.apache.catalina.valves.ErrorReportValve;

 

public class CustomErrorReportValve extends ErrorReportValve {

 

    // Create a simple logger

private static final Logger logger = Logger.getLogger(CustomErrorReportValve.class.getName());



    @Override

    protected void report(Request request, Response response, Throwable t) {

        try {

            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), "UTF8"));

            out.write("<!DOCTYPE html>");

            out.write("<html xmlns=\"http://www.w3.org/1999/xhtml\" xmlns:th=\"http://www.thymeleaf.org\">");

            out.write("<head>");

            out.write(" <meta charset=\"utf-8\"/>");

            out.write(" <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"/>");

            out.write(" <meta name=\"description\" content=\"\"/>");

            out.write("</head>");

            out.write("<body>   ");

            out.write(" <script type=\"text/javascript\">");

            out.write("     alert(\"비정상적인 접근입니다.\");");

            out.write("     history.back();");

            out.write(" </script>");

            out.write("</body>");

            out.write("</html>");

            out.close();

 

            // Log the error with your favorite logging framework...

     logger.severe("Uncaught throwable was thrown: " + t.getMessage());

 

        } catch (IOException e) {

            e.printStackTrace();

        }

    }

}

 

대충 위와 같은 클래스를 만들고 jar 파일로 만들어야 한다. 

 

jar는 다음과 같이 만들면 된다. 

 

 

해당 파일을 우클릭해서 Export 클릭.

 

Export type에 JAR file 선택.

 

만들어질 위치 선택하고 Finish.

 

그럼 jar파일이 만들어지는데 이것을 다음의 경로에 복사한다.

 

..\tomcat\lib

 

아마 이미 많은 jar파일들이 있을 것이다. 

 

마지막으로 tomcat\conf\server.xml 에서 수정을 좀 해야한다. 

 

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"

errorReportValveClass="com.motolies.config.CustomErrorReportValve">

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"

prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b" />

</Host>

 

다음과 같이 빨간색 부분을 추가해주고 톰캣을 재시작하면 된다.

 

 

반응형
728x90

Linux 에서 Tomcat과 nginx를 연동해 사용하려고 할 때 설정.

Tomcat

초기 설정대로 8080 포트로 실행한다.

nginx

  • /etc/nginx/conf.d/default.conf 수정 : location 정보를 추가해 80포트로 들어오는 요청을 8080 포트로 pass한다.
location = /50x.html {
        root   /usr/share/nginx/html;
    }
    location ~ \.do$ {
      proxy_pass              http://localhost:8080;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        Host $http_host;
    }
    location ~ \.jsp$ {
      proxy_pass              http://localhost:8080;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        Host $http_host;
    }
    location ^~/servlets/* {
      proxy_pass              http://localhost:8080;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        Host $http_host;
    }
    location ^~/* {
      proxy_pass              http://localhost:8080;
      proxy_set_header        X-Real-IP $remote_addr;
      proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header        Host $http_host;
    }

nginx Load Balancer

  • 만약 여러대의 서버로 구성된 애플리케이션 서버들이 있고 그 앞에 LB용(세션 정보 공유) 서버가 있다면 아래와 같이 upstream 정보로 서버 ip를 나열하고 proxy_pass로 upstream 정보를 입력한다.
upstream backend {
    ip_hash;
    server 210.122.7.224:80;
}

server {
    listen       80;
    location /resources/ {
        alias   /home/townus/resources/;
        autoindex off;
        access_log off;
        expires max;
    }
    location / {
        #root   /usr/share/nginx/html;
        #index  index.html index.htm;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass  http://backend;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }


반응형

'WEB & WAS > nginx' 카테고리의 다른 글

NginX를 이용한 static 컨텐츠 서비스 와 캐시 설정  (0) 2022.08.09
728x90

출처 : http://kogun82.tistory.com/88


probe  설치


1. psi-probe를 다음의 홈 페이지에서 다운 받는다.(http://code.google.com/p/psi-probe/)


2. 다운 받은 압축 파일을 해제하고 나면 probe.war 파일을 확인 할 수 있다.


3. Tomcat manager에 접속하여 probe.war 파일을 선택하여 업로드하고, deploy를 실행한다.


4. deploy가 완료되면 http://loclahost:8080/probe/ 에 접속하여 확인 할 수 있다. 접속 시 필요한 계정 정보는 Tomcat manager에 계정과 동일하다.





반응형
728x90

출처 : http://stevenjsmin.tistory.com/103


문제점

Linux(혹은 Unix)에서는 1024번 이하의 포트가 보안상의 이유로 root권한을 가지고 있는 로세스만이 포트를 선점할 수 있다.(root reserved ports) root계정이 아닌 일반계정으로 Tomcat을 서비스 할 때정상적으로 Tomcat의 리스너(Listener)가 동작하지 않음을 TOMCAT LOG(logs/catalina.out)를 
통하여 확인 할 수 있다

 

2009. 12. 15 오후 4:14:31 org.apache.coyote.http11.Http11Protocol init
심각: Error initializing endpoint
java.net.BindException: Permission denied<null>:80

 

따라서 일반계정으로 Tomcat 80번 포트(HTTP 기본포트)에서 서비스 하려 한다면, Tomcat HTTP Connector Port 1024이상의 포트번호로 지정해준 뒤80포트로의 모든 인바운딩을 Tomcat HTTP Connector Port로 리다이렉트 해주어야한다아래는 iptables 명령을 이용한 간단한 예제이다.(반드시 root권한으로 수행되어야 한다.)

 

우선 8080포트가 리슨을 하고있는지 확인한다.

# netstat -ntl

The output will look something like

Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State
tcp        0      0 127.0.0.1:25                0.0.0.0:*                   LISTEN
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN
tcp        0      0 ::ffff:127.0.0.1:8005       :::*                        LISTEN
tcp        0      0 :::8009                     :::*                        LISTEN
tcp        0      0 :::8080                     :::*                        LISTEN
tcp        0      0 :::22                       :::* 

예제

TOMCAT 서버가 구동되는 호스트의 IP : 211.110.33.86 또는 localhost
TOMCAT 서버의 HTTP Connector Port : 8080

iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -I OUTPUT -p tcp --dport 80 -j REDIRECT --to-ports 8080 

위의 예제는 현재 서버(211.110.33.86) 8080포트에 대한 모든 인바운딩을 80 포트로 리다이렉트(REDIRECT)하는 명령이다.


또는 다음과 같이 명령을 입력한다.

# iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080

 

Run the folloing command to verify that redirect is working fine

# iptables -t nat -L

The output will look something like

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             anywhere            tcp dpt:http redir ports 8080

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination

 Run the following command to remove the routing

# iptables -t nat -D PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080

 Remember, when you are modifying your running configuration of iptables, you will still need to save your changes in order for it to persist on reboot. Be sure to test your configuration before saving it with "service iptables save" so that you don't lock yourself out

 

생각해보아야할 문제

root권한으로 TOMCAT과 같은 WAS를 구동하였을때 발생 할 수 있는 문제점은?
WEB SERVER없이 TOMCAT과 같은 WAS만으로 서비스하였을때 발생 할 수 있는 문제점은?
SSL(https)의 경우 443번 포트를 해당 프로토콜의 기본 포트로 사용하는데, SSL도 이와같은 처리를 해주어야할까?

반응형

'WEB & WAS > tomcat' 카테고리의 다른 글

Tomcat 오류 페이지 설정하기 web.xml  (0) 2023.12.11
톰캣 오류처리하기 400  (0) 2021.02.26
Tomcat 상태 모니터링 probe 설치 하기  (0) 2016.08.17

+ Recent posts