用 ZooKeeper 做队列很容易:顺序节点就是天然的排队号。但消费者崩溃时消息去哪了?这篇用 kazoo 的两种队列做对比实验:普通 Queue 丢了一条,LockingQueue 没丢但会重复,以及这个队列到底能跑多快。
/q 子节点: ['entry-010-0000000005', 'entry-100-0000000000', 'entry-100-0000000001', 'entry-100-0000000002', 'entry-100-0000000003', 'entry-100-0000000004'] 取出顺序(priority 小的先出,同优先级先进先出): ['urgent', 'job-0', 'job-1', 'job-2', 'job-3', 'job-4'] 队列空时 get() 返回: None
和顺序节点那篇的结论一致:序号由服务端分配,所以多个生产者同时入队也不会冲突。
队列最终为空;200 条中被取出 200,处理完成 199,被取出但没有完成(被杀的消费者手里那条)= ['m020']
出队是「读最小节点 + 删除」一步完成,消息一旦出队就只存在于消费者内存里。消费者在处理中崩溃,没有任何地方还留着它。能接受丢失(比如统计、缓存刷新通知)才用这种。
30 条:被取出的消息数 30,被确认 30;被取出了 2 次的:['j12'];取出却没确认的:[]
j12 ['-0.1s C0 GOT', '+3.8s C1 GOT', '+4.1s C1 ACK']消息先被「锁」住(一个临时节点,属于消费者的会话),处理完调用 consume() 才真正删除。消费者崩溃后会话过期,锁节点消失,消息重新可见。结果是不丢但可能重复:如果消费者处理完了、还没来得及 consume 就崩溃,这条会被处理第二次。
Queue:入队 305 条/s,出队 308 条/s;LockingQueue:入队 295 条/s,出队+确认 143 条/s /bq 里的单条消息大小限制:ZooKeeper 默认每个 znode ≤ 1 MiB(见第 1 篇);队列里所有未消费的消息都在内存与快照里(第 10 篇)
队列里未消费的每条消息都是内存里的 znode、也在快照里(见DataTree),积压成千上万条会拖慢 get_children。堆积量大、吞吐高的场景请用 Kafka / RabbitMQ 之类,ZooKeeper 队列适合少量、低频、需要强一致排序的协调任务。