# 项目亮点!DDD、系统架构、分库分表、高性能、吞吐量

作者:Tom哥
公众号:微观技术
博客:https://offercome.cn (opens new window)
人生理念:知道的越多,不知道的越多,努力去学

面试官拿到我们的简历,一般会关注两块内容,一块是专业技能,另一块是项目经历。

简单的个人介绍后,一般会先问些偏基础的技术问题,热热身。当然也有很多面试官上来就顺着项目问。根据你介绍项目的过程细节,穿插设置一系列的技术问题。

面试官一般会关注一些有挑战性的方案设计、解决了什么复杂难题,简单一句话,就是你的项目一定要有亮点。

那么,什么是亮点?我们的项目如何积累这些亮点?

下面我们会介绍项目中一些高频亮点设计,我们平时做项目,设计技术方案时也可以多用用,积累些实战经验 。

# 面对复杂业务,架构设计有什么通用思路?

答案: 业务理解转化能力、思维抽象能力、软件建模能力、高并发、高性能、高可用的分布式系统架构设计能力。

1、“拆分” ,降低架构复杂度。

2、认知抽象,架构模式有通用性

3、平台化、中台化系统衍化建设。

详细内容请查看 人人都是架构师???谈何容易!! (opens new window)

# 谈谈对 DDD 的理解?

答案:

通过实体、值对象、聚合根、领域服务、领域对象、限界上下文、资源库,指导微服务落地,将一个大的复杂业务域拆分成若干业务子域。定义领域模型(包含数据、行为),相似业务聚合。

更多内容,参考 DDD是如何解决复杂业务扩展问题? (opens new window)

# 项目中用过哪些设计模式?

答案:

工厂、装饰、克隆、代理、适配器、观察者、策略、模板、单例、责任链、门面等23种软件设计模式,这是软件开发的基本功,每一种设计模式都要非常熟悉。否则很难写出扩展性很高的代码。

之前写过三篇文章,每一种模式都有详细介绍:

1、解决复杂业务架构,软件设计模式系列(第一期) (opens new window)

2、解决复杂业务架构,软件设计模式系列(第二期) (opens new window)

3、解决复杂业务架构,软件设计模式系列(第三期) (opens new window)

# 面对海量数据,什么是垂直拆分、水平拆分?

答案:

1、垂直拆分可以分为垂直分库、垂直分表

  • 垂直分库:结合DDD领域驱动设计,将一个大的业务域拆分为若干业务子域,比如电商可以拆分为商户、商品、库存、权限、会员、营销、交易、支付、履约、订单、结算、仓储、物流、财务等。每个子域都有自己独立的数据库。
  • 垂直分表:将一个有很多字段的表,按字段的大小、使用频率等特点,拆分为多张表。比如:将用户表拆分为 用户基本信息表 和 用户扩展表。

2、水平拆分

由于单台机器的性能有限,无法支撑海量数据存储。我们引入逻辑表概念,采用集群模式,将一张逻辑表拆分成多张物理表分散存储在不同服务器,通过分表键路由,比如:时间、区域、用户id等。

特点:虽然有多张表,但每张表的表结构都是一样的,区别是数据不一样。所有表的数据合并起来才是这个业务表的完整数据。

画外音:数据量大,就分表;并发高,就分库

更多内容,参考 单台 MySQL 支撑不了这么多的并发请求,我们该怎么办? (opens new window)

# 分库分表,全局主键ID有哪些生成方案?

答案:

  • 1、UUID,生成的是 32 位的字符串,虽然可以做到全局唯一性,但我们一般推荐使用整型。
  • 2、基于一个单表做自增主键
  • 3、雪花算法,生成一个 64 位的 Long 类型数据。组成结构:正数位(占1位)+ 时间戳(占41位)+ 工作机器id(10位)+ 序列号部分(12位)
  • 4、数据库号段模式,对不同的业务类型定义初始值和步长,业务系统引入SDK,本地缓存预申请一定数据量的主键ID值,满足一定的并发要求。
  • 5、TinyID,滴滴的开源框架
  • 6、Redis 的 incr 命令
  • 7、Leaf,美团的开源框架
  • 8、uid-generator,百度的开源框架

# SQL 优化,有哪些方案?

答案:

  1. SQL 查询时,尽量不要使用 select * ,而是 select 具体字段
  2. 如果只有一条查询结果(或者最大值、最小值),建议使用 limit 1
  3. where 语句中尽量避免使用 or来连接条件。or 可能会导致索引失效,从而全表扫描
  4. 优化 limit 分页
  5. 优化 like 语句,不要把 % 放到前面
  6. where 语句的条件字段要充分,查询的数据都是有用的,避免在上层的业务代码中做 filter
  7. 避免在 索引列使用mysql 内置函数、表达式操作,会导致索引失效
  8. 优先使用 inner join,如果使用 left join 要小表驱动大表
  9. 尽量避免在 where 语句中使用 != 、<>
  10. 使用联合索引时,要注意索引列的顺序
  11. where 、 order by 涉及的列上建索引,避免全表扫描
  12. 尽量采用 覆盖索引,减少回表
  13. 如果检索的结果不会有重复的记录,建议使用 union all 替换 union
  14. 索引不宜太多,一般控制在 5个以内
  15. 索引尽量避免建在有大量重复数据的字段上,如:性别
  16. 尽量使用 varchar 代替 char
  17. 如果字段类型是字符串,where 时一定要用引号括起来,否则会索引失效
  18. 万能方案,使用 MySQL 自带的诊断命令 explain 分析优化 SQL

MySQL的explain,你真的会用吗? (opens new window)

  1. show profile分析,了解SQL执行的线程的状态以及消耗的时间
  2. 数据量太大,考虑分库分表 或者借助 es 查询

# 高性能,有哪些方案?

答案:

  1. 流量入口采用LVS、Nginx, 通过负载均衡算法,将大流量压力均匀、平稳的分摊到下游的多台微服务机器上,从而减轻单台服务器的压力。
  2. 性能不够,缓存来凑。引入多级缓存,如:Guava、caffeine 本地缓存、Redis 分布式缓存。但是要注意缓存key集中失效、穿透、雪崩、热点、大 key、缓存数据一致性、并发更新等问题。
  3. 对于前端的一些 JS、CSS、图片等静态文件,可以借助 CDN 加速
  4. 分库分表。关系型数据库存储,如果数据量过大可以分表,如果要提高吞吐量可以分库。当然一些复杂的查询可以借助 ES 来实现
  5. DB 索引设计。设计表结构时,我们要考虑后期对表数据的查询操作,设计合理的索引结构,一旦表索引建立好了之后,也要注意后续的查询操作,避免索引失效。
  6. 数据库的SQL 索引优化
  7. 对于一些海量数据的存储与查询,可以考虑 NoSQL,如:Hbase、MongoDB、TiDB等
  8. 异步化。梳理业务流程,非核心逻辑可以异步化处理,如:线程池、MQ、延迟任务等
  9. 并行化。梳理业务流程,画出时序图,分清楚哪些是串行?哪些是并行?充分利用多核CPU的并行化处理能力
  10. 引入 MQ 中间件,对大流量削峰填谷,借助 MQ 中间件对下游系统起到缓冲作用。
  11. 缓存预热,如:大促秒杀,提前将热点数据预热到缓存中
  12. 预计算,如:抢红包场景,可以提前计算好红包金额缓存起来,发红包时直接使用即可。
  13. 减少 IO 次数。如:数据库和缓存的批量读写、RPC的批量接口调用、或者通过冗余数据的方式减少 RPC 调用。索引/分布式计算代替全表扫描、零拷贝减少IO复制次数、分库分表增加连接数
  14. 减少 IO 数据包大小。如:采用轻量级的通信协议、合适的数据结构(比如 PB 协议)、去掉接口中的多余字段、减少缓存key的大小、压缩缓存value等。
  15. 加快IO速度。顺序读写代替随机读写、硬件上SSD提升等;
  16. 各种池化技术。如:HTTP请求池、线程池(考虑CPU密集型还是IO密集型设置核心参数)、数据库和Redis连接池等。
  17. 代码逻辑优化。将一些多次查询的结果通过 Context 上下文传递,或者采用更高效的算法或者类
  18. JVM 优化。如:新生代和老年代的大小、GC算法的选择等,尽可能减少GC频率和耗时。
  19. 锁选择。如:读多写少的场景用乐观锁,或者考虑通过分段锁的方式减少锁冲突。

# 高可用,有哪些方案?

答案:

  1. 限流。保证系统最大可用性,如:秒杀。包括前端限流、Nginx接入层的限流、服务端的限流。
  2. 熔断策略。对于网络不稳定,可以自动将接口熔断,并按配置的恢复时间检查恢复
  3. 降级。临时关闭一些非核心业务,释放更多的系统资源让给核心业务。
  4. 接口设置超时配置,防止慢请求拖垮系统,引发雪崩效应。
  5. 接口重试、幂等策略
  6. 故障转移。如果一个节点挂了,会自动将流量切到对等节点。如:Nginx、MySQL、Redis 都具备这种能力。
  7. MQ场景的消息可靠性保证,如:producer端的重试机制、broker侧的持久化、consumer端的ack机制等。
  8. 灰度发布,先小流量部署,观察系统日志和业务指标,等运行平稳后再推全量。
  9. 监控报警:全方位的监控体系,包括最基础的CPU、内存、磁盘、网络的监控,以及Web服务器、JVM、数据库、各类中间件的监控和业务指标的监控。
  10. 灾备演练:类似当前的“混沌工程”,对系统进行一些破坏性手段,观察局部故障是否会引起可用性问题。

# 接口性能优化,有哪些技巧?

答案:

  1. 优化代码逻辑,如:将一些无关联的操作并行化处理;多次查询,可以考虑采用Context上下文传递
  2. 串行改为并行。对于没有上下文依赖的接口调用采用并行化处理,JDK 里面提供了 CompleteFuture 可以实现该功能。
  3. 批量思想。读写一样批量操作。比如:
    • 连续调用多次的单个查询,我们可以考虑合并请求,开批量接口。将一个 100次 循环的单个查询替换成一个支持 100 个id的批量查询。这里要特别说明一点,集合的大小要做限制,建议控制每次请求的记录条数在500以内。
    • 写操作也是一样道理,可以批量处理。
  4. 池化思想。如:线程池、对象池、数据库连接池、HttpClient 连接池、HTTP 的 Keep-Alive 长连接 等,避免资源频繁的创建和销毁,可以循环使用。
  5. 线程池合理设计。线程池可以让任务并行处理,如果参数不合理,影响执行效率。重点关注几个参数:核心线程数最大线程数阻塞队列
  6. 分批查询。避免一次查询太过数据,如果是远程接口可能导致接口超时,要做分页处理。将一次获取所有的数据的请求,改成分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。
  7. 异步处理。将一些非核心的业务逻辑从同步执行中剥离出去,比如:发短信、邮件等,异步化来进行处理。有两种实现方式:
    • 封装成 Runnable 任务,交由线程池异步处理
    • 封装成 MQ 消息,借助 主流的 MQ 框架通过 发布/订阅 方式处理
  8. 事件回调。如果被调用接口耗时很长,不要一直阻塞等待,可以参考IO多路复用模型,先去做别的事,等被调用接口处理完,通过事件回调,触发我们之前埋入的回调函数
  9. 锁粒度。为了解决多线程并发修改某个共享数据,会引入,如:synchronizedReentrantLockRedis 分布式锁 等。如果加锁的粒度过粗,影响接口的性能。只需要在共享临界资源加锁即可,不涉及共享资源的,就不必要加锁。
  10. 缓存。性能不够,缓存来凑。借助 本地缓存 GuavaCaffeine 等;分布式缓存 Redismemcached等 来加速。两者通常可以互补,本地缓存没有网络开销,但受内存大小限制;分布式缓存能支持更大的容量上限,扩容更方便,但有约近似 1 ms 的网络开销。
  • 本地缓存和分布式缓存可以一起使用,但要注意数据一致性问题
  1. 预热思想。提前把要经过复杂计算的数据计算好,并提前预热到缓存中,需要时,直接去缓存取即可。
  • 像一些双 11大促活动,一般会将各种热点数据通过定时任务提前预热
  1. 数据异构。将一些高频信息,经过计算或转换处理后,存储到缓存中,下次可以直接使用,避免每次查库
  2. 索引优化。不管什么业务都涉及到数据库存储,SQL 索引优化是关键点,如:有没有加索引加了索引有没有生效索引建立是否合理 等。
  • 写完 SQL 后,可以通过 explain 查看执行计划
  • 前文「SQL优化,有哪些方案?」列举了详细优化方案
  1. 开启数据库的慢查询日志,有针对性的收集慢 SQL,并优化
  2. 避免大事务。事务中避免嵌套 RPC 远程调用
  3. 深度分页。会扫描太多的数据行,可以考虑标签记录法,根据主键id快速定位、查找
  4. 分库分表。数据量大时,可以分库分表
  5. 数据压缩。如果涉及网络传输,选型合适的序列化框架,并对内容压缩
  6. 优化程序结构。比如,你的程序创建多不必要的对象、或者程序逻辑混乱,多次重复查数据库、又或者你的实现逻辑算法不是最高效的,等等。
  7. 数据过期策略。一张表的数据量太大的情况下,对DB的查询性能是非常有影响的,建议合理的设计数据过期策略,历史数据定期放入history表,或者备份到离线表中,减少线上大量数据的存储。
  8. 技术方案
  • 如果数据太大,可以先采用文件/MQ 等暂存数据,后面再慢慢保存到数据库中
  1. NoSQL。如果数据量太多,可以考虑引入 NoSQL,比如 Hbase、Elasticsearch 等
  2. 增加监控,比如 :Prometheus,将一些慢响应接口、或者异常太多接口收集起来,用于监控报警。我们可以采集的信息:
  • 接口响应时间
  • 调用第三方服务耗时
  • 慢查询sql耗时
  • cpu使用情况
  • 内存使用情况
  • 磁盘使用情况
  • 数据库使用情况
  1. 也可以引入类似 skywalking 这样的分布式链路跟踪系统,串联一个接口请求的完整链路。支持查看链路的各个环节的耗时,指导我们优化系统。
  2. 与业务保持沟通,找到平衡点。技术不是万能的,无论白猫黑猫,能抓到老鼠就是好猫
上次更新: 2023/3/19