java和python之间数据交互,不同语言间传输数据:使用RabbitMQ
问题描述
项目过程中可能会遇到,java从数据库取了很多数据,但java本身不方便处理,所以传递给python去处理,如何传?这里我结合很多已存在的方法,然后再谈谈怎么用rabbitmq来操作。
这里会举简单的例子示范,当然前提你需要了解基本的java,python代码编写,大致了解下队列、rabbitmq是啥,还有相关的文件操作、json等等。
方法
方法一:java直接执行python脚本.py文件,把数据放到参数里传递
举例说明:
java代码,
@Test
public void test() {
String pythonPath="/Library/Frameworks/Python.framework/Versions/3.6/bin/python3 ";
String filePath="/Users/guang/Documents/Python_Project/Test/test/receiver.py ";
//首先定义个list,赋值。
List<Integer> list1 = new ArrayList<Integer>();
int i = 1;
while(i<=10000) {
list1.add(i);
i++;
}
//调用python脚本并传递list
try {
Process process = Runtime.getRuntime().exec(
pythonPath + filePath + list1);
BufferedReader in=new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line=in.readLine())!=null){
System.out.println(line);
}
in.close();
int re=process.waitFor();
System.out.println(re==1?"----状态码1----运行失败":"----状态码0----运行成功");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
python代码,
if __name__ == '__main__':
print("------------- start -------------")
# print("len(sys.argv[1:])==", len(sys.argv[1:]))
print("sys.argv[1:]==", sys.argv[1:])
运行结果:
这样的方式有两个问题:
1、参数传输过程中形式会发生变化,比如上面我实际想传一个list过去,但通过sys.argv[1:]可以看到,拿到的数据是一个被加工过的字符串形式,这样你又要花费大量精力去切割或者其他操作来把它在python中变成一个列表;
2、你不妨试着把数据量调大10倍,从10000 -> 100000,发现会报下面的错误,因为传不了那么大的。
方法二:通过第三方文件作为中间站
(因为遇到项目的特殊性,这里举的例子是把大量数据存储到了excel中),即增加了写入和读取的过程。
数据准备:
为了方便测试,这里模拟java从数据库取出的每条数据是这么个简单的形式:
某地区|某年指标1|某年指标2|某年指标3| …
模拟生成数据代码:
public class testObj {
private String place;
private List<Integer> datalist;
public String getPlace() {
return place;
}
public void setPlace(String place) {
this.place = place;
}
public List<Integer> getDatalist() {
return datalist;
}
public void setDatalist(List<Integer> datalist) {
this.datalist = datalist;
}
public testObj(String place, List<Integer> datalist) {
super();
this.place = place;
this.datalist = datalist;
}
}
public class MockData {
public static List<testObj> mockData() {
List<testObj> list_obj = new ArrayList<testObj>();
for (int i = 0; i < 50000; i++) { //模拟5万行,每行70列
List<Integer> list_int = new ArrayList<Integer>();
for(int j=1949; j<=2019; j++) {
list_int.add(j);
}
testObj obj = new testObj("address: 中国"+i+"省,"+i+"市,"+i+"县,"+"某水利部门", list_int);
list_obj.add(obj);
}
return list_obj;
}
}
写入excel:
public class writeToExcel {
public static void writeExcel(List<testObj> list, String path) {
try {
// Excel底部的表名
String sheetn = "sheet1";
// 用JXL向新建的文件中添加内容
File myFilePath = new File(path);
if (!myFilePath.exists())
myFilePath.createNewFile();
OutputStream outf = new FileOutputStream(path);
WritableWorkbook wwb = Workbook.createWorkbook(outf);
jxl.write.WritableSheet writesheet = wwb.createSheet(sheetn, 1);
// 内容添加
for (int i = 0; i < list.size(); i++) {
testObj obj = (testObj)list.get(i);
Label label = new Label(0, i, obj.getPlace());
writesheet.addCell(label);
List<Integer> numbersList = obj.getDatalist();
for(int j=1;j<numbersList.size();j++) {
writesheet.addCell(new jxl.write.Number(j,i,(Integer)numbersList.get(j)));
}
}
wwb.write();
wwb.close();
} catch (WriteException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
long startTime=System.currentTimeMillis(); //获取开始时间
//数据准备
List<testObj> list = MockData.mockData();
//存放路径
String path;
try {
path = "/Users/guang/Documents/Java_Project/testdemo/src/main/java/com/test/testdemo/test.xls";
//System.out.println(path);
writeExcel(list, path);
} catch (Exception e) {
e.printStackTrace();
}
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(endTime-startTime)/1000.0+"s");
}
}
excel里已存在这么多模拟数据,
python读excel:
import xlrd
import datetime
if __name__ == '__main__':
starttime = datetime.datetime.now()
workbook = xlrd.open_workbook('/Users/guang/Documents/Java_Project/testdemo/src/main/java/com/test/testdemo/test.xls')
sheet = workbook.sheets()[0]
endtime = datetime.datetime.now()
print("拿到数据时间:{}".format(endtime - starttime))
nrows = sheet.nrows
print('数据行数:{}'.format(nrows))
这种方法其实是可取的,在数据量不是很大的情况下,效率和后面的rabbitmq差别并不是很大,但是数据量特别大的情况下速度可能会慢,在本例中有时候还要去考虑excel单个sheet的行列最大限制容量问题。
方法三:使用消息队列的方式,RabbitMQ
基本概念简述:
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信。
MQ是消费-生产者模型的一个典型的代表,一端往消息队列中不断写入消息,而另一端则可以读取或者订阅队列中的消息。MQ和JMS类似,但不同的是JMS是SUN JAVA消息中间件服务的一个标准和API定义,而MQ则是遵循了AMQP协议的具体实现和产品。RabbitMQ是一个在AMQP基础上完成的,可复用的企业消息系统。
上图是简单抽象的描述,具体到 rabbitmq 有很多详细的概念,这里不一一详述。当然,我们上面也说过 ,rabbitmq 是 AMQP 协议的一个开源实现,所以其内部实际上也是 AMQP 中的基本概念,包括:Message(消息)、Publisher(消息生产者),Exchange(交换器),Binding(绑定),Queue(消息队列),Connection(网络连接),Channel(信道),Consumer(消息消费者),Virtual Host(虚拟主机),Broker(消息队列服务器)。
使用说明:
首先,安装rabbitmq,这个可以参考网上相关教程比较简单,可以安装在本地,也可以其他地方,而对于我们这里java/python传输问题上,区别其实也就是代码中的host到底是localhost还是你的其他ip地址192.168.***.***, 因为我们是来告诉大家怎么传输,所以安装过程这里不一一赘述,大家可以去网络上了解一下。我是单独安装在虚拟机的ubuntu上的,安装成功后,主机也可以访问管理界面了:
当然我们这里只是简单的传数据问题,也不需要用这个复杂界面管理啥,有兴趣的同学可以去详细了解下rabbitmq综合的知识。
代码部分(数据我们依旧是用上面的模拟数据):
java代码(即生产者),
public class Producer1 {
public final static String QUEUE_NAME="data";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException{
long startTime=System.currentTimeMillis(); //获取开始时间
//数据准备
List<testObj> list = MockData.mockData();
//创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//设置RabbitMQ相关信息
factory.setHost("192.168.43.211");
factory.setUsername("test");
factory.setPassword("123456");
//factory.setPort(5672);
//创建一个新的连接
Connection connection = factory.newConnection();
//创建一个通道
Channel channel = connection.createChannel();
//声明一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
List<testObj> list1 = new ArrayList<testObj>();
list1 = new MockData().mockData();
//发送消息到队列中
ObjectMapper mapper=new ObjectMapper();
String message = mapper.writeValueAsString(list1);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
//关闭通道和连接
channel.close();
connection.close();
long endTime=System.currentTimeMillis(); //获取结束时间
System.out.println("程序运行时间: "+(endTime-startTime)/1000.0+"s");
}
}
这时候可以看到rabbitmq管理界面消息队列queue那里出现了我们定义的data队列,被java发送上去了
python代码(即消费者),
if __name__ == '__main__':
starttime = datetime.datetime.now()
# 创建socket链接
credentials = pika.PlainCredentials('test', '123456')
connection = pika.BlockingConnection(pika.ConnectionParameters(
'192.168.43.211', 5672, '/', credentials))
# 创建管道
channel = connection.channel()
# 创建队列
queue_name = 'data'
channel.queue_declare(queue_name)
# 如果接受到消息就调用回调函数,准备接受消息
# 声明回调函数
def callback(ch, method, properties, body):
message = json.loads(body.decode())
endtime = datetime.datetime.now()
print("拿数据时间:{}".format(endtime - starttime))
list = message
for i in list:
print(i)
channel.basic_consume(callback, queue=queue_name, no_ack=False)
channel.start_consuming()
结果:
我们可以看到python顺利拿到java传输的数据,至于你拿到数据后,后面要做什么复杂的操作、分析,那就是看你自己的需要了。
另外,你不妨尝试把模拟数据条数提高到10万条甚至更多,前两种方法未必能得到有效的支持,但rabbitmq基本能保持稳定有效。
总结
在解决不同语言程序之间数据传输问题上,方法各异,rabbitmq是一个很好的选择,处理数据量大,且从时间效率上来说也快(单从我的数据测试结果上来看整个过程速度提高3倍左右,当然其他复杂结构数据可能稍有差异)。但如果你的数据量并不巨大,其他方法也可行,根据不同情况,做最好的选择保证代价最低一定是我们优先考虑的事儿。
最后声明,本篇文章只是针对java向python传输数据的几种方法进行分析,所以里面用到的一些知识不面面详解,具体可以去参考网上相关教程。另外,由于不同机器、不同系统环境下的差异是不确定性的,所以数据测试结果可能和其他环境下有所差异,纯属正常,本文仅供参考,请根据你自己的想法去合理选择。若有更好的方法,欢迎大家一起交流共同改进!谢谢!本篇文章著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。