List存储

c# redis系列三_h5

 

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

 封装的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可视化工具看到的具体数据:

c# redis系列三_消息队列_02

 

 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个显示如下:

c# redis系列三_服务器_03

 

 

 

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

c# redis系列三_redis_04

 

 

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

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