【问题标题】:ZeroMQ multithreading: create sockets on-demand or use sockets object pool?ZeroMQ 多线程:按需创建套接字还是使用套接字对象池?
【发布时间】:2013-05-15 14:30:51
【问题描述】:

我正在利用 ZeroMQ N 对 N 发布/订阅模型构建 POC。在我们的应用服务器中,当一个 http 请求得到服务时,如果线程从数据库中提取数据,它会使用该数据更新一个本地 memcache 实例。为了同步应用服务器集群中的其他 memcache 实例,请求线程使用 ZMQ 发布者发送带有数据的消息......所以问题是:什么策略是最有效的关于最小化套接字当应用程序有许多依赖于套接字发送消息的线程时创建/销毁开销?我们是否共享一个套接字池,是否为每个线程创建/销毁套接字等?

策略 1 - 线程管理的发布者套接字
在这种方法中,每个线程T1T2T3 通过创建、建立连接、发送消息和最后关闭套接字来管理套接字对象(发布者)的生命周期。基于this,这当然是最安全的方法,但我们担心重复创建、连接和销毁套接字时的开销;如果开销对性能产生负面影响,我们希望避免它。

策略 2 - 发布者套接字对象池
在这种方法中,父进程(应用服务器)在启动时初始化一个 ZMQ 发布者池。当一个线程需要一个发布者时,它从对象池中获取一个,发送它的消息,然后将发布者返回到池中;相对于使用发布者的线程而言,创建、连接和销毁套接字的过程被消除了,但是对池的访问是同步以避免任何两个线程同时使用同一个发布者对象,并且这就是可能出现死锁和并发问题的地方。

我们没有对这两种方法进行分析,因为想先对 SO 测试做一个试金石。就数量而言,我们的应用程序不会发布“大量”消息,但可能有 100-150 个线程(每个应用服务器)同时需要发布消息。

所以,重申一下:当应用程序有许多依赖于发布者发送消息的线程时,什么策略是最有效的

【问题讨论】:

  • 线程不能重用自己的私有套接字吗?
  • 不,这些是 HTTP 处理线程,由应用服务器管理;我会更新问题,谢谢。
  • 编程语言/应用服务器是什么?
  • Java,在 Tomcat 或 Jetty 上

标签: java multithreading sockets connection-pooling zeromq


【解决方案1】:

如果不提供估计吞吐量的真实数据,您就无法真正提出有关性能的问题。我们是在谈论每秒 10 个请求、100、1,000、10K 吗?

如果 HTTP 服务器真的为每个请求创建和销毁线程,那么重复创建 0MQ 套接字会给操作系统带来压力,并且取决于请求的数量和您的进程限制,它会起作用,或者它会用完手柄。您可以简单地对此进行测试,这是第一步。

然后,共享一个套接字池(您所说的“ZMQ 发布者”)是令人讨厌的。人们会这样做,但套接字不是线程安全的,因此这意味着在将套接字切换到另一个线程时要非常小心。

如果有办法让线程保持持久性,那么每个线程都可以在需要时创建其 PUB 套接字,并在它存在时一直保留它。如果不是,那么我的第一个设计无论如何都会创建/销毁套接字,但使用 inproc:// 将消息发送到单个永久转发器线程(SUB-PUB 代理)。我会对此进行测试,然后如果它坏了,就去寻找更奇特的设计。

一般来说,最好做出最简单的设计并打破它,而不是过度思考设计过程(尤其是在开始时)。

【讨论】:

  • Pieter,哪个例子最能说明inproc 与永久转发线程的使用? (感谢您的信息!)
  • 您建议使用clientThread.connect(inproc://...) 而不是clientThread.connect(tcp:///...) 是因为如果客户反复创建和销毁发布者,inproc 的开销会更少?如果是这样的话,我现在明白你在指南中所说的关于使用线程之间的消息进行通信而不是 MT 和锁的内容。
【解决方案2】:

这听起来对我来说也是过早的优化,如果可能的话,你应该坚持第一个策略,省去你的麻烦。

但是作为第二个选项的替代方案,您也许可以在应用程序中维护一个执行器线程池来执行实际的 zmq 发送。这样每个执行器线程都可以保留自己的套接字。您可以通过监听应用程序/servlet 生命周期事件来了解何时关闭池和清理套接字。

编辑:

最简单的方法是使用 Executors.newFixedThreadPool() 创建 Executor 并为其提供使用 ThreadLocal 套接字的 Runnable 作业。 (见Java Executors and per-thread (not per-work unit) objects?)线程只会被创建一次并从那时起重复使用,直到执行器关闭。

当作业的run() 方法中抛出异常时,这会变得有点棘手。我怀疑您会发现您需要对执行程序线程的生命周期进行更多控制。如果是这样,您可以复制newFixedThreadPool的来源:

return new ThreadPoolExecutor(nThreads, nThreads,
                              0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>());

并将被实例化的ThreadPoolExecutor 子类化以对其进行自定义。例如,您可以通过这种方式覆盖 afterExecute 以检测和清理损坏的套接字。

发送作业通过阻塞队列转移到工作线程。我意识到这不是将消息传递给工作线程的 ZeroMQ 方式,这将是 inproc 消息传递。这使 ZeroMQ 远离生命周期不受您控制的 HTTP 工作线程,因此难以维护套接字,更靠近应用程序的边缘。您必须简单地测试两者中哪一个更有效,并且必须对您希望应用程序采用 ZeroMQ 消息传递范式进行线程间通信的严格程度做出判断。

【讨论】:

  • 我正在查看 Executor 模型,但是我发现的所有示例都按需创建和执行工作人员,我想创建工作人员(发布者)并将它们添加到工作人员池中,然后将请求发送到工作队列进行实际发送。你有这方面的例子吗?
  • 我添加了一些创建和使用固定线程池的指针
  • 我同意,根据 Pieter 的 cmets,inproc 可能是最好的方法,但如果它没有达到预期,我也会尝试这种方法,谢谢。
猜你喜欢
  • 2018-08-31
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-16
相关资源
最近更新 更多