6

我的一个朋友在一次采访中被问到这个问题。我想在这里讨论这个问题

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

我想到的一个简单的想法是普通的 memqueue ,使用 Memcache 机器来扩展多个请求,并运行一个消费者作业,它将内容从 memcache 写入 DB。稍后对于第二部分,我们可以运行一个 sql 查询来查找匹配订阅者的列表。

问题:-

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

有 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 代表推荐人代码。)

IE。– 每个谓词都是一些相等条件的合取。请注意,谓词不一定对所有列都有相等子句(即,谓词可能不关心某些或所有列的值)。(在上面的示例中:#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

4 回答 4

1

我想到的一个解决方案是:

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

当接收到一个事件时,我们查找与 Cn 匹配的订阅的所有订阅者,并接收一个包含 0 个或更多订阅者的集合。在这个集合中,我们将来自该 Cn 的“ANY”集合中的订阅者添加到该集合中。

我们对每 n <= N 执行此操作,产生 n 组订阅者。所有n个集合的交集就是匹配这个事件的订阅者集合。

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

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

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

于 2012-12-26T15:18:46.267 回答
1

有趣的。

我最初的想法。我觉得如果订户谓词例如

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

来到调度员

公共无效注册用户

方法需要展平,以便比较性能更友好。类似下面的东西(疯狂猜测)

C1IN|C2search.php|C3nytimes.com

然后需要在内存中维护一个带有事件字符串和订阅者 ID 的映射

在里面

findMatchingIds

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

通过这种方式,调度程序可以水平扩展,并行服务许多事件

于 2013-01-03T15:35:25.610 回答
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 语句。以这种方式,您实际上可能会在少量处理器的情况下每秒获得数百万个事件。

您甚至可以哄骗像flex 扫描仪生成器这样的标准工具来为您完成大部分工作。

于 2013-01-04T05:42:19.057 回答
1

我认为这更像是一个设计问题——我认为面试官不会一直在寻找工作代码。一般的问题叫做 基于内容的发布订阅,如果你搜索同一领域的论文,你会得到很多结果:例如——这篇论文也

这是系统需要的一些东西

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

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

3)我们是否想要一个基于推或拉的模型:

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) (Server,Subscriber) 之间的消息传递层的更多细节 (a) 服务器和订阅者之间的通信是同步的还是异步的?
(b) 客户端/服务器之间是否需要二进制协议或基于文本的协议?(两者都有取舍)

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

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

9)This was assuming that we have a single server- What if we need a cluster of servers that the subscribers can connect to? (Whole bunch of issues here: ) a)How can network-partitioning be handled? ( example: of say 5 nodes,3 nodes are reachable from each other, and other 2 nodes can only reach other?) b) How are events/workload distributed among the members of the cluster?

10) Is absolute correctness of information sent to the subscriber a requirement,ie, can the client receive additional information,that what it's subscription rules indicate? This can determine choice of data-structure- example using a probabilistic data structure like a Bloom filter on the server side, while doing the filtering

11)How is time-ordering of events maintained on the server side? (Time-order sorted linked list? timestamps?)

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

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

于 2013-01-05T09:21:45.067 回答