在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
参考
在dropwizard中使用feign,使用hystrix的更多相关文章
- Feign Ribbon Hystrix 三者关系 | 史上最全, 深度解析
史上最全: Feign Ribbon Hystrix 三者关系 | 深度解析 疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之 -25[ 博客园 总入口 ] 前言 疯狂创客圈(笔者尼恩创建的 ...
- SpringCloud(五)之Spring Cloud 中 Feign结合Hystrix断路器开发实战
1.先讲hystrx(断路器) 在springcloub 中的使用 1.1 加入依赖 注意:网上新旧版本问题,所以要以官网为主,不然部分注解会丢失最新版本 2.0 <dependency> ...
- Spring Cloud中,如何解决Feign整合Hystrix第一次请求失败的问题
Spring Cloud中,Feign和Ribbon在整合了Hystrix后,可能会出现首次调用失败的问题,要如何解决该问题呢? 造成该问题的原因 Hystrix默认的超时时间是1秒,如果超过这个时间 ...
- SpringCloud Feign对Hystrix(断路由)的支持
第一步:首先开启Feign对Hystrix的支持,在properties文件中添加以下配置: feign.hystrix.enabled=true. 第二步:在上一篇Feign的基础上添加Hystri ...
- Spring Cloud中关于Feign的常见问题总结
一.FeignClient接口,不能使用@GettingMapping 之类的组合注解 代码示例: @FeignClient("microservice-provider-user" ...
- Feign 与 Hystrix
Feign 与 Hystrix Feign是一个声明式的web服务客户端,它使得web服务调用非常的简单,当我们使用Feign时,Spring Cloud 整合了Ribbon和Eureka,从而为我们 ...
- Feign使用Hystrix
Feign使用Hystrix开发步骤 1.导入依赖spring-cloud-starter-hystrix 2.消费启动类开启@EnableCircuitBreaker 3.配置yml文件feign. ...
- Spring Cloud(Dalston.SR5)--Feign 与 Hystrix 断路器整合
创建项目 要使 Feign 与 Hystrix 进行整合,我们需要增加 Feign 和 Hystrix 的依赖,修改 POM.xml 中增加以下依赖项如下: <?xmlversion=" ...
- feign的hystrix不起作用.
在springCloud中使用feign内嵌的断路器hystrix时.feign中的hystrix不起作用.这可能是由于springCloud的版本原因造成的.需要在application.prope ...
随机推荐
- WebApi 的CRUD 的方法的应用
一.最近一直在忙于开发公司的新的项目和搭建公司的框架,也好久没有写博客了.对于RaidDevelopmentFramework 我有着自己的见解在应用到实际的框架中确实挺好用的,但是还是存在一部分的问 ...
- Android自定义控件系列之应用篇——圆形进度条
一.概述 在上一篇博文中,我们给大家介绍了Android自定义控件系列的基础篇.链接:http://www.cnblogs.com/jerehedu/p/4360066.html 这一篇博文中,我们将 ...
- [UWP]了解模板化控件(5.1):TemplatePart vs. VisualState
1. TemplatePart vs. VisualState 在前面两篇文章中分别使用了TemplatePart及VisualState的方式实现了相同的功能,其中明显VisualState的方式更 ...
- 这是一个简单的前台短信验证码功能 ajax实现异步处理 (发送和校验)
<script type="text/javascript"> var InterValObj; //timer变量,控制时间 var count = 60; //间隔 ...
- django服务器正常打开,本地localhost能连上,其他计算机却连不上
最近在学习Django,在搭建好django并且启动服务器后,遇到了麻烦. 问题现象: django服务器正常打开,本地localhost能连上,其他计算机却连不上 问题原因(可能存在原因): 这里的 ...
- JavaScript 日期处理类库
Moment.js http://momentjs.cn/
- Nginx入门案例(Mac版)
Nginx(engine x)是一个轻量级.高性能的Web和反向代理服务器,也是一个IMAP.POP3和SMTP服务器,并且安装十分简单,配置文件非常简洁,启动特别容易,运行十分稳定,几乎可以做到7* ...
- form enctype参数
application/x-www-form-urlencoded 表示在发送前编码所有字符(默认) multipart/form-data 不对字符编码.在使用包含文件上传控件的表单时,必须使用该值 ...
- 每次打开VS都报错:我们无法自动填充你的 Visual Studio Team Services 帐户
我们无法自动填充你的 Visual Studio Team Services 帐户.遇到下面的错误: TF400813: Resource not available for anonymous ac ...
- Catalan数——卡特兰数
一.Catalan数的定义 令h(0)=1,h(1)=1,Catalan数满足递归式:h(n) = h(0)*h(n-1) + h(1)*h(n-2) + ... + h(n-1)*h(0) (n& ...