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聚簇功能。