在Go和Python之間通過ActiveMQ通訊

猛禽大叔發表於2018-07-17

踩到的坑

首先說一句老話:Windows就是善於製造別的作業系統中不存在的問題。

本來我這裡有一個提供檔案服務的東西是基於Python開發的,在Linux上執行的還行。但是最近因為需要部署到Windows上,就碰到了一個在別的平臺不存在的問題:常用的WSGI伺服器都不支援Windows或者支援得不好。而如果不用WSGI伺服器來跑的話,併發處理又不行。

找了半天也沒有找到什麼像樣的解決辦法,只好用Go改寫了,但是因為還是有很多後續處理不方便用Go改寫(因為沒有相關的庫可用),所以折衷方案是Web端用Go,後端繼續用Python,二者之間通過訊息佇列解耦。因為正好伺服器上有一個給其它Java應用使用的ActiveMQ,就拿來用了。

本來改寫還挺順利的,但是結果踩到一個坑:Go的Stomp庫文件不夠詳細,所以拿它的例子程式碼寫了個DEMO結果調不通。搜了半天也不知道問題在哪裡。只能自己看原始碼研究,還好最後還是搞通了。

一個DEMO

下面的DEMO裡,Go版和Python分別訂閱了一個佇列,然後Go向Python的佇列發一條訊息,Python收到後再向Go的佇列傳送一條訊息。

Golang

package main


import (
    "time"
    "github.com/go-stomp/stomp"
)


var config map[string]interface{}
var stop = make(chan bool)


var options []func(*stomp.Conn) error = []func(*stomp.Conn) error{
    stomp.ConnOpt.Host("demo"),
    stomp.ConnOpt.HeartBeat(360*time.Second, 360*time.Second),
    stomp.ConnOpt.HeartBeatError(360*time.Second),
}

func recvMessages(subscribed chan bool) {
    conn, err := stomp.Dial("tcp", config["AMQ_SERVER"].(string), options...)

    if err != nil {
        println("Cannot connect to server: ", err.Error())
        subscribed <- false
        return
    }

    defer func() {
        conn.Disconnect()
        println("Go disconnected")
        stop <- true
    }()

    sub, err := conn.Subscribe(config["AMQ_DEST_RESP"].(string), stomp.AckAuto)
    if err != nil {
        println("Cannot subscribe: ", config["AMQ_DEST_RESP"].(string), err.Error())
        subscribed <- false
        return
    }
    println("Go subscribed!")
    subscribed <- true
    close(subscribed)

    msg := <- sub.C
    println("Go received: ", string(msg.Body))
}

func sendMessage(content string) {
    conn, err := stomp.Dial("tcp", config["AMQ_SERVER"].(string), options...)
    if err != nil {
        println("Cannot connect to server: ", err.Error())
        return
    }

    defer func() {
        conn.Disconnect()
        stop <- true
    }()

    err = conn.Send(config["AMQ_DEST_REQ"].(string), "text/plain", []byte(content))
    if err != nil {
        println("Send fail: ", err.Error())
        return
    }
    println("Go sent!")
}

func main() {
    config = map[string]interface{} {
        "AMQ_SERVER": "localhost:61613",
        "AMQ_DEST_REQ": "demoRequest",
        "AMQ_DEST_RESP": "demoResponse",
    }
    subscribed := make(chan bool)
    go recvMessages(subscribed)
    sub_res := <-subscribed
    if sub_res {
        println("Go Sending...")
        go sendMessage("Hello world")
    }

    <-stop
    <-stop
}
複製程式碼

Python

# -*- coding:UTF-8 -*-
import atexit
import time
import logging

import stomp


__author__ = 'raptor'

__doc__ = """

"""


logger = logging.getLogger(__name__)


class AMQListener(stomp.ConnectionListener):
    def __init__(self, amq):
        super(AMQListener, self).__init__()
        self.amq = amq

    def on_error(self, headers, message):
        logger.error('Python received an error "%s"' % message)

    def on_message(self, headers, message):
        logger.info("Python received: %s" % message)
        self.amq.send("Hello ActiveMQ")
        self.amq.term = True


class AMQConnection(object):
    HOST = "localhost"
    PORT = 61613
    DEST_REQ = "demoRequest"
    DEST_RESP = "demoResponse"

    def __init__(self):
        self.conn = stomp.Connection([(self.HOST, self.PORT)])
        self.conn.set_listener(self.DEST_REQ, AMQListener(self))
        self.conn.start()
        self.conn.connect()
        self.conn.subscribe(self.DEST_REQ, '1', 'auto')
        logger.info("Python subscribed!")
        atexit.register(self.close)
        self.term = False

    def run_forever(self):
        while not self.term:
            time.sleep(5)

    def send(self, content):
        logger.info("Python sending...")
        self.conn.send(self.DEST_RESP, body=content)
        logger.info("Python sent!")

    def close(self):
        self.conn.disconnect()
        logger.info("Python disconnected")


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    amq = AMQConnection()
    amq.run_forever()
複製程式碼

相關文章