环境:
springBoot 1.3.6.RELEASE thrift 0.9.3 ubuntu IntelliJ IDEA 14.1.4
生成springBoot项目框架
本文采用了idea 自带的Spring Initializr作为生成项目框架的向导, 依赖管理采用Gradle
如果你的idea 新建项目中没有这个选项,那么需要添加这个 Spring Boot 这个idea插件
- 打开idea设置选项框,搜索中搜索 Spring Boot
- 在插件(Plugins)中,把 Spring Boot这个插件勾上
添加thrift依赖及gradle build插件
完整的build.gradle文件如下:
buildscript {
ext {
springBootVersion = '1.3.6.RELEASE'
}
repositories {
jcenter()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'spring-boot'
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "gradle.plugin.org.jruyi.gradle:thrift-gradle-plugin:0.3.1"
}
}
apply plugin: "org.jruyi.thrift"
compileThrift {
generator 'rb'
outputDir = file('src/main/thrift/generated')
sourceDir = file('src/main/thrift/src')
}
task copyTask(type: Copy) {
from 'src/main/thrift/generated/gen-java'
into 'src/main/java/com/albert/thrift'
}
jar {
baseName = 'thriftSpringBoot'
version = 'v0.0.1'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
jcenter()
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile("org.apache.thrift:libthrift:0.9.3")
runtime('mysql:mysql-connector-java')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
其中:
apply plugin: "org.jruyi.thrift"
插件是方便把*.thrift编译成实际使用的各个开发语言的接口文件,你也可以采用thrift的命令行生成compileThrift
就是该插件提供的gradle task, 重写这个task是为了配置thrift编译的参数。generator
是设置生成什么语言的接口,此文件中添加了ruby语言的接口, JAVA语言的接口是自动生成的,因为我们添加了apply plugin: 'java'
这个task。outputDir
和sourceDir
分别代表了thrift文件的源文件地址和编译后的目标文件地址compile("org.apache.thrift:libthrift:0.9.3")
用于添加thrift依赖task copyTask(type: Copy)
是用来把生成java接口文件复制到源代码目录的一个task,当然,你也可以手动把生成的java接口文件复制到制定的文件夹(此项目中为:src/main/com/albert/thrift)。文件复制完毕后,额外需要手动操作的就是给这些个接口文件添加 package 信息,不然无法在其他的源文件中import引用。
实现thrift的接口
源文件如下:
package com.albert.thriftHandler;
import com.albert.service.CalculatorService;
import com.albert.thrift.TCalculatorService;
import com.albert.thrift.TDivisionByZeroException;
import com.albert.thrift.TOperation;
import org.apache.thrift.TException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Created by albert on 16-7-26.
*/
@Component
public class CalculatorServiceHandler implements TCalculatorService.Iface {
@Autowired
CalculatorService calculatorService;
@Override
public int calculate(int num1, int num2, TOperation op) throws TException {
switch (op) {
case ADD:
return calculatorService.add(num1, num2);
case SUBTRACT:
return calculatorService.subtract(num1, num2);
case MULTIPLY:
return calculatorService.multiply(num1, num2);
case DIVIDE:
try {
return calculatorService.divide(num1, num2);
} catch (IllegalArgumentException e) {
throw new TDivisionByZeroException();
}
default:
throw new TException("Unknown operation " + op);
}
}
}
其中:
calculatorService
是一个具体的逻辑业务类,单独分开写。通过@Autowired
可以自动注入到当前的类中。- 添加
@Component
注解充分利用spring作为容器的优势。
启动thrift服务的包装类,方便thrift服务的启动
源码如下:
package com.albert;
import com.albert.thrift.TCalculatorService;
import com.albert.thriftHandler.CalculatorServiceHandler;
import org.apache.log4j.Logger;
import org.apache.thrift.TProcessor;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TServerSocket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* Created by albert on 16-7-26.
*/
@Component
public class ThriftServer {
private static Logger logger = Logger.getLogger(ThriftServer.class);
@Value("${thrift.server.port}")
private int thriftServerPort;
@Autowired
CalculatorServiceHandler calculatorServiceHandler;
public void startServer() {
try {
TProcessor tProcessor = new TCalculatorService.Processor<>(calculatorServiceHandler);
TServerSocket tServerSocket = new TServerSocket(thriftServerPort);
TThreadPoolServer.Args tpsArgs = new TThreadPoolServer.Args(tServerSocket);
tpsArgs.processor(tProcessor);
tpsArgs.transportFactory(new TFramedTransport.Factory());
tpsArgs.protocolFactory(new TCompactProtocol.Factory());
TServer server = new TThreadPoolServer(tpsArgs);
logger.info("start thrift server at: " + thriftServerPort);
server.serve();
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中:
thriftServerPort
也是自动注入,其配置在文件src/main/resources/application.properties中设置- thrift的服务器类型采用了
TThreadPoolServer
,关于thrift中各种服务器之前的对比说明可以参看本文最后的附录
springBoot配置及启动服务
为了可以在 src/main/resources/application.properties中设置 系统的参数,需要添加一下的配置文件:
package com.albert.config;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
/**
* Created by albert on 16-7-26.
*/
@Configuration
@EnableAutoConfiguration
@ComponentScan("com.albert")
public class AppConfig {
@Bean
public static PropertyPlaceholderConfigurer properties() {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ClassPathResource[] resources = new ClassPathResource[]
{new ClassPathResource("application.properties")};
ppc.setLocations(resources);
ppc.setIgnoreUnresolvablePlaceholders(true);
return ppc;
}
}
配置完毕后,需要在src/main/java/com/albert/thriftSpringBootApplication.java中启动thrift服务器:
package com.albert;
import com.albert.config.AppConfig;
import org.apache.log4j.Logger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@SpringBootApplication
public class ThriftSpringBootApplication {
private static Logger logger = Logger.getLogger(ThriftSpringBootApplication.class);
public static void main(String[] args) {
SpringApplication.run(ThriftSpringBootApplication.class, args);
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ThriftServer thriftServer = (ThriftServer) context.getBean("thriftServer");
thriftServer.startServer();
}
}
经过以上的开发,我们就可以在idea中把spring服务启动起来,选中thriftSpringBootApplication.java文件,右键选择‘Run’
最后还差单元测试
这个不是一个比选项,你可以自己写一个thrift client测试你的接口服务,但是强烈建议写单元测试,这个是代码是否可靠的保障
测试代码如下:
package com.albert;
import com.albert.thrift.TCalculatorService;
import com.albert.thrift.TDivisionByZeroException;
import com.albert.thrift.TOperation;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = ThriftSpringBootApplication.class)
@WebAppConfiguration
@IntegrationTest("server.port:0")
public class ThriftSpringBootApplicationTests {
@Test
public void contextLoads() {
}
protected TCalculatorService.Client client;
@Before
public void setUp() throws Exception {
TTransport transport = new TFramedTransport(new TSocket("localhost", 6666));
TProtocol protocol = new TCompactProtocol(transport);
transport.open();
client = new TCalculatorService.Client(protocol);
}
@Test
public void testAdd() throws Exception {
assertEquals(5, client.calculate(2, 3, TOperation.ADD));
}
@Test
public void testSubtract() throws Exception {
assertEquals(3, client.calculate(5, 2, TOperation.SUBTRACT));
}
@Test
public void testMultiply() throws Exception {
assertEquals(10, client.calculate(5, 2, TOperation.MULTIPLY));
}
@Test
public void testDivide() throws Exception {
assertEquals(2, client.calculate(10, 5, TOperation.DIVIDE));
}
@Test(expected = TDivisionByZeroException.class)
public void testDivisionByZero() throws Exception {
client.calculate(10, 0, TOperation.DIVIDE);
}
}
你可以直接用过右键跑一下上面的文件,或者可以在命令行项目的根目录下运行gradle build
附加说明
thrift各个服务器比较说明:
-
TSimpleServer
TSimplerServer接受一个连接,处理连接请求,直到客户端关闭了连接,它才回去接受一个新的连接。正因为它只在一个单独的线程中以阻塞I/O的方式完成这些工作,所以它只能服务一个客户端连接,其他所有客户端在被服务器端接受之前都只能等待。TSimpleServer主要用于测试目的,不要在生产环境中使用它!
-
TNonblockingServer
TNonblockingServer使用非阻塞的I/O解决了TSimpleServer一个客户端阻塞其他所有客户端的问题。它使用了java.nio.channels.Selector,通过调用select(),它使得你阻塞在多个连接上,而不是阻塞在单一的连接上。当一或多个连接准备好被接受/读/写时,select()调用便会返回。TNonblockingServer处理这些连接的时候,要么接受它,要么从它那读数据,要么把数据写到它那里,然后再次调用select()来等待下一个可用的连接。通用这种方式,server可同时服务多个客户端,而不会出现一个客户端把其他客户端全部“饿死”的情况。
-
THsHaServer
所有消息是被调用select()方法的同一个线程处理的。假设有10个客户端,处理每条消息所需时间为100毫秒,那么,latency和吞吐量分别是多少?当一条消息被处理的时候,其他9个客户端就等着被select,所以客户端需要等待1秒钟才能从服务器端得到回应,吞吐量就是10个请求/秒。如果可以同时处理多条消息的话,会很不错吧?因此,THsHaServer(半同步/半异步的server)就应运而生了。它使用一个单独的线程来处理网络I/O,一个独立的worker线程池来处理消息。这样,只要有空闲的worker线程,消息就会被立即处理,因此多条消息能被并行处理。用上面的例子来说,现在的latency就是100毫秒,而吞吐量就是100个请求/秒。
-
TThreadedSelectorServer
Thrift 0.8引入了另一种server实现,即TThreadedSelectorServer。它与THsHaServer的主要区别在于,TThreadedSelectorServer允许你用多个线程来处理网络I/O。它维护了两个线程池,一个用来处理网络I/O,另一个用来进行请求的处理。当网络I/O是瓶颈的时候,TThreadedSelectorServer比THsHaServer的表现要好。
-
TThreadPoolServer
TThreadPoolServer与其他三种server不同的是有一个专用的线程用来接受连接。一旦接受了一个连接,它就会被放入ThreadPoolExecutor中的一个worker线程里处理。 worker线程被绑定到特定的客户端连接上,直到它关闭。一旦连接关闭,该worker线程就又回到了线程池中。 你可以配置线程池的最小、最大线程数,默认值分别是5(最小)和Integer.MAX_VALUE(最大)。这意味着,如果有1万个并发的客户端连接,你就需要运行1万个线程。所以它对系统资源的消耗不像其他类型的server一样那么“友好”。此外,如果客户端数量超过了线程池中的最大线程数,在有一个worker线程可用之前,请求将被一直阻塞在那里。TThreadPoolServer的表现非常优异。如果你提前知道了将要连接到你服务器上的客户端数量,并且你不介意运行大量线程的话,TThreadPoolServer对你可能是个很好的选择。
参考连接:
http://sgq0085.iteye.com/blog/2213959 https://dzone.com/articles/building-microservices-spring http://bsideup.blogspot.jp/2015/04/spring-boot-thrift-part2.html https://github.com/jruyi/thrift-gradle-plugin http://stackoverflow.com/questions/35232344/gradle-thrift-plugin-by-example https://beyondalbert.com/thrift-with-java-server-and-ruby-client/
发布评论