一、前言
因为工作需要,用thrift实现rpc的时候,考虑到创建、销毁连接的开销比较大,因此想到弄一个thrift客户端连接池,每次使用thrift客户端只需要从池子中获取一个连接,用完后再放回去,这样可以保证程序重复使用少数的几个连接而不需要每次访问都创建和销毁连接, 从而提高系统性能。
网上有部分用org.apache.commons.pool2构建thrift客户端连接池的方法,因为我对springboot不够熟悉,在整合三者的时候遇到点困难,好在最后解决了,在此做个记录。
更新日志(每次更新的作用在更新日志):
- 2020.03.06 新增了ping()功能
二、thrift服务端
这部分内容比较简单,网上教程很多,这里简单提一下。
首先是编写thrift文件
struct DebugResponse{
1: required i32 code;
2: required string message;
}
service DebugService{
DebugResponse debug(1:string app_id,2:string data);
bool ping();
}
然后用thrift.exe
自动生成java文件,放在项目中,然后编写业务逻辑
public class DebugServiceImpl implements DebugService.Iface {
private Logger logger = LoggerFactory.getLogger(DebugServiceImpl.class);
@Override
public DebugResponse debug(String id, String data) throws TException {
//这里实现你的业务逻辑
logger.info("get a new request");
return new DebugResponse(0, id + "*-*-*" + data);
}
@Override
public boolean ping() throws TException {
//ping的功能是检查客户端是否可用
return true;
}
}
然后编写服务端启动程序
public static void main(String[] args) {
try {
TProcessor tProcessor = new DebugService.Processor<DebugService.Iface>(new DebugServiceImpl());
TNonblockingServerSocket socket = new TNonblockingServerSocket(6868);
TThreadedSelectorServer.Args args = new TThreadedSelectorServer.Args(socket);
args.processor(tProcessor);
args.transportFactory(new TFramedTransport.Factory());
args.protocolFactory(new TBinaryProtocol.Factory());
TServer server = new TThreadedSelectorServer(args);
} catch (Exception e) {
}
System.out.println("debug server start...");
server.serve();
}
三、thrift客户端连接池
网上的方法大体相似,我这里加上了springboot的一些特性
引入pom依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.5.0</version>
</dependency>
需要池化的对象
需要池化的对象是thrift的TTransport
。
对象工厂
创建工厂只需要继承BasePooledObjectFactory
,并重写部分函数。
因为每次创建TTransport时,要指定ip和端口等参数,因此这里使用springboot的自动注入功能@ConfigurationProperties(prefix = "debug.thrift")
,这样的话不用在代码里写死配置,可以通过在配置文件app.properties里加上debug.thrift.host=XX.XX.XX.XX
等完成配置。
@ConfigurationProperties(prefix = "debug.thrift")
@Component
public class DebugConnectionFactory extends BasePooledObjectFactory<TTransport> {
private String host = "localhost";
private int port = 6868;
private int timeOut = 10000;
public void setHost(String host) {
this.host = host;
}
public void setPort(int port) {
this.port = port;
}
public void setTimeOut(int timeOut) {
this.timeOut = timeOut;
}
@Override
public TTransport create() throws Exception {
TTransport tTransport = new TFramedTransport(new TSocket(host, port, timeOut));
if (!tTransport.isOpen()) {
tTransport.open();
}
return tTransport;
}
@Override
public boolean validateObject(PooledObject<TTransport> p) {
TTransport tTransport = p.getObject();
TProtocol tProtocol = new TBinaryProtocol(tTransport);
DebugService.Client client = new DebugService.Client(tProtocol);
boolean ping = false;
try {
//ping在这里起到的作用是检查TTransport是否可用
ping = client.ping();
} catch (Exception ignored) {
}
return ping;
}
@Override
public PooledObject<TTransport> wrap(TTransport tTransport) {
return new DefaultPooledObject<>(tTransport);
}
@Override
public void destroyObject(PooledObject<TTransport> p) {
//这里的作用是保证客户端在关闭时,两边都不会报错
if (p.getObject().isOpen()) {
try {
p.getObject().flush();
p.getObject().close();
} catch (Exception ignore) {
}
}
}
}
app.properties配置文件
debug.thrift.host=XX.XX.XX.XX
debug.thrift.port=12321
debug.thrift.timeOut=30000
对象池
直接继承了GenericObjectPool这个类,因为我们池化的对象是TTransport,而我们更希望获得Client,因此加上获得和回收的两个方法
public class DebugConnectionPool extends GenericObjectPool<TTransport> {
public DebugConnectionPool(PooledObjectFactory<TTransport> factory) {
super(factory);
}
public DebugConnectionPool(PooledObjectFactory<TTransport> factory, GenericObjectPoolConfig config) {
super(factory, config);
}
public DebugConnectionPool(PooledObjectFactory<TTransport> factory, GenericObjectPoolConfig config, AbandonedConfig abandonedConfig) {
super(factory, config, abandonedConfig);
}
//获得
public DebugService.Client getClient() throws Exception {
TTransport tTransport = this.borrowObject();
TProtocol tProtocol = new TBinaryProtocol(tTransport);
return new DebugService.Client(tProtocol);
}
//回收
public void releaseConnection(DebugService.Client client) {
TTransport tTransport = client.getInputProtocol().getTransport();
// TTransport tTransport = client.getOutputProtocol().getTransport();
//因为我们声明的时候是new DebugService.Client(tProtocol),这里两个protocol是一样的,选一个就可
this.returnObject(tTransport);
}
}
对象池的配置类
从上面的构造方法里可以看到,对象池除了传入一个工厂外,还可以传入一个配置类,所以我继承了GenericObjectPoolConfig
对象池配置类
@ConfigurationProperties(prefix = "debug.pool")
@Component
public class PoolConfig extends GenericObjectPoolConfig {
}
跟前面一样,因为加上了@ConfigurationProperties(prefix = "debug.pool")
,所以可以在配置文件app.properties里加上配置,在写配置文件的时候idea也会提示我们哪些属性可以配置。
Spring配置类
要在Spring中使用DebugConnectionPool这个对象池,还得加个配置类,声明一个@Bean
@Configuration
public class PoolAutoConfiguration {
@Bean
public DebugConnectionPool debugConnectionPool(PoolConfig poolConfig, DebugConnectionFactory debugConnectionFactory) {
//这里一定要加这个设置,不然springboot启动会报已经注册了某个bean的错误
poolConfig.setJmxEnabled(false);
return new DebugConnectionPool(debugConnectionFactory, poolConfig);
}
}
四、使用
至此,就完成了所有的准备工作了,要想使用,只需要注入DebugConnectionPool
即可
@Autowired
DebugConnectionPool debugConnectionPool;
@Test
public void testPool() throws Exception {
DebugService.Client client = debugConnectionPool.getClient();
DebugResponse debug = client.debug("test_id", "data");
System.out.println(debug.toString());
debugConnectionPool.releaseConnection(client);
}
五、总结
其实有很多人写了thrift客户端连接池的教程,但是我满脑子就是想用上springboot自动注入配置的特性,所以折腾了一下午,最终结果虽然功能简单,但还算满意。
更新日志
- 2020.03.06更新:写完这边博客的当天我就遇到bug了,当客户端和服务端已经建立连接后,池子里已经有几个socket了,这时如果服务端意外关闭再重启,那么thrift客户端池是不知道里面的socket哪些是可用的,因此补上了一个ping(),作用就是检测池子中的客户端哪些是可用的,此外还不够,建议在配置文件里加上
debug.pool.test-on-borrow=true
,作用是每次从池子里借一个客户端之前都先检查一下是否还能用,此时就会调用我们的ping(),保证每次拿的客户端都是可用的。
我自己很少写博客,写的不对的地方欢迎指出,觉得有用的话麻烦评论区告诉我吧。