微服務之間通過RabbitMQ通訊
微服務之間是相互獨立的,不像單個工程一樣各個模組之間可以直接通過方法調用實現通訊,相互獨立的服務直接一般的通訊方式是使用 HTTP協議
、rpc協議
或者使用訊息中介軟體如RabbitMQ``Kafka
等
image
在這篇文章 使用Golang和MongoDB構建微服務 已經實現了一個微服務的應用,在文章中已經實現了各個服務直接的通訊,是使用的 HTTP
的形式 ,那各個服務之間如何通過 RabbitMQ
進行訊息通訊呢,我們現在要實現一個功能,就是一個使用者預訂電影票的介面,需要服務 User Service(port 8000) 和 服務 Booking Service(port 8003)之間通訊,使用者預訂之後,把預訂資訊寫入到 booking的資料庫中
安裝 RabbitMQ
安裝 RabbitMQ
之前需要先安裝 Erlang 的環境 ,然後下載安裝RabbitMQ ,請選擇對應的版本,安裝完成之後,RabbitMQ在Windows上是作為一個服務在後台運行,關於 RabbitMQ
的介面如何使用,請參考官網的 教程,有各個主流語言的實現我們使用的是Go
版本,請下載對應的實現介面 go get github.com/streadway/amqp
對RabbitMQ
的介面做一下簡單的封裝
messaging/message.go
type IMessageClient interface { ConnectToBroker(connectionStr string) error PublishToQueue(data []byte, queueName string) error SubscribeToQueue(queueName string, handlerFunc func(amqp.Delivery)) error Close()}type MessageClient struct { conn *amqp.Connection}
func (m *MessageClient) ConnectToBroker(connectionStr string) error { if connectionStr == "" { panic("the connection str mustnt be null") } var err error m.conn, err = amqp.Dial(connectionStr) return err}
func (m *MessageClient) PublishToQueue(body []byte, queueName string) error { if m.conn == nil { panic("before publish you must connect the RabbitMQ first") } ch, err := m.conn.Channel() defer ch.Close() failOnError(err, "Failed to open a channel") q, err := ch.QueueDeclare( queueName, false, false, false, false, nil, ) failOnError(err, "Failed to declare a queue") err = ch.Publish( "", q.Name, false, false, amqp.Publishing{ ContentType: "application/json", Body: body, }, ) failOnError(err, "Failed to publish a message") return nil}
func (m *MessageClient) SubscribeToQueue(queueName string, handlerFunc func(amqp.Delivery)) error { ch, err := m.conn.Channel() //defer ch.Close() failOnError(err, "Failed to open a channel") q, err := ch.QueueDeclare( queueName, false, false, false, false, nil, ) failOnError(err, "Failed to declare a queue") msgs, err := ch.Consume( q.Name, "", true, false, false, false, nil, ) failOnError(err, "Failed to register a consumer") go consumeLoop(msgs, handlerFunc) return nil}
實現通訊
在 User Service中定義一個新的POST
介面 /user/{name}/booking
,實現使用者的預訂功能,預訂之後,通過RabbitMQ
發布一個訊息給
Booking Service,Booking Service接收到訊息之後,做相應的處理(寫入資料庫)
User Service
users/controllers/user.go
var client messaging.IMessageClientfunc init() { client = &messaging.MessageClient{} err := client.ConnectToBroker("amqp://guest:guest@localhost:5672/") if err != nil { fmt.Println("connect to rabbitmq error", err) }}
routes.go
register("POST", "/user/{name}/booking", controllers.NewBooking, nil)
users/controllers/user.go
func NewBooking(w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) user_name := params["name"] defer r.Body.Close() var bookings models.Booking body, _ := ioutil.ReadAll(r.Body) err := json.Unmarshal(body, &bookings) if err != nil { fmt.Println("the format body error ", err) } fmt.Println("user name:", user_name, bookings) go notifyMsg(body)}
func notifyMsg(body []byte) { err := client.PublishToQueue(body, "new_booking") if err != nil { fmt.Println("Failed to publis message", err) }}
Booking Service
var client messaging.IMessageClientfunc initMessage() { client = &messaging.MessageClient{} err := client.ConnectToBroker("amqp://guest:guest@localhost:5672/") if err != nil { fmt.Println("Failed to connect to RabbitMQ", err) } err = client.SubscribeToQueue("new_booking", getBooking) if err != nil { fmt.Println("Failed to comsuer the msg", err) }}
在 web服務之前啟動
func main() { initMessage() r := routes.NewRouter() http.ListenAndServe(":8003", r)}
func getBooking(delivery amqp.Delivery) { var booking models.Booking json.Unmarshal(delivery.Body, &booking) booking.Id = bson.NewObjectId().Hex() dao.Insert("Booking", "BookModel", booking) fmt.Println("the booking msg", booking)}
驗證,需要啟動 User Service 和 Booking Service
使用 Postman
發送對應的資料
post 127.0.0.1:8000/user/kevin_woo/booking{ "name":"kevin_woo", "books":[ { "date":"20180727", "movies":["5b4c45d49d5e3e33c4a5b97a"] }, { "date":"20180810", "movies":["5b4c45ea9d5e3e33c4a5b97b"] } ]}
可以看到資料庫已經有了一條新的預訂資訊
說明,我這裡POST的資料就是booking資料庫中的結構,實際情況需要對資料進行封裝處理,在POST資料時,沒有對資料進行驗證,
在實際開發過程中需要對各個資料做相應的驗證,這裡主要是看一下 RabbitMQ的訊息傳遞處理的過程
源碼 Github