您可以使用本指南来了解什么是Java微服务,如何构建和构建它们。 另:看一下Java微服务库和常见问题。
[ 编者注 :大约7,000个单词,您可能不想尝试在移动设备上阅读。 将其添加为书签,稍后再返回。]
Java微服务:基础
为了真正理解Java微服务,从最基本的基础开始是有道理的:臭名昭著的Java整体,它是什么以及它的优点或缺点是什么。
什么是Java整体程序?
假设您正在为一家银行或金融科技初创公司工作。 您为用户提供了一个移动应用程序,他们可以使用它来开设一个新的银行帐户。
在Java代码中,这将导致一个看起来像简化的控制器类,如下所示。
@Controller
class BankController {
@PostMapping ( "/users/register" )
public void register ( RegistrationForm form ) {
validate ( form );
riskCheck ( form );
openBankAccount ( form );
// etc..
}
}
您将要:
验证注册表。
对用户的地址进行风险检查,以确定是否要给他一个银行帐户。
开立银行账户
您的BankController类将与所有其他源代码一起打包到一个bank.jar或bank.war文件中,以进行部署:一个很好的旧版本,包含银行运行所需的所有代码。 (作为粗略的指针,.jar / .war文件最初的大小为1-100MB)。
然后,在服务器上,您只需运行.jar文件-这就是部署Java应用程序所需要做的全部工作。
Java整体组件有什么问题?
从本质上讲,Java整体没有错。 仅仅是项目经验表明,如果您:
让许多不同的程序员/团队/顾问...
在高压和不清楚的条件下在相同的整体上工作...
几年了...
然后,您的小bank.jar文件变成了一个千兆字节的大型代码怪兽,每个人都担心部署。
如何使Java整体组件更小?
这自然导致了如何减小整体尺寸的问题。 现在,您的bank.jar在一个JVM中运行,在一个服务器上运行一个进程。 仅此而已。
现在你能想出的主意,说:嗯,风险检查服务正在使用由我公司其他部门,并没有真正有什么用我的单声道(岩屑)银行领域 ,
因此我们可以尝试将其从整体中删除,并将其部署为自己的产品,或者更严格地说,将其作为自己的Java进程运行。
什么是Java微服务?
实际上,这意味着您不必将BankController中的所有方法与该方法/ bean及其所有帮助器类一起移至其自己的Maven / Gradle项目中,而是将其置于源代码管理下,并独立于您的部署,而无需在BankController中调用riskCheck()方法。银行巨石。
整个提取过程不会使新的RiskCheck模块本身成为微服务 ,这是因为微服务的定义是开放的,可以进行解释(这导致团队和公司进行大量讨论)。
如果里面只有5-7个班级,那它是微型的吗?
100或1000个课程仍然微不足道吗?
它甚至与班级数量有关系吗?
与其理论化,不如说实用,做两件事:
调用所有可单独部署的服务微服务-与大小或域边界无关。
关注服务间通信的重要主题,因为您的微服务需要彼此通信的方法。
因此,总结一下:在拥有一个JVM进程之前,有一个Banking整体。 现在,您拥有一个银行业的整体JVM流程和一个RiskCheck微服务,它们在其自己的JVM流程中运行。 现在,您的整体必须调用该微服务进行风险检查。
你是怎样做的?
Java微服务之间如何通信?
您基本上有两种选择: 同步通信或异步通信 。
(HTTP)/ REST-同步通讯
同步微服务通信通常通过返回XML或JSON的HTTP和类似REST的服务来完成-但这并不是必需的(例如,请查看Google的协议缓冲区 )。
当您需要立即响应时,请使用REST通讯,在这种情况下,我们会这样做,因为在开设帐户之前必须进行风险检查:无风险检查,无帐户。
在工具方面,请查看#synchronous-rest-tools 。
消息传递-异步通信
异步微服务通信通常通过使用JMS实现和/或类似AMQP的消息传递来完成。 通常,因为在实践中不要低估例如电子邮件/ SMTP驱动的集成的数量。
当您不需要立即响应时(例如,用户按下“立即购买”按钮并且您要生成发票)就可以使用它,发票当然不必在用户的购买请求-响应周期中发生。
在工具方面,请查看#asynchronous-rest-tools 。
示例:以Java调用REST API
假设我们选择使用同步微服务通信,那么上面的Java代码将在较低的层次上看起来像这样。 低级,因为对于微服务通信,您通常会创建客户端库,从而将实际的HTTP调用从您那里抽象出来。
@Controller
class BankController {
@Autowired
private HttpClient httpClient ;
@PostMapping ( "/users/register" )
public void register ( RegistrationForm form ) {
validate ( form );
httpClient . send ( riskRequest , responseHandler ());
setupAccount ( form );
// etc..
}
}
看一下代码,很明显,您现在必须部署两个Java(微)服务。 您的银行和RiskCheck服务。 您将最终获得两个JVM,两个进程。 之前的图形如下所示:
这就是开发Java Microservices项目所需的全部:构建和部署较小的块,而不是一个较大的块。
但这留下了问题:您究竟如何削减或设置这些微服务? 这些较小的块是什么? 合适的尺寸是多少?
让我们做一个现实检查。
Java微服务架构
实际上,公司可以通过多种方式来设计或设计微服务项目。 这取决于您是想将现有的整体变成Java微服务项目,还是要开始新的Greenfield项目。
从Monolith到微服务
一个相当有机的想法是将微服务从现有的整体中分离出来。 请注意,此处的“微”实际上并不意味着提取的服务本身确实会是微的-它们本身可能仍然很大。
让我们看一些理论。
想法:将整体式服务转变为微服务
遗留项目有助于采用微服务方法。 主要有以下三个原因:
它们通常很难维护/更改/扩展。
从开发人员到操作人员,每个人都希望使事情变得更简单,希望事情变得更简单。
您具有(某种程度上)明确的域边界,这意味着:您知道软件应该做什么。
这意味着您可以查看Java银行整体,并尝试将其沿域边界拆分-一种明智的方法。
您可以得出结论,应该有一个“帐户管理”微服务,该微服务处理诸如姓名,地址,电话号码之类的用户数据。
或前面提到的“风险模块”,它可以检查用户风险水平,并且可以由公司的许多其他项目甚至部门使用。
或开发票模块,可通过PDF或实际邮件发送发票。
现实:让别人去做
尽管这种方法在纸上和类似UML的图表上看起来确实不错,但它也有缺点。 主要地,您需要非常强大的技术技能才能实现。 为什么?
因为有了解 ,这将是提取,也就是说,高度耦合的账户管理模块出你的整体的一个好东西, 这样做 (正确地)之间的巨大差异。
大多数企业项目都达到了这样的阶段:开发人员不敢将其升级到7年之久的Hibernate版本到较新的版本,这只是库的更新,但是要进行大量工作以确保不破坏任何内容。
现在,那些相同的开发人员应该深入研究具有遗忘的数据库事务边界的旧的旧代码,并提取定义良好的微服务吗? 可能,但通常是一个真正的挑战,在白板或建筑会议中无法解决。
这已经是本文中的第一次, 在Twitter上@simonbrown的引用适用于:
我会继续这样说...如果人们不能正确构建整体,微服务将无济于事。
西蒙·布朗
绿地项目微服务架构
开发新的未开发的Jav项目时,情况看起来有些不同。 现在,以上三点看起来有些不同:
您从一个干净的开始,所以没有旧行李要维护。
开发人员希望将来事情变得简单。
问题:您对域边界的了解更加模糊:您不知道软件实际上应该做什么(提示:敏捷;))
这导致公司尝试解决新的Java微服务项目的各种方式。
技术微服务架构
第一种方法对开发人员来说最明显,尽管强烈建议不要这样做。 支持Hadi Hariri提出IntelliJ中的“提取微服务”重构。
尽管下面的示例过于简化,但不幸的是,在实际项目中看到的实际实现距离也不远。
微服务之前
@Service
class UserService {
public void register ( User user ) {
String email = user . getEmail ();
String username = email . substring ( 0 , email . indexOf ( "@" ));
// ...
}
}
使用子字符串Java微服务
@Service
class UserService {
@Autowired
private HttpClient client ;
public void register ( User user ) {
String email = user . getEmail ();
// now calling the substring microservice via http
String username = httpClient . send ( substringRequest ( email ), responseHandler ());
// ...
}
}
因此,您实际上是将Java方法调用包装到HTTP调用中,没有明显的理由。 但是,原因之一是:缺乏经验并试图强制采用Java微服务方法。
建议 :不要这样做。
面向工作流的微服务架构
下一种常见方法是在工作流程之后对Java微服务进行模块化。
现实生活中的例子:在德国,当您去看(公共)医生时,他需要在您的健康软件CRM中记录您的约会。
为了从保险中获得收益,他将通过XML将您的治疗数据以及他治疗过的所有其他患者的数据发送给中介。
中介人员将查看该XML文件,并(简化):
尝试验证文件是否为正确的XML
尝试并验证其真实性:1岁的儿童一天要从妇科医生那里洗三牙是否有意义?
使用其他一些官僚数据增强XML
将XML转发给保险以触发付款
并为返回医生的整个过程建模,包括一条“成功”消息或“请重新发送该数据输入-一旦有意义”
如果现在尝试对此工作流进行建模,那么最终将获得至少六个Java微服务。
注意 :在此示例中,微服务之间的通信无关紧要,但是很可能可以与RabbitMQ之类的消息代理异步进行,因为医生无论如何都无法立即获得反馈。
再次,这在纸面上看起来不错,但立即导致几个问题:
您是否需要部署六个应用程序来处理1个xml文件?
这些微服务真的彼此独立吗? 它们可以彼此独立部署吗? 使用不同的版本和API方案?
如果验证微服务已关闭,那么合理性微服务会做什么? 系统是否仍在运行?
这些微服务现在是否共享同一数据库(它们肯定需要数据库表中的一些公共数据),还是您要承担更大的重担呢?
还有大量其他基础架构/运营问题。
有趣的是,对于某些架构师来说,上面的图看起来更简单,因为现在每个服务都有其明确定义的用途 。 以前,它看起来像是这个可怕的巨石:
尽管争论可能在于这些图的简单性,但您现在肯定要解决以下额外的操作难题:
不仅需要部署一个应用程序,而且至少需要部署六个应用程序。
甚至可能是数据库,具体取决于您要使用它的距离。
必须确保每个系统都在线,健康且正常运行。
必须确保您在微服务之间的调用实际上具有弹性 (请参阅#resilience )
该设置还包含其他所有内容-从本地开发设置到集成测试
建议 :
除非:
你是Netflix(不是)...
您具有超强的操作技能:打开开发IDE,这会触发混乱的猴子,该猴子会丢弃您的生产数据库,并在5秒钟内轻松自动恢复
或者您觉得@monzo尝试了1500个微服务,仅仅是因为您可以。
→不要这样做。
不过,夸张程度较低。
在域边界之后尝试对微服务建模是一种非常明智的方法。 但是,域边界(例如,用户管理与开发票)并不意味着要采用单个工作流程并将其拆分为最小的单个部分(接收XML,验证XML,转发XML)。
因此,每当您开始一个新的Java微服务项目并且域边界仍然很模糊时,请尝试将微服务的大小保持在较低端 。 您以后可以随时添加更多模块。
并确保您在整个团队/公司/部门中具有非凡的DevOps技能,以支持新的基础架构。
多种语言或面向团队的微服务架构
还有第三种几乎是自由主义的方法来开发微服务:使您的团队甚至个人可以使用所需的多种语言或微服务来实现用户故事(市场术语:多语言编程)。
因此,上面的XML验证服务可以用Java编写,而Plausibility Microservice用Haskell编写(从数学上讲是合理的),而Insurance Forwarding Microservice应该用Erlang编写(因为它确实需要扩展;))。
从开发人员的角度看(从孤立的环境中用您的完美语言开发一个完美的系统)看似有趣的事情基本上不是组织想要的:同质化和标准化。
这意味着需要一套相对标准化的语言,库和工具,以便其他开发人员可以在将来获得绿色牧场后继续维护Haskell微服务。
有趣的是:从历史上看,标准化做得太过分了。 有时甚至不允许财富500强公司的开发人员使用Spring,因为“这不在公司的技术蓝图中”。 但是,全面发展多语种几乎是同一回事,只是同一枚硬币的另一面。
建议 :如果要使用多种语言,请在同一编程语言生态系统中尝试较小的多样性。 示例:Kotlin和Java(彼此之间100%兼容的基于JVM),而不是Haskell和Java。
部署和测试Java微服务
快速回顾一下本文开头提到的基础知识将很有帮助 。 任何服务器端Java程序(以及任何微服务)都只是一个.jar / .war文件。
关于Java生态系统,或者说JVM,有一件很棒的事情:您只需编写一次Java代码,就可以在任何您想要的操作系统上运行它,只要您不使用比目标更高的Java版本来编译代码。 JVM的版本)。
了解这一点很重要,尤其是涉及诸如Docker,Kubernetes或(颤抖的) The Cloud之类的主题时 。 为什么? 让我们看一下不同的部署方案:
最低限度的Java微服务部署示例
继续以银行为例,我们最终得到了monobank.jar文件(整体文件)和新提取的riskengine.jar(第一个微服务)。
我们还假定,与世界上任何其他应用程序一样,这两个应用程序都需要一个.properties文件,而不仅仅是数据库url和凭据。
因此,最低限度的部署可能只由两个目录组成,看起来像这样:
-r-r------ 1 ubuntu ubuntu 2476 Nov 26 09:41 application.properties
-r-x------ 1 ubuntu ubuntu 94806861 Nov 26 09:45 monobank-384.jar
ubuntu@somemachine:/var/www/www.monobank.com/java$ java -jar monobank-384.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
...
-r-r------ 1 ubuntu ubuntu 2476 Nov 26 09:41 application.properties
-r-x------ 1 ubuntu ubuntu 94806861 Nov 26 09:45 risk-engine-1.jar
ubuntu@someothermachine:/var/www/risk.monobank.com/java$ java -jar risk-engine-1.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
...
这就留下了一个问题:如何将.properties和.jar文件放入服务器?
不幸的是,这个问题有各种各样诱人的答案。
如何使用SSH和Ansible构建工具进行Java微服务部署
对于Java微服务部署而言,无聊但完美的答案是管理员在过去20年中如何在公司中部署任何 Java服务器端程序。 混合使用:
您最喜欢的构建工具(Maven,Gradle)
好的旧SSH / SCP,用于将您的.jar复制到服务器
Bash脚本来管理您的部署脚本和服务器
甚至更好:一些Ansible脚本。
如果您不希望创建由自动负载平衡服务器组成的呼吸云,不停地搅乱您的机器,或者让ZooKeeper的领导者选举产生成功的热情和模糊感,那么此设置将使您受益匪浅。
Oldschool,很无聊,但是可以工作。
如何使用Docker进行Java微服务部署
回到诱人的选择。 几年前, Docker或容器化的话题出现了。
如果您以前没有经验,那么这就是面向最终用户或开发人员的全部内容:
容器就像简化的旧虚拟机一样(简化),但是更轻便。 看一下Stackoverflow的答案,以了解轻量级在这种情况下的含义。
容器向您保证它是便携式的,可以在任何地方运行。 这听起来很熟悉吗?
有趣的是,凭借JVM的可移植性和向后兼容性,这听起来并不是主要的好处。 您可以在任何服务器,Raspberry Pi(甚至手机)上下载JVM.zip,解压缩并运行所需的任何.jar文件。
对于PHP或Python之类的语言而言,它看上去有点不同,在这些语言中,版本不兼容或部署设置历来更为复杂。
或者,如果您的Java应用程序依赖于大量其他已安装的服务(具有正确的版本号):请考虑使用Postgres之类的数据库或Redis之类的键值存储。
因此,Docker对于Java微服务或Java应用程序的主要优势在于:
使用诸如Testcontainers之类的工具设置同质化的测试或集成环境。
使复杂的可部署项目“更简单”地进行安装。 使用Discourse论坛软件。 您可以使用一个包含所需内容的Docker映像进行安装:从用Ruby编写的Discourse软件到Postgres数据库,再到Redis和厨房水槽。
如果您的可部署项目看起来很相似,或者您想在开发机器上运行一个不错的小型Oracle数据库,请尝试Docker。
因此,总而言之,您现在将不再是简单地scp'.jar文件:
将jar文件捆绑到Docker映像中
将该Docker映像转移到私有Docker注册表
在目标平台上拉并运行该映像
或直接将Docker映像scp到您的生产系统并运行
如何使用Docker Swarm或Kubernetes进行Java微服务部署
假设您正在尝试Docker。 每次部署Java微服务时,现在都将创建一个Docker映像,该映像将您的.jar文件捆绑在一起。 您有几个Java微服务,并且想要将这些服务部署到两台机器上: 集群 。
现在出现了一个问题:您如何管理该集群,这意味着运行Docker容器,运行状况检查,推出更新,扩展(brrrr)?
该问题的两个可能答案是Docker Swarm和Kubernetes 。
在本指南的范围内不可能对这两个选项进行详细介绍,但实际情况是:最终,这两个选项都取决于您编写YAML文件(请参阅#yaml-tales )来管理集群。 如果您想知道在实践中会引起什么感受,请在Twitter上进行快速搜索。
因此,您的Java微服务的部署过程现在看起来像这样:
设置和管理Docker Swarm / Kubernetes
上面Docker步骤中的所有内容
编写并执行YAML,直到您的眼睛出血为止
如何测试Java微服务
假设您已经解决了在生产环境中部署微服务的问题,但是如何在开发过程中集成测试n微服务? 要查看完整的工作流程是否有效,而不仅仅是单个工作流程?
在实践中,您会发现三种不同的方式:
进行一些额外的工作(如果您使用的是Spring Boot之类的框架),则可以将所有微服务包装到一个启动器类中,并使用一个Wrapper.java类启动所有微服务-取决于您的计算机上是否有足够的内存运行您的所有微服务。
您可以尝试在本地复制Docker Swarm或Kubernetes设置。
只是不再在本地进行集成测试。 而是具有专用的DEV / TEST环境。 屈指可数的团队实际上是在做本地微服务设置的痛苦。
此外,除了Java微服务之外,您可能还需要启动并运行的消息代理(例如: ActiveMQ或RabbitMQ ),或者可能需要电子邮件服务器或Java微服务需要相互通信的任何其他消息传递组件。
这导致在DevOps方面相当低估了复杂性。 看一下微服务测试库,以减轻其中的某些痛苦。
无论如何,这种复杂性导致我们遇到常见的微服务问题:
常见的Java微服务问题
让我们看一下Java特定的微服务问题,从弹性等更抽象的东西到特定的库。
如何使Java微服务具有弹性?
概括地说,在构建微服务时,您实际上是将JVM方法调用替换为同步HTTP调用或异步消息传递 。
尽管基本上保证了方法调用的执行(除了JVM突然退出),但是默认情况下,网络调用是不可靠的。
它可以工作,也由于各种原因而不能工作:从网络中断或拥塞,到正在实施的新防火墙规则导致消息代理爆炸。
为了查看其含义,让我们看一个示例BillingService示例。
HTTP / REST弹性模式
说客户可以在您公司的网站上购买电子书。 为此,您仅实现了一个计费微服务,您的网上商店可以调用该微服务来生成实际的PDF发票。
现在,我们将通过HTTP同步进行该调用。 (异步调用该服务会更有意义,因为从用户的角度来看,PDF生成不是必须立即进行的。但是我们想在下一部分中重用此示例并查看其区别。)
@Service
class BillingService {
@Autowired
private HttpClient client ;
public void bill ( User user , Plan plan ) {
Invoice invoice = createInvoice ( user , plan );
httpClient . send ( invoiceRequest ( user . getEmail (), invoice ), responseHandler ());
// ...
}
}
考虑一下HTTP调用可能产生什么样的结果。 概括起来,您将得到三个可能的结果:
OK :通话已完成,发票已成功创建。
延迟 :通话进行了,但是花费了非常长的时间。
错误 :呼叫未通过,可能是因为您发送了不兼容的请求,或者系统已关闭。
对于任何程序,都应该处理错误,而不仅仅是处理错误的情况。 即使必须格外小心,微服务也是一样
从单个微服务部署和发行版开始,就可以使所有已部署的API版本保持兼容。
而且,如果您想全面使用chaos-monkey,还必须忍受这样的可能性,即服务器在请求处理期间只是受到限制,并且您可能希望将请求重新路由到另一个工作实例。
一个有趣的“警告”案例是延迟案例。 响应的微服务硬盘可能已满,而不是50ms,而是需要10秒钟来响应。 体验时,这会变得更加有趣
一定的负载,以便BillingService的无响应性开始在系统中级联 。 想一想缓慢的厨房,慢慢地将所有餐厅的侍者挡在门外。
该部分显然不能深入介绍微服务弹性主题,但可以提醒开发人员这是要实际解决的问题 ,直到您的第一个版本发布(根据经验,它经常发生的次数比预期多)时才应予以忽略 。
Netflix的Hystrix是一个受欢迎的库,可帮助您考虑延迟和容错能力。 使用其文档进一步探讨该主题。
消息复原能力模式
让我们仔细看看异步通信。 现在,如果我们使用Spring和RabbitMQ进行消息传递,我们的BillingService代码可能看起来像这样。
为了创建发票,我们现在将消息发送到RabbitMQ消息代理,该代理有一些工作人员在等待新消息。 这些工作人员创建PDF发票并将其发送给相应的用户。
@Service
class BillingService {
@Autowired
private RabbitTemplate rabbitTemplate ;
public void bill ( User user , Plan plan ) {
Invoice invoice = createInvoice ( user , plan );
// converts the invoice to,for example, json and uses it as the message's body
rabbitTemplate . convertAndSend ( exchange , routingkey , invoice );
// ...
}
}
现在,潜在的错误情况看起来有些不同,因为您不再像在同步HTTP通信中那样立即获得OK或ERROR响应。 相反,您将大致遇到以下三种错误情况:
我的信息是由工人传递和使用的吗? 还是迷路了? (用户没有发票)。
我的留言只发送了一次吗? 还是交付了不止一次而仅处理了一次? (用户将获得多张发票)。
配置:从“我是否使用了正确的路由键/交换名称”,到“我的消息代理设置和维护正确,还是队列溢出?”? (用户没有发票)。
同样,在本指南的范围内,不对每个异步微服务弹性模式进行详细介绍。 更重要的是,它的意思是指向正确方向的指针,尤其是它还取决于您所使用的实际消息传递技术时。 例子:
如果您使用RabbitMQ,则至少要确保已阅读并理解了本指南 ,然后仔细考虑确认,确认和消息的可靠性。
并且还具有在设置活动服务器或RabbitMQ服务器以及正确配置它们方面的经验,尤其是在与集群和Docker结合使用时(网络分裂,有人吗?;))
哪个Java微服务框架是最好的?
一方面,您已经建立并选择了非常受欢迎的选择,例如Spring Boot ,它使构建诸如Tomcat或Jetty的嵌入式Web服务器附带的.jar文件变得非常容易,并且您可以立即在任何地方运行。
不过,由于并行开发(例如反应式编程, Kubernetes或GraalVM)的启发 ,最近出现了一些专用的微服务框架。
仅举几例: Quarkus , Micronaut , Vert.x , Helidon 。
最后,您将需要做出自己的选择,但是本文可以提供一些指导,也许是非常规的指导:
除了Spring Boot之外,所有微服务框架通常都以惊人的快速 ,惊人的快速启动时间 , 低内存占用 ,可以无限扩展的方式推销自己,并以令人印象深刻的图表将自己与Spring Boot庞然大物或彼此进行比较。
对于正在维护旧项目(有时需要几分钟才能启动)的开发人员或想在50毫秒内开始或停止运行尽可能多的微型容器的云原生开发人员,这令人震惊。
但是,问题在于,(人工)裸机启动时间和重新部署时间几乎不会影响项目的整体成功,而远没有强大的框架生态系统,强大的文档,社区和强大的开发人员技能。
您必须以这种方式查看它。
如果到现在为止:
您可以让ORM大量运行,并为简单的工作流生成数百个查询。
您需要无尽的千兆字节来运行中等复杂的整体。
您添加了如此多的代码和复杂性,以至于您的应用程序现在(无需考虑Hibernate之类的缓慢启动器)现在只需几分钟即可启动。
然后在顶部添加其他微服务挑战(例如:弹性,网络,消息传递,DevOps,基础架构),比启动一个空的问候世界对您的项目的影响要大得多 。 对于开发过程中的热调,您最终可能希望研究JRebel或DCEVM之类的解决方案。
回到西蒙·布朗的名言:如果人们无法构建(快速且高效)的整体组件,那么无论框架如何,他们都将很难构建(快速高效)微服务。
因此,明智地选择您的框架。
哪些库最适合同步Java REST调用?
有关调用HTTP REST API的更多实际方面。 在底层技术方面,您可能最终会使用以下HTTP客户端库之一:
Java自己的HttpClient (自Java 11起), Apache的HttpClient或OkHttp 。
请注意,我在这里说“可能”是因为还有许多其他方法,从好的旧的JAX-RS客户 端到现代的WebSocket客户端。
在任何情况下,HTTP客户端生成都是一种趋势,而不是自己搞乱HTTP调用。 为此,您想看看OpenFeign项目及其文档,以此作为进一步阅读的起点。
哪些代理最适合异步Java消息传递?
从异步消息传递开始,您可能最终会使用ActiveMQ(经典或Artemis) , RabbitMQ或Kafka 。 同样,这只是一个受欢迎的选择。
不过,这里有几个随机点:
ActiveMQ和RabbitMQ都是传统的成熟消息代理。 这意味着一个相当聪明的经纪人,并且愚蠢的消费者。
ActiveMQ历来具有易于嵌入(用于测试)的优势,可通过RabbitMQ / Docker / TestContainer设置缓解这种情况
卡夫卡不是传统的经纪人。 恰恰相反,本质上是一个相对“笨拙”的消息存储库(认为是日志文件),需要更聪明的使用者进行处理。
为了更好地了解何时使用RabbitMQ(或通常的传统消息代理)或Kafka,请以Pivotal的匹配博客文章作为起点 。
但是,总的来说,在选择经纪人时,请尽量消除任何人为的业绩原因。 曾经有一段时间,团队和在线社区就RabbitMQ的运行速度和ActiveMQ的运行速度争论不休。
现在,您对RabbitMQ运行缓慢的观点相同,每隔一秒钟只有 20-30K /消息保持一致。 卡夫卡每秒被引用10万条消息。 首先,这些比较很方便地忽略了您实际上是在比较苹果和橙子。
但更是如此:对于阿里巴巴集团而言 ,这两个吞吐率数字可能都处于较低或中等水平,但是您的作者从未在现实世界中看到过如此大的项目(每分钟数百万条消息)。 它们确实存在,但是对于其他99%的常规Java业务项目而言,这些数字都无需担心。
因此,请忽略炒作并明智地选择。
我可以使用哪些库进行微服务测试?
根据您的堆栈,您可能最终会使用Spring专用工具 (Spring生态系统)或类似Arquillian (JavaEE生态系统)的工具。
您需要查看Docker和非常好的Testcontainers库,例如,它可以帮助您轻松快速地为本地开发或集成测试设置Oracle数据库。
要模拟整个HTTP服务器,请查看Wiremock 。 为了测试异步消息传递,请尝试嵌入(ActiveMQ)或dockering(RabbitMQ),然后使用Awaitility DSL编写测试。
除此之外,您所有通常的可疑对象(例如Junit , TestNG)都适用于AssertJ和Mockito 。
Note that this is by no means a comprehensive list and if you are missing your favorite tool, post it in the comments section and I'll pick it up in the next revision of this guide.
How do I enable logging for all my Java microserviecs?
Logging with microservices is an interesting and rather complex topic. Instead of having one log file that you can less or grep, you now have n-log files, that you would like to see combined.
A great starting point for the whole logging ecosystem is this article . Make sure to read it, especially the Centralized Logging section in terms of microservices.
In practice, you'll find various approaches:
A sysadmin writing some scripts that collect and merge log files from various server into one log file and put them onto FTP servers for you to download.
Run cat/grep/unig/sort combos in parallel SSH sessions. You can tell your manager: that's what Amazon AWS does internally .
Use a tool like Graylog or the ELK Stack (Elasticsearch, Logstash, Kibana)
How do my microservices find each other?
So far, we kind of assumed that our microservices all know each other, know their corresponding IPS. More of a static setup. So, our banking monolith[ip=192.168.200.1] knows that he has to talk to the risk server[ip=192.168.200.2], which is hardcoded in a properties file.
You can, however, choose to make things much more dynamic:
You could not deploy application.properties files with your microservices anymore, instead use a cloud config server where all microservices pull their config from.
Because your service instances might change their locations dynamically (think of Amazon EC2 instances getting dynamic IPs and you elastic-auto-scale the hell out of the cloud), you soon might be looking at a service registry, that knows where your services live with what IP and can route accordingly.
And now since everything is dynamic, you have new problems like automatic leader election: Who is the master that works on certain tasks to eg not process them twice? Who replaces the leader when he fails? 和谁一起?
In general terms, this is what's called microservice orchestration and another huge topic by itself.
Libraries like Eureka or Zookeeper try to 'solve' these problems, like clients or routers knowing which services are available where. On the other hand, they introduce a whole lot of additional complexity.
Just ask anyone who ever ran a ZooKeeper setup.
How to do authorization and authentication with Java microservices?
Another huge topic, worth its own essay. Again, options range from hardcoded HTTPS basic auth with self-coded security frameworks, to running an Oauth2 setup with your own Authorization Server .
How do I make sure that all my environments look the same?
What's true for non-microservice deployments is also true for microservice deployments. You will try a combination of Docker/Testcontainers as well as scripting/Ansible.
Try and keep it simple.
Not a question: Yaml Indentation Tales
Making a hard cut from specific library questions, let's have a quick look at Yaml. It is the file format being used as the de-facto file format to 'write configuration as code'. From simpler tools like Ansible to the mighty Kubernetes.
To experience YAML indentation pain yourself, try and write a simple Ansible files and see how often you need to re-edit the file to get indentation working properly, despite various levels of IDE support. And then come back to finish off this guide.
Yaml :
- is :
- so
- great
What about Distributed Transactions? Performance Testing? Other topics?
Unfortunately, those topics didn't make it in this revision of this guide. 敬请期待更多。
Conceptual Microservice Challenges
In addition to the specific Java microservice issues, there's also issues that come with any microservice project. These are more from an organizational, team or management perspective.
Frontend/Backend Mismatch
Something that occurs in many microservice projects, is what I would call the frontend-backend microservice mismatch. 这意味着什么?
That in good old monoliths, frontend developers had one specific source to get data from. In microservice projects, frontend developers suddenly have n-sources to get data from.
Imagine you are building some Java-IoT microservices project. Say, you are surveilling machines, like industry ovens across Europe. And these ovens send you regular status updates with their temperatures etc.
Now sooner or later, you might want to be able to search for ovens in an admin UI, maybe with the help of a "search oven" microservices. Depending on how strict your backend colleagues might interpret domain driven design or microservice laws it could be that the "search oven" microservice only returns you IDs of ovens, no other data, like its type, model or location.
For that, frontend developers might have to do one or n-additional calls (depending on your paging implementation), to a "get oven details" microservice, with the ids they got from the first microservice.
And while this only a simple (but taken from a real-life project(!)) example, it demonstrates the following issue:
Real-life supermarkets got huge acceptance for a reason. Because you don't have to go to 10 different places to shop vegetables, lemonade, frozen pizza and toilet paper. Instead you go to one place.
It's simpler and faster. It's the same for frontend developers and microservices.
Management Expectations
This issue is something that is an unfortunate side-effect of individual developers, programming magazines or cloud companies pushing microservices:
Management having the impression that you now can pour in an infinite amount of developers into the (overarching) project, as developers can now work completely independent from each other, everyone on their own microservice. With just some tiny integration work needed, at the very end (ie shortly before go-live).
Let's see why this mindset is such an issue in the next paragraphs.
Smaller pieces do not mean better pieces
One rather obvious issue is, that 20 smaller pieces (as in microservices) does not actually mean 20 better pieces . Purely from a technical quality perspective, it could mean that your individual services still execute 400 Hibernate queries to select a User from a database across layers and layers of unmaintainable code.
To go back to Simon Brown's quote, if people cannot build monoliths properly, they will be having a hard time building proper microservices.
Especially resilience and everything that happens after the go-live is such an afterthought in many microservice projects, that it is somewhat scary to see the microservices running live.
This has a simple reason though: Because Java developers usually are not interested not trained properly in resilience, networking and other related topics.
Smaller pieces lead to more technical pieces
In addition, there's the unfortunate tendency for user stories to get more and more technical (and therefore stupid), the more micro and abstracted away from the user they get.
Imagine your microservice team is asked to write a technical, login-against-a-database-microservice which is roughly this:
@Controller
class LoginController {
// ...
@PostMapping ( "/login" )
public boolean login ( String username , String password ) {
User user = userDao . findByUserName ( username );
if ( user == null ) {
// handle non existing user case
return false ;
}
if (! user . getPassword (). equals ( hashed ( password ))) {
// handle wrong password case
return false ;
}
// 'Yay, Logged in!';
// set some cookies, do whatever you want
return true ;
}
}
Now your team might decide (and maybe even convince businesspeople): That is way too simple and boring, instead of a login service, let's write a really capable UserStateChanged microservice - without any real, tangible business requirements.
And because Java is currently out of fashion, let's write the UserStateChanged microservice in Erlang. And let's try to use red-black trees somewhere, because Steve Yegge wrote you need to know them inside-out to apply for Google.
From an integration, maintenance and overall-project perspective, this is just as bad as writing layers of spaghetti code inside the same monolith.
Fabricated and over-the-top example? 是。
Unfortunately, also not uncommon in real-life.
Smaller pieces lead to smaller understanding
Then there's this topic of understanding the complete system, its processes and workflows, if you as a developer are only responsible to work on isolated microservice[95:login-101:updateUserProfile].
It blends in with the previous paragraph, but depending on your organization, trust and communication levels, this can lead to a lot of shoulder-shrugging and blaming, if a random part of the whole microservice chain breaks down - with no-one accepting full responsibility anymore.
Not just insinuating bad faith, but the problem that it actually is really difficult to understand n-amount of isolated pieces and their place in the big picture.
Communication & Maintenance
Which blends in with the last issue here: Communication & Maintenance. Which obviously depends heavily on company size, with the general rule: The bigger, the more problematic.
Who is working on microservice number 47?
Did they just deploy a new, incompatible microservice version? Where was this documented?
Who do I need to talk to for a new feature request?
Who is going to maintain that Erlang microservice after Max left the company?
All our microservice teams work not only in different programming languages, but also in different time zones! How do we coordinate properly?
The overarching theme here is, that similarly to DevOps skills, a full-on microservices approach in a bigger, maybe even international company, comes with a ton of additional communication challenges. As a company, you need to be prepared for that.
鳍
Having read this article you might conclude that your author is recommending strictly against microservices. This is not entirely true - I am mainly trying to highlight points that are forgotten in the microservices frenzy.
Microservices are on a pendulum
Going full-on Java microservices is one end a pendulum. The other end would be something like hundreds of good old Maven modules in a Monolith. You'll have to strike the right balance.
Especially in greenfield projects there is nothing stopping you from taking a more conservative, monolithic approach and building fewer, better-defined Maven modules instead of immediately starting out with twenty, cloud-ready Microservices.
Microservices generate a ton of additional complexity
Keep in mind that, the more microservices you have, and the less really strong DevOps talent you have (no, executing a few Ansible scripts or deploying on Heroku does not count), the more issues you will have later on in production.
Reading through the #issues-and-questions section of this guide is already exhausting. Then think about implementing solutions for all these infrastructure challenges. You'll suddenly realize that none of this has to do with business programming anymore (what you are paid for), but rather with a fixation of more technology on even more technology.
Siva summed it up perfectly on his blog :
I can't explain how horrible it feels when the team spends 70% of the time fighting with this modern infrastructure setup and 30% of the time on actual business logic.
Siva Prasad Reddy
Should you create Java microservices?
To answer that question, I'd like to end this article with a very cheeky, Google-like interview teaser. If you know the answer to this question by experience even though it seemingly has nothing to do with microservices, then you might be ready for a microservices approach.
情境
Imagine you have a Java monolith running solo on the smallest Hetzner dedicated machine. The same goes for your database server, it also runs on a similar Hetzner machine.
And let's also assume that your Java monolith can handle workflows like user registrations and you do not spawn hundreds of database queries per workflow, but only a reasonable handful (< 10).
题
How many database connections should your Java monolith (connection pool) open up to your database server?
为什么? And to how many concurrently active users do you think your monolith can (roughly) scale?
回答
Post your reply to these questions in the comment section. I'm looking forward to all answers.
Now, make up your own mind
If you are still here with me: Thanks for reading!
还有更多来自哪里
This article originally appeared at https://www.marcobehler.com/guides/java-microservices-a-practical-guide as part of a series of guides on modern Java programming. 要查找更多指南,请访问网站或订阅新闻通讯以获取有关新发布的指南的通知: https : //bit.ly/2K0Ao4F 。
From: https://dev.to/marcobehler/java-microservices-a-practical-guide-29kn