20. DB访问Part 2: Redis

iFun引擎提供了可以和Redis进行通信的 RedisClient class 。 RedisClient提供了与 Redis Commands 相对应的函数。

以下的说明内容并没有覆盖所有功能和函数。具体内容请参考 RedisClient class

Note

RedisClient以Redis Server Version 2.8.4为基准编制。

20.1. 连接初始化

20.1.1. 指定参数值

class RedisClient {
  static Ptr<RedisClient> Create(server_ip, server_port, auth_pass,
                                 connection_count [, invoke_as_event = true]);
};
public class RedisClient
{
  static public RedisClient Create (server_ip, server_port, auth_pass,
                                    connection_count [, invoke_as_event = true]);
}

指定用于初始化连接的参数值。

server_ip

输入Redis服务器的IP。例)127.0.0.1

server_port

输入Redis服务器的Port。例) 6379

auth_pass

输入在Redis服务器上设置的auth pass。

connection_count

输入Connection Pool的连接数。

invoke_as_event

决定是否通过 事件 处理已传输给非同步函数 的回调函数。默认值为true。 输入false时,将在单独的线程中调用。

Note

当连接数为2个以上时,将并行执行,因此不保证Redis命令的执行 顺序。如须保证Redis命令的执行顺序, 请参考 非同步命令的执行顺序和标签

Note

若执行Redis Subscribe命令,要在连接Pool中选择一个负责进行 Subscribe处理的连接,并设置为今后仅处理Subscribe。 因此,若连接数为1个,在执行Subscribe命令后,Get等 Sub以外的其他命令将不会执行,仅堆积在内部队列中。 (若Unsubscribe,则今后会处理队列中堆积的命令。) 此时,一定要将连接数设置为2个以上。

20.1.2. Connection Pool初始化

class RedisClient {
  void Initialize();
};
public class RedisClient
{
  public void Start();
}

按照调用Create()函数时所输入的连接数来初始化connection pool。 从该函数被调用后,即可开始执行Redis命令。

20.2. 执行命令

20.2.1. 支持的命令

当前支持的Redis命令如下所示。Del、Exists等函数 为非同步函数,同步函数在函数名结尾带有Sync。

各命令的具体说明请参考 Redis Commands

Keys

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

Strings

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

Hashes

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

Lists

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

Sets

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

Sorted Sets

ZCard, ZCount, ZScore, ZRange, ZRangeByScore, ZRank, ZRevRange, ZRevRangeByScore, ZRevRank, ZAdd, ZIncrBy, ZRem, ZRemRangeByRank, ZRemRangeByScore

Pub/Sub

Publish, Subscribe, PSubscribe, Unsubscribe, PUnsubscribe

20.2.2. 直接处理命令

当需要上述Redis命令以外的其他命令,或是想直接处理相应命令时, 可通过以下函数处理。

class RedisClient {
  void ExecuteCommand(const string &command_name,
                      const std::vector<string> *arguments,
                      const Callback &callback,
                      const SerializationTag &tag = kDefaultSerializationTag);

  Ptr<Reply> ExecuteCommandSync(const string &command_name,
                                const std::vector<string> *arguments,
                                Result *result = NULL);
};
public class RedisClient
{
  public void ExecuteCommand (string command_name,
                              List<string> arguments,
                              Callback callback,
                              Guid tag = default(Guid))

  public Reply ExecuteCommandSync (string command_name,
                                   List<string> arguments,
                                   out RedisClient.Result out_result)
}

为了通过上述函数执行命令,须要理解含有Redis服务器响应的Reply结构体和 Redis Commands 中所介绍的各个Redis命令 的返回值。

class RedisClient {
  struct Reply {
    enum Type {
      kString = 1,
      kArray = 2,
      kInteger = 3,
      kNil = 4,
      kStatus = 5,
      kError = 6
    };

    DECLARE_CLASS_PTR(Reply);

    Type type;
    int64_t integer;
    string str;
    std::vector<Ptr<Reply> > elements;
  };
};
public class RedisClient
{
  public enum ReplyType
  {
    kString = 1,
    kArray = 2,
    kInteger = 3,
    kNil = 4,
    kStatus = 5,
    kError = 6
  }

  public struct Reply
  {
    public ReplyType Type { get; }
    public long Integer { get; }
    public string Str { get; }
    public List<Reply> Elements { get; }
  }
}
kString

返回string的情况,如下所示。 C++可通过str获取值, C#可通过Str获取值。

redis> GET mykey
"hello"
kArray

返回array的情况,如下所示。 C++可通过elements获取值, C#可通过Elements获取值。

redis> KEYS *
1) "one"
2) "two"
kInteger

返回integer的情况,如下所示。 C++可通过integer获取值, C#可通过Integer获取值。

redis> HLEN myhash
(integer) 2
kNil

返回nil的情况,如下所示。由于无相关值, 所以C++仅确认type,C#仅确认Type即可。

redis> GET notfoundmykey
(nil)
kStatus

返回status的情况,如下所示。 C++可通过str获取值, C#可通过Str获取值。

redis> SET mykey value
OK
kError

返回error的情况,如下所示。 C++可通过str获取值, C#可通过Str获取值。

redis> SET mykey value value2
(error) ERR syntax error

20.2.3. 非同步命令的执行顺序和标签

若通过非同步函数执行Redis,将按照默认的执行顺序进入队列中。 但当处理Redis命令的连接数为2个以上时,Redis命令将并行 执行。

这在须要保证执行顺序的情况下较为困难。RedisClient 支持将 事件执行顺序和事件标签 等须要保证处理顺序的Redis命令通过相同标签 进行捆绑的功能。

非同步函数最后一个参数输入tag,如下所示。当将tag设置为默认值时, Redis命令将并行执行。

class RedisClient {
  typedef Uuid SerializationTag;
  static const SerializationTag kDefaultSerializationTag;

  void Del(const string &key, const IntegerCallback &callback,
           const SerializationTag &tag = kDefaultSerializationTag);
};
public class RedisClient {
  public void Del (string key, IntegerCallback callback,
                   Guid tag = default(Guid))
};

若按如下所示输入tag值,各Redis命令将依次执行,并按照 OnDeleted1 => OnDeleted2的顺序调用回调函数。

Ptr<RedisClient> the_redis_client;

void Initialize() {
  // ...
}

void OnDeleted1(const RedisClient::Result &result, int64_t value) {
}

void OnDeleted2(const RedisClient::Result &result, int64_t value) {
}

void Example() {
  RedisClient::SerializationTag tag = RandomGenerator::GenerateUuid();
  the_redis_client->Del("hello1", OnDeleted1, tag);
  the_redis_client->Del("hello2", OnDeleted2, tag);
}
Ptr<RedisClient> the_redis_client;

void Initialize()
{
  // ...
}

void OnDeleted1(RedisClient.Result result, long value)
{
}

void OnDeleted2(RedisClient.Result result, long value)
{
}

void Example()
{
  System.Guid tag = RandomGenerator.GenerateUuid ();
  the_redis_client.Del("hello1", OnDeleted1, tag);
  the_redis_client.Del("hello2", OnDeleted2, tag);
}

20.3. 使用示例

20.3.1. 示例-保存用户数据

下面是用户登录后通过HMSet保存用户数据的示例。

 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
Ptr<RedisClient> the_redis_client;

void Initialize() {
  the_redis_client = RedisClient::Create("127.0.0.1", 6379, "", 4);
  the_redis_client->Initialize();
}

void OnUserDataWrote(const RedisClient::Result &result, const string &value) {
  if (result.type == RedisClient::kError) {
    LOG(ERROR) << "Error: type=" << result.type
               << ", code=" << result.error_code
               << ", desc=" << result.error_desc;
    return;
  }

  LOG_ASSERT(value == "OK");

  LOG(INFO) << "User data wrote.";
}

void OnLogin(const Ptr<Session> &session, const Json &message) {
  string user_id = message["user_id"].GetString();
  string login_time = WallClock::GetTimestring(WallClock::Now());

  // std::vector<std::pair<string, string > >
  RedisClient::StringPairList field_values;
  field_values.push_back(std::make_pair(user_id, login_time));

  the_redis_client->HMSet("user:data", field_values, OnUserDataWrote);
}
 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
RedisClient the_redis_client;

void Initialize()
{
  the_redis_client = RedisClient.Create("127.0.0.1", 6379, "", 4);
  the_redis_client.Initialize();
}

void OnUserDataWrote(RedisClient.Result result, string value)
{
  if (result.Type == RedisClient.ResultType.kError) {
    Log.Error("Error: type={0}, code={1}, desc={2}",
              result.Type, result.ErrorCode, result.ErrorDesc);
    return;
  }

  Log.Assert(value == "OK");

  Log.Info("User data wrote.");
}

void OnLogin (Session session, JObject message)
{
  string user_id = (string) message["user_id"];
  string login_time = WallClock.GetTimestring();

  List<Tuple<string, string>> field_values = new List<Tuple<string, string>> ();
  field_values.Add (Tuple.Create (user_id, login_time));

  the_redis_client.HMSet("user:data", field_values, OnUserDataWrote);
}

20.3.2. 示例 - ExecuteCommand()

下面是把利用HMSet进行处理的示例通过ExecuteCommand()函数进行保存的示例。

 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
Ptr<RedisClient> the_redis_client;

void Initialize() {
  the_redis_client = RedisClient::Create("127.0.0.1", 6379, "", 4);
  the_redis_client->Initialize();
}

void OnUserDataWrote(const RedisClient::Result &result,
                     const Ptr<RedisClient::Reply> &reply) {
  if (result.type == RedisClient::kError) {
    LOG(ERROR) << "Error: type=" << result.type
               << ", code=" << result.error_code
               << ", desc=" << result.error_desc;
    return;
  }

  LOG_ASSERT(reply)
  LOG_ASSERT(reply->type == RedisClient::Reply::kStatus &&
             reply->str == "OK");

  LOG(INFO) << "User data wrote.";
}

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

  the_redis_client->ExecuteCommand("HMSET", &arguments, OnUserDataWrote);
}
 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
RedisClient the_redis_client;

void Initialize()
{
  the_redis_client = RedisClient.Create("127.0.0.1", 6379, "", 4);
  the_redis_client.Initialize();
}

void OnUserDataWrote(RedisClient.Result result, RedisClient.Reply reply)
{
  if (result.Type == RedisClient.ResultType.kError) {
    Log.Error("Error: type={0}, code={1}, desc={2}",
              result.Type, result.ErrorCode, result.ErrorDesc);
    return;
  }

  Log.Assert(reply.Type == RedisClient.ReplyType.kStatus &&
             reply.Str == "OK");

  Log.Info("User data wrote.");
}

void OnLogin (Session session, JObject message)
{
  string key = "user:data";
  string user_id = (string) message["user_id"];
  string login_time = WallClock.GetTimestring();

  List<string> arguments = new List<string> ();
  arguments.Add (key);
  arguments.Add (user_id);
  arguments.Add (login_time);

  the_redis_client.ExecuteCommand("HMSET", arguments, OnUserDataWrote);
}

20.4. Redis 레플리케이션

아이펀 엔진은 Redis Sentinel 을 통한 Failover 를 지원합니다.

Note

Redis Sentinel 설정과 관련된 더 자세한 내용은 `Redis Sentinel`_ 을 참고해주세요.

20.4.1. Sentinel 설정값 지정

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

class RedisClient {
  static Ptr<RedisClient> Create(master_name, sentinel_addresses, auth_pass,
                                 connection_count [, invoke_as_event = true][, database = 0]);
};
차후 지원 예정입니다.

연결 초기화를 위한 설정값을 지정합니다.

master_name

Redis Sentinel 에 설정한 Master name 을 입력합니다.

sentinel_addresses

Redis Sentinel 서버들의 주소를 입력합니다. 예) “192.168.0.1:26379,192.168.0.2:26379”

auth_pass

Redis 서버에 설정된 auth pass 를 입력합니다.

connection_count

Connection Pool 의 연결 수를 입력합니다.

invoke_as_event

비동기 함수에 전달한 콜백 함수를 事件 로 처리할지 결정합니다. 기본값은 true 입니다. false 를 입력할 경우 별도 스레드에서 호출합니다.

Note

현재 Master 와 연결이 끊어지는 경우, Redis Sentinel 에 의해 Master가 변경 되기 전까지는 현재 Master 에 대해 재연결을 시도합니다. 또한 Master 변경 과정 중 발생하는 사용자의 요청에 대하여 Redis Client 는 내부적으로 요청을 큐잉한 뒤 Master가 변경되면 다시 요청을 처리하기 시작합니다.

20.4.2. Master 변경 통지 받기

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

Note

최초 Sentinel 연결 후 Master를 알아내는 경우에도 이 콜백이 호출됩니다.

class RedisClient {
  typedef boost::function<
      void (const string &/*master_name*/,
            const string &/*old_master_address*/,
            const string &/*new_master_address*/)>
                SentinelMasterSwitchedCallback;

  void SetSentinelMasterSwitchedCallback(
      const SentinelMasterSwitchedCallback &cb);
};
차후 지원 예정입니다.

20.4.2.1. Redis聚簇

Note

iFun引擎本身不支持Redis聚簇功能。