一、OA系统高并发挑战:业务场景分析、性能瓶颈识别、架构演进路线图
1.1 业务场景分析:到底哪些场景在“挤”你的系统?
做OA系统架构,我第一个要问自己的问题是:“用户到底在什么时候最着急?”
很多人觉得OA嘛,不就是审批、考勤、公告,能有多大并发?
嗯,我以前也这么想。直到有一次,一家5000人规模的客户上线第一天,早上8:59分,系统直接挂了。
为什么会这样?
因为OA的并发场景,比你想象的要“集中”得多。
典型高并发场景清单
- 早高峰打卡(8:30 - 9:00):全员同时刷脸/定位打卡,瞬间QPS冲到几千甚至上万。我见过最夸张的一次,某公司3000人同时打卡,数据库连接池直接被打满。
- 月末审批潮(每月最后3天):报销单、请假单、合同审批集中提交。这时候不光是读,还有大量写操作,锁冲突严重。
- 全员公告/通知(突发):比如公司发奖金、放假通知,所有人同时点开查看。静态资源还好,但如果有附件下载,带宽和IO瞬间吃紧。
- 会议预约冲突(周一上午):大家抢会议室,并发写同一个资源,容易产生死锁或超时。
核心结论:OA系统的并发特点是“潮汐式”的——平时风平浪静,高峰时波涛汹涌。你不能按平均负载来设计,必须按峰值来扛。
1.2 性能瓶颈识别:你的系统到底“卡”在哪?
识别瓶颈,说白了就是“找最短的那块木板”。
我习惯从三个层面去排查:客户端、网络、服务端。但大部分时候,问题出在服务端。
1.2.1 数据库层:最常见的“死穴”
你想想看,OA系统里最频繁的操作是什么?
查待办、查已办、查流程状态。这些全是数据库查询。
我在项目中遇到过最典型的问题:
一张 act_ru_task 表(流程引擎的待办任务表),数据量才50万行,但一个“查我的待办”SQL跑了3秒多。
为什么?
因为 WHERE assignee_ = ? 字段没建索引。而且流程引擎的ORM框架会生成N+1查询,一次请求下来,数据库要执行几十条SQL。
避坑指南:我曾经接手过一个OA系统,高峰期数据库CPU直接100%。一查,发现是“流程历史归档”任务和“用户查询”任务在抢数据库连接。后来我把归档任务挪到了凌晨执行,问题解决。记住:OLTP和OLAP不要混在一个连接池里。
1.2.2 应用层:线程池与锁的博弈
应用层的瓶颈,往往出在线程池配置和锁粒度上。
举个例子:
OA里有个“批量审批”功能,用户勾选10条待办,点一下“批量通过”。
如果代码里用 synchronized 锁住了整个审批方法,那并发一上来,所有线程都在排队等锁。明明有100个线程,实际只有1个在工作。
我建议的做法是:
用 分段锁 或者 乐观锁。比如按“流程实例ID”进行hash,把锁粒度降到最小。
// 错误示范:锁了整个方法
public synchronized void batchApprove(List<String> taskIds) {
// 逐个审批
}
// 正确示范:按任务ID分段锁
public void batchApprove(List<String> taskIds) {
taskIds.parallelStream().forEach(taskId -> {
String lockKey = "approve:lock:" + taskId;
// 使用Redis分布式锁,或者本地分段锁
boolean locked = redisLock.tryLock(lockKey, 5, TimeUnit.SECONDS);
if (locked) {
try {
approveSingle(taskId);
} finally {
redisLock.unlock(lockKey);
}
}
});
}
1.2.3 缓存层:用了缓存就万事大吉?
很多人觉得“加个Redis就快了”。
但缓存也有坑。我记得有一次,OA的“组织架构树”接口,缓存过期时间设了1小时。结果下午3点人事部门批量调整了部门,所有用户看到的还是旧数据。
这就是典型的缓存与数据库不一致问题。
我的经验是:
对于OA这种“读多写少”的场景,用 Cache-Aside 模式,并且写操作时主动失效缓存。如果对一致性要求极高,可以考虑 读写锁 或者 版本号机制。
1.3 架构演进路线图:从小单体到高可用集群
架构不是一步到位的。我见过太多团队一上来就上微服务、上K8s,结果业务还没跑起来,运维先崩溃了。
我个人习惯的演进路线是:先单体,再拆分,后弹性。
第一阶段:单体应用 + 读写分离(支撑1000人以内)
- 应用:单台Tomcat/Jetty,部署在4核8G的机器上
- 数据库:MySQL主从,读走从库,写走主库
- 缓存:本地缓存(Caffeine) + Redis(缓存热点数据)
- 静态资源:Nginx直接代理,开启gzip和强缓存
这个阶段,重点是把数据库的读压力卸掉。我一般会把“待办列表”、“公告列表”这种高频读接口,全部走缓存,命中率能做到90%以上。
第二阶段:应用集群 + 分布式会话(支撑3000-5000人)
- 应用:多台Tomcat,前面挂Nginx做负载均衡(轮询或IP hash)
- 会话:用Redis存储Session,解决多节点登录状态丢失问题
- 任务调度:引入XXL-Job或Elastic-Job,把定时任务(如流程归档、数据统计)剥离到独立节点
- 消息队列:引入RabbitMQ或RocketMQ,处理异步操作(如发送通知、日志记录)
小技巧:Nginx的负载均衡策略,我建议用 ip_hash。因为OA系统里用户登录后,Session信息虽然存到了Redis,但每次请求都去Redis查,还是有网络开销。用ip_hash可以让同一个用户的请求落到同一台机器上,减少Redis的访问次数。
第三阶段:微服务化 + 弹性伸缩(支撑万人以上)
- 拆分原则:按业务域拆分,比如“用户服务”、“流程服务”、“消息服务”、“文档服务”
- 服务治理:Nacos做注册中心,Sentinel做限流降级
- 数据库拆分:按租户分库,或者按业务分表(比如流程实例表按月份分表)
- 弹性伸缩:K8s + HPA,根据CPU和QPS自动扩缩容
这个阶段,最核心的是限流和降级。我见过一个案例,OA系统里的“导出Excel”功能,因为数据量太大,直接把整个服务的内存撑爆了。后来加了Sentinel限流,并发超过5个就排队,系统才稳定下来。
架构演进对比表
| 阶段 | 支撑规模 | 核心组件 | 典型瓶颈 |
|---|---|---|---|
| 单体+读写分离 | 1000人以内 | MySQL主从、Redis、Nginx | 数据库连接数、单点故障 |
| 集群+分布式会话 | 3000-5000人 | Nginx集群、Redis集群、MQ | 数据库写压力、任务堆积 |
| 微服务+弹性伸缩 | 万人以上 | K8s、Sentinel、分库分表 | 服务间调用链、数据一致性 |
小结
这一章,我们聊了OA系统高并发的三个核心问题:
场景——早高峰打卡、月末审批潮、突发公告;
瓶颈——数据库慢查询、锁竞争、缓存不一致;
路线——从单体到集群再到微服务,一步步演进。
说白了,架构设计没有银弹。你得根据业务规模、团队能力、成本预算,找到最适合当下的方案。
下一章,我会深入讲讲数据库层面的优化实战,包括索引设计、SQL改写、分库分表策略。到时候我会拿一个真实的“待办列表慢查询”案例来拆解,保证你听完就能用。
一句话总结:OA高并发,先识别场景,再定位瓶颈,最后按节奏演进。别一上来就搞微服务,先把单体优化到极致再说。