【Redis】-封装Redis消息队列-流量削峰

一.未做消息队列
缺陷:用户秒杀,请求到了上游服务秒杀服务,然后上游服务调用下游服务下订单,减去库存,更新余额。
上游服务秒杀服务的并发量能力有10000,下游服务的并发量能力有1000,当真的客户端并发量是10000,上游服务秒杀服务能接收10000个请求,但是下游服务只能接收1000个请求,那么下游服务就宕机了。

二.配合消息队列

上游服务并发来了10000个请求,只把1000个请求写入消息队列。

三.封装Redis消息队列,优化流量请求

namespace MyRedisUnitty
{
    /// <summary>
    /// 封装Redis消息队列
    /// </summary>
    public class RedisMsgQueueHelper : IDisposable
    {
        /// <summary>
        /// Redis客户端
        /// </summary>
        public RedisClient redisClient { get; }

        public RedisMsgQueueHelper(string redisHost)
        {
            redisClient = new RedisClient(redisHost);
        }

        /// <summary>
        /// 入队
        /// </summary>
        /// <param name="qKeys">入队key</param>
        /// <param name="qMsg">入队消息</param>
        /// <returns></returns>
        public long EnQueue(string qKey, string qMsg)
        {
            //1.编码字符串
            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(qMsg);

            //2.Redis消息队列入队
            long count = redisClient.LPush(qKey, bytes);

            return count;
        }

        /// <summary>
        /// 出队(非阻塞) === 拉
        /// </summary>
        /// <param name="qKey">出队key</param>
        /// <returns></returns>
        public string DeQueue(string qKey)
        {
            //1.redis消息出队
            byte[] bytes = redisClient.RPop(qKey);
            string qMsg = null;
            //2.字节转string
            if (bytes == null)
            {
                Console.WriteLine($"{qKey}队列中数据为空");
            }
            else
            {
                qMsg = System.Text.Encoding.UTF8.GetString(bytes);
            }

            return qMsg;
        }

        /// <summary>
        /// 出队(阻塞) === 推
        /// </summary>
        /// <param name="qKey">出队key</param>
        /// <param name="timespan">阻塞超时时间</param>
        /// <returns></returns>
        public string DeQueueBlock(string qKey, TimeSpan? timespan)
        {
            //1.Redis消息出队
            string qMsg = redisClient.BlockingPopItemFromList(qKey, timespan);

            return qMsg;
        }

        /// <summary>
        /// 获取队列数量
        /// </summary>
        /// <param name="qKey">队列key</param>
        /// <returns></returns>
        public long GetQueueCount(string qKey)
        {
            return redisClient.GetListCount(qKey);
        }

        /// <summary>
        /// 关闭Redis
        /// </summary>
        public void Dispose()
        {
            redisClient.Dispose();
        }
    }
}

四.上游服务-使用Redis消息队列优化流量请求
发送消息,模拟下游可以接受请求量100,队列中数量超出100则抛出异常,否则写入队列。

namespace MyRedisFlowPeak.FlowLimit
{
    /// <summary>
    /// 秒杀上游服务:客户接受请求量100
    /// </summary>
    class SecKillUpstream
    {
        /// <summary>
        /// 处理的最大请求数
        /// </summary>
        private int HandlerRequestCounts = 100;

        /// <summary>
        /// 秒杀方法
        /// </summary>
        /// <param name="RequestCounts">请求数量</param>
        public void CreateSkillOrder(int requestCounts)
        {
            //1.创建秒杀订单
            Console.WriteLine($"秒杀请求数量:{requestCounts}");

            //如何使用Redis消息队列优化流量请求?
            //Redis优化
            using (var msgQueue = new RedisMsgQueueHelper("localhost:6379"))
            {
                //1.循环写入队列
                for (int i = 0; i < requestCounts; i++)
                {
                    //1.1.获取消息队列数量
                    long count = msgQueue.GetQueueCount("My_Order");

                    //1.2.判断是否已满
                    if (count >= HandlerRequestCounts)
                    {
                        Console.WriteLine($"系统正常繁忙,请稍后...");
                    }
                    else
                    {
                        //1.3.写入队列订单编号
                        Console.WriteLine($"入队成功");
                        msgQueue.EnQueue("My_Order", "OrderNo:" + i + "");
                    }
                }
            }
        }
}

五.下游服务

消费上游消息,做下游服务的业务逻辑。

namespace MyRedisSecKillDownStream
{
    class Program
    {
        static void Main(string[] args)
        {
            //Redis优化
            using (var msgQueue = new RedisMsgQueueHelper("localhost:6379"))
            {
                Console.WriteLine("上游消息......");
                //1.获取上游消息
                while (true)
                {
                    string rm_sms = msgQueue.DeQueueBlock("My_Order", TimeSpan.FromSeconds(60));

                    Console.WriteLine($"*****************开始秒杀下游业务调用**********************");
                    //2.下游业务逻辑,秒杀业务处理
                    SecKillDownstream secKillDownstream = new SecKillDownstream();
                    secKillDownstream.HandlerRequest(rm_sms);
                    Console.WriteLine($"*****************秒杀下游业务调用完成**********************");
                }
            }
        }
    }
}

 

namespace MyRedisSecKillDownStream.FlowLimit
{
    /// <summary>
    /// 下游服务:最大请求处理量为100
    /// </summary>
    class SecKillDownstream
    {
        /// <summary>
        /// 处理请求
        /// </summary>
        /// <param name="requestCount"></param>
        public void HandlerRequest(string requestCount)
        {
            Thread.Sleep(10);
            //1.创建订单
            Console.WriteLine($"秒杀订单创建成功");
            //2.扣减库存
            Console.WriteLine($"秒杀订单库存扣减生成");
            //3.扣减余额
            Console.WriteLine($"用户余额扣减成功");

            Console.WriteLine($"处理的请求数:{requestCount}");
        }
    }
}

六.调用客户端

namespace MyRedisFlowPeak
{
    class Program
    {
        static void Main(string[] args)
        {
            SecKillUpstream secKillUpstream = new SecKillUpstream();
            secKillUpstream.CreateSkillOrder(1000);
        }
    }
}

七.运行效果

八.项目截图

posted @ 2020-05-31 07:41  David.Meng  阅读(2503)  评论(0编辑  收藏  举报