硬核干货——《中小企业 AI 实战指南》免费下载! 了解详情
写点什么

基于云原生技术和服务网格的 Java EE

  • 2018-04-25
  • 本文字数:5065 字

    阅读完需:约 17 分钟

关键要点

  • 服务网格将所需的技术关注点透明地添加到微服务中。
  • 路由、弹性或认证等问题成为服务网格的职责。
  • 应用程序代码变得更加精简,并更多地关注实际的业务逻辑。
  • Istio 通过边车代理容器增强工作负载,例如 Kubernetes Pod。
  • Java EE 通过支持开发人员实施精益业务逻辑与云原生技术完美地集成在一起。

Java EE、云原生和服务网格——听起来似乎不应该把它们放在一起,又或者它们确实应该在一起呢?我们是否有可能在无需自己实现所有东西的情况下开发现代云原生 Java 企业应用程序来满足可扩展性、监控、跟踪或路由等问题?如果可以,那么该怎样实现?

采用微服务架构的企业面临着一个挑战,就是如何将服务发现、安全、监控、跟踪、路由或故障处理等技术问题以一致的方式添加到微服务当中。软件团队可以使用不同的技术来实现各自的服务,但他们需要遵守组织标准。通过添加共享资源(如 API 网关)将微服务耦合在一起,这在某种程度上破坏了微服务架构的原本意义。但不管怎样,我们应该避免冗余。

服务网格增强了每个微服务,而这些微服务是网格的一部分。这些增强功能以​​与技术无关的方式添加到系统中,不会影响到应用程序。应用程序专注于实现业务逻辑,由环境负责来处理技术依赖。

仪器和 3D 打印机

我们即将要给出的示例应用程序是仪器商店和 3D 打印机机器人。假设有这样的一个仪器商店 SaaS 应用程序,客户可以通过这个应用程序订购制作好的仪器。商店本身不直接提供仪器,而是将请求转发给通过 3D 打印技术生产仪器的机器人。

我们使用 Java EE 8 来实现这两种云原生微服务,部署到 Kubernetes 集群中,并由 Istio 来管理。

云原生技术简介

为了使用 Kubernetes 和 Istio 来管理 Java EE 应用程序,我们需要将它们打包成容器。 Docker 镜像是通过定义 Dockerfile 文件来创建的。这些文件指定了整个应用程序的运行时,包括配置、Java 运行时(即 JRE 和应用程序容器)以及所需的操作系统二进制文件。

下面的 Dockerfile 用于打包仪器商店应用程序,它使用了一个自定义基础镜像,其中包含了一个 OpenLiberty 应用服务器:

复制代码
FROM docker.example.com/open-liberty:1
COPY target/instrument-craft-shop.war $DEPLOYMENT_DIR

OpenLiberty 基础镜像里已经包含了运行应用程序服务器所必需的东西。Dockerfile 将添加可能需要用到的配置。通过使用这种简单的部署方法,我们不仅很好地利用了 Docker 的 Copy-On-Write 文件系统,而且带来了快速构建和缩短交付时间的可能性。

构建好的镜像将在编排环境中运行,在我们的例子里,就是要在 Kubernetes 群集中运行。

因此,与 Kubernetes 环境相关的文件也成为应用程序代码库的一部分。 YAML 描述符包含了集群将如何运行、分布和组织我们的应用程序及 Docker 容器。

以下显示了仪器商店服务的定义,Kubernetes 服务是对应用程序的逻辑抽象。

复制代码
kind: Service
apiVersion: v1
metadata:
  name: instrument-craft-shop
  labels:
    app: instrument-craft-shop
spec:
  selector:
    app: instrument-craft-shop
  ports:
    - port: 9080
      name: http

该服务将请求分发给正在运行的实例,容器则由 Kubernetes 来管理。Kubernetes 部署文件定义了如何执行 Kubernetes Pod(也就是实际运行的工作负载)以及需要多少副本:

复制代码
kind: Deployment
apiVersion: apps/v1beta1
metadata:
  name: instrument-craft-shop
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: instrument-craft-shop
        version: v1
    spec:
      containers:
      - name: instrument-craft-shop
        image: docker.example.com/instrument-craft-shop:1
        imagePullPolicy: IfNotPresent
      restartPolicy: Always

该服务将采用与定义的选择器相匹配的 Pod。这里的应用程序标签实际上是标准名称,与我们的应用程序相匹配。我们最好可以再定义一个版本标签,以便在多个应用程序版本同时存在的时候能够进一步自定义服务路由。

集群外部的客户端将会调用仪器商店应用程序。Kubernetes 的摄入资源将入口流量路由到相应的服务:

复制代码
kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: instrument-craft-shop
  annotations:
    kubernetes.io/ingress.class: istio
spec:
  rules:
  - http:
      paths:
      - path: /instrument-craft-shop/.*
        backend:
          serviceName: instrument-craft-shop
          servicePort: 9080

ingress.class 注释指定了将 istio 作为入口实现,因此 Kubernetes 将为我们部署正确的 Istio 入口。

仪器商店应用程序将通过 HTTP 与后端的机器人应用程序进行通信。机器人应用程序定义了类似的 Kubernetes 服务和部署资源,名为 maker-bot。

因为这两个应用程序都是 Kubernetes 群集的一部分,所以它们可以使用服务定义作为主机名进行通信。 Kubernetes 通过 DNS 来解析服务名称。

下面是机器人客户端的代码:

复制代码
@ApplicationScoped
public class MakerBot {
    private Client client;
    private WebTarget target;
    @PostConstruct
    private void initClient() {
        client = ClientBuilder.newBuilder()
                .connectTimeout(1, TimeUnit.SECONDS)
                .readTimeout(3, TimeUnit.SECONDS)
                .build();
        target = client.target("http://maker-bot:9080/maker-bot/resources/jobs");
    }
    public void printInstrument(InstrumentType type) {
        JsonObject requestBody = createRequestBody(type);
        Response response = sendRequest(requestBody);
        validateResponse(response);
    }
    private JsonObject createRequestBody(InstrumentType type) {
        return Json.createObjectBuilder()
                .add("instrument", type.name().toLowerCase())
                .build();
    }
    private Response sendRequest(JsonObject requestBody) {
        try {
            return target
                .request()
                .post(Entity.json(requestBody));
        } catch (Exception e) {
            throw new IllegalStateException("Could not print instrument, reason: "
                    + e.getMessage(), e);
        }
    }
    private void validateResponse(Response response) {
        if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL)
            throw new IllegalStateException("Could not print instrument, status: "
                    + response.getStatus());
    }
    @PreDestroy
    private void closeClient() {
        client.close();
    }
}

从 Java EE 8 开始,JAX-RS 客户端构建器 API 支持 connectTimeout 和 readTimeout 方法。我们强烈建议设置这些超时参数,以防止长时间阻塞线程。

正如你所看到的,机器人应用程序通过主机名称 maker-bot 和端口 9080(与 Kubernetes 的服务定义相匹配)进行配置。我们因此能够摆脱服务发现配置,例如为不同的环境定义不同的目标端点、IP 地址或主机名称。不管在哪个 Kubernetes 集群环境中,URL 都是稳定不变的,并可以被恰当地解析。

Istio 登场

接下来我们将演示 Istio,它是 Java/JVM 领域使用最为广泛的服务网格示例之一。

Istio 将技术切面关注点透明地添加到应用程序中。它通过代理边车容器增强了应用程序 Pod,捕获主容器的流入和流出流量。主应用程序容器连接到必要的服务上,但不知道代理服务器的存在。我们可以将 Istio 视为一个切面,就像在面向方面的编程模型里一样,它们被透明地添加到应用程序中。Istio 可以使用多种编排框架实现,包括 Kubernetes。

我们的示例应用程序被部署到 Kubernetes 集群中,这个集群使用了 Istio 和自动边车注入。边车注入自动将 Istio 代理容器添加到每个 Pod。

Istio Pilot 负责配置边车代理的路由规则和弹性参数。我们在 YAML 文件中配置 Istio 切面,类似 Kubernetes 的资源。

根据最佳实践,我们为相应的应用程序服务添加默认路由:

复制代码
apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
  name: instrument-craft-shop-default
spec:
  destination:
    name: instrument-craft-shop
  precedence: 1
  route:
  - weight: 100
    labels:
      version: v1

该路由规则指定了所有流向仪器商店应用程序的流量都将被路由到版本为 v1 的实例上。Istio 资源以与 Kubernetes 资源相同的方式添加到群集中,例如通过 kubectl 命令行。我们现在可以进一步增强这些路由规则。

以下路由规则给后端机器人增加了一个 2 秒种的超时时间:

复制代码
apiVersion: config.istio.io/v1alpha2
kind: RouteRule
metadata:
name: maker-bot-default
spec:
destination:
name: maker-bot
precedence: 1
route:
- weight: 100
labels:
version: v1
httpReqTimeout:
simpleTimeout:
timeout: 2s

这个超时时间与其他应用程序级别的超时时间是相互独立的,一旦被触发,代理就会返回 503 错误。这样可以防止系统出现无限制的阻塞,即使没有为 MakerBot 类的 JAX-RS 客户端配置超时时间。客户端将收到超时通知,以先触发的那个超时时间为准。

Istio 的另一个特点是可以通过添加回路断路器来以防止应用程序出现过载和整体失效。我们为后端机器人目标策略添加了一个回路断路器:

复制代码
apiVersion: config.istio.io/v1alpha2
kind: DestinationPolicy
metadata:
  name: maker-bot-circuit-breaker
spec:
  destination:
    name: maker-bot
  circuitBreaker:
    simpleCb:
      httpConsecutiveErrors: 1
      sleepWindow: 10s
      httpDetectionInterval: 10s
      httpMaxEjectionPercent: 100
      maxConnections: 1
      httpMaxPendingRequests: 1
      httpMaxRequestsPerConnection: 1

这个目标策略一次只允许一个连接,并会拒绝其他连接。我们还可以配置回路断路器如何再次打开和关闭,具体需要根据特定的系统设置进行调整。

被透明地添加到现有应用程序中的还有监视、日志和跟踪,以及身份认证。边车容器中包含了 Envoy 代理,我们就是通过它们来添加这些横切面关注点,并把它们暴露给外部环境。

DevOps 工程师可以通过检查 Grafana 和 Prometheus 扩展或跟踪解决方案来访问他们需要的信息。我们通过加密边车代理之间的连接来添加认证。用户可以添加自己的证书,并配置通信策略。

结论

Java EE 与服务网格背后的想法相得益彰,而技术横切面问题(如路由、弹性或认证)成为服务网格环境的职责。

实际上,Java EE 是基于这个想法而构建起来的。应用程序本身应该更多地关注业务逻辑,解决实际的领域问题,为应用程序的用户提供价值。而处理生命周期管理、依赖注入、事务或线程等问题则是应用程序容器的职责。

编排框架和服务网格进一步采用这种方法,进行服务发现、增强弹性、认证、监控或追踪。因此这些问题不再是应用程序的关注点,应用程序应该专注于实现业务逻辑。

在未来,我们将使用存粹的 Java EE 8 或 Jakarta EE 来构建和打包应用程序,然后从应用程序外部添加技术横切面。

如果域名需要额外的关注点,例如与业务相关的指标,则可以通过集成第三方扩展来添加,例如 MicroProfile Metrics。使用支持 MicroProfile 的容器,或者将第三方库安装到应用程序容器中,作为底层的 Docker 镜像层,我们仍然能够利用精简部署的优势。这种想法符合关注点分离原则。

Docker、Kubernetes 和 Istio 等云原生技术与 Java EE 或将来的 Jakarta EE 相结合,是未来企业应用程序的最佳选择。

更多资源

关于作者

Sebastian Daschner 是一名独立 Java 顾问、作者和培训师,对编程和 Java(EE)充满热情。他是“架构现代 Java EE 应用程序”一书的作者。Sebastian 积极参与 JCP,帮助制定未来的 Java EE 标准,服务于 JAX-RS、JSON-P 和 Config 专家组,并在各种开源项目上进行合作。他因为在 Java 社区和生态系统中的贡献而被授予了 Java Champion、Oracle Developer Champion 和 2016 年 JavaOne Rockstar 的荣誉。除 Java 外,Sebastian 还是 Linux 和云原生技术的重要用户。他通过 @DaschnerS 在博客、新闻组和推特上传播计算机科学实践。平常他还喜欢搭飞机或骑摩托环游世界。

查看英文原文 Get Ready for Cloud Native, Service-Meshed Java Enterprise

2018-04-25 18:052309
用户头像

发布了 731 篇内容, 共 477.1 次阅读, 收获喜欢 2008 次。

关注

评论

发布
暂无评论
发现更多内容

《从0到1学习Flink》—— Flink 读取 Kafka 数据批量写入到 MySQL

zhisheng

大数据 flink 流计算

【迁移】撸论文系列之——Bigtable

罗琦

论文阅读 bigtable

《从0到1学习Flink》—— Mac 上搭建 Flink 1.6.0 环境并构建运行简单程序入门

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— Data Source 介绍

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— Flink 中几种 Time 详解

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— Flink 项目如何运行?

zhisheng

大数据 flink 流计算

图文并茂讲述如何正确的使用缓存

小趴菜~

缓存 后端 缓存穿透 缓存击穿 缓存雪崩

【迁移】CQRS很难吗?(译文:底部有原文地址)

罗琦

领域驱动设计 DDD

【迁移】Flink vs Spark

罗琦

大数据 flink spark

《从0到1学习Flink》—— 如何自定义 Data Source ?

zhisheng

大数据 flink 流计算

要弄清楚if/switch的本质区别,以及优化方式

张驰

Java

码农理财(二)

北漂码农有话说

极客时间的三种身份:碎片整合的大师、成长焦虑的救星、工作技能的提升站

大橘栗

《从0到1学习Flink》—— Flink 配置文件详解

zhisheng

大数据 flink 流计算

你没必要活的那么累

小天同学

深度思考 个人成长 生活 成长 感悟

ARTS 第 51 周

马克图布

ARTS 打卡计划

python 实现·十大排序算法之选择排序(Selection Sort)

南风以南

Python 排序算法

《从0到1学习Flink》—— Apache Flink 介绍

zhisheng

大数据 flink 流计算

【迁移】用Redlock构建Redis分布式锁【译】

罗琦

分布式锁

《从0到1学习Flink》—— Flink JobManager 高可用性配置

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— 介绍Flink中的Stream Windows

zhisheng

大数据 flink 流计算

勇攀监控高峰-EMonitor之根因分析

乒乓狂魔

监控 全链路监控 故障定位 根因分析 AIOPS

《从0到1学习Flink》—— 如何自定义 Data Sink ?

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— Flink Data transformation(转换)

zhisheng

大数据 flink 流计算

【迁移】读完了GFS论文之后的感悟

罗琦

大数据 GFS 论文阅读

Review week1: Amazon的领导力法则

猫吃小怪兽

学习 高效工作 程序员 个人成长

写给产品经理的信(1):产品经理的经济基础逻辑思维能力

punkboy

产品经理 产品设计 职业规划 逻辑思维 工作

《从0到1学习Flink》—— Flink 写入数据到 Kafka

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— Flink 读取 Kafka 数据写入到 RabbitMQ

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— Data Sink 介绍

zhisheng

大数据 flink 流计算

《从0到1学习Flink》—— Flink 写入数据到 ElasticSearch

zhisheng

大数据 flink 流计算

基于云原生技术和服务网格的Java EE_Java_Sebastian Daschner_InfoQ精选文章