我必须设计和开发在游戏应用程序中的反馈形式在后端的功能.

因此,使用 case 是管理员将创建多个反馈表单,这些表单将在游戏应用程序中显示给用户.此表将包含一些与应用程序反馈相关的问题.

该表单将具有开始时间和结束时间,并且仅当表单处于活动状态时才向用户显示,即其当前时间在开始时间和结束时间之间,并且当获得用户的特定触发器集时. 这里的触发器可以是game wonXP Increased.这些是预定义的触发器,将与管理员创建的每个表单相关联.

在游戏结束时,将调用一个API,该API将判断用户注册的触发器,并根据注册的触发器和当前时间从数据库中获取相关反馈. 我的设计是将这些数据存储在某个文档数据库(NOSQL)中,因为不需要事务,并且表单的格式可以变化 对于表单,我将创建一个具有如下文档 struct 的集合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集合将具有如下文档 struct

{
  _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...
  ]
}

现在将有两个流

流程一:与用户一起存储触发器信息

无论何时触发发生Game won,该触发都将存储在user集合内

流程二:为用户获取反馈表单

我们将获取用户的当前触发器集,然后查询当前时间介于表单的开始时间和结束时间之间的文档集合,然后从该列表中筛选出映射了用户获取的所有触发器的文档 这将需要搜索两次,而且看起来并不优化.我可以在开始时间添加索引,并在form_collection中触发数组,但这似乎不是针对高流量优化的.

Another approach I can think of is

预计算触发器列表

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

<TriggerOne-TriggerTwo-TriggerThree> : [Forms]

有没有其他方法可以降低每次获取表单的复杂性,预计算方法是否正确,因为它将达到表单列表的限制(可以添加到列表中的表单数量) 任何建议都将受到高度赞赏.此外,如果我不能正确/充分地解释,我是一个新人,非常抱歉,我愿意在 comments 中讨论任何要求的纠正.我可以使用任何数据库作为后端.MongoDB不是必须的. 再次感谢你们,伙计们.

推荐答案

在使用NoSQL数据库存储灵活的表单和使用触发器动态获取表单方面,您当前的设计和方法很好.但是,正如您所提到的,在高流量场景中,获取表单的过程可能会变得效率低下.

您建议的另一种方法是为一组触发器预先计算表单列表,这是一个很好的方法,但正如您所提到的,它可能会达到列表中可以添加的表单数量的限制.

我会建议一种混合的方法,基于"Caching a MongoDB Database with Redis"或"Database Cashing in-memory with Redis NoSQL Databases"这样的 idea :

  1. Keep your current design:您当前的数据库设计很好,因为它允许灵活的表单 struct 和用户触发器关联.为了完整性和灵活性,在数据库中保留标准化形式的数据是一种良好的做法.

  2. Add a caching layer:当管理员创建新表单时,您可以预先计算触发器和表单之间的映射,并将其存储在缓存中,如100.该缓存可以是哈希图,其中键是触发器集,值是表单ID列表.这将加快表单获取过程,因为您不必每次都查询数据库.

  3. Limit the cache size:为了防止缓存变得太大,您可以 for each 触发器集设置表单列表的大小限制.如果列表超过此限制,您可以删除最旧的表单(基于end_time),也可以不将新表单添加到缓存中.您可以根据您的特定用例来决定这一点.

  4. Fall back to the database:如果在缓存中找不到表单,则可以退回到查询数据库.如果超过缓存限制或缓存因某种原因不可用,则可能会发生这种情况.这可确保即使缓存不可用,您的系统仍可正常运行.

  5. Update the cache periodically:您可以定期更新缓存以删除不再活动的表单(基于end_time)或添加由于限制而未添加到缓存中的新表单.这可以使用定期运行的后台作业(job)来完成.

这种方法允许您保持当前设计的灵活性,同时降低每个用户获取表单的复杂性和时间.缓存层为常见情况提供加速,而数据库为边缘情况提供后备.这种方法也是可伸缩的,因为它可以处理大量的表单和用户.


缓存层可以使用诸如Redis之类的缓存服务器来实现.以下是一般步骤:

  1. Install Redis: Redis是一种流行的开源内存数据 struct 存储,可用作数据库、缓存和消息代理.它以其高性能和对多种数据 struct (如字符串、散列、列表、集等)的支持而闻名.

  2. Create a Cache Service:这是您的后端代码中与Redis交互的服务.该服务应该具有从Redis添加、获取和删除数据的方法.

  3. Pre-compute and Add Data to 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. Fetch Data from 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. Update Data in Redis:创建一个定期运行的后台作业(job),以更新Redis中的数据.此作业(job)应删除不再处于活动状态的表单,并添加由于缓存大小限制而未添加到Redis的新表单.

请记住,虽然Redis是内存存储,但它也提供持久性功能,因此您可以根据需要进行配置.

请注意,上面的示例过于简化,在实践中,您需要处理边缘情况、错误,并维护到Redis的连接池.具体的实现还取决于您使用的编程语言和框架.


我只想确认一下,如果我们想要向用户显示表单,我们将获取用户拥有的所有触发器的列表,然后相应地从Redis获取表单,对吗?

Yes, that is correct. When you want to show forms to the user, you would first fetch the triggers associated with the user.
For each of these triggers, you would then look up the corresponding forms in your Redis cache.

以下是更详细的循序渐进的过程:

  1. Fetch User Triggers:从您的主数据库(原始设计中的MongoDB)检索用户的触发器.

  2. Generate Key Strings:对于每个触发器或触发器的组合(取决于您如何设计触发器到表单的映射),生成您在Redis缓存中使用的键字符串.

  3. 对于这些关键字串中的每一个,从您的Redis缓存中获取相应的表单ID.

  4. Fallback to Database:如果由于任何原因,Redis中没有关键字,您将退回到您的主数据库中查询表单.如果您的Redis缓存是冷的,或者如果一组特定触发器的表单比您的Redis缓存中可以存储的表单多,则可能会发生这种情况.

  5. Compile List of Forms:将您从Redis(可能还有您的主数据库)获取的所有表单ID合并到一个列表中,确保删除任何重复项.这是您随后将呈现给用户的表单列表.

Again, do note that the actual implementation might be a bit more complex, depending on your specific requirements and constraints.
For example, you might want to consider sorting the forms by some priority or another attribute, or you might need to filter out forms that are no longer active based on their start and end times.

Mongodb相关问答推荐

MongoDB Aggregate-如何在条件中替换字符串中的变量

从两个相连的文件中获取电话和邮箱的渠道是什么?

MongoDB通过查找具有多个数组的对象进行聚合

MongoDB与合并对象聚合

Golang mongodb聚合错误:管道阶段规范对象必须包含一个字段

多键索引,性能问题

如何在MongoDB中搜索有序子集的方法?

MongoDB - 来自先前匹配文档的聚合匹配字段

在mongoose中添加多个验证

为什么 local.oplog.rs 上每隔几分钟的活动就会锁定 mongo 客户端

什么 Javascript 库可以针对对象判断类似 MongoDB 的查询谓词?

从 MongoDB find() 结果集中识别最后一个文档

加载时将 mongo 存储的日期转换回自 Unix 纪元以来的毫秒数?

spring-data-mongo - 可选查询参数?

使用 MongoDB 进行嵌套分组

使用 Mongoid 和 Ruby 查询最近 30 天的日期范围?

如何使用 angular.js 推送通知?

通过 Java 执行 Mongo like Query (JSON)

用于 MongoDB 的 Node.js 模块

如何在 Windows 上停止 mongodb 服务器?