系统设计
1. 唯一订单号如何设计实现
- 数据库自增
数据量泄露风险
数据库写压力大,故障后不可用 并发性不好 - UUID 生成
太长,不易于存储;无序查询效率低 - snowflake 雪花算法 bitint(64bit)作为 id 生成类型,划分多段
- Redis生成
适用生成流水号,例如:订单号=日期+当日自增长号,每天在 Redis 中生成一个 Key,使用 INCR 进行累加;
集群可使用多个 key,如三个主从 key1 初始化1,key2 初始化2,key3 初始化3,都以步长为3进行 INCR 操作,随机到某一主从,集群分摊压力
2. 如何理解高并发
2.1. 并发与并行
- 并行:系统可以同时处理多个任务。同时进行,不抢占 cpu,不需要线程切换
- 并发:系统在一段时间内可以处理多个任务。短时间内 cpu 交替执行,处理多个任务
2.2. 提高并发能力
2.2.1. 水平扩展
分布式,增加服务器数量
- 反向代理层使用 DNS 轮询进行水平扩展
- 站点使用 nginx 进行水平扩展
- 服务层使用服务连接池进行水平扩展
- 数据库使用哈希的方式进行水平扩展
2.2.2. 垂直扩展
- 增强单机性能
- 提升单机架构性能,如使用缓存、异步和无锁结构减少响应时间
2.3. 一些注意的点
最佳线程数
线程池的线程数量不是越多越好,在性能压测中,我们经常能看到随着并发(线程数)的增加,QPS也会随着增加,但到了一定的阈值之后,QPS可能不增反降低。过多的线程意味着过多的内存开销和上下文切换。根据
当响应时间由于CPU开销大于1的时候,QPS就会增加变缓甚至下降。所以做性能压测的意义是根据最大的QPS找到最佳的线程数和找出其他影响性能的问题。
一般情况下,服务器端最佳线程数量=((线程等待时间+线程cpu时间)/线程cpu时间) * cpu数量
设置合理的堆空间
不是把服务器大部分的内存都分给堆就是好事,除了Java虚拟机本身内存, 应用还会用到一些本地内存,比如创建线程的时候,JVM没回创建一个Thread对象,同时创建一个操作系统线程,系统线程用的是系统的内存。
如果给 JVM 分配的内存越多,那么能用来创建系统线程的内存就会越少,越容易发生 unable to create new native thread
。所以,JVM 内存不是分配的越大越好。
3. CPU 占用高如何排查
- top 命令看占用高的进程pid,free 查看内存占用,dh 查看硬盘占用
- jstack 查看线程堆栈信息,分析线程状态。查看是否有堵塞死锁情况。jstack pid > jstack.log
- 可以再找到线程 tid。jstack pid |grep tid 查看具体线程的堆栈信息
- jstat 查看 GC 日志,看看 GC 是否频繁,是否有 Full GC 情况
- jmap -dump:format=b,file=heap.hprof pid 保存堆现场。用工具分析,如 JVisualVM、MAT、JProfiler等
4. OOM 如何排查
a.堆OOM,调整 -Xmx 大小;
b.本地内存 OOM,压测线程创建过多占满内存,减少线程池最大线程数,减小堆内存。
c.扩展:栈溢出(比如递归);
- 线程请求深度大于虚拟机深度,StackOverflowError
- 虚拟机扩展时无法申请到足够内存,OutOfMemoryError
5. 内存泄漏可能表现情况
如果具有满足一下两个条件的对象:
1)对象是可达的。即在有向图中,存在通过达到该对象,GC 不会回收。
2)对象的无用的。即程序以后不会再使用这些对象。
那么这些对象是无有,但是占用着内存空间,并且不会被 GC 回收这就是所谓的内存泄漏。
如果内存泄漏非常严重的话,最终会导致内存溢出。
- OOM 异常
- 内存持续上升
- 频繁 Full GC
6. 数据库调优
6.1. 服务器层面优化,参数调整
- 根据应用接入设置最大链接数量,一般设个3000
- innodb_buffer_pool_size 缓冲区大小,设为系统内存约80%
- 开启慢查询日志,便于定位和监控慢查询 sql
6.2. 数据库设计层面,表设计和结构选择
- 表设计字段的类型选择,长度设置,适当冗余部分字段减少关联表查询
- 使用读写分离
- 分表分库
- 根据业务创建合理的索引
- 数据唯一性差的字段不适合建索引
- 频繁更新的字段不适合建索引
- 不在 where 后面作为查询条件的不加索引
6.3. 数据库执行层面,优化 sql 语句,合理使用索引
- 尽量使用主键查询
- 尽量使用单表查询而不是关联表
- 优化 sql 语句,使用 explain 查看执行计划并分析
- 只需要少量字段时候不用 select *
- 关联语句一般是小表关联大表
- 防止索引失效
- 索引遵循最左匹配原则,只有在查询条件中使用了创建索引时的第一个字段, 索引才会被使用,使用复合索引时遵循最左前准集合
- <>/ is NULL/ is NOT NULL /Like 等关键字会使索引失效
- or 要使用索引必循每个条件字段都有索引
- where 子句索引列上有使用函数运算,索引也会失效
6.4. 系统架构层面增强补充
- Redis
- Memcached
6.5. 开发编码层面
- 使用数据库连接池
- 使用批量操作