C#的Redis入门案例

2016-4-28 雨辰 C#

Redis是内存数据库,在很多地方中,是作为关系型数据库的缓存来使用的,类似MemoryCache。


它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型), redis的出现,很大程度补偿了memcached这类key/ value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。


但Redis也有持久化的功能,所以有的地方也会抛弃关系型数据库直接使用Redis。


比如开发一个网站,有些频繁更新的数据,使用关系型数据库会不太合适。遇到这种情况,通常,可以自己做一层内存缓存或者使用其他的缓存工具。

自己做内存缓存的话,那么数据就被锁定在缓存它的进程中了,如果是分布式系统,就不好同步。而放在Redis之类的内存数据库中的话,需要通过通信交互才能获得数据。


两种方式各有利弊,抛开这层不谈,直说使用Redis的情况,假设有这样一款应用使用Redis作为数据容器,作为一个基础应用的案例,一步步体验Redis中的功能,最后封装出一个方便使用Redis存取的类库,就是本文的内容了。


首先这个应用是数据相关的,也就没有了界面,可以当做是Web后端,或者是网络游戏的游戏服务器部分。因为是只处理数据,所以不引入网络通信,只是假设有这样一个入口,能够接收符合接口的各种输入。


我就直接设定这是一个游戏,语言使用的是C#。


使用VS创建解决方案


包含两个项目,一个ConsoleApplication名为RedisTest,一个类库RoyEngine,RedisTest添加项目引用RoyEngine。RoyEngine就是目标封装的Redis的快速使用类库了。


创建类Player


作为一个数据应用,第一步,是要能保存数据,最先的数据就是用户信息。所以我首先在RedisTest中创建一个类Player。


                              

非常简单,只有名字和战斗力两个属性。


然后在Main函数中var player = new Player();并给属性赋值。

我的目的是要把这个player实例保存到Redis中去。


所以我需要这样一个方法,Save(player);


添加Redis快速访问类Roy


在RoyEngine中创建class Roy,添加函数void Save<T>(T entity)

毫无疑问,这必须是一个泛型函数。感觉有点像是关系型数据库的ORM。

现在实现这个函数就需要连接Redis,这里需要说明的是Redis读写是单线程的,也就是说不管创建几个连接,数据到了Redis都会依序读写,所以,Roy并不会创建多个对Redis的连接。


连接Redis


C#对Redis的对接已经有很多类库了,通过NuGet搜索Redis,你将看到一堆如StackExchange.Redis、ServiceStack.Redis等等,这里我使用的是ServiceStack.Redis的V3版本,因为之后的V4是要收费的,所以分出了一个分支叫NServiceKit.Redis,可以在GitHub上找到它的源码。


这里通过Nuget安装到RoyEngine中,点开引用,可以看到如下图:



增加了4个引用,值得一提的是其中的NServiceKit.Text其实是有JSON功能的。

有了这个类库,就可以给Roy添加一个私有字段RedisClient _redis;


然后在构造函数中传入host,port,password和db四个参数,并对_redis进行实例化了。



我重载了两个构造函数,做完这一步,可以到RedisTest项目的Main函数中添加对应实例化的代码


Roy roy = newRoy(“localhost”,6379);


那么Redis的连接就告成了。


实现Save<T>()函数


其实对这个Save的实现有一个很简单方式,就是直接Json序列化保存。

Redis是一个键值对数据库,存取都是通过键值对匹配的方式,只要有一个Id作为Key就能保存各种各样的数据。


上面提及过,NServiceKit.Text其实是带有JSON功能的,所以,如果只想使用JSON保存,可以直接使用_redis.Set(id, entity);就搞定了。


这里使用的Set是对应redis的SET命令,对应的读取命令是Get,也有对应的函数实现。


NSK.Redis在Set时会对entity自动json序列化保存。


当然我不这么做,Redis有一个结构叫做Hash,本身具有一个键,而保存的值类型是键值对的Hash表,非常适合做这种实例的浅序列化(只保存一层基础类型)。


那么Id怎么获取呢?


Redis有一个incr的命令,其实就是维护一个key对应的increment值。把这个key设定为className,那么每一个class获取到的自增值肯定都隔离了。

在自增的下,设定实例的Id={ClassName}:{incrId},那就可以保证每一个保存的class实例都是唯一的了。


有了Hash的Id,就可以对这个Hash进行HMSet命令了,这个命令是HSET的multi版,可以设置多个keyValue对。


通过typeOf(T).GetProperties()获得属性,然后通过PropertyInfo.GetValue()获得属性值,进行设置保存。


由于这里获得的所有value都是object的,而HashSet需要的是byte[],这里就需要转换为字节数组。其实,因为只是浅保存,所以只支持几个基本数据类型,可以设定一个支持的基本类型枚举,然后分别ConvertToBytes,最后保存到一个byte[][]中,一口气进行HMSet。


优化


上面部分主要是反射的应用。当然反射是比较伤性能的,不过也有部分可以优化的地方,比如GetProperties。


因为所有需要保存的类,在编译的时候肯定是可以确定了的,所以,在写代码的时候就可以加上一些标记,那么可以使用C#的Attribute特性。


我想过给Class打上一个特性来标识它是需要被存档的。但是之后,实际编写的时候发现还有一个Id这个大头的东西很重要。每一个需要存档的Class都需要一个Id,而这个Id在刚才被我设定成了string类型。那么仅仅是特性的约束就不太够了,干脆就做成基类好了。


所以,就有了这么一个基类RoyDataBase。只有继承自RoyDataBase的类型才能被保存。


那就给刚才的Save泛型加上where T:RoyDataBase的约束吧。


然后还可以写一个静态方法,在程序启动时执行,反射出所有RoyDataBase的子类和他们的Properties,保存到一个Dictionary里面,那么下次至少这部分不需要反射了。实际测试一下,做了这个之后,Save的速度提高了一倍。


忽略那些不需要保存的字段属性


有时候为了方便,会在需要持久化的域类型中添加一些其他的属性或字段,而这些是不需要保存也不想被保存的,那么需要给他们打上标记。那就干脆给想保存的打上标记吧。


于是增加RoyValueAttribute这个特性,在程序初始化反射的时候忽略那些不带该特性的PropertyInfo。现在的Player就像这样:



读取


既然保存了,也就需要有相应的读取,同样的读取也是一个泛型方法:


public TGet<T>(id) where T:RoyDataBase,new(){

  T t = new T();

//读取Redis

//找到初始化时保存好的元数据,用PropertyInfo.SetValue()方法进行赋值

}


这里读取Redis需要用到的是Hash的另一个命令HMGet, 全部读取之后遍历反射赋值,这个函数也就实现完毕了。


测试


终于到了最后一步,实验下刚才完成的两个函数吧。哦,对了,Redis还没有吧。


可以在我最下方提供的链接里面找到redis的文件夹,里面有两个cmd文件,双击其中的server.cmd就能运行一个redis实例。


运行起redis之后,就在RedisTest的Main函数中保存然后读取一万个不同的Player试试看吧。


然后你还可以改一下Save和Get的实现,对比下用JSON保存和Hash保存的不同。


当然也可以改成sql insert试试,看看速度差多少。


附上代码和Redis工具的地址:https://git.oschina.net/roytin/RoyEngine


接下来还有唯一字段、排序、分组提取、保存集合类型等功能要添加。预定下一篇,增加排序的功能。

 

转载 蛮牛社区

标签: Redis

发表评论:

雨辰 joyimp|@2011-2018 京ICP备16030765号