雲原生微服務框架——Helidon

在互聯網早期的至關長一段時間內,WEB應用都是」單體應用(monolithic)「。也就是說全部的API和前端展現層代碼都被封裝在一個獨立的、自給自足的應用當中。業務邏輯,校驗,數據獲取及計算,持久化,安全,UI都封裝成一個大的包,部署在應用服務器或者web服務器上,好比說Tomcat, Apache或者Microsoft IIS。這個方法過去有效,將來也仍將有效,只不過當你的應用到達必定規模以後,就會面臨諸多挑戰:前端

  1. 部署:對於單體應用來講,checkout源代碼,編譯,測試,打包和部署這些都須要花費至關長的時間。
  2. 依賴關係,框架及開發語言:整個應用都和具體的選型和版本強綁定,一旦這些基礎框架發佈了新的版本,升級會很是困難。
  3. 單點故障:單體應用很是脆弱,若是web服務器掛了,整個應用也就掛了。
  4. 擴展性:哪怕只是應用程序其中的某一部分引起的負載升高,也必須對整個應用來進行擴容。

固然還會碰到其它問題,但這些就已經夠讓開發人員、項目經理、運維人員頭疼的了。長久以來,你們不得不去處理這些事情。java

  1. 部署:每一個服務均可以單獨地測試、編譯及部署。
  2. 依賴關係、框架及開發語言:每一個服務均可以使用本身所須要的開發語言、框架、依賴及不一樣的版本。
  3. 單點故障:每一個服務都部署在容器裏並使用編排工具來管理,單點宕機會被隔離掉,不會影響到整個應用。
  4. 擴展性:服務能夠獨立進行擴展,高負載的服務擴容,低負載的服務縮容。

微服務並非萬能的,但在許多場景下仍是很是有用的。咱們已經介紹了「爲何」須要微服務,如今來介紹下」如何「實現微服務。react

目前市面上已經有很多微服務框架了,再造一個新的彷佛沒這個必要,不過Oracle還真就這麼作了,這個項目即是Helidon。光看項目名字你可能就知道Oracle爲何要建立這個項目了:Helidon在希臘語中是燕子的意思——一種小巧、靈活的鳥類,它們自然就適合在雲端翱翔。所以,這個項目的發起人應該是想開發出一款無需應用服務器且能被用於Java SE應用的輕量級框架。web

Helidon有兩種版本:SE和MP。Helidon SE算是一個微框架(microframework),比較簡單、輕量,採用了函數式編程、響應式編程的思想,運行在自帶的Netty web服務器上。它比較相似於Javalin、Micronaut或者Spark Java這樣的框架。而Helidon MP實現了MicroProfile的規範,採用了Java EE/Jakarta EE開發人員所熟知的註解和組件的技術,好比說JAX-RS/Jersey, JSON-P以及CDI。它和Open Liberty, Payara還有Thorntail (正式名稱是 WildFly Swarm)的定位差很少。咱們先從Helidon SE開始,來了解一下這個框架。docker

Helidon SE入門

新工具的學習就是在摸着石頭過河,不過Helidon不存在這個問題。只須要安裝一些必要的依賴軟件(JDK 8+,Maven 3.5+)就能夠開始使用了。使用Docker或者Kubernetes的話,能讓容器的建立和部署更加容易。那還須要安裝Docker 18.02或更新的版本,以及Kubernetes 1.7.4+。(可使用Minikube或Docket Desktop在桌面操做系統上運行你的Kubernetes集羣)。編程

確認下軟件的版本:api

$ java --version
$ mvn --version
$ docker --version
$ kubectl version --short

一旦安裝完成,便可以經過Helidon提供的Maven項目模板(原型,Archetype)來快速生成一個工程。可能你對Maven Archetype還不太瞭解,它其實就是一些項目模板,能夠用來搭建某個框架的啓動工程以便快速使用。Oracle提供了兩套項目模板:Helidon SE和Helidon MP各一個。瀏覽器

mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion=0.10.2 \
    -DgroupId=[io.helidon.examples] \
    -DartifactId=[quickstart-se] \
    -Dpackage=[io.helidon.examples.quickstart.se]

項目模板在Maven的中央倉庫中,在這裏你能夠找到最新發布的版本。前面方括號內的值是和具體項目相關的,能夠根據你的須要來進行編輯。本文中的示例將使用下面的命令來建立完成:安全

$ mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-se \
    -DarchetypeVersion=0.10.2 \
    -DgroupId=codes.recursive \
    -DartifactId=helidon-se-demo \
    -Dpackage=codes.recursive.helidon.se.demo

完成以後,一個完整的示例工程就會在新生成的目錄當中了,目錄名即是artifactId參數裏所指定的。這是一個完整的可運行的工程,能夠編譯打包一下:服務器

$ mvn package

這個命令會把全部生成的測試用例全執行一遍,並在target/libs目錄下生成應用的jar包。這個框架還自帶了一個內嵌的web服務器,如今你能夠經過下述的命令來運行一下:

$ java -jar target/helidon-se-demo.jar

能夠看到應用程序啓動起來了,工做在8080端口上:

[DEBUG] (main) Using Console logging
2018.10.18 14:34:10 INFO io.netty.util.internal.PlatformDependent Thread[main,5,main]:
Your platform does not provide complete low-level API for accessing direct buffers
reliably. Unless explicitly requested, heap buffer will always be preferred to avoid
potential system instability.
2018.10.18 14:34:10 INFO io.helidon.webserver.netty.NettyWebServer
Thread[nioEventLoopGroup-2-1,10,main]: Channel '@default' started:
[id: 0x3002c88a, L:/0:0:0:0:0:0:0:0:8080]
WEB server is up! http://localhost:8080

可是訪問根路徑會報錯,由於這個模板並無在根路徑下配置路由。你能夠訪問http://localhost:8080/greet,它會返回一個JSON格式的」Hello Wrold「的信息。

到目前爲止,除了執行了幾個maven命令並啓動應用以外,咱們沒寫過一行代碼,卻已經有了一個搭建好的完整的可運行的應用程序。固然了,將來仍是要和代碼打交道的,不過在這以前咱們先來看下Helidon爲Docker提供了什麼樣的支持。

咱們先經過ctrl+c把程序停掉。在target目錄中,咱們能夠看到運行mvn package命令時額外生成了一些文件。Helidon生成了一個能夠用來構建Docker容器的Dockerfile,以及一個用於部署到Kubernetes的application.yaml。這兩文件雖然簡單,但有了它們能夠很快把應用部署起來。

下面是這個demo工程的Dockerfile(爲了簡潔起見,受權信息就去掉了):

FROM openjdk:8-jre-alpine

RUN mkdir /app
COPY libs /app/libs
COPY helidon-se-demo.jar /app

CMD ["java", "-jar", "/app/helidon-se-demo.jar"]

也許你是第一次接觸Dockerfile,在首行它聲明瞭一個基礎的鏡像。這裏用的是8-jre-alpine的openjdk鏡像,這是基於Alpine Linux的包含了Java 8 JRE的一個很是輕量級的鏡像。兩行以後,Dockerfile建立了一個app目錄來存儲應用程序。接下來這行將libs目錄中的文件拷貝到app/libs下,而後將jar也包複製到app下。最後一行告訴Docker執行java jar命令來啓動應用。

咱們在工程的根目錄下運行下面的命令來測試一下這個Dockerfile:

(注:若是你用的是kubemini,在運行後面的docker build命令前,必定要先執行下:

eval $(minikube docker-env)

不然後面kubernetes會找不到鏡像。)

$ docker build -t helidon-se-demo target

它會告訴Docker使用target目錄下的Dockerfile去建立一個tag爲helidon-se-demo的鏡像。執行完docker builld的輸出結果大概是這樣的:

Sending build context to Docker daemon  5.231MB
Step 1/5 : FROM openjdk:8-jre-alpine
 ---> 0fe3f0d1ee48
Step 2/5 : RUN mkdir /app
 ---> Using cache
---> ab57483b1f76
Step 3/5 : COPY libs /app/libs
 ---> 6ac2b96f4b9b
Step 4/5 : COPY helidon-se-demo.jar /app
 ---> 7d2135433bcc
Step 5/5 : CMD ["java", "-jar", "/app/helidon-se-demo.jar"]
 ---> Running in 5ab71094a72f
Removing intermediate container 5ab71094a72f
 ---> 7e81289d5267
Successfully built 7e81289d5267
Successfully tagged helidon-se-demo:latest

運行下這個命令確認下結果是否ok:

docker images helidon-se-demo

你能夠在目錄下找到一個叫helidon-se-demo的容器文件。我這裏生成的文件大小是88.2MB。經過下面的命令來啓動這個容器:

$ docker run -d -p 8080:8080 helidon-se-demo

docker run命令加上-d開關後會在後臺運行容器實例,-p開關用來指定端口。最後是要運行的鏡像名,這裏是helidon-se-demo。

若是想看下你的系統中有哪些容器在運行,可使用這個命令:
$ docker ps -a
你也可使用像Kitematic或[Portainer這樣的GUI工具。我我的比較喜歡Portainer,如今用它來看下運行狀態,結果如圖一所示。
雲原生微服務框架——Helidon
固然你也能夠訪問http:localhost:8080/greet來確認下應用程序是否還在本地運行着(只不過此次它是運行在Docker裏了)。

在Kubernetes中運行

瞭解完Helidon對Docker的支持度後,咱們再來看看它對Kubernetes支持得怎麼樣。先kill掉Docker容器(命令行或GUI工具均可以)。而後看一下生成的target/app.yaml文件。它的內容以下:

kind: Service
apiVersion: v1
metadata:
  name: helidon-se-demo
  labels:
    app: helidon-se-demo
spec:
  type: NodePort
  selector:
    app: helidon-se-demo
  ports:
  - port: 8080
    targetPort: 8080
    name: http
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: helidon-se-demo
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: helidon-se-demo
        version: v1
    spec:
      containers:
      - name: helidon-se-demo
        image: helidon-se-demo
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
---

這裏我再也不詳細介紹配置細節,你能夠用它來快速地將應用部署到Kubernetes中,Kubernetes則提供了容器管理和編排的能力。經過下述命令將它部署到Kubernetes集羣裏(一樣的,也是工程根目錄下執行,不然的話須要更新下app.yaml的路徑):

$ kubectl create -f target/app.yaml

若是一切正常,應該能看到這樣的結果:

service/helidon-se-demo created
deployment.extensions/helidon-se-demo created

能夠經過kubectl get deployments來確認下部署狀況,kubectl get services能夠用來檢查服務狀態:

NAME              TYPE     CLUSTER-IP     EXTERNAL-IP PORT(S)
helidon-se-demo   NodePort 10.105.215.173 <none>      8080:32700/TCP

能夠看到如今服務運行在32700端口上,你能夠在瀏覽器中訪問該地址確認一下。

目前爲止咱們已經搭建好一個應用、生成Docker容器,而且部署到了Kubernetes中——仍然沒有寫過一行代碼。

那如今咱們就換一換,來看一下代碼。打開
src/main/java/Main.java,看一看startServer()方法中Helidon SE是如何初始化內嵌的Netty服務器的:

protected static WebServer startServer() throws IOException {

    // load logging configuration
    LogManager.getLogManager().readConfiguration(
        Main.class.getResourceAsStream("/logging.properties"));

    // By default this will pick up application.yaml from

  // the classpath
    Config config = Config.create();

    // Get web server config from the "server" section of
    // application.yaml
    ServerConfiguration serverConfig =
        ServerConfiguration.fromConfig(config.get("server"));

    WebServer server =
        WebServer.create(serverConfig, createRouting());

    // Start the server and print some info.
    server.start().thenAccept(ws -> {
        System.out.println(
            "WEB server is up! http://localhost:" + ws.port());
  });

    // Server threads are not demon. NO need to block. Just react.
    server.whenShutdown().thenRun(()
        -> System.out.println("WEB server is DOWN. Goodbye!"));

    return server;
}

代碼中生成的註釋已經解釋的很清楚了,總結一下:

  1. 日誌初始化:從生成的application.yaml中獲取配置信息(額外的配置變量能夠放到這裏)
  2. 經過配置文件中的host/port信息來建立一個ServerConfiguration實例。
  3. 建立並啓動WebServer實例,將createRouting()返回的路由信息傳給它。

createRouting()方法是這樣註冊服務的:

private static Routing createRouting() {
    return Routing.builder()
             .register(JsonSupport.get())
             .register("/greet", new GreetService())
             .build();
}

這裏咱們註冊了"/greet"服務,指向了GreetService。會看到有幾個類變量經過Config從前面提到的application.yaml文件中獲取配置值。

private static final Config CONFIG =
    Config.create().get("app");
private static String greeting =
    CONFIG.get("greeting").asString("Ciao");

GreetService類實現了Service接口並重寫了update()方法,裏面定義了子路徑/greet的實現。
@Override

public final void update(final Routing.Rules rules) {
    rules
        .get("/", this::getDefaultMessage)
        .get("/{name}", this::getMessage)
        .put("/greeting/{greeting}", this::updateGreeting);
}

update()方法接收Routing.Rules的實例對象,Routing.Rules的方法分別對應着不一樣的HTTP請求——get(),post(),put(),head(),options()和trace()——還有一些比較有用的方法好比any(),它能夠用來兜底,實現一些日誌或安全類的功能。

這裏我註冊了三個endpoint:/greet/, /greet/{name}和/greet/greeting。每一個endpoint都有一個指向服務方法的引用。註冊成endpoint的方法接收兩個參數:request和response。這樣設計的話,你能夠從request中獲取參數,好比請求頭及參數,也能夠往response中設置響應頭及響應體。getDefaultMessage()方法的內容以下:

private void getDefaultMessage(final ServerRequest request,
                               final ServerResponse response) {
    String msg = String.format("%s %s!", greeting, "World");
    JsonObject returnObject = Json.createObjectBuilder()
            .add("message", msg)
            .build();
    response.send(returnObject);
}

這是個很是簡單的例子,可是也能看出服務方法的基本實現結構。getMessage()方法是一個動態路徑參數({name}參數是在URL路徑中註冊進來的)的例子,你能夠從URL中獲取參數。

private void getMessage(final ServerRequest request,
                        final ServerResponse response) {
    String name = request.path().param("name");
    String msg = String.format("%s %s!", greeting, name);
    JsonObject returnObject = Json.createObjectBuilder()
      .add("message", msg)
            .build();
    response.send(returnObject);
}

http://localhost:8080/greet/todd的結果如圖二所示

雲原生微服務框架——Helidon
下面要講的updateGreeting()方法和getMessage()有很大的不一樣,須要注意的是這裏只能調用Put方法而不是get,由於在update()裏就是這樣註冊的。

private void updateGreeting(final ServerRequest request, final ServerResponse response)
{
    greeting = request.path().param("greeting");
    JsonObject returnObject = Json.createObjectBuilder()
            .add("greeting", greeting)
            .build();
    response.send(returnObject);
}

Helidon SE還包含不少東西,包括異常處理、靜態內容、metrics以及健康度。強烈推薦閱讀下項目文檔(https://helidon.io/docs/latest/#/about/01_introduction)來了解更多特性

Helidon MP入門

Helidon MP是MicroProfile規範的實現版本。若是你使用過Java EE的話應該不會以爲陌生。前面也提到,你可能會看到像JAX-RS/Jersey, JSON-P以及CDI這些經常使用的東西。

和Helidon SE同樣,咱們先經過Helidon MP的項目模板來快速建立一個工程:

$ mvn archetype:generate -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-quickstart-mp \
    -DarchetypeVersion=0.10.2 \
    -DgroupId=codes.recursive \
    -DartifactId=helidon-mp-demo \
    -Dpackage=codes.recursive.helidon.mp.demo

看一下Main.java類,你會發現它比Helidon SE還要簡單。

protected static Server startServer() throws IOException {
    // load logging configuration
    LogManager.getLogManager().readConfiguration(
        Main.class.getResourceAsStream("/logging.properties"));
    // Server will automatically pick up configuration from
    // microprofile-config.properties
    Server server = Server.create();
    server.start();

    return server;
}

應用的定義在GreetApplication類中,它的getClasses()方法中註冊了路由資源。

@ApplicationScoped
@ApplicationPath("/")
public class GreetApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> set = new HashSet<>();
        set.add(GreetResource.class);
        return Collections.unmodifiableSet(set);
  } 

}

Helidon MP中的GreetResource和Helidon SE中的GreetService的角色差很少。不過它不用單獨去註冊路由信息,你可使用註解來表示endpoint、HTTP方法和content-type頭。

@Path("/greet")
@RequestScoped
public class GreetResource {

    private static String greeting = null;

    @Inject
    public GreetResource(@ConfigProperty(name = "app.greeting")
      final String greetingConfig) {

      if (this.greeting == null) {
          this.greeting = greetingConfig;
    } 
  }

  @Path("/")
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public JsonObject getDefaultMessage() {
      String msg = String.format("%s %s!", greeting, "World");

      JsonObject returnObject = Json.createObjectBuilder()
            .add("message", msg)
            .build();
      return returnObject;
  }

  @Path("/{name}")
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public JsonObject getMessage(@PathParam("name") final String name){
      String msg = String.format("%s %s!", greeting, name);

      JsonObject returnObject = Json.createObjectBuilder()
            .add("message", msg)
            .build();
      return returnObject;
  }

  @Path("/greeting/{greeting}")
  @PUT
  @Produces(MediaType.APPLICATION_JSON)
    public JsonObject updateGreeting(@PathParam("greeting")
                                     final String newGreeting) {
        this.greeting = newGreeting;

        JsonObject returnObject = Json.createObjectBuilder()
                .add("greeting", this.greeting)
                .build();
        return returnObject;
  } 
}

結論

Helidon MP和Helidon SE的區別還不止這些,但它們的目標都是一致的,即下降微服務的使用門檻。Helidon是一個功能很是強大的框架,可以幫助你快速開發微服務應用。若是你不但願使用容器技術,你也能夠像部署傳統jar同樣去部署它。若是你的團隊使用容器技術,它內建的支持可以幫忙你快速地部署到任何雲上或自有的Kubernetes集羣中。因爲Helidon是Oracle公司開發的,所以團隊後續會計劃將它集成到Oracle Cloud上。若是你已經在使用Oracle Cloud部署應用,或者最近有計劃要遷移到上面,那麼Helidon將是你的不二選擇。

相關文章
相關標籤/搜索