#: of connection failure (initialized by :attr:failover_strategy
).
cycle = None
#: Additional transport specific options,
#: passed on to the transport instance.
transport_options = None
#: Strategy used to select new hosts when reconnecting after connection
#: failure. One of “round-robin”, “shuffle” or any custom iterator
#: constantly yielding new URLs to try.
failover_strategy = ‘round-robin’
#: Heartbeat value, currently only supported by the py-amqp transport.
heartbeat = None
failover_strategies = failover_strategies
4.3 Channel
===============
Channel:与AMQP中概念类似,可以理解成共享一个Connection的多个轻量化连接。就是真正的连接。
-
Connection 是 AMQP 对 连接的封装;
-
Channel 是 AMQP 对 MQ 的操作的封装;
Channel 可以认为是 redis 操作和连接的封装。每个 Channel 都可以与 redis 建立一个连接,在此连接之上对 redis 进行操作,每个连接都有一个 socket,每个 socket 都有一个 file,从这个 file 可以进行 poll。
4.3.1 定义
============
简化版定义如下:
class Channel(virtual.Channel):
“”“Redis Channel.”“”
QoS = QoS
_client = None
_subclient = None
keyprefix_queue = ‘{p}_kombu.binding.%s’.format(p=KEY_PREFIX)
keyprefix_fanout = ‘/{db}.’
sep = ‘\x06\x16’
_fanout_queues = {}
unacked_key = ‘{p}unacked’.format(p=KEY_PREFIX)
unacked_index_key = ‘{p}unacked_index’.format(p=KEY_PREFIX)
unacked_mutex_key = ‘{p}unacked_mutex’.format(p=KEY_PREFIX)
unacked_mutex_expire = 300 # 5 minutes
unacked_restore_limit = None
visibility_timeout = 3600 # 1 hour
max_connections = 10
queue_order_strategy = ‘round_robin’
_async_pool = None
_pool = None
from_transport_options = (
virtual.Channel.from_transport_options +
(‘sep’,
‘ack_emulation’,
‘unacked_key’,
…
‘max_connections’,
‘health_check_interval’,
‘retry_on_timeout’,
‘priority_steps’) # <-- do not add comma here!
)
connection_class = redis.Connection if redis else None
self.handlers = {‘BRPOP’: self._brpop_read, ‘LISTEN’: self._receive}
4.3.2 redis消息回调函数
=====================
关于上面成员变量,这里需要说明的是
handlers = {dict: 2}
{
‘BRPOP’: <bound method Channel._brpop_read of <kombu.transport.redis.Channel object at 0x7fe61aa88cc0>>,
‘LISTEN’: <bound method Channel._receive of <kombu.transport.redis.Channel object at 0x7fe61aa88cc0>>
}
这是redis有消息时的回调函数,即:
-
BPROP 有消息时候,调用 Channel._brpop_read;
-
LISTEN 有消息时候,调用 Channel._receive;
大约如下:
±--------------------------------------------------------------------------------------------------------------------------------------+
| ±-------------+ 6 parse_response |
| ±-> | Linux Kernel | ±–+ |
| | ±-------------+ | |
| | | |
| | | event |
| | 1 | |
| | | 2 |
| | | |
±------±–+ socket + | |
| redis | <------------> port ±-> fd ±–>+ v |
| | | ±-----±-------+ |
| | socket | | Hub | |
| | <------------> port ±-> fd ±–>----------> | | |
| port=6379 | | | | |
| | socket | | readers ±----> Transport.on_readable |
| | <------------> port ±-> fd ±–>+ | | + |
±----------+ ±--------------+ | |
| |
3 | |
±---------------------------------------------------------------------------------------+ |
| v
| _receive_callback
| 5 ±------------+ ±----------+
±-----------±-----+ ±------------------------+ ‘BRPOP’ = Channel._brpop_read ±----> | Channel | ±-----------------> | Consumer |
| Transport | | MultiChannelPoller | ±-----> channel . handlers ‘LISTEN’ = Channel._receive ±------------+ ±–±------+
| | | | | 8 |
| | on_readable(fileno) | | | ^ |
| cycle ±--------------------> | _fd_to_chan ±---------------> channel . handlers ‘BRPOP’ = Channel._brpop_read | |
| | 4 | | | ‘LISTEN’ = Channel._receive | |
| _callbacks[queue]| | | | | on_m | 9
| + | ±------------------------+ ±-----> channel . handlers ‘BRPOP’ = Channel._brpop_read | |
±------------------+ ‘LISTEN’ = Channel._receive | |
| | v
| 7 _callback |
±----------------------------------------------------------------------------------------------------------------------------------------+ User Function
手机如图:
4.4 Transport
=================
Transport:真实的 MQ 连接,也是真正连接到 MQ(redis/rabbitmq) 的实例。就是存储和发送消息的实体,用来区分底层消息队列是用amqp、Redis还是其它实现的。
我们顺着上文理一下:
-
Connection 是 AMQP 对 连接的封装;
-
Channel 是 AMQP 对 MQ 的操作的封装;
-
那么两者的关系就是对 MQ 的操作必然离不开连接,但是 Kombu 并不直接让 Channel 使用 Connection 来发送/接受请求,而是引入了一个新的抽象 Transport,Transport 负责具体的 MQ 的操作,也就是说 Channel 的操作都会落到 Transport 上执行;
在Kombu 体系中,用 transport 对所有的 broker 进行了抽象,为不同的 broker 提供了一致的解决方案。通过Kombu,开发者可以根据实际需求灵活的选择或更换broker。
Transport负责具体操作,但是 很多操作移交给 loop 与 MultiChannelPoller 进行。
其主要成员变量为:
-
本transport的驱动类型,名字;
-
对应的 Channel;
-
cycle:MultiChannelPoller,具体下文会提到;
其中重点是MultiChannelPoller。一个Connection有一个Transport, 一个Transport有一个MultiChannelPoller,对poll操作都是由MultiChannelPoller完成,redis操作由channel完成。
定义如下:
class Transport(virtual.Transport):
“”“Redis Transport.”“”
Channel = Channel
polling_interval = None # disable sleep between unsuccessful polls.
default_port = DEFAULT_PORT
driver_type = ‘redis’
driver_name = ‘redis’
implements = virtual.Transport.implements.extend(
asynchronous=True,
exchange_type=frozenset([‘direct’, ‘topic’, ‘fanout’])
)
def init(self, *args, **kwargs):
super().init(*args, **kwargs)
All channels share the same poller.
self.cycle = MultiChannelPoller()
4.5 MultiChannelPoller
==========================
MultiChannelPoller 定义如下,可以理解为 执行 engine,主要作用是:
-
收集 channel;
-
建立 socks fd 到 channel 的映射;
-
建立 channel 到 socks fd 的映射;
-
使用 poll;
或者从逻辑上这么理解,MultiChannelPoller 就是:
-
把 Channel 对应的 socket 同 poll 联系起来,一个 socket 在 linux 系统中就是一个file,就可以进行 poll 操作;
-
把 poll 对应的 fd 添加到 MultiChannelPoller 这里,这样 MultiChannelPoller 就可以 打通 Channel —> socket —> poll —> fd —> 读取 redis 这条通路了,就是如果 redis 有数据来了,MultiChannelPoller 就马上通过 poll 得到通知,就去 redis 读取;
具体定义如下:
class MultiChannelPoller:
“”“Async I/O poller for Redis transport.”“”
eventflags = READ | ERR
def init(self):
active channels
self._channels = set()
file descriptor -> channel map.
self._fd_to_chan = {}
channel -> socket map
self._chan_to_sock = {}
poll implementation (epoll/kqueue/select)
self.poller = poll()
one-shot callbacks called after reading from socket.
self.after_read = set()
4.6 Consumer
================
Consumer 是消息接收者。Consumer & 相关组件 的作用主要如下:
-
Exchange:MQ 路由,消息发送者将消息发至 Exchange,Exchange 负责将消息分发至队列。
-
Queue:对应的队列抽象,存储着即将被应用消费掉的消息,Exchange 负责将消息分发 Queue,消费者从Queue 接收消息;
-
Consumers 是接受消息的抽象类,consumer 需要声明一个 queue,并将 queue 与指定的 exchange 绑定,然后从 queue 里面接收消息。就是说,从用户角度,知道了一个 exchange 就可以从中读取消息,而具体这个消息就是从 queue 中读取的。
在具体 Consumer 的实现中,它把 queue 与 channel 联系起来。queue 里面有一个 channel,用来访问redis,也有 Exchange,知道访问具体 redis 哪个key(就是queue对应的那个key)。
Consumer 消费消息是通过 Queue 来消费,然后 Queue 又转嫁给 Channel。
所以服务端的逻辑大致为:
-
建立连接;
-
创建Exchange ;
-
创建Queue,并将Exchange与Queue绑定,Queue的名称为routing_key ;
-
创建Consumer对Queue监听;
Consumer 定义如下:
class Consumer:
“”"Message consumer.
Arguments:
channel (kombu.Connection, ChannelT): see :attr:channel
.
queues (Sequence[kombu.Queue]): see :attr:queues
.
no_ack (bool): see :attr:no_ack
.
auto_declare (bool): see :attr:auto_declare
callbacks (Sequence[Callable]): see :attr:callbacks
.
on_message (Callable): See :attr:on_message
on_decode_error (Callable): see :attr:on_decode_error
.
prefetch_count (int): see :attr:prefetch_count
.
“”"
ContentDisallowed = ContentDisallowed
#: The connection/channel to use for this consumer.
channel = None
#: A single :class:~kombu.Queue
, or a list of queues to
#: consume from.
queues = None
#: Flag for automatic message acknowledgment.
no_ack = None
#: By default all entities will be declared at instantiation, if you
#: want to handle this manually you can set this to :const:False
.
auto_declare = True
#: List of callbacks called in order when a message is received.
callbacks = None
#: Optional function called whenever a message is received.
on_message = None
#: Callback called when a message can’t be decoded.
on_decode_error = None
#: List of accepted content-types.
accept = None
#: Initial prefetch count
prefetch_count = None
#: Mapping of queues we consume from.
_queues = None
_tags = count(1) # global
此时总体逻辑如下图:
±---------------------+ ±------------------+
| Consumer | | Channel |
| | | | ±----------------------------------------------------------+
| | | client ±------------> | Redis<ConnectionPool<Connection<host=localhost,port=6379> |
| channel ±-------------------> | | ±----------------------------------------------------------+
| | | pool |
| | ±--------> | | <------------------------------------------------------------+
| queues | | | | |
| | | ±—> | connection ±--------------+ |
| | | | | | | | |
±---------------------+ | | ±------------------+ | |
| | | v |
| | | ±------------------+ ±–±----------------+ ±-------------------+ |
| | | | Connection | | redis.Transport | | MultiChannelPoller | |
| | | | | | | | | |
| | | | | | | | _channels ±-------+
| | | | | | cycle ±-----------> | _fd_to_chan |
| | | | transport ±--------> | | | _chan_to_sock |
| ±------->+ | | | | | ±-----+ poller |
| | | ±------------------+ ±--------------------+ | | after_read |
| | | | | |
| | | | ±-------------------+
| | | ±-----------------+ ±--------------+
| | | | Hub | |
| | | | | v
| | | | | ±-----±-----+
| | | | poller ±--------------> | _poll |
| | | | | | | ±------+
| | | | | | _poller±--------> | poll |
v | | ±-----------------+ | | ±------+
| | ±------------+
±------------------+ | ±---------------+
| Queue | | | | Exchange |
| _chann+l | ±—+ | |
| | | |
| exchange ±---------------> | channel |
| | | |
| | | |
±------------------+ ±---------------+
手机如下:
现在我们知道:
4.7 Producer
================
Producer 是消息发送者。Producer中,主要变量是:
-
_channel :就是channel;
-
exchange :exchange;
class Producer:
“”"Message Producer.
Arguments:
channel (kombu.Connection, ChannelT): Connection or channel.
exchange (kombu.entity.Exchange, str): Optional default exchange.
routing_key (str): Optional default routing key.
“”"
#: Default exchange
exchange = None
#: Default routing key.
routing_key = ‘’
#: Default serializer to use. Default is JSON.
serializer = None
#: Default compression method. Disabled by default.
compression = None
#: By default, if a defualt exchange is set,
#: that exchange will be declare when publishing a message.
auto_declare = True
#: Basic return callback.
on_return = None
#: Set if channel argument was a Connection instance (using
#: default_channel).
connection = None
逻辑如图:
±---------------------+ ±------------------+
| Producer | | Channel |
| | | | ±----------------------------------------------------------+
| | | client ±------------> | Redis<ConnectionPool<Connection<host=localhost,port=6379> |
| channel ±-----------------> | | ±----------------------------------------------------------+
| | | pool |
| exchange | ±--------> | | <------------------------------------------------------------+
| | | | | |
| connection | | ±—> | connection ±--------------+ |
| + | | | | | | |
±-±------------------+ | | ±------------------+ | |
| | | | v |
| | | | ±------------------+ ±–±----------------+ ±-------------------+ |
| | | | | Connection | | redis.Transport | | MultiChannelPoller | |
| ±---------------------> | | | | | | |
| | | | | | | | _channels ±-------+
| | | | | | cycle ±-----------> | _fd_to_chan |
| | | | transport ±--------> | | | _chan_to_sock |
| ±------->+ | | | | | ±-----+ poller |
| | | ±------------------+ ±--------------------+ | | after_read |
| | | | | |
| | | | ±-------------------+
| | | ±-----------------+ ±--------------+
| | | | Hub | |
| | | | | v
| | | | | ±-----±-----+
| | | | poller ±--------------> | _poll |
| publish | | | | | | ±------+
±-------------------------------+ | | | _poller±--------> | poll |
| | | ±-----------------+ | | ±------+
| | | ±------------+
±------------------+ | ±----> ±---------------+
| Queue | | | | Exchange |
| _channel | ±--------+ | |
| | | |
| exchange ±-------------------> | channel |
| | | |
| | | |
±------------------+ ±---------------+
手机如图:
4.8 Hub
===========
用户可以通过同步方式自行读取消息,如果不想自行读取,也可以通过Hub(本身构建了一个异步消息引擎)读取。
4.8.1 自己的poller
===================
Hub 是一个eventloop,拥有自己的 poller。
前面在 MultiChannelPoller 中间提到了,MultiChannelPoller 会建立了自己内部的 poller。但是实际上在注册时候,Transport 会使用 hub 的 poller,而非 MultiChannelPoller 内部的 poller。
4.8.2 Connection
====================
Connection注册到Hub,一个Connection对应一个Hub。
hub = Hub()
conn = Connection(‘redis://localhost:6379’)
conn.register_with_event_loop(hub)
4.8.3 联系
============
在注册过程中,Hub 把自己内部的 poller 配置在 Transport 之中。这样就通过 transport 内部的 MultiChannelPoller 可以把 Hub . poller 和 Channel 对应的 socket 同poll联系起来,一个 socket 在 linux 系统中就是一个file,就可以进行 poll 操作;
因而,如前面所述,这样 MultiChannelPoller 就可以 打通 Channel —> socket —> poll —> fd —> 读取 redis 这条通路了,就是如果 redis 有数据来了,MultiChannelPoller 就马上通过 poll 得到通知,就去 redis 读取。
def register_with_event_loop(self, loop):
self.transport.register_with_event_loop(self.connection, loop)
4.8.4 定义
============
Hub定义如下:
class Hub:
“”"Event loop object.
“”"
def init(self, timer=None):
self.timer = timer if timer is not None else Timer()
self.readers = {}
self.writers = {}
self.on_tick = set()
self.on_close = set()
self._ready = set()
self._create_poller()
@property
def poller(self):
if not self._poller:
self._create_poller()
return self._poller
def _create_poller(self):
self._poller = poll()
self._register_fd = self._poller.register
self._unregister_fd = self._poller.unregister
def add(self, fd, callback, flags, args=(), consolidate=False):
fd = fileno(fd)
try:
self.poller.register(fd, flags)
except ValueError:
self._remove_from_loop(fd)
raise
else:
dest = self.readers if flags & READ else self.writers
if consolidate:
self.consolidate.add(fd)
dest[fd] = None
else:
dest[fd] = callback, args
def run_forever(self):
self._running = True
try:
while 1:
try:
self.run_once()
except Stop:
break
finally:
self._running = False
def run_once(self):
try:
next(self.loop)
except StopIteration:
self._loop = None
def create_loop(self, …):
readers, writers = self.readers, self.writers
poll = self.poller.poll
while 1:
for fd, event in events or ():
cb, cbargs = readers[fd]
if isinstance(cb, generator):
next(cb)
cb(*cbargs)
else:
no sockets yet, startup is probably not done.
sleep(min(poll_timeout, 0.1))
yield
0x05 总结
===========
我们通过文字和图例来总结下本文。
5.1 逻辑
==========
-
Message:消息,发送和消费的主体,其实就是我们所谓的一条条消息;
-
Connection是AMQP对消息队列连接的封装抽象,那么两者的关系就是:对MQ的操作必然离不开连接。
-
Channel是AMQP对MQ的操作的封装,可以理解成共享一个Connection的多个轻量化连接。Channel将Consumer标签,Consumer要消费的队列,以及标签与队列的映射关系都记录下来,等待循环调用。还通过Transport将队列与回调函数列表的映射关系记录下来。Kombu对所有需要监听的队列_active_queues都查询一遍,直到查询完毕或者遇到一个可以使用的Queue,然后就获取消息,回调此队列对应的callback。Channel初始化的过程就是连接的过程。
-
Kombu并不直接让Channel使用Connection来发送/接受请求,而是引入了一个新的抽象Transport,Transport负责具体的MQ的操作,也就是说Channel的操作都会落到Transport上执行。是以Transport为中心,把Channel代表的真实redis与Hub其中的poll联系起来。
-
Queue:消息队列,消息内容的载体,存储着即将被应用消费掉的消息。Exchange 负责将消息分发 Queue,消费者从 Queue 接收消息;
-
Exchange:交换机,消息发送者将消息发至 Exchange,Exchange 负责将消息分发至 Queue;消息发送是交给 Exchange 来做的,但Exchange只是将发送的 routing_key 转化为 queue 的名字,这样发送就知道应该发给哪个queue;实际发送还是得 channel 来干活,即从 exchange 得到 routing_key —> queue 的规则,然后再依据 routing_key 得到 queue。就知道 Consumer 和 Producer 需要依据哪个 queue 交换消息。每个不同的 Transport 都有对应的 Channel;生产者将消息发送到Exchange,Exchange通过匹配BindingKey和消息中的RouteKey来将消息路由到队列,最后队列将消息投递给消费者。
-
Producers: 发送消息的抽象类,Producer 包含了很多东西,有 Exchange、routing_key 和 channel 等等;
-
Consumers:接受消息的抽象类,consumer需要声明一个queue,并将queue与指定的exchange绑定,然后从queue里面接收消息;Consumer绑定了消息的处理函数,每一个Consumer初始化的时候都是和Channel绑定的,也就是说我们Consumer包含了Queue也就和Connection关联起来了。Consumer消费消息是通过Queue来消费,然后Queue又转嫁给Channel,再转给connection。
-
用户可以通过同步方式自行读取消息,如果不想自行读取,也可以通过Hub(本身构建了一个异步消息引擎)读取。
-
Hub是一个eventloop,Connection注册到Hub,一个Connection对应一个Hub。Hub 把自己内部的 poller 配置在 Transport 之中。这样就通过 transport 内部的 MultiChannelPoller 可以把 Hub . poller 和 Channel 对应的 socket 同poll联系起来,一个 socket 在 linux 系统中就是一个file,就可以进行 poll 操作;
-
MultiChannelPoller是Connection 和 Hub的枢纽,它负责找出哪个 Channel 是可用的,但是这些 Channel 都是来自同一个 Connection。
5.2 示例图
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

最后
光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性
Java面试宝典2021版
最常见Java面试题解析(2021最新版)
2021企业Java面试题精选
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-OlUSxtOM-1713518768360)]
[外链图片转存中…(img-02ytljwe-1713518768361)]
[外链图片转存中…(img-ZMjT7SE9-1713518768362)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

最后
光给面试题不给答案不是我的风格。这里面的面试题也只是凤毛麟角,还有答案的话会极大的增加文章的篇幅,减少文章的可读性
Java面试宝典2021版
[外链图片转存中…(img-owZzGC5Y-1713518768363)]
[外链图片转存中…(img-0ZanWFax-1713518768365)]
最常见Java面试题解析(2021最新版)
[外链图片转存中…(img-fj965tMS-1713518768366)]
[外链图片转存中…(img-ZdKuu9LF-1713518768367)]
2021企业Java面试题精选
[外链图片转存中…(img-L2DGaUL2-1713518768368)]
[外链图片转存中…(img-zO2CQBjy-1713518768369)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!