ServiceStack.Redis实践
Redis的C#客户端我选择的是ServiceStack.Redis,相比Booksleeve redis-sharp等方案,它提供了一整套从 Redis数据结构都强类型对象转换的机制;看一个例子来了解一下ServiceStack.Redis是如何组织数据的,我们使用的实体类定义如下:
?
1
2
3
4
5
6
7
8
9
10
11
|
public class User
{
public User()
{
this .BlogIds = new List< long >();
}
public long Id { get; set; }
public string Name { get; set; }
public List< long > BlogIds { get; set; }
}</ long ></ long >
|
使用下面的代码片段,我们存入两条数据到Redis:
?
1
2
3
4
5
6
7
|
using (var redisUsers = redisClient.GetTypedClient<user>())
{
var ayende = new User { Id = redisUsers.GetNextSequence(), Name = "Oren Eini" };
var mythz = new User { Id = redisUsers.GetNextSequence(), Name = "Demis Bellot" };
redisUsers.Store(ayende);
redisUsers.Store(mythz);
}</user>
|
我们看下Redis中的结果:
?
1
2
3
4
5
|
redis 127.0.0.1:6379[1]> keys *
1) "seq:User"
2) "ids:User"
3) "urn:user:1"
4) "urn:user:2"
|
我们逐一检查一下数据类型:
seq:User
|
string
|
维护当前类型User的ID自增序列,用做对象唯一ID
|
ids:User
|
set
|
同一类型User所有对象ID的列表
|
urn:user:1
|
string
|
user对象
|
seq:User 维护的是类型User的ID序列 redisUsers.GetNextSequence()
?
1
2
3
4
5
6
7
8
|
public long GetNextSequence( int incrBy)
{
return IncrementValueBy(SequenceKey, incrBy);
}
public long IncrementValue(string key)
{
return client.Incr(key);
}
|
这里的SequenceKey就是 "seq:User",然后我们通过存一个对象到Redis看另外两个key是什么作用:
?
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
|
public T Store(T entity)
{
var urnKey = entity.CreateUrn();
this .SetEntry(urnKey, entity);
return entity;
}
//entity.CreateUrn();的结果是"urn:user:1"
public void SetEntry(string key, T value)
{
if (key == null)
throw new ArgumentNullException( "key" );
client.Set(key, SerializeValue(value));
client.RegisterTypeId(value);
}
internal void RegisterTypeId<t>(T value)
{
var typeIdsSetKey = GetTypeIdsSetKey<t>();
var id = value.GetId().ToString();
if ( this .Pipeline != null)
{
var registeredTypeIdsWithinPipeline = GetRegisteredTypeIdsWithinPipeline(typeIdsSetKey);
registeredTypeIdsWithinPipeline.Add(id);
}
else
{
this .AddItemToSet(typeIdsSetKey, id);
}
}</t></t>
|
这里的typedIdsSetKey 就是"ids:User"
ids:User相当于一个索引,包含了所有同为类型User的ID;由于维护了这样一个分组信息,所以很容易实现GetAll()这样的功能;
在redis-cli中查询一下 get urn:user:1 返回值是 JSON格式:
"{\"Id\":1,\"Name\":\"Oren Eini\",\"BlogIds\":[1]}"
ServiceStack.Redis 自己实现了一套序列化功能, Fastest JSON Serializer for .NET released 支持 POCO(Plain Old CLR Object)序列化.
实际应用中,由于我们使用的数据是来自关系型数据库,本身包含关联关系,所以并不需要这样的对象组织方式;我们只需要把关系型数据中一对多的关系在Redis中表达出来即可;这里我扩展修改了RedisClient的实现,由于RedisClient本身已经通过 partial方式 分割成若干个文件,所以很容易把变动的代码集中在同一个代码文件中.具体业务对象存储,主帖和回帖会有字段级修改,所以设计成为Hash结构,其它几个子对象读写都是以对象为单位,设计成为POCO方式持久化;
使用管道Pipeline遇到的问题
使用管道可以将客户端到Redis的往返次数减少,不过在使用ServiceStack.Redis的时候,遇到这样一个问题,比如要把一个List全部存储,代码不可以写成下面这样:
?
1
2
3
4
5
6
7
8
9
|
%%第一种写法
logs.ForEach(n =>
{
pipeline.QueueCommand(r =>
{
((RedisClient)r).Store<oplog>(n, n.GetObjectID(), n.GetUrnKey());
((RedisClient)r).Expire(n.GetUrnKey(), dataLifeTime);
});
});</oplog>
|
而是要写成这样:
?
1
2
3
4
5
6
7
8
|
%%第二种写法
logs.ForEach(n =>
{
pipeline.QueueCommand(r => ((RedisClient)r).Store< log >(n, n.ID, n.GetUrnKey()));
pipeline.QueueCommand(r => ((RedisClient)r).Expire(n.GetUrnKey(), dataLifeTime));
});</ log >
|
什么原因呢?RedisQueueCompletableOperation的AddCurrentQueuedOperation方法会在
执行CurrentQueuedOperation =
null;如果按照第一种写法会丢失回调函数,这就造成有返回值在没有及时提取,后续的操作获取返回值时首先取到的是积压的结果信息,就出现了异常,而第
二种写法就避免了这个问题.
?
1
2
3
4
5
|
protected virtual void AddCurrentQueuedOperation()
{
this .QueuedCommands.Add(CurrentQueuedOperation);
CurrentQueuedOperation = null;
}
|
时间: 2024-10-03 07:38:52