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 답글이 올라오면 이메일로 알려드리겠습니다.