StackExchange.Redis在net中使用
2018-01-26 18:14 糯米粥 阅读(7773) 评论(0) 编辑 收藏 举报redis 官网 https://redis.io
redis 下载 进入下载页面 https://redis.io/download
https://github.com/MicrosoftArchive/redis/releases
单击 Learn more 进入GitHub页面
github地址: https://github.com/antirez/redis
然后:
这里只有64位的。下载msi。安装后。就可以在服务看到redis服务了
RedisDesktopManager 管理工具下载 https://redisdesktop.com/download
https://github.com/uglide/RedisDesktopManager/releases/tag/0.8.8
https://github.com/uglide/RedisDesktopManager
net客户端使用redis 一般都是使用 StackExchange.Redis 和 ServiceStack.Redis
SE是免费的。SS是收费的。这两个dll都能通过NuGet安装
既然SE是免费的。所以本文就使用SE.。
Redis简介(收集网络)
Remote Dictionary Server(Redis这个名称是一个缩写)是一个基于 key-value 键值对的、可以持久化的、完全开源免费的、遵守BSD协议的内存数据库存储系统,常用作缓存或者消息队列。支持多种数据结构,包括 string (字符串)、list (链表)、set (集合)、zset (sorted set:有序集合)和 hash(哈希类型)。这些数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
接下来,我们在说说Redis的优势的,如果没有优势,鬼才会使用它呢。
1、读写性能极高 – Redis读的速度是110000次/s,写的速度是81000次/s,所以使用Redis缓存数据,存取数据几乎是0感觉,当然是对于用户来说的 。
2、支持丰富的数据类型 – Redis支持丰富的数据类型,如: String(字符串), Lists(链表), Hash(哈希),Set(无序集合) 及 ZSet(有序集合)等数据类型,所以我们放弃了Memched,因为它支持的数据类型太少了。
3、所有操作支持原子性 – Redis的所有操作都是原子性的,意思就是要么成功执行,要么失败。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
4、丰富的特性 – Redis支持 publish/subscribe(发布/订阅),也支持事务、队列、持久化,可以设置key过期时间等等特性。
准备工作
导入dll后,创建一个单列
/// <summary> /// 单列 /// </summary> public class RedisManager { private RedisManager() { } private static ConnectionMultiplexer instance;
private static readonly object locker = new object(); /// <summary> /// 单例模式获取redis连接实例 /// </summary> public static ConnectionMultiplexer Instance { get {
lock (locker)
{
if (instance == null) { if (instance == null) instance = ConnectionMultiplexer.Connect("127.0.0.1"); //这里应该配置文件,不过这里演示就没写 }
} return instance; } } }
打开RedisDesktopManager 工具
RedisDesktopManager文档:http://docs.redisdesktop.com/en/latest/quick-start/
String(字符串)
如果RedisKey 存在,则会替换
var db = RedisManager.Instance.GetDatabase(); var result = db.StringSet("string", "字符串"); //插入成功,返回true
db.StringGet("string") //获取值
存json也可以
StringAppend 追加字符串
db.StringAppend("string", "追加");
List (列表)
特点:有序排列,值可以重复。我们可以通过pop,push操作来从头部和尾部删除或者添加元素。这使得list既可以做栈也可以做队列
主要有:
ListRightPush:底部插入数据
ListLeftPush:顶部插入数据
ListLeftPop:出栈一条数据,出一条。则redis中就少一条
ListRightPop:同理
首先看看ListRightPush 方法,在list_right 中添加10条数据
for (int i = 0; i < 10; i++) { db.ListRightPush("list_right", i); }
ListLeftPush 方法:
for (int i = 0; i < 10; i++) { db.ListLeftPush("list_left", i); }
如果有个需求,要显示3个最后来的用户,可以用ListLeftPush 方法。
只要3个,则可以用ListTrim 方法
db.ListTrim("list_left", 0, 2); //只显示前3个
看看pop方法。比如ListRightPop
db.ListRightPop("list_left");
从底部出栈一条数据
来看看获取List数据,获取不是pop操作
删除一个键可以用 db.KeyDelete("list_right");
set(无序排列)
db.SetAdd("set_add", "你好"); db.SetAdd("set_add","小明");
set提供了取并集,交集,差集的方法
SetCombine方法
RedisValue[] ds = db.SetCombine(SetOperation.Union, "set_add", "set_add"); foreach (var item in ds.OrderBy(m => m).ToList()) { Console.Write((string)item + " "); }
SortedSet(有序排列)
添加顺序不同,但最后会排序
db.SortedSetAdd("sort", "t1",2); db.SortedSetAdd("sort", "t2", 12); db.SortedSetAdd("sort", "t3", 5);
SortedSet方法提供了累加的方法SortedSetIncrement
如果key和value都存在则累加。否则则新增
db.SortedSetIncrement("sort", "t1", 8);//t1存在,则累加 db.SortedSetIncrement("sort", "t4", 5); //t4不存在,则新增
可以看到。如果Score相同。则根据value排序
获取key的话。通过SortedSetRangeByRank方法,该方法可以排序。通过Order.Ascending,
还可以从指定位置获取
获取key后。通过SortedSetScore 获取Score
RedisValue[] vs = db.SortedSetRangeByRank("sort", 1, 2, Order.Ascending); for (int i = 0; i < vs.Length; i++) { var value = db.SortedSetScore("sort", vs[i]); }
Hash(哈希表)
Hash是一个string类型的field和value的对应表,它更适合来存储对象,相比于每个属性进行一次缓存,利用hash来存储整个对象会占用更小的内存。但是存储速度并不会更快
db.HashSet("hash", "name","张三"); db.HashSet("hash", "age", "20"); db.HashSet("hash", "address", "中国");
取值也很容易
db.HashGet("hash", "name"); //取单个 db.HashGetAll("hash"); //取所有 db.HashGet("hash", new RedisValue[] { "name", "age" }); //根据指定的hashkey取值
上面也说了,哈希表一般会把一个对象序列化后缓存。这样比每个属性缓存一次效率高
string 存在二进制文件
上面讲了。string的值是字符串的。可以是字符串类型,json类型。当然也可以是二进制类型
序列化二进制的方法是BinaryFormatter 命名空间在System.Runtime.Serialization.Formatters.Binary;
BinaryFormatter是不能序列化匿名类的。因为序列化的类必须要标记为Serializable
[Serializable] class user { public string name; public string age; }
当然。你也可以把json字符串序列化成二进制文件在缓存。根据你的需求吧。
现在我们把一个对象序列化成二进制文件后在缓存
user u = new user { name = "你好",age="18"}; var json = JsonConvert.SerializeObject(post); byte[] bytes; //序列化成二进制 using(var stream = new MemoryStream()) { new BinaryFormatter().Serialize(stream, u); bytes = stream.ToArray(); } db.StringSet("bf", bytes); //读取二进制文件 byte[] bf = (byte[])db.StringGet("bf"); using(var stream = new MemoryStream(bf)) { user obj =(user) new BinaryFormatter().Deserialize(stream); }
Redis 事物
Redis因为有并发的问题,所以要考虑到事物。Redis通过CreateTransaction函数(multi)来创建一个事物,调用其Execute函数(exec)提交事物
var db = RedisManager.Instance.GetDatabase(); //设置值 db.StringSet("name", "张三"); db.StringSet("age", 90); string name = db.StringGet("name"); string age = db.StringGet("age"); Console.WriteLine("name:" + name); Console.WriteLine("age:" + age); //创建一个事物 ITransaction trans = db.CreateTransaction(); //锁定RedisKey=name RedisValue=张三的缓存 trans.AddCondition(Condition.StringEqual("name", name)); Console.WriteLine("begin trans"); trans.StringSetAsync("name", "Tom"); bool isExec = trans.Execute(); //提交事物,name才会修改成功 name = Tom Console.WriteLine("事物执行结果:" + isExec); string _name = db.StringGet("name"); string _age = db.StringGet("age"); Console.WriteLine("name:" + _name); Console.WriteLine("age:" + _age); Console.WriteLine("end trans");
执行结果
这里通过trans.AddCondition(Condition.StringEqual("name", name)); 锁定了name,
必须通过 trans.Execute();提交事物。才能成功
可以试试,在Execute 提交事物之前,先修改name的值,在执行 trans.Execute()的时候,则会失败,图中红色标记部分
执行结果
因为在trans.Execute()的时候发现值已经被修改了。则回滚事物,导致事物执行失败
StackExchange.Redis中对于连续多次的缓存等请求,我们会多次调用相关的函数来执行Redis命令。然而这种方式有个弊端就是每一次的请求都需要等待返回结果
如果在网络状况不好的情况下,可能会造成不好的用户体验。
对于这种问题可以用StackExchange.Redis提供的CreateBatch()解决
IBatch batch = db.CreateBatch(); Task t1 = batch.StringSetAsync("name", "tom"); Task t2 = batch.StringSetAsync("age", 20); batch.Execute(); Task.WaitAll(t1, t2);
batch会把所需要执行的命令打包成一条请求发到Redis,然后一起等待返回结果。这样批量操作的速度就大大提升啦!
StackExchange.Redis中的发布和订阅
发布端代码
var db = RedisManager.Instance.GetDatabase(); db.PublishAsync("name", "张三"); db.PublishAsync("age", 18); db.PublishAsync("address", "长沙");
订阅端代码
var redis = RedisManager.Instance.GetSubscriber(); //channel是通道名称,message是消息 redis.SubscribeAsync("name", (channel, message) => { Console.WriteLine("我叫:" + message); }); redis.SubscribeAsync("age", (channel, message) => { Console.WriteLine("我今年:" + message); }); redis.SubscribeAsync("address", (channel, message) => { Console.WriteLine("我来自:" + message); });
先运行订阅端,然后在打开发布端。否则收不到信息
参考:
https://www.cnblogs.com/bluesummer/p/7677969.html
http://www.cnblogs.com/PatrickLiu/p/8260228.html
http://blog.csdn.net/WuLex/article/details/52637196
http://www.cnblogs.com/bluesummer/p/7874788.html