在dropwizard中使用feign,使用hystrix

前言

用惯了spring全家桶之后,试试dropwizard的Hello World也别有一帆风味。为了增强对外访问API的能力,需要引入open feign。这里简单在dropwizard中使用feign。

1. 什么Dropwizard

Dropwizard is a Java framework for developing ops-friendly, high-performance, RESTful web services.

Dropwizard使成熟、稳定的java生态系统更加简单、轻量(light-weight), 让你更专注于业务逻辑。

Dropwizard 为配置(configuration)、统计(application metrics)、日志(logging)、operational tools提供了开箱即用的能力。让您和您的团队能够在最短的时间内开发出具有生产环境的质量的Web服务。

下面的简介来自REST微服务架构之Dropwizard

DropWizard是由Yammer开发团队贡献的一个后台服务开发框架,其集成了Java生态系统中各个问题域中最优秀的组件,帮助开发者快速的打造一个Rest风格的后台服务。

对开发者来说,使用DropWizard有如下好处: 1、和Maven集成良好,也就是说和Gradle集成也很良好; 2、开发迅速,部署简单; 3、代码结构好,可读性高; 4、自动为服务提供OM框架; 5、让开发者自然的把一个应用拆分为一个个的小服务

DropWizard结构的Web服务组成 1、Configuration:用于设置该服务的配置,比方说在服务开放在哪个端口,数据库配置是怎样的等等。 2、Application(即Service):该服务的主入口,定义该服务使用哪个配置文件,开放哪些Resource,该服务需要哪些HealthCheck等等。 3、Resource:定义一个资源,包括如何获取该资源,对该资源做Get/Post/Delete/Query时,对应的各种业务逻辑。 4、Representation:定义了一个服务返回值对象,当服务返回该对象时,会自动的把该对象按属性值生成一个Json格式的字符串返回给服务调用者。 5、HealthCheck:在DropWizard为每个服务提供的OM框架中用到,通过它可以随时检测当前服务是否可用。

Dropwizard内置了Jetty

Web应用程序不能没有HTTP,所以Dropwizard使用Jetty HTTP库将一个令人难以置信的HTTP服务器直接嵌入到您的项目中。 Dropwizard项目不需要将应用程序交给一个复杂的应用程序服务器,而是一个main方法,它会自动连接一个HTTP服务器。将应用程序作为一个简单的过程运行,消除了Java在生产中的一些不好的东西(没有PermGen问题,没有应用程序服务器配置和维护,没有复杂的部署工具,没有类加载器(class loader)故障,没有隐藏的应用程序日志,没有尝试调整一个垃圾收集器来处理多个应用程序工作负载),并允许您使用所有现有的Unix进程管理工具。

Dropwizard 使用Jersey提供Rest能力

Dropwizard 使用Jackson来处理json

Dropwizard 提供了Metrics类库

2. Hello World For Dropwizard

吹完牛逼,开始干活。 照例,首先本次测试(https://github.com/Ryan-Miao/l4dropwizard)的完整结构图如下

.
├── dependency-reduced-pom.xml
├── l4dropwizard.iml
├── pom.xml
├── readme.md
└── src
    └── main
        ├── java
        │   └── com
        │       └── test
        │           ├── HelloWorldApplication.java
        │           ├── configuration
        │           │   ├── HelloWorldConfiguration.java
        │           │   └── modules
        │           │       ├── ConnectAndReadConfig.java
        │           │       └── GithubApiConfig.java
        │           └── domain
        │               ├── connect
        │               │   ├── GithubClient.java
        │               │   └── GithubConnector.java
        │               ├── entiry
        │               │   ├── GithubUser.java
        │               │   └── Saying.java
        │               ├── health
        │               │   └── TemplateHealthCheck.java
        │               └── resource
        │                   ├── GithubResource.java
        │                   └── HelloWorldResource.java
        └── resources
            └── config
                └── dev.yml

14 directories, 16 files

2.1 添加依赖

依旧是maven项目,pom中添加dropwizard

<properties>
        <dropwizard.version>1.0.6</dropwizard.version>
        <java.version>1.8</java.version>
        <mainClass>com.test.HelloWorldApplication</mainClass>
    
</properties>
<dependencies>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-core</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
</dependencies>

2.2 添加配置中心

dropwizard采用yaml作为配置文件,同时需要有个配置类对应yaml中的属性。 创建config/dev.yml

template: Hello, %s!
defaultName: Stranger


server:
#  softNofileLimit: 1000
#  hardNofileLimit: 1000
  applicationConnectors:
    - type: http
      port: 8080

    #this requires the alpn-boot library on the JVM's boot classpath
    #- type: h2
    #  port: 8445
    #  keyStorePath: example.keystore
    #  keyStorePassword: example
  adminConnectors:
    - type: http
      port: 8082

然后,新建对应的配置类com.test.configuration.HelloWorldConfiguration

package com.test.configuration;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import org.hibernate.validator.constraints.NotEmpty;

/**
 * Created by rmiao on 3/14/2017.
 */
public class HelloWorldConfiguration extends Configuration {

    @NotEmpty
    private String template;

    @NotEmpty
    private String defaultName = "Stranger";

    @JsonProperty
    public String getTemplate() {
        return template;
    }

    @JsonProperty
    public void setTemplate(String template) {
        this.template = template;
    }

    @JsonProperty
    public String getDefaultName() {
        return defaultName;
    }

    @JsonProperty
    public void setDefaultName(String name) {
        this.defaultName = name;
    }
}

下一步就是启动类:com.test.application.HelloWorldApplication

package com.test;

import com.test.domain.health.TemplateHealthCheck;
import com.test.domain.resource.HelloWorldResource;
import com.test.configuration.HelloWorldConfiguration;
import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

import java.util.Map;

/**
 * Created by Ryan Miao on 3/14/2017.
 */
public class HelloWorldApplication extends Application<HelloWorldConfiguration> {

    public static void main(String[] args) throws Exception {
        new HelloWorldApplication().run(args);
    }

    @Override
    public String getName() {
        return "hello-world";
    }

    @Override
    public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
        // nothing to do yet
    }

    @Override
    public void run(HelloWorldConfiguration configuration, Environment environment) throws Exception {
        final HelloWorldResource resource = new HelloWorldResource(
                configuration.getTemplate(),
                configuration.getDefaultName()
        );
        final TemplateHealthCheck healthCheck =
                new TemplateHealthCheck(configuration.getTemplate());
        environment.healthChecks().register("template", healthCheck);
        environment.jersey().register(resource);
        environment.jersey().register(healthCheck);

    }
}

到此,配置基本完成,只需要添加接口resource就好。

2.3 创建第一个API

对应于springmvc中conroller, dropwizard采用jersey,使用resourc作为接口类:com.test.com.test.resource.HelloWorldResource

package com.test.domain.resource;

import com.codahale.metrics.annotation.Timed;
import com.test.domain.entiry.Saying;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Created by rmiao on 3/14/2017.
 */
@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public class HelloWorldResource {
    private final String template;
    private final String defaultName;
    private final AtomicLong counter;

    public HelloWorldResource(String template, String defaultName) {
        this.template = template;
        this.defaultName = defaultName;
        this.counter = new AtomicLong();
    }

    @GET
    @Timed
    public Saying sayHello(@QueryParam("name") Optional<String> name) {
        final String value = String.format(template, name.orElse(defaultName));
        return new Saying(counter.incrementAndGet(), value);
    }


}

这里的template没啥意思,官网用在这里就是为了彰显下读取配置文件的能力: 通过configuration类来操作配置属性。

另外,需要注意的是,resource并不能像Spring一样自动扫描,需要手动去environment.jersey().register(resource);

2.4 启动

启动前还需要配置fat jar,同Spring-boot一样,fat jar首选. 在pom配置:

<build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.3</version>
                <configuration>
                    <createDependencyReducedPom>true</createDependencyReducedPom>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <exclude>META-INF/*.SF</exclude>
                                <exclude>META-INF/*.DSA</exclude>
                                <exclude>META-INF/*.RSA</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>${mainClass}</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <configuration>
                    <mainClass>${mainClass}</mainClass>
                    <arguments>
                        <argument>server</argument>
                        <argument>target/classes/config/dev.yml</argument>
                    </arguments>
                    <systemProperties>
                        <systemProperty>
                            <key>application.name</key>
                            <value>HelloWorld</value>
                        </systemProperty>
                        <systemProperty>
                            <key>application.home</key>
                            <value>.</value>
                        </systemProperty>
                        <systemProperty>
                            <key>application.environment</key>
                            <value>dev</value>
                        </systemProperty>
                    </systemProperties>
                </configuration>
            </plugin>
        </plugins>
    </build>

接下来,打包:

mvn package 

然后,run jar:

java -jar target\l4dropwizard-1.0-SNAPSHOT.jar server target/classes/config/dev.yml

浏览器访问http://localhost:8080/hello-world?name=Ryan 将得到:

{
    "id": 1,
    "content": "Hello, Ryan!"
}

至此,hello world完成。

什么是Feign

Feign是一个网络请求客户端,简化了网络请求代码,使得我们可以采用更加友好的方式发送请求,并且管理请求。Feign采用注解驱动模板,所以目前只支持text-based apis.

Dropwizard with Feign

依赖

首先,添加依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <version>${feign.version}</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hystrix</artifactId>
    <version>${feign.version}</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-slf4j</artifactId>
    <version>${feign.version}</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-jackson</artifactId>
    <version>${feign.version}</version>
</dependency>
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-gson</artifactId>
    <version>${feign.version}</version>
</dependency>
<dependency>
    <groupId>io.reactivex</groupId>
    <artifactId>rxjava</artifactId>
    <version>1.2.1</version>
    <scope>compile</scope>
</dependency>

配置

Feign的配置主要有三个,一个是isolation.thread线程存活时间。一个是connectTimeoutMillis连接超时,一个是readTimeoutMillis

本次测试将采用github的公共API,获取用户信息。首先配置线程存活时间。在dev.yml中添加:

hystrixConfig:
  hystrix.command.GithubConnector#getUserProfile(String).execution.isolation.thread.timeoutInMilliseconds: 7000

然后是两个超时配置:

githubApiConfig:
  baseUrl: "https://api.github.com"
  getUserProfile:
    connectTimeoutMillis: 2000
    readTimeoutMillis: 5000

Dropwizard通过配置类和配置文件绑定的方式获取配置内容。因此,需要对应的在配置类中创建对应的字段。 com.test.configuration.modules.ConnectAndReadConfig

package com.test.configuration.modules;

/**
 * Created by Ryan Miao on 9/14/17.
 */
public class ConnectAndReadConfig {
    private int connectTimeoutMillis;
    private int readTimeoutMillis;

    public int getConnectTimeoutMillis() {
        return connectTimeoutMillis;
    }

    public int getReadTimeoutMillis() {
        return readTimeoutMillis;
    }
}

com.test.configuration.modules.GithubApiConfig

package com.test.configuration.modules;

/**
 * Created by Ryan Miao on 9/14/17.
 */
public class GithubApiConfig {
    private String baseUrl;
    private ConnectAndReadConfig getUserProfile;

    public String getBaseUrl() {
        return baseUrl;
    }

    public ConnectAndReadConfig getGetUserProfile() {
        return getUserProfile;
    }
}

在com.test.configuration.HelloWorldConfiguration中添加:

@NotEmpty
private Map<String, Object> hystrixConfig;

@NotNull
private GithubApiConfig githubApiConfig;

然后在application中配置好hystrix的配置: 在HelloWorldApplication#run方法中

//init hystrix config
Map<String, Object> hystrixConfig = configuration.getHystrixConfig();
for (final Map.Entry<String, Object> config : hystrixConfig.entrySet()) {
    ConfigurationManager.getConfigInstance().setProperty(config.getKey(), config.getValue());
}

创建Feign的connector接口

创建接口com.test.domain.connect.GithubConnector:

package com.test.domain.connect;

import com.test.domain.entiry.GithubUser;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import rx.Observable;

/**
 * Created by ryan on 9/14/17.
 */
public interface GithubConnector {
    /**
     * @param username
     * @return
     */
    @RequestLine("GET /users/{username}")
    @Headers({"Accept: application/vnd.github.v3+json"})
    Observable<GithubUser> getUserProfile(@Param("username") String username);
}

创建调用客户端

创建客户端com.test.domain.connect.GithubClient

package com.test.domain.connect;

import com.test.configuration.modules.ConnectAndReadConfig;
import com.test.configuration.modules.GithubApiConfig;
import com.test.domain.entiry.GithubUser;
import feign.Request;
import feign.Response;
import feign.gson.GsonDecoder;
import feign.gson.GsonEncoder;
import feign.hystrix.HystrixFeign;
import feign.slf4j.Slf4jLogger;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;

import java.io.IOException;
import java.util.UUID;

/**
 * Created by Ryan Miao on 9/14/17.
 */
public class GithubClient {
    public static final Logger LOGGER = LoggerFactory.getLogger(GithubClient.class);

    private GithubApiConfig githubApiConfig;

    public GithubClient(GithubApiConfig githubApiConfig) {
        this.githubApiConfig = githubApiConfig;
    }


    public Observable<GithubUser> getUserProfile(String username) {
        String baseUrl = githubApiConfig.getBaseUrl();
        ConnectAndReadConfig getUserProfile = githubApiConfig.getGetUserProfile();
        GithubConnector connector = HystrixFeign.builder()
                .decoder(new GsonDecoder())
                .encoder(new GsonEncoder())
                .logger(new Slf4jLogger())
                .options(new Request.Options(getUserProfile.getConnectTimeoutMillis(), getUserProfile.getReadTimeoutMillis()))
                .errorDecoder((methodKey, response) -> {
                    StringBuilder msg = new StringBuilder("status=").append(response.status())
                            .append(";request_headers=").append(response.request().headers())
                            .append(";response_headers=").append(response.headers())
                            .append(";body=");
                    Response.Body body = response.body();
                    if (body != null && body.length() > 0) {
                        try {
                            msg.append(IOUtils.toString(body.asReader()));
                        } catch (IOException e) {
                            msg.append("can not read body,"+e.getMessage());
                        }
                    }

                    return new RuntimeException(msg.toString());
                })
                .requestInterceptor(template -> template.header("requestId", UUID.randomUUID().toString()))
                .target(GithubConnector.class, baseUrl);

        return connector.getUserProfile(username).onErrorReturn(error -> {
            LOGGER.error("Get github user profile failed. ", error);
            return null;
        });
    }
}

创建一个接口测试

最后,创建一个接口来测试下: com.test.domain.resource.GithubResource

package com.test.domain.resource;

import com.codahale.metrics.annotation.Timed;
import com.test.configuration.modules.GithubApiConfig;
import com.test.domain.connect.GithubClient;
import com.test.domain.entiry.GithubUser;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

/**
 * Created by Ryan Miao on 9/14/17.
 */
@Path("/github")
@Produces(MediaType.APPLICATION_JSON)
public class GithubResource {

    private GithubApiConfig githubApiConfig;

    public GithubResource(GithubApiConfig githubApiConfig) {
        this.githubApiConfig = githubApiConfig;
    }

    @GET
    @Timed
    @Path("/users/{username}")
    public GithubUser getUserProfile(@PathParam("username") final String username){
        GithubClient client = new GithubClient(githubApiConfig);
        return client.getUserProfile(username).toBlocking().first();
    }

}

run main方法启动。访问localhost:8080/github/users/Ryan-Miao就可以得到我的github信息了:

{
    "login": "Ryan-Miao",
    "id": 11866078,
    "avatar_url": "https://avatars3.githubusercontent.com/u/11866078?v=4",
    "url": "https://api.github.com/users/Ryan-Miao",
    "name": "Ryan Miao",
    "email": null,
    "location": "中国深圳",
    "blog": "https://ryan-miao.github.io/"
}

至此,feign的简单集成就搞定了。

一些注意事项

feign采用hystrix的配置的时候,grop key是baseUrl.上栗中,grop Key为https://api.github.com, commandKey为接口+方法和参数,上栗中为GithubConnector#getUserProfile(String)。因此,配置线程超时用了commandKey如上。如果要配置coreSize之类的,必须使用url做为group key了。

source

https://github.com/Ryan-Miao/l4dropwizard

参考

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CSDN技术头条

资源控制在大数据和云计算平台中的应用

本文针对大数据平台中资源控制这个层面来详细介绍资源控制在不同操作系统上的具体技术实现,以及大数据平台和资源控制的集成。

5508
来自专栏Golang语言社区

Go语言实现的WebSocket

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知,我们会立即删除并表示歉意。谢谢。

1272
来自专栏SDNLAB

数据中心网络虚拟化 隧道技术

如何实现不同租户和应用间的地址空间和数据流量的隔离是实现数据中心网络虚拟化首先需要解决的几个问题之一。所谓地址空间的隔离是指不同租户和应用之间的网络(ip)地址...

3645
来自专栏PHP技术

TCP/IP、HTTP协议概述

术语TCP/IP代表传输控制协议/网际协议,指的是一系列协议。“IP”代表网际协议,TCP和UDP使用该协议从一个网络传送数据包到另 一个网络。把IP想像成一种...

2935
来自专栏恰同学骚年

.NET Core微服务之基于Ocelot实现API网关服务(续)

  为了验证负载均衡,这里我们配置了两个Consul Client节点,其中ClientService分别部署于这两个节点内(192.168.80.70与192...

1323
来自专栏编程坑太多

『高级篇』docker之开发课程EdgeService(16)

PS:微服务跟之前说的一样就是互相通过RPC的方式进行通信,之间有自己的数据库,只是RPC暴露接口的方式来获取其他的微服务之间的数据。

917
来自专栏pangguoming

微信公众号网页授权获取用户openid

最近一个项目是在微信公众号内二次开发,涉及到微信公众号支付,根据文档要求想要支付就必须要获取到用户的openid。

1721
来自专栏谢强能的专栏

LVS 生产环境架构详解

基于商业负载均衡解决方案的(F5/A10)价格昂贵且版本更新较慢、灵活性差,较难适应快速变化的需求,使用开源产品(LVS+NGINX)构建高性价比、稳定、高效、...

2.2K0
来自专栏青玉伏案

iOS开发之调用系统打电话发短信接口以及程序内发短信

  在本篇博客开头呢,先说一下写本篇的博客的原因吧。目前在做一个小项目,要用到在本应用程序内发验证码给其他用户,怎么在应用内发送短信的具体细节想不大起来了,于是...

2155
来自专栏移动端开发

Socket学习总结系列(一) -- IM & Socket

Socket通讯在iOS中也是很常见,自己最近也一直在学习Telegram这个开源项目,Telegram就是在Socket的基础上做的即时通讯,这个相信了解这...

2885

扫码关注云+社区