feat: update duplicate files and generate pagination

移除冗余文件,激活页面底部导航
This commit is contained in:
yanglbme
2020-04-05 16:25:25 +08:00
parent d497fffafb
commit ee7f18e797
177 changed files with 241 additions and 201 deletions

View File

@@ -12,7 +12,7 @@
下图展示的是这种架构:
![deployment-strategy-1](/images/deployment-strategy-1.png)
![deployment-strategy-1](./images/deployment-strategy-1.png)
这种模式有一些参数,一个参数代表每个服务实例由多少进程构成。例如,需要在 Apache Tomcat Server 上部署一个 Java 服务实例作为 web 应用。一个 Node.js 服务实例可能有一个父进程和若干个子进程构成。
@@ -40,7 +40,7 @@
但是用单虚拟机单实例模式一般将服务打包成虚拟机映像image例如一个 Amazon EC2 AMI。每个服务实例是一个使用此映像启动的 VM例如EC2 实例)。下图展示了此架构:
![deployment-strategy-2](/images/deployment-strategy-2.png)
![deployment-strategy-2](./images/deployment-strategy-2.png)
Netfix 采用这种架构部署 video streaming service。Netfix 使用 Aminator 将每个服务打包成一个 EC2 AMI。每个运行服务实例就是一个 EC2 实例。
@@ -72,7 +72,7 @@ CloudNative 公司有一个用于创建 EC2 AMI 的 SaaS 应用Bakery。用
下图展示了这种模式:
![deployment-strategy-3](/images/deployment-strategy-3.png)
![deployment-strategy-3](./images/deployment-strategy-3.png)
使用这种模式需要将服务打包成容器映像。一个容器映像是一个运行包含服务所需库和应用的文件系统​。某些容器映像由完整的 linux 根文件系统组成,其它则是轻量级的。例如,为了部署 Java 服务,需要创建包含 Java 运行库的容器映像,也许还要包含 Apache Tomcat server以及编译过的 Java 应用。

View File

@@ -21,7 +21,7 @@
相反的,微服务架构下,订单和客户表分别是相对应服务的私有表,如下图所示:
![service table](/images/Private-table-of-the-corresponding-service.png)
![service table](./images/Private-table-of-the-corresponding-service.png)
订单服务不能直接访问客户表,只能通过客户服务发布的 API 来访问。订单服务也可以使用 distributed transactions, 也就是周知的两阶段提交 (2PC)。然而2PC 在现在应用中不是可选性。根据 CAP 理论必须在可用性availability和 ACID 一致性consistency之间做出选择availability 一般是更好的选择。但是,许多现代科技,例如许多 NoSQL 数据库,并不支持 2PC。在服务和数据库之间维护数据一致性是非常根本的需求因此我们需要找其他的方案。
@@ -35,15 +35,15 @@
1. 订单服务创建一个带有 NEW 状态的 Order (订单),发布了一个 “Order Created Event创建订单” 的事件。
![Order-Created-Event](/images/Order-Created-Event.png)
![Order-Created-Event](./images/Order-Created-Event.png)
2. 客户服务消费 Order Created Event 事件,为此订单预留信用,发布 “Credit Reserved Event信用预留” 事件。
![Credit-Reserved-Event](/images/Credit-Reserved-Event.png)
![Credit-Reserved-Event](./images/Credit-Reserved-Event.png)
3. 订单服务消费 Credit Reserved Event ,改变订单的状态为 OPEN。
![Status-is-OPEN](/images/Status-is-OPEN.png)
![Status-is-OPEN](./images/Status-is-OPEN.png)
更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。
@@ -51,7 +51,7 @@
亦可以使用事件来维护不同微服务拥有数据预连接pre-join的实现视图。维护此视图的服务订阅相关事件并且更新视图。例如客户订单视图更新服务维护客户订单视图会订阅由客户服务和订单服务发布的事件。
![pre-join](/images/pre-join.png)
![pre-join](./images/pre-join.png)
当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。可以使用文档数据库(例如 MongoDB来实现客户订单视图为每个用户存储一个文档。客户订单视图查询服务负责响应对客户以及最近订单通过查询客户订单视图数据集的查询。
@@ -65,7 +65,7 @@
获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions技巧在于一个 EVENT 表,此表在存储业务实体数据库中起到消息列表功能。应用发起一个(本地)数据库交易,更新业务实体状态,向 EVENT 表中插入一个事件,然后提交此次交易。另外一个独立应用进程或者线程查询此 EVENT 表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示:
![multi-step process](/images/multi-step-process.png)
![multi-step process](./images/multi-step-process.png)
订单服务向 ORDER 表插入一行,然后向 EVENT 表中插入 Order Created event事件发布线程或者进程查询 EVENT 表,请求未发布事件,发布他们,然后更新 EVENT 表标志此事件为已发布。
@@ -77,7 +77,7 @@
另外一种不需要 2PC 而获得线程或者进程发布事件原子性的方式就是挖掘数据库交易或者提交日志。应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见:
![No-2PC-required](/images/No-2PC-required.png)
![No-2PC-required](./images/No-2PC-required.png)
此方法的例子如 LinkedIn Databus 项目Databus 挖掘 Oracle 交易日志根据变化发布事件LinkedIn 使用 Databus 来保证系统内各记录之间的一致性。
@@ -93,7 +93,7 @@ Event sourcing (事件源)通过使用根本不同的事件中心方式来
为了理解事件源工作方式,考虑事件实体作为一个例子。传统方式中,每个订单映射为 ORDER 表中一行,例如在 ORDER_LINE_ITEM 表中。但是对于事件源方式,订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够数据来重建订单状态。
![Event-sourcing](/images/Event-sourcing.png)
![Event-sourcing](./images/Event-sourcing.png)
事件是长期保存在事件数据库中,提供 API 添加和获取实体事件。事件存储跟之前描述的消息代理类似,提供 API 来订阅事件。事件存储将事件递送到所有感兴趣的订阅者,事件存储是事件驱动微服务架构的基干。

View File

@@ -20,11 +20,11 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
## 服务注册中心Eureka Server
我们在项目中引入 `Eureka Server` 的相关依赖,然后在启动类加上注解 `@EnableEurekaServer`,就可以将其作为注册中心,启动服务后访问页面如下:
![eureka-server-homepage.png](/images/eureka-server-homepage.png)
![eureka-server-homepage.png](./images/eureka-server-homepage.png)
我们继续添加两个模块 `service-provider``service-consumer`,然后在启动类加上注解 `@EnableEurekaClient` 并指定注册中心地址为我们刚刚启动的 `Eureka Server`,再次访问可以看到两个服务都已经注册进来了。
![eureka-instance-registered-currently.png](/images/eureka-instance-registered-currently.png)
![eureka-instance-registered-currently.png](./images/eureka-instance-registered-currently.png)
`Demo` 仓库地址https://github.com/mghio/depth-in-springcloud
@@ -33,7 +33,7 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
### 服务注册Register
注册中心提供了服务注册接口,用于当有新的服务启动后进行调用来实现服务注册,或者心跳检测到服务状态异常时,变更对应服务的状态。服务注册就是发送一个 `POST` 请求带上当前实例信息到类 `ApplicationResource``addInstance` 方法进行服务注册。
![eureka-server-applicationresource-addinstance.png](/images/eureka-server-applicationresource-addinstance.png)
![eureka-server-applicationresource-addinstance.png](./images/eureka-server-applicationresource-addinstance.png)
可以看到方法调用了类 `PeerAwareInstanceRegistryImpl``register` 方法,该方法主要分为两步:
1. 调用父类 `AbstractInstanceRegistry``register` 方法把当前服务注册到注册中心
@@ -41,34 +41,34 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
服务注册信息保存在一个嵌套的 `map` 中,它的结构如下:
![eureka-server-registry-structure.png](/images/eureka-server-registry-structure.png)
![eureka-server-registry-structure.png](./images/eureka-server-registry-structure.png)
第一层 `map``key` 是应用名称(对应 `Demo` 里的 `SERVICE-PROVIDER`),第二层 `map``key` 是应用对应的实例名称(对应 `Demo` 里的 `mghio-mbp:service-provider:9999`),一个应用可以有多个实例,主要调用流程如下图所示:
![eureka-server-register-sequence-chart.png](/images/eureka-server-register-sequence-chart.png)
![eureka-server-register-sequence-chart.png](./images/eureka-server-register-sequence-chart.png)
### 服务续约Renew
服务续约会由服务提供者(比如 `Demo` 中的 `service-provider`)定期调用,类似于心跳,用来告知注册中心 `Eureka Server` 自己的状态,避免被 `Eureka Server` 认为服务时效将其剔除下线。服务续约就是发送一个 `PUT` 请求带上当前实例信息到类 `InstanceResource``renewLease` 方法进行服务续约操作。
![eureka-server-instanceresource-renew.png](/images/eureka-server-instanceresource-renew.png)
![eureka-server-instanceresource-renew.png](./images/eureka-server-instanceresource-renew.png)
进入到 `PeerAwareInstanceRegistryImpl``renew` 方法可以看到,服务续约步骤大体上和服务注册一致,先更新当前 `Eureka Server` 节点的状态,服务续约成功后再用异步的方式同步状态到其它 `Eureka Server` 节上,主要调用流程如下图所示:
![eureka-server-renew-sequence-chart.png](/images/eureka-server-renew-sequence-chart.png)
![eureka-server-renew-sequence-chart.png](./images/eureka-server-renew-sequence-chart.png)
### 服务下线Cancel
当服务提供者(比如 `Demo` 中的 `service-provider`)停止服务时,会发送请求告知注册中心 `Eureka Server` 进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个 `DELETE` 请求带上当前实例信息到类 `InstanceResource``cancelLease` 方法进行服务剔除下线操作。
![eureka-server-instanceresource-cancellease.png](/images/eureka-server-instanceresource-cancellease.png)
![eureka-server-instanceresource-cancellease.png](./images/eureka-server-instanceresource-cancellease.png)
进入到 `PeerAwareInstanceRegistryImpl``cancel` 方法可以看到,服务续约步骤大体上和服务注册一致,先在当前 `Eureka Server` 节点剔除下线该服务,服务下线成功后再用异步的方式同步状态到其它 `Eureka Server` 节上,主要调用流程如下图所示:
![eureka-server-cancellease-sequence-chart.png](/images/eureka-server-cancellease-sequence-chart.png)
![eureka-server-cancellease-sequence-chart.png](./images/eureka-server-cancellease-sequence-chart.png)
### 服务剔除Eviction
服务剔除是注册中心 `Eureka Server` 在启动时就启动一个守护线程 `evictionTimer` 来定期(默认为 `60` 秒)执行检测服务的,判断标准就是超过一定时间没有进行 `Renew` 的服务,默认的失效时间是 `90` 秒,也就是说当一个已注册的服务在 `90` 秒内没有向注册中心 `Eureka Server` 进行服务续约Renew就会被从注册中心剔除下线。失效时间可以通过配置 `eureka.instance.leaseExpirationDurationInSeconds` 进行修改,定期执行检测服务可以通过配置 `eureka.server.evictionIntervalTimerInMs` 进行修改,主要调用流程如下图所示:
![eureka-server-evict-sequence-chart.png](/images/eureka-server-evict-sequence-chart.png)
![eureka-server-evict-sequence-chart.png](./images/eureka-server-evict-sequence-chart.png)
## 服务提供者Service Provider
@@ -77,17 +77,17 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
### 服务注册Register
一个服务要对外提供服务,首先要在注册中心 `Eureka Server` 进行服务相关信息注册,能进行这一步的前提是你要配置 `eureka.client.register-with-eureka=true`,这个默认值为 `true`,注册中心不需要把自己注册到注册中心去,把这个配置设为 `false`,这个调用比较简单,主要调用流程如下图所示:
![eureka-service-provider-register-sequence-chart.png](/images/eureka-server-register-sequence-chart.png)
![eureka-service-provider-register-sequence-chart.png](./images/eureka-server-register-sequence-chart.png)
### 服务续约Renew
服务续约是由服务提供者方定期(默认为 `30` 秒)发起心跳的,主要是用来告知注册中心 `Eureka Server` 自己状态是正常的还活着,可以通过配置 `eureka.instance.lease-renewal-interval-in-seconds` 来修改,当然服务续约的前提是要配置 `eureka.client.register-with-eureka=true`,将该服务注册到注册中心中去,主要调用流程如下图所示:
![eureka-service-provider-renew-sequence-chart.png](/images/eureka-service-provider-renew-sequence-chart.png)
![eureka-service-provider-renew-sequence-chart.png](./images/eureka-service-provider-renew-sequence-chart.png)
### 服务下线Cancel
当服务提供者方服务停止时,要发送 `DELETE` 请求告知注册中心 `Eureka Server` 自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类 `DiscoveryClient``shutdown` 方法加上注解 `@PreDestroy`,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示:
![eureka-service-provider-cancel-sequence-chart.png](/images/eureka-service-provider-cancel-sequence-chart.png)
![eureka-service-provider-cancel-sequence-chart.png](./images/eureka-service-provider-cancel-sequence-chart.png)
## 服务消费者Service Consumer
@@ -96,16 +96,16 @@ At Netflix, Eureka is used for the following purposes apart from playing a criti
### 获取服务列表Fetch
服务消费者方启动之后首先肯定是要先从注册中心 `Eureka Server` 获取到可用的服务列表同时本地也会缓存一份。这个获取服务列表的操作是在服务启动后 `DiscoverClient` 类实例化的时候执行的。
![eureka-service-consumer-fetchregistry.png](/images/eureka-service-consumer-fetchregistry.png)
![eureka-service-consumer-fetchregistry.png](./images/eureka-service-consumer-fetchregistry.png)
可以看出,能发生这个获取服务列表的操作前提是要保证配置了 `eureka.client.fetch-registry=true`,该配置的默认值为 `true`,主要调用流程如下图所示:
![eureka-service-consumer-fetch-sequence-chart.png](/images/eureka-service-consumer-fetch-sequence-chart.png)
![eureka-service-consumer-fetch-sequence-chart.png](./images/eureka-service-consumer-fetch-sequence-chart.png)
### 更新服务列表Update
由上面的 `获取服务列表Fetch` 操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心 `Eureka Server` 获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置 `eureka.client.registry-fetch-interval-seconds` 修改,默认为 `30` 秒,能进行这一步更新服务列表的前提是你要配置 `eureka.client.register-with-eureka=true`,这个默认值为 `true`。主要调用流程如下图所示:
![eureka-service-consumer-update-sequence-chart.png](/images/eureka-service-consumer-update-sequence-chart.png)
![eureka-service-consumer-update-sequence-chart.png](./images/eureka-service-consumer-update-sequence-chart.png)
## 总结

View File

@@ -13,7 +13,7 @@
单体应用程序可以取得成功,但越来越多的人对它们感到不满——尤其是在将更多应用程序部署到云的时候。变更周期被捆绑在一起——即使只是对应用程序的一小部分进行了更改,也需要重建和部署整个单体应用。随着时间的推移,通常很难保持良好的模块化结构,也更难以保持应该只影响该模块中的一个模块的更改。对系统进行扩展时,不得不扩展整个应用系统,而不能仅扩展该系统中需要更多资源的那些部分。
![sketch](/images/sketch.png)
![sketch](./images/sketch.png)
这些不满催生了微服务架构风格:将应用程序构建为服务套件。除了服务可独立部署、独立扩展的事实之外,每个服务还提供了一个牢固的模块边界,甚至允许以不同的编程语言编写不同的服务。他们也可以由不同的团队管理。
@@ -42,11 +42,11 @@
> 任何设计系统(广义上的)的组织都会产生一种设计,其结构是组织通信结构的副本。<br> —— 梅尔文•康威1967年
![conways-law](/images/conways-law.png)
![conways-law](./images/conways-law.png)
微服务采用不同的划分方式,它是围绕业务功能将系统拆分为多个服务 。这些服务为该业务领域采用广泛的软件实现,包括用户界面、持久化存储和任何外部协作。因此,团队是跨职能的,包括开发所需的全部技能:用户体验、数据库和项目管理。
![PreferFunctionalStaffOrganization](/images/PreferFunctionalStaffOrganization.png)
![PreferFunctionalStaffOrganization](./images/PreferFunctionalStaffOrganization.png)
以这种方式组建的一家公司是 [www.comparethemarket.com](http://www.comparethemarket.com/)。跨职能团队负责构建和运营每个产品,每个产品拆分为多个独立的服务,彼此通过消息总线来通信。
@@ -98,7 +98,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
和概念模型的去中心化决策一样,微服务也去中心化数据存储决策。虽然单体应用程序更喜欢单一的逻辑数据库做持久化存储,但企业往往倾向于一系列应用程序共用一个单一的数据库——这些决定是供应商授权许可的商业模式驱动的。微服务更倾向于让每个服务管理自己的数据库,或者同一数据库技术的不同实例,或完全不同的数据库系统 - 这就是所谓的[混合持久化](https://martinfowler.com/bliki/PolyglotPersistence.html)(Polyglot Persistence)。你可以在单体应用程序中使用混合持久化,但它更常出现在为服务里。
![decentralised-data](/images/decentralised-data.png)
![decentralised-data](./images/decentralised-data.png)
对跨微服务的数据来说,去中心化责任对管理升级有影响。处理更新的常用方法是在更新多个资源时使用事务来保证一致性。这个方法通常用在单体中。
@@ -111,7 +111,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
许多使用微服务构建的产品或系统都是由具有丰富的持续交付和持续集成经验的团队构建的。以这种方式构建软件的团队广泛使用基础设施自动化技术。如下面显示的构建管道所示。
![basic-pipeline](/images/basic-pipeline.png)
![basic-pipeline](./images/basic-pipeline.png)
由于这并不是一篇关于持续交付的文章,我们在这里只关注持续交付的几个关键特性。我们希望有尽可能多的信心确保我们的软件正常运行,因此我们进行了大量的**自动化测试**。想让软件达到“晋级”(Promotion)状态从而“推上”流水线,就意味着要在每一个新的环境中,对软件进行**自动化部署**。
@@ -119,7 +119,7 @@ Netflix 是遵循这一理念的一个很好的例子。尤其是,以库的形
我们看到团队大量的基础设施自动化的另一个领域是在管理生产环境中的微服务。与我们上面的断言(只要部署很无聊)相比,单体和微服务之间没有太大的区别,但是每个部署的运行环境可能会截然不同。
![micro-deployment](/images/micro-deployment.png)
![micro-deployment](./images/micro-deployment.png)
### 设计时为故障做好准备
使用服务作为组件的结果是需要设计应用程序以便它们能够容忍服务的失败。如果服务提供者商不可用任何服务呼叫都可能失败客户必须尽可能优雅地对此做出响应。与单体设计相比这是一个缺点因为它这会引入额外的复杂性来处理它。结果是微服务团队不断反思服务失败是如何影响用户体验的。Netflix 的 [Simian Army](https://github.com/Netflix/SimianArmy) 能够引发服务甚至数据中心的故障在工作日发生故障,从而来测试应用程序的弹性和监控能力。

View File

@@ -17,7 +17,7 @@ Martin Fowler 将这种现代化策略成为绞杀Strangler应用名字
Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用不可管理时这是最佳建议。换句话说,应该停止让单体式应用继续变大,也就是说当开发新功能时不应该为旧单体应用添加新代码,最佳方法应该是将新功能开发成独立微服务。如下图所示:
![1](/images/Law-of-Holes.png)
![1](./images/Law-of-Holes.png)
除了新服务和传统应用还有两个模块其一是请求路由器负责处理入口http请求有点像之前提到的 API 网关。路由器将新功能请求发送给新开发的服务,而将传统请求还发给单体式应用。
@@ -46,7 +46,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
3. 数据访问层——访问基础元素,例如数据库和消息代理。
在表现层与业务数据访问层之间有清晰的隔离。业务层有由若干方面组成的粗粒度coarse-grained的 API内部包含了业务逻辑元素。API 是可以将单体业务分割成两个更小应用的天然边界,其中一个应用是表现层,另外一个是业务和数据访问逻辑。分割后,表现逻辑应用远程调用业务逻辑应用,下图表示迁移前后架构不同:​
![2](/images/Before-and-after-migration.png)
![2](./images/Before-and-after-migration.png)
单体应用这么分割有两个好处,其一使得应用两部分开发、部署和扩展各自独立,特别地,允许表现层开发者在用户界面上快速选择,进行 A/B 测试;其二,使得一些远程 API 可以被微服务调用。
@@ -72,7 +72,7 @@ Law of Holes 是说当自己进洞就应该停止挖掘。对于单体式应用
一旦完成粗粒度接口也就将此模块转换成独立微服务。为了实现必须写代码使得单体应用和微服务之间通过使用进程间通信IPC机制的 API 来交换信息。如图所示迁移前后对比:
![3](/images/30103116_ZCcM.png)
![3](./images/30103116_ZCcM.png)
此例中,正在使用 Y 模块的 Z 模块是备选抽取模块,其元素正在被 X 模块使用,迁移第一步就是定义一套粗粒度 APIs第一个接口应该是被 X 模块使用的内部接口,用于激活 Z 模块;第二个接口是被 Z 模块使用的外部接口,用于激活 Y 模块。