美文网首页
点餐场景并发知识点小纪

点餐场景并发知识点小纪

作者: morning_king | 来源:发表于2019-11-04 22:27 被阅读0次

    场景

    我们现在有一个餐馆名叫王小二菜馆,用户会不时的访问我们的在线点餐系统,这个系统的核心功能是点餐,我们做这个需求的逐步迭代:

    1. 用户访问我们系统时,我们返回欢迎光临王小二菜馆
    2. 用户访问我们系统时,我们返回历史访问情况欢迎光临王小二菜馆,您是第N位客户
    3. 用户访问我们系统时,我们返回目前的点餐情况欢迎光临王小二菜馆,您的取餐号为X,您前面还有N位进餐用户,请耐心等待...,用户可以手工刷新查看最新排队号;
    4. 什么?都9102年了,还要用户手工刷新?用户进入页面后,不想手工刷新,需要实时查看就餐情况;

    方案

    针对上面提出的逐步迭代的需求,我们来各个击破,并总结其中涉及到的关键知识点:

    这里假设我们只有一台应用服务器

    1. 用户访问我们系统时,我们返回欢迎光临王小二菜馆

    有过编程经历的童鞋一定会说,这个问题难道不是基本操作吗?还需多言?我们java猿儿只需开启一个tomcat,引入spring-mvc,写一个controller,轻轻松松get一分,So easy!
    当然思路肯定是这样,不过这里需要提出的是,tomcat作为一个应用服务器,其实已经为我们做好了请求承接和请求分发的事儿,所以我们只用简简单单写一段代码就轻松搞定,其实这儿还是有很大学问的,如何接收用户请求以及分发请求其实都是tomcat帮我们透明处理了,tomcat在这做这些事情时其实干了两个关键的事儿:

    • 接收请求:tomcat需要建立一个本地socket链接来处理每一次请求;
    • 请求分发:针对每一次请求,tomcat需要做协议解析得到相应的请求体,并根据配置好的线程模型来分发这次请求,默认tomcat是BIO模型即每个请求一个线程,然后每个线程执行我们相应代码的逻辑,返回欢迎光临王小二菜馆,需要注意的是这些线程由一个线程池进行维护,每次请求都会从这个线程池中获取,如果并发请求过多则会引发排队;

    知识点:应用服务器、线程模型、线程池;

    2. 用户访问我们系统时,我们返回历史访问情况欢迎光临王小二菜馆,您是第N位客户

    在简单介绍了应用服务器后,我们暂且先不深挖,关注问题您是第N位客户,这不就是一个简单的计数器吗?So easy,我定义一个全局变量,来一个请求+1不就ok了嘛,这又是一道送分题😂
    不过等等,我们刚刚讲到,每个请求都会从线程池拿出一个线程,如果我们定义一个全局变量,这里我们一定能够保证每个请求+1后,后续线程都能看到最新的值吗?这可不一定呢,另外,同时来了多个线程,他们同时+1,那么是不是就少算了呀?
    这里就碰到了我们经典的一致性问题了,同一个共享变量,一个线程写入,下一个线程一定可以看到吗?多个一起写,最后的数据一定可以保证符合预期吗?
    有过一定经验的童鞋肯定会说,加个锁吧,一步到位,当然这是一种解决方法,我们还有更好的方法呢。

    知识点:可见性、顺序性、锁;

    3. 用户访问我们系统时,我们返回目前的点餐情况欢迎光临王小二菜馆,您的取餐号为X,您前面还有N位进餐用户,请耐心等待...

    这应该是我们平时经常碰到的情况:需求很短,范围很大,无形需求最为致命😂
    让我们来详细分解,首先每个用户要有一个排队号,另外要知道目前有多少用户正在进餐,另外前面用户吃完后,需要通知第X位用户可以就餐,且取餐号越小,越快就位。
    每个用户一个排队号,嗯,这是已知题,pass;
    前面有多个用户进餐,且他们吃完后通知,嗯哼,这个问题可是有那么点难度呀,这里我们可以使用两个数据结构,一个用于保存目前正在进餐的用户列表L1,一个保存目前正在排队的用户列表L2,叫号请求过来时,先生成顺序号,然后看L1是否满,如果满了就放到L2,L1里面的用户吃完后,从L2取一个放入L1,bingo,不过这儿我们需要注意的问题是,L1和L2都是需要支持多个线程访问的,需要保证线程安全。

    知识点:并发容器、线程安全;

    4. 什么?都9102年了,还要用户手工刷新?用户进入页面后,不想手工刷新,需要实时查看就餐情况;

    猿儿:这难道不是一个送分题?我三十秒刷新一次,不就行了。
    PM:什么,三十秒钟,五秒钟我都嫌长了,用户不耐烦走了怎么办?转换率谁来负责?
    被暴击后的猿儿顿时阻塞,开始了长达五秒钟的沉默,空气瞬间凝固...
    猿儿别心灰意冷,每一次暴击都是你成长的机会,😄
    这里提供一个不那么优雅的方案,我们设计一个信号量字典,key是用户顺序号,value是一个信号量,默认信号量被征用,然后用户排队请求过来如果需要排队,那么在对应信号量上面做超时等待,5秒超时,当L1用户用餐完毕拿到下一个排队顺序号,释放信号量,该用户即可就餐,嗯哼,not bad。
    当然,这个方案有一定风险,第一题我们讲过,应用服务器默认会使用一个线程来处理当前请求,线程陷入等待,那岂不是线程池可用线程-1,服务器可用风险+1,这里我们期待更好的解决方案🥺。

    知识点:信号量,线程等待、线程唤醒

    ok, 到这里,我们就我们四个小需求做了简单的设计,里面也引出了一些并发编程的关键知识点,也解决了我们的任务,🍗+4,😄。
    等等,这些知识点都只是提出来了而已呀,还没具体讲解呢,别着急,后续我会开一个小系列来专门整理讲解这些知识点,敬请期待~

    相关文章

      网友评论

          本文标题:点餐场景并发知识点小纪

          本文链接:https://www.haomeiwen.com/subject/itrjbctx.html