写点什么

基于云原生技术和服务网格的 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:051869
用户头像

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

关注

评论

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

微服务平滑迁移上云最佳实践

阿里巴巴云原生

阿里云 微服务 云原生

聊一聊华为云弹性公网IP的那些事儿

爱尚科技

Docker搭建私有registry镜像仓库

蜗牛也是牛

行业首个测试开发技术大赛开始报名啦~ 10万现金奖励等你来挑战

测试人

软件测试 自动化测试 测试开发 测试比赛

什么是组织孤岛?它会带来哪些影响?可以这样去对付它

Baklib

剖析一下"抢茅台"脚本底层逻辑

京东科技开发者

接口 后端 脚本 风险识别 风险控制

被性能优化撂倒无数次后的顿悟!465页调优笔记助力大厂面试之旅

程序知音

Java JVM 性能调优 java架构 后端技术

首份关基安全国标刚发布,客户把我叫到了办公室......

青藤云安全

网络安全 青藤云安全

PCB焊接出问题了,是PCB工程师的锅吗?

华秋PCB

PCB PCB设计 焊接

防止会议被入侵,华为云会议更专业

IT科技苏辞

聊聊mybatis的反射之Reflector类

急需上岸的小谢

11月月更

一个关于X证券20000台服务器的血泪故事

青藤云安全

网络安全 青藤云安全

我服了,阿里挖过来的leader连垃圾回收都说不清楚

钟奕礼

Java java程序员 java面试 java编程

一个 3 年 Java 程序员 5 家大厂的面试总结(已拿Offer)

钟奕礼

Java java程序员 java面试 java编程

行业首个测试开发技术大赛开始报名啦~ 10万现金奖励等你来挑战

测吧(北京)科技有限公司

软件测试

HDC.Cloud Day | 全国首场上海站告捷,聚开发者力量造梦、探梦、筑梦

华为云开发者联盟

云计算 华为云

2022 Java 企业面试题汇总

钟奕礼

Java java程序员 java面试 java编程

Java面试读这一篇就够了:100个互联网大厂Java面试真题整理

钟奕礼

Java Java 面试 java程序员 java编程

索引数据结构千千万 , 为什么B+Tree独领风骚

程序知音

PG SQL 语法汇总

蜗牛也是牛

基于云原生网关的可观测性最佳实践

阿里巴巴云原生

阿里云 微服务 云原生 可观测

数据库索引相关和EFCore的索引映射

C++后台开发

数据库 后端开发 Linux服务器开发 C++开发 数据库索引

Zookeeper安装与基础命令操作

石臻臻的杂货铺

zookeeper 11月月更

Java 反射 (二) Class类

浅辄

Java 反射 11月月更

聊聊mybatis的架构模块

急需上岸的小谢

11月月更

阿里P8大佬神创“Netty突击笔记”,堪称全网最全最牛逼的核心原理手册

程序知音

Java Netty JAVA开发 java架构 后端技术

浅析分布式事务的底层实现模型

移动云大数据

小令观点 | 急需身份证扫描件?【A4证照扫描王】来帮你

令牌云数字身份

软件推荐 入职 打印 证照扫描

聊聊索引

急需上岸的小谢

11月月更

进大厂必刷的Java面试题

钟奕礼

Java java程序员 java面试 java编程

简述SpringAOP的实现原理

千锋IT教育

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