写点什么

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

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

关注

评论

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

spring4.1.8初始化源码学习三部曲之一:AbstractApplicationContext构造方法

程序员欣宸

Java spring 6月月更

C#入门系列(七) -- 循环语句

陈言必行

C# 6月月更

如何写好技术博客

卢卡多多

技术 博客 6月月更

vue指令-2

小恺

6月月更

Junit 测试中如何对异常进行断言

HoneyMoose

Linux驱动开发_视频广告机开发、Linux进程编程介绍

DS小龙哥

6月月更

在线JADE转HTML工具

入门小站

工具

Fabric.js 精简输出的JSON🎫

德育处主任

FabricJS 6月月更

AssertJ 的异常(Exception )断言

HoneyMoose

浅谈JavaScript原型和原型链

大熊G

JavaScript 前端 6月月更

见自身,见天地:华为,将ICT技术变成绿色未来的支点

脑极体

NFT赛道或进入聚合时代,OKALEIDO成BNB Chain上的首个NFT聚合平台

股市老人

【100个 Unity实用技能】| 游戏中使技能或装备跟随角色环绕,持续旋转

呆呆敲代码的小Y

PHP开发者福音,支持CRUD代码生成且前后分离的tp6+Vue3后台管理系统开源啦!

妙码生花

php typescript Vue3 thinkphp Pinia

Web Service进阶(五)SOAPBinding方式讲解

No Silver Bullet

6月月更 SOAPBinding

【100个 Unity踩坑小知识点】| Unity 使用Quaternion.AngleAxis随机一个方向

呆呆敲代码的小Y

【100个 Unity踩坑小知识点】| Unity调用API ,动态获取Android权限,附带所有Android权限表格

呆呆敲代码的小Y

如何使用物联网低代码平台进行模型管理?

AIRIOT

物联网 低代码开发

【100个 Unity实用技能】| 游戏中获取鼠标点击的坐标,并将游戏对象移动到鼠标的点击位置

呆呆敲代码的小Y

【100个 Unity踩坑小知识点】| Unity中的 碰撞盒检测 Physics.OverlapBox、OverlapCapsule、OverlapSphere

呆呆敲代码的小Y

【网络编程知识】什么是Socket?概念及原理分析

呆呆敲代码的小Y

软件设计本质

GalaxyCreater

架构 设计模式 架构师 系统 软件设计

【100个 Unity踩坑小知识点】| Unity控制物体持续指向某个方向

呆呆敲代码的小Y

【愚公系列】2022年06月 面向对象设计原则(一)-单一职责原则

愚公搬代码

6月月更

【100个 Unity踩坑小知识点】| Unity中的 Development build 详细解析

呆呆敲代码的小Y

Django API 开发:博客系统的权限管理

宇宙之一粟

django 6月月更

【100个 Unity踩坑小知识点】| 在编辑器中绘制正方体虚线、球体虚线(Gizmos 辅助线框)

呆呆敲代码的小Y

7 个 Flutter 开源项目,让你成为更好的 Flutter 开发者

坚果

6月月更

理解 Java 中的 NumberFormatException 异常

HoneyMoose

数据类型

Jason199

js 数据类型 6月月更

数据库每日一题---第4天:从不订购的客户

知心宝贝

数据库 程序员 前端 后端 6月月更

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