后端好书阅读与推荐续七

52次阅读

共计 9434 个字符,预计需要花费 24 分钟才能阅读完成。

后端好书阅读与推荐系列文章:
后端好书阅读与推荐
后端好书阅读与推荐(续)
后端好书阅读与推荐(续二)
后端好书阅读与推荐(续三)
后端好书阅读与推荐(续四)
后端好书阅读与推荐(续五)
后端好书阅读与推荐(续六)
后端好书阅读与推荐(续七)

Spring 微服务实战

Spring 微服务实战 (豆瓣): https://book.douban.com/subje…

Spring 体系用来做微服务在当下可谓是风头正劲,这本书就用一个实例来手把手教我们实现微服务。实战系列的口碑一直不错,这本也不例外,看完就可以对微服务的概念有一个完整的理解,并且想上手也有路可寻。

亮点:

  • 编码就像艺术,是一种创造性活动。构建分布式应用就像指挥一个管弦乐队演奏音乐,是一件令人惊奇的事情
  • 微服务是一个小的、松耦合的分布式服务,分解和分离 大型复杂 应用程序的功能,使它们独立,这样负责每个功能的团队都拥有自己的代码库和基础设施,技术不限,能灵活地 独立开发部署 ,职责分离,降低团队协作成本。随着云的普及,微服务单元的增减(每个服务单元都是完整的)变得非常容易,使得整个应用更具 弹性伸缩 能力。Spring勇于自我革新,当初出场取代了沉重的 J2EE,后面的 Spring Boot 使用简单注解去掉了自己原本繁重的配置,后来的 Spring Cloud 更是为分布式服务提供了一套完整的解决方案,所以 Spring 体系是我们构建微服务的绝佳选择
  • 微服务构建的一个关键是划分,而划分的一个关键是粒度,这个没有绝对标准,但是有几个 原则 :开始可以让服务设计范围大一些,后续可以重构至更小的服务;重点关注服务之间如何交互;随着对问题域的理解变化,服务的职责也要变化(演化思维)。需要注意微服务的几个 坏味道(太粗;太细):职责过多,跨表超过 5 个,URL 太长,测试用例太多;数量巨大、彼此依赖严重、成为简单的 curd、只在一个表操作等
  • 微服务没有标准,但是作者提出了 12 种最佳实践:独立代码库、显式依赖、配置存储、后端可切换、构建发布运行必须完整、进程无状态、端口命令行制定、横向扩展并发、可 down 可 up、环境一致、日志流式处理、管理脚本化。微服务的生命周期:装配、引导、发现、服务和监控、关闭
  • 少量程序可以使用低层级文件(YAML、json、XML)来存储配置,将其与代码分开,但是到了数百单元(每个单元可能有多个实例)时就不行了。手动管理既慢又复杂还可能配置漂移,这时应该引入配置管理工具(etcd、eureka、consul、zookeeper、spring cloud config 等),服务启动时通过环境变量注入配置或者从集中式存储中获取
  • 服务发现提供了快速水平伸缩的能力,且当服务不健康时可以快速删除,还能取代传统的手动管理负载均衡。主要涉及服务注册、客户端服务地址查找、信息共享、健康监测 4 个方面
  • 一般大家关注高可用都是某个组件彻底不可用(容易检测)的情况,但是一个性能不佳而没有完全垮掉(不易检测)的服务也可以彻底拖垮整个应用,因此也需要保护这些不佳服务,避免连锁效应,导致彻底不可用。一般有客户端负载均衡(Ribbon)、断路器、后备、舱壁(Hystrix)等四个弹性模式来实现保护缓冲区
  • AOP 的思想帮我们分离关注点,那么要在微服务中实现靠啥?答案就是网关(比如 Zuul,核心就是反向代理)了,我们可以在网关中实现验证授权、数据搜集与日志等关注点,但是要注意,避免网关成为单点要注意使其 轻量且无状态 (无状态就可以很容易扩展,而服务发现必须 有状态 ,所以要扩展还要用 Gossip 等协议来同步状态数据,保障 一致性和可用性
  • 安全注意事项:都要使用 HTTPSSSL、所有服务都要经过网关、将服务划分为公共 API 和私有 API、封锁不必要的端口来限制微服务的攻击面
  • 微服务不是单体,其好处是灵活,代价就是解决问题时难以定位,所以需要追踪并聚合日志,最终定位问题。spring cloud 给每一个事务开启之前注入关联 ID(一般由网关完成),每个服务都可以传播关联 ID 并将其添加进日志中,这些日志被统一发送到日志聚合点中,就可以供我们统一检索了,常见实现有 ELK、Splunk 等。光能检索还不够,一张直观的事务流图抵过 1 万条日志,Sleuth 和 ZipKin 一起可以实现该功能
  • 微服务强调灵活迅速,所以一个成熟的构建与部署管道(引入 CI/CD)必不可少:提交代码 、自动构建(钩子实现)、构建期间进行 单元测试 集成测试 后获得一个 jar(自包含 tomcat)、用 jar 构建并 提交机器镜像 、在新环境中拉取机器镜像并进行 平台测试 后启动镜像、每个新环境都要重复前面一个步骤

书很厚,所以很多具体工具可以跳过,尝试几个即可,将来使用的时候知道这本书里有就行了。

持续交付

持续交付 (豆瓣): https://book.douban.com/subje…

微服务离不开 CI/CD,而 CI/CD 核心就是几点:自动化、连续、小范围、快速、可靠。我们通过这本书来了解 CI/CD,也看看持续交付是如何解决“Last Mile”问题,让软件交付不再令人紧张,成为一件简单平常的事情。

亮点:

  • 从决定修改到结果上线的时间为周期时间,CI/CD 技术能让周期从传统手段的周月单位变成小时甚至分钟级别(产品快速落地,降低机会成本),发布过程可靠可重复(减少错误与人力资源),核心技术就是 部署流水线 (一个应用从构建、部署、测试到发布这整个过程的 自动化实现 ,过程中需要的所有东西包括需求、源码、配置、脚本、文档、运行环境等都要纳入VCS 的管理,但是结果性的东西比如二进制镜像就不用,因为它可以随时构建得到,作者罗列了一些相应的工具)
  • 提出了一些反模式,让我们避免:手工部署软件(复杂 不可重复和审计 易出错)、开发完成之后才向类生产环境部署(不确定性很大 发布有风险)、生产环境手工配置管理(不能完全掌握 不可重复)。同时也提出了一些应该遵循的正面原则
  • 持续集成的前提是版本控制、自动化构建、团队共识,需要做到频繁提交、自动化测试、保持构建和测试过程较短、管理开发工作区、构建失败之后修复成功之前不能提交新代码、提交之前自己运行测试、提交测试通过之后再继续工作、时刻准备回滚(回滚之前要有一个修复时间)、为自己的问题负责、测试驱动等等
  • 持续集成能提高团队对复杂系统的自信心与控制力,其主要关注是开发团队,并不能解决软件部署、交付过程的低效,所以需要一个端到端的自动化构建、部署、测试和发布流程,也就是部署流水线(关注的是软件从 CVS 到用户手中这个过程),它有一些相关实践:只生成一次二进制包、不同环境统一部署、对部署进行冒烟测试、向生产环境的副本部署、每次变更都要立即在流水线中传递、只要有环节失败停止整个流水线。CI/CD 的关键都是 记录变更,为尽早发现问题提供信息,消除低效环节
  • 部署流水线的几个要点:构建与部署脚本化(配置初始化数据、基础设施、中间件等)、提交阶段快速反馈与及时修复、自动化验收测试(验收测试是验证客户的期待,单元测试是验证开发人员的期待)、注意非功能测试(主要指性能)、部署与发布要有策略并且可重复执行(文本化)且可回滚(不同版本文件不删除,用符号链接到当前版本)

作者说无论项目大小都应使用 CI/CD,这个我感觉有点偏激了,所谓磨刀不误砍柴工,前提是这个柴要么很多,要么很大,如果只是几根细柴,有那个磨刀的功夫柴都砍完了。但是实际工作中这么小的项目应该很少,所以大多数项目我们还是都还是应该搭建部署流水线,用上 CI/CD。
书很厚,其实好多地方可以跳过,你只需要看标题就能抓住主旨而无需多看。
PS:可以先看看这本持续集成。

敏捷革命

敏捷革命:提升个人创造力与企业效率的全新协作模式 (豆瓣): https://book.douban.com/subje…

CI/CD 实际上正是敏捷开发的最佳实践,有了前面的铺垫,我们可以通过这本书我们来真正了解敏捷开发的全貌。

亮点:

  • 2005 年之前,大多数软件开发项目都是采用“瀑布法”:整个项目被划分为多个阶段,每个阶段都要经过严格的评审,以期为客户或软件使用者提供完美的产品(甘特图表现),每一阶段的工作做得足够好时才允许进入下一阶段。这种工作方式看似完美,实际全凭想象和猜测、华而不实,往往导致开发进度缓慢,常常超出预算,且现实和规划差异巨大,Scrum(敏捷开发流程)的出现就是解决这个问题的(不凭猜测,而是 PDCA:计划、执行、检查、行动)
  • 任何项目的管理都需要实现两个目标:可控性与可预测性
  • 管理层的职责在于制定战略目标,团队的工作则是决定如何完成目标。无论任何人,只要不在现场,都不可能及时跟上事态变化的步调,所以团队要有自主决策权,此外一个团队需要包含完成一个项目的所有技能,同时要小而精(7 人左右)。团队成员之间不要互相指责,而是尽量改善制度
  • “冲刺”(一般以星期为周期)可以让团队成员集中精力快速做出成果并得到反馈,“每日立会”(15 分钟以内)能让成员清楚地知道冲刺进度如何。Scrum 的核心就是节奏
  • 确定懂项目、懂市场、懂顾客的产品负责人,拟定待办事项清单并检测两遍,重要的事情优先做

这本书细看的话真的很洗脑,看完感觉自己迫不及待地想要冲进一家公司试试 Scrum 了。

DevOps 实践指南

DevOps 实践指南 (豆瓣): https://book.douban.com/subje…

DevOps 是软件开发、运维和质量保证三个部门之间的沟通、协作和集成所采用的流程、方法和体系的一个集合(所以也要基于 CI/CD,前 4 本书可以看做一个连续的专题,核心都是 敏捷)。它取代了传统开发、测试、运维职责大分离的思想,填平了部门之间的鸿沟,让大家更有效的工作。我们可以通过这本书来对 DevOps 有一个全面的了解。

亮点:

  • 开发部通常负责对市场变化做出响应,以最快的速度将新功能或者变更上线。而运维部则要以提供稳定、可靠和安全的服务为已任,让任何人都很难甚至无法引入可能会危害生产环境的变更。这种配置方式让开发部门和 IT 运维部门的目标和动因之间存在“根本的、长期的冲突”——公司对不同部门的考核和激励不同,这种冲突造成了一种恶性循环,阻碍了公司全局目标的实现。DevOps 能够提高公司业绩,实现开发、QA、IT 运维、信息安全等各职能技术角色的目标,同时改善人们的境遇
  • DevOps 是精益、约束理论、丰田生产系统、柔性工程、学习型组织、康威定律等知识体系的集大成者
  • DevOps“三步工作法”:流动、反馈、持续学习与实验,并阐述了 DevOps 实施需遵守的原则与最佳实践(流动:它加速了从开发、运维到交付给客户的正向流程;反馈:它使组织构建安全可靠的工作体系,并获得反馈;持续学习与实验:它打造出一种高度信任的文化,并将改进和创新融入日常工作中)
  • 为了能识别工作在哪里流动、排队或停滞,需要将工作尽可能地可视化,如在看板或 Sprint 计划板上,使用纸质或电子卡片将各项工作展示出来, 通过这种方式,不仅能将工作内容可视化,还能有效地管理工作,加速其从左至右的 流动, 还可以通过卡片从在看板上创建到移动至“完成”这一列,度量出工作的前置时间。此外,看板还能控制在制品数量(队列长度)
  • 文中关于小批量和大批量的差异,我以前在博客中也提到过。如此看来,两种方式各有优劣,关键看能分配的资源是什么?更追求总体效率还是效果出现的等待时间?对返工的要求是什么?再来决定使用方法
  • 第一步描述的原则,使得工作能够在价值流中从左向右快速地流动。第二步描述的原则,使得在从右向左的每个阶段中能够快速、持续地获得工作反馈。快速发现问题、群策群力解决问题,可以避免把问题带入下游环节,避免修复成本指数增加。根据精益原则,我们最重要的客户是我们的下游(不一定是最终付费用户),为他们而优化我们的工作,在源头保障质量。第三步描述的原则可以持续提升个人技能,进而转化为团队的财富
  • ……

感觉历史的天平总是左右摇摆,一开始职责混乱、一个人干所有的事,后来职责分离、分工明确,现在又提倡填平鸿沟、部门融合。随着时代的发展,适用于时代的技术也总是不停变更,要想不被淘汰就得终身学习呀。

Web 容量规划的艺术

Web 容量规划的艺术 (豆瓣): https://book.douban.com/subje…

容量规划(很早就有了,如道路规划、电力运营)是一门省钱的艺术,保证用合理的资源来实现最大化需求,通过这本书我们来敲开容量规划在互联网世界中实际运用的大门。

亮点:

  • 容量规划整个过程:首先要明确定义响应时间、可供消耗容量以及峰值驱动处理等 明确指标 来定义总体负载和容量需求,然后了解当前基础设施的 负荷特征,预测需要的资源来达到这种性能,然后如何管理资源,最后不断迭代,最终目标介于“没有买足够资源”和“浪费太多资源”之间
  • 有几个方法:预测系统何时失败、用统计图表(比数字更直观)呈现问题、性能调优与容量规划要同步进行、搜集数据驱动未来的规划
  • 测量是规划的前提,要有坚实的数据支撑而不是猜测,有许多工具可以测量,他们应该可以随着时间记录和保存数据、自定义度量、比较指标、导入和导出指标,当然测量工具本身要轻量,尽量对资源本身影响较小。
  • 如果说测量是对已有情况的了解,那么估计就是根据数据趋势预测未来。预测 部分靠直觉,部分靠数学。我们可以做曲线拟合,注意到趋势和变更,并进行迭代和校准(看来基于机器学习或者说 AI 的运维是未来啊)

文章除了基于传统模式的容量规划,还涉及到了基于虚拟化和云计算的模式,所以我们学习也要注意趋势和变化。

领域驱动设计

领域驱动设计 (豆瓣): https://book.douban.com/subje…

构建程序之前,我们都要对业务进行梳理和理解,然后是领域划分与建模等一系列重要步骤,最后才是编码实现,这就是一本讲解这些步骤的好书。而且本书会告诉你,设计和实现可以交错进行和演化,来达到最优。还提出了专业术语,你在和别人交流时可以使用。
我在读到 假同源 这个词语时真是犹如醍醐灌顶,因为之前开发项目就有过:同一个对象,这个模块改吧改吧,那个模块改吧改吧,最后导致对两个模块而言,这个对象都不完全属于它,要修改都得小心翼翼怕影响对方,本书告诉我,遇上假同源,要么重新理解和建模,统一该对象表示,要么果断分开这两个模块,用两个对象分别服务这两个模块。

亮点:

  • 模型是一种简化,是对现实的解释,把与解决问题密切相关的方面抽象出来,而忽略无关的细节(所以需要我们消化和提炼已有知识,包括深层次探索)。用户应用软件的问题区域就是软件的领域(有形的如机票系统,无形的如会计系统)。成功的项目有一个共同特征:都有一个丰富的领域模型,这个模型在迭代设计过程中不断演变(我们要持续学习),与实现绑定,成为项目不可分割的一部分
  • 很多因素可能会导致项目偏离轨道,但真正决定软件复杂性的是设计方法。当 复杂性 失去控制时,开发人员就无法很好地理解软件,因此无法轻易、安全地更改和扩展它,而好的设计则可以为开发复杂特性创造更多机会。一些设计因素是技术上的,很多技术人员都能轻易注意到并改进,但是很多程序主要的复杂性并不在技术上,而是来自领域本身、用户的活动或业务,这部分往往被许多人忽略
  • 要避免不设计和过度设计(极限编程)
  • 模型、设计的核心、实现互相影响和关联;模型是团队所有人员沟通的中枢,使得开发人员和领域专家无需翻译就能沟通,高效简洁;模型是浓缩的知识
  • 技术人才更愿意从事精细的框架工作,试图用技术来解决领域问题,把学习领域知识和领域建模的工作留给别人去做。软件核心的复杂性需要我们直接去面对和解决,如果不这样做,则可能导致工作重点的偏离——软件的初衷以及核心就是为了解决领域问题
  • 对于比较重要的业务规则(这个知识点需要我们自己去理解)比如货船超订 110%,应该单独抽象成 1 个实体(具体就可以是 1 个方法),而不是简单的用一个 guard clause 来实现,这样既能明确这个知识点本身,又利于代码的扩展性。当然,把不重要的细节也单独抽象就是典型的过度设计了
  • 以文本为主,简洁的小图为辅(大而全的图反而失去了解释能力)来阐释模型最好。文档是代码和口头交流的补充,为团队提供稳定和共享的交流。只用代码做文档容易使人陷入细节,不能把控全局,所以应该文档和代码互补,文档不再重复阐述代码能表现的内容而是着重核心元素,阐明设计意图,文档还要注意和代码保持同步不脱节(不再适用的文档要进行历史归档),不然就失去了意义。模型与实现也要同步,通过模型驱动设计 MDD 实现,保证模型既利于实现,也利于前期的分析
  • 要想创建出能处理复杂任务的程序,需要做到关注点分离,使设计中的每个部分都得到单独的关注,行业普遍采用 分层架构 ,分层的价值在于每一层都只代表程序中的某一特定方面,每个方面的设计都更具内聚性,更容易解释。分层设计大都是“用户层界面 - 应用层 - 领域层 - 基础设施层” 这种四层架构的变体,其中领域层是软件的核心,将其分离才是 MDD 的关键,也是领域驱动设计 DDD 的前提。领域层与应用层的区分关键在于领域层包含实际业务规则(如转账操作),而应用层是为了实现业务的辅助功能(如导入转账文本记录)
  • DDD 适用于大型项目,小项目用“Smart UI”更合适,还有其他的架构模式都有自己的使用场景和局限
  • 领域中用来表示某种具有连续性和标识(比如银行账户)的事物是 ENTITY,用于描述某种状态的属性是VALUE OBJECT(不可变,无标识,比如数字 3,尽量设计为不可变,便于复制和共享),动作或操作最好用SERVICE 来表示(在大型系统中,中等粒度、无状态的 SERVICE 更容易被复用),对象之间的 关联 可以通过限定条件进行简化,MODULE是一种更粗粒度的建模和设计单元(提供了两种观察模型的方式,一是可以在 MODULE 中查看细节,而不会被整个模型淹没,二是观察 MODULE 之间的关系,而不考虑其内部细节)。领域模型中的每个概念都应该在实现元素中反映出来
  • 由于汽车的装配和驾驶永远不会同时发生,因此将这两种功能合并到同一个机制中是毫无价值的。同理,装配复杂的复合对象的工作也最好与对象要执行的工作分开。应该将创建复杂对象(比如依赖其他对象 B 的对象 A 就是复杂对象,不要直接在 A 构造函数中调用 B 的构造函数)的实例和 AGGREGATE(一组相关对象的集合,比如车辆与发动机)的职责转移给单独的对象:FACTORY
  • 初始模型通常都是基于对领域的浅显认知而构建的,不够成熟也不够深入,通过重构(不仅是一般的 代码细节的重构 ,还有 领域的重构 ,后者通常风险很高,但是回报也很高,需要在前者的不断积累下寻找突破)最终开发出能够捕捉到领域深层含义的模型,这也是管理项目的 规模和复杂性 的要素,加上柔性设计(软件易于修改)就能让项目稳步发展。持续重构渐渐被认为是一种“最佳实践”,但大部分项目团队仍然对它抱有很大的戒心。人们虽然看到了修改代码会有风险,还要花费开发时间,但却不容易看到维持一个拙劣设计也有风险,而且迁就这种设计也要付出代价
  • 代码除了要能准确得到结果外,还要能显式的表达出领域的规则,易于理解和预测修改代码的影响。所以有一些原则:揭示意图的接口 ,能避免用户去研究它的实现(失去了封装的价值); 无副作用的函数 ,安全地预见函数的作用,避免组合爆炸; 断言 可以帮助展示和理解副作用
  • 技术角度的设计模式中的一些也适用于领域设计,比如 Strategy 和 Composite 模式,把设计模式用作领域模式的唯一要求是这些模式能够描述关于概念领域的一些事情,而不仅是作为解决技术问题的技术解决方案
  • 大型系统的模型很复杂,需要注意三个要素:上下文(不要强制在大型系统中统一模型,可以在不同的上下文使用不同的模型(注意 重复概念 假同源),划定好边界即可)、精炼和大型结构,才能操纵和理解这个模型
  • ……

DDD 我们可能都用过,但是很可能没把它当成一项正经学问,都是大概过一下需求,稍微捋一捋逻辑然后就开始编码了,实际上,在我们这个过程我们已经经历了 ddd,看完本书以后希望能把这个过程正规化,流程化,高效化。

Go 语言实战

Go 语言实战 (豆瓣): https://book.douban.com/subje…

上本书给我们讲了 go 的基础知识和原理,这本书就带领我们用 go 的各种库和工具进行实战。

亮点:

  • 计算机一直在演化,但是编程语言并没有以同样的速度演化。现在的高性能服务器拥有 64 核、128 核,甚至更多核。但是我们依旧在使用为单核设计的技术在编程。Go 语言对传统的面向对象开发进行了重新思考,并且提供了更高效的复用代码的手段。Go 语言还让用户能更高效地利用昂贵服务器上的所有核心,而且它编译大型项目的速度也很快
  • 经验,如果需要声明初始为零值的变量,应该使用 var 关键字声明变量;如果提供确切的非零值初始化变量或者使用函数返回值创建变量,应该使用简化变量声明运算符 :=
  • go vet工具不能让开发者避免严重的逻辑错误,或者避免编写充满小错的代码。不过可以很好地捕获一部分常见错误。每次对代码先执行 go vet 再将其签入源代码库是一个很好的习惯;在保存文件或者提交到代码库前执行 go fmt可以统一代码风格
  • go 在函数之间传递变量时,总是以值的方式传递的。函数间传递数组可能涉及大量数据拷贝,最好传递数组的指针,就只用拷贝 8 字节的指针而非拷贝数组本身。相反,与切片关联的数据包含在底层数组里,不属于切片本身,所函数间直接传递切片没有性能影响,映射也是;在创建切片时设置切片的容量和长度一样,就可以强制让新切片的第一个 append 操作创建新的底层数组,与原有的底层数组分离,可以安全地进行修改而不影响原切片,同时也保持了为切片申请新的底层数组的代码简洁性
  • 关键字 func 和函数名之间的参数被称作接收者,将函数与接收者的类型绑在一起。如果一个函数有接收者,这个函数就被称为方法。值接收者使用值的拷贝副本来调用方法,而指针接受者使用实际值来调用方法。Go 语言会调整传入的参数,无论是指针接受者还是值接受者都可以接受指针或者值两种类型,说是方便开发,但我觉得这反而是一个不必要的歧义,比如到了接口的方法集中,如果使用指针接收者来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口,主要原因是编译器并不是总能自动获得一个值的地址
  • 通过嵌入类型,与内部类型相关的标识符会提升到外部类型上。这些被提升的标识符就像直接声明在外部类型里的标识符一样,也是外部类型的一部分,可以无缝实现对象组合,需要注意嵌入类型不需要声明字段。如

      type admin struct {            type admin struct {
          user // 嵌入类型                user user// 声明字段,不是嵌入
          level string                   level string
      }                              }
    
  • Go 语言的并发同步模型来自一个叫作通信顺序进程(Communicating Sequential Processes,CSP)的范型(paradigm)。CSP 是一种消息传递模型,通过在 goroutine 之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。go 的竞争检测器 go build -race 可以有效检测代码里面的竞争状态。go 可以通过原子函数、互斥锁和通道发送接受共享资源来实现同步

查看原文,来自 MageekChiu。

正文完
 0