.NET的RedisProvider

目录

介绍

用法

基本

模板化键创建

事务和批次

强类型数据对象

RedisItem和RedisBitmap

RedisList

RedisSet

RedisSortedSet

RedisHash,tvalue>

示例应用程序

兴趣点


介绍

.NET已经有几个Redis客户端库——tackExchange.RedisMicrosoft.Extensions.Caching.Redis并且ServiceStack.Redis最受欢迎。它为什么还要编写另一个库?我想要Redis客户端库中的一些内容:

  • 应用程序缓存的模型,类似于EF中的DbContext
  • 自动处理POCO数据类型,并轻松支持其他数据原语
  • 帮助一致的键命名
  • 支持键namespace
  • 轻松识别键的类型和内容
  • Intellisense仅显示键类型允许的命令

这些目标导致设计了一个名为RedisContainer的上下文/容器,其中包含建模Redis键类型的强类型数据对象。RedisContainer提供了一个键命名空间,并允许在应用程序中使用一个直观的Redis键模型,可以选择跟踪使用的键,但它本身不缓存任何数据。强类型对象也不会在应用程序内存中缓存任何数据,而是仅封装特定于每种常见数据类型的命令:

Redis数据类型

RedisItem<T>

二进制安全字符串

RedisBitmap

位数组

RedisList<T>

list

RedisSet<T>

set

RedisSortedSet<T>

zset

RedisHash<K, V>

hash

RedisDtoHash<T>

将哈希映射为DTO

RedisObject

*所有键类型的基类

该库依赖于StackExchange.RedisRedis服务器的所有通信,并且该API仅支持异步I/O

用法

基本

创建一个连接和容器。RedisConnection需要StackExchange配置字符串。RedisContainer对于所有键,需要一个连接和一个可选的名称空间。

var cn = new RedisConnection("127.0.0.1:6379,abortConnect=false");
var container = new RedisContainer(cn, "test");

键由容器管理。该键可能已存在于Redis数据库中。或没有。该GetKey方法不调用Redis。如果容器正在跟踪键创建并且键已经添加到容器中,则返回该对象,否则将创建并返回所请求类型的RedisObject的新键。

// A simple string key
var key1 = container.GetKey<RedisItem<string>>("key1");

// A key holding an integer.
var key2 = container.GetKey<RedisItem<int>>("key2");

对于任何类型的通用参数可以是一个IConvertiblebyte[]POCO/DTO。例:

var longitem = container.GetKey<RedisItem<long>>("longitem");
var intlist = container.GetKey<RedisList<int>("intlist");
var customers = container.GetKey<RedisHash<string, Customer>>("customers");
var cust1 = container.GetKey<RedisDtoHash<Customer>>("cust1");

POCO类型的自动JSON序列化/反序列化:

var key3 = container.GetKey<RedisItem<Customer>>("key3");
await key3.Set(new Customer { Id = 1, Name = "freddie" });
var aCust = await key3.Get();

所有键类型均支持基本命令:

key1.DeleteKey()
key1.Expire(30)
key1.ExpireAt(DateTime.Now.AddHours(1))
key1.IdleTime()
key1.KeyExists()
key1.Persist()
key1.TimeToLive()

访问StackExchange.Redis.Database可以直接执行RedisProvider不支持的任何命令。例:

var randomKey = container.Database.KeyRandom();

模板化键创建

当使用在键名中包含对象ID的通用模式时,例如user:1user:1234,手动创建每个键并确保数据类型和键名格式正确都是容易出错的。KeyTemplate<T>充当指定类型和键名称模式的密钥的工厂

var docCreator = container.GetKeyTemplate<RedisItem<string>>("doc:{0}");

// Key name will be "doc:1"
var doc1 = docCreator.GetKey(1);

// Key name will be "doc:2"
var doc2 = docCreator.GetKey(2);

事务和批次

通过基于StackExchange.Redis的事务和批处理支持管道。使用RedisContainer来创建批处理或事务,然后使用WithBatch()WithTransaction()添加排队的任务。

// A simple batch
var key1 = container.GetKey<RedisSet<string>>("key1");

var batch = container.CreateBatch();
key1.WithBatch(batch).Add("a");
key1.WithBatch(batch).Add("b");
await batch.Execute();

 

// A simple transaction
var keyA = container.GetKey<RedisItem<string>>("keya");
var keyB = container.GetKey<RedisItem<string>>("keyb");

await keyA.Set("abc");
await keyB.Set("def");

var tx = container.CreateTransaction();

var task1 = keyA.WithTx(tx).Get();
var task2 = keyB.WithTx(tx).Get();

await tx.Execute();

var a = task1.Result;
var b = task2.Result;

 

或者,您可以使用以下所示的语法将任务直接添加到事务或批处理中:

var keyA = container.GetKey<RedisItem<string>>("keya");
var keyB = container.GetKey<RedisItem<string>>("keyb");

await keyA.Set("abc");
await keyB.Set("def");

var tx = container.CreateTransaction();

tx.AddTask(() => keyA.Get());
tx.AddTask(() => keyB.Get());

await tx.Execute();

var task1 = tx.Tasks[0] as Task<string>;
var task2 = tx.Tasks[1] as Task<string>;
var a = task1.Result;
var b = task2.Result;

强类型数据对象

RedisItem<T>RedisBitmap

Redis二进制安全字符串RedisBitmapRedisItem<byte[]>添加位操作的操作。RedisValueItem是当通用参数类型不重要时可以使用的RedisItem<RedisValue>

RedisItem <T>

Redis命令

Get and set

 

Get(T)

GET

Set(T, [TimeSpan], [When])

SETSETEXSETNX

GetSet(T)

GETSET

GetRange(long, long)

GETRANGE

SetRange(long, T)

SETRANGE

GetMultiple(IList<RedisItem<T>>)

MGET

SetMultiple(IList<KeyValuePair<RedisItem<T>, T>>

MSET MSETNX

与字符串相关:

 

Append(T)

APPEND

StringLength()

STRLEN

与数字有关:

 

Increment([long])

INCR INCRBY

Decrement([long])

DECR DECRBY

RedisBitmap

 

GetBit(long)

GETBIT

SetBit(long, bool)

SETBIT

BitCount([long], [long])

BITCOUNT

BitPosition(bool, [long], [long])

BITPOS

BitwiseOp(Op, RedisBitmap, ICollection<RedisBitmap>)

BITOP

RedisList<T>

Redis中的LIST是元素的集合,按照插入的顺序排序。当列表项不是同一类型时,使用RedisValueList

RedisList<T>

Redis命令

添加和删​​除:

 

AddBefore(T, T)

LINSERT BEFORE

AddAfter(T, T)

LINSERT AFTER

AddFirst(params T[])

LPUSH

AddLast(params T[])

RPUSH

Remove(T, [long])

LREM

RemoveFirst()

LPOP

RemoveLast()

RPOP

索引访问:

 

First()

LINDEX 0

Last()

LINDEX -1

Index(long)

LINDEX

Set(long, T)

LSET

Range(long, long)

LRANGE

Trim(long, long)

LTRIM

杂项:

 

Count()

LLEN

PopPush(RedisList<T>)

RPOPLPUSH

Sort

SORT

SortAndStore

SORT .. STORE

GetAsyncEnumerator()

 

RedisSet<T>

Redis中的SET是独一无二的集合,包含未排序的元素。当设置的项目不是同一类型时,使用RedisValueSet 

RedisSet <T>

Redis命令

添加和删​​除:

 

Add(T)

SADD

AddRange(IEnumerable<T>)

SADD

Remove(T)

SREM

RemoveRange(IEnumerable<T>)

SREM

Pop([long])

SPOP

Peek([long])

SRANDMEMBER

Contains(T)

SISMEMBER

Count()

SCARD

Set操作:

 

Sort

SORT

SortAndStore

SORT .. STORE

Difference

SDIFF

DifferenceStore

SDIFFSTORE

Intersect

SINTER

IntersectStore

SINTERSTORE

Union

SUNION

UnionStore

SUNIONSTORE

杂项:

 

ToList()

SMEMBERS

GetAsyncEnumerator()

SSCAN

RedisSortedSet<T>

Redis中的ZSETSET相似,但是每个元素都有一个关联的浮点值,称为score。当设置的项目不是同一类型时,使用RedisSortedValueSet

RedisSortedSet <T>

Redis命令

添加和删​​除:

 

Add(T, double)

ZADD

AddRange(IEnumerable<(T, double)>)

ZADD

Remove(T)

ZREM

RemoveRange(IEnumerable<(T, double)>)

ZREM

RemoveRangeByScore

ZREMRANGEBYSCORE

RemoveRangeByValue

ZREMRANGEBYLEX

RemoveRange([long], [long])

ZREMRANGEBYRANK

范围和计数:

 

Range([long], [long], [Order])

ZRANGE

RangeWithScores([long], [long], [Order])

ZRANGE ... WITHSCORES

RangeByScore

ZRANGEBYSCORE

RangeByValue

ZRANGEBYLEX

Count()

ZCARD

CountByScore

ZCOUNT

CountByValue

ZLEXCOUNT

杂项:

 

Rank(T, [Order])

ZRANK ZREVRANK

Score(T)

ZSCORE

IncrementScore(T, double)

ZINCRBY

Pop([Order])

ZPOPMIN ZPOPMAX

Set操作:

 

Sort

SORT

SortAndStore

SORT .. STORE

IntersectStore

ZINTERSTORE

UnionStore

ZUNIONSTORE

GetAsyncEnumerator()

ZSCAN

RedisHash<TKey,TValue>

Redis HASH是由与值关联的字段组成的映射。RedisHash<TKey, TValue>将哈希处理为强类型键-值对的字典。RedisValueHash可以用于在键和值中存储不同的数据类型,而RedisDtoHash<TDto>则将DTO的属性映射到散列的字段。

RedisHash <TKeyTValue>

Redis命令

获取,设置和删除:

 

Get(TKey)

HGET

GetRange(ICollection<TKey>)

HMGET

Set(TKey, TValue, [When])

HSET, HSETNX

SetRange(ICollection<KeyValuePair<TKey, TValue>>)

HMSET

Remove(TKey)

HDEL

RemoveRange(ICollection<TKey>)

HDEL

哈希操作:

 

ContainsKey(TKey)

HEXISTS

Keys()

HKEYS

Values()

HVALS

Count()

HLEN

Increment(TKey, [long])

HINCRBY

Decrement(TKey, [long])

HINCRBY

杂项:

 

ToList()

HGETALL

GetAsyncEnumerator()

HSCAN

RedisDtoHash <TDto>

 

FromDto<TDto>

HSET

ToDto()

HMGET

示例应用程序

Redis文档提供了一个简单的Twitter克隆教程以及一个具有更完善应用程序的电子书。该示例基于其中描述的Redis概念。

示例“Twit”是一个非常基本的Blazor Webassembly应用程序。我们在这里感兴趣的部分是CacheService,它使用RedisProvider来建模和管理Redis缓存。

public class CacheService 
{
  private readonly RedisContainer _container;
  private RedisItem<long> NextUserId;
  private RedisItem<long> NextPostId;
  private RedisHash<string, long> Users; 
  private RedisHash<string, long> Auths; 
  private RedisList<Post> Timeline;

  private KeyTemplate<RedisDtoHash<User>> UserTemplate;
  private KeyTemplate<RedisDtoHash<Post>> PostTemplate;
  private KeyTemplate<RedisSortedSet<long>> UserProfileTemplate;
  private KeyTemplate<RedisSortedSet<long>> UserFollowersTemplate;
  private KeyTemplate<RedisSortedSet<long>> UserFollowingTemplate;
  private KeyTemplate<RedisSortedSet<long>> UserHomeTLTemplate;
  ...
}

这里的CacheService包含了RedisContainer,但它可以很容易地扩展RedisContainer而不是:public class CacheService : RedisContainer {}

在这两种情况下,容器都将提供连接信息和keyNamespace,在这种情况下为twit容器创建的所有键名将采用twit:{keyname}格式。

在这里,我们看到所谓的固定键,名称为常数的键和动态键(其名称包含ID或其他变量数据)。

因此NextUserIdNextPostId简单的二进制安全字符串项就是一个长整数。这些字段用于获取新创建的用户和帖子的ID

NextUserId = _container.GetKey<RedisItem<long>>("nextUserId");
NextPostId = _container.GetKey<RedisItem<long>>("nextPostId");

var userid = await NextUserId.Increment();
var postid = await NextPostId.Increment();

UsersAuths哈希,就像简单的字典一样,用于将用户名或身份验证票证字符串映射到用户ID

Users = _container.GetKey<RedisHash<string, long>>("users");
Auths = _container.GetKey<RedisHash<string, long>>("auths");

 // Add a name-id pair
 await Users.Set(userName, userid);

 // Get a userid from a name
var userid = Users.Get(userName);

TimelinePost POCO类型的列表。(该示例包括多个时间轴,通常以集合的形式存储。这个列表与其说是有用的,不如说是说明问题的。)

Timeline = _container.GetKey<RedisList<Post>>("timeline");

var data = new Post {
   Id = id, Uid = userid, UserName = userName, Posted = DateTime.Now, Message = message };

await Timeline.AddFirst(data);

现在为动态键。我们将为每个用户和帖子维护一个散列,其键名包含IDKeyTemplate<T>将允许我们定义键类型和键名曾经的格式,然后根据需要获取单个键。此处的哈希键还自动映射到POCO/DTO类型,其中POCO的属性是存储的哈希中的字段。

UserTemplate = _container.GetKeyTemplate<RedisDtoHash<User>>("user:{0}");
PostTemplate = _container.GetKeyTemplate<RedisDtoHash<Post>>("post:{0}");

 var user = UserTemplate.GetKey(userId);
 var post = PostTemplate.GetKey(postId);

 var userData = new User {
    Id = userId, UserName = name, Signup = DateTime.Now, Password = pwd, Ticket = ticket
    };
 user.FromDto(userData);

 var postData = new Post {
    Id = postId, Uid = userid, UserName = userName, Posted = DateTime.Now, Message = message
    };
 post.FromDto(postData);

最后,该模型包含由用户ID键入的几个排序集(ZSET)的模板。

// The post ids of a user's posts:
UserProfileTemplate = _container.GetKeyTemplate<RedisSortedSet<long>>("profile:{0}");

// The user ids of a user's followers:
UserFollowersTemplate = _container.GetKeyTemplate<RedisSortedSet<long>>("followers:{0}");

// The user ids of who the user is following:
UserFollowingTemplate = _container.GetKeyTemplate<RedisSortedSet<long>>("following:{0}");

// The post ids of the posts in a user's timeline:
UserHomeTLTemplate = _container.GetKeyTemplate<RedisSortedSet<long>>("home:{0}");

有了这些,Id = 1的用户将具有以下键:

RedisDtoHash<User>("user:1")
RedisSortedSet<long>("profile:1")
RedisSortedSet<long>("home:1")
RedisSortedSet<long>("following:1")
RedisSortedSet<long>("followers:1")

因此,这里有一个简单的模型,并且由于强类型的关键字段和模板而易于概念化。现在需要注意的是,RedisContainer将跟踪这些键值,但是如果有很多键值—例如,数以千计的用户和帖子—您可能不想让容器维护所有这些键值的字典。

CacheService提供RegisterUserLoginUserCreatePostGetTimelineFollowUser等类似于上面提到的电子书中的功能,我把它们留给有兴趣的人自己去探索。这是显示RegisterUser逻辑的最后一个片段:

public async Task<string> RegisterUser(string name, string pwd) 
{
    if ((await Users.ContainsKey(name))) throw new Exception("User name already exists");

    // Get the next user id
    var id = await NextUserId.Increment();

    // Get a RedisDtoHash<User>("user:{id}") key
    var user = UserTemplate.GetKey(id);

    // Populate a dto 
    var ticket = Guid.NewGuid().ToString();
    var userData = new User { 
       Id = id, UserName = name, Signup = DateTime.Now, Password = pwd, Ticket = ticket };

    // Create a transaction - commands will be sent and executed together
    var tx = _container.CreateTransaction();

    //  -- populate user hash
    user.WithTx(tx).FromDto(userData);
    //  -- add name-id pair 
    Users.WithTx(tx).Set(name, id);
    //  -- add ticket-id pair
    Auths.WithTx(tx).Set(ticket, id);

    // And now execute the transaction
    await tx.Execute();

    return ticket;
 }

兴趣点

为什么只异步?因为I/O操作应该是异步的,并且Redis(和StackExchange.Redis)非常快,所以最好记住Redis不是本地进程内缓存。

API为什么不使用“*Async”命名方法?因为我不喜欢他们。

RedisProvider中仍然存在一些痛点,但总的来说,我发现它是对基本StackExchange API的改进。事务(和批处理)的语法很笨拙,就像StackExchange要求您添加异步任务但不等待它们一样,这经常导致CS4014令人烦恼的因为未等待此调用...”编译器警告。可以通过编译指示禁用这些功能,但仍然可以使代码更易于出错。

当前不支持其他Redis数据类型或功能——HyperLogLogsGEOstreamsPub / Sub

我最初计划让这些强类型数据对象实现.NET接口,IEnumerable<T>至少,IList<T>ISet<T>IDictionary<K, V>作为适当的提供熟悉的.NET语义。RedisProvider的第一个版本仅提供一个同步API并实现了.NET接口,但是存在两个主要问题。首先,我发现Redis.NET经常出现阻抗不匹配,这是Redis键类型支持的功能以及看似互补的接口所需要的。其次,鉴于我的目标是使Intellisense的范围仅限于键类型可用的命令,对我来说更重要,这是System.Linq实现IEnumerable<T>时带来的大量扩展方法或其任何子接口。鉴于这些对象不在本地保存数据,因此大多数方法如果被调用,效率将非常低下,并且会不必要地造成API查找混乱。

Github仓库在这里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值