如何避免重复添加?

时间:2019-04-19 02:17:35

标签: ruby mongodb asynchronous mongoid

我正在尝试解决的问题

我有一个工作进程,该进程通过累积工作进程收到的一些JSON消息中的值来更改资源的属性(比如说MyResource)。我正在尝试提出一种最佳方法来避免重复积累,即使工作进程两次或多次收到相同的JSON消息也是如此。

这就是我尝试过的

解决方案1 ​​

每个JSON消息都有唯一的时间戳,具体取决于创建JSON消息的时间,我将该时间戳保存在MyResource上,如果时间戳值小于MyResource上的JSON消息,则拒绝该JSON消息。

问题

由于整个体系结构都是异步的,因此消息可以按任何顺序接收,而不必与创建时的顺序相同。

解决方案2

我在MyResource上创建了一个新属性(例如,added_ids)。每个JSON消息都有一个唯一的ID,我将该ID附加到MyResource.added_ids。并每次为已处理的JSON消息累积使用的added_ids。

问题

我正在使用mongo存储MyResource。由于每个MyResource的JSON消息很多,因此每个MyResource文档都开始使用此ID数组。另外,在数组中查找也是一项昂贵的操作。

我在寻找

我正在寻找一个可以解决异步问题且不会炸毁mongo文档的答案。另外我不是在寻找确切的解决方案,是否有用于解决类似问题的算法/模式?我尝试使用谷歌搜索,但不知道如何称呼该问题才能获得相关结果。

3 个答案:

答案 0 :(得分:0)

我认为您在第二种解决方案上走了正确的路,但是如果将每个add_id存储为自己的键值而不是存储在数组中,则性能可能会更好。

逻辑非常简单:每次从队列中获取输入时,都会在缓存中查找是否有该消息ID的条目。如果有条目,则不要累积该输入。否则,请累积输入并将密钥存储在缓存中。

正如您提到的,这种方法存在可伸缩性问题,因为缓存将无限增长。要解决此问题,可以使用具有到期和收回功能的缓存。进行此操作的最简单方法是明确设置您编写的每个键的“ expires at”。 Mongo,Memcached和Redis支持此功能。

问题是,即使您在每个点上都设置了“ expires at”,但如果负载足够大,您的缓存仍然会耗尽内存。因此,您需要进行后备操作-当缓存内存不足时要执行的操作。为此,您可以使用具有“自动逐出”功能的缓存,这意味着它具有在必要时删除内容的算法。

Mongo似乎不支持这种功能(它是具有缓存功能而不是适当的缓存的数据库)。 Memcache使用LRU算法(请参见https://github.com/memcached/memcached/wiki/UserInternals#when-are-items-evicted)。 Redis有多种算法可供您选择(请参见https://redis.io/topics/lru-cache)。

我要记住的另一件事是,在分布式或多线程应用程序上执行此整个过程会引入竞争条件。假设您有20台工作计算机,无论出于何种原因,它们几乎都在同一时间收到相同的消息。它们每个都将检查高速缓存中是否有条目,并且什么也找不到,因此它们都不被标记为重复。

要解决此问题,您可以对在同一台计算机上运行的多个线程使用互斥体/信号量(垂直缩放),如果您完全拥有多台计算机(水平缩放),则可以使用“分布式锁”。参见https://redis.io/topics/distlock

编辑

我收到一条提示,指出Mongo可以使用Capped Collections进行自动驱逐。它仅支持FIFO逐出(总是首先使最旧的数据到期),无论如何仍可以满足您的需求。

答案 1 :(得分:0)

如果可以按任何顺序应用JSON消息,则时间戳方法似乎没有多大意义。问题的描述不是很清楚,仅是需要确保避免重新处理同一条消息。

我在具有类似约束的系统上工作,而我们采取的方法是专注于消息而不是资源。该方法是计算消息的MD5校验和(或者至少是关键片段,因为它将影响MyResource实例...包括资源ID)。您会将消息存储在mongoDB文档中,也许将整个消息存储为一个属性,将MD5校验和存储在另一个属性中。当工作程序收到消息时,它将计算消息的校验和,检查是否已接收到该消息,并且仅当该校验和不存在文档时才处理该消息(存储在mongoDB中,对MyResource实例采取措施)。

此方法的一个优点是,如果由于某种原因“对MyResource采取行动”失败,您可以在将来“回放”消息。您可能想在接收文档时给文档打上时间戳记,以保证播放顺序(由于制作是异步的,而且您可能希望支持多个制作人,因此收货时间为准)。 / p>

答案 2 :(得分:0)

让我分享另一个疯狂的解决方案。如果您可以为每个资源消息分配唯一的质数,则可以标识重复项。在这种情况下,您必须在时间和空间之间进行权衡。

import {
    View,
    Text,
    ActivityIndicator
} from 'react-native';

export default class ProductDetail extends Component {
    ... your code ...

    render() {
        if (!this.state.test) {
            return <ActivityIndicator size='large' color='black' />
        }

        console.log(4);
        console.log(this.state.test);
        return (
          <View><Text>test</Text></View>
        );
    }
}

在每个过程之后,您将存储当前消息素数和先前计算的乘积。

messages for MyResource 1 => message 2 | message 3 | message 5 | message 7  
messages for MyResource 2 => message 2 | message 3 | message 5 | message 7  

每当收到一条消息时,请验证现有值是否可以除以消息ID。

MyResource 1 | 2  (processed 2 only)  
MyResource 2 | 70 (processed 2*5*7)

在第二个选项中,您担心空间(MongoDB 15mb限制,插入/查找延迟),为此,您应该考虑节省空间的数据结构,例如70 % 5 == 0 true (already processed) 70 % 3 == 0 false (not processed) 。但是,这是一个概率数据结构,这意味着可能会出现假阳性匹配,但不会出现假阴性。 redis has a nice implementation您可以尝试。

bloom filter