一、问题描述

在上一篇《由浅入深了解Thrift之服务模型和序列化机制》文章中,我们已经了解了thrift的基本架构和网络服务模型的优缺点。如今的互联网圈中,RPC服务化的思想如火如荼。我们又该如何将thrift服务化应用到我们的项目中哪?实现thrift服务化前,我们先想想这几个问题:服务注册、服务发现、服务健康检测、服务“Load Balance”、隐藏client和server端的交互细节、服务调用端的对象池化。

  • 服务的注册、发现和健康检测,我们使用zookeeper可以很好的解决
  • 服务“Load Balance",我们可以使用简单的算法“权重+随机”,当然也可以使用成熟复杂的算法
  • 服务调用端的对象池化,我们可以使用common pool,使用简单又可以满足我们的需求

二、实现思路

1、thrift server端启动时,每个实例向zk集群以临时节点方式注册(这样,遍历zk上/server下有多少个临时节点就知道有哪些server实例)

thrift server端可以单机多端口多实例或多机部署多实例方式运行。

2、服务调用方实现一个连接池,连接池初始化时,通过zk将在线的server实例信息同步到本地并缓存,同时监听zk下的节点变化。

3、服务调用方与Server通讯时,从连接池中取一个可用的连接,用它实现RPC调用。

三、具体实现

1、thrift server端

thrift server端,向zk中注册server address

package com.wy.thriftpool.commzkpool;

import java.lang.instrument.IllegalClassFormatException;
import java.lang.reflect.Constructor; import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TBinaryProtocol.Factory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadedSelectorServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.springframework.beans.factory.InitializingBean; import com.wy.thrift.service.UserService.Processor;
import com.wy.thriftpool.commzkpool.support.ThriftServerAddressReporter;
import com.wy.thriftpool.commzkpool.support.ThriftServerIpTransfer;
import com.wy.thriftpool.commzkpool.support.impl.LocalNetworkIpTransfer; /**
* thrift server端,向zk中注册server address
*
* @author wy
*
*/
public class ThriftServiceServerFactory implements InitializingBean { // thrift server 服务端口
private Integer port;
// default 权重
private Integer priority = 1;
// service实现类
private Object service;
// thrift server 注册路径
private String configPath; private ThriftServerIpTransfer ipTransfer;
// thrift server注册类
private ThriftServerAddressReporter addressReporter;
// thrift server开启服务
private ServerThread serverThread; @Override
public void afterPropertiesSet() throws Exception {
if (ipTransfer == null) {
ipTransfer = new LocalNetworkIpTransfer();
}
String ip = ipTransfer.getIp();
if (ip == null) {
throw new NullPointerException("cant find server ip...");
}
String hostname = ip + ":" + port + ":" + priority;
Class<? extends Object> serviceClass = service.getClass();
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] interfaces = serviceClass.getInterfaces();
if (interfaces.length == 0) {
throw new IllegalClassFormatException("service-class should implements Iface");
} // reflect,load "Processor";
Processor<?> processor = null;
for (Class<?> clazz : interfaces) {
String cname = clazz.getSimpleName();
if (!cname.equals("Iface")) {
continue;
}
String pname = clazz.getEnclosingClass().getName() + "$Processor";
try {
Class<?> pclass = classLoader.loadClass(pname);
if (!pclass.isAssignableFrom(Processor.class)) {
continue;
}
Constructor<?> constructor = pclass.getConstructor(clazz);
processor = (Processor<?>) constructor.newInstance(service);
break;
} catch (Exception e) {
// TODO
}
} if (processor == null) {
throw new IllegalClassFormatException("service-class should implements Iface");
}
// 需要单独的线程,因为serve方法是阻塞的.
serverThread = new ServerThread(processor, port);
serverThread.start();
// report
if (addressReporter != null) {
addressReporter.report(configPath, hostname);
}
} class ServerThread extends Thread {
private TServer server; ServerThread(Processor<?> processor, int port) throws Exception {
// 设置传输通道
TNonblockingServerSocket serverTransport = new TNonblockingServerSocket(port);
// 设置二进制协议
Factory protocolFactory = new TBinaryProtocol.Factory(); TThreadedSelectorServer.Args tArgs = new TThreadedSelectorServer.Args(serverTransport);
tArgs.processor(processor);
tArgs.transportFactory(new TFramedTransport.Factory());
tArgs.protocolFactory(protocolFactory);
int num = Runtime.getRuntime().availableProcessors() * 2 + 1;
tArgs.selectorThreads(num);
tArgs.workerThreads(num * 10); // 网络服务模型
server = new TThreadedSelectorServer(tArgs); } @Override
public void run() {
try {
server.serve();
} catch (Exception e) {
//TODO
}
} public void stopServer() {
server.stop();
}
} public void close() {
serverThread.stopServer();
} public void setService(Object service) {
this.service = service;
} public void setPriority(Integer priority) {
this.priority = priority;
} public void setPort(Integer port) {
this.port = port;
} public void setIpTransfer(ThriftServerIpTransfer ipTransfer) {
this.ipTransfer = ipTransfer;
} public void setAddressReporter(ThriftServerAddressReporter addressReporter) {
this.addressReporter = addressReporter;
} public void setConfigPath(String configPath) {
this.configPath = configPath;
}
}

thrift server address注册到zk

package com.wy.thriftpool.commzkpool.support.impl;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.imps.CuratorFrameworkState;
import org.apache.zookeeper.CreateMode; import com.wy.thriftpool.commzkpool.support.ThriftServerAddressReporter; /**
* thrift server address注册到zk
*
* @author wy
*
*/
public class DynamicAddressReporter implements ThriftServerAddressReporter { private CuratorFramework zookeeper; public DynamicAddressReporter() {
} public DynamicAddressReporter(CuratorFramework zookeeper) {
this.zookeeper = zookeeper;
} public void setZookeeper(CuratorFramework zookeeper) {
this.zookeeper = zookeeper;
} @Override
public void report(String service, String address) throws Exception {
if (zookeeper.getState() == CuratorFrameworkState.LATENT) {
zookeeper.start();
zookeeper.newNamespaceAwareEnsurePath(service);
}
zookeeper.create().creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(service + "/i_", address.getBytes("utf-8"));
} public void close() {
zookeeper.close();
} }

。。。

spring配置文件

<!-- zookeeper -->
<bean id="thriftZookeeper" class="com.wy.thriftpool.commzkpool.zookeeper.ZookeeperFactory" destroy-method="close">
<property name="connectString" value="127.0.0.1:2181"></property>
<property name="namespace" value="thrift/thrift-service"></property>
</bean> <bean id="serviceAddressReporter" class="com.wy.thriftpool.commzkpool.support.impl.DynamicAddressReporter" destroy-method="close">
<property name="zookeeper" ref="thriftZookeeper"></property>
</bean> <bean id="userService" class="com.wy.thrift.service.UserServiceImpl"/> <bean class="com.wy.thriftpool.commzkpool.ThriftServiceServerFactory" destroy-method="close">
<property name="service" ref="userService"></property>
<property name="configPath" value="UserServiceImpl"></property>
<property name="port" value="9090"></property>
<property name="addressReporter" ref="serviceAddressReporter"></property>
</bean>

2、服务调用端

连接池实现

杯了个具,为啥就不能提交。代码在评论中。

   连接池工厂,负责与Thrift server通信

package com.wy.thriftpool.commzkconnpool;

import java.net.InetSocketAddress;

import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import com.wy.thriftpool.commzkpool.support.ThriftServerAddressProvider; /**
* 连接池工厂,负责与Thrift server通信
*
* @author wy
*
*/
public class ThriftPoolFactory implements PoolableObjectFactory<TTransport> {
private final Logger logger = LoggerFactory.getLogger(getClass());
// 超时设置
public int timeOut; private final ThriftServerAddressProvider addressProvider;
private PoolOperationCallBack callback; public ThriftPoolFactory(ThriftServerAddressProvider addressProvider, PoolOperationCallBack callback) {
super();
this.addressProvider = addressProvider;
this.callback = callback;
} public ThriftPoolFactory(ThriftServerAddressProvider addressProvider, PoolOperationCallBack callback, int timeOut) {
super();
this.addressProvider = addressProvider;
this.callback = callback;
this.timeOut = timeOut;
} /**
* 创建对象
*/
@Override
public TTransport makeObject() throws Exception {
try {
InetSocketAddress address = addressProvider.selector();
TTransport transport = new TSocket(address.getHostName(), address.getPort(), this.timeOut);
transport.open();
if (callback != null) {
callback.make(transport);
}
return transport;
} catch (Exception e) {
logger.error("creat transport error:", e);
throw new RuntimeException(e);
}
} /**
* 销毁对象
*/
@Override
public void destroyObject(TTransport transport) throws Exception {
if (transport != null && transport.isOpen()) {
transport.close();
}
} /**
* 检验对象是否可以由pool安全返回
*/
@Override
public boolean validateObject(TTransport transport) {
try {
if (transport != null && transport instanceof TSocket) {
TSocket thriftSocket = (TSocket) transport;
if (thriftSocket.isOpen()) {
return true;
} else {
return false;
}
} else {
return false;
}
} catch (Exception e) {
return false;
}
} @Override
public void activateObject(TTransport obj) throws Exception {
// TODO Auto-generated method stub } @Override
public void passivateObject(TTransport obj) throws Exception {
// TODO Auto-generated method stub } public static interface PoolOperationCallBack {
// 创建成功是执行
void make(TTransport transport); // 销毁之前执行
void destroy(TTransport transport);
}
}

连接池管理

package com.wy.thriftpool.commzkconnpool;

import org.apache.thrift.transport.TSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; /**
* 连接池管理
*
* @author wy
*
*/
@Service
public class ConnectionManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
// 保存local对象
ThreadLocal<TSocket> socketThreadSafe = new ThreadLocal<TSocket>(); // 连接提供池
@Autowired
private ConnectionProvider connectionProvider; public TSocket getSocket() {
TSocket socket = null;
try {
socket = connectionProvider.getConnection();
socketThreadSafe.set(socket);
return socketThreadSafe.get();
} catch (Exception e) {
logger.error("error ConnectionManager.invoke()", e);
} finally {
connectionProvider.returnCon(socket);
socketThreadSafe.remove();
}
return socket;
} }

spring配置文件

<!-- zookeeper -->
<bean id="thriftZookeeper" class="com.wy.thriftpool.commzkpool.zookeeper.ZookeeperFactory" destroy-method="close">
<property name="connectString" value="127.0.0.1:2181" />
<property name="namespace" value="thrift/thrift-service" />
</bean>
<bean id="connectionProvider" class="com.wy.thriftpool.commzkconnpool.impl.ConnectionProviderImpl">
<property name="maxActive" value="10" />
<property name="maxIdle" value="10" />
<property name="conTimeOut" value="2000" />
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="true" />
<property name="testWhileIdle" value="true" /> <property name="addressProvider">
<bean class="com.wy.thriftpool.commzkpool.support.impl.DynamicAddressProvider">
<property name="configPath" value="UserServiceImpl" />
<property name="zookeeper" ref="thriftZookeeper" />
</bean>
</property>
</bean>

参考:http://www.cnblogs.com/mumuxinfei/p/3876187.html

由于本人经验有限,文章中难免会有错误,请浏览文章的您指正或有不同的观点共同探讨!

由浅入深了解Thrift之客户端连接池化的更多相关文章

  1. 由浅入深了解Thrift之客户端连接池化续

    前文<由浅入深了解Thrift之客户端连接池化>中我们已经实现了服务调用端 连接的池化,实现的过于简陋,离实际的项目运用还很遥远.本文将在进一步改造,主要是两方面:1.服务端如何注册多个服 ...

  2. Redis客户端连接池

    使用场景 对于一些大对象,或者初始化过程较长的可复用的对象,我们如果每次都new对象出来,那么意味着会耗费大量的时间. 我们可以将这些对象缓存起来,当接口调用完毕后,不是销毁对象,当下次使用的时候,直 ...

  3. thrift - C#(CSharp)客户端连接池(ConnectionPool)

      调用示例:   var tran = ThriftPool.Instance().BorrowInstance(); TProtocol protocol = new TBinaryProtoco ...

  4. Swoole2.0协程客户端连接池的实现

    Swoole2.0官方默认的实例是短连接的,在请求处理完毕后就会切断redis或mysql的连接.实际项目可以使用连接池实现复用. 实现原理也很简单,使用SplQueue,在请求到来时判断资源队列中是 ...

  5. 高可用的池化 Thrift Client 实现(源码分享)

    本文将分享一个高可用的池化 Thrift Client 及其源码实现,欢迎阅读源码(Github)并使用,同时欢迎提出宝贵的意见和建议,本人将持续完善. 本文的主要目标读者是对 Thrift 有一定了 ...

  6. 由浅入深了解Thrift之服务模型和序列化机制

    一.Thrift介绍 Thrift是一个软件框架,用来进行可扩展且跨语言的服务的开发.它结合了功能强大的软件堆栈和代码生成引擎.其允许你定义一个简单的定义文件中的数据类型和服务接口.以作为输入文件,编 ...

  7. 一个I/O线程可以并发处理N个客户端连接和读写操作 I/O复用模型 基于Buf操作NIO可以读取任意位置的数据 Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel 事件驱动消息通知观察者模式

    Tomcat那些事儿 https://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650860016&idx=2&sn=549 ...

  8. redis客户端连接异常

    本文参考:http://mdba.cn/2015/04/02/redistwemproxy-%e5%ae%a2%e6%88%b7%e7%ab%af%e8%bf%9e%e6%8e%a5%e5%bc%82 ...

  9. atitit.客户端连接oracle数据库的方式总结

    客户端连接oracle数据库的方式总结 目录 Java程序连接一般使用jar驱动连接..... 桌面GUI一般采取c语言驱动oci.dll 直接连接... 间接连接(需要配置tns及其envi var ...

随机推荐

  1. DrawTool画笔之图形笔

    相关知识参考DrawTool画笔之纹理笔  , 图形笔的实现跟纹理笔的实现是一样的,重载Stroke的DrawCore方法,效果图: --------------------------------- ...

  2. StyleCop学习笔记——自定义规则

    本文将简单的一步一步的指导这可能有助于学习如何创建自己的规则 1.创建一个项目. Visual Studio创建一个新的类库项目.NET3.5 2.引用两个DLL,StyleCop.dll和Style ...

  3. .NET开源工作流RoadFlow-系统布署中常见错误及处理方法

    1.未开启asp.net状态服务时会出现以下错误: 解决办法:开启ASP.NET STATE SERVICE服务 2.未开启应用程序池32位: 解决办法:启用32位

  4. SSH使用缩写

    需要经常ssh到其它机器上,但如果每次都使用主机命名或ip地址,那挺难受的.这里有2个方法可以在ssh登陆时缩写(简写)主机名. 1. 在~/.ssh中添加config文件 [toughhou@hd1 ...

  5. strcpy/strlen/strcat/strcmp面试总结

    <strcpy拷贝越界问题> 一. 程序一 #include<stdio.h> #include<string.h> void main() { char s[]= ...

  6. core java 10~12(多线程 & I/O & Network网络编程)

    MODULE 10 Threads 多线程-------------------------------- 进程: 计算机在运行过程中的任务单元,CPU在一个时间点上只能执行一个进程,但在一个时间段上 ...

  7. golang初试:坑爷的

    用Golang与perl脚本比较, 初想至多差一倍吧...结果可不是一般的坑爹, 简直就是坑爷了. Perl脚本 #!/bin/bash source /etc/profile; function e ...

  8. Go语言示例-函数返回多个值

    Go语言中函数可以返回多个值,这和其它编程语言有很大的不同.对于有其它语言编程经验的人来说,最大的障碍不是学习这个特性,而是很难想到去使用这个特性. 简单如交换两个数值的例子: package mai ...

  9. matlab 函数的编写与调用

    matlab中写个函数,在主程序中调用该函数的方法 跟其它的编程语言都一样,但是子函数与主函数要存于不同的文件中,文件名就是函数名字.文件必须保存在current directory中,才能调用. 函 ...

  10. Java Day 12

    包 编译格式 javac -d . **.java 包之间的访问 类找不到: 类名写错,包名.类名 包不存在:指定classpath 其他包的类无法访问:权限 public protected 包导入 ...