Redis RESP

레디스 내부구조 교육 레디스 정기점검/기술지원
Redis Technical Support
레디스 엔터프라이즈 서버
Redis Enterprise Server

RESP (REdis Serialization Protocol)

개요

RESP(REdis Serialization Protocol)는 레디스 통신 프로토콜(포멧) 이름입니다.
redis-cli, 마스터-복제간 통신, redis client library(lettuce, jedis)가 이 프로토콜에 따라 통신합니다.

버전

  • RESP는 버전 2와 3이 있는데, RESP 버전 2는 레디스의 기본 출력 프로토콜입니다. 레디스 6.x 부터는 RESP 버전 3을 선택해서 사용할 수 있습니다.
  • RESP 3는 2018년 5월 부터 시작해서 2019년에 개발이 완료되었고, 2020년 4월에 발표된 레디스 6에 적용되었습니다.
  • RESP 3를 사용하려면 hello 3 명령을 실행해야 하고, 접속 컨텍션(connection)별로 적용됩니다.

RESP 버전 2

프로토콜(포멧)

다섯 가지 포멧으로 구성됩니다.
  • status: 구분자 + 특수문자가 포함되지 않는 일반 문자열을 표시할 때 사용한다.
    포멧: +<String><CR><LF>     예) OK
  • error: 구분자 - 특수문자가 포함되지 않는 에러 문자열을 표시할 때 사용한다.
    포멧: -<ERR msg><CR><LF>     예) ERR no such key
  • integer: 구분자 : 정수를 표시할 때 사용한다.
    포멧: :<1234><CR><LF>     예) 1234
    사용하는 명령: llen, scard
  • bulk: 구분자 $ 특수문자가 포함된 문자열을 표시할 때 사용한다.
    포멧: $11<CR><LF><hello world><CR><LF>
    값(value)를 표시할 때 사용하고, 실수(double)도 이것을 사용한다.
    사용하는 명령: get, hget, 등
  • multibulk: 구분자 * 배열을 표시할 때 사용한다.
    포멧: *10<CR><LF>

    사용하는 명령: mget, lrange, smembers, zrange, hgetall, 등

RESP 버전 3

개발 배경

RESP 2에서 부족한 점
  • 정수(integer)와 실수(floating point number)의 구분이 없습니다.
    정수는 ':'로 구분했지만, 실수는 bulk('$') 문자열을 사용했습니다.
  • 불린(boolean, 참 거짓)을 명시적으로 표현하는 방법이 없었습니다.
    그래서 1/0로 대신 했습니다.
  • 널(null)도 구분 방법이 없어서 bulk $-1을 사용했습니다.
  • JAVA같은 프로그래밍 언어에서 제공하는 array, set, map을 따로 구분하지 않고 모두 multibulk(array)를 사용했습니다.
    레디스 데이터 타입과 대응해보면 List -> array, Set -> set, Hash -> map에 해당합니다.
  • 그리고 status(+), error(-) 같은 메시지도 CR, LF 같은 특수문자를 포함할 필요도 발생했습니다.
이런 부족한 부분을 개선하기 위해서 RESP 3를 개발하게 되었습니다.

프로토콜(포멧) - 일반 데이터 타입

  • Simple string: '+'   v2 status
    특수문자가 포함되지 않는 일반 문자열을 표시할 때 사용한다.
    포멧: +String<CR><LF>
  • Simple error: '-'   v2 error
    특수문자가 포함되지 않는 에러 문자열을 표시할 때 사용한다.
    포멧: -ERR msg<CR><LF>
  • Blob string: '$'   v2 bulk
    특수문자가 포함된 문자열을 표시할 때 사용한다. binary safe
    포멧: $<length><CR><LF><String value><CR><LF>
  • Blob error: '!'
    특수문자가 포함된 문자열로 에러를 표시할 때 사용한다. binary safe
    포멧: !<length><CR><LF><Error String><CR><LF>
  • Number: ':'   v2 integer
    정수를 표시할 때 사용한다.   사용 명령: LLEN, SCARD, 등
    포멧: :<number><CR<<LF>
  • Big number: '('   v2 bulk
    큰 정수를 표시할 때 사용한다.
    포멧: (<big number><CR><LF>
  • Double: ','   v2 bulk
    실수를 표시할 때 사용한다. ZSet의 스코어는 이 포멧을 사용한다.
    포멧: ,<double><CR><LF>
  • Null: '_'   v2 bulk "$-1\r\n"
    널을 표시할 때 사용한다.
    포멧: _<CR><LF>
  • Boolean: '#'   v2 1/0
    참/거짓을 표시할 때 사용한다.
    포멧: #t<CR><LF>     #f<CR><LF>
  • Verbatim string: '='
    text를 그대로 출력할 때 사용
    사용하는 명령: INFO, MEMORY, CLIENT, LATENCY, CLUSTER
    포멧: =<length><CR><LF> txt:Some string <CR><LF> txt: plain text
    포멧: =<length><CR><LF> mkd:Some string <CR><LF> mkd: markdown
    mkd는 정의는 되어 있으나 아직 사용되지는 않는다. 위 명령은 모두 txt를 사용한다.

프로토콜(포멧) - 반복 지정

Array, Set, Map을 지정할 수 있습니다.
  • Array: *
    반복을 지정할 때 사용한다. Array 안에 array를 또 지정할 수 있다.
    포멧: *<length><CR><LF>   v2 multibulk

    List에서 사용 예

    ZSet에서 사용 예

    레디스에서 아래와 같이 사용하는 경우는 없습니다만, 프로토콜 상으로는 가능합니다.
  • Set: ~
    반복을 지정할 때 사용한다. 레디스 데이터 타입 Set에서 사용한다.
    이 구분자로 데이터를 받으면 Java의 Set 컬렉션을 사용하면 된다.
    포멧: ~<length><CR><LF>   v2 multibulk

    Set에서 사용 예: ~3

    레디스에서 아래와 같이 사용하는 경우는 없습니다만, 프로토콜 상으로는 가능합니다. ~5
  • Map: %
    Map 반복을 지정할 때 사용한다. 레디스 데이터 타입 Hash에서 사용한다.
    이 구분자로 데이터를 받으면 Java의 Map 컬렉션을 사용하면 된다.
    포멧: %<length><CR><LF>   v2 multibulk

    Hash에서 사용 예: %2 레디스에서 아래와 같이 사용하는 경우는 없습니다만, 프로토콜 상으로는 가능합니다.

프로토콜(포멧) - 스트리밍(Streaming)

전체 데이터의 길이를 모를 경우 사용할 수 있습니다.
*, %, $ 다음에 '?'를 사용합니다.
*, %의 END 구분자는 .<CR><LF> 이고
$의 END 구분자는 ;0<CR><LF> 입니다.
레디스에서 실제 사용되지는 않습니다.
  • Array *? 사용 예
  • Map %? 사용 예
  • Bulk $? 사용 예

RESP 버전 2 - Functions

공통

  • void addReply(client *c, robj *obj)
    • addReply(c,shared.mbulkhdr[ll]); - "*%d\r\n"
    • addReply(c,shared.bulkhdr[ll]); - "$%d\r\n"
    • addReply(c,shared.nullbulk); - "$-1\r\n" --> RESP3: addReplyNull(c);
    • addReply(c,shared.czero); - ":0\r\n"
    • addReply(c,shared.cone); - ":1\r\n"
    • addReply(c,shared.ok); - "+OK\r\n"
    • addReply(c,shared.syntaxerr); "-ERR syntax error\r\n"
  • RESP2와 3에서 이름이 변경된 functions.
    • void addReplyString(client *c, const char *s, size_t len) --> RESP3:
      void addReplyProto(client *c, const char *s, size_t len)
    • void *addDeferredMultiBulkLength(client *c) --> RESP3:
      void *addReplyDeferredLen(client *c)
    • void setDeferredMultiBulkLength(client *c, void *node, long length) --> RESP3:
      void setDeferredReply(client *c, void *node, const char *s, size_t length)
    • void addReplyMultiBulkLen(client *c, long length) --> RESP3:
      void addReplyArrayLen(client *c, long length)
    • void _addReplyStringToList(client *c, const char *s, size_t len) --> RESP3:
      void _addReplyProtoToList(client *c, const char *s, size_t len)

Status: 구분자 +

  • void addReplyStatus(client *c, const char *status)
  • void addReplyStatusLength(client *c, const char *s, size_t len)
  • void addReplyStatusFormat(client *c, const char *fmt, ...)

Error: 구분자 -

  • void addReplyError(client *c, const char *err)
  • void addReplyErrorLength(client *c, const char *s, size_t len)
  • void addReplyErrorFormat(client *c, const char *fmt, ...)

Integer: 구분자 :

  • void addReplyLongLong(client *c, long long ll)
  • void addReplyLongLongWithPrefix(client *c, long long ll, char prefix)

Bulk: 구분자 $

  • void addReplyString(client *c, const char *s, size_t len)
  • void addReplyBulk(client *c, robj *obj)
  • void addReplyBulkLen(client *c, robj *obj)
  • void addReplyBulkCBuffer(client *c, const void *p, size_t len)
  • void addReplyBulkSds(client *c, sds s)
  • void addReplyBulkCString(client *c, const char *s)
  • void addReplyBulkLongLong(client *c, long long ll)
  • void addReplySds(client *c, sds s)
  • void addReplyDouble(client *c, double d) - $%d\r\n%s\r\n
  • void addReplyHumanLongDouble(client *c, long double d)

Multibulk: 구분자 *

  • void addReplyMultiBulkLen(client *c, long length)
    --> RESP3: addReplyArrayLen(client *c, long length)
  • void *addDeferredMultiBulkLength(client *c)
    --> RESP3: addReplyDeferredLen(client *c)
  • void setDeferredMultiBulkLength(client *c, void *node, long length)
    --> RESP3: setDeferredArrayLen(client *c, void *node, long length)

RESP 버전 3 - Functions

공통

  • void addReply(client *c, robj *obj)

Simple string: '+' v2 status

  • void addReplyStatus(client *c, const char *status)
  • void addReplyStatusLength(client *c, const char *s, size_t len)
  • void addReplyStatusFormat(client *c, const char *fmt, ...)

Simple error: '-' v2 error

  • void addReplyError(client *c, const char *err)
  • void addReplyErrorLength(client *c, const char *s, size_t len)
  • void addReplyErrorFormat(client *c, const char *fmt, ...)
  • void afterErrorReply(client *c, const char *s, size_t len)
  • void addReplyErrorObject(client *c, robj *err)
  • void addReplyErrorSds(client *c, sds err)

Blob string: '$' v2 bulk

  • void addReplyProto(client *c, const char *s, size_t len)
  • void addReplyBulk(client *c, robj *obj)
  • void addReplyBulkLen(client *c, robj *obj)
  • void addReplyBulkCBuffer(client *c, const void *p, size_t len)
  • void addReplyBulkSds(client *c, sds s)
  • void addReplyBulkCString(client *c, const char *s)
  • void addReplyBulkLongLong(client *c, long long ll)
  • void addReplySds(client *c, sds s)

Blob error: '!'

  • Blob error를 사용하는 function은 없다.

Number: ':' v2 integer

  • void addReplyLongLong(client *c, long long ll)
  • void addReplyLongLongWithPrefix(client *c, long long ll, char prefix)

Big number: '(' v2 bulk

  • void addReplyBigNum(client *c, const char* num, size_t len) - resp2 $, resp3 (

Double: ',' v2 bulk

  • void addReplyDouble(client *c, double d) - ,%.17g\r\n
  • void addReplyHumanLongDouble(client *c, long double d)

Null: '_' v2 bulk "$-1\r\n"

  • void addReplyNull(client *c) - resp2 "$-1\r\n", resp3 "_\r\n"

Boolean: '#' v2 1/0

  • void addReplyBool(client *c, int b) - resp2 shared.cone, shared.czero, resp3 "#t\r\n", "#f\r\n"

Verbatim string: '='

  • void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext) - "=%zu\r\nxxx:" txt, mkd

Aggregate: *,~,%,|,>

  • void addReplyAggregateLen(client *c, long length, int prefix)
  • void setDeferredAggregateLen(client *c, void *node, long length, char prefix)

Array: *

  • void addReplyArrayLen(client *c, long length)
  • void *addReplyDeferredLen(client *c)
  • void setDeferredArrayLen(client *c, void *node, long length)

Set: ~

  • void addReplySetLen(client *c, long length) -> int prefix = c->resp == 2 ? '*' : '~';
  • void setDeferredSetLen(client *c, void *node, long length) int prefix = c->resp == 2 ? '*' : '~';

Map: %

  • void addReplyMapLen(client *c, long length) -> int prefix = c->resp == 2 ? '*' : '%';
  • void setDeferredMapLen(client *c, void *node, long length) int prefix = c->resp == 2 ? '*' : '%';

Attribute: |

  • void addReplyAttributeLen(client *c, long length) c->resp >= 3
  • void setDeferredAttributeLen(client *c, void *node, long length) -> '|' c->resp >= 3

Push: >

  • void addReplyPushLen(client *c, long length) c->resp >= 3
  • void setDeferredPushLen(client *c, void *node, long length) -> '>' c->resp >= 3

RESP 관련 소스: networking.c

RESP 개발자의 글


<< Stream RESP

Email 답글이 올라오면 이메일로 알려드리겠습니다.

혹시 처음이세요?
레디스게이트에는 레디스에 대한 많은 정보가 있습니다.
레디스 소개, 명령어, SQL, 클라이언트, 서버, 센티널, 클러스터 등이 있습니다.
혹시 필요한 정보를 찾기 어려우시면 redisgate@gmail.com로 메일 주세요.
제가 찾아서 알려드리겠습니다.
 
close
IP를 기반으로 보여집니다.