• c# redis系列三


    List存储

    链表:存储非紧密摆放 修改新增方便 查询性能稍慢

     封装的List存储的redis类:

        /// <summary>
        ///  Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,
        ///  Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。  
        /// </summary>
        public class RedisListService : RedisBase
        {
            #region 赋值
            /// <summary>
            /// 从左侧向list中添加值
            /// </summary>
            public void LPush(string key, string value)
            {
                base.iClient.PushItemToList(key, value);
            }
            /// <summary>
            /// 从左侧向list中添加值,并设置过期时间
            /// </summary>
            public void LPush(string key, string value, DateTime dt)
            {
    
                base.iClient.PushItemToList(key, value);
                base.iClient.ExpireEntryAt(key, dt);
            }
            /// <summary>
            /// 从左侧向list中添加值,设置过期时间
            /// </summary>
            public void LPush(string key, string value, TimeSpan sp)
            {
                base.iClient.PushItemToList(key, value);
                base.iClient.ExpireEntryIn(key, sp);
            }
            /// <summary>
            /// 从右侧向list中添加值
            /// </summary>
            public void RPush(string key, string value)
            {
                base.iClient.PrependItemToList(key, value);
            }
            /// <summary>
            /// 从右侧向list中添加值,并设置过期时间
            /// </summary>    
            public void RPush(string key, string value, DateTime dt)
            {
                base.iClient.PrependItemToList(key, value);
                base.iClient.ExpireEntryAt(key, dt);
            }
            /// <summary>
            /// 从右侧向list中添加值,并设置过期时间
            /// </summary>        
            public void RPush(string key, string value, TimeSpan sp)
            {
                base.iClient.PrependItemToList(key, value);
                base.iClient.ExpireEntryIn(key, sp);
            }
            /// <summary>
            /// 添加key/value
            /// </summary>     
            public void Add(string key, string value)
            {
                base.iClient.AddItemToList(key, value);
            }
            /// <summary>
            /// 添加key/value ,并设置过期时间
            /// </summary>  
            public void Add(string key, string value, DateTime dt)
            {
                base.iClient.AddItemToList(key, value);
                base.iClient.ExpireEntryAt(key, dt);
            }
            /// <summary>
            /// 添加key/value。并添加过期时间
            /// </summary>  
            public void Add(string key, string value, TimeSpan sp)
            {
                base.iClient.AddItemToList(key, value);
                base.iClient.ExpireEntryIn(key, sp);
            }
            /// <summary>
            /// 为key添加多个值
            /// </summary>  
            public void Add(string key, List<string> values)
            {
                base.iClient.AddRangeToList(key, values);
            }
            /// <summary>
            /// 为key添加多个值,并设置过期时间
            /// </summary>  
            public void Add(string key, List<string> values, DateTime dt)
            {
                base.iClient.AddRangeToList(key, values);
                base.iClient.ExpireEntryAt(key, dt);
            }
            /// <summary>
            /// 为key添加多个值,并设置过期时间
            /// </summary>  
            public void Add(string key, List<string> values, TimeSpan sp)
            {
                base.iClient.AddRangeToList(key, values);
                base.iClient.ExpireEntryIn(key, sp);
            }
            #endregion
    
            #region 获取值
            /// <summary>
            /// 获取list中key包含的数据数量
            /// </summary>  
            public long Count(string key)
            {
                return base.iClient.GetListCount(key);
            }
            /// <summary>
            /// 获取key包含的所有数据集合
            /// </summary>  
            public List<string> Get(string key)
            {
                return base.iClient.GetAllItemsFromList(key);
            }
            /// <summary>
            /// 获取key中下标为star到end的值集合 
            /// </summary>  
            public List<string> Get(string key, int star, int end)
            {
                return base.iClient.GetRangeFromList(key, star, end);
            }
            #endregion
    
            #region 阻塞命令
            /// <summary>
            ///  阻塞命令:从list为key的尾部移除一个值,并返回移除的值,阻塞时间为sp
            /// </summary>  
            public string BlockingPopItemFromList(string key, TimeSpan? sp)
            {
                return base.iClient.BlockingPopItemFromList(key, sp);
            }
            /// <summary>
            ///  阻塞命令:从多个list中尾部移除一个值,并返回移除的值&key,阻塞时间为sp
            /// </summary>  
            public ItemRef BlockingPopItemFromLists(string[] keys, TimeSpan? sp)
            {
                return base.iClient.BlockingPopItemFromLists(keys, sp);
            }
    
    
            /// <summary>
            ///  阻塞命令:从list中keys的尾部移除一个值,并返回移除的值,阻塞时间为sp
            /// </summary>  
            public string BlockingDequeueItemFromList(string key, TimeSpan? sp)
            {
                return base.iClient.BlockingDequeueItemFromList(key, sp);
            }
    
            /// <summary>
            /// 阻塞命令:从多个list中尾部移除一个值,并返回移除的值&key,阻塞时间为sp
            /// </summary>  
            public ItemRef BlockingDequeueItemFromLists(string[] keys, TimeSpan? sp)
            {
                return base.iClient.BlockingDequeueItemFromLists(keys, sp);
            }
    
            /// <summary>
            /// 阻塞命令:从list中一个fromkey的尾部移除一个值,添加到另外一个tokey的头部,并返回移除的值,阻塞时间为sp
            /// </summary>  
            public string BlockingPopAndPushItemBetweenLists(string fromkey, string tokey, TimeSpan? sp)
            {
                return base.iClient.BlockingPopAndPushItemBetweenLists(fromkey, tokey, sp);
            }
            #endregion
    
            #region 删除
            /// <summary>
            /// 从尾部移除数据,返回移除的数据
            /// </summary>  
            public string PopItemFromList(string key)
            {
                var sa = base.iClient.CreateSubscription();
                return base.iClient.PopItemFromList(key);
            }
            /// <summary>
            /// 从尾部移除数据,返回移除的数据
            /// </summary>  
            public string DequeueItemFromList(string key)
            {
                return base.iClient.DequeueItemFromList(key);
            }
    
            /// <summary>
            /// 移除list中,key/value,与参数相同的值,并返回移除的数量
            /// </summary>  
            public long RemoveItemFromList(string key, string value)
            {
                return base.iClient.RemoveItemFromList(key, value);
            }
            /// <summary>
            /// 从list的尾部移除一个数据,返回移除的数据
            /// </summary>  
            public string RemoveEndFromList(string key)
            {
                return base.iClient.RemoveEndFromList(key);
            }
            /// <summary>
            /// 从list的头部移除一个数据,返回移除的值
            /// </summary>  
            public string RemoveStartFromList(string key)
            {
                return base.iClient.RemoveStartFromList(key);
            }
            #endregion
    
            #region 其它
            /// <summary>
            /// 从一个list的尾部移除一个数据,添加到另外一个list的头部,并返回移动的值
            /// </summary>  
            public string PopAndPushItemBetweenLists(string fromKey, string toKey)
            {
                return base.iClient.PopAndPushItemBetweenLists(fromKey, toKey);
            }
    
            /// <summary>
            /// 清理数据,保持list长度
            /// </summary>
            /// <param name="key"></param>
            /// <param name="start">起点</param>
            /// <param name="end">终结点</param>
            public void TrimList(string key, int start, int end)
            {
                base.iClient.TrimList(key, start, end);
            }
    
            #endregion
    
            #region 发布订阅
            public void Publish(string channel, string message)
            {
                base.iClient.PublishMessage(channel, message);//取消订阅
            }
    
            public void Subscribe(string channel, Action<string, string, IRedisSubscription> actionOnMessage)
            {
                var subscription = base.iClient.CreateSubscription();
                subscription.OnSubscribe = c =>
                {
                    Console.WriteLine($"订阅频道{c}");
                    Console.WriteLine();
                };
                //取消订阅
                subscription.OnUnSubscribe = c =>
                {
                    Console.WriteLine($"取消订阅 {c}");
                    Console.WriteLine();
                };
                subscription.OnMessage += (c, s) =>
                {
                    actionOnMessage(c, s, subscription);
                };
                Console.WriteLine($"开始启动监听 {channel}");
                subscription.SubscribeToChannels(channel); //blocking
            }
    
            public void UnSubscribeFromChannels(string channel)
            {
                var subscription = base.iClient.CreateSubscription();
                subscription.UnSubscribeFromChannels(channel);
            }
            #endregion
        }

     下面我们进行一些简单的api的调用展示:

       using (RedisListService service = new RedisListService())
                    {
                        service.FlushAll();
                        service.Add("article", "Richard1234");
                        service.Add("article", "kevin");
                        service.Add("article", "大叔");
                        service.Add("article", "C卡");
                        service.Add("article", "触不到的线");
                        service.Add("article", "程序错误");
                        service.FlushAll();
                        service.LPush("article", "Richard1234");//头部插入
                        service.LPush("article", "kevin");
                        service.LPush("article", "大叔");
                        service.LPush("article", "C卡");
                        service.LPush("article", "触不到的线");
                        service.LPush("article", "程序错误");
                        service.FlushAll();
                        service.RPush("article", "Richard1234");//尾部插入
                        service.RPush("article", "kevin");
                        service.RPush("article", "大叔");
                        service.RPush("article", "C卡");
                        service.RPush("article", "触不到的线");
                        service.RPush("article", "程序错误"); 
                        var result11 = service.Get("article");
                        //获取索引0到3的数据,包括0,3 注意list存储中没有索引,这里的索引是经过内部处理之后给外界显示的。
                        var result2 = service.Get("article", 0, 3);
                        //限制article中数据最大有多少
                        service.TrimList("article", 0, 3); 
                        service.FlushAll();
                        ////队列:生产者消费者模型 
                        service.Add("article", "Richard1234");
                        service.Add("article", "kevin");
                        service.Add("article", "大叔");
                        service.Add("article", "C卡");
                        service.Add("article", "触不到的线");
                        service.Add("article", "程序错误"); 
                        for (int i = 0; i < 5; i++)
                        {
                            //PopItemFromList 从尾部移除数据,每次移除一个
                            Console.WriteLine(service.PopItemFromList("article"));
                            var result1 = service.Get("article");
                        }
                    }

     这是redis可视化工具看到的具体数据:

     ServiceStack程序集中有一个GetRangeFromList(string listId, int startingFrom, int endingAt)方法,根据这个方法可以实现分页。比如说知乎,每天都有几万条问答的数据进入到平台,如果我每次都去数据里去查询;数据库肯定是扛不住,可以通过List 来存储。将key存储一个文章的id(可以是数据库中的主键),value存储Id_标题,首页获取最新的数据,我就可以通过前20条数据,至于详情就可以根据主键到数据库中查询,减少数据库压力。

     消息队列

     它的List存储天生支持消息队列。

    一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式。利用redis这两种场景的消息队列都能够实现。

    定义:
            生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列,谁先抢到消息谁就会从队列中取走消息;即对于每个消息只能被最多一个消费者拥有。
            发布者订阅者模式:发布者生产消息放到队列里,多个监听队列的消费者都会收到同一份消息;即正常情况下每个消费者收到的消息应该都是一样的。

    生产者消费者模式

      一个进程在向Redis写入数据,可以来多个进程在Redis 里面去获取数据;

     生产者进程:

                    #region 生产者消费者
                    //一个进程在向Redis写入数据
                    //可以来多个进程在Redis 里面去获取数据;
                    using (RedisListService service = new RedisListService())
                    {
                        service.FlushAll();
                        List<string> stringList = new List<string>();
                        for (int i = 0; i < 10; i++)
                        {
                            stringList.Add(string.Format($"放入任务{i}"));
                        }
    
                        service.Add("test", "task1 这是一个学生Add1");
                        service.Add("test", "task1 这是一个学生Add2");
                        service.Add("test", "task1 这是一个学生Add3");
                        service.LPush("test", "task1 这是一个学生LPush1");
                        service.LPush("test", "task1 这是一个学生LPush2");
                        service.LPush("test", "task1 这是一个学生LPush3");
                        service.LPush("test", "task1 这是一个学生LPush4");
                        service.LPush("test", "task1 这是一个学生LPush5");
                        service.LPush("test", "task1 这是一个学生LPush6");
                        service.RPush("test", "task1 这是一个学生RPush1");
                        service.RPush("test", "task1 这是一个学生RPush2");
                        service.RPush("test", "task1 这是一个学生RPush3");
                        service.RPush("test", "task1 这是一个学生RPush4");
                        service.RPush("test", "task1 这是一个学生RPush5");
                        service.RPush("test", "task1 这是一个学生RPush6");
    
                        service.Add("test", "task2 这是一个学生Add1");
                        service.Add("test", "task2 这是一个学生Add2");
                        service.Add("test", "task2 这是一个学生Add3");
                        service.LPush("test", "task2 这是一个学生LPush1");
                        service.LPush("test", "task2 这是一个学生LPush2");
                        service.LPush("test", "task2 这是一个学生LPush3");
                        service.LPush("test", "task2 这是一个学生LPush4");
                        service.LPush("test", "task2 这是一个学生LPush5");
                        service.LPush("test", "task2 这是一个学生LPush6");
                        service.RPush("test", "task2 这是一个学生RPush1");
                        service.RPush("test", "task2 这是一个学生RPush2");
                        service.RPush("test", "task2 这是一个学生RPush3");
                        service.RPush("test", "task2 这是一个学生RPush4");
                        service.RPush("test", "task2 这是一个学生RPush5");
                        service.RPush("test", "task2 这是一个学生RPush6"); 
                        service.Add("test", "这是一个学生Add1");
                        service.Add("test", "这是一个学生Add2");
                        service.Add("test", "这是一个学生Add3"); 
                        service.LPush("test", "这是一个学生LPush1");
                        service.LPush("test", "这是一个学生LPush2");
                        service.LPush("test", "这是一个学生LPush3");
                        service.LPush("test", "这是一个学生LPush4");
                        service.LPush("test", "这是一个学生LPush5");
                        service.LPush("test", "这是一个学生LPush6");
    
                        service.RPush("test", "这是一个学生RPush1");
                        service.RPush("test", "这是一个学生RPush2");
                        service.RPush("test", "这是一个学生RPush3");
                        service.RPush("test", "这是一个学生RPush4");
                        service.RPush("test", "这是一个学生RPush5");
                        service.RPush("test", "这是一个学生RPush6");
                        service.Add("task", stringList);
                        Console.WriteLine(service.Count("test"));
                        Console.WriteLine(service.Count("task"));
                        var list = service.Get("test");
                        list = service.Get("task", 2, 4);
    
                        Action act = new Action(() =>
                        {
                            while (true)
                            {
                                Console.WriteLine("************请输入数据**************");
                                string testTask = Console.ReadLine();
                                service.LPush("test", testTask);
                            }
                        });
    
                        //EndInvoke等待异步完成
                        act.EndInvoke(act.BeginInvoke(null, null));
                    }
                    #endregion

     消费者:

        public class ServiceStackProcessor
        {
            public static void Show()
            {
                string path = AppDomain.CurrentDomain.BaseDirectory;
                string tag = path.Split('/', '\').Last(s => !string.IsNullOrEmpty(s));
                Console.WriteLine($"这里是 {tag} 启动了。。");
                using (RedisListService service = new RedisListService())
                {
                    Action act = new Action(() =>
                    {
                        while (true)
                        {
                            //阻塞命令:从多个list中尾部移除一个值,并返回移除的值&key,阻塞时间为sp
                            var result = service.BlockingPopItemFromLists(new string[] { "test", "task" }, TimeSpan.FromHours(3));
                            Thread.Sleep(100);
                            Console.WriteLine($"这里是 {tag} 队列获取的消息 {result.Id} {result.Item}");
                        }
                    });
                    act.EndInvoke(act.BeginInvoke(null, null));
                }
            }
    
        }

    我们开启多个消费者进程,其中2个显示如下:

    类似于12306买票系统或者美团买票,如果是高峰期,你买票是肯定不会立即就能买到,可能需要你等待一段时间,用户将买票信息申请传到服务器,服务器将信息存到队列中,然后会有多个专门的进程来处理队列中的数据。

     通过这种方式可以实现下面的优点

    1.可以控制并发数量

    2.以前要求立马处理完,现在可以在一个时段完成

    比如说等待一段时间,服务器处理完成之后会告诉用户。

    3.失败还能重试

    比如说消息队列中的这个任务失败了,我们可以重新将这个任务加入到消息队列中再次进行处理。

    4.流量削峰,降低高峰期的压力

    因为有多个服务器来处理消息队列中的任务,所以降低压力

    5.高可用

    比如说处理消息队列中服务器中存在某几个崩溃了,或者需要升级,对真正的服务器是没什么影响的,问题处理完成之后再接着处理消息队列中的任务就行,用户实际不会感受到问题。

    6.可扩展

    缺点:

    不能立即处理、即时性不高;事务问题

    发布者订阅者模式

     比如微信公众号之类的关注系统

    订阅了对应的频道之后可以接收到这个频道发布的所有信息

                    #region 发布订阅:观察者,一个数据源,多个接受者,只要订阅了就可以收到的,能被多个数据源共享    这里的是用多个线程代表多个进程订阅,
    
                    Task.Run(() =>
                    {
                        using (RedisListService service = new RedisListService())
                        {
    //这个线程订阅Richard频道 service.Subscribe(
    "Richard", (c, message, iRedisSubscription) => { Console.WriteLine($"注册{1}{c}:{message},Dosomething else"); //如果传的消息是exit,那么就取消订阅 if (message.Equals("exit")) iRedisSubscription.UnSubscribeFromChannels("Richard"); });//blocking } }); Task.Run(() => { using (RedisListService service = new RedisListService()) { service.Subscribe("Richard", (c, message, iRedisSubscription) => { Console.WriteLine($"注册{2}{c}:{message},Dosomething else"); if (message.Equals("exit")) iRedisSubscription.UnSubscribeFromChannels("Richard"); });//blocking } }); Task.Run(() => { using (RedisListService service = new RedisListService()) { service.Subscribe("Twelve", (c, message, iRedisSubscription) => { Console.WriteLine($"注册{3}{c}:{message},Dosomething else"); if (message.Equals("exit")) iRedisSubscription.UnSubscribeFromChannels("Twelve"); });//blocking } }); using (RedisListService service = new RedisListService()) { Thread.Sleep(1000); service.Publish("Richard", "Richard123"); service.Publish("Richard", "Richard234"); service.Publish("Richard", "Richard345"); service.Publish("Richard", "Richard456"); service.Publish("Twelve", "Twelve123"); service.Publish("Twelve", "Twelve234"); service.Publish("Twelve", "Twelve345"); service.Publish("Twelve", "Twelve456"); Console.WriteLine("**********************************************"); service.Publish("Richard", "exit"); service.Publish("Richard", "123Richard"); service.Publish("Richard", "234Richard"); service.Publish("Richard", "345Richard"); service.Publish("Richard", "456Richard"); service.Publish("Twelve", "exit"); service.Publish("Twelve", "123Twelve"); service.Publish("Twelve", "234Twelve"); service.Publish("Twelve", "345Twelve"); service.Publish("Twelve", "456Twelve"); } #endregion

      

  • 相关阅读:
    Python之datetime模块
    PEP8规范 Python
    redis操作命令
    Django之Cookie、Session和自定义分页
    登录之验证码相关实现
    装饰器进阶
    js中的cookie使用和vue-cookie的使用
    vue-cli的安装使用
    Django之进阶相关操作
    PyMySQL模块的使用
  • 原文地址:https://www.cnblogs.com/anjingdian/p/15383351.html
Copyright © 2020-2023  润新知