--- title: 第五篇:项目亮点!DDD、系统架构、分库分表、高性能、吞吐量 pay: https://articles.zsxq.com/id_lohhr6h38w2w.html --- # 项目亮点!DDD、系统架构、分库分表、高性能、吞吐量 > 作者:Tom哥 >
公众号:微观技术 >
博客:[https://offercome.cn](https://offercome.cn) >
人生理念:知道的越多,不知道的越多,努力去学 面试官拿到我们的简历,一般会关注两块内容,一块是专业技能,另一块是项目经历。 简单的个人介绍后,一般会先问些偏基础的技术问题,热热身。当然也有很多面试官上来就顺着项目问。根据你介绍项目的过程细节,穿插设置一系列的技术问题。 面试官一般会关注一些有挑战性的方案设计、解决了什么复杂难题,简单一句话,就是你的项目一定要有亮点。 那么,什么是亮点?我们的项目如何积累这些亮点? 下面我们会介绍项目中一些高频亮点设计,我们平时做项目,设计技术方案时也可以多用用,积累些实战经验 。 ## 面对复杂业务,架构设计有什么通用思路? 答案: 业务理解转化能力、思维抽象能力、软件建模能力、高并发、高性能、高可用的分布式系统架构设计能力。 1、“拆分” ,降低架构复杂度。 2、认知抽象,架构模式有通用性 3、平台化、中台化系统衍化建设。 详细内容请查看 [人人都是架构师???谈何容易!!](https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjQzNg==&mid=2247484929&idx=1&sn=d8cb3306dea9f1b92fd30d59da3f536a&scene=21#wechat_redirect) ## 谈谈对 DDD 的理解? 答案: 通过实体、值对象、聚合根、领域服务、领域对象、限界上下文、资源库,指导微服务落地,将一个大的复杂业务域拆分成若干业务子域。定义领域模型(包含数据、行为),相似业务聚合。 更多内容,参考 [DDD是如何解决复杂业务扩展问题?](https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjQzNg==&mid=2247484859&idx=1&sn=bcf2167350e473d9fde2f03fe812086b&scene=21#wechat_redirect) ## 项目中用过哪些设计模式? 答案: 工厂、装饰、克隆、代理、适配器、观察者、策略、模板、单例、责任链、门面等23种软件设计模式,这是软件开发的基本功,每一种设计模式都要非常熟悉。否则很难写出扩展性很高的代码。 之前写过三篇文章,每一种模式都有详细介绍: [1、解决复杂业务架构,软件设计模式系列(第一期)](https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjQzNg==&mid=2247485170&idx=1&sn=b97e57fcc0af6101531b022e578bc64f&scene=21#wechat_redirect) [2、解决复杂业务架构,软件设计模式系列(第二期)](https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjQzNg==&mid=2247485309&idx=1&sn=1209b7e3324440f7893847a935b3af5f&scene=21#wechat_redirect) [3、解决复杂业务架构,软件设计模式系列(第三期)](https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjQzNg==&mid=2247485697&idx=1&sn=d0a3fd3fe1d5d70eb576d69d1d5a8399&scene=21#wechat_redirect) ## 面对海量数据,什么是垂直拆分、水平拆分? 答案: 1、垂直拆分可以分为垂直分库、垂直分表 - **垂直分库**:结合DDD领域驱动设计,将一个大的业务域拆分为若干业务子域,比如电商可以拆分为商户、商品、库存、权限、会员、营销、交易、支付、履约、订单、结算、仓储、物流、财务等。每个子域都有自己独立的数据库。 - **垂直分表**:将一个有很多字段的表,按字段的大小、使用频率等特点,拆分为多张表。比如:将用户表拆分为 用户基本信息表 和 用户扩展表。 2、水平拆分 由于单台机器的性能有限,无法支撑海量数据存储。我们引入逻辑表概念,采用集群模式,将一张逻辑表拆分成多张物理表分散存储在不同服务器,通过分表键路由,比如:时间、区域、用户id等。 特点:虽然有多张表,但每张表的表结构都是一样的,区别是数据不一样。所有表的数据合并起来才是这个业务表的完整数据。 > 画外音:数据量大,就分表;并发高,就分库 更多内容,参考 [单台 MySQL 支撑不了这么多的并发请求,我们该怎么办?](https://mp.weixin.qq.com/s?__biz=Mzg2NzYyNjQzNg==&mid=2247484864&idx=1&sn=e25652505319d4d13dcf5fc8e265bcae&scene=21#wechat_redirect) ## 分库分表,全局主键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,你真的会用吗?](https://mp.weixin.qq.com/s/nmT-LCOyChTmbON3bCMoiA) 19. show profile分析,了解SQL执行的线程的状态以及消耗的时间 20. 数据量太大,考虑分库分表 或者借助 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. 锁粒度。为了解决多线程并发修改某个共享数据,会引入`锁`,如:`synchronized`、`ReentrantLock` 、`Redis 分布式锁` 等。如果加锁的粒度过粗,影响接口的性能。只需要在`共享临界资源`加锁即可,不涉及共享资源的,就不必要加锁。 10. 缓存。`性能不够,缓存来凑`。借助 本地缓存 `Guava`、`Caffeine` 等;分布式缓存 `Redis` 、`memcached`等 来加速。两者通常可以互补,本地缓存没有网络开销,但受内存大小限制;分布式缓存能支持更大的容量上限,扩容更方便,但有约近似 1 ms 的网络开销。 - 本地缓存和分布式缓存可以一起使用,但要注意`数据一致性`问题 11. 预热思想。提前把要经过复杂计算的数据计算好,并提前预热到缓存中,需要时,直接去缓存取即可。 - 像一些`双 11`大促活动,一般会将各种热点数据通过`定时任务`提前预热 12. 数据异构。将一些高频信息,经过计算或转换处理后,存储到缓存中,下次可以直接使用,避免每次查库 13. 索引优化。不管什么业务都涉及到数据库存储,SQL 索引优化是关键点,如:`有没有加索引`、`加了索引有没有生效`、`索引建立是否合理` 等。 - 写完 SQL 后,可以通过 `explain` 查看执行计划 - 前文「SQL优化,有哪些方案?」列举了详细优化方案 14. 开启数据库的慢查询日志,有针对性的收集`慢 SQL`,并优化 15. 避免`大事务`。事务中避免嵌套 `RPC` 远程调用 16. 深度分页。会扫描太多的数据行,可以考虑`标签记录法`,根据`主键id`快速定位、查找 17. 分库分表。数据量大时,可以分库分表 18. 数据压缩。如果涉及网络传输,选型合适的序列化框架,并对内容压缩 19. 优化程序结构。比如,你的程序创建多不必要的对象、或者程序逻辑混乱,多次重复查数据库、又或者你的实现逻辑算法不是最高效的,等等。 20. 数据过期策略。一张表的数据量太大的情况下,对DB的查询性能是非常有影响的,建议合理的设计数据过期策略,历史数据定期放入history表,或者备份到离线表中,减少线上大量数据的存储。 21. 技术方案 - 如果数据太大,可以先采用`文件/MQ` 等暂存数据,后面再慢慢保存到数据库中 22. NoSQL。如果数据量太多,可以考虑引入 NoSQL,比如 Hbase、Elasticsearch 等 23. 增加监控,比如 :`Prometheus`,将一些慢响应接口、或者异常太多接口收集起来,用于`监控`、`报警`。我们可以采集的信息: - 接口响应时间 - 调用第三方服务耗时 - 慢查询sql耗时 - cpu使用情况 - 内存使用情况 - 磁盘使用情况 - 数据库使用情况 24. 也可以引入类似 `skywalking` 这样的分布式链路跟踪系统,串联一个接口请求的完整链路。支持查看链路的各个环节的耗时,指导我们优化系统。 25. 与业务保持沟通,找到平衡点。技术不是万能的,无论白猫黑猫,能抓到老鼠就是好猫