五个解决方案让MongoDB拥有RDBMS的事务

2023-12-08 07:32

本文主要是介绍五个解决方案让MongoDB拥有RDBMS的事务,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章转自:

http://www.csdn.net/article/2014-08-07/2821104-Implement-Robust-and-Scalable-Transactions-with-MongoDB


事务问题

数据库支持数据块间的事务是有原因的。典型的场景是应用需要修改几个独立的比特时,如果只有一些而不是全部改变存储到了数据库,那么这就会出现不一致问题。因此ACID的概念是:

  • 原子性:所有的改变要么都做了,要么都没做
  • 一致性:数据保持一致性状态
  • 隔离性:其它用户看不到部分改变
  • 持久性:一旦向用户确认了事务,数据就处于安全的状态(通常存在硬盘上)

引入NoSQL数据库后,文档间ACID事务的支持通常就取消了。许多键/值存储仍有ACID,但它只适用于单个条目,取消ACID的主要原因是其可扩展限制。如果文档横跨几个服务器,事务将会很难实施以及性能。假设事务横跨数十个服务器,一些数据库是远程的,一些是不可靠的,想象下这会变的多难,多慢!

在单个文档等级上,MongoDB支持ACID。更准确的说,默认情况下是“ACI”,打开“j”WriteConcern选项后是ACID。Mongo有丰富的查询语言,横跨多个文档,因此人们一直在寻找多文档事务来使用他们的SQL代码。一个常见的办法是利用文档的性质:不需要很多行、很多关系,你可以将所有的东西嵌入到一个大文档中,Denormalization将带你回归事务。

这个技术解决了从一对一关系到一对多关系的很多事务问题。这也可能使应用更简单,数据库更快,所以这是双赢。不过当数据库必须分离时,该怎么办?

减少ACID

其实大部分应用都可以归结为:

  • 原子性:实际上你希望所有的改变都完成
  • 一致性:系统短时间不一致没关系,只要最终一致就行
  • 隔离性:缺乏隔离性导致暂时的不一致,这并不理想,但是当今线上服务时代,很多用户对此都习惯了(如用户支持:“它要花几秒传输”)。
  • 持久性:很重要,要支持。

这样问题就简化为鲁棒性、可扩性、最终一致性。

解决方案 1:字段同步

这种解决方案的使用场景最简单,最常见:文档间有些字段需要保持“同步”。例如,你有一个用户名为“John”的用户文档,文档代表John发表过的评论。如果用户可以更换用户名,那么这个改变需要发送给所有文档,即使进程中有应用错误或数据库错误。

为了实现这一目标,一个简单的办法是在主文档(这个情况下主文档是用户文档)中使用一个新字段(如“syncing”)。给“syncing”设置一个日期时间戳,记录用户文档的更新。

db.user.update({ _id: userId }, { $set:{ syncing: currentTime }, { rest of updates ... } })

然后应用会修改所有的评论文档。结束后,需要移除标识:

db.user.update({ _id: userId }, {$unset: { syncing: 1 } })

现在假设进程中出现了问题:有些评论使用的是旧用户名。不过这些地方仍然会保留标识,所以应用知道哪些进程需要重新进行。因此,你需要后台进程在指定的时间(如1小时)检查“syncing”文件是否有未完成的地方。索引应设为“sparse”,这样只有实际设置的文档需要被索引,索引量就会比较小。

db.user.ensureIndex({ syncing: 1 }, { sparse: true })

因此,系统通常可以保持事情在短时间内同步,在系统故障的情况下,时间周期为一个小时。如果时间不重要,当探测到“syncing”标志时,应用可以轻易修复文档。

解决方案2:作业队列

以上原理良好工作的前提是应用不需要很多内容,只依赖于通用进程(如:复制一个值)。一些事务需要执行特定变化,这些变化稍后很难识别。例如,用户文档包括一个朋友列表:

{ _id: userId, friends: [ userId1,userId2, ... ]}

现在A和B决定成为朋友:你需要把B添加到A的列表,也需要把A添加到B的列表。如果两者没有同时发生也没有关系(只要没有引发困扰)。针对这种情况和大多数事务问题的解决方案是使用作业队列,作业队列也存储在MongoDB。一个作业文档就像这样:

{ _id: jobId, ts: timeStamp, state: "TODO", type: "ADD_FRIEND", details: { users: [ userA, userB ]} }

或者是原始线程可以插入作业转发改变,或者是“worker”线程可以捡起工作。worker使用findAndModify()获取最原始的未加工的工作,findAndModify()是完全原子性的。操作中findAndModify()将工作标注为将被处理,同时也会表明worker name、当前时间以便于追踪。{ state: 1, ts: 1 } 上的索引使这些调用很迅速。

db.job.findAndModify({ query: { state: "TODO" }, sort: { ts: 1 }, update: { $set: { state: "PROCESSING", worker: { name: "worker1", ts: startTime } } } })

之后worker以一种幂等的方式对双方用户文档进行修改,这些改变能应用很多次,并且有同样的效果——这很重要!为了这个目的,我们只需要使用一个$addToSet。一种更通用的替代方式是在查询端添加一个测试,检测修改是否执行了。

db.user.update({ _id: userA }, {$addToSet: { friends: userB } })

最后一步是删除作业或标注作业完成。再保留一段时间作业是一种安全的方式,唯一的缺点是随着时间的流逝,先前的索引会变得越来越大,尽管你可以在指定域{ undone: 1 } 上使用稀疏索引,并且根据实际情况修改查询。

db.job.update({ _id: jobId }, { $set: { state: "DONE" } })

如果进程在某一时刻故障了,作业仍然会在队列中,并标注为处理中。后台进程停止一段时间后会将作业标注为需要再次处理,然后作业会重新从头开始。

解决方案3 :二阶段提交

二阶段提交是一个众所周知的解决方案,很多分布式系统都采用了这种解决方案。MongoDB简化了这种解决方案的实施,因为灵活的框架,我们可以将所有需要执行的数据全都放入文档中。我几年前就写过关于这种方法的文章,你可以去MongoDB Cookbook中查阅《 执行二阶段提交》(Perform Two Phase Commits)或者到MonoBD Manual中查阅《 执行二阶段提交》(Perform Two Phase Commits)。

解决方案4: Log Reconciliation

很多财务系统常用的解决方案是 log reconciliation。这种解决方案将事务写作简单的日志,这避免了复杂性和潜在的故障。然后从上次良好状态以来所有的变化推测当前账户的状态。在极端情况下,你可以清空账户,然后通过实施从第一天以来所有的变化重建账户……这听起来很恐怖,但是可行。账户文件需要一个“缓存”来提高速度,还需要一个seqId,seqId计算如下:

{ _id: accountId, cache: { balance:10000, seqId: 115 } }

执行事务时,一个典型的财务系统会给事务写一个条目,会给与事务有关的账户写一个“账户变化”条目。这个方法需要进一步的写保证,“作业队列”解决方案可以实现写保证,事务中所有的作业在所有账户更改写入前都会保持不变。不过有了MongoDB,我们可以写一个包括事务和账户更改的文档。这个文档应该嵌入tx集合,如下:

{ _id: ObjectId, ts: timestamp , proc: "UNCOMMITTED", state: "VALID", changes: [ { account: 1234, type: "withdraw", value: -100, seqId: 801, cachedBal: null }, { account: 2345, type: "deposit", value: 100, seqId: 203, cachedBal: null } ] }

几个重点:

  • 步骤:事务从“UNCOMMITTED” 状态开始,变为“COMMITTED”,此时涉及这些账户的所有先前事务也会变为 “COMMITTED” ,这表明这个事务也可以用作“anchor”来进行平衡计算。
  • 状态:状态可能是 “VALID”、“CANCELLED等。如果不是VALID,即使是“COMMITTED”,平衡计算也会忽略事务。
  • seqId:这是账户的独有的seqId,这个seqId给账户更改一个确定的顺序。
  • cachedBal:账户的缓存平衡。如果事务时“COMMITTED”状态,那么缓存平衡(如果设置了)是一个有效值。
  • 注意我们在 { changes.account: 1, changes.seqId: 1 }上使用一个独特的索引。reconciliation需要这个索引来提速,一个账户也不会有seqId副本。

关键是确保即使事务没有按顺序发生,缓存平衡也可以安全的计算/取消,还有就是事务状态可能改变。因此我们每个账户使用一个seqId,这确保了账户更改按确定的顺序发生,可以避免复杂的锁。在写事务前,应用首先通过简单地查询推断每个账户的下一个sqlId:

db.tx.find({ "changes.account": 1234 }, { "changes.$.seqId": 1 }).sort({ "changes.seqId": -1 }).limit(1)

然后每个sqlId都本地增长,然后写作事务的一部分。如果另一个线程也可能同时包括同样的seqId,独特的索引会确保写失败,线程会进行重试直到顺利完成任务。另一种方法是在账户集中保存一个当前seqId,然后用 findAndModify()获得下一个seqId,这通常会比较慢,除非你对账户有很多争用。注意如果因为某种原因事务没有写时,seqId可能会被跳过去,不过只有没有副本情况下才会成为。

下面我们谈谈reconciliation的基础。后台进程确保所有未提交的事务都会继续进行。只有所有账户的低seqId的事务都提交后一个事务才会被标注为提交。事务被标记为提交后就会变成不可变的。下面来谈谈好的方面:获得账户平衡。首先我们获得好的平衡,我们可以通过索引进行查询:

db.tx.find({ "changes.account": 1234, proc: "COMMITTED" }, { "changes.$": 1 }).sort({ "changes.seqId": -1 }).limit(1)

我们通过较大seqId的事务获得所有将发生的更改:

db.tx.find({ "changes.account": 1234, "changes.seqId": { $gt: lastGoodSeqId } }, { "changes.$": 1 }).sort({ "changes.seqId": 1 })

我们可以使用这些解决展示即将发生的损耗。如果我们只想简单的了解将来的平衡点在哪,我们可以让MongoDB收集所有变更展示总数:

db.tx.aggregate([{ $match: { "changes.account": 1234, "changes.seqId": { $gt: lastGoodSeqId }, state: "VALID" }}, 
{ $unwind: "changes" }, 
{ $match: { "account": 1234 }}, 
{ $group: { _id: "total", total: { $sum: "$value" } }}])

为了确保系统快速、计算量小,后台工作者要确保所有的事务都达到提交状态,平衡得到缓存。理想情况下一个事务是不可逆的,取而代之的是提交一个逆向事务来实施事务。不过只要所有的进一步事务状态和缓存都是正确设置的,取消是可行的。

解决方案5:版本控制

有时变得很复杂,以至于不能再JSON中表示,这些变更可能涉及很多有着复杂关系的文件(如树结构)。如果仅是部分变化(如破坏树)将会很混乱,这种情况下我们需要隔离。获取隔离性的一种方式是插入有着高版本号的新文档,取代对现有文档的更新。可以通过同日志和解同样的技术很容易、很安全的获得新版本号。通常{ itemId: 1, version: 1}上有一个独特的索引。

嵌入文档的应用从子文档开始,到主文档结束(如根节点)。当获取数据时,应用检查主文档的版本号,忽略高于版本号高于此版本号的文档。未完成的事务可以保持原状,可以忽略,可以清楚。

总结

综上所述,我们提供了在文档间实施鲁棒可扩展事物的五种解决方案:

  • 同步标志:最适用于仅从主文档复制数据的情况
  • 作业队列:比较通用,适用于95%的情况,大部分系统至少需要一个作业队列
  • 二阶段提交:这种技术确保每个实体都有为保持一致性状态所需的所有信息
  • Log Reconciliation:最鲁棒的技术,最适用于财务系统
  • 版本控制:提供了隔离性,适用于复杂的结构

此外,我们还提到了很多次MongoDB最终将支持真正的原子性和文档间的隔离事务。这已经作为分区的一部分了,但目前还只是内部的……只有文档在同一分区时这一特性才可能实现,否则我们将回到不可扩展的SQL世界。

事务问题

数据库支持数据块间的事务是有原因的。典型的场景是应用需要修改几个独立的比特时,如果只有一些而不是全部改变存储到了数据库,那么这就会出现不一致问题。因此ACID的概念是:

  • 原子性:所有的改变要么都做了,要么都没做
  • 一致性:数据保持一致性状态
  • 隔离性:其它用户看不到部分改变
  • 持久性:一旦向用户确认了事务,数据就处于安全的状态(通常存在硬盘上)

引入NoSQL数据库后,文档间ACID事务的支持通常就取消了。许多键/值存储仍有ACID,但它只适用于单个条目,取消ACID的主要原因是其可扩展限制。如果文档横跨几个服务器,事务将会很难实施以及性能。假设事务横跨数十个服务器,一些数据库是远程的,一些是不可靠的,想象下这会变的多难,多慢!

在单个文档等级上,MongoDB支持ACID。更准确的说,默认情况下是“ACI”,打开“j”WriteConcern选项后是ACID。Mongo有丰富的查询语言,横跨多个文档,因此人们一直在寻找多文档事务来使用他们的SQL代码。一个常见的办法是利用文档的性质:不需要很多行、很多关系,你可以将所有的东西嵌入到一个大文档中,Denormalization将带你回归事务。

这个技术解决了从一对一关系到一对多关系的很多事务问题。这也可能使应用更简单,数据库更快,所以这是双赢。不过当数据库必须分离时,该怎么办?

减少ACID

其实大部分应用都可以归结为:

  • 原子性:实际上你希望所有的改变都完成
  • 一致性:系统短时间不一致没关系,只要最终一致就行
  • 隔离性:缺乏隔离性导致暂时的不一致,这并不理想,但是当今线上服务时代,很多用户对此都习惯了(如用户支持:“它要花几秒传输”)。
  • 持久性:很重要,要支持。

这样问题就简化为鲁棒性、可扩性、最终一致性。

解决方案 1:字段同步

这种解决方案的使用场景最简单,最常见:文档间有些字段需要保持“同步”。例如,你有一个用户名为“John”的用户文档,文档代表John发表过的评论。如果用户可以更换用户名,那么这个改变需要发送给所有文档,即使进程中有应用错误或数据库错误。

为了实现这一目标,一个简单的办法是在主文档(这个情况下主文档是用户文档)中使用一个新字段(如“syncing”)。给“syncing”设置一个日期时间戳,记录用户文档的更新。

db.user.update({ _id: userId }, { $set:{ syncing: currentTime }, { rest of updates ... } })

然后应用会修改所有的评论文档。结束后,需要移除标识:

db.user.update({ _id: userId }, {$unset: { syncing: 1 } })

现在假设进程中出现了问题:有些评论使用的是旧用户名。不过这些地方仍然会保留标识,所以应用知道哪些进程需要重新进行。因此,你需要后台进程在指定的时间(如1小时)检查“syncing”文件是否有未完成的地方。索引应设为“sparse”,这样只有实际设置的文档需要被索引,索引量就会比较小。

db.user.ensureIndex({ syncing: 1 }, { sparse: true })

因此,系统通常可以保持事情在短时间内同步,在系统故障的情况下,时间周期为一个小时。如果时间不重要,当探测到“syncing”标志时,应用可以轻易修复文档。

解决方案2:作业队列

以上原理良好工作的前提是应用不需要很多内容,只依赖于通用进程(如:复制一个值)。一些事务需要执行特定变化,这些变化稍后很难识别。例如,用户文档包括一个朋友列表:

{ _id: userId, friends: [ userId1,userId2, ... ]}

现在A和B决定成为朋友:你需要把B添加到A的列表,也需要把A添加到B的列表。如果两者没有同时发生也没有关系(只要没有引发困扰)。针对这种情况和大多数事务问题的解决方案是使用作业队列,作业队列也存储在MongoDB。一个作业文档就像这样:

{ _id: jobId, ts: timeStamp, state: "TODO", type: "ADD_FRIEND", details: { users: [ userA, userB ]} }

或者是原始线程可以插入作业转发改变,或者是“worker”线程可以捡起工作。worker使用findAndModify()获取最原始的未加工的工作,findAndModify()是完全原子性的。操作中findAndModify()将工作标注为将被处理,同时也会表明worker name、当前时间以便于追踪。{ state: 1, ts: 1 } 上的索引使这些调用很迅速。

db.job.findAndModify({ query: { state: "TODO" }, sort: { ts: 1 }, update: { $set: { state: "PROCESSING", worker: { name: "worker1", ts: startTime } } } })

之后worker以一种幂等的方式对双方用户文档进行修改,这些改变能应用很多次,并且有同样的效果——这很重要!为了这个目的,我们只需要使用一个$addToSet。一种更通用的替代方式是在查询端添加一个测试,检测修改是否执行了。

db.user.update({ _id: userA }, {$addToSet: { friends: userB } })

最后一步是删除作业或标注作业完成。再保留一段时间作业是一种安全的方式,唯一的缺点是随着时间的流逝,先前的索引会变得越来越大,尽管你可以在指定域{ undone: 1 } 上使用稀疏索引,并且根据实际情况修改查询。

db.job.update({ _id: jobId }, { $set: { state: "DONE" } })

如果进程在某一时刻故障了,作业仍然会在队列中,并标注为处理中。后台进程停止一段时间后会将作业标注为需要再次处理,然后作业会重新从头开始。

解决方案3 :二阶段提交

二阶段提交是一个众所周知的解决方案,很多分布式系统都采用了这种解决方案。MongoDB简化了这种解决方案的实施,因为灵活的框架,我们可以将所有需要执行的数据全都放入文档中。我几年前就写过关于这种方法的文章,你可以去MongoDB Cookbook中查阅《 执行二阶段提交》(Perform Two Phase Commits)或者到MonoBD Manual中查阅《 执行二阶段提交》(Perform Two Phase Commits)。

解决方案4: Log Reconciliation

很多财务系统常用的解决方案是 log reconciliation。这种解决方案将事务写作简单的日志,这避免了复杂性和潜在的故障。然后从上次良好状态以来所有的变化推测当前账户的状态。在极端情况下,你可以清空账户,然后通过实施从第一天以来所有的变化重建账户……这听起来很恐怖,但是可行。账户文件需要一个“缓存”来提高速度,还需要一个seqId,seqId计算如下:

{ _id: accountId, cache: { balance:10000, seqId: 115 } }

执行事务时,一个典型的财务系统会给事务写一个条目,会给与事务有关的账户写一个“账户变化”条目。这个方法需要进一步的写保证,“作业队列”解决方案可以实现写保证,事务中所有的作业在所有账户更改写入前都会保持不变。不过有了MongoDB,我们可以写一个包括事务和账户更改的文档。这个文档应该嵌入tx集合,如下:

{ _id: ObjectId, ts: timestamp , proc: "UNCOMMITTED", state: "VALID", changes: [ { account: 1234, type: "withdraw", value: -100, seqId: 801, cachedBal: null }, { account: 2345, type: "deposit", value: 100, seqId: 203, cachedBal: null } ] }

几个重点:

  • 步骤:事务从“UNCOMMITTED” 状态开始,变为“COMMITTED”,此时涉及这些账户的所有先前事务也会变为 “COMMITTED” ,这表明这个事务也可以用作“anchor”来进行平衡计算。
  • 状态:状态可能是 “VALID”、“CANCELLED等。如果不是VALID,即使是“COMMITTED”,平衡计算也会忽略事务。
  • seqId:这是账户的独有的seqId,这个seqId给账户更改一个确定的顺序。
  • cachedBal:账户的缓存平衡。如果事务时“COMMITTED”状态,那么缓存平衡(如果设置了)是一个有效值。
  • 注意我们在 { changes.account: 1, changes.seqId: 1 }上使用一个独特的索引。reconciliation需要这个索引来提速,一个账户也不会有seqId副本。

关键是确保即使事务没有按顺序发生,缓存平衡也可以安全的计算/取消,还有就是事务状态可能改变。因此我们每个账户使用一个seqId,这确保了账户更改按确定的顺序发生,可以避免复杂的锁。在写事务前,应用首先通过简单地查询推断每个账户的下一个sqlId:

db.tx.find({ "changes.account": 1234 }, { "changes.$.seqId": 1 }).sort({ "changes.seqId": -1 }).limit(1)

然后每个sqlId都本地增长,然后写作事务的一部分。如果另一个线程也可能同时包括同样的seqId,独特的索引会确保写失败,线程会进行重试直到顺利完成任务。另一种方法是在账户集中保存一个当前seqId,然后用 findAndModify()获得下一个seqId,这通常会比较慢,除非你对账户有很多争用。注意如果因为某种原因事务没有写时,seqId可能会被跳过去,不过只有没有副本情况下才会成为。

下面我们谈谈reconciliation的基础。后台进程确保所有未提交的事务都会继续进行。只有所有账户的低seqId的事务都提交后一个事务才会被标注为提交。事务被标记为提交后就会变成不可变的。下面来谈谈好的方面:获得账户平衡。首先我们获得好的平衡,我们可以通过索引进行查询:

db.tx.find({ "changes.account": 1234, proc: "COMMITTED" }, { "changes.$": 1 }).sort({ "changes.seqId": -1 }).limit(1)

我们通过较大seqId的事务获得所有将发生的更改:

db.tx.find({ "changes.account": 1234, "changes.seqId": { $gt: lastGoodSeqId } }, { "changes.$": 1 }).sort({ "changes.seqId": 1 })

我们可以使用这些解决展示即将发生的损耗。如果我们只想简单的了解将来的平衡点在哪,我们可以让MongoDB收集所有变更展示总数:

db.tx.aggregate([{ $match: { "changes.account": 1234, "changes.seqId": { $gt: lastGoodSeqId }, state: "VALID" }}, 
{ $unwind: "changes" }, 
{ $match: { "account": 1234 }}, 
{ $group: { _id: "total", total: { $sum: "$value" } }}])

为了确保系统快速、计算量小,后台工作者要确保所有的事务都达到提交状态,平衡得到缓存。理想情况下一个事务是不可逆的,取而代之的是提交一个逆向事务来实施事务。不过只要所有的进一步事务状态和缓存都是正确设置的,取消是可行的。

解决方案5:版本控制

有时变得很复杂,以至于不能再JSON中表示,这些变更可能涉及很多有着复杂关系的文件(如树结构)。如果仅是部分变化(如破坏树)将会很混乱,这种情况下我们需要隔离。获取隔离性的一种方式是插入有着高版本号的新文档,取代对现有文档的更新。可以通过同日志和解同样的技术很容易、很安全的获得新版本号。通常{ itemId: 1, version: 1}上有一个独特的索引。

嵌入文档的应用从子文档开始,到主文档结束(如根节点)。当获取数据时,应用检查主文档的版本号,忽略高于版本号高于此版本号的文档。未完成的事务可以保持原状,可以忽略,可以清楚。

总结

综上所述,我们提供了在文档间实施鲁棒可扩展事物的五种解决方案:

  • 同步标志:最适用于仅从主文档复制数据的情况
  • 作业队列:比较通用,适用于95%的情况,大部分系统至少需要一个作业队列
  • 二阶段提交:这种技术确保每个实体都有为保持一致性状态所需的所有信息
  • Log Reconciliation:最鲁棒的技术,最适用于财务系统
  • 版本控制:提供了隔离性,适用于复杂的结构

此外,我们还提到了很多次MongoDB最终将支持真正的原子性和文档间的隔离事务。这已经作为分区的一部分了,但目前还只是内部的……只有文档在同一分区时这一特性才可能实现,否则我们将回到不可扩展的SQL世界。

这篇关于五个解决方案让MongoDB拥有RDBMS的事务的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/469034

相关文章

MySql 事务练习

事务(transaction) -- 事务 transaction-- 事务是一组操作的集合,是一个不可分割的工作单位,事务会将所有的操作作为一个整体一起向系统提交或撤销请求-- 事务的操作要么同时成功,要么同时失败-- MySql的事务默认是自动提交的,当执行一个DML语句,MySql会立即自动隐式提交事务-- 常见案例:银行转账-- 逻辑:A给B转账1000:1.查询

js异步提交form表单的解决方案

1.定义异步提交表单的方法 (通用方法) /*** 异步提交form表单* @param options {form:form表单元素,success:执行成功后处理函数}* <span style="color:#ff0000;"><strong>@注意 后台接收参数要解码否则中文会导致乱码 如:URLDecoder.decode(param,"UTF-8")</strong></span>

Lua 脚本在 Redis 中执行时的原子性以及与redis的事务的区别

在 Redis 中,Lua 脚本具有原子性是因为 Redis 保证在执行脚本时,脚本中的所有操作都会被当作一个不可分割的整体。具体来说,Redis 使用单线程的执行模型来处理命令,因此当 Lua 脚本在 Redis 中执行时,不会有其他命令打断脚本的执行过程。脚本中的所有操作都将连续执行,直到脚本执行完成后,Redis 才会继续处理其他客户端的请求。 Lua 脚本在 Redis 中原子性的原因

明明的随机数处理问题分析与解决方案

明明的随机数处理问题分析与解决方案 引言问题描述解决方案数据结构设计具体步骤伪代码C语言实现详细解释读取输入去重操作排序操作输出结果复杂度分析 引言 明明生成了N个1到500之间的随机整数,我们需要对这些整数进行处理,删去重复的数字,然后进行排序并输出结果。本文将详细讲解如何通过算法、数据结构以及C语言来解决这个问题。我们将会使用数组和哈希表来实现去重操作,再利用排序算法对结果

UE5 半透明阴影 快速解决方案

Step 1: 打开该选项 Step 2: 将半透明材质给到模型后,设置光照的Shadow Resolution Scale,越大,阴影的效果越好

MySQL主从同步延迟原理及解决方案

概述 MySQL的主从同步是一个很成熟的架构,优点为: ①在从服务器可以执行查询工作(即我们常说的读功能),降低主服务器压力; ②在从主服务器进行备份,避免备份期间影响主服务器服务; ③当主服务器出现问题时,可以切换到从服务器。 相信大家对于这些好处已经非常了解了,在项目的部署中也采用这种方案。但是MySQL的主从同步一直有从库延迟的问题,那么为什么会有这种问题。这种问题如何解决呢? MyS

安装SQL2005后SQL Server Management Studio 没有出来的解决方案

一种情况,在安装 sqlServer2005 时 居然出现两个警告: 1 Com+ 目录要求 2 Edition change check 郁闷!网上说出现两个警告,是肯定装不成功的!我抱着侥幸的态度试了下,成功了。 安装成功后,正准备 “ 仅工具、联机丛书和示例(T)” 但是安装不了,他提示我“工作站组件”安装过了对现有组件无法更新或升级。 解决办法: 1 打开“控

AI和新基建赋能智慧工地超融合管理平台解决方案

1. 项目背景与需求 电力行业的工程管理正朝着智慧化发展,但目前仍处于起步阶段。为满足数字化、网络化、智能化的发展需求,需要构建一个高效综合监控平台,实现对电力项目全过程的精益化管控。 2. 综合管理平台的构建 该平台集成了超融合实景监控、安全智能监测、公共安全防范、技术管理、人员管控和绿色施工等多个方面,通过BIM协同优化设计,提升项目质量和进度管理。 3. 安全智能监测的重要性 安全

【解决方案】软件大屏实现整体技术解决方案

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.1.系统权限设计 3.2.数据查询过程设计 软件全套资料部分文档清单: 工作安排任务书,可行性分

生产mongodb 分片与集群 方案

链接:http://my.oschina.net/pwd/blog/411439#navbar-header 注:主要是有一键安装的脚本可以借鉴