JavaRush /Java 博客 /Random-ZH /Java 微服务指南。第 3 部分:一般问题

Java 微服务指南。第 3 部分:一般问题

已在 Random-ZH 群组中发布
《Java微服务:实用指南》的 翻译和改编。指南的前几部分: 让我们从抽象的事物开始,到具体的库结束,看看 Java 中微服务的固有问题。 Java 微服务指南。 第 3 部分:一般问题 - 1

如何使 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:通话接通,账户创建成功。
  • DELAY:呼叫已接通,但完成时间太长。
  • 错误。呼叫失败,您可能发送了不兼容的请求,或者系统可能无法工作。
任何程序都应该处理错误情况,而不仅仅是成功的情况。这同样适用于微服务。即使在开始部署和发布各个微服务后,您需要付出额外的努力来确保 API 的所有已部署版本都兼容。 Java 微服务指南。 第 3 部分:一般问题 - 2一个有趣的例子是延迟的情况。例如,响应者的微服务硬盘已满,响应时间不再是 50 毫秒,而是 10 秒。当您遇到一定的负载,导致 BillingService 的无响应开始在您的系统中级联时,情况会变得更加有趣。作为一个说明性的例子,想象一下厨房慢慢地启动了所有餐厅服务员的“区块”。本节显然无法提供微服务弹性主题的详尽概述,但它可以提醒开发人员,这实际上是在首次发布之前需要解决而不是忽略的问题(根据经验,这种情况发生的频率比不是)。如下)。Netflix 的 Hystrix 是一个可以帮助您考虑延迟和容错的流行库。使用其文档更深入地了解该主题。

消息传递弹性模式

让我们仔细看看异步通信。我们的 BillingService 程序现在可能看起来像这样,假设我们使用 Spring 和 RabbitMQ 进行消息传递。为了创建帐户,我们现在向 RabbitMQ 消息代理发送一条消息,其中有几个工作人员正在等待新消息。这些工作人员创建 PDF 发票并将其发送给适当的用户。
@Service
class BillingService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

     public void bill(User user, Plan plan) {
        Invoice invoice = createInvoice(user, plan);
        // преобразует счет, например, в json и использует его How тело messages
        rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
        // ...
    }
}
潜在的错误现在看起来有点不同,因为您不再像使用同步 HTTP 连接那样立即收到 OK 或 ERROR 响应。相反,我们可能会遇到三种可能出错的情况,这可能会引发以下问题:
  1. 我的消息是否已被员工传递和使用?还是丢失了?(用户不会收到发票)。
  2. 我的消息只发送一次吗?或者多次交付但仅处理一次?(用户将收到多张发票)。
  3. 配置:从“我是否使用了正确的路由键/名称进行交换”到“我的消息代理是否已正确配置和维护,或者其队列是否已满?” (用户不会收到发票)。
每个单独的异步微服务弹性模式的详细描述超出了本指南的范围。然而,有迹象表明,方向是正确的。此外,它们将依赖于消息传递技术。例子:
  • 如果您使用 ActiveMQ 等 JMS 实现,则可以用速度换取两阶段 (XA) 提交的保证。
  • 如果您使用 RabbitMQ,请先阅读本教程,然后仔细考虑一般的确认、容错和消息可靠性。
  • 也许有人精通配置 Active 或 RabbitMQ 服务器,尤其是与集群和 Docker 结合使用(有人吗?;))

哪个框架是 Java 微服务的最佳解决方案?

一方面,您可以安装非常流行的选项,例如Spring Boot。它使创建 .jar 文件变得非常容易,附带 Tomcat 或 Jetty 等内置 Web 服务器,并且可以在任何地方快速运行。非常适合构建微服务应用程序。最近,出现了一对专门的微服务框架,KubernetesGraalVM,部分受到反应式编程的启发。这里有一些更有趣的竞争者:QuarkusMicronautVert.xHelidon。最后,您必须自己选择,但我们可以给您一些可能不完全标准的建议:除了 Spring Boot 之外,所有微服务框架通常都被宣传为速度快得令人难以置信,几乎可以瞬时启动,低内存占用,并且能够扩展到无限。营销材料通常具有令人印象深刻的图形,展示了庞然大物 Spring Boot 旁边或彼此之间的平台。从理论上讲,这可以减轻支持遗留项目的开发人员的紧张,这些项目有时需要几分钟才能加载。或者在云中工作的开发人员希望在 50 毫秒内启动/停止当前需要的尽可能多的微容器。 Java 微服务指南。 第 3 部分:一般问题 - 3然而,问题是这些(人为的)裸机启动时间和重新部署时间几乎对项目的整体成功没有贡献。至少它们的影响远小于强大的框架基础设施、强大的文档、社区和强大的开发人员技能。所以最好这样看:如果到目前为止:
  • 您让 ORM 大量运行,为简单的工作流程生成数百个查询。
  • 您需要无尽的千兆字节来运行中等复杂的整体应用程序。
  • 您有如此多的代码并且复杂性如此之高(我们现在不是在谈论像 Hibernate 这样的潜在缓慢启动器),以至于您的应用程序需要几分钟才能加载。
如果是这种情况,那么添加额外的 mycoservice 问题(网络弹性、消息传递、DevOps、基础设施)将对您的项目产生比加载空的 Hello, world 更大的影响。对于开发期间的热重新部署,您最终可能会得到JRebelDCEVM等解决方案。让我们花点时间再次引用Simon Brown 的话:“如果人们无法构建(快速且高效)单体应用,那么无论结构如何,他们都将很难构建(快速且高效)微服务。” 所以明智地选择你的框架。

哪些库最适合同步 Java REST 调用?

在低级技术方面,您可能最终会得到以下 HTTP 客户端库之一:Java 的本机 HttpClient(自 Java 11 起)、Apache 的 HttpClientOkHttp。请注意,我在这里说“可能”是因为还有其他选项,从老式的JAX-RS 客户端到现代的WebSocket客户端。无论如何,趋势是生成一个 HTTP 客户端,而不是自己摆弄 HTTP 调用。为此,您需要查看OpenFeign项目及其文档,作为进一步阅读的起点。

异步 Java 消息传递的最佳代理是什么?

您很可能会遇到流行的ActiveMQ(经典或 Artemis)RabbitMQKafka
  • ActiveMQ 和 RabbitMQ 是传统的、成熟的消息代理。它们涉及“聪明的经纪人”和“愚蠢的用户”的互动。
  • 从历史上看,ActiveMQ 具有易于内联(用于测试)的优点,可以通过 RabbitMQ/Docker/TestContainer 设置来缓解。
  • Kafka 不能被称为传统的“智能”经纪人。相反,它是一个相对“愚蠢”的消息存储(日志文件),需要聪明的消费者来处理。
为了更好地了解何时使用 RabbitMQ(或一般的其他传统消息代理)或 Kafka,请查看此 Pivotal 帖子作为起点。一般来说,在选择消息代理时,尽量忽略人为的性能原因。曾经有一段时间,团队和在线社区会不断争论 RabbitMQ 有多快以及 ActiveMQ 有多慢。现在,对于 RabbitMQ 也提出了同样的论点,他们说它运行缓慢,每秒处理 20-30,000 条消息。Kafka 每秒记录 10 万条消息。坦率地说,这样的比较就像温暖与柔软的比较。此外,在这两种情况下,吞吐量值可能处于阿里巴巴集团的中低范围内。然而,您在现实中不太可能遇到这种规模的项目(每分钟数百万条消息)。它们肯定存在,并且会有问题。与其他 99% 的“常规”Java 商业项目不同。所以不要关注时尚和炒作。做出明智的选择。

我可以使用哪些库来测试微服务?

这取决于你的堆栈。如果您部署了 Spring 生态系统,那么使用该框架的特定工具将是明智的选择。如果 JavaEE 类似于ArquillianDocker 和非常好的Testcontainers库可能值得一看,它特别有助于轻松快速地设置 Oracle 数据库以进行本地开发或集成测试。要模拟测试整个 HTTP 服务器,请查看Wiremock。要测试异步消息传递,请尝试实现 ActiveMQ 或 RabbitMQ,然后使用Awaitility DSL编写测试。此外,还使用了所有常用工具 - JunitTestNG for AssertJMockito。请注意,这不是完整列表。如果您在这里没有找到您最喜欢的工具,请在评论部分发布。

如何为所有 Java 微服务启用日志记录?

微服务中的日志记录是一个有趣且相当复杂的主题。您现在拥有 n 个日志文件,而不是可以使用 less 或 grep 命令操作的一个日志文件,并且您希望它们不要太分散。本文(英文)很好地描述了日志生态系统的特征。请务必阅读它,并注意从微服务角度进行集中日志记录部分。在实践中,您会遇到不同的方法:系统管理员编写某些脚本,收集来自不同服务器的日志文件并将其合并为一个日志文件,并将它们放在 FTP 服务器上以供下载。在并行 SSH 会话中运行 cat/grep/unig/sort 组合。这正是 Amazon AWS 所做的,您可以让您的经理知道。使用GraylogELK Stack(Elasticsearch、Logstash、Kibana)等工具

我的微服务如何找到彼此?

到目前为止,我们假设我们的微服务彼此了解并且知道相应的 IPS。我们来谈谈静态配置。因此,我们的银行整体 [ip = 192.168.200.1] 知道它需要与风险服务器 [ip = 192.168.200.2] 通信,该服务器硬编码在属性文件中。但是,您可以使事情变得更加动态:
  • 使用基于云的配置服务器,所有微服务都可以从中提取其配置,而不是在其微服务上部署 application.properties 文件。
  • 由于您的服务实例可以动态更改其位置,因此值得查看了解您的服务所在位置、它们的 IP 以及如何路由它们的服务。
  • 既然一切都是动态的,就会出现新的问题,例如自动选举领导者:谁是执行某些任务的主人,以免重复处理这些任务?当领导者失败时谁来取代他?更换的依据是什么?
通俗点讲,这就是所谓的微服务编排,又是一个无底洞的话题。像EurekaZookeeper这样的库试图通过显示哪些服务可用来“解决”这些问题。另一方面,它们引入了额外的复杂性。询问曾经安装过 ZooKeeper 的人。

如何使用Java微服务组织授权和认证?

这个话题也值得单独讲一个故事。同样,选项范围从使用自定义安全框架的硬编码基本 HTTPS 身份验证到使用自己的授权服务器运行 Oauth2 安装。

如何确保我的所有环境看起来都一样?

对于没有微服务的部署来说是这样,对于有微服务的部署来说也是如此。尝试结合使用 Docker/Testcontainers 和 Scripting/Ansible。

没有问题:简单介绍一下 YAML

让我们暂时远离库和相关问题,快速浏览一下 Yaml。该文件格式实际上用作“将配置编写为代码”的格式。Ansible 等简单工具和 Kubernetes 等巨头也使用它。要体验 YAML 缩进的痛苦,请尝试编写一个简单的 Ansible 文件,并查看需要对该文件进行多少编辑才能使其按预期工作。尽管所有主要 IDE 都支持该格式!之后,再回来阅读本指南。
Yaml:
  - is:
    - so
    - great

那么分布式事务呢?性能测试?其他主题?

也许有一天,在手册的未来版本中。现在,仅此而已。和我们在一起!

微服务的概念问题

除了 Java 中微服务的具体问题之外,任何微服务项目中都会出现其他问题。它们主要与组织、团队和管理有关。

前端和后端不匹配

前端和后端不匹配是许多微服务项目中非常常见的问题。这是什么意思?只是在古老的巨石中,Web 界面开发人员有一个特定的来源来获取数据。在微服务项目中,前端开发人员突然有n个来源可以获取数据。想象一下,您正在使用 Java 创建某种 IoT(物联网)微服务项目。假设您管理整个欧洲的大地测量机器和工业炉。这些烤箱会定期向您发送温度等最新信息。迟早您可能想在管理 UI 中找到烤箱,也许使用“熔炉搜索”微服务。根据后端对应方应用领域驱动设计或微服务法的严格程度,“查找烤箱”微服务可能只返回烤箱 ID,而不返回其他数据,例如类型、型号或位置。为此,前端开发人员需要使用从第一个微服务收到的 ID 在“获取熔炉数据”微服务中进行一个或 n 个额外调用(取决于分页实现)。 Java 微服务指南。 第 3 部分:一般问题 - 4虽然这只是一个简单的例子,虽然取自一个真实的(!)项目,但它也说明了以下问题:超市已经变得非常受欢迎。那是因为有了它们,你不必去 10 个不同的地方购买蔬菜、柠檬水、冷冻披萨和卫生纸。相反,您可以前往一个地方。这样更容易、更快捷。对于前端和微服务开发人员来说也是如此。

管理层期望

管理层错误地认为,他们现在需要为一个(总体)项目雇用无限数量的开发人员,因为开发人员现在可以完全独立地彼此工作,每个人都在自己的微服务上工作。最后(发布前不久)只需要进行少量集成工作。 Java 微服务指南。 第 3 部分:一般问题 - 5事实上,这种做法是有很大问题的。在下面的段落中,我们将尝试解释原因。

“较小的部分”并不等于“更好的部分”

如果认为分成 20 个部分的代码必然比整个代码的质量更高,那将是一个很大的错误。即使我们从纯粹的技术角度来考虑质量,我们的各个服务仍然可能运行 400 个 Hibernate 查询来从数据库中选择用户,遍历不支持的代码层。我们再次回到西蒙·布朗的名言:如果你不能正确构建单体,那么就很难构建正确的微服务。谈论微服务项目中的容错能力往往已经为时已晚。以至于有时看到微服务在实际项目中如何工作会让人感到害怕。其原因是 Java 开发人员并不总是准备好在适当的级别上研究容错、网络和其他相关主题。“部件”本身较小,但“技术部件”较大。想象一下,您的微服务团队被要求编写一个用于登录数据库系统的技术微服务,如下所示:
@Controller
class LoginController {
    // ...
    @PostMapping("/login")
    public boolean login(String username, String password) {
        User user = userDao.findByUserName(username);
        if (user == null) {
            // обработка варианта с несуществующим пользователем
            return false;
        }
        if (!user.getPassword().equals(hashed(password))) {
            // обработка неверного пароля
            return false;
        }
        // 'Ю-ху, залогинorсь!';
        // установите cookies, делайте, что угодно
        return true;
    }
}
现在你的团队可能会决定(甚至可能说服业务人员)这一切都太简单和无聊了,与其编写一个登录服务,不如编写一个真正有用的 UserStateChanged 微服务,而不需要任何实际和有形的业务需求。由于目前有些人将 Java 视为恐龙,因此让我们用时尚的 Erlang 编写 UserStateChanged 微服务。让我们尝试在某个地方使用红黑树,因为 Steve Yegge 写道,你必须彻底了解它们才能申请 Google。从集成、维护和整体设计的角度来看,这就像在单个整体中编写意大利面条式代码层一样糟糕。一个人为的普通例子?这是真实的。然而,这在现实中是可能发生的。

更少的碎片 - 更少的理解

那么自然会出现关于理解整个系统、其流程和工作流程的问题,但与此同时,作为开发人员,您只负责处理独立的微服务[95:login-101:updateUserProfile]。它与上一段一致,但根据您的组织、信任和沟通水平,如果微服务链中出现意外故障,这可能会导致很多混乱、耸肩和指责。没有人会对所发生的事情承担全部责任。这根本不是不诚实的问题。事实上,连接不同的部分并理解它们在项目整体图中的位置是非常困难的。

通讯与服务

沟通和服务的水平很大程度上取决于公司的规模。然而,一般关系是显而易见的:越多,问题就越多。
  • 谁运行微服务#47?
  • 他们是否刚刚部署了一个新的、不兼容的微服务版本?这是在哪里记录的?
  • 我需要与谁联系来请求新功能?
  • 在唯一懂得这种语言的人离开公司之后,谁将支持 Erlang 中的微服务?
  • 我们所有的微服务团队不仅使用不同的编程语言,而且还处于不同的时区!我们如何正确协调这一切?
Java 微服务指南。 第 3 部分:一般问题 - 6主要的一点是,与 DevOps 一样,在大型公司(甚至可能是国际公司)中成熟的微服务方法会带来一系列额外的通信问题。公司必须为此认真做好准备。

结论

读完本文后,您可能会认为作者是微服务的狂热反对者。这并不完全正确——我基本上是想强调在新技术的疯狂竞争中很少有人关注的点。

微服务还是整体服务?

随时随地使用 Java 微服务是一种极端。另一个结果是像一个整体中的数百个良好的旧 Maven 模块。您的任务是找到适当的平衡。对于新项目尤其如此。没有什么可以阻止您采取更保守的“整体”方法并创建更少的优秀 Maven 模块,而不是从 20 个云就绪微服务开始。

微服务产生额外的复杂性

请记住,您拥有的微服务越多,而您拥有的 DevOps 功能就越弱(不,运行几个 Ansible 脚本或部署到 Heroku 不算!),您在此过程中遇到的问题就越多。即使只是读到本指南专门讨论有关 Java 微服务的一般问题的部分的结尾也是一项相当乏味的任务。认真思考为所有这些基础设施问题实施解决方案,您会突然意识到,这不再全是业务编程(您获得报酬的事情),而是在更多技术上修复更多技术。Shiva Prasad Reddy 在他的博客中对此进行了完美的总结: “当一个团队花费 70% 的时间与这种现代化基础设施作斗争,而只剩下 30% 的时间来处理实际的业务逻辑时,你不会知道这是多么可怕。 ” 希瓦·普拉萨德·雷迪

值得创建 Java 微服务吗?

为了回答这个问题,我想用一个非常厚颜无耻、类似谷歌面试的预告片来结束这篇文章。如果您从经验中知道这个问题的答案,即使它看起来与微服务没有任何关系,您也可能已经准备好采用微服务方法。

设想

想象一下,您有一个单独运行在最小的Hetzner专用服务器上的 Java 整体应用程序。这同样适用于您的数据库服务器,它也运行在类似的Hetzner机器上。我们还假设您的 Java 整体架构可以处理工作流(例如用户注册),并且您不会为每个工作流创建数百个数据库查询,而是创建更合理的数量(<10)。

问题

您的 Java 整体应用程序(连接池)应在数据库服务器上打开多少个数据库连接?这是为什么?您认为您的整体架构可以(大约)扩展多少并发活跃用户?

回答

在评论部分留下您对这些问题的答案。我期待所有的答案。 Java 微服务指南。 第 3 部分:一般问题 - 8现在你下定决心吧。如果您读到最后,我们非常感谢您!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION