跟我一起学习ZeroMQ(8):带broker的发布订阅模式:ZMQ_PUB、broker(ZMQ_XPUB和ZMQ_XSUB)、ZMQ_SUB

本文介绍了ZeroMQ中的XPub/XSub模式,该模式通过引入Broker作为中间件,解决了Pub/Sub模式下新Publisher加入时的成本问题,实现了系统的动态扩展与负载均衡。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

broker的目的

相对于XPub/XSub模式,我们很容易想到Pub/Sub模式,即订阅发布模式。当我们使用ZeroMQ创建一个包含订阅发布模式的系统时,我们通常创建一个消息的发布者,即Publisher,和若干个消息订阅者(Subscriber)。消息的发布者绑定端口,订阅者通过发布者的IP和端口连接发布者,并且注册消息主题(Topic),然后进行接收匹配主题的消息。整体结构如下图:
在这里插入图片描述
在订阅发布模式下,订阅者可以动态加入,随时连接消息的发布者,然后接收消息。但是,在这种结构中,如果有新的Publisher加入,那么所有订阅者都需要连接到这个Publisher上。如果系统中有成百上千的订阅者,每一个新的Publisher的加入都会给系统造成很大的操作成本,这显然限制了系统规模。
要解决这个问题,也很简单,就像只有一个发布者情况,所有的订阅者都只与这一个消息发布者交互,不管是Publisher内部发生什么变化,Subscriber都可以动态感知这种变化。所以很容易我们可以想到创建一个中间件来解耦Publishers和Subscribers,所有Subscriber都只与这一个中间件交互,换句话说,这个中间件从很多个Publisher那里接收消息,然后转发给Subscibers。事实上,有了这个中间件,我们可以做很多Pub/Sub模式做不了的事情,比如说对传送过程中的消息进行管理,重构,或者对系统进行负载均衡等等。我们把这个中间件称为Broker,上面说的这种模式,我们称之为XPub/XSub模式。

XPub/XSub

在XPub/XSub模式中,对Publisher来说由Pub/Sub模式中的bind操作变成了connect操作,connect的对象为Broker中的XSub端口。对Subscriber而言,和Publisher的操作一样,只不过connect的是Broker的XPub端口。在Broker中我们绑定XSub和XPub这两个端口。Proxy的作用即为中转消息,在ZMQ的API中提供了zmq.proxy方法来中转消息,其实Proxy就是一个代码块,在这个代码块中可以做任何我们想做的操作。后面会介绍一个简单的例子。从XPub/XSub这个模式中,我们可以发现,不管是Publisher还是Subscriber,它们的加入和离开都可以被系统动态发现。
在这里插入图片描述
从上图可以看出,在增加了broker(XSUB_XPUB)之后,Publish和Subscriber都可以动态的加入和离开,并且双方都是透明的,互不影响。这就达到了动态扩展和横向水平扩容的目的。

示例代码

publisher

/**
 * @file pub.cpp
 * @brief pub demo
 * @author shlian
 * @version 1.0
 * @date 2020-11-06
 */

#include <chrono>
#include <iostream>
#include <string>

#include <gflags/gflags.h>

#include <zmqpp/context.hpp>
#include <zmqpp/context_options.hpp>
#include <zmqpp/loop.hpp>
#include <zmqpp/message.hpp>
#include <zmqpp/socket_types.hpp>
#include <zmqpp/zmqpp.hpp>

#include "../include/common.h"

DEFINE_string(broker_endpoint,"tcp://127.0.0.1:15556","the broker backend endpoint that connected by pub server");
DEFINE_int32(pub_interval,1000,"publish interval,ms");
DEFINE_int32(io_thread_count,1,"the io_thread count of the zeromq context");
DEFINE_string(id,"pub","the server id");

bool pub_message(zmqpp::socket &socket);

int main(int argc, char *argv[])
{
    gflags::SetUsageMessage("Usage");
    gflags::ParseCommandLineFlags(&argc,&argv,true);

    zmqpp::context context;
    context.set(zmqpp::context_option::io_threads,FLAGS_io_thread_count);

    zmqpp::socket pub_socket(context,zmqpp::socket_type::pub);
    pub_socket.connect(FLAGS_broker_endpoint);

    zmqpp::loop looper;
    looper.add(std::chrono::milliseconds(FLAGS_pub_interval),0,std::bind(pub_message,std::ref(pub_socket)));
    looper.start();

    return 0;
}

bool pub_message(zmqpp::socket &socket)
{
    static unsigned long long index=0;

    zmqpp::message msg;
    if(index++%2==0)
    {
        msg.add("even");
    }else{
        msg.add("odd");
    }

    msg.add(FLAGS_id+":"+common::format_time());

    LOG_INFO(FLAGS_id<<" pub["<<msg.get(0)<<"],["<<msg.get(1)<<"]");

    auto res=socket.send(msg);
    return res;
}

broker

/**
 * @file broker.cpp
 * @brief broker demo
 * @author shlian
 * @version 1.0
 * @date 2020-11-06
 */
#include <gflags/gflags.h>

#include <zmqpp/context.hpp>
#include <zmqpp/context_options.hpp>
#include <zmqpp/loop.hpp>
#include <zmqpp/socket_options.hpp>
#include <zmqpp/socket_types.hpp>
#include <zmqpp/zmqpp.hpp>

#include "../include/common.h"

DEFINE_string(front_endpoint,"tcp://*:15555","the endpoint of the broker that connected by sub client");
DEFINE_string(backend_endpoint,"tcp://*:15556","the endpoint of the broker that connected by pub server");
DEFINE_int32(io_thread_count,1,"the io_thread count of the zeromq context");

bool front_proxy(zmqpp::socket &xpub_socket,zmqpp::socket &xsub_scoket);
bool backend_proxy(zmqpp::socket &xsub_scoket,zmqpp::socket &xpub_socket);

int main(int argc, char *argv[])
{
    gflags::SetUsageMessage("Usage");
    gflags::ParseCommandLineFlags(&argc,&argv,true);

    zmqpp::context context;
    context.set(zmqpp::context_option::io_threads,FLAGS_io_thread_count);

    //bind front endpoint
    zmqpp::socket xpub_socket(context,zmqpp::socket_type::xpub);
    xpub_socket.set(zmqpp::socket_option::xpub_verbose,1);
    xpub_socket.bind(FLAGS_front_endpoint);

    //bind backend endpoint
    zmqpp::socket xsub_socket(context,zmqpp::socket_type::xsub);
    xsub_socket.bind(FLAGS_backend_endpoint);
    xsub_socket.send(std::string(1,0x01));

    //start event loop
    zmqpp::loop looper;
    looper.add(xsub_socket,std::bind(backend_proxy,std::ref(xsub_socket),std::ref(xpub_socket)));
    looper.add(xpub_socket,std::bind(front_proxy,std::ref(xpub_socket),std::ref(xsub_socket)));

    looper.start();

    return 0;
}

unsigned long long forward_topic=0;
bool front_proxy(zmqpp::socket &xpub_socket,zmqpp::socket &xsub_socket)
{
    zmqpp::message msg;

    bool res=xpub_socket.receive(msg);
    if(res)
    {
        ++forward_topic;
        //std::string topic=msg.get(0);//must manage the topics and process subscribe and unsubscribe topic,because socket receives only topic part and do not known if subscribe or unsubscribe

        LOG_INFO("handle subscribe topic:["<<msg.get(0)<<"],parts="<<msg.parts());
        std::string topic(1,0x01);
        topic.append(msg.get(0));
        res=xsub_socket.send(topic);
    }
    return res;
}

unsigned long long forward_data_msg=0;
bool backend_proxy(zmqpp::socket &xsub_socket,zmqpp::socket &xpub_socket)
{
    zmqpp::message msg;

    bool res=xsub_socket.receive(msg);
    if(res)
    {
        res=xpub_socket.send(msg);
        if((res)&&(forward_data_msg++%100==0))
        {
            LOG_INFO("forward:"<<forward_data_msg<<" data messages");
        }
    }
    return res;
}

subscriber

/**
 * @file sub.cpp
 * @brief sub demo
 * @author shlian
 * @version 1.0
 * @date 2020-11-06
 */

#include <gflags/gflags.h>

#include <zmqpp/context.hpp>
#include <zmqpp/context_options.hpp>
#include <zmqpp/socket.hpp>
#include <zmqpp/socket_types.hpp>
#include <zmqpp/zmqpp.hpp>

#include "../include/common.h"

DEFINE_string(broker_endpoint,"tcp://127.0.0.1:15555","the broker endpoint that connected by sub client");
DEFINE_string(topic,"","the subscribing topics");
DEFINE_int32(io_thread_count,1,"the io_thread count of the zeromq context");

bool handle_message(zmqpp::socket &socket);

int main(int argc, char *argv[])
{
    gflags::SetUsageMessage("Usage");
    gflags::ParseCommandLineFlags(&argc,&argv,true);

    zmqpp::context context;
    context.set(zmqpp::context_option::io_threads,FLAGS_io_thread_count);

    zmqpp::socket sub_socket(context,zmqpp::socket_type::sub);
    sub_socket.connect(FLAGS_broker_endpoint);
    sub_socket.subscribe(FLAGS_topic);
    LOG_INFO("subscribe:"<<FLAGS_topic);

    zmqpp::loop looper;
    looper.add(sub_socket,std::bind(handle_message,std::ref(sub_socket)));
    looper.start();
    
    return 0;
}

bool handle_message(zmqpp::socket &socket)
{
    zmqpp::message msg;

    auto res=socket.receive(msg);
    if(res)
    {
        LOG_INFO("recv ["<<msg.get(0)<<"]"<<"["<<msg.get(1)<<"]");
    }
    return res;
}

CMakeLists

cmake_minimum_required( VERSION 3.8 FATAL_ERROR)
project(broker LANGUAGES CXX)
 
#set dirs
set(PROJECT_ROOT ${CMAKE_CURRENT_LIST_DIR})
message("project dir:${PROJECT_ROOT}")
 
SET(BIN_DESTINATION ${PROJECT_SOURCE_DIR}/bin)
SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${BIN_DESTINATION})
SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${BIN_DESTINATION})
SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DESTINATION})
 
#include cmake files
include(${PROJECT_ROOT}/../version.cmake)
 
#set compile flags
#add_definitions(-std=c++11 -g -rdynamic)
set(CMAKE_CXX_FLAGS "-g3 -rdynamic -std=c++11")
set(CMAKE_CXX_FLAGS_DEBUG "-g3 -O0 ")#-fsanitize=address -fno-omit-frame-pointer -fsanitize=leak")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
 
#include dirs
include_directories(./ ../include/
    )
 
#link dirs
link_directories(${BIN_DESTINATION})
 
#execute 
SET(SRC_MAIN broker.cpp ../include/common.cpp)
add_executable( ${PROJECT_NAME} ${SRC_MAIN})
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})    
target_link_libraries(${PROJECT_NAME} pthread zmq zmqpp gflags)

add_executable(pub pub.cpp ../include/common.cpp)
set_target_properties(pub PROPERTIES VERSION ${PROJECT_VERSION})    
target_link_libraries(pub pthread zmq zmqpp gflags)

add_executable(sub sub.cpp ../include/common.cpp)
set_target_properties(sub PROPERTIES VERSION ${PROJECT_VERSION})    
target_link_libraries(sub pthread zmq zmqpp gflags)

运行效果截图

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ztenv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值