如何思考问题与思考具体问题同等重要。
引子
如何思考一个技术问题?如何高效地理解某个技术 ?如何快速学习一个新的技术点 ? 这都需要一个好的技术思考框架。缺乏思考框架,知识和技术就变成一堆细节的堆砌,缺乏关联,难以理解和记忆,也就难以掌握和应用。
本文来探索一种好的技术思考框架。
技术思考框架
在多年的开发生涯及阅读和理解源码的过程中,我逐渐形成了一种个人觉得比较可行的技术思考框架:“抽象-思路-考量-优化-细节” 五步曲。
- 抽象:给用户提供怎样的抽象;
- 思路:如何去实现它,基本的依据、结构、算法与实现;
- 考量:工程应用上需要考虑哪些因素,做到安全可靠高质量;
- 优化:在考量的基础上,从多个层面去优化和完善;
- 细节:在优化的基础上,打磨细节,精益求精。
抽象
抽象非常重要。一个典型的例子是删除操作。删除操作提供的是“用户不可见”的抽象。可以采用两种思路:
- 物理删除:直接抹掉数据的所有痕迹,再也找不到了;
- 逻辑删除:没有抹掉数据,只是提供了一个标记,在用户查询时根据标记过滤掉这个数据。逻辑删除为数据恢复提供了前提。
可以看到,提供良好的抽象,而不是直觉上按照用户的要求去做,往往会带来更灵活的设计空间。
抽象最好能用一句话概括。比如,文字编辑器(UI界面)提供的是“所见即所得”的抽象;理财产品提供的是符合利率模型的计算抽象。
思路
思路是如何去基本实现的问题。在问题讨论初期,提出尽可能多种思路,应对不同的场景。比如排序,就有插入排序、冒泡排序、快速排序、合并排序、希尔排序、桶排序、外部排序、多关键字排序等。不同的排序具有不同的特性,在不同的场景下使用。挖掘尽可能多的思路,有助于拓展对问题的思考。好的思路是成功的一半。
考量和优化
考量和优化往往需要丰富的开发实践经验。知道往哪个方向使力,如何使力,需要解决什么技术难点等。
细节
在掌握宏观视角后,细节的打磨尤为重要。细节处理不当,可能会导致整体工作前功尽弃。程序员或工程师的一大特质,即是体现在细节的考虑和思维的缜密上。
考虑各种错误和异常,考虑依赖服务、服务器、网络等的不可靠,是做好细节工作的必要前提。
学习技术知识
以 Redis 持久化为例。
抽象
持久化,即是给用户提供一个“数据保存了、即使掉电或重启也能从某处找回数据”的抽象。
思路
如何实现 Redis 持久化 ? 持久化就是将 Redis 内存数据库的数据集状态保存在某个磁盘文件里。可以基于两种思路:
- 基于数据。直接将数据存储在(压缩过的)二进制磁盘文件里,恢复的时候读取数据文件即可,比如 RDB 机制;
- 基于命令。将 Redis 命令存储在磁盘文件里,恢复的时候执行文件里的命令即可,比如 AOF 机制。
考量
实际工程中要考虑什么因素呢 ?
- 避免数据丢失: 持久化过程若被中断,已持久化的数据不丢失;持久化期间,尽力保证不丢数据,或者丢数据在可接受范围内;
- 性能:持久化能更高效地完成;
- 稳定性:持久化的过程中,不能影响到 Redis 的正常服务;
- 存储空间: 持久化文件占用的空间应当尽可能少;
- 同步: 新增的数据及已持久化后的数据更新也要持久化;
- 恢复速度: 服务器启动或紧急情况需要恢复数据状态时,应当尽可能高效、便捷、减少不必要的坑;
- 可读性: 持久化文件可读,能够为人所理解;可解析,能够为机器高效读取;
- 可追溯: 当数据出现不一致时,或者不对劲时,可以通过追溯持久化文件来定位原因。
优化
针对上述考量因素,可制定相应优化措施:
- 异步:新起子进程在后台去做持久化的事情,而不占用服务进程,不阻塞服务的正常运行。比如 BGSAVE 命令;
- 缓冲区:在持久化的过程中,如果新增命令都直接写到持久化文件,势必会影响正常服务的性能。因此常用做法是,在持久化开启之后,新增的命令或数据会先写入到缓冲区,然后以某种策略来同步到持久化文件里。可以配置相应的策略,来实现服务性能和避免数据丢失的权衡;
- AOF 重写:将多条命令重写为一条命令,从而减少可执行命令数,减少 AOF 文件体积,提升恢复速度。读取键值的数据库当前数据状态,生成一条新的命令写入 AOF。为什么不直接写入键值呢 ? 因为要满足可追溯的需求。 AOF 重写后的文件用于恢复数据库状态, 而 AOF 的原文件可以考虑归档,便于在急需的时候定位原因;
- 二进制与文本文件:二进制文件机器解析更高效,兼容性更强,但人类可读性差; 文本文件可读,解析时可能会有坑,需要通过统一协议来避免。
细节
RDB 机制的细节:
- SAVE 会阻塞主服务进程,一般用于业务量低峰期; BGSAVE 会创建子进程去做持久化工作,不会阻塞;
- BGSAVE 执行期间,新的 SAVE, BGSAVE 会被拒绝;BGREWRITEOF 会在 BGSAVE 完成之后执行。避免并发操作磁盘引起性能波动或降低;
- save 命令可以配置多久执行一次 BGSAVE ; 可以配置多个 save 命令,任一满足都会执行 BGSAVE(或关系)。默认 [900, 1], [300, 10], [60, 10000]。实际配置值需要根据业务状态调优;
- save 的实现:[ dirty & lastsave ] 。dirty 记录了距离最近一次创建 RDB 文件快照之后的数据库状态修改次数; lastsave 记录了最近一次创建 RDB 文件快照的时间戳。 每隔 100ms 都会检测一次,save 条件是否满足。
AOF 机制的细节:
- appendfsync: 同步持久化文件的策略。 always 每次写缓冲区都同步到持久化文件,不会丢数据,但性能会受影响; everysec 每秒同步,可能会丢失最近 1s 内的数据,性能可以; no 性能最优,但会有较大的丢数据风险。 默认策略是 everysec;
- AOF 重写集合: 为了避免缓冲区溢出,重写集合时,可根据 REDIS_AOF_REWRITE_ITEMS_PER_CMD 配置,一个数量为 N 的很大的集合,会拆分为 ( N / REDIS_AOF_REWRITE_ITEMS_PER_CMD +1 ) 个重写命令;
- 应用启动时,默认加载 RDB 文件;恢复数据库状态时,优先使用 AOF 机制,若 AOF 关闭,则采用 RDB 机制。
思考技术问题
以高并发处理为例。如何应对高并发流量呢?这是互联网应用必然面对的一个重大挑战。
抽象
高并发流量问题,本质是资源有限的问题。即是用有限的资源去应对不确定性的巨大流量的矛盾。假设有足够的资源,加以适当的水平扩展,高并发也不算什么事。难在用现有的架构、现有的资源来应对巨大流量,而且无论预留多少资源,总是不一定能承载未来可能降临的超过负载能力的峰值流量。
思路
既然是资源有限的问题,那么思路也就三条:
- 用更少资源做更多的事情,提升服务能力,提升 QPS;
- 加资源;
- 限流。
考量
- 稳定性:高并发流量下,首先要考虑的是保证应用不会被超出负荷能力的流量压垮;
- 保障正常服务:对于负荷范围内的正常流量,能够正常服务,正常响应,正常的服务体验。不会因为高负荷导致服务处理能力变差,连正常流量的请求都无法有效应对,或者用户体验变差。
优化
提升服务能力:
-
用好缓存:可以提升 QPS ,但依然无法阻挡高并发流量;
-
熔断降级:高并发情况下,自动熔断次要的链路,提升 QPS;
-
读写分离: 将高并发流量中的读写部分分开,分别提升读写能力;
-
分库分表: 提升 DB 的并发处理能力。
加资源(在不同层面):
-
加资源的同时做好负载均衡(分流作用)。通过负载均衡进行分流,一部分流量直接进入服务器,一部分流量走消息队列再走服务。负载均衡可采用七层的 ngnix 和 四层的 LVS 或 F5。负载均衡能抗住大流量;
-
多机房 + CDN:多机房 + CDN + 负载均衡,分别抗住来自不同地区的大流量。
限流: 仍然无法承载的流量,采用限流处理掉。
细节
- 资源消耗:一个请求会消耗多少资源?CPU、内存、磁盘空间、网络连接及带宽、DB 连接等;
- 如何将缓存用到极致?需要对业务数据有敏感度,采取适当的策略,不断调优,密切注意监控;高并发场景下,要防止缓存穿透/雪崩/击穿问题;
- 熔断降级的策略?熔断配置的设定与调优;
- 在哪些层面限流?限流值如何确定?
- 读库的复制时延如何解决?
- 分库分表的切换过程中的新数据如何做到不丢失?
- 如何部署负载均衡和多机房?需要网络运维能力。
高并发实际上是一个实战性很强的事情。没有经过高并发流量的洗礼,只谈理论终究还是隔着点。不过,至少要了解相关理论,才能在实战中积极运用并积累经验。
技术体系结构
从上述示例可以看到:
- 抽象可以提供更灵活的设计空间和思路;
- 考量因素往往会决定优化的大方向;
- 优化往往需要通盘综合考虑,且要有实操经验;
- 细节在优化的过程中产生和解决。
抽象、考量和优化,需要建基于一个比较丰富的技术体系结构。为什么考虑这个问题而不考虑那个问题?为什么选择优化这个而不是优化那个?良好的技术体系结构可以串联起所有的知识、技术、经验,使其关联清晰可见,并提供强有力的理论指导。
因此,打造一个适合自己的技术体系结构非常重要,这也是未来的核心竞争力之一。比如 “互联网应用服务端的常用技术思想与机制纲要” 即是围绕“数据处理”这个中心主题,从灵活性、高性能、高可用、高可靠、一致性、海量、安全、自动化、智能化八个方面打造的技术体系结构。这个体系结构几乎可以容纳各种技术流派和技术优化手段。
小结
本文探讨了一种技术思考框架:基于技术体系结构的“抽象-思路-考量-优化-细节”五步曲。
合适的抽象会带来更灵活的设计空间和思路,而考量则会决定优化的大方向,在优化的过程中解决细节问题。在学习技术的过程中,要逐步打造适合自己的技术体系结构,不断充实这个体系结构,从而具备更强的系统思考力和技术吸收力。