RPC的框架的应用--thrift

本文是多篇文章的转载,但是经过本人的亲自实践,多谢大神的分享,有幸学的好多的东西

thrift的安装

安装镜像下载地址
http://archive.apache.org/dist/thrift/0.12.0/

安装
1.在C盘新建一个Thtift文件夹,将下载的thrift-0.9.1.exe重新命名为thrift.exe后放到Thtift文件夹下
2.配置环境变量path
在这里插入图片描述

3、测试安装是否成功
在这里插入图片描述

4、编写thrift文件

namespace java com.thrifttest.generate
enum RequestType {
    SAY_HELLO,   //问好
    QUERY_TIME,  //询问时间
}
struct Request {
    1: required RequestType type;  // 请求的类型,必选
    2: required string name;       // 发起请求的人的名字,必选
    3: optional i32 age;           // 发起请求的人的年龄,可选
}
exception RequestException {
    1: required i32 code;
    2: optional string reason;
}
// 服务名
service HelloWordService {
    string doAction(1: Request request) throws (1:RequestException qe); // 可能抛出异常。
}

5、运行命令生成代码
在这里插入图片描述
在这里插入图片描述

应用

1、test.thrift

namespace java com.thrifttest.generate
enum RequestType {
    SAY_HELLO,   //问好
    QUERY_TIME,  //询问时间
}
struct Request {
    1: required RequestType type;  // 请求的类型,必选
    2: required string name;       // 发起请求的人的名字,必选
    3: optional i32 age;           // 发起请求的人的年龄,可选
}
exception RequestException {
    1: required i32 code;
    2: optional string reason;
}
// 服务名
service HelloWordService {
    string doAction(1: Request request) throws (1:RequestException qe); // 可能抛出异常。
}

2、执行命令:
thrift -gen java test.thrift

3、生成java文件
在这里插入图片描述
4、放到项目的对应的namespace下
namespace java com.thrifttest.generate
包为 com.thrifttest.generate
在这里插入图片描述
5、创建接口HelloWordService的服务端的实现:

package com.thrifttest.server;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.thrift.TException;

import com.thrifttest.generate.HelloWordService;
import com.thrifttest.generate.Request;
import com.thrifttest.generate.RequestException;

public class HelloWordServiceImpl  implements HelloWordService.Iface{

	Logger LOG= LogManager.getLogger(HelloWordServiceImpl.class);
	@Override
	public String doAction(Request request) throws RequestException, TException {
		LOG.info("服务端接收到消息: request.age={}, request.name={}, request.type={}", request.getAge(), request.getName(), request.getType());
		
		return null;
	}

}

6、创建服务端
server的创建socket的几个类:
在这里插入图片描述
server的几个实现类:
在这里插入图片描述
TServer的构造函数:
在这里插入图片描述
在这里插入图片描述

代码来了:
ThriftServer.java`

public class ThriftServer {

	Logger LOG=LogManager.getLogger(ThriftServer.class);
	
	private void Main() throws TTransportException {
        //设置传输方式
		TServerTransport serverTransport =	createTserverTransport(165,false);
		//接口实现
		Processor<Iface> processor = new Processor<>(new HelloWordServiceImpl());
		
		//启用多线程服务端
		TServer server =createTServer(serverTransport,processor,false,false);
		
		//启动
		server.serve();
		System.out.println("服务端启动!");
	}
	
	/**
	 *  设置传输的协议
	 * @param port
	 * @param isblock
	 * @return
	 * @throws TTransportException 
	 */
	private TServerTransport createTserverTransport(int port, boolean isblock) throws TTransportException {
		if(isblock) {
			//阻塞传输方式,不推荐,性能比TNonblockingServerSocket 差
		return 	new TServerSocket(port);
		}
		
		return new TNonblockingServerSocket(port);
	}

	/**
	 * 创建服务端
	 * @param serverTransport
	 * @param processor
	 * @param singleThread
	 * @param isBlock
	 * @return
	 */
	public TServer createTServer(TServerTransport serverTransport,Processor processor,boolean singleThread,boolean isBlock ) {
		   if (isBlock) {
			 if(singleThread) {
				// 同步阻塞单线程服务端,性能很差,一般做测试用,禁止在正式环境中使用,协议服务端和客户端必须保持一致。
				return new TSimpleServer(new TSimpleServer
						            .Args(serverTransport)
						            .processor(processor)
						            .transportFactory(new TFramedTransport.Factory())
						            .protocolFactory(new TCompactProtocol.Factory()));
			 }
			// 同步阻塞多线程服务端,客户端如果连接一直不释放,服务端会一直有个线程与之对应,仅适合客户端极少或是没有常连接的情况,协议服务端和客户端必须保持一致。
			 return  new TThreadPoolServer(new TThreadPoolServer
					 .Args(serverTransport)
					 .processor(processor)
					 .transportFactory(new TFramedTransport.Factory())
			         .protocolFactory(new TCompactProtocol.Factory()));
		   }
	  if(singleThread) {
		// 半同步半异步非阻塞单线程服务端,serverTransport必须为TNonblockingServerTransport类型,客户端传输对象必须为TFramedTransport,协议服务端和客户端必须保持一致
		  return new THsHaServer(new THsHaServer
				  .Args((TNonblockingServerTransport)serverTransport)
				  .processor(processor)
				  .transportFactory(new TFramedTransport.Factory())
				  .protocolFactory(new TCompactProtocol.Factory()));
	  }
	// 半同步半异步非阻塞多线程服务端,强烈推荐,serverTransport必须为TNonblockingServerTransport类型,客户端传输对象必须为TFramedTransport,协议服务端和客户端必须保持一致。
	  return new TThreadedSelectorServer(new TThreadedSelectorServer
			     .Args((TNonblockingServerTransport)serverTransport)
			     .processor(processor)
				 .transportFactory(new TFramedTransport.Factory())
				 .protocolFactory(new TCompactProtocol.Factory()));
	}
	
}

7、创建客户端:

public class ThriftClient {

	
	private static final Logger LOG = LogManager.getLogger(ThriftClient.class);
	
	
	public static void main(String[] args) throws RequestException, TException {
		LOG.info("send with pool");
		rpcWithPool();
		LOG.info("send simple");
		rpcSimple();
	}

	/**
	 * 使用连接池进行RPC调用, 强烈推荐
	 * @throws TException 
	 * @throws RequestException 
	 */
	private static void rpcWithPool()  {
		//创建一个代理发送服务请求
		HelloWordService.Iface helloWordService= new HelloWordServiceProxy(createPool());
		String result;
		try {
			result = helloWordService.doAction(createRequest("小明", 16));
			LOG.info("result is {}", result);
			result = helloWordService.doAction(createRequest("小白", 20));
		} catch (RequestException e) {
			LOG.error("reason is " + e.getReason());
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 创建请求参数
	 * @param string
	 * @param i
	 * @return
	 */
	private static Request createRequest(String name, int age) {
		Request request = new Request();
		request.setAge(age);
		request.setName(name);
		request.setType(RequestType.SAY_HELLO);
		return request;
	}

	//创建连接池
	public static GenericObjectPool<TProtocol> createPool() {
		// 初始化连接池
				GenericObjectPool<TProtocol> pool = new GenericObjectPool<>(new TProtocolFactory("127.0.0.1", 168));
				// 连接池设置
				GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
				// 最大空闲连接数
				genericObjectPoolConfig.setMaxIdle(2);
				// 最大连接数
				genericObjectPoolConfig.setMaxTotal(5);
				// 从连接池获取连接的时候需要测试
				genericObjectPoolConfig.setTestOnBorrow(true);
				// 创建连接的时候需要测试
				genericObjectPoolConfig.setTestOnCreate(true);
				// 归还连接的时候需要测试
				genericObjectPoolConfig.setTestOnReturn(true);
				// 连接空闲的时候需要测试
				genericObjectPoolConfig.setTestWhileIdle(true);
				// 使设置生效
				pool.setConfig(genericObjectPoolConfig);
				return pool;
	}
	
	/**
	 * 简单RPC请求,不带重连和连接池
	 * 
	 * @throws IOException
	 */
	private static void rpcSimple() {
		TTransport transport=null;
		try {
			//创建传输的方式
			transport =createSocket(true,"127.0.0.1",80);
			transport=createWrapperTransport(transport,false);
			transport.open();
			//设置传输的协议
			TProtocol protocol=createTProtocol(transport,true);
			HelloWordService.Iface client=new HelloWordService.Client(protocol);
			String result = client.doAction(createRequest("小明", 16));
			LOG.info("result is {}", result);
			result = client.doAction(createRequest("小白", 20));
		} catch (IOException e) {
			LOG.error("catch IOException: ", e);
		}catch (RequestException e) {
			LOG.error("reason is " + e.getReason());
		}catch (Exception e) {
			LOG.error("catch exception: ", e);
		}finally {
			if(null !=transport) {
				transport.close();
			}
		}
		
	}
     //包装
	private static TTransport createWrapperTransport(TTransport socket, boolean isZip) {
		if (isZip) {
			// 读取时按1K为单位将数据读出并调用JDK的zip函数进行解压再放到Buffer,写入时,在flush时先zip再写入。
			return new TZlibTransport(socket);
		}
		// 强烈推荐,性能或许稍有不如TZlibTransport和TFastFramedTransport,但是兼容性更好
		// 按Frame读写数据。
		// 每Frame的前4字节会记录Frame的长度(少于16M)。读的时候按长度预先将整Frame数据读入Buffer,
		// 再从Buffer慢慢读取。写的时候,每次flush将Buffer中的所有数据写成一个Frame。
		// 有长度信息的TFramedTransport是后面NonBlockingServer粘包拆包的基础。
		return new TFramedTransport(socket);
		// TFastFramedTransport 与TFramedTransport相比,始终使用相同的Buffer,提高了内存的使用率。
		// TFramedTransport的ReadBuffer每次读入Frame时都会创建新的byte[],WriteBuffer每次flush时如果大于初始1K也会重新创建byte[]。
		// 而TFastFramedTransport始终使用相同的ReadBuffer和WriteBuffer,都是1K起步,不够时自动按1.5倍增长,和NIO的ByteBuffer一样,加上limit/pos这样的指针,每次重复使用时设置一下它们。
		// return new TFastFramedTransport(socket);
	}

	/**
	 * 创建协议连接
	 * 
	 * @return
	 */
	private static TProtocol createTProtocol(TTransport transport, boolean isJson) {
	   if(isJson) {
		// JSON传输协议, JSON协议还有一种是
		// TSimpleJSONProtocol,不过这种只能写,而且不是标准JSON,给脚本语言解析用,不推荐
		   return new TJSONProtocol(transport);
	   }
	 // 压缩二进制传输协议,强烈推荐!传输带宽占用最小,
     // 二进制协议还有一种是TBinaryProtocol,这种带宽占用比TCompactProtocol大,不推荐
	    return new TCompactProtocol(transport);
	}

	/**
	 * 
	 * @param isSyn
	 * @param ip
	 * @param port
	 * @return
	 * @throws IOException 
	 */
	private static TTransport createSocket(boolean isSyn,String ip, int port) throws IOException {
		if(isSyn) {
			//同步
			return new TSocket(ip,port);
		}
		//异步
		return new TNonblockingSocket(ip,port);
	}

	
}

8、创建代理

public class HelloWordServiceProxy implements HelloWordService.Iface{
	
	private static final Logger LOG = LogManager.getLogger(HelloWordServiceProxy.class);
	

	/**
	 * thrift连接池
	 */
	private GenericObjectPool<TProtocol> pool;
	
	/**
	 * 初始化代理
	 * 
	 * @param pool
	 */
	public HelloWordServiceProxy(GenericObjectPool<TProtocol> pool) {
		this.pool=pool;
	}

	@Override
	public String doAction(Request request) throws RequestException, TException {
		TProtocol protocol=null;
		try {
			 try {
				protocol = pool.borrowObject();
			} catch (Exception e) {
				e.printStackTrace();
			}
			HelloWordService.Iface client=ReconnectingThriftClient.wrap(new HelloWordService.Client(protocol),HelloWordService.Iface.class);
			LOG.info("xxxxxxx borrow protocol in pool");
			return client.doAction(request);
		} finally {
			if(null!=protocol) {
				pool.returnObject(protocol);
			}
		}
	}
	  

}

9、创建协议工厂:

public class TProtocolFactory extends BasePooledObjectFactory<TProtocol> {

	private Logger LOG = LogManager.getLogger(TProtocolFactory.class);

	private String host;
	private int port;
	private boolean keepAlive = true;
	
	

	public TProtocolFactory(String host, int port, boolean keepAlive) {
		super();
		this.host = host;
		this.port = port;
		this.keepAlive = keepAlive;
	}

	
	public TProtocolFactory(String host, int port) {
		super();
		this.host = host;
		this.port = port;
	}


	@Override
	public TProtocol create() throws Exception {
             TSocket socket=new TSocket(host, port);
             TTransport transport=new TFramedTransport(socket);
             transport.open();
             LOG.info("xxxxxxx create a tprotocol and open ttransport");
             TProtocol protocol=new TCompactProtocol(transport);
		return protocol;
	}

	@Override
	public PooledObject<TProtocol> wrap(TProtocol obj) {
		return new DefaultPooledObject<TProtocol>(obj);
	}
	
	 /**
     * 对象钝化(即:从激活状态转入非激活状态,returnObject时触发)
     *
     * @param pooledObject
     * @throws TTransportException
     */
    @Override
    public void passivateObject(PooledObject<TProtocol> pooledObject) throws TTransportException {
        if(!keepAlive) {
        	LOG.info("XXXXXX close ttransport");
        	pooledObject.getObject().getTransport().flush();
        	pooledObject.getObject().getTransport().close();
        }else {
        	validateObject(pooledObject);
        }
    }
 
 
    /**
     * 对象激活(borrowObject时触发)
     *
     * @param pooledObject
     * @throws TTransportException
     */
    @Override
    public void activateObject(PooledObject<TProtocol> pooledObject) throws TTransportException {
        if (!pooledObject.getObject().getTransport().isOpen()) {
            pooledObject.getObject().getTransport().open();
        }
    }
 
 
    /**
     * 对象销毁(clear时会触发)
     * @param pooledObject
     * @throws TTransportException
     */
    @Override
    public void destroyObject(PooledObject<TProtocol> pooledObject) throws TTransportException {
        passivateObject(pooledObject);
        pooledObject.markAbandoned();
    }
 
 
    /**
     * 验证对象有效性
     *
     * @param p
     * @return
     */
    @Override
    public boolean validateObject(PooledObject<TProtocol> p) {
        if(p.getObject()!=null) {
        	if(p.getObject().getTransport().isOpen()) {
        		return true;
        	}
        	try {
				p.getObject().getTransport().open();
				return true;
			} catch (Exception e) {
                 e.printStackTrace();
			}
        }
        return false;
    }
}

10、自动重连机制:

这段代码来自:http://liveramp.com/engineering/reconnecting-thrift-client/

/**
 * thrift 自动断线重连
 * @author mmm
 *
 */
public final class ReconnectingThriftClient {
    private static final Logger LOG = LoggerFactory.getLogger(ReconnectingThriftClient.class);

    /**
     * List of causes which suggest a restart might fix things (defined as constants in {@link org.apache.thrift.transport.TTransportException}).
     */
    private static final Set<Integer> RESTARTABLE_CAUSES = Sets.newHashSet(
            TTransportException.NOT_OPEN,
            TTransportException.END_OF_FILE,
            TTransportException.TIMED_OUT,
            TTransportException.UNKNOWN);

    public static class Options {
        private int numRetries;
        private long timeBetweenRetries;

        /**
         * @param numRetries         the maximum number of times to try reconnecting before giving up and throwing an
         *                           exception
         * @param timeBetweenRetries the number of milliseconds to wait in between reconnection attempts.
         */
        public Options(int numRetries, long timeBetweenRetries) {
            this.numRetries = numRetries;
            this.timeBetweenRetries = timeBetweenRetries;
        }

        private int getNumRetries() {
            return numRetries;
        }

        private long getTimeBetweenRetries() {
            return timeBetweenRetries;
        }

        public Options withNumRetries(int numRetries) {
            this.numRetries = numRetries;
            return this;
        }

        public Options withTimeBetweenRetries(long timeBetweenRetries) {
            this.timeBetweenRetries = timeBetweenRetries;
            return this;
        }

        public static Options defaults() {
            return new Options(5, 10000L);
        }
    }

    /**
     * Reflectively wraps a thrift client so that when a call fails due to a networking error, a reconnect is attempted.
     *
     * @param baseClient      the client to wrap
     * @param clientInterface the interface that the client implements (can be inferred by using
     *                        {@link #wrap(org.apache.thrift.TServiceClient, intellif.mining.thrift.ReconnectingMqttClient.spruce_lib.singletons.ReconnectingThriftClient.Options)}
     * @param options         options that control behavior of the reconnecting client
     * @param <T>
     * @param <C>
     * @return
     */
    public static <T extends TServiceClient, C> C wrap(T baseClient, Class<C> clientInterface, Options options) {
        Object proxyObject = Proxy.newProxyInstance(clientInterface.getClassLoader(),
                new Class<?>[]{clientInterface},
                new ReconnectingClientProxy<T>(baseClient, options.getNumRetries(), options.getTimeBetweenRetries()));

        return (C) proxyObject;
    }

    /**
     * Reflectively wraps a thrift client so that when a call fails due to a networking error, a reconnect is attempted.
     *
     * @param baseClient the client to wrap
     * @param options    options that control behavior of the reconnecting client
     * @param <T>
     * @param <C>
     * @return
     */
    public static <T extends TServiceClient, C> C wrap(T baseClient, Options options) {
        Class<?>[] interfaces = baseClient.getClass().getInterfaces();

        for (Class<?> iface : interfaces) {
            if (iface.getSimpleName().equals("Iface") && iface.getEnclosingClass().equals(baseClient.getClass().getEnclosingClass())) {
                return (C) wrap(baseClient, iface, options);
            }
        }

        throw new RuntimeException("Class needs to implement Iface directly. Use wrap(TServiceClient, Class) instead.");
    }

    /**
     * Reflectively wraps a thrift client so that when a call fails due to a networking error, a reconnect is attempted.
     *
     * @param baseClient      the client to wrap
     * @param clientInterface the interface that the client implements (can be inferred by using
     *                        {@link #wrap(org.apache.thrift.TServiceClient, intellif.mining.thrift.ReconnectingMqttClient.spruce_lib.singletons.ReconnectingThriftClient.Options)}
     * @param <T>
     * @param <C>
     * @return
     */
    public static <T extends TServiceClient, C> C wrap(T baseClient, Class<C> clientInterface) {
        return wrap(baseClient, clientInterface, Options.defaults());
    }

    /**
     * Reflectively wraps a thrift client so that when a call fails due to a networking error, a reconnect is attempted.
     *
     * @param baseClient the client to wrap
     * @param <T>
     * @param <C>
     * @return
     */
    public static <T extends TServiceClient, C> C wrap(T baseClient) {
        return wrap(baseClient, Options.defaults());
    }

    /**
     * Helper proxy class. Attempts to call method on proxy object wrapped in try/catch. If it fails, it attempts a
     * reconnect and tries the method again.
     *
     * @param <T>
     */
    private static class ReconnectingClientProxy<T extends TServiceClient> implements InvocationHandler {
        private final T baseClient;
        private final int maxRetries;
        private final long timeBetweenRetries;

        public ReconnectingClientProxy(T baseClient, int maxRetries, long timeBetweenRetries) {
            this.baseClient = baseClient;
            this.maxRetries = maxRetries;
            this.timeBetweenRetries = timeBetweenRetries;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                return method.invoke(baseClient, args);
            } catch (InvocationTargetException e) {
                if (e.getTargetException() instanceof TTransportException) {
                    TTransportException cause = (TTransportException) e.getTargetException();

                    if (RESTARTABLE_CAUSES.contains(cause.getType())) {
                        reconnectOrThrowException(baseClient.getInputProtocol().getTransport(), maxRetries, timeBetweenRetries);
                        return method.invoke(baseClient, args);
                    }
                }

                throw null == e.getCause() ? e : e.getCause();
            }
        }

        private static void reconnectOrThrowException(TTransport transport, int maxRetries, long timeBetweenRetries) throws TTransportException {
            int errors = 0;
            transport.close();

            while (errors < maxRetries) {
                try {
                    LOG.info("Attempting to reconnect...");
                    transport.open();
                    LOG.info("Reconnection successful");
                    break;
                } catch (TTransportException e) {
                    LOG.error("Error while reconnecting:", e);
                    errors++;

                    if (errors < maxRetries) {
                        try {
                            LOG.info("Sleeping for {} milliseconds before retrying", timeBetweenRetries);
                            Thread.sleep(timeBetweenRetries);
                        } catch (InterruptedException e2) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }

            if (errors >= maxRetries) {
                throw new TTransportException("Failed to reconnect");
            }
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
跨语言 RPC 框架 Thrift 是一个开源的高效通信协议和序列化框架,可以用于构建跨语言的客户端和服务器端应用程序。 下面是一个简单的 Thrift 实战示例: 1. 定义 Thrift 文件:首先,我们需要创建一个 .thrift 文件来定义我们的 RPC 接口和数据结构。例如,我们可以创建一个名为 "calculator.thrift" 的文件,其中包含以下内容: ``` namespace java com.example.calculator namespace py calculator service CalculatorService { i32 add(1: i32 num1, 2: i32 num2), i32 subtract(1: i32 num1, 2: i32 num2), i32 multiply(1: i32 num1, 2: i32 num2), double divide(1: double num1, 2: double num2) } ``` 2. 生成代码:使用 Thrift 编译器将 .thrift 文件生成相应语言的代码。例如,我们可以使用以下命令生成 Java 和 Python 的代码: ``` thrift --gen java calculator.thrift thrift --gen py calculator.thrift ``` 这将生成相应语言的客户端和服务器端代码。 3. 实现服务器端:在服务器端,我们需要实现 Thrift 定义的接口。对于 Java,我们可以编写一个实现 CalculatorService 接口的类,并在该类中实现具体的业务逻辑。对于 Python,我们可以编写一个类似的实现。 4. 启动服务器:在服务器上启动 Thrift 服务器,以便接受客户端的请求。不同语言的服务器实现方式可能有所不同,但通常都需要提供一个监听指定端口的入口。 5. 实现客户端:在客户端,我们可以使用生成的客户端代码来调用服务器端的接口。对于 Java,我们可以创建一个 Thrift 客户端,并使用生成的代码来调用服务器端提供的方法。对于 Python,我们可以编写相应的客户端代码。 6. 运行客户端:运行客户端代码,通过网络连接到服务器,并发送请求调用服务器端的方法。 以上是一个简单的 Thrift 实战示例。请注意,具体的实现方式和步骤可能因语言和框架而异,但基本原理是相似的。Thrift 还支持更多高级特性,如异步调用、连接池等,可以根据具体需求进行扩展和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值