代码改变世界

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