[JAVA] Try deploying Zipkin in your Jersey (JAX-RS2) application

What you want to do

Trace the JAX-RS2 Java application created in Jersey with Zipkin.

jersey-zipkin.png

Set up a Zipkin server

Install docker.

Linux installs docker-compose additionally. Mac and Windows are not required because they are included in the Docker installer.

sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Clone docker-zipkin from github and run Zipkin server with docker

git clone https://github.com/openzipkin/docker-zipkin.git
cd docker-zipkin
docker-compose up -d

You can access the Zipkin web UI at the following URL

Implement the application

The application is made with Jersey as the title suggests. I uploaded the completed one to github.

Dependent library (pom.xml)

It utilizes Jersey 2, a server-side JAX-RS2 reference implementation.

Note that groupId progressivecom.sun.jersey``, which often appears when googled with Jersey, is not compatible with Jersey 1 series.

<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-server -->
<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-server</artifactId>
  <version>2.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.inject/jersey-hk2 -->
<dependency>
  <groupId>org.glassfish.jersey.inject</groupId>
  <artifactId>jersey-hk2</artifactId>
  <version>2.26</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-servlet -->
<dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-servlet</artifactId>
  <version>2.26</version>
</dependency>

The client-side JAX-RS2 also makes use of the reference implementation Jersey 2.

<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.core/jersey-client -->
<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-client</artifactId>
  <version>2.26</version>
</dependency>

Web Application written in Java usually runs on Tomcat, Glassfish, Jetty, etc., but this time, for the sake of simplicity, use jersey-container-jdk-http to implement the Http server. Embed directly in the application.

<!-- https://mvnrepository.com/artifact/org.glassfish.jersey.containers/jersey-container-jdk-http -->
<dependency>
  <groupId>org.glassfish.jersey.containers</groupId>
  <artifactId>jersey-container-jdk-http</artifactId>
  <version>2.26</version>
</dependency>

A Zipkin Instrumentation library for JAX-RS2 Java applications that allows you to trace on a JAX-RS server / client.

<!-- https://mvnrepository.com/artifact/io.zipkin.brave/brave-instrumentation-jaxrs2 -->
<dependency>
  <groupId>io.zipkin.brave</groupId>
  <artifactId>brave-instrumentation-jaxrs2</artifactId>
  <version>4.3.1</version>
</dependency>

The collected tracing data must be sent to the Zipkin server. This library will send it to any destination.

<!-- https://mvnrepository.com/artifact/io.zipkin.reporter/zipkin-sender-urlconnection -->
<dependency>
  <groupId>io.zipkin.reporter</groupId>
  <artifactId>zipkin-sender-urlconnection</artifactId>
  <version>1.1.2</version>
</dependency>

Java code

JAX-RS2 allows you to plug in external libraries that implement the javax.ws.rs.core.Feature IF.

Brave, a Zipkin library for Java applications, supports various Java frameworks. This time, we will use brave-instrumentation-jaxrs2 for JAX-RS2.

package zipkin;

import brave.Tracing;
import brave.jaxrs2.TracingFeature;
import brave.sampler.Sampler;
import zipkin.reporter.AsyncReporter;
import zipkin.reporter.urlconnection.URLConnectionSender;
import javax.ws.rs.core.Feature;

public class ZipkinFeature {

    /**
     * @param localServiceName Service name displayed in Zipkin Web UI
     */
    public static Feature create(String localServiceName) {
        // create a zipkin reporter.
        AsyncReporter<Span> asyncReporter = AsyncReporter
                .builder(URLConnectionSender.create("http://localhost:9411/api/v1/spans"))
                .build();

        // create a zipkin tracing.
        Tracing tracing = Tracing.newBuilder()
                .reporter(asyncReporter)
                .localServiceName(localServiceName)
                .sampler(Sampler.ALWAYS_SAMPLE)
                .build();

        // create a JAX-RS feature.
        Feature tracingFeature = TracingFeature.create(tracing);

        return tracingFeature;
    }
}

Create a class that implements the server-side JAX-RS2 entry point javax.ws.rs.core.Application IF. Here, the tracing function brave.jaxrs2.TracingFeature generated earlier Register with server-side JAX-RS2.

package app;

import zipkin.ZipkinFeature;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Feature;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

@ApplicationPath("")
public class MyApplication extends Application {

    @Override
    public Set<Object> getSingletons() {
        // create a JAX-RS feature.
        Feature tracingFeature = ZipkinFeature.create("server");

        return new LinkedHashSet<>(Arrays.asList(tracingFeature));
    }
}

Create a resource class (API definition) for server-side JAX-RS2. Originally, I would like to deploy multiple services and check the operation of Zipkin, but for the sake of simplicity, define 3 APIs and client JAX. -Called in order by RS2. Here, too, the tracing function brave.jaxrs2.TracingFeature generated earlier is registered in the client JAX-RS2.

package resource;

import zipkin.ZipkinFeature;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("")
public class MyResource {

    /**
     * /front --> /mid --> /back
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("front")
    public Response front() {
        // create a JAX-RS feature.
        Feature tracingFeature = ZipkinFeature.create("client");

        // http client: GET /mid
        Response r = ClientBuilder.newClient()
                .register(tracingFeature)
                .target("http://localhost:8080/myapp/mid")
                .request()
                .get();

        return Response.fromResponse(r).build();
    }

    /**
     * /mid --> /back
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("mid")
    public Response mid() {
        // create a JAX-RS feature.
        Feature tracingFeature = ZipkinFeature.create("client");

        // http client: GET /back
        Response r = ClientBuilder.newClient()
                .register(tracingFeature)
                .target("http://localhost:8080/myapp/back")
                .request()
                .get();

        return Response.fromResponse(r).build();
    }

    /**
     * /back
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    @Path("back")
    public String back() {
        return "hello";
    }
}

We use jersey-container-jdk-http for the Http server implementation because we want it to work easily.

package main;

import app.MyApplication;
import com.sun.net.httpserver.HttpServer;
import org.glassfish.jersey.jdkhttp.JdkHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.net.URI;

public class Main {

    public static void main(String[] args) {
        // register JAX-RS Application class.
        ResourceConfig rc = ResourceConfig.forApplicationClass(MyApplication.class);

        // register a path of REST resources.
        rc.packages(true, "resource");

        // run a jersey server.
        URI uri = URI.create("http://localhost:8080/myapp/");
        HttpServer httpServer = JdkHttpServerFactory.createHttpServer(uri, rc);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> httpServer.stop(0)));
    }
}

Run the application. You can access it at the following URL.

Visit the Zipkin Web UI. You should be able to see the tracing data you just accessed.

zipkin.png

Recommended Posts

Try deploying Zipkin in your Jersey (JAX-RS2) application
Try deploying Rails application to EC2-Part 2 (Server construction)-
JAX-RS (Jersey) Tips