21. DB access part 2: Redis¶
iFun Engine provides a RedisClient class to communicate with Redis servers. RedisClient provides a function to respond to Redis Commands.
The explanation below does not include all features and functions of RedisClient. For more details, please see RedisClient class.
Note
RedisClient was written based on Redis Server version 2.8.4.
21.1. Starting connections¶
21.1.1. Setting values¶
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]);
}
Set values to start the initial connection.
server_ip |
Enter the Redis server’s IP. E.g. 127.0.0.1 |
server_port |
Enter the Redis server’s port. E.g. 6379 |
auth_pass |
Enter the auth pass set on the Redis server. |
connection_count |
Enter the number of connections in the connection pool. |
invoke_as_event |
Whether to handle callback functions sent to asynchronous functions is decided through Events. The default setting is true. If false, it is invoked in a separate thread. |
Note
If there are more than 2 connections, they are executed in parallel, so the Redis command order is not guaranteed. If the sequence of Redis commands must be guaranteed, please refer to Asynchronous command order and tags.
Note
If executing a Redis Subscribe command, one connection from the connection pool is chosen to handle Subscribe and set to handle only Subscribe afterward. Therefore, if there is only one connection, a command like Get is not performed after the Subscribe command, but left in the internal queue. (Commands left in the queue after that are handled when unsubscribing.) In this case, there must be at least 2 connections.
21.1.2. Initializing the connection pool¶
class RedisClient {
void Initialize();
};
public class RedisClient
{
public void Start();
}
Reset the connection pool to match the number of connections input using Create(). You can execute Redis commands after invoking this function.
21.2. Executing commands¶
21.2.1. Supported commands¶
The currently supported Redis commands are as follows. Del, Exists and similar functions are asynchronous commands. Sync is added to the end of synchronous commands.
Details on each command can be found in 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
21.2.2. Handling commands directly¶
If you need Redis commands other than the above or want to handle commands directly, use the following function.
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)
}
To run commands with the above functions, the reply structure for Redis server responses and return value of each Redis command, explained in Redis Commands, must be understood.
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
In this case, the string is returned as follows. You can get C++ values as str and C# as Str.
redis> GET mykey "hello"
- kArray
In this case, the array is returned as follows. You can get C++ values as elements and C# as Elements.
redis> KEYS * 1) "one" 2) "two"
- kInteger
In this case, the integer is returned as follows. You can get C++ values as integer and C# as Integer.
redis> HLEN myhash (integer) 2
- kNil
In this case, nil is returned as follows. There is no value for this, so you can only confirm that C++ is type and C# is Type.
redis> GET notfoundmykey (nil)
- kStatus
In this case, the status is returned as follows. You can get C++ values as str and C# as Str.
redis> SET mykey value OK
- kError
In this case, the error is returned as follows. You can get C++ values as str and C# as Str.
redis> SET mykey value value2 (error) ERR syntax error
21.2.3. Asynchronous command order and tags¶
When Redis commands are run asynchronously, they enter the queue in the default order. However, if there are 2 or more connections handling Redis commands, these commands are handled in parallel.
This can cause problems when the execution order must be guaranteed. RedisClient supports a feature which bundles Redis commands that must be handled in a guaranteed order as tags, as in Event order and event tags.
Asynchronous functions receive these tags as the last parameter, as follows. If tags are left by default, each Redis command is executed in parallel.
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))
};
If the tag value is input as follows, each Redis command is executed sequentially and the callback functions are called in the order of 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);
}
21.3. Example of use¶
21.3.1. Example - Saving user data¶
The following example shows user data saved as HMSet when a user logs in.
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);
}
|
21.3.2. Example - ExecuteCommand()¶
The following example shows the example handled with HMSet saved as an ExecuteCommand() function.
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);
}
|
21.4. Redis 레플리케이션¶
아이펀 엔진은 Redis Sentinel 을 통한 Failover 를 지원합니다.
Note
Redis Sentinel 설정과 관련된 더 자세한 내용은 Redis Sentinel 을 참고해주세요.
21.4.1. Sentinel 설정값 지정¶
아이펀 엔진은 sentinel_addresses
에 나열된 서버들과 통신하여 Master 서버를 알아내고, Master 서버가 변경되었다면 새로운 Master 서버로 자동 재연결합니다.
class RedisClient {
static Ptr<RedisClient> Create(const string &master_name, const vector<string> &sentinel_addresses,
const string &auth_pass, size_t connection_count
[, bool invoke_as_event = true][, size_t database = 0]);
};
public class RedisClient
{
public static RedisClient Create (string master_name, List<string> sentinel_addresses,
string auth_pass, ulong connection_count
[, bool invoke_as_event = true])
}
연결 초기화를 위한 설정값을 지정합니다.
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 |
비동기 함수에 전달한 콜백 함수를 Events 로 처리할지 결정합니다. 기본값은 true 입니다. false 를 입력할 경우 별도 스레드에서 호출합니다. |
Note
현재 Master 와 연결이 끊어지는 경우, Redis Sentinel 에 의해 Master가 변경 되기 전까지는 현재 Master 에 대해 재연결을 시도합니다. 또한 Master 변경 과정 중 발생하는 사용자의 요청에 대하여 Redis Client 는 내부적으로 요청을 큐잉한 뒤 Master가 변경되면 다시 요청을 처리하기 시작합니다.
21.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);
};
public class RedisClient
{
public delegate void SentinelMasterSwitchedCallback(string master_name, string old_master_name, string new_master_name);
public void SetSentinelMasterSwitchedCallback (SentinelMasterSwitchedCallback callback)
}
master_name |
Redis Sentinel 에 설정한 Master name 입니다. |
old_master_address |
이전 Master 의 주소입니다. |
new_master_address |
새 Master 의 주소입니다. |
21.4.2.1. Redis clustering¶
Note
iFun Engine itself does not support Redis clustering.