粉丝3031获赞1.4万

刚入行的同学肯定遇到过异常, concurrent modification exception, 今天我们来聊聊如何在便利 list 集合的同时删除其中元素。第一种,使用负循环删除。 如果你采用正向循环删除, list 中的元素在 jvm 眼里属于一边拆桥一边走路是肯定不行的。因此我们可以转换一下思路,也就是通过倒序便利的方式进行删除, 这个方式虽然土,但能解决问题。第二种,使用迭代器删除,也就是用迭代器便利集合。用迭代器的 render 方法删除集合元素, 这是官方认可的正确姿势,不会抛异常,语义也清晰。第三种,使用并发安全的集合类,如 copy on write a list。 这玩意儿的设计思路特别有意思,它在修改的时候会先 copy 一个新副本,在副本上改完再指回去, 所以你随便删,怎么折腾都不会报错。第四种,使用 java stream 进行过滤。既然删不明白,那可以直接使用 java stream 进行过滤,代码干净,逻辑清晰,也不容易出事。第五种,使用集合的 remove if 方法。 remove if 不是 你想象中的边便利边删,它内部用的就是迭代器, 而且是由集合自己控制便利和删除节奏。好了,今天就聊到这,觉得有用,点个赞。我是遥望二零三五,咱们下期见!

在 java 中,有几种方法可以便利 list 并动态删除元素。以下是一些常见的方法,一、使用 four h 循环推荐,这是最简单且最常见的方法。 你可以使用 free 一循环来便利类似并在需要的时候删除元素。这种方式最大的优点是简单易用,但也有一些缺点,比如如果列表很大 可能会导致性能问题。二、使用增强富尔循环推荐,在扎俄舞之后还可以使用增强富尔循环,也称为 footage 循环来便利类似, 并在需要的时候删除元素。这种方式和 forage 循环类似,但是更加简洁,但是和 forage 循环一样,如果列表很 大可能会影响性能。三、使用 iterator 你也可以使用 iterator 来便利类似并删除元素。这种方式的好处是可以按需删除, 而不是在列表中间进行。但是他的缺点是代码更复杂,并且在某些情况下可能会导致数据不一致。四、使用 java 八的流 stream 扎巴巴引入了刘 stream 的概念,可以很方便的处理集合。但是要注意的是,使用刘进行便利并删除元素并不是最佳实践,因为刘本身并不是设计用来修改集合的。 如果需要修改集合,最好使用其他方法。但是如果你只是想展示流的使用方法,以下是一个例子。总结,以上几种方法都 都有其优缺点,你可以根据自己的需求和场景选择合适的方法。一般来说,推荐使用 forage 循环或增强 for 循环来便利并删除类似中的元素。如果列表很大可能需要考虑性能问题。 对于更复杂的需求或性能要求更高的场景,可以考虑使用 iterat 或扎巴巴的流。

哈喽,昨天跟大家分享了我们如何用 list 去删除里面的元素,然后呢就有小伙伴问,为什么我们的增强或循环会出现并发修改异常,而我们的迭代器却不会出现呢?今天呢,我们就来看一下底层的一个原理啊。首先我们运行一下, 然后呢我们就看到了这样的一个并发修改异常,对吧?然后呢我们去追溯这样的一个元代码,点进去这边呢出现了一个。 net 方法,那这个 net 方法呢?在我们 itr 这个内部类里面, itr 这个类呢是我们 released 迭代器的一个实现,我们可以看一下 他这边呢是六了一个 i t r, 然后返回这样的一个叠带器,那增强后循环其实就是用到了底层叠带器的一个便利啊。那 i t r 里面呢,有一个 hard next 方法和 next 方法,很明显我们肯定是先要 去判断有没有下一个元素,有的话我们才会获取下一个元素,对吧?啊,当我们在调用 next 方法的时候呢,它底层会调用这样的一个方法叫 check for commodification, 然后我们点进去看一下, 这个里面呢,他会判断两个值是否相等,如果不相等的话,我们会抛一个叫并发修改异常。那茉莉康的这样的一个属性呢?他是我们 release 的一个成员变量值,他标记的呢就是我们 release 的修改的一个次数。 那 expected mode count 的这个值呢?它是我们 r e t r 内部的一个属性,那在初始化的时候,我们就会把我们 mode count 的的一个值给我们 expected mode count 值保持一个同步。然后呢我们看一下 list 点 remove 的一个方法,我们点进去看一下,看一下 released 的一个实现类, 我们可以看到在这个方法里面呢,其实主要就是一个 faster move, 我们可以点进去看一下,那在这个 方法里面呢,我们对 mod count 的一个值进行加加,但是呢我们没有看到任何关于对 expected mod count 的一个值进行修改,那他在下一次便利的时候呢,就会调用我们迭代器的一个 next 方法,然后呢就会走到这边, 然后去检查两个值是否相等,而这边值已经被修改过了,所以呢就会抛这样的一个异常。那我们的迭代器便利为什么就没有出现这样的异常呢? 那在叠带器的乳木方法里面啊,他把这两个值呢进行了同步,所以呢就不会出现那个异常。那如果说大家想要这样的一个 demo 以及之前的 demo 的话呢,可以到这来这,然后给他去发一个消息,像这样就可以拿到我们的 demo。 好,今天的一个分享呢,就到这。

章的中便利类似动态删除元素的六种方式,我们在工作中可能经常的需要去便利历史的里面的内容,并且去判断某一个元素是不是符合我们的条件,然后要把它删除掉,那么有几种方式可以完成这件事情呢?那么首先来看第一种方式, 这一种方式呃也很简单啊,就是便利历史的里面的每一个元素,然后去判断,然后去移除,那么这种方式可不可以呢?我们来呃跑一下这辆代码, 好来,他没有报错,但是呢,他并没有把周瑜删除掉,明明周瑜是以周开始的对不对?那这是为什么呢?其实主要原因是关于这个问题的详细文字法,我已经整理了一份八十万字的状元,知道传笔记放在视频的最后面,坚持看完一定对你有帮助。我们每一次便利其实 i 肯定会不断的加一,但是呢,每一次便利的时候,这个 呃历史的点 size, 它的是会不断的减少的,或者说有可能会减少对不对?有可能会发生变化啊,所以就有可能会 在整个过程中间会漏掉一些元素啊,这里我就不详细分析了,大家可以在回头自己去想一下这个过程啊,所以这种方式呢,肯定就不合适。那么再看第二种方式,第二种方式呢,刚刚不是点塞子他是会变化的吗?那我就把它在循环之前啊,就计算出来,然后呢再去循环,那么这种方式可不可以呢?我们也先来 跑一下,我们一跑发现他直接报了一个错,他说宿主越界了啊,其实也是能够理解的,因为我们这个 size 他不变了,那么很明显就是四,但是呢,随着我们呃可能有些元元素一移除,那么真正这个 size 里面他其实就没有四个元素了, 在这个过程中间就发生了变化。那么你这里假设你的 i 还去等于三的话,那么很明显就拿不到了,就住着越界了啊,所以说这种方式呢,呃,有这种情况下面也是不太合适的。那么我们再看第三种方式。第三种方式我就采用了一个倒序的方式啊,因为前 面的这两种方式相当于我的 i 是在增加的,然后我的历史和大小呢,是在减少的,他们两个是相反的,所以说会出现问题。但是我现在采用道序的话, 呃,首先我的逆时的肯定还是在减小的,减少的对不对?但是我的 i 其实也是在减小的,所以说他们两个是相当于是同方向的,同方向的话,那么其实问题就没有那么严重了,比如说我们 a 跑,他是能够正常的把我们的 需要的这两个元素都把它删除掉的,对不对?好的,我们再看第四种方式。第四种方式呢,我们就直接采用的点代器的方式,这种方式的话呢,就不用去考虑历史的的一些变化呀,哎的一些变化了,直接就反正是点代器哪个元素是需要删除的,那我就把它删除掉就可以了。所以这种方式其实可能大家用的也可能比较多。这种方式 好的,就是可能在 jdk 发之前,我们可能用这种方式用的比较多,因为它比较安全嘛,比较靠谱一点。那么我们再看第五 五种方式,想很明显这就是 ktk 八以后我们能够采用的方式,这种方式呢很简单,我就去呃利用这个历史的去过滤啊,去按照我的条件去过滤啊,过滤剩下的东西呢,就是我们想要的,相当于这就是我们删除的逻辑了,所以这种方式呢,它也是没有什么问题的,比如说也是能够正常的删除掉结果,对,删除到我们想要的 那个的,得到我们的结果,然后这第这一种主要就是过滤,然后还有第六种,第六种其实就更加简单啊,那么他就是直接调用历史的 removee 方,传一个 number, 老师传一个你要删除的那个元素的条件其实就可以了,所以这种方式啊,可能会比前面的方式要更加简单一点, 对,就一行代码,所以这个方法他的底层其实呃就是采用了叠带器啊,也就是采用了叠带器啊,只不过相当于进行了一个封装而已,所以这是给大家讲的这六种方式。那么大家还有没有其他方式可以来完成 我刚刚说的这个功能呢?好吧,大家可以就呃把自己想到的方式,可以在评论区留言,或者说呃想要拿到这些代码的啊,其实也可以在评论区留言,好吧,那么今天我就分享到这里,大家如果说有收获呃可以给我点点赞。

hello, 大家好,今天给大家讲一下 java 新手入门第五十二课集合便利好,我们直接打开我们的开发工具,这节课的代码呢,我也提前啊已经写好了,然后三节课的代码我们可以删除。 这节课我们来给大家讲一下啊,利用这个集合来便利一下,获取到集合里面的数据。首先我们先创建一个啊集合对不对?创建集合, 然后是制服串类型的啊,里面大家注意一下,是一个制服串类型的集合,我们在集合里面添加上啊这个三行数据,我还有小明和小红,对不对? 接下来呢,我们就用一个 for 循环啊,来便利我们集合里面的所有数据内容对不对?这个 for 循环呢,其实是有快捷方式写的,怎么写呢?首先我们写上这个集合的名称,然后点 for i 对不对?点 for i, 一个 four four 循环,再加上一个 i, 然后按下回车键,它就会自动帮我们生成了这个 four 循环了,对不对?这是一个快捷键,大家记一下啊,对不对?然后呢, 这个负循环跟我们其他的负循环是一样的,唯独这里获取这个负循环的长度啊,不是用我们之前获取的长度啊,而是用它。这里里面有个 sets, 返回此列表中 的元素数,对不对?其实说白了就是获取这个列表当中的里面的元素的个数。就是比如说目前我们这个集合里面呢,他是有三个啊,三个元素对呀,三个内容对不对,三个内容 他就会获取到三,明白了,就是这个意思啊,他就会获取到三啊三好。然后呢, 我们啊再把获取到的内容对不对,获取到的内容负责到我们新写的一个字符串的变量里面来啊,名字对不对?名字。 然后我们再判断一下,判断一下啊,利用这个获取字符串长度啊,获取我们这个名字的长度,看 下他是不是五个字,因为我的名字是五个字对不对?判断一下,如果是五个字,他就会输出出来。好,就是这么简单。我们直接运行看一下结果。运行 好,大家看到了啊,输出我的名字对不对,因为我的名字是五个字的,那,而这小明和小红他是两个字的,所以 会输出出我的名字对不对?那如果我们把这个获取支付断程度改成二,就是获取两个字名字的啊内容,这个时候就会输出小明和小红对不对?我们再运行看一下, 大家看到了啊,这个时候就会输出两个这不算长度 的啊内容啊,通过这个获取支付账长度的方法去判断啊,就是这么简单。这个样就是输出了我们的啊, 集合里面的内容,而且还可以根据我们想要获取的长度,名字的长度去获取啊,就是这么简单啊,就这么简单。好。然后我们接着再看一下我们今天要讲的第二个内容。第二个内容呢, 我们用到了我们之前写到的累,大家还记不记得我们之前写到了人物信息的一个累,不知道大家还有没有留着 啊,如果大家删除了也没有关系,如果大家删除了,可以重新在我们的文件里面新建一个类啊,新建一个类, 新建类了以后呢,我们只需要在类这里写上我们的名字和年龄,写上这两个参数,然后我们在右键利用插件直接快捷创建这些函数啊这些方法 就可以了,就可以直接用了对不对啊?因为我们之前上节课写的这个泪呢,我还留着,我没有删除,所以我直接拿来用就行啊。这些我们在之前的课程有讲过,大家如果忘记可以翻看之前的课程。首先这里我们创建一个啊集合, 但是大家这里注意一下,我们这个集合它的类型啊,是我们写的这个类的类型,类的类型填写上我们写的这个类的名称即可。好,写完了以后,创建完 这个集合以后,然后我们再写上我们这里面啊类的数据内容对不对?添加三个数据内容啊,我和小明和小红,然后再写上年龄十八岁和十七岁。 添加完以后啊,把这个我们这个类的这个啊名称和年龄的数据写完了以后呢,然后我们再添加到我们创建的集合里面啊,添加 a, d, d 添加, 然后我们再用这个 four 循环把它便利出来对不对? four 循环把它便利出来,刚刚我们已经讲过了啊,破循环便利完以后, 我们再把我们便利出来的所有数据内容放到我们新创建的这个啊信息里面,懂吧, 写上一个啊,备注这个是信息。好,这个信息的类型同样是我们这个类的类型,我们这个类的类型啊,名称已经上面已经写的很清楚。 最后我们再判断一下啊,获取到这个信息,获取到这个信息的年龄,判断一下是否大于十八岁,我只需要啊,输出大于或者是等于十八岁人的信息。那这里大家看到了, 这里唯一大于或者等于十八岁的人只有我一个,小明和小红,他是不满十八岁,对不对?然后大于十八岁呢,我们再输出出来,我们年姓名和年龄对不对,我们直接运行一下,看一下结果。 好,大家看到了啊,唯一满十八岁的只有五小明和小红,他是不满十八岁的,对不对啊?因为这里我是用年龄作为看的,这是我们经常用啊,经常会啊,做一个 只要是满十八岁的信息,我们通通都列出来,经常会做到。那如果我们想要获取啊,比如说我小明,小红,我也要全部读取出来。那这里我们只要写啊,填写,如果满十七岁或者是十七岁以上 都可以都输出出来,这样就行,对不对?就这么简单。当然了,年龄我们可以自己控制,通常都是写满十八岁的人啊,信息的人物信息都输出出来,这是我们经常会使用到的,对不对?看你是否满十八岁啊,对不对?好, 那今天这个集合便利啊,已经讲的很清楚,已经给大家讲完,大家学会以后,一定要自己亲手一行代码,一行代码写一下,千万不要偷懒,认为自己看一遍就学会了。好,那这节课就到这里。

你可以把圈理解成一条河,河流过去后东西就处理完了。所以抓瓦的圈的本质是什么?其实它本质就是一个否循环。大家想想啊,我们平时是不是经常便利宿主或者集合做一些操作, 这样代码既繁琐又无趣,所以 shin 就产生了。你只要记住, shin 的本质就是一个 for 循环。理解的这一点,你甚至都不用看他的细节,你都能够知道他的使用前提和应用场景了, 一切都会变得自然而然。比如怎样才能创建 extent 对象呢?那当然是需要使用一个数组或者集合啦,这样才能用 for 循环来操作嘛。又比如有什么应用场景啊,那肯定是能在 for 循 往里面进行的操作,都应该是他的应用场景。比如用 filter 来刷选元素,或者用 map 来把元素映射成其他元素等等。你所要做的就是要把要做的操作告诉券就可以了。 好了,有了这个 high level 的 overview, 是你即使不去了解它的细节,你也个你应该也能懂它了。

我们来学习一下 java 现代 i o 最佳实践,高效便利加速包处理,临时文件的管理 java file 等 files。 该类提供了一套功能非常全面的文件操作方法,包括创建、复制、移动、删除文件和目录,变了目录,为此目录创建 j 部压缩文件,创建临时文件和目录。 这些方法大大简化了文件的 i o 操作,避免了老旧的 java i o 点 fill 的 繁琐用法。我们先来看一下便利文件和便利字目录,我们来写个视例, 这里呢,我们通过 fills 点 list 便利当前目录,返回一个流,进行个循环输出。运行该方法 可以看到将当前文件夹下的目录进行一个输出。需要注意的是, windows 点 list, 它不会递归进入子目录。如果你需要遍历子目录,可以调用 windows 点 work 来辨析如下代码。 这里呢,我们通过 fill 点 word 来便利整个目录数,它默认会便利所有的子目录,然后它返回一个流,设置 a p i。 然后我们可以通过 filter 进行一个过滤,过滤以点 java 结尾的内容转化为数组,就会打印出来 进行方法,可以看到将所有 java 文件进行的输出。如果你想要限制便利的深度,可以指定第二个参数啊,例如 这里呢,我们指定深度二啊,这样就把第二层级的目录文件给编列出来,可以看到没有问题。如果你想更高级的控制啊,例如在编列过程当中想做一些其他的操作,可以使用 web visitor 来进行一个自定义,如下方式。 这里呢?删除文件啊,删除封目录,这里就不做演示了,有人他会把我当前的整个项目它删除掉,然后从 zelda 八开始,可以把一个 zip 文件当做一个虚拟文件系统来操作。例如我们读一个 压缩包啊,然后通过 use file system 返回 file system, 然后进行 word 的 一个便利便利并且打印的内容,然后可以读取对应的文件。 例如这里我创建了一个一个 java cip, 然后我们将它一个便利读取,可以看到可以将压缩包内的文件进行了一个读取,我们可以指定读一下文件的内容,这里把 license 改一下, 可以看到它可以将压缩包里的内容也一并的读取了出来。这样的话呢,就不需要手动地使用 jpeg output stream, 呃, input stream。 然后呢,我们也可以创建临时文件,例如这里我们创建临时文件,文件名与后缀,还有文件的目录啊,我们运行一下方法, 可以看到它创建在了临时文件当中。我要是 linux 就是 存在 pmp, windows 就是 存在用户临时目录, 一般系统重启会自动的清理。然后我们总结一下,我们可以使用 files dir list 与 files dir work 来进行编辑目录。然后在处理压缩包的时候呢,可以使用 files dir system 当做一个文件系统来处理。使用 files dir create temp file 和 create temp directory, 可以 创建文件和目录。好,这是本节内容。

大家好,我是马农 ag。 今天我们聊一个 java 开发中常见的性能问题,集合便利方式低效,尤其是用 for 循环下标便利 linked list, 选择正确的便利方式,性能差距可达百倍。第一个问题, for 循环下标便利 linked list, 每次 get i 都需要从头部重新便利。 link to list 不是 连续内存。 get 操作式 o n 复杂度便利,一千个元素可能需要五十万次时针跳转。第二个问题,数组越界风险,边界条件处理复杂, 手动控制下标容易出错。 array index out of bounds exception, 影响系统稳定性。第三个问题,便利时删除元素,容易触发 concurrent modification exception, 迭代器有自己的修改技术,和集合的 mod count 不 一致时会抛异常。 第四个问题,未选择合适的集合类型,导致性能差异巨大。 linklist 按缩写访问是 o n, 而 relist 是 o e, 选择错误就会导致性能问题。第五个问题,忽略集合特性,导致 o n 方,复杂度明明只需要顺序便利,却用了随机访问的集合,白白浪费性能。 如何排查?第一步,代码审查,检查所有集合便利代码。第二步,用 profiler 做性能分析,对比不同便利方式的耗时。第三步,用 g m h 做微机准测试,量化不同集合的性能差异。 接下来看问题代码。第一处,用 for i 循环便利 linked list, 每次 get i 都要从头部开始找复杂度 o n 方。第二处,在便利 a realist 时删除元素,没有使用 itrater 的 安全删除方式。正确优化方案有两个, 第一,使用 each reader 便利 each reader 维护当前位置,指真 has next 判断和 next 获取都是 o e, 复杂度便利,效率最高。第二,使用 remove if 安全删除。 java 八、引入的 remove if 方法在内部正确处理了 mount count, 可以 安全地边便利边删除。优化效果对比, 优化前便利十万条数据用下标便利 linkedlist 的 耗时两千五百毫秒。优化后用 iterater 便利耗时降到二十五毫秒,性能提升一百倍。总结四个要点,第一, linkedlist 便利用 iterater 不要用下标访问。 第二, arraylist 便利,优先用 foreach 或 iterater。 第三,需要边便利边删除时用 remove f 或 iterater 的 remove。 第四,根据访问模式选择集合类型,需要随机访问,用 arraylist 需要频繁增删,用 linkedlist。 核心原则正确选择合理便利。我是马农 ag, 我 们下期见。

好,现在我们把这个呃一级缓存的这个事务打开了,是吧?那么我们应该是这里要开始生效了啊。好,往下走八, 他去查数据库是吧?好, ok, 打印打印了最新的是吧?数据库里面七八八九九啊,九九,那么我现在数据库把把它改成一 改成一,是吧?那么这条收口准备开始走是吧?准备开始走出数据库啊,看他如果他真正走的数据库的话,他应该会取到这个测试名一是吧?如果说他没走真正数据库的话, 走到谎称的话,他应该还是打印这个啊,所以说按我们的想法这个是走的失误,然后一级谎称是不能打开的。买拜提斯,那么应该是要打出值钱还是还是这 这个啊?对,看见没有自己弹出来,是不是第二次测试的他还是找到这个,但是数据户已经是多少了? 数据库刚刚已经是测试名一了吗?这看到没看到区别了,没有说明这条售后他是没有走真正数据库的。走到缓冲啊,走的是一级缓冲,这种缓冲叫一级缓冲,然后这两个打出来看没有,他是做事相等啊, 所以他他依旧谎称取出这两个对象的都是相等的,知道吧。 好,我们那意见谎称,那你在这个方法里面那,那难道就永远都不去读真真书记过了吗?啊,那什么时候他就不走谎称呢啊,只有谎称里面数据没有了才会才会 不会去走这个缓存啊。那么怎么什么时候这缓存你们说就没有啊?你只需要再走收口啊。看啊,只只要在这个同样的这个表里面执行了啊,不对的操作啊,啊不对的的收口的话他就会没有啊,我们测一下第三次,好吧。嗯 啊,我们这里执行一条收口,好吧?执行一条收口,他改他改了个用户,是不是?但是我们改的是另外一个用户,看到没有?我们改的是啊啊,这个用户是不是用户名啊啊的这个数据啊,我们看一下啊,我们改完之后,改完之后他改成功了,是吧?然后我们再去取的话啊,大家看一下,我们再去取的话, 按我们的预想啊,因为已经做了修改,那么他应该去查真正数据库啊,那么看一下这个三是吧,哎,打印出来是吧?测试名一啊,测试名 数据库是名是四零一。所以说他这里已经缓存,之前的缓存已经失效了,他这里就走真正数据库了吗?啊,所以这里就是啊,下下面两个的话, 咱就判断一和二和三的问题是不是一个小不小的。因为三只肯定是走最新的数据库吗?肯定对象肯定都不一样的吗?所以说打出两个护士, 所以打出你的 boss, 那么这个等多少?那么整个这个景关键是。嗯,我们买白铁是光价的。买白铁的问题啊,一级方针 你在这一款就需要开启失误的情况下,然后默认的话买办理是关键。一级跑车是打开的啊,打开一级跑车之后,然后你默认是有失误,然后你 同样的一条收口啊,记住啊,你同样一条收口啊,就什么意思啊?你参数的必须一样嘛,你同样一个收口肯定参数一样嘛,是吧?你同样一条收口,像这两个收口他第二条收口是走的黄,在没走真正数据库啊, 然后什么时候失效呢?那你就在这中间,你只要有这个关于这个表的修改的任何操作, 那么他们自己能缓解就会失效。他所以说他第三次查的话,第三次查这个人的这个应付信息的话,他就会找到整理收口,这个是我们刚刚测试过的啊 啊,所以说你前提必须要开失误,你关掉失误这里一九分钟就不生气了,好吧?所以这是一九分钟的问题。

如何解决重复消费这道面试题的话呢,其实是问的消息中间件啊,也就是 m q 这道面试题来自于美团薪资的话是二十五 k, 然后呢对应的高岗位是高级开发。 首先呢,如果你想去解决重复消费的话,我们必须要知道一个问题,就是如果你使用了消息中间键啊,比如说 mq 啊,无论是 rap and q 还是 rap and q, 还是卡不卡啊,你都是没有办法 去避免这个 mq 中消息的重复的。那么遇到这种情况该怎么办呢?我们就是在消费的时候, 我们要消费时要做密等信处理啊,这个是处理消息中间件当中的消息重复当中的唯一方案啊。那么首先我们来看一下什么是密等信呢? 大家知道啊,密等信的话,我可以通过一个 cycle 语句给大家演示,比如我写一个简单的 cycle, 这个 cycle 的 话呢,就是 update, 然后我们的一个 order 表, 然后设置一下它里面的一个 counter 啊,这个 order 表底下呢,它有一个 set, 然后一个 counter 等于多少呢?等于十,然后问 id, 问 id 等于一 啊,就相当于是这样的一句 c 口的话呢,就是对我们的相关的这个 order 表里面的 counter, 把它进行设置啊。 好,这种情况下面的话呢,就是你进行多次操作的时候,他其实是密等型的,就是作为这这个 c 口语句,大家可以看到我执行一次,再执行一次,我再执行一次,如果在消息中间键里面有三条重复的消息,我是不是要去干三次,那干三次没有关系, 那么我们就说这样的 sql 语句它就是密等性的,那么什么样的语句是非密等性的呢?是这个,你说改一改, update、 order、 set、 count 等于什么?等于 count 加一。 好,这样的一个 sql 语句它本质上面就是非密等的,为什么呢?因为这样的语句的话,我们可以看到你要是发生的消息重复,那么我是不是会要去加三次? ctrl 等于 ctrl, 加一 ctrl 等于 ctrl, 加一 ctrl 等于 ctrl, 加一加三次,那么加完三次之后,原来他的值可能是十,直接就变成了多少一十三了,所以这个地方就会导致我们发发生了一些问题, 因为消息中间键当中的消息有重复,对不对?导致了你这一个 count 值变成了一个我们不是期望的值。所以常见的解决办法其实它会有两种啊。这个地方我们说一下解决方案, 第一个解决方案的话呢,就是通过 m、 v、 c、 c 多版本并发控制,这个多版本并发控制呢,其实它也是乐观所的一种实现啊,就是你再去生产发消息的时候,你可能要去带上, 比如说生产的时候我要带上数据的版本号,哎,什么叫数据的版本号呢? 举个例子啊,我们比如说一段尾代码当中我去发消息,对不对?我去调用的时候有一个 add 方法,这个 add 方法呢?它原来它有一个 id 啊,这个 id 是 它的入餐,对不对?然后我给它加一个,加一个威信,加一个版本号 啊,也也就是每一次你去调用这个 add 方法的时候,你还要加个版本号,然后加个版本号之后,我们可以看到它的这个 sql 语句会变成什么样子呢? 哎,就变成这个样子,我把它比如说这个地方问 id 等于一对不对?把它的一个 counter 进行一次加一了,然后后面还要加个条件啊,叫做 well, 我 们的这个 vision 等于你送进来的这个 vision, ok, 当然做完这个操作之后的话呢,这个 vision 不 会变化,所以我们在处理的时候,我们 set 操作,我们这个叫做 vision 等于 vision 加一。哎,你来看一下这样的一个语句, 就这样的一个语句,如果说你第一次调用的时候,我们举个例子,你送入的时候这个 vision 是 零,对不对?然后你去判断的时候,我们的数据库当中刚好也是零,所以这个地方可以相等好,然后你的 id 是 一, 然后紧接着你是不是又重复了数据,又送这个东西,又送这个东,送这个东西的时候,我们再去执行语句的时候,你可以看到,因为你前面已经执行了在数据表里面的这个 wechat, 它已经变成一了,所以啊,这个地方的这个不叫 wechat, 这个地方是个 on 的 条件, 这个按的条件,后面的东西它肯定就不满足了,对不对?也就是说作为 m v c c 的 话呢,其实就是带上数据的版本号,它只有数据版本号一致的情况,它才会执行一次, 就相当于是说你有消息的重复,我只有一次可以成功,对不对?然后失败的话呢?这个地方虽然它进行了操作,但是它不会把这一个 alt 表里面的这个 count 进行一次加一, 所以这称之为 m v c c 多版本并发控制啊。但是你发现了这种 m v c c 的 多版本并发控制的话呢,其实对我们的业务是有冲击的,因为原来呢你只要送一个你的方法里面只要送一个 id, 包括你的前端,包括你的接口,现在的话呢,你送的时候还要加个版本号,所以他在我们开发当中会带来很多的不便,所以这个地方又会有第二种方案,第二种方案的话呢叫做去重表的方案, 那去钟表的方案是怎么做的呢?我们来看一下啊,其实你在进行操作的时候,它就可以利用数据库的一些特性,比如说我数据库里面去定义一张这样的表,这张表 啊,比如说我定一张 a 表,这张 a 表里面呢,它只会有一个字段 id 啊,这个 id 它是唯一的, 他他是唯一的。如果说要去处理我们的 a、 d、 d 这个方法的时候,他会怎么操作呢?首先我先要去叉这张驱虫表啊,就是我写的伪蛋嘛,啊,就是我在执行逻辑的时候,首先 insert 我们的 a, 那 么大家知道啊,你去把这个 id 作为一个违心的插入 a 的 时候,第一次肯定会成功,对不对?然后你就执行你的业务,比如说执行我们的这一个修改好,执行完修改的时候呢,大家发现有可能它会失败, 所以我们在写代码的时候,我们这个地方写一个串, 好捕捉一下,然后在这段逻辑里面呢,我们再来捕捉一下 这个地方,是不是我们最终会有一个 cat, 对 不对?好,有这个 cat 之后的话呢,就是你需要把你的这个处理放到我们的这个 cat 里面, 如果你这个地方插不成功对不对?它是不是会抛异常?所以这个 cat 这个异常的话,你其实要捕捉到,并且的话不要抛出来,哎,你可以继续嘛, 对不对?你把这个东西的异常给它吞掉就可以了,所以这里面这个异常 啊,吃了怎么吃的呢?就是有,如果说你发生这种情况的话,你也可以用其他的方式记录啊,也没问题,但是的话不要往上跑。 为什么不要往上抛了?因为抛出之后本身这个地方是一些重复数据,对不对?比如说我作为一个 rock and q 的 消费端的话, 我是消费者,那干脆我还是做 ack 确认对不对?我还是要把这个消息给确认出来了,所以不要往上抛,依然呢做完我们的消费的 ack 确认就可以了。 所以这就是我们使用到的这种去重表的方案啊。两种方案的话呢,我们可以比比较一下, 本质上面的话呢, m v c c 的 话呢,它属于多版本变化控制,这个地方呢,用版本号来做,效率会高一点。然后去重表的方案呢,利用数据库的唯一性的特性,就是你进来如果你确定哪个 id 它是唯一的或者重复的,那么就建立一张这样的表, 这张表的话往里面插对不对?插入之后的话,如果有异常的吃了,然后把这个消息依然消费啊,用其他的方式呢,去记录一下就行了 啊,如果说他第一次成功,那当然就没问题,所以这就是我们站在了如何去解决重复消费的一个方案啊。 整体思想是密等性处理,密等性处理里面常用的有两种,一种多版本没法控制 m v c c。 第二种就是去重表的方案。