本文讨论了软件复杂性及其产生原因,介绍了如何度量软件复杂性,及 SOLID 软件设计原则,并探讨管理复杂性的方法,包括使用代码重构、设计模式、领域驱动设计等。通过遵循这些原则和方法,开发人员可以降低软件复杂性,提高代码质量和可维护性。这篇文章内容涵盖了软件开发的道与术,希望能对你所有帮助,欢迎评论交流~
软件系统复杂性指的是系统内部组件、模块、包、类、方法之间的交互关系以及整体设计的复杂程度。这种复杂度可能源自于多方面因素,包括但不限于代码规模、结构的混乱程度、各个模块之间的耦合度、算法的复杂性以及系统中存在的条件分支和循环等。
系统复杂度的高低直接影响着软件的可理解性、可维护性和可扩展性。高复杂度的系统通常意味着更难以理解和修改,也更容易引入错误。此外,复杂度过高还会增加软件开发和维护的成本。
所以说理解和管理软件系统复杂度至关重要。通过采用适当的设计原则、模式和工程实践,以及持续的重构和优化,可以有效地控制和降低软件系统的复杂度,从而提高系统的可维护性、可理解性和可靠性。
以下是两个相同功能的示例代码段,通过对比可以观察下复杂性差异:
多层嵌套条件语句
1 | public void processOrder(Order order) { |
通过早期返回重构
1 | public void processOrder(Order order) { |
上面两段代码,通过对比可以发现第一段代码展示了典型的深度嵌套条件语句,可读性差、扩展性差。第二段重构后的代码使用了早期返回的方式,将每个条件检查分开处理,遇到不满足条件的情况就提前返回错误消息。这样可以减少嵌套的条件语句,提高代码的可读性和可维护性。
第二段代码是对第一段代码的改进,但也是存在一定的可读性和扩展性差的问题的,提前返回是一种断路思考方式,不利于记忆,如果方法比较长,或者后期叠代码使方法变得很长,是不太容易梳理出”什么情况下会执行订单处理操作”,你需要记住各种断路情况。
复杂性是系统的固有属性,它来源于系统的规模、结构、功能、行为等多个方面,有外在和内在两方面原因,下面列举几点:
需求变更
随着时间的推移,产品需求会不断变化。这些变化可能需要对现有系统进行修改或添加新功能,从而增加了软件系统的复杂性。
技术选型
选择不合适的技术栈或架构模式可能会导致系统的过度复杂化。有时为了解决一个小问题可能会引入大量不必要的技术组件,使系统变得更加复杂。
规模扩大
随着业务的发展,软件系统可能需要处理更多种类的数据和用户,这会导致系统规模的扩大,系统的元素和关系会随着规模的增大而增多。
不完善的设计
缺乏清晰的系统设计和架构规划可能导致系统出现混乱和复杂性。如果最初的设计没有考虑到系统的未来发展,系统将很快变得难以管理和理解。
巨著《人月神话》中提出了两个重要概念:
偶然复杂度不是待求解问题的本质,相对而言, 本质复杂度和待求解问题的本质有关,是无法避免的。偶然复杂度一般是在选用求解问题的方法时所引入的。上面列举的四点,其中技术选型不当和不完善的设计都是因为开发人员经验和预判不足而产生的,属于偶然复杂度;而需求变更和规模扩大则是待求解问题逐渐变多变复杂而产生的,属于本质复杂度。
之前写过一篇简单介绍过 软件架构与系统复杂性,下面主要介绍软件系统复杂度度量方式。
圈复杂度(Cyclomatic Complexity)
圈复杂度是一种用来衡量代码复杂性的指标,它通过计算代码中独立路径的数量来评估代码的复杂程度。通俗地说,圈复杂度越高,代码的可读性和维护性就越差。
时间空间复杂度
时间复杂度是用于衡量程序在执行过程中所需的时间资源的多少,而空间复杂度则衡量程序在执行过程中所需的内存资源的多少。
代码行数
代码行数是衡量软件规模和复杂度的一种指标。通常情况下,代码行数越多,系统的复杂度也越高。然而,这并不是绝对的,因为有时候简洁的代码可能实现了复杂的功能。
嵌套层数
嵌套层数指的是代码中条件语句、循环语句和函数调用的嵌套深度。如果嵌套层数过多,会导致代码逻辑混乱,增加代码的理解和维护难度。
组件的相互依赖关系
软件系统中各个组件之间的相互依赖关系也是衡量复杂度的重要标准。如果组件之间的依赖关系错综复杂,那么系统的修改和扩展将变得困难。
通常来说,要想构建一个好的软件系统,应该从写整洁的代码开始做起。毕竟,如果建筑所使用的砖头质量不佳,那么架构所能起到的作用也会很有限。反之亦然,如果建筑的架构设计不佳,那么其所用的砖头质量再好也没有用。SOLID 是一组软件设计原则,旨在帮助开发人员设计可维护、可扩展和易于理解的软件架构。下面简要介绍每个原则:
单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起它变化的原因。这意味着一个类应该只负责一项明确定义的职责或功能,这样可以使类更加内聚,易于理解和修改。
开放封闭原则(Open-Closed Principle,OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着在修改现有代码时,应该通过扩展现有代码的行为来实现变化,而不是直接修改已有的代码。
里氏替换原则(Liskov Substitution Principle,LSP):子类应该能够替换其父类并且不会破坏系统的正确性。这意味着子类应该能够在不改变程序正确性的前提下,替代父类的行为。这样可以确保代码的可靠性和可扩展性。
接口隔离原则(Interface Segregation Principle,ISP):客户端不应该依赖于它不需要的接口。这意味着接口应该尽量小而专注,而不是大而笼统。通过定义精确的接口,可以避免客户端依赖无关的接口,提高系统的灵活性和可维护性。
依赖倒置原则(Dependency Inversion Principle,DIP):高层模块不应该依赖于低层模块,而是应该依赖于抽象。这意味着应该通过抽象来解耦模块之间的依赖关系,使得系统更加灵活和可扩展。
这些原则共同强调了代码的高内聚性、低耦合性和可扩展性。遵循这些原则可以提高代码的可维护性、可测试性和可重用性,从而使软件架构更加健壮和可靠。你可能会说道理我都懂但是做不到,王阳明在《传习录》中说,“未有知而不行者,知而不行,只是未知”,如果不能在开发中深切体会这些原则的精髓那便是不懂,是需要在日常开发中不断思考和体会的。
如果你读到这里,说明看过了很多 “大泥球” 代码,想找到其中的破解之道,下面会抽丝剥茧,介绍一些方式方法。
“大泥球”(Big Ball of Mud)是指一种缺乏清晰结构和良好架构的代码,通常随着时间推移不断添加功能和修复问题而产生。还是以上面的代码为例,展示一个可能被称为”大泥球”代码的案例:
1 | public void processOrder(Order order) { |
如果要增加一个判断订单是否为礼品订单的处理逻辑,最直接的方式是在其基础上继续嵌套更多的条件判断。久而久之这样的“大泥球”代码就存在多层嵌套的条件判断,逻辑复杂、难以理解和维护。
对于超大型的方法和类,最简单的、较低风险的方式是拆分方法和类,之前写过一篇小文章 从小重构说起。对于方法和类的拆分,可以借助 IDE 来实现,这样可以进一步降低风险;对于静态变量需要提取到公共配置类;通常来说业务方法大都是无状态的,对于有状态方法需要谨慎操作。
要提高上面方法的可读性和可维护性,可以将其拆分成更小的方法。下面是重构后的代码:
1 | public void processOrder(Order order) { |
重构后的代码中,我们将原来的单个方法拆分成了多个方法。processOrder
方法负责处理整个订单流程的控制,进行基本的前置条件检查,然后根据订单类型(礼品订单或非礼品订单)调用相应的处理方法。
processStandardOrder
方法用于处理非礼品订单,而 processGiftOrder
方法用于处理礼品订单。这样,我们可以在具体的处理方法中添加更多的逻辑,而不会让整个代码过于复杂。同时,通过使用早期返回,遇到不满足条件的情况就会提前返回错误消息,避免了过多的嵌套条件。
另外,我们还引入了一个辅助方法 sendErrorMessage
,用于发送错误消息,避免了重复的代码。
这样的重构使得代码结构更加清晰,逻辑更易于理解和维护。每个方法负责一个具体的任务,代码的可读性和可维护性都得到了提升。
随着需求发生变化,不同状态的订单需要有不同的处理方式,当当前的方法不再满足现状时,就需要进行扩充,复杂度也会相应提升。为了进一步应对复杂度的提升,可以考虑适合的设计模式。
设计模式通过提供可重用的解决方案,帮助我们管理软件复杂度。它们提供了一种通用的、经过验证的方法来解决常见的软件设计问题,使得系统更易于理解、扩展和修改。同时,设计模式也促进了代码的重用和降低了系统的耦合度,从而提高了软件的质量和可维护性。
以上面的例子,要根据不同的订单状态生成不同的后续处理行为,可以使用策略模式来表达这一设计。策略模式允许定义一系列算法(策略),将它们封装在独立的策略类中,并使得它们可以互相替换。
下面是使用策略模式来重构的代码示例:
首先,定义一个接口 OrderProcessingStrategy
,表示订单处理策略,其中包含一个 processOrder()
方法来执行订单处理操作:
1 | public interface OrderProcessingStrategy { |
然后,实现不同的订单处理策略,每个策略都实现 OrderProcessingStrategy
接口,并根据订单状态执行相应的处理操作:
1 | public class PaidShippedOrderStrategy implements OrderProcessingStrategy { |
接下来,在 Order
类中添加一个 process()
方法,用来触发订单的处理操作。在该方法中,根据订单状态选择相应的策略,并调用策略的 processOrder()
方法来执行处理操作:
1 | public class Order { |
最后,在客户端代码中,我们可以创建订单对象,并根据订单状态设置相应的处理策略。通过调用订单对象的 process()
方法来触发订单的处理操作:
1 | public class Client { |
通过使用策略模式,我们可以将不同状态的订单处理逻辑解耦,使得每个策略类负责自己的处理操作。这样,可以更灵活地扩展和修改不同订单状态的处理行为,同时避免了原始代码的大泥球结构。
从电商交易流程上来说有以下简单几步:
这个处理流程需要一整个系统的支持才能实现。其中每一步执行后都需要有后继行为和通知,这些通知可能是以站内的、短信的方式触达用户,或者通过一些机制发送给下游系统。这时候就涉及到一个决策,哪些是我交易系统的核心能力,哪些是外围能力。如果前期缺乏良好的架构设计,有可能演变成一个”大泥球”系统。
为了避免系统的无序性演变,可以通过领域驱动设计的思想,识别交易领域核心行为,保护领域内部行为不被侵蚀,及领域内部行为是不变或者少变的。
领域驱动设计提倡将软件系统划分为不同的层次,以便更好地组织和解耦系统的各个部分。在DDD中,常用的四层架构和对应职责如下:
用户界面层(User Interface Layer):
应用层(Application Layer):
领域层(Domain Layer):
基础设施层(Infrastructure Layer):
下面是一个示意性的代码结构,用于展示不同层级及其职责:
1 | - xxx-order-app |
在这个设计中,每个层级都有不同的职责和角色,以实现更好的代码结构和可维护性。用户界面层负责展示页面和处理用户输入,应用层负责协调各个领域服务的调用,领域层负责处理业务逻辑,基础设施层负责与外部系统的交互和数据持久化。
这样的设计可以更好地组织代码,使不同的职责分离,减少了耦合性,并且便于扩展和修改。同时,通过领域驱动设计,我们能够更好地表达业务领域的概念和规则,使代码更加贴近业务需求。
**用户界面层 (Presentation Layer)**:
1 | public class OrderDetailPage { |
**应用层 (Application Layer)**:
1 | public class OrderApplicationService { |
**领域层 (Domain Layer)**:
1 | public class Order { |
**基础设施层 (Infrastructure Layer)**:
1 | public interface OrderRepository { |
从上述代码中我们可以看到,基础设施层和领域层设计符合依赖倒置原则,从调用关系看领域层调用基础设施层进行数据的交互,而从依赖关系来看,领域层依赖于领域抽象,不依赖于具体实现,DDD 的精髓在于保护核心领域的自治性,降低层间的偶合度,同时有助于遵循单一职责原则,关注领域核心行为的管理和维护。
这篇文章在这里第一次直面架构,讨论的问题是软件架构(architecture)究竟是什么?从我的经验总结,是站在更高的层次去整体分析软件系统,抓大放小,把握重点,重点是组织结构,而不论是子系统、模块、包,还是分层、服务等,都可以看作为一个”构件”,需要关注的是如何组织使整体高效有序。
如果你要想理解它,可以从设计者的角度去审视,上面的方法从小到大逐层递进地讲了代码的组织形式,后面还要面临更多的复杂性问题,如当用户达到千万级规模,程序如何高效部署和管理,多人协作开发时如何做到高效。
微服务架构是一种软件架构风格,它将一个大型应用程序拆分为一组小型、独立的服务,每个服务都有自己的业务功能,并通过轻量级的通信机制进行交互。每个服务都可以独立开发、部署和扩展,从而提供了灵活性、可伸缩性和可维护性。有下面几个特点:
拆分与自治性:应用程序被拆分为多个小型服务,每个服务关注于特定的业务功能。每个服务都是自治的,可以独立开发、部署和运行,使团队可以并行开发和部署不同的服务。
独立部署和扩展:由于每个服务都是独立的,可以根据需求独立部署和扩展。这种灵活性使得系统能够更好地应对高负载和变化的需求,同时减少了对整个应用的影响。
技术多样性:微服务架构允许使用不同的技术栈和编程语言来实现不同的服务。这使得团队可以选择最适合其需求的技术,提高开发效率和灵活性。
弹性和容错性:由于每个服务都是独立的,当一个服务出现故障时,其他服务仍然可以正常运行,从而提高系统的弹性和容错性。
松耦合和可维护性:微服务通过轻量级的通信机制(如RESTful API或消息队列)进行交互,服务之间的耦合度较低。这使得系统更易于理解、修改和维护。
团队自治和快速交付:每个服务都可以由独立的团队负责开发和维护,团队可以根据自己的需求和进度进行快速交付。这种团队自治的方式促进了敏捷开发和持续交付的实践。
然而,微服务架构也带来了一些挑战,如服务间通信的复杂性、分布式事务管理、服务发现和监控等。在采用微服务架构时,需要仔细权衡利弊,并根据具体的业务需求和团队能力做出决策。微服务更多是关于组织和团队,而不是技术。
这里必须要谈一下康威定律:Conway’s law: Organizations which design systems[…] are constrained to produce designs which are copies of the communication structures of these organizations.
(设计系统的组织,其产生的设计和架构等价于组织间的沟通结构。)
简单来说,这意味着一个组织的沟通和组织结构会直接影响到所开发的软件系统的结构。
组织结构:组织内部的团队结构、沟通渠道和决策层级等因素会直接影响到软件系统的设计。
沟通结构:组织内部团队之间的沟通方式和频率会反映在系统设计中。如果团队之间的沟通不畅或存在壁垒,那么系统的设计可能会反映出这种分隔和隔离。
系统结构:根据康威定律,软件系统的结构往往会与组织结构相似。如果组织结构是分散的,那么系统的结构可能会呈现出分散的特征;如果组织结构是集中的,那么系统的结构可能会呈现出集中的特征。
康威定律的应用意义在于,通过理解组织结构和沟通结构对系统设计的影响,可以更好地规划和调整组织结构,以促进系统的设计和开发。例如,如果希望实现松耦合和模块化的系统,可以通过优化团队之间的沟通和协作方式来达到这个目标。
总结
这篇文章整理了近几年的关于治理系统复杂性的一些经验,主要包括概念介绍、度量方式、设计原则、治理方法几个方面去介绍。其中治理方法部分由浅入深的介绍了几个方式,不同方式面临的问题复杂程度也是不同的。这些方式通过不同的角度帮助我们管理软件复杂度,提高代码的可维护性和可扩展性,确保软件系统与业务需求紧密结合。管理软件复杂性是软件开发过程中非常重要的一环,不论面对何种问题,“简单,易于理解”都应该是我们要坚持的方向。
]]>目标是需要拆分出内部服务 Y 为独立的系统,且暂时不改变系统 A 的被依赖关系,拆分前的情况如下图。
这里假定两个接口层处理模块只会调用只会调用内部服务 Y,并且其中存在着业务逻辑,也许你会疑惑接口层为什么会有业务逻辑,事实上你大多数情况下会遇到。更具体的说,接口层的业务接口 1 中包含业务逻辑,于是会产生对内部服务 Y 的两个及以上接口的调用。
那么你会遇到以下几种情况需要处理。
针对 case 1 的 RPC 接口逻辑两种处理方式:
针对 case2 的 HTTP 接口逻辑也有两种处理方式:
针对 case 3 的内部服务调用:
对于内部服务调用,基于内部服务 Y 接口定义,迁移服务层逻辑到系统 B ,实现基于 BizDto 定义的 rpc 接口,系统 A 只需要增加 BizDto 到 BizThriftVo2 对象的转换。
对 内部服务 X 调 Y 的场景下,需要注意其中是否包含事务型依赖关系。因为如果从本地调用改为远程调用可能会破坏整个事务的完整性,产生数据不一致。
如果我们要拆分内部服务 Y,从上游的依赖来看,有系统 A 和手机客户端的依赖关系(通过虚线表示)。
如果我们不考虑上下游依赖关系,就会和上面说的几种情况一样处理,这时候系统 B 的 RPC 接口层就只是一层很薄的代理,存在的问题是资源的浪费和服务稳定性的打折扣,而且你还要写比较多的胶水代码。而更优的一种方式是消除这种传递依赖,使系统 A 和系统 B 解耦,使系统 C 的功能更内聚,每个系统只负责自己对象和 BizThriftVo 对象的转换。
]]>分布式系统主要的目的之一就是解决大量用户的高并发问题。自己做过几个业务系统,也和别人聊过他们所做过的业务系统,其实大家都使用了相同的数据库,有的系统会使用 Redis 缓存,会使用 MQ 做系统解耦,有的也会使用搜索引擎。这些系统的构件相同的地方都是在处理数据,只不过职责不同罢了。归纳有以下几类:
以上这几个构件就可以组成相对完备的实时数据系统,可以应对常见的业务需求。
关于一个业务系统的通用数据框架可以用下面的图来表述。
关于整个框架的运行方式可以简单的从读和写两个角度来看。
从写的角度来看,首先需要保证数据被正确处理和持久化,处理完主存数据,需要发送事件消息到 MQ,然后将数据同步到高速缓存和搜索引擎,整个流程是需要满足事务性。
从读的角度来看,需要面临的主要问题是和主存的一致性问题,一般保证弱一致性即可。读数据的简化流程是先读缓存,读不到读数据库,再回填缓存。
而各式各样业务功能和逻辑对数据的处理都归为两种操作——读和写,只是不同的系统侧重点不同,主要分为以下几类:
首先说说『读多』的解决方案,最常见的是用户到服务器之间的多级缓存策略(也许描述的不够准确,可以继续往下看),从服务端到用户逐层递进有以下几种:
从上到下,缓存越接近用户对服务器的压力约小,访问速度越快,弊端是一致性的处理越不可控,机器成本和问题排查成本越高。
从数据变化角度来看可以分为动态内容和静态内容,动态内容可以根据业务需要采用分布式缓存和内存缓存的方式,可以通过设置过期时间来自动刷新。静态内容可以通过 CDN 和客户端缓存的方式,一般是一些图片、HTML、CSS、JS 文件。
缓存的更新方式可以分为推和拉两种形式。缓存常见的三个问题略过。
第二种策略是串行读改并行,对于用户的一个请求,如果需要三个外部依赖,耗时分别是 T1\T2\T3。如果是串行化调用总耗时是 T1+T2+T3,在三者没有耦合关系的情况下,改成异步执行的总耗时为 Max(T1, T2, T3)。
第三种策略是批量请求,通过缓存或存储提供的批量命令,可以将单次读写请求改为批量请求,可以减少网络传输的总耗时。
对于『写多』的解决方案,最常见的解决思路是对于数据分片,比如现实世界的高速多车道,医院的多诊室,以此来提升整体的吞吐量。在服务端比较常见的是数据库层面的分库分表,通过合理的分片算法,将数据尽量均匀的分散在不同的库表,通过分库可以利用起多台机器的资源。
除此之外,数据分片的设计策略还在其它方面有所体现:
第二种策略是任务分片,将一个大任务拆分成若干子任务执行。你可能会立刻想到 CPU 的指令流水线,一条指令分为取指、译码、执行、访存、写回五个阶段,单条指令占用 5 个时间周期,每增加一条指令整体只需要再增加 1 个时间周期。
这些策略使用的就是分治思想,耳熟能详的就是 Map/Reduce 了,在 Java 中 ForkJoinPool 也是利用这一思想设计。
分治从字面上也很容易理解,分、治其实还有个合并的过程:
第三种策略是队列缓冲,如果请求量超过系统最大负载,可以放到 MQ 异步化处理请求,这时需要客户端支持异步结果响应。秒杀场景就可以将瞬时大量用户请求放到消息中间件,由服务端慢慢消费,再异步通知用户。
第四种策略是批量写。
根据数据的访问特点,上面提到的各种策略本质上是读写分离,是微服务架构中提到的 CQRS。关于读写分离模式一般具有以下特征:
回到最上面总结的数据框架,实现一个高并发系统所需的主要数据构件有缓存、数据库、搜索引擎、消息队列,以读和写两个视角将用户的大量请求分流到不同地方处理,然后通过多副本的方式对数据构件水平扩容,这本身也是一种分治思想。
]]>这篇文章是汇总历史发布过的,所有关于我的博客编写发布系统文章。文章以时间线倒序的方式罗列整理。
日常会在 macOS 和 Ubuntu 之间切换,博客是基于 Hexo 生成的,不同系统的 Node 版本会有较大差异、环境稳定性比较差,为了方便平时写博客,想到了用 Docker 统一博客生成环境,于是自己写了 Dockerfile,在结合 VS Code 编写,可以做到系统无差。
日常开发只需要在 VS Code 中边写边预览,图片是通过 PasteImage 插件快捷键插入。预览和发布只需要以下两个命令即可。
预览本地博客:alias run-blog='docker exec -it container_id python utils/goto.py blog'
发布博客文章:alias push-blog='docker exec -it container_id python utils/goto.py push'
Docker 项目:https://github.com/noogel/noogel.github.io.docker
在 mac 机器上可以使用 mweb 来写博客,比较好用的地方就是可以直接把剪贴板的图片粘贴上来,缺点是 mac 键盘超难用并且不支持窗口内开启命令行。平时在家的时候都用 Ubuntu 台式机,博客使用 VS Code 编写,一直以来阻挡我的是图片的粘贴特别费劲,今天发现一个很好用的插件 pasteimage,可以直接将剪贴板图片粘贴到 markdown 使用,并且支持配置保存路径。
然后按照教程配置好参数:
1 | { |
就可以直接将图片粘贴到 markdown 中,其中遇到个问题就是配置不生效,会导致文件直接保存到当前文件目录,具体配置方法可以参考下面连接。
https://www.crifan.com/vscode_how_to_config_setting_plugin/ 这篇文章写的很详细了。
https://github.com/mushanshitiancai/vscode-paste-image 这篇是配置教程,里面有些地方比较容易被误导。
对于Linux系统需要有 xclip 支持,使用的时候会给提示的。
另外记录一下 Ubuntu 的截屏和粘贴快捷键:
1 | Ctrl + Shift + Print Screen // 区域截屏到剪贴板 |
我们平时拍照的照片中会包含很多额外信息可能暴露我们的地理位置、拍摄数据、拍照时间等信息。在一些网站上传原图时会暴露这些敏感信息,这个脚本主要用来通过 pillow 库将照片的 exif 信息抹掉。
其它校验网站: https://exif.tuchong.com/
通过这个网站也可以查看这些额外信息:
『MWeb 是 Mac 平台上一款专业的 Markdown 写作、记笔记、静态博客生成软件。』主要有以下几个优点:
1 | # H1 |
引用
斜体
粗体
行内代码
1 | 多行代码 |
分割线:
1 | $$d=\sqrt{\sum_{k=1}^n(x\_{1k}-x\_{2k})^2}$$ |
效果:
$$d=\sqrt{\sum_{k=1}^n(x_{1k}-x_{2k})^2}$$
sequenceDiagram 老板C ->> 员工C : 开始实行996 par 并行 员工C ->> 员工C : 刷微博 and 员工C ->> 员工C : 工作 and 员工C ->> 员工C : 刷朋友圈 end 员工C -->> 老板C : 9点下班]]>
按照上述清单可以在进行架构设计时进行思维训练,同时不要局限于清单,做到动态调整。
]]>从工作经验来分,以三年为界,分为两类面试思路:
今天要说的是 macOS 下的一款效率软件 —— Alfred,想必大家就算没用过也耳闻过,老实说用好它带来的效率提升绝对不止 10 倍。博主已经安利给很多同事使用,他们普遍觉得上手有些困难,主要是配置复杂,今天的文章会一步步地介绍这款神器的高效之处。
有的人可能会说系统自带的 Spotlight 就很好用,确实是这样。在之前我会用 Spotlight 搜应用、文件、进行计算等,而 Alfred 的功能更强大,是一款可以更加 All in 的效率工具,里面还有我最常用的剪贴板历史、快速网页搜索、谷歌二次口令扩展等功能,接下来我会逐一介绍。
先说一说如何调出命令面板,在 General 中配置的 Alfred Hotkey 就是使用快捷键。如下图我配置的是 Command + Space。
调出的样子是下面这样的,在框中输入内容即可,输入内容后就会展示响应结果,选中后回车执行对应的操作。执行的操作一般是打开浏览器网址,或者将结果复制到剪贴板。
然后说一说最常用的几个功能
在 Windows 上对应的是 Ditto,macOS 上也试过几个都不太好用。Alfred 自带这个还能搜索内容,可以自定义保存时间和查看快捷键,选中回车就能保存到剪贴板。如下图我配置 Option + Command + C 是调出历史面板。
配置面板
查看历史面板
搜索功能
主要有两个使用场景,一个是在需要多次复制粘贴的场景下,可以一次性复制完,再到新页面按顺序选择性粘贴。另一个是快速搜索某天复制过的片段。
输入 find 命令和文件名就能找到,回车会自动在 Finder 中打开所在目录。
快速搜索网页的输入结构是『关键字 搜索内容』,关键字就是下图中的 Keyword,然后空格再输入搜索内容。
关键字是可以自定义配置的,只要有有规律的页面就能进行配置。比如我下面的配置是在谷歌搜索时过滤某网站内容。其中的『{query}』是被替换的搜索内容,『gog』是搜索指令。
以下两张图描述了如下两个动作,一个是输入搜索指令,另一个是回车操作后的浏览器页面。
再比如我要配置自动搜索知乎,我会先去看下知乎搜索页面的结构,如下图。
网址是:https://www.zhihu.com/search?type=content&q=%E9%85%8D%E7%BD%AE
于是在 Alfred 上就可以进行如下配置。
一般公司内部网址的规律性会更强一些,于是可以做很多的快捷操作。比如快速打开某个内部网址、在 Gitlab 快速发起 Merge Request 申请、快速打开联调环境、快速在 Sentry 搜索内容等。
附上一些常用自定义配置:
这个功能用的不多,但也要提一下,默认会打开系统自带终端并执行,我这里的配置是结合 iTerm 启动执行。
Workflow 才是这款软件的灵魂所在,可以通过开发插件的形式扩展软件功能。
我举三个最常用的扩展吧。
谷歌身份验证器是在做某些危险操作时进行二次确认的一个机制,在第一次初始化时根据账户绑定一个 key,然后每次使用都需要根据当前时间算出一个数字输入,来增强安全性保障,这个原理跟游戏的令牌是一样的。有了这个插件我就可以只输入一个 ok 关键字就可以获得验证口令,回车粘贴,只需要 2 秒。
与此同时,而别人的操作流程是
以上四步的操作时间平均在 20 秒以上,除非手机时刻在 Authy 界面。
这个是博主自己开发的一个扩展,目前已在 Github 开源,https://github.com/noogel/Alfred-Workflow,欢迎 Star、Fork、提 Feature ~
具体的使用教程欢迎查看 https://github.com/noogel/Alfred-Workflow
初衷就是提高平时的开发效率,将一些常用的重复性操作给命令化,以此来节省时间。比如我最常用的 ntm 命令,可以获取当前时间戳或者标准时间,也可以将标准时间和时间戳互转。整个操作 2 秒完成。
而别人的操作则分为以下几步。
工具箱中的内容不仅这个,还有很多实用的工具,可以大大的提高效率。
最后一个常用的插件是有道翻译扩展,输入关键字 yd 就能自动转换中英文。
平时工作的使用频率还是蛮高的,节省下来的时间可以做更多的事情。
Alfred 的功能不止于此,在其官网有更详尽的描述,感兴趣的可以试用一下,有什么问题也可以咨询我。我是知一,如果这篇文章对你有益,欢迎一键三连~
]]>首先是同事推荐的一本书 ——《书读完了》,由金克木先生所著,豆瓣评分 8.6 ,还是蛮高的。如今书籍浩如烟海,怎么可能读完?其实先生之所谓读完,我主要记住了以下两个观点。
一是说书也有层级,有些书是最基础的书,如《诗经》、《春秋》这些表达中国最底层文化思想的书籍,而其他书则是基于这些书发展而来的,所以要读就读经典。读懂经典,那么后继的书不读也自然懂了大半。中国古书不过是那几十种,是读得完的。
二是说经典虽难读懂,但也不要贪图潮流而追逐当下。古人说『读书得间』,读出字里行间的微言大义,于无字处看出字来。看字读句,要看出问题,若能『望气』而知书的『格局』,会看书的『相』,那就可以有『略览群书』的本领,不必一字一句的读,而做到『博览群书』。很可惜在碎片化的时间里很难有如此深度的思考。
第二本说一下《金字塔原理》,这本书主要讲逻辑思维与表达呈现。首先作者讲的是金字塔的基本结构:中心思想明确,结论先行,以上统下,归类分组,逻辑递进。先重要后次要,先全局后细节,先结论后原因,先结果后过程。然后围绕金字塔原理通过一个个案例来讲具体的做法:自上而下表达,自下而上思考,纵向疑问回答/总结概括,横向归类分组/演绎归纳,序言讲故事,标题提炼思想精华。读这本书是因为校友群有人在看,刚好能解决我写博客困难的问题,于是就找来看了看,没有读完,不过写文章的思路总归是有了。
第三本便是《空谷幽兰》,作者比尔·波特是著名的汉学家,在台湾当过两年沙弥,对中国传统文化有着很大的兴趣。这本书主要是讲他在八零年代实地寻访中国隐士的过程,讲述了中国传统隐士在终南山一带的隐居生活,探寻他们为何要离开城市或人群,到幽静之处筑居,有的人五十年不下山,有的人活了近百岁。又讲述了中国传统道与佛的异同。书中描述的意境我只能摘录摘录几句给大家看看:
有个叫宝胜的和尚讲:“真修行的人太少了。至于我自己,我不怎么修行。我晚上打坐,白天干杂活儿。我只是在照管这座庙。”这是得道之人所说的话。
陈世杰道长这样谈修道:“当人们努力去寻找道的时候,他们就失去了道。他们混淆了有和无,我们所能做的一切只是修德(美德,精神力量),德包括我们的精神、我们的心、我们的想法。真正的德导致真正的道。但是大多数人修的不是真正的德。他们修炼的是神通和心念,于是我们以为他们得道了。但是他们错了。修习真正的德不是要去掉所有的神通和念头,像一个婴儿一样,无看而看,无听而听,无知而知。首先你要修德,道自然就来了。”这是我听过最好也最透彻的对修道的说法。
《毛泽东传》这部伟人传记还没读完,不过非常值得推荐,可以结合《毛泽东选集》一起读,会有很多收获。
然后要说的是关于技术类的,整个一年读的都是关于架构和领域驱动设计的书,都很不错。
《架构整洁之道》主要讲了整洁架构的设计目标,系统地剖析其缘起、内涵及应用场景,涵盖软件研发的完整过程及所有核心架构模式。绝对是架构书类中的必读经典。
《软件架构设计》围绕软件架构设计,系统化地梳理技术架构与业务架构的方法论与实践。这本书不仅讲了业务层面的内容,也讲清了很多技术深度上的知识和一些底层的技术思想。
《实现领域驱动设计》这本书出了很多年了,算是领域驱动设计里的经典,但是奈何翻译太差劲,不建议作为入门书来读。系统性读书的话建议先看看国人写的,再结合思特沃克公司写的文章理解。
《复杂软件设计之道》这本国人写的书,前半部分写的还可以,解决了我一些疑问,后半部分就读不进去了。
关于领域驱动设计总结了一篇文章《万字长文谈谈领域驱动设计》
《垃圾回收的算法与实现》这本书写 GC 还是不错,重点在于算法和不同语言的实现,我也是读了个大概。
然后说两本旅游攻略的书,《西藏不止旅行》、《慢拍西藏》,因为 2021 年十一计划去西藏玩,所以提前看了两本书给自己种种草。写了一篇关于西藏的文章《云之彼端·生活的另一种打开方式》
接下来就是四本关于科普和健身的书了。
《复杂》一书作者梅拉妮·米歇尔是计算机科学教授,她在书中讲述了复杂与复杂系统的关系,给复杂系统一个基本的定义。讲述了各个领域的复杂系统以及度量系统复杂度的方法。读书收获则是加深了对于复杂系统的认知,结合领域驱动设计了解了拆解系统、管理系统复杂度的方法。豆瓣评分 9.0 ,非常值得一读。
《现实不似你所见》深入浅出地讲述了物理学的前世今生,虽然作者已经讲得比较简单了,很少用公式来表达,但我只看懂了只言片语。没想到的是在经典力学之后,又经过了相对论和量子力学,直至现在的量子引力,科学家们竟然进行了这么多的探索。在量子引力的世界,时间、空间、场、粒子都不存在,它们被极度简化为协变量子场的表现形式,感兴趣的读者可以看一看。
《运动改造大脑》这本书最初来源于樊登听书对我产生的影响,作者主要讲了适度复杂运动对于大脑的正向影响。
透过美国高中的体育改革计划、真实的案例与亲身经历、上百项科学研究证实,运动不只能健身、锻炼肌肉,还能锻炼大脑,改造心智与智商,让你更聪明、更快乐、更幸福!运动能刺激脑干,提供能量、热情和动机,还能调节脑内神经递质,改变既定的自我概念,稳定情绪,增进学习力。
《这里是中国 2》这本书讲述了中国在建筑和经济方面的一些成就,不如前两年看的同系列的第一本书有意思。两本都值得看看。
小说类基本是听,刘慈欣在《三体》之后变得几乎家喻户晓,他写的其他作品同样很好,比如我听的《球状闪电》,当我在听了《神们自己》之后,发现阿西莫夫是西方的刘慈欣,不过他是比刘慈欣要早的,两者的风格很像,鸿篇巨著,构建一个史诗级世界。读完刘慈欣建议看看阿西莫夫合集。
再有一篇是网络小说《13 路末班车》,这篇网文不同于其他的,特点是足够恐怖但又不吓人,主角不是脑残有一定智商,剧情连贯不拖拉。当然也有缺点是有些环节逻辑不缜密,人物动机牵强。
最后附上书名-作者-豆瓣评分-京东分类
《书读完了》 - 金克木 - 8.6 - 文化随笔
《金字塔原理》 - 芭芭拉·明托 - 8.1 - 管理经典
《空谷幽兰》 - 比尔波特 - 8.3 - 文化随笔
《架构整洁之道》 - 罗伯特 C·马丁 - 8.7 - 软件工程及软件方法学
《软件架构设计》 - 余春龙 - 8.7 - 软件工程及软件方法学
《实现领域驱动设计》 - 沃恩·弗农 - 8.4 - 软件工程及软件方法学
《复杂软件设计之道》 - 邱晨阳 - 7.6 - 软件工程及软件方法学
《垃圾回收的算法与实现》 - 中村成洋 相川光 - 8.2 - 编程语言与程序设计
《西藏不止旅行》 - 周硚 - 7.2 - 旅游攻略
《慢拍西藏》 - 赵利山 - 7.5 - 旅游攻略
《毛泽东传》 - 中共中央文献研究室 - x - 传记
《复杂》 - 梅拉妮·米歇尔 - 9.0 - 科普读物
《这里是中国 2》 - 星球研究所 - 8.3 - 科普读物
《现实不似你所见》 - 卡洛·罗韦利 - 9.2 科普读物
《运动改造大脑》 - 约翰·瑞迪 - 7.5 - 运动健身
《球状闪电》 - 刘慈欣 - 9.0 - 科幻小说
《神们自己》 - 阿西莫夫 - 8.4 - 科幻小说
《13 路末班车》 - 老八零 2 - 网络小说
前文提到了事件风暴产出的领域模型是概念模型,到实际落地还有些距离,而落地的结果也是各不相同,我觉得说落地,要先回顾一下领域驱动设计的两个作用。
也就是说领域驱动设计产出的结果是指导性的,并不是一个直接可落地的结果。落地的方案则是要通过架构设计和框架选择上来进行。架构是为了控制软件复杂性而做,就好像『一千个读者心中有一千个哈姆雷特』,不同人做架构不尽相同。下面说说我的落地方式。
我们最初接触和使用的分层架构是三层的,三层架构解决了程序内部代码调用复杂和职责不清的问题,在 DDD 分层架构中的关于对象和服务被重新归类到不同分层中,确定了层与层之间的职责边界。DDD 提出了四层架构,其中最主要的变化是提出领域层的概念,需要领域专家对于业务知识的精准把握之上,根据领域设计方法建立领域模型,把变动较少的领域模型放入领域层,而多变的业务场景代码放入应用层。如下图对应三层到四层的演进过程。
分层架构的一个重要原则是每层只能与位于其下方的层发生耦合,可以简单分为以下两种:
这两种分层架构的耦合方式是各有利弊,在网络上对于他们也是各有各的见解。结合实际情况在开发中,更倾向于采用松散分层架构,但是要禁止用户接口层直接访问基础设施层,防止一些潜在的安全问题。
基于现有三层架构,在其中增加 domain 包的形式增加领域服务层。不同的子域通过包来划分如下:
1 | package noogel.xyz.domain.deal; // 交易子域 |
同一个领域服务下面再按照领域对象、领域服务、领域资源库、防腐层等方式组织。
1 | package noogel.xyz.domain.xxx.repository; // 资源库接口定义 |
领域驱动解决的一个问题就是对象的贫血问题。通过如下促销领域对象来说明,对于当前购买商品组合能否满足购买规则的检查逻辑不是放在服务层或者工具类中,而是由领域对象提供方法支持。
1 | @Getter |
资源库对外的整体访问由 Repository 提供,它聚合了各个资源库的数据信息,同时也承担了资源存储的逻辑。我们将资源库的接口定义放在领域层,而具体实现放在基础设施层。
1 | package noogel.xyz.domain.xxx.repository; // 资源库接口定义 |
资源库接口定义,提供必要的入参,并且以领域对象的形式作为结果返回。至于组织返回的领域对象,交由具体实现类来实现,可以通过调用数据库、缓存系统、RPC 接口等形式来组织生成领域对象。
1 | public interface PromotionRepository { |
用来消除外部上下文结构差异的作用,也叫适配层。比如在算价上下文中需要调用促销上下文数据,不同的促销数据源提供了不同的接口和数据,这时就需要引入防腐层来屏蔽差异,防止外部上下文侵入领域内部影响代码模型。首先定义需要的数据接口规范。
1 | public interface PromotionFacade { |
实现类来用处理外部数据的差异,按照接口要求封装数据,简化模型的复杂性。
1 | public class Promotion1Facade implements PromotionFacade { |
对于上下文集成的手段可以通过 RPC 服务、HTTP 服务、MQ 消息订阅。
上面我们讲述了各个要素对于资源和行为的封装,业务逻辑的实现代码应该尽量放在聚合根边界内。但是总会遇到不适合放在聚合根上的业务逻辑,而此时领域服务就需要承载编排组合领域对象、资源库和防腐接口等一系列要素,提供对其它上下文的交互接口。
1 | public interface PromotionService { |
DDD 的设计概念很多,学习成本比较高,于是我们组织了《实现领域驱动设计》的读书分享会,通过共读分享交流理解的方式,让大家对于 DDD 的设计方法和概念有了比较统一的认知。同时发现在做设计分享时,组内的认知比较一致,而对外的理解成本则会比较高。
不论我们怎样称呼应用层和领域层,但是四层架构的优势已经显而易见,对于电商交易这样一类相对复杂的系统而言。DDD 教会我们怎么拆分领域,如何沉淀领域模型,而如何组织领域服务提供业务功能上是匮乏的,下面是基于系统问题和业界资料总结的一个抽象框架,描述的是如何组合核心能力与业务场景,并提供一个配置化的灵活系统。
能力单元
提供基础能力的独立单元,只单纯依赖下游数据提供能力,职责比较单一,对应领域驱动设计的领域服务。
场景单元
通过编排不同能力单元,形成一个预定义的执行流程,叫做场景单元。场景单元有以下关键要素:
所以一个场景单元的实际处理通路由条件控制和干预策略决定。
策略配置服务
框架图
核心能力封装数据和行为,职责要单一且通用,对外提供完善的接口供场景调用,核心能力内部是高内聚的,能力外不能与其它能力模块发生直接耦合,只能通过场景进行间接耦合,要保证核心能力的职责单一性。
能力模型是指对于复杂场景进行归类和抽象得出的一个模型,可以用来解决某一类通用问题。能力模型既可以是由订单系统内部提供的,也可能是由外部系统通过 RPC 形式提供的一整套能力接口包装而得。
内部事件,由于能力之间不允许直接耦合,所以内部事件不允许在能力模块内部发送,只能由场景中进行控制发送,并且能力内部不允许直接监听,而应该把监听事件作为场景的一种入口,实现场景之间的依赖调用。
场景单元偏流程数据编排,需要组织和协调资源的代码被定义为流程。场景单元与策略服务耦合更重,通过策略服务控制场景流程图的走向,以此来实现系统配置化。
《复杂软件设计之道:领域驱动设计全面解析与实战》 - 彭晨阳
《实现领域驱动设计》 - 沃恩·弗农
《解构领域驱动设计》 - 张逸
《DDD实战课》 - 极客时间
https://insights.thoughtworks.cn/backend-development-ddd/
https://zhuanlan.zhihu.com/p/383427771
https://cloud.tencent.com/developer/article/1549817
领域驱动设计学习拦路虎之一就是众多的概念,第一次接触这些概念会有一定的理解成本,不过正是这些概念支撑起的领域驱动设计,接下来会以电商为例对其中的核心概念做介绍。
网上购物已经成为我们生活中不可分割的一部分,作为一个用户而言我们经历的流程有以下几点:
作为电商的管理人员我们需要做的则是以下几点:
电商平台作为一个复杂系统主要有多阶段、⻓链路、多角⾊参与、多信息互通的商品/服务交换过程的特点。而领域驱动设计中的概念能支撑我们将电商复杂流程拆解消化,并且建立一个易扩展、更稳定的系统。
既然有多方协作参与系统的建设和运营,就需要沟通,而降低沟通成本的一个关键就是统一概念和认知,比如我们对于商品的认知,同样都是 iPhone 13,蓝色和粉色,128G 和 256G ,我们说卖掉了一个 iPhone 13 还是卖掉了一个 iPhone 13 蓝色 256G 要怎么表达,这时我们需要有两个概念 SKU 和 SPU 来区分,SKU 作为商品最小售卖单元表达后者,SPU 作为商品信息聚合的最小单位表达前者。
正是因为不同参与角色可能有不同的理解,为了降低大家沟通的障碍,提出了通用语言和限界上下文这两个重要概念。
使团队交流达成共识的能够明确简单清晰地描述业务规则和业务含义的语言就是通用语言。 解决各岗位的沟通障碍问题,促进不同岗位的和合作,确保业务需求的正确表达。通用语言贯穿于整个设计过程,基于通用语言可以开发出可读性更好的代码,能准确的把业务需求转化为代码。
界限上下文则是用来封装通用语言和领域对象,提供上下文环境,保证在上下文内的业务概念和流程等有一个确切的含义,没有二义性。
业务概念往往由领域专家带领团队统一通用语言,明确上下文边界,以结算单这个概念在订单上下文和结算上下文的差异来举例:
明确上下文边界后,我们跟不同岗位的人沟通即使使用相同词汇也能准确理解其含义。
领域驱动设计强调由领域专家带领大家进行领域建模。领域专家指的是对一个领域的概念和业务流程精通的人,能快速识别或预判业务风险并能给出有效解决方案的人。 他可以是各个岗位的人,包括一个开发也能成为领域专家。领域知识则是这个领域的各种概念和业务流程。
领域驱动设计作为一种设计方法论,从两个方向指导设计思想,提出了战略设计和战术设计的概念。
战略设计是从业务视角出发,建立业务领域模型,划分领域边界,建立通用语言下的限界上下文。它是从顶层视角来审视我们的软件系统各个子模块之间的边界。
拿上面的流程举例来说明,一个有经验的领域专家会带领大家通过事件风暴建模的方法进行子域拆分,大致分为交易域、营销域、支付域、商品域、履约域。
战术设计则是从技术视角出发,侧重于领域模型的技术实现,完成软件开发和落地,它主要关注的是技术层面的实施。战术设计识别出来的是聚合根、实体、值对象、领域服务、应用服务和资源库等代码逻辑的设计和实现。
我们都不喜欢写 CRUD 的代码,只因为这些代码往往逻辑很简单,也不具备足够的扩展性,单一场景下可以很快开发出来,如果再加一个场景就又要开发一套,如果场景复杂并且不断变化,开发效率不仅会变慢,而且会更难以维护。下面通过支付系统来举例。
对于 CRUD 的实践来说,在对接支付渠道的时候,给每一家渠道都增加渠道单记录表,字段参照渠道参数定义的,对接微信时增加 wechat_trade 表,增加支付宝时增加 alipay_trade 表。问题就是当渠道增多时每次都建表显然不现实。
正常的做法则是,统一支付单记录,提取支付关键信息,通过总表和渠道表来记录,总表记录关键信息,把次要信息放入渠道表。相当于把支付单信息做了一次垂直拆分。
随着发展,新增了连续订阅业务,产品说需要在支付单中识别出是系统扣费还是用户主动付费的,这时你会想着扩列来支持,可是业务千变万化,不能每次都这样做。
其实软件开发中的许多问题,例如沟通问题、演化问题都和领域模型有关。领域模型是对领域内的概念类或现实世界中对象的可视化表示。它专注于分析问题领域本身,发掘重要的业务领域概念,并建立业务领域概念之间的关系。
实体和值对象是组成领域模型的基础单元。
实体拥有唯一标识符,且标识符在历经各种状态变更后仍能保持一致。 对实体而言,重要的不是其属性,而是其延续性和标识,对象的延续性和标识会跨越甚至超出软件的生命周期。我们把这样的对象称为实体。从上面的实例来说,支付单有唯一的 ID,渠道单有自己的唯一 ID,它们都是实体。
当一个对象用来描述一个实物,而没有唯一的标识符,叫做值对象。 值对象本质就是一个集合,可以保证属性归类的清晰和概念的完整性。由于金额不能单独表达用户的消费额,需要由支付金额和货币类型组合才能表达,消费额是一组值对象。
聚合是领域模型的具体表达。
聚合是业务和逻辑紧密关联的实体和值对象组合而成,聚合是数据修改和持久化的基本单元,一个聚合对应一个数据的持久化。 聚合在 DDD 分层架构中属于领域层,一个聚合提供一个业务核心能力,领域层包含了多个聚合,聚合内的实体以充血模型实现个体业务能力,以及业务逻辑的高内聚。
聚合根也叫做根实体,它不仅仅是实体,还是实体的管理者。 聚合之间通过聚合根关联引用,如果需要访问其他聚合的实体,先访问聚合根,再导航到聚合内部的实体。即外部对象不能直接访问聚合内的实体。
拿上面支付的例子来说,支付是一个聚合,支付单是聚合根,渠道单是依附于聚合根的另一个实体,渠道单的所有行为都要通过支付单进行操作。
上面说到聚合之间通过聚合根关联引用,一个实体是否属于聚合根取决于所处的聚合。在退款聚合中,退款单是聚合根,绑定的支付单,在这里支付单是普通实体。所以是否是聚合根取决于具体场景。
聚合的特点:高内聚、低耦合,它是领域模型中最底层的边界,可以作为拆分微服务的最小单位。
关于领域驱动设计的核心概念已经介绍了一部分,后面还有一部分。关于这些概念的涵盖范围见下图。
在这里我说一下电商中比较核心的一个流程。在京东购物我们会选择很多需要的商品添加到购物车,在双十一的时候会凑单满减,然后从购物车选中下单。现在我们要设计的部分是用户在选择多件商品时自动给用户使用上最优的多种促销活动,在用户下单的时候能够计算好用户应该付多少钱,每件商品分别应付和优惠多少钱。后面的表达我会用算价来代表这个流程。
在领域驱动设计中很强调领域专家这角色,与团队人员共同协作完成任务。而往往团队人员就拥有领域专家所拥有的部分知识,从而承担领域专家的职责,那么剩下的领域知识就需要靠团队人员借助外援来填补,方式包括但不限于以下三种方式:
当我们团队获得该领域下主要的领域知识后,需要结合实际需求进行战略设计和战术设计,就可以通过事件风暴建模方法进行领域建模。
本来是想着拿实际的例子来讲一遍事件风暴建模的过程,现在想想与其照本宣科的讲知识,不如写写经验和感悟来的实在。
事件风暴建模的标准流程可以很轻松地找到,这里不再赘述。主要说下从传统软件开发模型到领域驱动设计的领域建模,发生了什么变化。
传统模式:产品需求->需求分析->详细设计->ER模型->UML 设计
DDD 模式:事件风暴->产品愿景->场景分析->领域建模->微服务拆分与设计。
在传统模式下的产出的是可直接落地的设计结果,但是缺乏顶层设计,对于后期的变更维护难以高效支撑。而 DDD 的关注点更多的是顶层设计和概念模型,概念模型并不是可直接落地的结果,这样的优势便是在后期的扩展和变更中更容易。
关于如何拆分子域,看了很多的内容后得到的一句话:『凭经验』,这个就让人很糊涂,我如何知道我拆分的是否准确。
当我带着问题去找书查资料,收获还是比较快的,有一段话驱散了一部分迷雾:『领域的边界划分不断演绎,只要发现复杂性凝聚的地方,就划定为有界上下文,割裂它与其他系统的关系,并派出精兵强将专门对付。』它给了我两个点醒:
从这两点出发,可以通过以下两点执行:
假定产品愿景是可行并且可执行的。在场景分析和领域建模的过程,有个通用的范式。
由此我们可以得出领域分析模型,这是一个比较抽象的模型,此时还无法落地。从复杂性角度来看领域建模控制的是业务复杂性。
在之前的文章中也提到过三点:
现在反过来看,提炼领域概念是抽象,子域拆分是分治,而要做到这两点的正需要的是领域知识。领域驱动设计不仅告诉了我们『道』,也告诉了我们『术』。
]]>概念可以简单描述某类事物,这类事物可以是实体也可以是问题。领域驱动设计是为了管理系统复杂性问题而生的一套方法论。
随着业务系统的复杂性不断提高,系统的性能和灵活性要求也会越来越高,如何构建一个扩展性强、可用性高的业务系统是需要我们不断思考的问题。
我们以交易系统为例,在互联网之初,实体商业占据绝对主导地位的时代,电子商务系统最初的目的就是把货物卖出去,业务需求很简单,就是一手付钱,一手交货,而更多的难点是在于如何让人们接受并认可在网络上进行交易。随着这几十年的发展,电商早已不是最初的样子,需求变为如何更快更多的把商品卖出去,于是产生出了层出不穷你算不清楚的促销活动,比如满减、凑单、会员价、拼团、优惠券等。你买东西的价格也许只有系统能真正算清楚。
系统的复杂性比起最初,呈几何倍的增长,如何控制并管理系统复杂度是我们需要在业务发展过程中需要解决的问题。复杂的业务各有各的复杂,而拆解之道也各有各的侧重,今天要介绍的是领域驱动设计如何帮助我们拆解需求,并建立一个灵活性高、可扩展的业务系统。
领域驱动设计中的领域是什么?我理解的是一个比行业更加细分的方向,比如互联网做电商业务是电商领域,电商中有专注交易的交易领域,做电子支付叫支付领域。领域范围可大可小,领域知识表示某些具有相关相关性知识的合集。
领域驱动设计是通过领域知识构建的领域模型来控制业务的复杂性,通过领域模型反映领域知识,构建更易维护的系统。解决软件难以理解,难以演化的问题。
上面的总结涉嫌鸡生蛋蛋生鸡的问题。其实领域模型和领域知识是迭代产生的,随着人类抽象总结而不断凝练而成的。拿之前讨论过的例子来说,一个电商领域专家可能脱口而出订单的概念,大家先入为主的很容易理解这个概念。
从人类历程来看最早出现的是物物交换的概念,后面逐渐变成等价货币交换,我们抽象的名词叫交易,再到后面你从我这里付一笔钱,我给你一个凭据,过段时间你来取货,我们管这叫购买凭据,进而逐渐演化成订单这个概念。
领域驱动设计的核心目标是基于特定业务范围,通过统一业务概念(统一语言),将系统参与各方整合在一起,从而减少不同角色和环节的信息熵减问题。
领域模型是领域驱动设计的核心产出,它不仅能描述真实的业务逻辑和业务场景,也是系统实现的表达方式。领域模型的适应性能直接反应系统的扩展性上,能否使系统在增大时仍然保持敏捷。
领域驱动设计之所以更加流行,很大因素是领域驱动设计提供的方法论上与近些年流行的微服务有很好的匹配性,通过领域驱动设计方法清晰地识别业务边界,以此来指导微服务的拆分。 领域驱动设计提供的领域划分方法可以指导我们对微服务的拆分,以及对于演进式架构有很强的助力。
通过上面对于领域驱动设计的介绍,可以提炼出三个主要作用:
通过以上三个作用来逐步介绍领域驱动设计的适用场景。
领域驱动设计中引入领域专家角色,是指对某个领域的概念和流程有着深入理解的一类人。开发人员与领域专家之间,他们掌握的知识存在巨大的差异。就比如电商领域专家清楚地了解交易单、订单、子单、售后、物流单、运单这些概念的准确含义,而开发人员更专注技术的运用,在沟通中如果没有达成一致的理解,沟通效率就会很差,甚至产生误解。
领域驱动设计提出从需求中提炼出统一语言,其实就是在两个不同的语言世界中进行正确翻译的过程。在多角色协作的场景中可以有效降低沟通成本,迭代式的探索和发现模型。
上面我们提到现代电商促销方案层出不穷,决定一笔交易的金额有很多影响因素,而算价结果直接影响到这笔交易的支付金额,以及每件商品的实付金额。如果我们认为促销价格计算和交易联系很紧密就把他们放到了一起去开发维护,我想这个系统后面必定会难以维护,最终进行拆分。
而系统拆分的指导思想就是我们耳熟能详的六个字:『高内聚,低耦合。』 领域驱动设计有着一套完整的方法论,指导我们对复杂问题进行拆分、梳理各个子系统间的关系,帮助我们落地复杂系统。
]]>在加乌拉山口拍珠峰,这里可以一眼望尽五座八千米高山。
旅行是为了什么?有的人是为了好吃好玩,有的人是为了看风景。我就是后者,如果你喜欢游览祖国广袤的山川河流,看尽一望无际的高原雪山,那么你一定要来趟西藏,看看还是那么相对纯粹的自然风光。
去西藏的计划是从八月推迟到了十一黄金周,不过今年西藏的十一却少了往日的火爆,人不是那么多,主要还是因为前一段事件疫情的影响,所以路上的体验都还不错。
全国除了西藏都是可以想去就去的,而西藏是需要好好准备一下的,为此也踩了一些坑。就拿抗高反来说吧,去之前你需要做的是好好休息,减少运动量就可以了,不用喝什么红景天、高原康之类的,作用不大。在高原防止高反只需要做到以下几点即可:
刚去的前几天高反加水土不服,在羊卓雍措的湖边住房车,大半夜的跑到车外吐了好几次,第二天买了藿香正气就好了,然后就是听司机的话,觉得不舒服就喝点葡萄糖,确实很有效。
到了西藏,面对壮阔的雪山群,纯净的圣湖,当然要留下一些回忆。
在海拔 5200 米的珠峰大本营,珠峰山顶总被云雾缠绕着,能近距离的看到很难得。
羊卓雍措是西藏的三大圣湖之一,从山上看下去,湖水呈靛青色,就跟染料一般纯净,和其它湖水的蓝很不一样。
纳木错也是西藏的三大圣湖之一,蓝色湖水对面的雪山就是念青唐古拉山脉。
在西藏还有一点独特的体验就是看星空,与平原不同地是这里的高海拔,使得星空银河非常清晰明显,你会看到银河从地上的一端穿过天空扎向另一端。站在山顶的那一刻我终于体会到儿时的一句话『天空是一块幕布,星星和月亮就是装饰这块幕布的花纹。』望着繁星与银河,感觉到自己渺小,如这沧海中的一粟。
西藏之旅还有很多有意思的故事与见闻,虽然这篇文章鸽了这么久,但是我还是不想再去讲了。总之这是一场头疼并快乐的旅途~
]]>调研环境说明
etcd –version
etcd Version: 3.5.1
Git SHA: d42e8589e
Go Version: go1.17.2
Go OS/Arch: darwin/amd64
启动参数
1 | --name etcd-1 // 节点名称 |
关于自动压缩
https://etcd.io/docs/v3.4/op-guide/maintenance/#defragmentation
--auto-compaction-mode=revision --auto-compaction-retention=1000
每5分钟自动压缩”latest revision” - 1000--auto-compaction-mode=periodic --auto-compaction-retention=12h
每1小时自动压缩并保留12小时窗口。自动压缩碎片后还需要单独再清理占用的系统存储空间,etcdctl defrag
。
单机环境写集群搭建,以下是端口映射
etcd1 2379 -> 2391 2380 -> 2381
etcd2 2379 -> 2392 2380 -> 2382
etcd3 2379 -> 2393 2380 -> 2383
1 | ETCDCTL_API=3 etcd --name etcd-1 --data-dir /Users/noogel/Debug/data/etcd1 --initial-advertise-peer-urls http://127.0.0.1:2381 --listen-peer-urls http://127.0.0.1:2381 --listen-client-urls http://127.0.0.1:2379 --advertise-client-urls http://127.0.0.1:2379 --initial-cluster-token etcd-lock-cluster-1 --initial-cluster etcd-1=http://127.0.0.1:2381,etcd-2=http://127.0.0.1:2382,etcd-3=http://127.0.0.1:2383 --initial-cluster-state new --heartbeat-interval 1000 --auto-compaction-mode=revision --auto-compaction-retention=1000 --quota-backend-bytes 8589934592 --election-timeout 5000 > /Users/noogel/Debug/data/etcd1/run.log 2>&1 & |
1 | rm -rf /Users/noogel/Debug/data/etcd1 |
1 | export ETCDCTL_API=3 |
1 | // 添加 root 用户 |
在生产机通过 systemd
启动。第一次启动命令--initial-cluster-state new
,后续节点的增加需要修改为 --initial-cluster-state existing
,不明白看节点增加部分。
修改配置
1 | // 编辑配置 |
Etcd 默认不会自动 compact,需要设置启动参数,或者通过命令进行compact,如果变更频繁建议设置,否则会导致空间和内存的浪费以及错误。Etcd v3 的默认的 backend quota 2GB,如果不 compact,boltdb 文件大小超过这个限制后,就会报错:”Error: etcdserver: mvcc: database space exceeded”,导致数据无法写入。
要从空间不足配额警报中恢复:
1 | # 1、获取当前的版本 |
需要注意的是整理碎片释放空间,要一个一个节点执行,因为在执行期间节点是无响应的,直到处理完。防止因为全部节点无响应导致的服务不可用
压缩key空间后,会出现内部碎片,这些压缩出来的碎片空间可以被etcd使用,但是不会真正的释放物理空间,需要进行碎片整理,如:
1 | $ etcdctl defrag |
环境中复刻。可以使用–cluster标记指定所有成员以自动查找所有集群成员。如:
1 | $ etcdctl defrag --cluster |
1 | # 查看成员信息 |
需要先移除故障节点成员,再添加进去成员列表。然后清理掉故障节点的工作目录内容,之后再启动服务,启动后服务会自动同步数据。
其中启动命令需要设置为 --initial-cluster-state existing
。
http://www.zhaowenyu.com/etcd-doc/ops/data-space-manage.html
https://xieys.club/etcd-backup-restore
https://www.cnblogs.com/lowezheng/p/10307592.html
https://bbotte.github.io/service_config/etcd-cluster-troubleshooting.html
https://www.mytecdb.com/blogDetail.php?id=211
https://www.cnblogs.com/tencent-cloud-native/p/14893209.html
http://www.dockone.io/article/2955
https://mytecdb.com/blogDetail.php?id=199
作为一个北方汉子对于面食真的是十分热爱的,尤其是发面后的。从小在家兜包子都是只能看不让参与的,长大后在外面都是买现成的,如今也想自己做一做。也许是从小在家耳濡目染,第一次做整体的效果还不错。
宋丹丹老师曾经说过把大象装进冰箱总共要分三步,那么蒸包子总共需要分为如下五步:
可是,真的就是只蒸了包子吗?
就像我之前聊到过的,复杂的东西是由许多简单的东西组成的。就像蒸包子这件事不一定多么复杂,但也不那么简单,要想做好也是要拆分成上面五个相对简单的步骤执行。作为一个后端工程师,现在做事情总是会带入一些思维模型去看,这个例子是分治法来将蒸包子这件事简单化,只要我们把其中简单的每一步做好就可以最终把包子蒸好。
当我把包子蒸好以后,发现耗时很长,那么怎么提高效率呢。刨除在网上买菜等菜的时间,大致的时间线如下:
发面准备:15:00 -> 15:30
发面期间:15:30 -> 17:00
做、调馅:16:30 -> 17:30
擀皮 :17:30 -> 18:00
包、蒸 :18:00 -> 19:00
总计人力耗时 3 小时,出锅 18 个包子。如果我要是蒸 36 个包子就要消耗 6 小时的时间吗?答案是要小于 6 小时的,主要原因有以下几点:
但是我要再多蒸一倍的包子是不是平均耗时会更短,这个就不一定了,如果蒸锅的承载上限就是 36 个包子,那平均耗时就不会再减少。
批处理的方案在一定程度上会提高我们的效率,但不会无限提高,而是有一个最优解,这个最优解取决于外在条件。
以上情况分析的是单人力情况下,如果再有一个人一起做。可以有以下方面提升:
最后大致的总人力耗时会大于 3 小时,平均人力耗时在 1.5 到 2小时之间。并发思维又是我们另外一个手段。
上面需要人操作的蒸包子需要四步,其中每一步都会有一些内耗是在每步切换时都需要思考下一步该怎么做,以及准备对应步骤的工具撤掉上一步骤的工具。如果我们厨房能供四人同时使用,并且每人只做一步的事,那么每一步的耗时就会因为熟能生巧而使时间大大缩短。如果这是一家包子店的厨房,那么这四个人就可以源源不断的高效生产包子。通过流水线模型来提高效率,这也是并发的一种。
再假如,北京所有的人早餐都要来这家店吃包子,那么任凭这四个人怎么日夜生产,也不能满足整个北京的需求。如果还是这四个人怎么做?如果恰好其中有一个人学过机械相关的知识并且动手能力又很强,这时候他可以和其他三个人一起交流蒸包子的心得,然后结合整个知识,设计出蒸包子机器,然后找工厂生产出几十台日夜生产,我想北京的包子供应应该就没问题了。他们四个人只需要盯盯机器,坏了修一修就好了。这里面就用到了工具化思维,可以极大的提高我们的生产效率。工业革命的意义之一就是创造了巨大的生产力。
说了这么多,下面附上我的蒸包子攻略~
加水的时候需要慢慢加慢慢抓,慢慢会形成一个光滑的圆团,面团的软硬跟水的多少有关,如果面硬的话可以适当加一些水调整软硬程度。
其中老抽用来调色,生抽、鸡精、耗油用来调味,胡椒粉、姜碎、料酒用来去腥,加一些香油可以让肉馅变得超级香。
最后来一张出锅照,年轻人的第一锅包子就这样做好了。
工作和生活中我们会遇到很多事和物,事事物物之间有很多共通之处,包括问题的产生和解决办法。不同表象的背后相同的本质的东西是思维方式还是抽象模型?看清它们,能带给我的是做出好吃的包子,不仅仅是这些,还有更多。
]]>复杂或复杂性与简单相对立,那么复杂是什么?它是我们大脑中的一个概念,但是我在网上找不到一个给复杂恰当的定义描述,它会有不同的解释。
其中洛克在《人类理解论》中说道:『一些思想是由简单的思想组合而成,我称此为复杂;比如美、感激、人、军队、宇宙等。』
作为研究复杂系统的专家 Melanie Mitchell,也没有给出一个明确的公认的定义。她在《复杂》一书中给出了复杂系统加以定义:『复杂系统是由大量组分组成的网络,不存在中央控制,通过简单运作规则产生出复杂的集体行为和复杂的信息处理,并通过学习和进化产生适应性。』
上述复杂系统中的组分对应软件系统中的组成部分,基于不同粒度可以是对象、函数、类、包、模块、组件和服务等。每一部分都应该是相对单一的职责,细粒度部分之间耦合提供更粗粒度功能,不同组分之间相互协作来提供系统功能,继而组合成我们复杂的软件系统。
计算机的产生对我们生产生活产生的影响不言而喻,其中软件系统的功能是随着我们实际生活需求的变化而变化的。人有七情六欲带来的各种需求,接收信息的方式主要是视觉、听觉。而机器擅长的只是简单的逻辑处理和数值计算,两者之间有着巨大的鸿沟。如何让机器提供视觉和听觉的手段来满足人们的需求,这里抛开硬件不谈,软件层面有操作系统提供基本的软件运行环境。
软件系统则只需要专注于如何组织和管理数据来满足人们的工作生活娱乐需求,一方面要关注人的需求和需求变化,另一方面要关注机器层面能提供的计算能力。
软件系统的复杂性来自于两个方面,一方面是需求侧复杂,导致大多数系统的功能都难以理解;另一方面是难以把控需求的变化,虽然我们遵循一些设计原则可以对未来进行一些预判,但还是存在不可预测的风险。
在《复杂》一书中作者列举了不同角度可能度量复杂性的方法。
复杂度并没有一个统一明确的度量方式,我们可以站在一个角度上对具体的某类或粒度提供一个可供参考的度量方法。不论我们如何度量,我们在开发软件系统中的一个重要目标就是控制和降低系统复杂度。在巨著《人月神话》中提出了两个重要概念:
偶然复杂度不是待求解问题的本质,相对而言, 本质复杂度和待求解问题的本质有关,是无法避免的。偶然复杂度一般是因为选用求解问题的方法时所引入的。
在源代码层面为了描述工程质量有以下两个方面衡量:
在认知复杂度的计算方法中主要基于以下三条规则:
下面实例对比两种复杂度度量方法的差异,在不同写法上圈复杂度的统计和认知复杂度的统计有何差异。
上图是两种写法在圈复杂度的统计方法,得出的值都是 4,也就是从逻辑上来说是相同的。但是在可读性上来说,明显右侧的 switch 代码更高。认知复杂度就是为了度量人的易于理解性上存在的。
以上是认知复杂度算法给这两种方法打出了明显不同的分数,这些分数更能反映出它们的相对可理解性。更具体的内容可以查看 CognitiveComplexity 。
架构的本质目标就是管理复杂度,而管理复杂度有以下三种有效的手段:
以 Drools 为例子
Drools 是一个基于Charles Forgy’s的RETE算法的,易于访问企业策略、易于调整以及易于管理的开源业务规则引擎,符合业内标准,速度快、效率高。业务分析师人员或审核人员可以利用它轻松查看业务规则,从而检验是否已编码的规则执行了所需的业务规则。
Drools相关概念
- 事实(Fact):对象之间及对象属性之间的关系
- 规则(rule):是由条件和结论构成的推理语句,一般表示为if…Then。一个规则的if部分称为LHS,then部分称为RHS。
- 模式(module):就是指IF语句的条件。这里IF条件可能是有几个更小的条件组成的大条件。模式就是指的不能在继续分割下去的最小的原子条件。
Drools通过 事实、规则和模式相互组合来完成工作,drools在开源规则引擎中使用率最广,但是在国内企业使用偏少,保险、支付行业使用稍多。
「规则引擎主要完成的就是将业务规则从代码中分离出来。」 在规则引擎中,利用规则语言将规则定义为if-then的形式,if中定义了规则的条件,then中定义了规则的结果。规则引擎会基于数据对这些规则进行计算,找出匹配的规则。这样,当规则需要修改时,无需进行代码级的修改,只需要修改对应的规则,可以有效减少代码的开发量和维护量。
易用性、广泛性、高性能、高可用、高一致性等方面。
Java开源的规则引擎有:Drools、Easy Rules、Mandarax、IBM ILOG。使用最为广泛并且开源的是Drools。
规则引擎优点
- 声明式编程
- 逻辑和数据分离
- 速度和可扩展性
- 知识集中化
规则引擎缺点
- 复杂性提高
- 需要学习新的规则语法
- 引入新组件的风险
Drools规则引擎的结构示意图:
在 Drools 中,规则被存 放在 Production Memory(规则库)中,推理机要匹配的 facts(事实)被存在 Working Memory(工作内存)中。当时事实被插入到工作内存中后,规则引擎会把事实和规则库里的模式进行匹配,对于匹配成功的规则再由 Agenda 负责具体执行推理算法中被激发规则的结论部分,同时 Agenda 通过冲突决策策略管理这些冲突规则的执行顺序。
Drools 中规则冲突决策策略有
- 优先级策略
- 复杂度优先策略
- 简单性优先策略
- 广度策略
- 深度策略
- 装载序号策略
- 随机策略
Rete 算法
最初是由卡内基梅隆大学的 Charles L.Forgy 博士在 1974 年发表的论文中所阐述的算法 , 该算法提供了专家系统的一个高效实现。自 Rete 算法提出以后 , 它就被用到一些大型的规则系统中 , 像 ILog、Jess、JBoss Rules 等都是基于 RETE 算法的规则引擎。Rete 在拉丁语中译为”net”,即网络。Rete 匹配算法是一种进行大量模式集合和大量对象集合间比较的高效方法,通过网络筛选的方法找出所有匹配各个模式的对象和规则。
其核心思想是将分离的匹配项根据内容动态构造匹配树,以达到显著降低计算量的效果。Rete 算法可以被分为两个部分:规则编译和规则执行。当Rete算法进行事实的断言时,包含三个阶段:匹配、选择和执行,称做 match-select-act cycle。
Drools 中的 Rete 算法被称为 ReteOO,表示 Drools 为面向对象系统(Object Oriented systems)增强并优化了 Rete 算法。
从Drools规则引擎的使用模版来看,输入、输出和判断三个中,判断是变化的,而输入和输出是基本固定的,所以适用的场合可以分为下面几种:
- 输入和输出的参数不变,即:规则文件接收固定的参数,产生固定的输出。比如:根据货物重量计算运输价格,输入参数是货物重量,规则根据级差价格表,输出运输价格。
- 输入和输出的JavaBean Object不变,即:规则文件接收固定类型的JavaBean,产生固定类型的JavaBean。比如:根据顾客信息和当前购物信息计算优惠价格,输入参数是顾客当前的类别(VIP客户等)和当前购物的种类、数量,规则根据顾客类别、商品种类和购买数量输出优惠价格。
所以,规则引擎适用于「问题确定」的场景,并且存在比较复杂的业务规则并且业务规则会「频繁变动」的系统。比如:
- 风险控制系统(风险贷款、风险评估)
- 反欺诈项目(银行贷款、征信验证)
- 决策平台系统(财务计算)
- 促销平台系统(满减、打折、加价购)
TODO
TODO
]]>网络文章、杂志专栏、论文等
https://blog.csdn.net/Taobaojishu/article/details/108231696
模板的好处就在于可以让你快速且全面的规划方案,一些你能想到和不能想到的地方。既能避免你在做旅游规划时漏掉什么,又能节省你思考的时间。有了模板你只需要按照大纲去调研即可。
如果你旅游前需要详细规划,那么这个模板很适合你。
如果你是想走开车就走的那种,可以跳过这篇。
模板按需填充即可,比如去西藏就需要特别关注海拔信息。去三亚就需要关注一下日出日落和潮汐时间。下面是我在做西藏旅游攻略做的一个简单攻略,仅供参考。
类别 | 明细(气温:白天 15 - 20 ;夜间 5 - 10) |
---|---|
其它装备 | 墨镜(镜夹)、遮阳帽、魔术头巾 |
上衣类 | 保暖衣两件 冲锋衣(防风) 轻便羽绒服 |
下衣类 | 保暖裤两件 冲锋裤(防风) |
鞋袜 | 徒步鞋一双 薄袜 5 双 |
洗漱类 | 旅行牙刷牙膏、便携洗发露、毛巾,(不要洗澡) |
工具类 | 充电装备:充电宝 2 个、充电器、充电线 2 根 拍摄装备:相机一个、电池两块、三脚架、B门 垃圾袋 10 个、湿巾 50 片、保温杯、口罩10个 |
关键物品 | 证件信息:身份证、社保卡、驾驶证 现金:500 分两处 |
关键药品 | 药品信息:藿香正气液(治水土不服)、感冒药、头晕药、葡萄糖 『我去过27次藏区,带过300多人,我的经验是在头疼腿疼时候,遵医嘱服用8分钱一包的阿咖酚散就很灵验』 |
日期(星期) | 行程 | 行程安排 | 其他备注 |
---|---|---|---|
9-30 | 准备做核酸检测 | ||
10-1 五 | 0 | 北京准备 | 理短发 |
10-2 六 | 1 | 北京 -> 正定 待定 西藏航空 TV9980 正定 T2 18:25 -> 贡嘎 T3 22:45 | 9-18 买票 洗澡洗头 |
10-3 日 | 2 | ||
10-4 一 | 3 | ||
10-5 二 | 4 | 洗头 | |
10-6 三 | 5 | ||
10-7 四 | 6 | ||
10-8 五(假) | 7 | 洗头 | |
10-9 六(假) | 8 | ||
10-10 日 | 9 | ||
10-11 一(假) | 10 | 西藏航空 TV9927 贡嘎 T3 12:25 -> 滨海T2 17:55 天津 -> 北京 待定 | 9-27 买票 回京洗头 |
地点 | 推荐菜品 | 费用/注意事项 |
---|---|---|
拉萨 | 酥油茶 | |
糌粑 | ||
藏式白肠 | ||
藏香猪 | ||
鲁朗石锅鸡 |
景点分布图
景点明细
景点 | 地点 | 海拔 | 图片 | 备注 |
---|---|---|---|---|
那根拉山口 | 当雄县境内,是通往纳木错的必经之地,也是藏民心中的神圣之地。 | 海拔达5190米 | ||
圣象天门 | 那曲地区班戈县青龙乡5村境内 ,圣湖纳木措北部恰多朗卡岛上。 | 4800米 | 日落星空银河 宿青龙小镇 | |
纳木错 | 位于西藏自治区中部,是西藏第二大湖泊,也是中国第三大的咸水湖。 | 湖面海拔4718米 | ||
藏北草原 | ||||
念青唐古拉山口 | ?? | |||
羊八井温泉 | 拉萨市西北91.8公里的当雄县境内 | 海拔4231米 | 途径 | |
尼木县 | 居雅鲁藏布江中游北岸,地势是北高南低,尼木河两岸为代表的河谷地区,地势平坦。 | 海拔: 3818米 | ||
日喀则 | 日喀则市区海拔3836米,市区海拔要比拉萨高。 | 宿 | ||
嘉措拉山口 | 5248 | 待一小时 | ||
定日 | 日喀则市 | 驻地协格尔海拔4300米。平均海拔5000米。 | ||
珠峰大本营 | 5200 | 看星空日落 | ||
加乌拉山口 | 前往珠穆朗玛峰大本营途中的一个垭口。在世界上唯一可以观赏5座8000米级雪峰的观景平台。 | 海拔5210米 | 一眼看尽5座8km雪山 | |
奇林峡 | 日喀则地区定结县,处于尼泊尔、印度边境。 | 5018米 | 大自然的奇观 | |
扎什伦布寺 | 日喀则市城西的尼玛山东面山坡上 | 海拔高度3874米 | ||
卡若拉冰川 | 冰舌前沿海拔5560米,观看卡若拉冰川的地方海拔约有5400米 | |||
拉满水库 | 日喀则地区江孜县龙马乡境内年楚河上游 | 坝址区高程4200~4300米 | ||
羊卓雍措(羊湖) | 湖面海拔4,441米。 | |||
日托寺 | 坐落在羊湖内湖深处半岛山顶的寺庙 | |||
以下七日团景点 | ||||
思金拉措 | 墨竹工卡县日多乡东南、山南地区桑日县增期乡以北,距川藏公路(318国道)约6公里,距墨竹工卡县城66公里,距拉萨市区124公里 | 海拔4500米 | ||
林芝 | 西藏东南部,雅鲁藏布江中下游 | 平均海拔3100米 | ||
雅鲁藏布大峡谷 | 主体在墨脱县 | 2880米 | ||
索松村 | 林芝市米林县派镇下辖自然村 | 3012米 | ||
米林朗县 | 林芝市,位于朗县位于林芝市西南部 | 平均海拔3200米 | ||
亲猴台 | ||||
达古峡谷 | 山南市桑日县与加查县交界处(属桑日县增期乡达古村,距桑日县县城40公里),离泽当有80km,距离拉萨220公里 | 3200米 | ||
拉姆拉措 | “拉姆拉措”正是被莲花捧起来的圣湖。 | 4000米以上 | ||
加查/泽当 | ||||
岗巴拉山 | 西藏山南地区浪卡子县和贡嘎县之间 | 4990米 | ||
推村 | 拉萨西南300多公里的蒙达岗日雪山脚下、普姆雍措湖畔,是世界上海拔最高的行政村落 | 5070米 | ||
杰岗日神山 | ||||
测东拉错 | ||||
色林措 | 申扎、班戈和尼玛3县交界处,冈底斯山北麓,申扎县以北。 | 湖面海拔4530m | ||
旅游团对比
编号 | A | B | C1 | C2 | D1 | D2 | E1 | E2 | F |
---|---|---|---|---|---|---|---|---|---|
渠道特点 | 微信关系5-4 | 国营飞猪房车团5-4 | 同A-国营飞猪5-4 | 同A-国营飞猪5-4 | 国营飞猪4-3 | 国营飞猪4-3 | 国营飞猪7-6 | ||
价格 | 3000 | 5000 | 2780 | 4680 | 2880 | 4780 | 1560-1880 | 2560 | 4180 |
链接 | 【拉萨国企】西藏拉萨旅游珠峰大本营圣象天门纳木错5天4晚五日游-旅游度假-飞猪 | 【拉萨国企】圣象天门珠峰大本营纳木错5天4晚五日游西藏拉萨旅游-旅游度假-飞猪 | 【拉萨国企】珠峰大本营羊湖纳木错圣象天门4天西藏拉萨旅游四日-旅游度假-飞猪 | 西藏拉萨林芝山南羊湖珠峰大本营纳木错圣象天门全景大环线7日游-旅游度假-飞猪 | |||||
行程 | |||||||||
行程特点 | 第一天去的海拔5000米,容易有高反。 | 第一天海拔4400,第二天才到 5000,舒适。 | 第一天去的海拔5000米,容易有高反。 | * 第一天去的海拔5000米,容易有高反。 * 比A多了羌塘无人区行程,都在车上。 | * 第一天去的海拔5000米,容易有高反。 * 自己做动车从日喀则回拉萨。 * 比五天团少羊卓雍措景点。 | 大环线,海拔由低到高。 穿越羌塘无人区。 | |||
车辆 | 4-5人房车团配无人机 | 7-17座正规旅游车 | 5座越野车 | 7-17座正规旅游车 | 5座越野车 | 7-17座正规旅游车 | 7-9座正规旅游车,无人机 | 无车辆信息 | |
住宿 | 1晚圣象天门藏家民宿多人间或多人帐篷(通铺)+日喀则2晚域腾酒店或同等级别酒店+1晚珠峰营地藏家民宿(多人间)或帐篷(通铺) | 日托寺、珠峰、圣象天门住房车上(房车无法洗澡),日喀则住三星或三钻酒店可洗澡,双人标间单床位价格,单人出行我们尽量拼房,如无人拼或不愿拼则需补房差。 | 近似A | 近似A | 近似A | 近似A | 近似A | 近似A | 近似A |
退改规则 | 订单生效后,因买家原因取消订单的,费用扣除标准如下:行程开始前【7日以上】扣除20%违约金;行程开始前【4-6日】扣除30%违约金;行程开始前【1-3日】扣除50%违约金;行程开始当天扣除100%违约金。 | 【买家违约】订单生效后,因买家原因取消订单的,费用扣除标准如下: 【商家违约】订单生效后,因商家原因取消订单的,除全额退款外,商家还应向买家支付下表对应金额的违约金: 行程开始前 违约金(占订单总费用) 7日以上 0%;4—6日 20%;1—3日 40%;行程开始当日 60% | 同 B | 同 B | 同 B | 同 B | 同 B | 同 B | 同 B |
提醒事项 | 无右侧提醒 | ②.因珠峰大本营海拔较高达5200左右,所有含珠峰行程司机均会带大家前往氧气及大衣补给站点(医用型钢瓶氧气3L装约300,10L装大氧气约600左右,军大衣100左右,费用较高),请根据自身身体是否有高反等情况自愿选择绝无强制消费,我社建议在拉萨提前自带便携社按压氧气(1L装约20左右一瓶,各大超市药店均有售,多带两个一般即可),此氧气补给点并非购物点环节,有人需要有人不需要请理性客观对待及选择,特此告知说明。 | 同A |
PS: 当地的风俗禁忌;
特别提示:出现高原反应的最大因素,是心里因素!心里的恐慌和压力,是造成高反的最主要原因!所以,我们应该放松心态去旅行。
珠峰大本营需要提前办理边防证。
https://www.zhihu.com/question/21451875
拉萨
日期 | 日出 | 日中 | 日落 |
---|---|---|---|
2021年10月01日 周五 | 07:48:46 | 13:45:02 | 19:41:19 |
2021年10月02日 周六 | 07:49:20 | 13:44:43 | 19:40:07 |
2021年10月03日 周日 | 07:49:54 | 13:44:24 | 19:38:55 |
2021年10月04日 周一 | 07:50:28 | 13:44:06 | 19:37:44 |
2021年10月05日 周二 | 07:51:03 | 13:43:48 | 19:36:33 |
2021年10月06日 周三 | 07:51:38 | 13:43:30 | 19:35:22 |
2021年10月07日 周四 | 07:52:13 | 13:43:13 | 19:34:12 |
2021年10月08日 周五 | 07:52:48 | 13:42:56 | 19:33:03 |
2021年10月09日 周六 | 07:53:24 | 13:42:39 | 19:31:54 |
2021年10月10日 周日 | 07:54:01 | 13:42:23 | 19:30:46 |
2021年10月11日 周一 | 07:54:37 | 13:42:08 | 19:29:38 |
参考:[拉萨市 2021年10月份 日出日落时刻查询 - 拉萨市日出日没时刻 - 查询拉萨市日出时间 - 查询拉萨市日落时间 (bmcx.com)](https://richurimo.bmcx.com/lasa__time__2021_10__richurimo/)
日期 | 满潮 | 干潮 | 满潮 | 干潮 |
---|
拉萨气温
【拉萨天气】拉萨40天天气预报,拉萨更长预报,拉萨天气日历,拉萨日历,15天天气预报,天气预报一周
北京气温
TODO
天气多变不可琢磨
地点 | 经纬 | 海拔(米) |
---|---|---|
拉萨 | 东经91°06′,北纬29°36′ | 3650 |
珠峰大本营 | 5200 | |
那根拉山口 | 5190 | |
圣象天门 | 4800 | |
日喀则 | 3836 | |
羊卓雍措 | 湖面海拔4441米 |
TODO
地点 | 交通设施 | 关键距离 |
---|---|---|
— | — | — |
天气 | 气温℃ | 穿衣建议 |
---|---|---|
酷热,很不舒适 | ≥40 | 停止户外作业,对老弱病幼人群采取保护措施 |
暑热,不舒适 | ≥37 | 避免在高稳时段进行户外活动,老弱病幼落实防暑降温保护措施。 |
闷热,不舒适 | ≥35 | 天气闷热,适宜着丝麻、轻棉织物制作的短衣、短裙、薄短裙、短裤等夏季服装。午后尽量减少户外活动,高温条件下作业和露天作业人员采取必要防护措施。 |
炎热,不舒适 | 33~34.9 | 天气炎热,适宜着短衫、短裙、短裤、薄型T恤衫、敞领短袖棉衫等夏季服装。 |
热,较不舒适 | 28~32.9 | 天气较热,适宜着棉麻面料的衬衫、薄长裙、薄T恤等夏季服装。年老体弱者:长袖衬衫和单裤。 |
热舒适 | 25~27.9 | 天气偏热,适宜着短衫、短裙、短套装、T恤等夏季服装。年老体弱者:单层薄衫裤、薄型棉衫。 |
较舒适 | 23~24.9 | 天气暖和,适宜着单层棉麻面料的短套装、T恤衫、薄牛仔衫裤、休闲服、职业套装等春秋过渡装。年老体弱者请适当增减衣服。 |
舒适 | 21~22.9 | 天气温暖,适宜着长袖衬衫加单裤、单层薄衫裤、薄型棉衫等春秋过渡装;年老体弱者:针织长袖衬衫+背心、长裤、薄型套装。 |
凉舒适 | 18~20.9 | 天气温和,适宜着 单层薄衫裤、薄型棉衫、长裤、针织长袖衫、长袖 T 恤、薄型套装、牛仔衫裤、西服套装、薄型夹克等春秋过渡装;年老体弱者宜着针织长袖衬衫 + 马甲、长裤、夹克衫、西服套装等。 |
温凉,较舒适 | 15.0~17.9 | 天气温凉,适宜着夹衣、马甲衬衫、长裤、夹克衫、西服套装加薄羊毛衫等春秋服装。年老体弱者:夹衣或风衣加羊毛衫。 |
微凉 | 13~14.9 | 天气微凉,适宜着一件羊毛衫、夹克衫、西服套装、马甲衬衫+夹克衫配长裤等春秋着装;年老体弱者:厚外套加毛衣、呢外套加羊毛衫。 |
较凉 | 11~12.9 | 天气较凉,适宜着厚外套加毛衣、大衣、毛套装、西服套装等春秋服装。体弱者宜着大衣、毛衣加呢外套等厚型春秋服装。 |
凉 | 8~10.9 | 天气凉,适宜着一到两件羊毛衫、大衣、毛套装、皮夹克等春秋着装;年老体弱者宜着大衣、夹衣或风衣加羊毛衫等厚型春秋着装。 |
微冷 | 5~7.9 | 天气微冷,适宜着毛衣、风衣、大衣、皮夹克、外套、毛套装、西装、防寒服等厚型春秋着装;老年体弱者,冬季着装:一到两件羊毛衫+大衣或毛套装、薄棉外套等。 |
较冷 | 0~4.9 | 天气微冷,适宜着毛衣、风衣、大衣、皮夹克、外套、毛套装、西装、防寒服等厚型春秋着装;老年体弱者,冬季着装:一到两件羊毛衫+大衣或毛套装、薄棉外套等。 |
冷 | -4.9~0 | 天气冷,冬季着装:棉衣、羽绒衣、冬大衣、皮夹克、毛衣再外罩大衣等;年老体弱者尤其要注意保暖防冻。 |
寒冷(风力≤4) | -9.9~-5 | 天气寒冷,冬季着装:棉衣、羽绒服、冬大衣、皮夹克加羊毛衫、厚呢外套、呢帽、手套等;年老体弱者尽量少外出。 |
很冷(风力≥4) | -9.9~-5 | 温度极低,尽量少外出;建议着厚棉衣、厚羽绒服、冬大衣、皮夹克、裘皮大衣、棉(皮)帽、棉(皮)手套、棉(皮)靴等隆冬着装。 |
极冷 | ≤-10 | 温度极低,尽量少外出;建议着厚棉衣、厚羽绒服、冬大衣、皮夹克、裘皮大衣、棉(皮)帽、棉(皮)手套、棉(皮)靴等隆冬着装。 |
重构分为三个层次:
复杂度问题的应对办法,防止偶然复杂性。
TODO
]]>