DB 접근 Part 2: Redis

아이펀 엔진은 Redis 서버와 통신할 수 있는 인터페이스를 제공하고 있습니다.

지원되는 Redis commands

아이펀 엔진은 Redis Commands 에 대응되는 함수들을 제공합니다. 각 Redis command 별로 Redis::Del() 과 같이 Redis:: 를 붙여서 사용하시면 됩니다. 또한, 비동기 함수의 경우 이름 뒤에 Redis::DelAsync() 처럼 Async 가 붙습니다.

Tip

Redis 명령어에 대응하는 각 함수의 설명은 Redis API 문서 를 참고해주세요.

현재 지원되는 Redis 함수들은 아래와 같습니다.

Keys

Del, Exists, Expire, Persist, PExpire, PTTL, Rename, TTL

Strings

Append, BitCount, BitOp, Decr, DecrBy, Get, GetBit, GetRange, GetSet, Incr, IncrBy, IncrByFloat, MGet, MSet, PSetEx, Set, SetBit, SetEx, SetNx, StrLen

Hashes

HDel, HExists, HGet, HGetAll, HIncrBy, HIncrByFloat, HKeys, HLen, HMGet, HMSet, HSet, HSetNx, HVals

Lists

LIndex, LInsert, LLen, LPop, LPush, LRange, LRem, LSet, LTrim, RPop, RPush

Sets

SAdd, SCard, SDiff, SDiffStore, SInter, SInterStore, SIsMember, SMembers, SMove, SPop, SRandMember, SRem, SUnion, SUnionStore

Sorted Sets

ZAdd, ZCard, ZCount, ZIncrBy, ZRem, ZRevRange, ZRevRank, ZScore

Pub/Sub

Publish, Subscribe, Unsubscribe, PUnsubscribe

Note

Subscribe, PSubscribe 는 동기 함수를 지원하지 않습니다.

Transaction

Discard, Exec, Multi

예제 - 유저 로그인 시간을 Redis 에 저장

아래는 Redis::HMSet() 를 이용하여 유저가 로그인하면 유저 데이터를 Redis 에 저장하는 예제입니다.

동기 함수를 사용한 버전

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void OnLogin(const Ptr<Session> &session, const Json &message) {
  string tag = "";

  string user_id = message["user_id"].GetString();
  string login_time = WallClock::GetTimestring(WallClock::Now());

  std::vector<std::pair<std::string, std::string > > fields_values;
  fields_values.push_back(std::make_pair(user_id, login_time));

  Redis::Result redis_result = Redis::HMSet("user:login", fields_values, tag);
  if (redis_result == Redis::kResultError) {
    return;
  }

  Json json_response;
  json_response["result"] = true;
  session->SendMessage("login", json_response);
}

C# 버전은 추후 지원 예정입니다.

비동기 함수를 사용한 버전

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
void OnLoginDataSaved(const Ptr<Session> &session, const Redis::Result &result) {
  if (result == Redis::kResultError) {
    return;
  }

  Json json_response;
  json_response["result"] = true;
  session->SendMessage("login", json_response);
}

void OnLogin(const Ptr<Session> &session, const Json &message) {
  string tag = "";

  string user_id = message["user_id"].GetString();
  string login_time = WallClock::GetTimestring(WallClock::Now());

  std::vector<std::pair<std::string, std::string > > fields_values;
  fields_values.push_back(std::make_pair(user_id, login_time));

  Redis::HMSetAsync("user:login", fields_values,
      boost::bind(&OnLoginDataSaved, session, _1), tag);
}

C# 버전은 추후 지원 예정입니다.

Redis::ExecuteCommand()

아이펀 엔진이 Redis command 들에 대응하는 함수들을 제공하지만, 직접 Redis 명령을 호출해야되는 상황이 있을 수 있습니다. 이런 경우 Redis::ExecuteCommand() 함수를 사용합니다.

static Ptr<Response> ExecuteCommand(const string &command_name,
                                    const std::vector<string> *arguments,
                                    const string &tag="")

static void ExecuteCommandAsync(const string &command_name,
                                const std::vector<string> *arguments,
                                const OnResponseReturned &callback,
                                const string &tag="")

Redis::ExecuteCommand() 를 사용할 때는 각각의 응답을 직접 처리해야되며, 이를 위해 struct ResponseRedis Commands 에서 설명하고 있는 각 Redis 명령들에 대한 반환값을 이해해야 합니다.

Important

Redis::ExecuteCommand() 함수로 Pub/Sub 처리는 할 수 없습니다. Pub/Sub 의 함수들을 이용해야됩니다.

Important

Redis::ExecuteCommand() 함수로 Transaction 처리는 할 수 없습니다. 제공하는 Transaction 의 함수들을 이용해야됩니다.

struct Redis::Response

// Redis::ExecuteCommand() 함수 호출 시 반환됩니다.
struct Response {
  enum Type {
    // Redis 명령 요청에 대한 결과값으로 string 을 반환한 경우입니다.
    // 반환값은 str 멤버변수에 저장됩니다.
    // 다음과 같은 경우에 해당합니다.
    // redis> GET mykey
    // "hello"
    kString = 1,

    // Redis 명령 요청에 대한 결과값으로 array 을 반환한 경우입니다.
    // 반환값은 elements 멤버변수에 저장됩니다.
    // 다음과 같은 경우에 해당합니다.
    // redis> KEYS *
    // "one"
    // "two"
    kArray = 2,

    // Redis 명령 요청에 대한 결과값으로 integer 을 반환한 경우입니다.
    // 반환값은 integer 멤버변수에 저장됩니다.
    // 다음과 같은 경우에 해당합니다.
    // redis> HLEN myhash
    // (integer) 2
    kInteger = 3,

    // Redis 명령 요청에 대한 결과값으로 nil 을 반환한 경우입니다.
    // nil 은 반환값이 없으므로 type 멤버변수로만 확인해야 합니다.
    // 다음과 같은 경우에 해당합니다.
    // redis> GET notfoundmykey
    // (nil)
    kNil = 4,

    // Redis 명령 요청에 대한 결과값으로 status 을 반환한 경우입니다.
    // 반환값은 str 멤버변수에 저장됩니다.
    // 다음과 같은 경우에 해당합니다.
    // redis> SET mykey value
    // OK
    kStatus = 5,

    // Redis 명령 요청에 대한 결과값으로 error 을 반환한 경우입니다.
    // 반환값은 str 멤버변수에 저장됩니다.
    // 다음과 같은 경우에 해당합니다.
    // redis> SET mykey value value2
    // "ERR syntax error"
    kError = 6
  };

  // Redis 명령 요청에 대한 응답 타입입니다.
  // 이 타입에 따라 다음 멤버변수에 접근하여 데이터를 얻습니다.
  Type type;

  // type 이 kInteger 인 경우
  int64_t integer;

  // type 이 kString, kStatus, kError 인 경우
  std::string str;

  // type 이 kArray 인 경우
  std::vector<Ptr<Response> > elements;
};

C# 버전은 추후 지원 예정입니다.

예제 - 유저 로그인 시간을 Redis 에 저장

Redis::HMSet() 함수 대신 Redis::ExecuteCommand() 이용하여 유저의 로그인 시간을 Redis 에 저장하는 예제입니다.

동기 함수를 사용한 버전

 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
void OnLogin(const Ptr<Session> &session, const Json &message) {
  string tag = "";

  string key = "user:login";

  string user_id = message["user_id"].GetString();
  string login_time = WallClock::GetTimestring(WallClock::Now());

  std::vector<string> arguments;
  arguments.push_back(key);
  arguments.push_back(user_id);
  arguments.push_back(login_time);

  Ptr<Redis::Response> response = Redis::ExecuteCommand("HMSET", &arguments, tag);
  if (not response || response->type == Redis::Response::kError) {
    return;
  }

  Json json_response;
  if (response->type == Redis::Response::kStatus && response->str == "OK") {
    json_response["result"] = true;
  } else {
    json_response["result"] = false;
  }

  session->SendMessage("login", json_response);
}

C# 버전은 추후 지원 예정입니다.

비동기 함수를 사용한 버전

 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
void OnUserDataSaved(const Ptr<Session> &session, const Ptr<Redis::Response> &response) {
  Json json_response;

  if (not response || response->type == Redis::Response::kError) {
    return;
  }

  Json json_response;
  if (response->type == Redis::Response::kStatus && response->str == "OK") {
    json_response["result"] = true;
  } else {
    json_response["result"] = false;
  }

  session->SendMessage("login", json_response);
}

void OnLogin(const Ptr<Session> &session, const Json &message) {
  string tag = "";

  string key = "user:login";

  string user_id = message["user_id"].GetString();
  string login_time = WallClock::GetTimestring(WallClock::Now());

  std::vector<string> arguments;
  arguments.push_back(key);
  arguments.push_back(user_id);
  arguments.push_back(login_time);

  Redis::ExecuteCommand("HMSET", &arguments,
      boost::bind(&OnUserDataSaved, session, _1), tag);
}

C# 버전은 추후 지원 예정입니다.

Redis 서버 관리

Tag 로 Redis Server 구분

특정 Redis Server 를 tag 하로 지정하고, 해당 tag 를 이용해 Redis 명령을 요청할 수 있습니다.

Tag 는 redis mode 에 따라 MAINFEST.json 의 Redis 항목에서 redis_serversredis_sentinel_servers property 에 Key 로 나열하시면 됩니다.

예제 - Tag 를 이용한 Redis 서버 구분

아래의 MANIFEST.json 은 두 개의 Redis 서버를 지정하되, 하나는 guild, 다른 하나는 party 라는 tag 를 준 경우입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
"Redis": {
  "enable_redis": true,

  "redis_mode": "redis",

  "redis_servers": {
    "guild": {
      "address": "127.0.0.1:6379",
      "auth_pass": ""
    },
    "party": {
      "address": "127.0.0.1:7379",
      "auth_pass": ""
    }
  }
}

Note

만약 tag 를 지정하고 싶지 않으면 “”(empty string) 으로 입력하면 됩니다.

새로운 guild 를 생성한다고 가정하여 guild tag 를 이용해 처리해보겠습니다. 간단한 예제 설명을 위해 동기 함수를 사용하겠습니다.

1
2
3
4
5
6
7
8
9
const string kGuildTag("guild");

void CreateGuild(const string &guild_name, const string &guild_master_name) {
  Redis::Result result =
      Redis::HSet(guild_name, "guild_master", guild_master_name, kGuildTag);
  if (result == kResultError) {
    return;
  }
}

C# 버전은 추후 지원 예정입니다.

이번에는 party 를 생성한다고 가정하여 party tag 를 이용해 처리해보겠습니다. 간단한 예제 설명을 위해 동기 함수를 사용하겠습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const string kPartyTag("party");

void CreateParty(const string &party_name,
                 const std::vector<string> &party_members) {
  Redis::Result result =
      Redis::LPush(party_name, party_members, NULL, kPartyTag);
  if (result == kResultError) {
    return;
  }
}

C# 버전은 추후 지원 예정입니다.

Redis 레플리케이션

아이펀 엔진은 Redis Sentinel 을 통한 Failover 를 지원합니다. redis-manifest 에서 redis_modesentinel 로 입력하고 redis_sentinel_servers 에 Redis sentinel 서버들의 정보를 입력하면됩니다.

아이펀 엔진은 redis_sentinel_servers 에 나열된 서버들과 통신하여 Master 서버를 알아내고, Master 서버가 변경되었다면 새로운 Master 서버로 자동 재연결합니다.

Master 변경 통지 받기

Redis sentinel 의 master 가 변경되면 아이펀 엔진이 자동으로 새로운 master 와 연결을 맺게 됩니다. 그와 별개로 master 가 변경되었다는 것을 통지 받고 싶다면 SubscribeSentinelSwitchMasterChannelAsync() 함수를 이용해 callback 을 등록할 수 있습니다.

typedef boost::function<void (
    const string &master_name,
    const string &old_master_address,
    const string &new_master_address)> OnSentinelMasterSwitched;

static void SubscribeSentinelSwitchMasterChannelAsync(
    const OnSentinelMasterSwitched &cb,
    const string &tag = "");

C# 버전은 추후 지원 예정입니다.

아래는 SubscribeSentinelSwitchMasterChannelAsync() 을 이용해 master 가 변경되었을 때 로그를 남기는 예제입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void MasterSwitched(const string &master_name,
                    const string &old_master_address,
                    const string &new_master_address) {
  LOG(INFO) << "master name: " << master_name
            << ", old master address: " << old_master_name
            << ", new master address: " << new_master_name;
}

void Example() {
  Redis::SubscribeSentinelSwitchMasterChannelAsync(MasterSwitched);
}

C# 버전은 추후 지원 예정입니다.

Redis 클러스터링

Note

아이펀 엔진 자체가 Redis 클러스터링을 지원하지는 않습니다.

Redis 기능 설정 파라미터

  • enable_redis: Redis 기능을 활성화 시킴. (type=bool, default=false)

  • redis_mode: 사용할 Redis mode. redissentinel 입력 가능 (type=string, default=”redis”)

  • redis_servers: redis_mode"redis" 를 입력한 경우 사용합니다. 접속하려는 Redis 서버들의 정보를 입력합니다.

    // tag 로 구분하는 Redis 서버의 연결 정보를 입력합니다.
    "redis_servers": {
      // Redis Server 를 식별할 수 있는 tag 값을 입력합니다.
      // 기본값은 ""(empty string) 입니다.
      // tag 는 ""(empty string) 을 포함하여 중복될 수 없습니다.
      "<tag>": {
        // Redis Server 의 ip:port 를 입력합니다.
        "address": "127.0.0.1:6379",
    
        // Redis Server 에 설정된 password 를 입력합니다.
        "auth_pass": ""
      },
      ...
    }
    
  • redis_sentinel_servers: redis_mode"sentinel" 을 입력한 경우 사용합니다. 접속하려는 Redis Sentinel 서버들의 정보를 입력합니다.

    // tag 로 구분하는 Redis Seninel 서버들의 연결 정보들을 입력합니다.
    "redis_sentinel_servers": {
      // 하나의 Redis Sentinel 구성을 식별할 수 있는 tag 값을 입력합니다.
      // 기본값은 ""(empty string) 입니다.
      // tag 는 ""(empty string) 을 포함하여 중복될 수 없습니다.
      "<tag>": {
        // Redis Sentinel 서버에 설정된 master 의 이름을 입력합니다.
        "master_name": "mymaster",
    
        // Json Array 타입으로 Redis Sentinel 서버들의 ip:port 들을 입력합니다.
        "addresses": ["127.0.0.1:26379"],
    
        // Redis Server 에 설정된 password 를 입력합니다.
        "auth_pass": ""
      },
      ...
    }
    

직접 설정을 바꿀 일이 거의 없는 설정들

  • redis_async_threads_size: Redis 비동기 명령어를 처리하는데 사용될 쓰레드 숫자. (type=uint64, default=4)