响应式微服务
让我们潜入吧! 在上一篇文章(第一部分)中,我们为该博客设置了上下文。 基本上,当我们引入将微服务引入我们的体系结构的策略时,我们不能也不应破坏当前的请求流。 我们的“整体”应用程序通常为业务提供很多价值,并且我们必须降低迭代和扩展时对这些系统造成负面影响的风险。 这使我们想到了一个经常被忽视的事实:当我们开始探索从单一服务到微服务的旅程时,我们将Swift遇到我们无法忍受的不良,有时令人讨厌的部分。 如果您还没有,我鼓励您回过头阅读第一部分 。 还可以阅读有关何时不做微服务的部分。
在Twitter或http://blog.christianposta.com上关注( @christianposta ),以获取最新更新和讨论。
在上一部分中,这是我们要解决的一些注意事项:
- 我们需要一种可靠且一致的方式来构建我们的服务。 我们需要一个持续交付系统。
- 我们需要一种方法来测试我们的服务/整体/等
- 我们需要一种方法来安全地将任何变更投入生产,包括黑暗发射,金丝雀等
- 我们希望有一种方法将流量路由到我们的新更改,或启用更改(或取消切换)任何新功能/更改
- 我们将应对许多令人讨厌的数据集成挑战
技术领域
我们将用来帮助我们指导这一旅程的技术:
- 开发人员服务框架( Spring Boot , WildFly , WildFly Swarm )
- API设计( APICur.io )
- 数据框架( Spring Boot Teiid , Debezium.io )
- 整合工具( Apache Camel )
- 服务网格( Istio服务网格 )
- 数据库迁移工具( Liquibase )
- 暗启动/功能标志框架( FF4J )
- 部署/ CI-CD平台( Kubernetes / OpenShift )
- Kubernetes开发人员工具( Fabric8.io )
- 测试工具( Arquillian , Pact / Arquillian Algeron , Hoverfly , Spring-Boot测试 , RestAssured , Arquillian Cube )
如果您想继续,我正在使用的示例项目基于http://developers.redhat.com上的TicketMonster教程,但已进行了修改,以显示从单片到微服务的演变。 您可以在github上找到代码和文档(文档仍在进行中!): https : //github.com/ticket-monster-msa/monolith
让我们逐步学习第一部分 ,看看如何解决每个步骤。 我们还将引入上一个博客中的注意事项,并在此情况下重新进行考虑。
遇见巨石
重新考虑注意事项
- 整体式(代码和数据库架构)很难更改
- 变更需要完全重新部署并在团队之间进行高度协调
- 我们需要进行大量测试以赶上回归
- 我们需要一种完全自动化的部署方式
这可能并不总是可能的,但是如果可以的话,请为整体编写大量测试。 当我们通过添加新功能或替换现有功能来发展整体时,我们需要对更改的影响有充分的了解。 迈克尔·费瑟斯(Michael Feathers)在有关重构遗留代码的书中将“遗留代码”定义为未进行测试。 使用诸如JUnit和Arquillian之类的工具会极大地帮助您。 您可以使用Arquillian随意选择细粒度或粗粒度,然后打包应用程序,但是需要(使用适当的模拟等)来练习要测试的应用程序部分。 例如,在我们的整体模型(TicketMonster)中,我们可以定义一个微部署,该微部署将数据库存入内存中,并预先将示例数据加载到数据库中。 Arquillian适用于Spring Boot应用程序,Java EE等。在这种情况下,我们正在测试Java EE整体:
public static WebArchive deployment() {
return ShrinkWrap
.create(WebArchive. class , "test.war" )
.addPackage(Resources. class .getPackage())
.addAsResource( "META-INF/test-persistence.xml" , "META-INF/persistence.xml" )
.addAsResource( "import.sql" )
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml" )
// Deploy our test datasource
.addAsWebInfResource( "test-ds.xml" ); }
甚至更有趣的是,您可以运行运行时中嵌入的测试来验证所有组件在内部是否正常工作。 例如,在上述测试之一中,我们可以将BookingService注入到我们的测试中并直接运行它:
@RunWith (Arquillian. class ) public class BookingServiceTest {
@Deployment
public static WebArchive deployment() {
return RESTDeployment.deployment();
}
@Inject
private BookingService bookingService;
@Inject
private ShowService showService;
@Test
@InSequence ( 1 )
public void testCreateBookings() {
BookingRequest br = createBookingRequest(1l, 0 , new int []{ 4 , 1 }, new int []{ 1 , 1 }, new int []{ 3 , 1 });
bookingService.createBooking(br);
BookingRequest br2 = createBookingRequest(2l, 1 , new int []{ 6 , 1 }, new int []{ 8 , 2 }, new int []{ 10 , 2 });
bookingService.createBooking(br2);
BookingRequest br3 = createBookingRequest(3l, 0 , new int []{ 4 , 1 }, new int []{ 2 , 1 });
bookingService.createBooking(br3);
}
对于一个完整的例子,看看在BookingServiceTest
从TicketMonster整体模块 。
但是部署呢?
Kubernetes已成为容器化服务/应用程序的实际部署平台。 Kubernetes处理诸如运行状况检查,扩展,重新启动,负载平衡等操作。对于Java开发人员,我们甚至可以使用诸如fabric8-maven-plugin之类的工具来自动构建我们的容器/ docker映像并生成任何部署资源文件。 OpenShift是RedHat的Kubernetes的产品版本,除其他功能外,还添加了开发人员功能,包括CI / CD管道之类的功能。
Kubernetes / OpenShift是您的应用程序/服务的部署平台,无论它们是微服务,整体还是两者之间的其他任何功能(具有处理持久性工作负载(例如数据库等)的能力)。 借助Arquillian,容器和OpenShift管道,我们有可靠的方法将变更不断交付到生产中。 顺便说一句…检出openshift.io ,可通过自动CI / CD管道,SCM集成, Eclipse Che开发人员工作区,库扫描等使开发人员体验更加深入 。
在这一点上,生产负荷指向整料。 如果转到其主页,则会看到类似以下内容的内容:
让我们开始进行一些更改…
提取UI
重新考虑注意事项
- 第一步,请勿修改整体。 只需将UI复制/粘贴到单独的组件中
- 我们需要在UI和整体之间建立一个合理的远程处理API-并非总是如此
- 安全面增加
- 我们需要一种以受控方式将流量路由/拆分到新用户界面和/或整体的方式,以支持黑暗发射/金丝雀/滚动释放
如果我们看一下TicketMonster UI v1代码 ,就会发现它非常简单。 我们已经将静态HTML / JS / CSS组件移至其自己的Web服务器,并将其打包到一个容器中。 这样,我们可以将其独立于整体部署,并独立进行更改/版本化。 这个UI项目仍将需要与Monolith对话以执行其功能,因此,这一演变的一部分应该是公开UI可以与之交互的REST接口。 对于某些整体而言,这说起来容易做起来难。 如果您在围绕旧式整体代码包装好的REST API时遇到了挑战,我强烈建议您看一下Apache Camel,尤其是其REST DSL 。
关于此步骤的一个有趣的部分是,我们实际上并未更改整体中的任何内容。 该代码保持不变,但我们的新UI也已部署。 如果查看Kubernetes,我们将看到两个单独的Deployment对象和两个单独的pod:一个用于整体,一个用于UI。
ceposta @postamac $ kubectl get deploy NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE mysql-backend 1 1 1 1 4d ticket-monster 1 1 1 1 4d tm-ui-v1 1 1 1 1 4d
即使我们已经部署了tm-ui-v1
UI,我们也看不到任何新的TicketMonster UI组件的流量。 为简单起见,即使此部署不会占用生产流量(而ticket-monster
整体目前占用了全部生产流量),我们仍然可以将其视为简单的暗发射。 如果我们向前移植到UI,我们仍然可以到达它:
kubectl port-forward tm-ui-v1- 3105082891 -gh31x 8080 : 80
我们使用kubectl
cli工具将本地盒中的端口转发到特定的pod(端口80
上的tm-ui-v1-3105082891-gh31x
其映射到本地端口8080
。现在,如果我们导航到http:// localhost :8080我们应该使用UI的新版本(请注意突出显示的文本指示这是一个不同的UI,但它直接指向整体)
如果我们对这个新版本感到满意,就可以开始将流量引向这个新版本。 为此,我们将使用Istio服务mesh 。 Istio是用于管理由入口点和服务代理组成的网格的控制平面。 我已经写了一些有关服务网格和数据平面(例如Envoy)的文章 。 我强烈建议您看一下它的全部功能。 在接下来的几节中,我们将迭代这个项目,以探索Istio的功能。 如果您对控制平面/数据平面的区别进一步感到困惑, 请看Matt Klein撰写的博客,其中谈到了
我们将从使用Istio Ingress Controller开始 。 该组件使您可以使用Kubernetes Ingress规范控制进入Kubernetes集群的流量 。 安装Istio之后 ,我们可以创建一个这样的Ingress
资源,将流量指向Ticket Monster UI的Kubernetes服务 tm-ui
:
apiVersion: extensions/v1beta1 kind: Ingress metadata:
name: tm-gateway
annotations:
kubernetes.io/ingress. class : "istio" spec:
backend:
serviceName: tm-ui
servicePort: 80
有了入口之后,就可以开始应用Istio路由规则了 。 例如,这是一条路由规则,上面写着“任何时候有人试图与Kubernetes中运行的tm-ui
服务进行对话,将其定向到该服务的v1
”:
apiVersion: config.istio.io/v1alpha2 kind: RouteRule metadata:
name: tm-ui- default spec:
destination:
name: tm-ui
precedence: 1
route:
- labels:
version: v1
这将使我们能够更好地控制进入集群甚至集群内部的流量。 稍作讨论。 在此步骤的最后,我们所有流量都流向了tm-ui-v1
部署,后者又直接与整体通信。
从整体中删除UI
重新考虑注意事项
- 我们正在从整体中删除UI组件
- 这就要求(希望)对整体进行最小的更改(弃用/删除/禁用UI,可能对REST API进行更新)
- 再次,我们使用受控的路由/整形方法来引入此更改而无需停机
这一步很简单。 我们正在通过从中删除静态UI组件(这些组件现已移至tm-ui-v1
部署)中来更新整体。 我们还可以对此部署进行一些API更改,因为我们现在已经释放了应用程序,使其成为具有UI可以使用的API以及其他应用程序可能的独占服务。 由于我们可能会对API进行一些更改,因此我们可能还希望部署UI的新版本。 在此步骤中,我们将部署backend-v1
服务以及一个新的UI tm-ui-v2
,该UI将在我们的backend
服务中利用此新API。
让我们看看在Kubernetes集群中部署了什么:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE backend-v1 1 1 1 1 4d mysql-backend 1 1 1 1 4d ticket-monster 1 1 1 1 4d tm-ui-v1 1 1 1 1 4d tm-ui-v2 1 1 1 1 4d
此时, ticket-monster
和tm-ui-v1
部署正在进行实时负载。 backend-v1
和指向它的UI, tm-ui-v2
不加负载。 需要注意的一件事是, backend-v1
部署与ticket-monster
部署共享相同的数据库,因为它几乎相同,但面向外部的API稍有不同。
我们新的backend-v1
和tm-ui-v2
组件已部署到生产中。 现在是集中讨论一个简单但至关重要的事实的好时机:我们已将更改部署到生产中,但尚未将其发布给任何人。 turbolabs.io上的好人有一个很棒的博客,更详细地阐明了这一点 。 我们有机会进行非正式的黑暗发射。 也许我们想首先将此部署缓慢地向我们的内部用户展开,或者可能向特定设备上的特定区域中的一部分用户展开,等等。
既然现在有了Istio,让我们看看它能为我们做些什么。 我们只想对内部用户进行暗启动。 可以识别那些内部用户,但是我们喜欢(标头,IP等),但是对于本示例,我们将说带有HTTP标头为x-dark-launch: v2
任何请求x-dark-launch: v2
将被路由到新的backend-v1
和tm-ui-v2
服务。 istio路由规则如下所示:
apiVersion: config.istio.io/v1alpha2 kind: RouteRule metadata:
name: tm-ui-v2-dark-launch spec:
destination:
name: tm-ui
precedence: 10
match:
request:
headers:
x-dark-launch:
exact: "v2"
route:
- labels:
version: v2
当我们以任何用户身份进入主页时,我们应该看到当前的部署( tm-ui-v1
与ticket-monster
Monolith对话):
现在,如果我们更改浏览器中的标头(例如使用Firefox的Modify Headers工具或类似工具 ),则应将其路由到暗中启动的一组服务( tm-ui-v2
与backend-v1
对话):
我们看到我们现在已被重定向到我们的服务的黑暗启动。 从这里开始,我们可以进行金丝雀发布(也许将1%的实时流量发送到我们的新部署中),然后慢慢增加流量负载(5%,10%,50%等),从而释放给客户群看到没有不良影响。 这是一个Istio路由规则示例,该规则将v2流量限制为1%:
apiVersion: config.istio.io/v1alpha2 kind: RouteRule metadata:
name: tm-ui-v2-1pct-canary spec:
destination:
name: tm-ui
precedence: 20
route:
- labels:
version: v1
weight: 99
- labels:
version: v2
weight: 1
能够“看到”或“观察”此版本的效果至关重要,我们稍后将进一步讨论。 还要注意,这种金丝雀释放方法目前正在我们的体系结构的边缘进行,但是服务间通信/交互也可以使用istio来控制金丝雀。 在接下来的几个步骤中,我们将开始看到这一点。
推出一项新服务
重新考虑注意事项
- 我们想专注于提取服务的API设计/边界
- 这可能是对巨石中存在的东西的重写
- 确定API后,我们将为该服务实现一个简单的脚手架/占位符
- 新的订单服务将拥有自己的数据库
- 新的订单服务目前将不会进行任何形式的交易
在这一步中,我们将开始为新的Orders服务设计所需的API,这很可能与我们通过某些域驱动的设计练习确定的边界更加一致。 我们可以投入大量精力来使用API建模工具来设计API,部署它的虚拟化实现并与我们的使用者进行迭代,而无需花费大量精力在前面构建API只是后来发现需要不断更改。
在我们的TicketMonster重构的情况下,我们可能希望保持与整体中类似的API,以使分解最初尽可能轻松,低风险。 无论哪种情况,我们都可以利用两个很棒的工具来帮助我们:一个名为apicur.io的基于Web的API设计器和一个名为Hoverfly的测试/ API虚拟化工具。 Hoverlfy是用于模拟API或捕获现有API流量的出色工具,因此可用于模拟模拟端点。
如果我们正在构建未开发的API,或者试图想象使用域驱动的设计方法进行迭代后,API的外观,则可以使用apicur.io工具来构建Swagger / Open API规范。
对于TicketMonster,我们通过在proxy
模式下启动hoverfly并捕获流量,使用hoverfly捕获了从应用程序到后端服务的流量。 我们可以在浏览器设置中轻松设置HTTP代理,以通过hoverfly发送所有流量。 它将每个请求/响应对的模拟存储在JSON文件中。 从这里开始,我们在模拟中甚至更好地使用请求/响应对,并使用它们开始编写测试,以对实现中想要的行为进行编码。
对于我们关心的请求/响应对,我们可以创建一个JSON模式(签出https://jsonschema.net/#/editor并在我们的测试中使用它。
例如,结合使用Rest Assured和Hoverfly,我们可以调用hoverfly模拟并断言响应符合我们期望的JSON模式:
@Test public void testRestEventsSimulation(){
get( "/rest/events" ).then().assertThat().body(matchesJsonSchemaInClasspath( "json-schema/rest-events.json" )); }
从新的Orders
服务中检出HoverflyTest.java测试。
有关测试Java微服务的更多信息,请从曼宁的这本很棒的书中找到我的同事Alex Soto Bueno , Jason Porter和Andy Gumbrecht的书“测试Java微服务” 。
由于这篇博客文章已经很长了,我决定将最后一部分划分为第三部分,该部分涉及在整体和微服务之间管理数据,消费者合同测试以及如何进行功能标记/更复杂的istio路由,本系列的第四部分将显示所有这些的录制演示,以及负载模拟测试和故障注入的演示。 请继续关注并关注Twitter !
翻译自: https://www.javacodegeeks.com/2017/10/low-risk-monolith-microservice-evolution-part-ii.html
响应式微服务
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/34292.html