mongodb 优化游戏应用程序的反馈表单后端设计

wkftcu5l  于 2023-06-29  发布在  Go
关注(0)|答案(1)|浏览(124)

我必须设计和开发一个游戏应用程序中反馈表单的后端功能。
因此,用例是管理员将创建多个反馈表单,这些表单将在游戏应用程序中显示给用户。此表单将包含一些与应用程序反馈相关的问题。
表单将具有开始时间和结束时间,并且仅在表单处于活动状态(即它的当前时间在开始时间和结束时间之间,并且当为用户获得某组触发时。这里的触发器可以是游戏获胜XP增加。这些是预定义的触发器,将与管理员创建的每个表单相关联。
在游戏结束时,将调用API,该API将检查向用户注册的触发器,并将根据注册的触发器和当前时间从数据库中获取相关反馈。我的设计是将这些数据存储在一些文档数据库(NOSQL)中,因为不需要事务,并且表单的格式可以变化。对于表单,我将创建一个集合form_collection,其文档结构如下所示

{
  
  _id: ObjectId,                 // Unique identifier for the form
  title: String,                  // Title or name of the feedback form
  description: String,            // Description or additional information about the form
  created_at: Date,               // Timestamp indicating when the form was created
  status : bool, 
  start_time : Date, //Timestamp when the form will be active
  end_time : Date, //Timestamp when the form will be inactive
  questions: [
    {
      question_id: ObjectId,      // Unique identifier for the question
      question_text: String,       // The actual text of the question
      question_type: String,       // Type of question (e.g., open-ended, multiple-choice, rating-based)
      options: [String]            // If applicable, an array of available options for multiple-choice questions
    },
    // Other questions...
  ],
  triggers: [ObjectId]            // Array of trigger IDs associated with the form 
}

并且user集合的文档结构为

{
  _id: ObjectId,                 // Unique identifier for the user
  username: String,               // User's username or identifier
  email: String,                  // User's email address
  // Other user profile fields...

  triggers: [
    {
      trigger_id: ObjectId,       // Unique identifier for the trigger
      triggered_at: Date          // Timestamp indicating when the trigger was triggered for the user
    },
    // Other triggers...
  ]
}

现在会有两个流程
流程一:与用户一起存储触发器信息
每当触发器发生游戏获胜时,该触发器将存储在user集合中
流程二:为用户获取反馈表单
我们将为用户获取一组当前触发器,然后查询当前时间在表单的start_time和end_time之间的文档集合,然后从该列表中筛选出用户获取的所有触发器都Map的文档。这将涉及两次搜索,并且似乎没有优化。我可以在开始时间添加索引,并在form_collection中触发数组,但这似乎并不适合高流量。

我能想到的另一种方法是

预计算触发器列表
每当管理员创建一个表单时,我们都会在一些键值类型的nosql数据库中为一组触发器设置一个键,并在那里添加一个文档列表。这将把一些复杂性从管理转移到表单创建上。

<TriggerOne-TriggerTwo-TriggerThree> : [Forms]

是否有任何其他方法可以降低每次获取表单的复杂性,并且预计算方法是否正确,因为它将达到表单列表的限制(没有可以添加到列表中的表单)任何建议都将受到高度赞赏。此外,我是一个新鲜的,所以很抱歉,如果我不能正确/充分地解释,并开放讨论的意见,任何整改的要求。我可以使用任何数据库的后端。MongoDB不是必需的。再次感谢各位

bwntbbo3

bwntbbo31#

您当前的设计和方法在使用NoSQL数据库存储灵活的表单和使用触发器动态获取表单方面是很好的。但是,正如您所指出的,在高流量场景中,获取表单的过程可能会变得低效。
您建议的另一种方法是为一组触发器预先计算表单列表,这是一种很好的方法,但正如您提到的,它可能会限制可以添加到列表中的表单数量。
我会建议一种混合方法,基于“Caching a MongoDB Database with Redis”或“Database Cashing in-memory with Redis NoSQL Databases”这样的想法:
1.**保持当前设计:**您当前的数据库设计很好,因为它允许灵活的表单结构和用户触发器关联。为了完整性和灵活性,在数据库中保持数据的规范化形式是一个很好的做法。
1.增加缓存层:管理员新建表单时,可以预先计算触发器和表单的Map关系,并存储在缓存中,如Redis。这个缓存可以是一个hashmap,其中键是触发器集,值是表单id列表。这将加快表单提取过程,因为您不必每次都查询数据库。
1.**限制该高速缓存大小:**为避免缓存过大,您可以对每个触发器集的表单列表大小进行限制。如果列表超过此限制,您可以删除最旧的表单(基于end_time),也可以不将新表单添加该高速缓存中。您可以根据您的特定用例来决定这一点。
1.**回退数据库:**如果该高速缓存中没有找到表单,可以回退查询数据库。如果超过该高速缓存限制或缓存由于某种原因不可用,则可能发生这种情况。这样可以确保即使该高速缓存不可用,系统也仍然可以正常工作。
1.**定期更新该高速缓存:**您可以定期更新缓存,以删除不再活动的表单(基于end_time)或添加由于限制而未添加到缓存的新表单。这可以使用定期运行的后台作业来完成。
这种方法允许您保持当前设计的灵活性,同时降低为每个用户获取表单的复杂性和时间。缓存层为常见情况提供加速,而数据库为边缘情况提供回退。这种方法也是可伸缩的,因为它可以处理大量的表单和用户。
缓存层可以使用诸如Redis的缓存服务器来实现。以下是一般步骤:
1.**安装Redis:**Redis是一种流行的开源内存数据结构存储,可用作数据库、缓存和消息代理。它以其高性能和支持多种数据结构而闻名,如字符串,哈希,列表,集合等。
1.**创建缓存服务:**这是您后端代码中与Redis交互的服务。该服务应该具有从Redis添加、获取和删除数据的方法。
1.**预计算并添加数据到Redis:**当管理员创建新表单时,将触发器集合预计算为单个字符串(例如,“TriggerOne-TriggerTwo-TriggerThree”)。使用这个字符串作为键,表单ID作为值,并将其存储在Redis中。如果键已经存在,则将新的表单ID追加到现有列表中。
下面是一个向Redis添加数据的伪代码示例:

key = "TriggerOne-TriggerTwo-TriggerThree"
value = form_id
if redis.exists(key):
    redis.append(key, value)
else:
    redis.set(key, [value])

1.**从Redis中获取数据:**当需要为用户获取表单时,将触发器集合计算为字符串,并从Redis中获取对应的表单ID。如果在Redis中找不到数据,则返回到查询数据库。
下面是一个从Redis获取数据的伪代码示例:

key = "TriggerOne-TriggerTwo-TriggerThree"
form_ids = redis.get(key)
if form_ids is None:
    // Fetch from database
else:
    // Use form_ids

1.**更新Redis中的数据:**创建一个定期运行的后台作业来更新Redis中的数据。此作业应删除不再活动的表单,并添加由于该高速缓存大小限制而未添加到Redis的新表单。
请记住,虽然Redis是一个内存存储,但它也提供了持久化功能,因此您可以根据需要对其进行配置。
请注意,上面的例子过于简化,在实践中,您需要处理边缘情况,错误,并维护到Redis的连接池。具体的实现还取决于您使用的编程语言和框架。
只是想确认一下,如果我们想向用户显示表单,我们将获取用户拥有的所有触发器的列表,然后从Redis中相应地获取表单,对吗?
是的,没错。当您想向用户显示表单时,首先需要获取与用户关联的触发器。
对于这些触发器中的每一个,您都可以在Redis缓存中查找相应的表单。
以下是更详细的分步过程:
1.**Fetch User Triggers:**从主数据库(原始设计中为MongoDB)中获取用户的触发器。

1.**生成Key字符串:**对于每个触发器或触发器组合(取决于您如何设计触发器到表单的Map),生成您在Redis缓存中使用过的Key字符串。
1.**Fetch Forms from Redis:**对于每个键字符串,从Redis缓存中获取对应的表单ID。
1.**回退到数据库:**如果由于某种原因,Redis中没有键,则回退到主数据库中查询表单。如果你的Redis缓存是冷的,或者一组特定的触发器的表单比你的Redis缓存中可以存储的多,就会发生这种情况。
1.**编译表单列表:**将所有从Redis(可能还有主数据库)获取的表单ID合并到一个列表中,确保删除任何重复的表单ID。这是您随后将呈现给用户的表单列表。
同样,请注意,实际的实现可能会更复杂一些,这取决于您的特定需求和约束。
例如,您可能需要考虑按某个优先级或其他属性对表单进行排序,或者可能需要根据表单的开始和结束时间筛选出不再活动的表单。

相关问题