点餐场景并发知识点小纪
场景
我们现在有一个餐馆名叫王小二菜馆
,用户会不时的访问我们的在线点餐系统,这个系统的核心功能是点餐,我们做这个需求的逐步迭代:
- 用户访问我们系统时,我们返回
欢迎光临王小二菜馆
; - 用户访问我们系统时,我们返回历史访问情况
欢迎光临王小二菜馆,您是第N位客户
; - 用户访问我们系统时,我们返回目前的点餐情况
欢迎光临王小二菜馆,您的取餐号为X,您前面还有N位进餐用户,请耐心等待...
,用户可以手工刷新查看最新排队号; - 什么?都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,😄。
等等,这些知识点都只是提出来了而已呀,还没具体讲解呢,别着急,后续我会开一个小系列来专门整理讲解这些知识点,敬请期待~