发布 - 订阅系统的设计/代码调度程序

时间:2012-12-24 16:20:02

标签: algorithm data-structures

我的一位朋友在接受采访时被问到这个问题。我想在这里讨论这个问题

这个问题的有效实施是什么?

我遇到的一个简单想法是正常的memqueue,使用Memcache机器来扩展多个请求,运行的消费者作业会将内存从memcache写入DB。 然后在第二部分我们可以运行一个sql查询来查找匹配订阅者列表。

问题: -

事件发布到此系统。每个事件可以被认为包含称为C1,C2,... CN的固定数量(N)的字符串列。因此,每个事件都可以作为字符串数组传递(C1是数组中的第0个元素,C2是第1个元素,依此类推)。

有M个订户 - S1,...... SM

每个订阅者都注册一个谓词,指定它感兴趣的事件的子集。每个谓词都可以包含:

Equality clause on columns, for example: (C1 == “US”)
Conjunctions of such clauses, example: 
    (C1 == “IN”) && (C2 == “home.php”) 
    (C1 == “IN”) && (C2 == “search.php”) && (C3 == “nytimes.com”)

(在上面的示例中,C1代表事件的国家/地区代码,C2代表网站的网页,C3代表引荐代码。)

即。 - 每个谓词都是一些平等条件的结合。请注意,谓词不一定具有所有列的相等子句(即 - 谓词可能不关心某些或所有列的值)。 (在上面的例子中:#a不关心列C3,...... CN)。

我们必须设计和编码可以将传入事件与注册订阅者匹配的Dispatcher。传入事件率以每秒百万次为单位。订阅者数量为数千。所以这个调度员必须非常高效。用简单的话说:

When the system boots, all the subscribers register their predicates to the dispatcher
After this events start coming to the dispatcher
For each event, the dispatcher has to emit the id of the matching subscribers.

就接口规范而言,可以粗略地说明以下内容(在Java中):

Class Dispatcher {

    public Dispatcher(int N /* number of columns in each event – fixed up front */);

    public void registerSubscriber( String subscriberId /* assume no conflicts */,
                                    String predicate /* predicate for this subscriberid */);

    public List<String> findMatchingIds(String[] event /* assume each event has N Strings */);

}

即:构建调度程序,然后进行一堆registerSubscriber调用。在此之后,我们不断调用方法findMatchingIds(),本练习的目标是使该函数尽可能高效。

4 个答案:

答案 0 :(得分:1)

我想到的解决方案是:

对于每个Cn,我们为订阅了Cn值的订阅者提供了从值到订阅者集的映射。此外,对于每个Cn,我们有一组不关心Cn('ANY')值的订阅者。

当收到一个事件时,我们会查找所有订阅者的Cn匹配订阅,并接收一个包含0或更多订阅者的订阅者。对于这个集合,我们从这个Cn的'ANY'集合添加那些订阅者。

我们为每个n&lt; = N执行此操作,产生n组订阅者。所有n个集合的交集是与此事件匹配的订户集合。

从Cn到订阅者的映射可以有效地存储为树,这给出了复杂度O(k)= log(k)以查找单个Cn的订阅者,假设存在对不同值的订阅。

因此,对于n个值,我们的复杂度为O(n,k)= n * log(k)。

相交n个集合也可以在O(n,m)= n * log(m)中完成,这样我们最终会得到一个对数复杂度,这不应该太糟糕。

答案 1 :(得分:1)

有趣。

我最初的想法。 我觉得如果用户预测例如

会更容易
  

(C1 ==“IN”)&amp;&amp; (C2 ==“search.php”)&amp;&amp; (C3 ==“nytimes.com”)

来到Dispatcher

  

public void registerSubscriber

方法需要展平,以便在比较时提供更高的性能。像下面的东西(疯狂猜测)

  

C1IN | C2search.php | C3nytimes.com

然后需要使用事件字符串和订户ID

在内存中维护映射

  

findMatchingIds

方法 - 字符串数组事件也需要使用类似的规则展平,以便可以为匹配的订阅者ID进行查找

这样,Dispatchers可以水平扩展,并行地为许多事件提供服务

答案 2 :(得分:1)

正如Hanno Binder暗示的那样,问题显然是为了允许预处理订阅以获得有效的查找结构。 Hanno说查找应该是地图

(N, K) -> set of subscribers who specified K in field N     
(N, "") -> set of subscribers who omitted a predicate for field N

当一个事件到来时,只需查找所有适用的集合并找到它们的交集。查找失败返回空集。我只是重新回答Hanno的好答案,指出哈希表是O(1),在这个应用程序中可能比树更快。另一方面,交叉树可以更快,O(S + log N)其中S是交叉点大小。所以它取决于集合的性质。

<强>替代

这是我的替代查找结构,在预处理期间只创建一次。首先编译地图

(N, K) -> unique token T (small integer)

还有一个尊贵的标记0代表“不关心。”

现在,每个谓词都可以被认为是具有N个令牌的正则表达式模式,或者表示特定的事件字符串键或者“不关心”。

我们现在可以提前建立一个决策树。您还可以将此树视为用于识别模式的确定性有限自动机(DFA)。边缘标有标记,包括“不关心”。如果没有其他边缘匹配,则不关心边缘。接受状态包含相应的订户集。

处理事件从将密钥转换为令牌模式开始。如果由于缺少映射条目而失败,则没有订户。否则将模式提供给DFA。如果DFA在不崩溃的情况下使用模式,则最终状态包含订户集。归还这个。

对于这个例子,我们有地图:

(1, "IN") -> 1
(2, "home.php") -> 2
(2, "search.php") -> 3
(3, "nytimes.com") -> 4

对于N = 4,DFA看起来像这样:

o --1--> o --2--> o --0--> o --0--> o
          \
            -3--> o --4--> o --0--> o

请注意,由于没有订阅者不关心,例如C1,起始状态没有一个不关心过渡。

中,任何在C1中没有“IN”的事件都会导致崩溃,并且将返回空集。

只有数千名订阅者,这个DFA的大小应该是合理的。

这里的处理时间当然是O(N),在实践中可能非常快。对于实际速度,预处理可以生成并编译C switch语句的嵌套。以这种方式,您实际上每秒可以使用少量处理器获得数百万个事件。

您甚至可以哄骗the flex scanner generator等标准工具为您完成大部分工作。

答案 3 :(得分:1)

我认为这更像是一个设计问题 - 我不认为面试官会一直在寻找工作代码。一般问题称为Content based Publish Subscribe,如果您在同一区域搜索论文,您将获得大量结果: 例如 - this paper also

以下是系统需要的一些东西

1)需要存储的订阅的数据存储:            a)存储订户列表            b)存储订阅列表

2)用于验证订阅请求和节点本身的方法           a)服务器订户通过ssl进行通信。在服务器处理数千个SSL连接的情况下 - 这是一项CPU密集型任务,特别是如果在突发中设置了大量连接。           b)如果所有订户节点都在同一个可信网络中,则不需要ssl。

3)我们是否需要基于Push或Pull的模型:

a)服务器可以维护每个节点看到的最新时间戳,每个过滤器匹配。当事件与过滤器匹配时,向订阅者发送通知。然后让客户 发送请求。然后,服务器启动发送匹配事件。

b)服务器匹配并一次性向客户端发送过滤器。

(a)和(b)之间的区别在于,在(a)中,您在客户端维护的状态更多。稍后可以更轻松地扩展特定于订户的逻辑。在(b)中,客户是愚蠢的。如果它不想因任何原因接收事件,它没有任何手段可以说。 (比方说,网络堵塞)。

4)如何在服务器端的内存中维护事件?

 a)The logical model here is table with columns of strings (C1..CN), and each new row added is a new event.
b)We could have A hash-table per column storing a tupple of  (timestamp, pointer to event structure). And each event is given a unique id. With different data-structures,we can come up with different schemes. 
 c) Events here are considered as infinite stream. If  we have a 32-bit eventId, we have chances of integer-overflow.
 d) If we have a timer function on  the server, matching and dispatching events,what is the actual resolution of the system timer? Does that have any implication? 
     e) Memory allocation is a very expensive operation.  If your filter-matching logic is going to do frequent allocations/ freeing, it will adversely affect performance.  How can we manage the memory-pool for this particular operation? Would we different size-buckets of  page-aligned memory? 

5)如果订户节点失去连接或发生故障,应该怎么办?           (a)客户在此期间是否可以丢失事件,或服务器是否应该缓冲所有事件?          (b)如果订户宕机,直到过去的历史时间可以请求匹配事件。

6)(服务器,订阅者)之间的消息传递层的更多细节            (a)服务器和用户之间的通信是同步还是异步?            (b)我们在客户端/服务器之间是否需要二进制协议或基于文本的协议? (两者都存在权衡)

7)我们是否需要服务器端的任何速率限制逻辑?如果我们在为其他人提供数据的同时让一些客户挨饿,我们该怎么办?

8)如何管理订阅的更改?如果某个客户希望更改它的subciption,那么它应该在更新永久数据存储之前先在内存中更新吗?或相反亦然?如果服务器出现故障,在写入数据存储之前会发生什么?我们如何确保数据存储 - 订阅/服务器列表的一致性?

9)这假设我们有一台服务器 - 如果我们需要一组服务器,该怎么办?    订阅者可以连接到? (这里有很多问题:)           a)如何处理网络分区? (例如:说5个节点,3个节点可以互相访问,其他2个节点只能到达其他节点?)           b)如何在集群成员之间分配事件/工作负载?

10)发送给订户的信息的绝对正确性是否是一个要求,即客户是否可以接收其订阅规则所指示的附加信息?这可以确定数据结构的选择 - 例如使用概率数据结构,例如服务器端的Bloom过滤器,同时进行过滤

11)如何在服务器端维护事件的时间顺序? (时间顺序排序链表?时间戳?)

12)订阅的谓词逻辑解析器是否需要unicode支持?

总之,基于内容的pub-sub是一个相当广阔的领域 - 它是一个分布式系统,涉及数据库,网络,算法,节点行为的交互(系统停机,磁盘坏了,系统耗尽内存)因为内存泄漏等) - 我们必须看看所有这些方面。最重要的是,我们必须查看实际实施的可用时间,然后确定我们希望如何解决此问题。