苍穹外卖-DAY10
苍穹外卖-DAY10
一、springtask

1.corn表达式
corn表达式在线生成网址
1 | cron.qqe2.com |
Java (Spring/Quartz):6 或 7 个字段 (秒 分 时 日 月 周 [年])
为了通用性,我们以最完整的 6-7 字段结构为例(这也是后端开发最常遇到的):
1 | 秒 分 时 日 月 周 年(可选) |
字段含义表
| 位置 | 字段含义 | 允许的值 | 允许的特殊字符 |
|---|---|---|---|
| 1 | 秒 (Seconds) | 0-59 | , - * / |
| 2 | 分 (Minutes) | 0-59 | , - * / |
| 3 | 时 (Hours) | 0-23 | , - * / |
| 4 | 日 (Day of Month) | 1-31 | , - * / ? L W |
| 5 | 月 (Month) | 1-12 或 JAN-DEC | , - * / |
| 6 | 周 (Day of Week) | 1-7 或 SUN-SAT | , - * / ? L # |
| 7 | 年 (Year) | 留空 或 1970-2099 | , - * / |
注意: 在 Linux
crontab中,没有“秒”这一位,第一位是“分”。
- 如果在“周”写了
*,意思是“不管今天是星期几(哪怕是周一到周日每一天)”。 - 如果在“周”写了
?,意思是“我不关心星期几,因为我已经指定了具体的日期(比如每月10号)”。 - 规则:Day 和 Week 字段,通常必有一个是
?。
2.springtask
自定义任务类
1 |
|
二、WebSocket
1.Socket网络协议

简单来说,WebSocket 解决了 HTTP 协议的一个最大痛点:服务器无法主动向客户端(浏览器)发消息。
2. 对比
- HTTP (传统的 Web):像 “发邮件”。
- 你需要在这个页面看到新消息,你必须手动刷新(或者用 JS 定时去问服务器)。
- 流程:你问服务器 -> 服务器回你 -> 挂断。
- 服务器:“你不问我,我就不理你。”
- WebSocket (实时 Web):像 “打电话”。
- 一旦电话接通(建立连接),你们俩谁都可以随时说话,不需要每次都重新拨号。
- 流程:你拨通服务器 -> 连接保持 -> 你说 -> 服务器说 -> 服务器再说 -> 你说…
- 服务器:“我有新消息,直接推给你,不用你问。”
2. WebSocket
在没有 WebSocket 之前,如果你想做一个“即时聊天”或者“股票实时大盘”,你只能用 轮询 (Polling):
前端 JS:老大,有新消息吗?
后端:没有。
(过了 1 秒)
前端 JS:老大,有新消息吗?
后端:没有。
(过了 1 秒)
前端 JS:老大,有新消息吗?
后端:有!
这种方式的缺点:
- 浪费带宽:每次请求都要带一堆 HTTP Header,有效数据可能就几个字。
- 延迟高:消息可能在两次轮询的中间到了,但必须等下次询问才能拿到。
- 服务器累:一万个用户每秒问一次,服务器 CPU 直接爆炸。
WebSocket 完美解决了这个问题:它实现了 全双工通信 (Full-Duplex)。连接一旦建立,服务器有数据就直接推过来,几乎没有延迟。
3. 建立连接握手协议
WebSocket 并不是一个全新的协议,它其实是 借用了 HTTP 来“搭桥”。
过程如下:
发起请求:浏览器发一个标准的 HTTP 请求给服务器,但是 Header 里带了特殊的暗号:
1
2
3
4GET /chat
Host: server.example.com
Upgrade: websocket
Connection: UpgradeUpgrade: websocket:意思是“大哥,我想升级协议,咱别用 HTTP 了,改用 WebSocket 吧”。
服务器响应:如果服务器支持,就会返回 101 状态码:
1
2
3101 Switching Protocols
Upgrade: websocket
Connection: Upgrade101 Switching Protocols:意思是“收到,协议切换成功”。
连接建立:哪怕这个 HTTP 请求结束了,底层的 TCP 连接不会断开。这就是一条全双工的通道了。此后传输的数据不再是 HTTP 报文,而是 WebSocket 的“数据帧”。
4. 核心注意事项
心跳检测 (Heartbeat):
网络是不稳定的。如果网线被拔了,服务器可能不知道客户端断了,还傻傻地拿着连接。所以通常客户端每隔 30 秒要发一个“Ping”,服务器回一个“Pong”,证明咱们还连着。
负载均衡 (Session 共享):
如果你的后端有 2 台服务器(集群),用户 A 连上了服务器 1,但支付回调打到了服务器 2。服务器 2 手里没有用户 A 的 WebSocket 连接,怎么推?
- 解决:需要引入 Redis 的 Pub/Sub(发布订阅)或者消息队列,让服务器 2 通知服务器 1 去推送。
5.springboot实现过程

二、ConcurrentHashMap
ConcurrentHashMap。
简单来说,ConcurrentHashMap 是 Java 中专门用于 多线程高并发场景 下的 Map(键值对集合)实现。
为了让你完全理解它的作用,我们需要对比一下你原本使用的 HashMap 和现在的 ConcurrentHashMap。
1. HashMap 会报错
在 WebSocket 服务器中,sessionMap 的作用是存储所有当前在线用户的连接对象(Session)。
- 场景: 想象有 100 个用户同时点击“连接”,还有 50 个用户同时“断开连接”或发送消息。
- HashMap 的弱点:
HashMap是线程不安全的。它就像一个没有锁的公共记事本。- 如果 线程 A 正在往本子上写名字(有人上线),同时 线程 B 正在撕掉某一页(有人下线),或者 线程 C 正在读取名单。
- 后果:
HashMap的内部结构会乱套,导致数据覆盖、丢失,或者直接抛出ConcurrentModificationException(并发修改异常),甚至在某些旧版本 Java 中会导致 CPU 100% 死循环。
2. ConcurrentHashMap
ConcurrentHashMap 就像是一个管理严格的档案室。它专为多线程设计,核心作用如下:
- 线程安全 (Thread Safety): 它保证了不管有多少个线程同时读写,数据内部结构都不会乱。你不需要自己写
synchronized锁,它内部已经处理好了。 - 高并发性能 (High Concurrency):
- 以前的笨办法 (
Hashtable): 相当于把整个档案室锁住,一个人进去操作,其他人都在门口排队。虽然安全,但效率极低。 ConcurrentHashMap的聪明办法: 它采用了分段锁(Java 7)或 CAS + 节点锁(Java 8+)机制。- 通俗解释: 它只锁住你正在操作的那一行柜子,而不是锁住整个房间。当你在操作“A”开头的用户时,别人完全可以同时操作“Z”开头的用户,互不干扰。
- 以前的笨办法 (
3. 核心差异对比表
| 特性 | HashMap (你的旧代码) | ConcurrentHashMap (修复后的代码) |
|---|---|---|
| 线程安全 | 不安全 (多线程必崩) | 安全 (专为多线程设计) |
| 性能 | 单线程极快,多线程无法工作 | 多线程下极高,单线程略慢(可忽略) |
| 允许 Null | key 和 value 都可以是 null | key 和 value 都不允许是 null (注意点!) |
| 适用场景 | 局部变量、单线程处理 | 全局缓存、WebSocket Session管理、共享资源 |
4. 代码场景还原
在你的 WebSocketServer 中,实际发生的事情是这样的:
使用 ConcurrentHashMap 后:
1 | // 当用户上线 (Thread 1) |
总结
这个修改将 sessionMap 从一个普通的非线程安全容器变成了一个线程安全的并发容器。
这是编写 WebSocket 服务端的标准操作。如果不改,你的服务在只有 1 个用户时是正常的,一旦有 2 个以上用户并发操作,服务就会随机崩溃。