Watermill GoChannel消息乱序问题求助(附Go语言测试代码)
问题:Watermill消息流转系统偶现FIFO乱序的竞态条件问题
我使用Watermill开发消息流转系统,消息预期依次经过service1、service2和最终服务。原本依赖GoChannel的FIFO特性,同时用线程安全切片辅助控制消息顺序,但多次运行后出现偶现的竞态条件问题:发送消息A、B时,最终服务中B会先于A到达。触发问题时会输出类似Slice value 68ad9d74-c0eb-476f-9cc0-5da98d947b61 value in message f01fff7d-1fb6-45a4-bda6-07e021511d3f的提示信息。
复现该问题的测试代码如下:
/* This application is a test of Watermill, a Go library for working efficiently with message streams. Sending and recieving messages from a channel. */ package main import ( "context" "fmt" "log" "os" "sync" "time" "github.com/ThreeDotsLabs/watermill" "github.com/ThreeDotsLabs/watermill/message" "github.com/ThreeDotsLabs/watermill/pubsub/gochannel" ) var pubSub1 *gochannel.GoChannel var safeSlice *SafeSlice // Safe Slice struct just for control of the messages type SafeSlice struct { mu sync.Mutex slice []string } func (s *SafeSlice) Append(value string) { s.mu.Lock() defer s.mu.Unlock() s.slice = append(s.slice, value) } func (s *SafeSlice) Get(index int) (string, bool) { s.mu.Lock() defer s.mu.Unlock() if index < 0 || index >= len(s.slice) { return "Index out of scope", false } return s.slice[index], true } func (s *SafeSlice) Remove(index int) bool { s.mu.Lock() defer s.mu.Unlock() if index < 0 || index >= len(s.slice) { return false } s.slice = append(s.slice[:index], s.slice[index+1:]...) return true } // service1 function is a handler for the "service-1" service. It appends the message UUID to the // safe slice and publishes the message to the "service-2-input" channel. func service1(msg *message.Message) error { safeSlice.Append(msg.UUID) err := pubSub1.Publish("service-2-input", msg) if err != nil { panic(err) } return nil } // service2 function is a handler for the "service-2" service. It receives a message, performs // some logic, and returns a slice of messages. func service2(msg *message.Message) ([]*message.Message, error) { fmt.Printf("Message in service 2 %v\n", msg) // Add some logic return message.Messages{msg}, nil } // service_last function is a handler for the "service_last" service. It compares the message // UUID with the first UUID in the safe slice and removes the first UUID if they match. func service_last(msg *message.Message) error { uuid, _ := safeSlice.Get(0) fmt.Printf("service_last %v\n", msg) if msg.UUID == uuid { fmt.Println("OK") safeSlice.Remove(0) } else { fmt.Printf("Slice value %s value in message %s\n", uuid, msg.UUID) os.Exit(0) } return nil } func main() { logger := watermill.NewStdLogger(true, true) safeSlice = &SafeSlice{} pubSub1 = gochannel.NewGoChannel(gochannel.Config{}, logger) router, err := message.NewRouter(message.RouterConfig{}, logger) if err != nil { log.Fatalf("could not create router: %v", err) } // Create handlers for each service router.AddNoPublisherHandler("service-1", "service-1-input", pubSub1, service1) router.AddHandler("service-2", "service-2-input", pubSub1, "service_last-input", pubSub1, service2) router.AddNoPublisherHandler("service_last", "service_last-input", pubSub1, service_last) // Start the router go func() { if err := router.Run(context.Background()); err != nil { log.Fatalf("could not run router: %v", err) } }() time.Sleep(1 * time.Second) for { // Publish a message to start the pipeline msg := message.NewMessage(watermill.NewUUID(), []byte{}) if err := pubSub1.Publish("service-1-input", msg); err != nil { log.Fatalf("could not publish message: %v", err) } //time.Sleep(1000 * time.Millisecond) } // Allow some time for the message to be processed select {} }
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

