小白关于“消息队列”处理“秒杀”业务的疑惑!

背包客1 2017-10-16 05:39:25
最近在学消息队列,一些不懂得地方,希望得到各位的解答,谢谢。
各大网站上面写到消息队列的时候,都有一个介绍说,能处理秒杀业务,比如数据库10个商品,有100个请求过来,先将请求放到队列里面,然后取队列里面取10个请求,然后对数据库进行操作。请求实列图如下(阿里云复制的)

原文地址:http://www.cnblogs.com/linjiqin/p/5720865.html

“将请求放入队列”,一度我以为,“队列”这个东西,可以把一个"http请求"存起来,程序到队列里面取的时候,取出来的还是一个“http请求”,然后把请求可以返回给客户端,我今天尝试了微软的msmq,简单的用了一下,好像队列只能放字符串,我就蒙了,那如果是这样,那怎么处理这个秒杀??

后面问了一些人,说这个队列是放请求的数据,不是“请求”。

就上面秒杀的例子,下面有这些疑问:
1、队列存放数据,假如我一个http请求发过来,服务器把请求数据放到队列里面,然后我的请求是不是会释放返回给客户端?此时客户端的状态或者表现是什么呢?等待中吗?
2、我数据库只有10个商品,我把请求的数据都放到队列里面,那队列怎么样通知我的程序,通知他可以开始去取队列里面的数据呢?
3、我的程序从队列里取出了数据,处理好了,我怎么样去通知前端在等待的界面,显示结果呢??



求各位有经验的大神,帮我分析一下这些细节,谢谢!


...全文
1852 21 打赏 收藏 转发到动态 举报
写回复
用AI写文章
21 条回复
切换为时间正序
请发表友善的回复…
发表回复
huan0101 2020-03-27
  • 打赏
  • 举报
回复
西瓜大湿 2019-03-13
  • 打赏
  • 举报
回复
说的都是啥?
丰云 2019-02-19
  • 打赏
  • 举报
回复
。。。。 思维这么僵化,看了脑瓜疼。。。
xuan.ye 2019-02-14
  • 打赏
  • 举报
回复
我今天尝试了微软的msmq,简单的用了一下,好像队列只能放字符串,我就蒙了,那如果是这样,那怎么处理这个秒杀??

后面问了一些人,说这个队列是放请求的数据,不是“请求”。
----------------------------------------------------------------------------------
没用过msmq ,所以百度了一下概念。按照我的理解是在一台机器上存储数据,多台服务器可以共享/共同使用这个队列,个人认为用这个做秒杀理论上是可以,但感觉麻烦些,而且只能在windows上使用。这种队列蛮多的例如redis。


就上面秒杀的例子,下面有这些疑问:
1、队列存放数据,假如我一个http请求发过来,服务器把请求数据放到队列里面,然后我的请求是不是会释放返回给客户端?此时客户端的状态或者表现是什么呢?等待中吗?
----------------------------------------------------------------------------
请求肯定会释放的,客户端根据服务器的逻辑显示,比如你想让他成功就显示成功,你要是想等待,做成等待也可以,假装很多人在强。


2、我数据库只有10个商品,我把请求的数据都放到队列里面,那队列怎么样通知我的程序,通知他可以开始去取队列里面的数据呢?
-----------------------------------------------------------------------------------------------------------------------------------------
页面总量显示数据库,剩余数显示队列剩余数,队列一般无法通知到客户端,需要客户端主动请求队列剩余数。


3、我的程序从队列里取出了数据,处理好了,我怎么样去通知前端在等待的界面,显示结果呢??
------------------------------------------------------------------------------------------------------
有一些第三方 ws.js 库支持传统的请求和ws ,只要分别写代码就好,复杂是一定的,有经验的前端开发一般都会写
wanghui0380 2019-02-13
  • 打赏
  • 举报
回复
对于秒杀,用队列只是策略问题。而且还是一个并不是很好的策略
实际上秒杀动作本身,应该是令牌桶限流策略,而非队列策略。

秒杀动作后续付款动作倒是可以丢队列里玩,这个意味着后续动作可以是分步/分布的
wanghui0380 2019-02-13
  • 打赏
  • 举报
回复
C# 的webapi,本身可以用async关键字的 ,这意味着本身语法机制上你就可以await 一个任务结束。
so,那还要我们怎么说呢?

如果说其他语言呢?其他语言说,十年前用ajax刷新或者等待服务器端转向到结果页(比如早期网银系统,你先转到他那边,他处理完毕再转向到你这边),当然现在则直接用websocket等待推送通知。

ps:秒杀是瞬时的,根本就不存在什么异步处理。原理上就是一个令牌桶,有牌子就进,没牌子就扔。
qq_42917391 2019-02-13
  • 打赏
  • 举报
回复
引用 13 楼 以专业开发人员为伍 的回复:
举这里的例子,这里的
 event Action<int> 有任务结束
声明了一个多播委托列表,它是不是数据?.net 线程池机制本身所管理的内部数据结构是不是数据? 唯一可以挑出来点儿的,也就是说这两个数据(多播委托、线程池内部机制)没有将这些数据序列化/反序列化到你自己的文件中而已。人家的现成的管理机制高速地管理大量排队信息,能不能只想着自己发明那些简单的博客上的最底层的简单“队列”概念而不认识 .net 框架中许多高级别的自动化流水处理任务调度的功能。
好了 说了这么多 我就想知道队列消息异步处理后成功后 怎么告诉用户秒杀成功
  • 打赏
  • 举报
回复
自己的想象的物理机器的数量 --> 自己的想象的物理机器的内部资源数量 内部资源是个综合指标,所以几十年前的作者只能粗劣地用一个自己想当然地“并发数”来控制这个指标,而且还是静态的数字。而 .net 系统线程池能够动态管理线程调度,一开始会延迟一点时间来避免同时启动过多任务,所以通常不用自己发明什么队列机制。
  • 打赏
  • 举报
回复
秒杀时,根本不减库存。 例如秒杀商品10万件,那么把这些单品放到高速内存中,每接受一个秒杀请求就减掉一个单品,但是整个管理信息系统、库存账,那是以后才要减的,甚至可能是20秒钟或者20分钟——用户已经被导航到支付甚至是发货环节——之后才处理的。 这里的问题是秒杀的任务处理调度机制,其实并不直接设计秒杀操作所涉及的数据结构问题。只是顺便说一下。
  • 打赏
  • 举报
回复
比如说有20种商品、一共有10万件秒杀货物,那么你顶多能够对排到10万以后的用户请求才能说“不处理”,并不能随便就根据自己的想象的物理机器的数量来说“不处理秒杀业务逻辑”。
baidu_27549073 2018-02-09
  • 打赏
  • 举报
回复
业务逻辑是其实就是:用户开始秒杀,如果现在服务器处理的请求过多,就将他转到错误页面,不处理秒杀的业务逻辑。 解决的问题应该是:当用户访问量大的时候可能出现网站崩溃,或者并发问题,比如只有30个商品,结果卖出去31个,库存为-1。 文中说的队列只是判断“处理的请求过多”这个逻辑的一种手段。 至于这个手段好不好,我没用过,不评论。
  • 打赏
  • 举报
回复
我上面先说了自己管理“队列”的控制机制,然后说明作为一个 .net 程序员基本上是并不用考虑队列的,那么最后一个解答就是,其实有些人所说的另外一种“队列”并非真正队列,而是人家微软(或者IBM等等)过去开发的通讯网关程序。人家允许一个字符串(比如说一个 .net 对象的 json 序列化结果)从一台机器传送到另外一台机器,目标机器顺序读取这些字符串。 那么你的目标机器读取了数据,就好像上面我写的 Test(i) 一样,你不过就是执行“解析字符串(str)”来判断这个 json 是不是一个任务描述、如果是就耗时处理它。所以这个时候所谓的“队列”实际上是指一个已经发布出来的成熟的用于字符串“流”的通讯网关框架(例如 MSMQ)概念,这个时候就更加需要清醒,不要纠结什么“队列”概念了!
  • 打赏
  • 举报
回复
让我们看看真正在 .net 上这个程序应该怎么写
using System;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {

        static void Main(string[] args)
        {
            for (var i = 0; i < 30; i++)
                Test(i);
            Console.WriteLine("................按任意键结束");
            Console.ReadKey();
        }

        static async void Test(int i)
        {
            var r = await 加100000(i);
            Console.WriteLine($"输入:{i}, 输出:{r}");
        }

        static Random rnd = new Random();

        static async Task<int> 加100000(int x)
        {
            var d = rnd.Next(3000, 5000);
            await Task.Delay(d);    //模拟耗时操作
            return x + 100000;
        }

    }
}
因为 .net 系统线程池会自动化地根据当前系统资源情况而调度——延迟——发起并发任务,所以你根本不需要自己写什么控制任务的代码,根本不用什么“队列”。因为 .net 系统线程池本身就是队列,而且它本身就把线程执行任务所需要的内存中的状态对象数据维持着(根本不需要什么“字符串)。 因此,实际上根本不用扯到什么“队列”,在 .net 上你用涉及到系统线程池的各种高级别的框架(例如PLinq)来编程,你基本上就不用考虑什么队列了。纠结队列时我们可以看作是 java 程序员或者一些 c 语言初学者,这些人才会特别喜欢底层的概念。
  • 打赏
  • 举报
回复
给你写一个控制台的demo
using System;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {

        static void Main(string[] args)
        {
            for (var i = 0; i < 30; i++)
                Test(i);
            Console.WriteLine("................按任意键结束");
            Console.ReadKey();
        }

        static int 执行中数量;
        static object lockflag = new object();
        static event Action<int> 有任务结束;

        static async void Test(int i)
        {
            lock (lockflag)
            {
                if (执行中数量 >= 3)
                {
                    Action<int> proc = null;
                    proc = new Action<int>(x =>
                    {
                        有任务结束 -= proc;
                        Test(i);
                    });
                    有任务结束 += proc;
                    return;
                }
                else
                    执行中数量++;
            }
            var r = await 加100000(i);
            Console.WriteLine($"输入:{i}, 输出:{r}");
            lock (lockflag)
            {
                Console.WriteLine($"--------输入:{i} 结束后执行中数量为:{执行中数量}");
                执行中数量--;
            }
            if (有任务结束 != null)
                有任务结束(i);
        }

        static Random rnd = new Random();

        static async Task<int> 加100000(int x)
        {
            var d = rnd.Next(3000, 5000);
            await Task.Delay(d);    //模拟耗时操作
            return x + 100000;
        }

    }
}
这里,假设任务是要把输入的 i 加上 100000 然后输出,主程序快速发起了30个并发任务,但是此 Test 方法要求最多同时执行3个任务,其它的排队等候。你可以运行这个例子,然后回答你自己的问题: 当产生了并发任务之后,首先进入第一个 lock 管理区域,此时判断如果运行的并发任务数量>=3的时候,则向事件监听队列注册监听,然后就立刻结束了。当事件发生(有其它任务结束),则从事件队列中首先注销监听,然后重新执行本任务(重新开始判断是否并发任务数量>=3)。否则,它就立刻把并发任务数量++。注意这两个动作是在 lock 管理范围中的,同一时间只有一个线程能够执行这个范围中的代码,所以这就保证不会产生混乱控制。 15或者20年前,像java的语言比较原始,远比当初的 .net 更加原始,所有一大堆垃圾java书都是抄袭30年前、40年前的c、unix书籍上的一些概念。所谓“队列”就是一个比较容易庸俗化的概念。许多程序貌似用了“队列”这个词儿给自己贴金,而实际上还是顺序阻塞的思路,那些书上的“队列”就是运行时低效的、代码繁琐的东西。所以这里关键是用精炼的代码,用优雅的.net机制来写高效率的代码,所以你提的几个问题很好,你没有纠结“队列”这个词儿而是问到了关键的细节上!
sinat_25277675 2018-02-09
  • 打赏
  • 举报
回复
楼主有思路了吗
sp1234_maJia 2018-02-09
  • 打赏
  • 举报
回复
比如说我们可以把代码
            if (有任务结束 != null)
                有任务结束(i);
改为代码
            if (有任务结束 != null)
            {
                var cnt = 0;
                foreach (Action<int> proc in 有任务结束.GetInvocationList())
                {
                    proc(i);
                    if (++cnt >= 3)
                        break;
                }
            }
这里仅仅通知最多3个宿主,而不是把事件通知给所有的监听宿主。 这里编程时就是围绕“数据的”,但是这是什么数据?这种数据是不是队列?这是高级的数据概念,是解决实际应用的各种实用框架,不是重复一个简单的数据结构。
  • 打赏
  • 举报
回复
举这里的例子,这里的
 event Action<int> 有任务结束
声明了一个多播委托列表,它是不是数据?.net 线程池机制本身所管理的内部数据结构是不是数据? 唯一可以挑出来点儿的,也就是说这两个数据(多播委托、线程池内部机制)没有将这些数据序列化/反序列化到你自己的文件中而已。人家的现成的管理机制高速地管理大量排队信息,能不能只想着自己发明那些简单的博客上的最底层的简单“队列”概念而不认识 .net 框架中许多高级别的自动化流水处理任务调度的功能。
  • 打赏
  • 举报
回复
秒杀就相当于你到医院看病时先要挂个号,所以它跟后续的什么减库存之类的操作是脱离开的。秒杀服务器上只要关系“放多少号”就行了。这决定了秒杀其实也非常简单。 关于“请求、数据”的区别,这个其实是文字游戏。任何请求都是数据,关键是你要知道这里是哪一种深度的数据,并且知道数据的保存跟监听和调度它的专用框架紧密相关(不可能脱离开过程而只谈数据),那么你才接触实质。
  • 打赏
  • 举报
回复
秒杀非常简单。之所以最初的国营软件公司做的12306网站总是崩溃,那是体制问题、人的问题,最常见地就是那些人满脑子只有“增删改查”而并不区分到底是哪一种东西的增删改查,就好像只关心搬砖而不关心建筑,结果就是做东西总是推倒重来。所以我们首先强调需求分析,强调流程和状态设计技术,而不失强调底层编程手艺。
weishaolin13x 2018-02-09
  • 打赏
  • 举报
回复
队列是缓存数据,不是缓存请求, petshop的案例有个队列处理订单的数据,将订单放到队列里,然后异步逐个处理订单
加载更多回复(1)

7,763

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 非技术区
社区管理员
  • 非技术区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧