我们有一种情况,我必须处理大量涌入服务器的事件,平均每秒大约1000个事件(峰值可能是2000个)。
问题
我们的系统托管在Heroku上,并使用相对昂贵的Heroku Postgres DB,该数据库最多允许500个DB连接。我们使用连接池从服务器连接到数据库。
事件传入的速度快于数据库连接池无法处理的速度
我们遇到的问题是事件的发生速度快于连接池无法处理的速度。到一个连接完成从服务器到DB的网络往返时,它可以释放回池中,而不是n
其他事件。
最终,事件堆积起来,等待保存,并且由于池中没有可用的连接,它们超时并且整个系统变得无法运行。
我们已经通过以较慢的速度从客户端发出有问题的高频事件来解决紧急情况,但是我们仍然想知道在需要处理高频事件时如何处理这种情况。
约束条件
其他客户端可能希望同时读取事件
其他客户端连续请求使用特定密钥读取所有事件,即使它们尚未保存在数据库中也是如此。
客户端可以查询GET api/v1/events?clientId=1
并获取客户端1发送的所有事件,即使这些事件尚未保存到DB中也是如此。
是否有有关如何处理此问题的“教室”示例?
可能的解决方案
使事件排队在我们的服务器上
我们可以在服务器上排队事件(队列的最大并发性为400,因此连接池不会用完)。
这是个坏主意,因为:
- 它将耗尽可用的服务器内存。堆积的排队事件将消耗大量RAM。
- 我们的服务器每24小时重启一次。这是Heroku施加的硬限制。当事件排队时,服务器可以重新启动,导致我们丢失排队的事件。
- 它在服务器上引入状态,从而损害了可伸缩性。如果我们有一个多服务器设置,并且客户端要读取所有已排队+保存的事件,则我们将不知道已排队事件在哪台服务器上。
使用单独的消息队列
我假设我们可以使用消息队列(例如RabbitMQ吗?),在其中将消息泵入其中,另一方面,还有另一台服务器仅处理将事件保存在DB上。
我不确定消息队列是否允许查询排队的事件(尚未保存),因此,如果另一个客户端想要读取另一个客户端的消息,我只能从数据库中获取已保存的消息,并从队列中获取待处理的消息。并将它们连接在一起,这样我就可以将它们发送回读取请求客户端。
使用多个数据库,每个数据库使用中央数据库协调器服务器保存一部分消息,以管理它们
不过,我们的另一个解决方案是使用多个数据库,并使用一个中央“ DB协调器/负载平衡器”。接收到事件后,此协调器将选择一个数据库来写入消息。这应该允许我们使用多个Heroku数据库,从而将连接限制提高到500 x数据库数。
在进行读取查询时,此协调器可以SELECT
向每个数据库发出查询,合并所有结果,然后将其发送回请求读取的客户端。
这是个坏主意,因为:
- 这个主意听起来像是...太设计了吗?管理(备份等)也将是一场噩梦。它的构建和维护非常复杂,除非绝对必要,否则听起来像是违反了KISS。
- 它牺牲了一致性。如果我们遵循这个想法,那么跨多个数据库进行事务是不可行的。
ANALYZE
在查询本身上运行,它们不是问题。我还构建了一个原型来测试连接池假设,并验证了这确实是问题所在。数据库和服务器本身位于不同的计算机上,因此存在延迟。另外,除非绝对必要,否则我们不想放弃Heroku,而不必担心部署对我们来说是一个巨大的好处。
select null
500个连接。我敢打赌,您会发现连接池不是那里的问题。