java处理异步接口_使用 Java+Thrift 实现异步事件处理服务

为什么要使用异步事件处理:

在web或者其他应用中,有一些并不是迫切需要返回值的操作,比如发短信,发邮件,下载第三方图片等,

如果这些第三方网络请求都在一次http请求中实现(传统方法使用php执行curl)将会造成客户端等待返回时间较长,

如果遇到第三方服务器出问题,或者dns服务器响应慢等网络问题,可能造成客户端主动断开连接

实际上队列服务有相应的软件,本文只做为原理和编程思想的学习

如果小型的需求也可以直接使用改程序,不过需要考虑单一线程处理效率较低,如果开多个线程,需要考虑线程管理(如线程池)的问题

场景描述:

客户端请求服务器 localhost,服务器需要请求第三方 http://a.360lt.com/ ,

thrift:

是一个跨语言开发框架,支持c、java、php、python等主流语言

使用一个表述声明语言定义接口,通过实现对应接口成员函数实现不用语言之间的调用

1.客户端浏览器请求服务器

2.服务器发送一个事件到Queue服务(通过调用Thrift生成的函数接口),并返回浏览器提示稍后查询

3.Queue收到事件,执行对应程序(如访问第三方服务器拉取数据)

4.Queue将结果保存到数据库

THRIFT 接口声明

service ThriftTest

{

bool sendMsg(1:i64 userId) ,

bool requestRemoteApi(1:string url)

}

JAVA QUEUE服务进程

ThriftTestServer.java

public class ThriftTestServer {

public static void main(String[] args) {

try {

// 设置服务端口为 7911

//TServerTransport serverTransport = new TServerSocket(7911);

//serverTransport.

TServerTransport serverTransport = new TServerSocket(new InetSocketAddress("127.0.0.1", 7911));

// 设置协议工厂为 TBinaryProtocol.Factory

//Factory proFactory = new TBinaryProtocol.Factory();

// 关联处理器与 Hello 服务的实现

TProcessor processor = new ThriftTest.Processor(new ThriftTestImpl());

//TServer server = new TThreadPoolServer(processor, serverTransport, proFactory);

TServer server = new TSimpleServer(new Args(serverTransport).processor(processor));

System.out.println("Start server on port 7911...");

(new Thread(new QueueThread()) ).start(); // start queue thread

server.serve();

} catch (TTransportException e) {

e.printStackTrace();

}

}

}

ThriftTestImpl.java

public class ThriftTestImpl implements ThriftTest.Iface {

@Override

public boolean sendMsg(long userId) throws TException {

// TODO Auto-generated method stub

System.out.println("hello world");

return false;

}

@Override

public boolean requestRemoteApi(String url) throws TException {

// TODO Auto-generated method stub

QueueThread.addUrl(url);

return false;

}

}

QueueThread.java

public class QueueThread implements Runnable {

public static ArrayDeque urls ;

{

QueueThread.urls = new ArrayDeque() ;

}

public static void addUrl(String _url) {

QueueThread.urls.add(_url) ;

}

@Test

public void unitTest(){

try {

doRequest("http://a.360lt.com/");

//saveData("{"title":"a.360 response","content":"response at 2016-04-28 14:14:48"}");

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

private boolean doRequest(String url){

System.out.println("try to connect " + url);

try {

URL u = new URL(url);

HttpURLConnection conn = (HttpURLConnection) u.openConnection() ;

BufferedInputStream bis = new BufferedInputStream( conn.getInputStream() );

int len = 1024 ;

int off = 0 ;

byte b[] = new byte[len] ;

int readLen = 0;

int realReadLen = 0 ;

ByteArrayOutputStream contentBytes = new ByteArrayOutputStream();

while((realReadLen = bis.read(b, off, len)) != -1) {

contentBytes.write(b);

contentBytes.flush();

readLen += realReadLen ;

}

String jsonStr = new String(contentBytes.toByteArray(), 0, readLen, "utf-8") ;

System.out.println(jsonStr);

saveData(jsonStr) ;

} catch (Exception e) {

//这里应该记录日志

System.out.println("catch " + e.getClass().toString() + " msg is " + e.getMessage());

}

return true;

}

private boolean saveData(String json){

JSONTokener jsonParser = new JSONTokener(json);

JSONObject jsonObj = (JSONObject)jsonParser.nextValue() ;

String title = jsonObj.getString("title") ;

String content = jsonObj.getString("content") ;

Connection gdbcConn = null ;

PreparedStatement ps = null ;

ResultSet res = null ;

try{

Class.forName("com.mysql.jdbc.Driver");

String gdbcUrl = "jdbc:mysql://" +db.HOST+ ":" +db.PORT+ "/" +db.DBNAME+ "?characterEncoding=" +db.CHARSET;

String user = db.USER;

String pwd = db.PWD;

gdbcConn = DriverManager.getConnection(gdbcUrl, user, pwd);

String sql = "insert into demo(title,content,sort) values(?,?,?)";

ps = gdbcConn.prepareStatement(sql);

ps.setString(1, title);

ps.setString(2, content);

ps.setInt(3, 1);

boolean isok = ps.execute();

System.out.println("save data ok");

}catch(Exception e){

System.out.println("catch " + e.getClass().toString() + " msg is " + e.getMessage());

System.out.println(e.getLocalizedMessage());

}finally{

try{

if(ps != null)

ps.close();

if(res != null)

res.close();

if(gdbcConn != null)

gdbcConn.close();

}catch(Exception e){

//记录日志

System.out.println("catch " + e.getClass().toString() + " msg is " + e.getMessage());

}

}

return true;

}

@Override

public void run() {

try{

while(true){

//int cs = QueueThread.urls.size();

while(!QueueThread.urls.isEmpty()){

String _url = QueueThread.urls.pop() ;

doRequest(_url);

}

Thread.sleep(1000);

}

}catch(NoSuchElementException e){

//这个异常应该单独处理,或重启队列服务

System.out.println("catch " + e.getClass().toString() + " msg is " + e.getMessage());

}catch (Exception e) {

//这里应该记录日志

System.out.println("catch " + e.getClass().toString() + " msg is " + e.getMessage());

}

}

}

主要处理程序为 QueueThread 这个类,该类是队列进程的一个子线程

流程:

web端通过requestRemoteApi这个接口不断地向QueueThread中的静态变量urls添加新条目(入队)

QueueThread线程则不断地从中获取(出队)

然后调用doRequest程序,请求第三方接口

最后调用saveData程序,将返回的json格式数据解析并保存到数据库

注:

1.

这里使用队列有可能会由于重启进程造成事件丢失,这里提供一个思路,用Redis来处理

2.

关于处理多个事件,可以定义若干协议,格式如:

{“type”:“sendmsg”,“values”:{“par1”:“val1” …}}

然后制作一个事件处理器,通过不同的事件执行不同的操作,如发短信、发邮件、下载图片、上传图片、同步数据等

3.

如果需要做的灵活些不断地添加事件,可以使用java的反射机制做,封装一个事件处理类,每个成员函数为处理一个事件,协议中包含处理事件的函数名

WEB 服务

index.php

namespace demo ;

require_once 'Thrift/ClassLoader/ThriftClassLoader.php';

use ThriftClassLoaderThriftClassLoader;

use ThriftProtocolTBinaryProtocol;

use ThriftTransportTSocket;

use ThriftTransportTHttpClient;

use ThriftTransportTBufferedTransport;

use ThriftExceptionTException;

$loader = new ThriftClassLoader();

$loader->registerNamespace('Thrift', __DIR__);

$loader->register();

include 'Types.php' ;

include 'ThriftTest.php' ;

$url = 'http://a.360lt.com/' ;

try{

$socket = new TSocket('127.0.0.1', 7911);

$transport = new TBufferedTransport($socket, 1024, 1024);

$protocol = new TBinaryProtocol($transport);

$cli = new ThriftTestClient($protocol);

$transport->open();

$isok = $cli->requestRemoteApi($url);

$transport->close();

}catch (Exception $e){

echo "catch exception " . get_class($e) . ", message is " . $e->getMessage() ;

}

?>

注:

由于是异步请求,所以并不适合需要立刻返回给客户端的操作(比如获取用户个人信息)

使用的场景一般是发短信、发邮件、发推送、同步其他服务器数据等

特别是需要一次发送多条(需要多次建立连接,发送http请求)的操作,必须上队列服务,否则客户端会由于等待时间过长造成断开连接

index.php

date_default_timezone_set('Asia/ChongQing');

// 模拟一个接口提供端,假设提供端效率较低,这里延迟10m

sleep(3);

$data = array(

'title' => 'a.360 response' ,

'content' => 'response at '.date('Y-m-d H:i:s')

) ;

echo json_encode($data) ;

exit();

?>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值