消息队列服务在虎扑的应用 消息队列服务在虎扑的应用
--- 使用异步和队列解决 web 开发中的问题
•关于虎扑– 可能是国内最棒的体育社区– 旗下有虎扑 ( 篮球 ), goalhi( 足球 ), 亮乐
( 综合体育 ),HelloF1, 卡路里 ( 商城 ) 等网站– alexa 中国前百强– PV 8 千万 / 天
•关于我– ID: 轩痕– mail: [email protected]– 后端开发 , 最近正纠结于 twisted– 喜欢 python,linux
话题预览 :1 我们为什么需要消息队列 ?2 理想的消息队列需要具有哪些特性 ?3 高级消息队列的模型
我们为什么需要消息队列•虎扑微博的业务情况
– 如果一个体育明星用户有一万个人关注– 每条状态 会产生 一万条数据库 插入需求– 世纪大战– 高峰时每秒成千上万条插入任务
•遇到的问题 :• 状态丢失 • 502 错误• mysql 服务器压力很大
解决方案 :1 加入新的 mysql 服务器 , 一个库 , 一张表 , 简易队列化
表结构 id server_id sql_statement2 去掉插入速度的约束3 不在这个数据库上进行不必要的查询和修改操作
优化的结果•状态不丢了• 502 也没有了
但这样做有没有弊端呢 ?
•弊端 : – 入队速度还不理想 , 因为要写硬盘– 数据必须持久化 , 无法选择– 后端的处理线程需要一直去取数据 , 取的频率不好确定
SMS •线下约战
– 队长要通过我们的 web 系统以短信的方式通知队员约定的比赛时间 , 场地信息
– 队长发起约战请求的时候可不希望页面卡在那里半天没反应
– 但是发送短信却需要一定时间的阻塞
这里会有什么问题 ?
• 弊端 : 1 多线程时会出现一条消息被多次取的问题 2 需要线程定时到队列查询
期望的特性 : 1 消息一旦被取走 , 就不能被别的线程再获取 2 如果那些消费线程能够在有消息到来的时候再来取 , 或许是个不错的事情
Google 分析
假如
假如有这样一个服务器放在我面前 ....它有非常快的消息进出速度可以从容地处理成千上万的并发还能很容易地控制数据是否持久化有消息来的时候会主动推送给消费线程消息一旦被某消费者取走 , 其他消费者就无法再取这条消息...
这个理想中的消息队列服务器叫什么名字
1 rabbitmq 是 AMQP 的一个实现2 完全开源3 开发语言是 erlang4 非常高的可靠性
关于 erlang Erlang 是一门被设计用于编写高并发、软实时、分布式系统的语言。
AMQP•高级消息队列协议 (Advanced Message
Queuing Protocol)•完全的开放协议•给力的互操作性• AMQP 模型
AMQP 模型• Exchanges(交换机 )• Message Queues( 消息队列 )• Bindings (交换机 和 队列 的绑定 )• message( 消息 )
http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_MRG/1.1/html/Messaging_User_Guide/sect-Messaging_User_Guide-Introduction_to_RHM-The_AMQP_0_10_Model.html
(exchange)交换机的分发机制 • Direct( 直接传递 )• Fanout(广播 )• Topic( 话题的形式 ,依赖于通配符 )
Direct Exchange
Fanout Exchange
Topic Exchange
当消息都进入了队列 ,接下来让我们讲讲队列吧
•
传统的队列末端消费模式
AMQP 模式下的消息处理模式
队列本身的特性•可以被设置为持久化•队列需要与交换机绑定 , 然后从交换机接受消息 , 并且和交换机的持久性保持一致 ,不能一个持久 , 一个非持久
•如果被设置为私有的 ,只有声明它的消费者才可以从这个队列拿走消息
消息•分为两类 :
– 消息量很大 ,而且并不怎么重要的 , 可以只放在内存中 , 不做持久化 , 不开启 ack
– 消息量不大 , 很重要的 , 可以设置为持久化 ,开启 ack
AMQP 的其他实现
• OpenAMQ• Apache ActiveMQ• ZeroMQ• AMQP Infrastructure
使用客户端同 rabbitmq交流
1 支持很多语言的客户端 .c,c++,php, java, ruby,python,erlang 等
2 基于 Python 的库 . pika, txamqp,celery 等等 ..
连接1 阻塞式的连接from pika.adapters import BlockingConnection connection = BlockingConnection()
2 非阻塞式的连接import select_connection select_connection.POLLER_TYPE = 'epoll'connection = select_connection.SelectConnectio
n()
创建 队列 ,交换机 ,绑定• 在物理连接的基础上创建一个逻辑通道• channel = connection.channel()
• 由逻辑通道来声明交换机和队列• channel.exchange_declare(exchange='direct_logs', type='direct')• result = channel.queue_declare(exclusive=True)
• 创建交换机和队列的绑定• channel.queue_bind(exchange='direct_logs', queue=queue_name,• routing_key=severity)
•准备工作做好了 , 我在哪里处理消息呢 ?
按照业务进行消费
• def callback(ch, method, properties, body):• print " [x] %r:%r" % (method.routing_key, body,)• print ' 我这里可以处理一些业务 '
• 另一个绑定 :– 在队列的尾端绑定一个业务回调
• channel.basic_consume(callback,• queue=queue_name,• no_ack=True)
如果处理消息时除了差错 ,而我又不想让这条消息丢失 .
怎么办 ?
处理消息时的分支1 设置 no_ack = False
channel.basic_consume(callback,queue=queue_name, no_ack=False)
告诉兔子我处理完消息会给你反馈的2 如果消息被成功处理 , 使用
channel.basic_ack(method.delivery_tag)
3 如果处理消息发生了异常 , 在异常处理的代码里面使用channel.basic_reject(method.delivery_tag)
让消息重回队列 ,避免丢失
消息的处理速度不够快怎么办 ?
多线程
猜想 :
假如队列里面有一万条消息 , 我们开启了一百个线程进行处理 , 每个线程能分配到多少条消息 ?
•控制每个消费者最大被分配的消息量– channel.basic_qos(prefetch_count=1)
处理任务的时间过长 , 会被兔子认为执行超时 , 并把任务分给别的消费者吗 ?
消费者挂了怎么办 ? 消息还在吗 ?