微服務之間通過RabbitMQ通訊

CoderMiner發表於2018-07-16

微服務之間通過RabbitMQ通訊

微服務之間是相互獨立的,不像單個工程一樣各個模組之間可以直接通過方法呼叫實現通訊,相互獨立的服務直接一般的通訊方式是使用 HTTP協議rpc協議或者使用訊息中介軟體如RabbitMQ``Kafka

微服務之間通過RabbitMQ通訊

在這篇文章 使用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

  • 初始化 MessageClient

users/controllers/user.go

var client messaging.IMessageClient

func 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

  • 初始化MessageClient
var client messaging.IMessageClient

func 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 ServiceBooking 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

相關文章