Skip to content

系统设计

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可能不增反降低。过多的线程意味着过多的内存开销和上下文切换。根据

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. 开发编码层面

  • 使用数据库连接池
  • 使用批量操作