Redis与MySQL的双写一致性问题

news2024/12/26 23:46:53

Redis与MySQL的双写一致性问题

      • 更新缓存? 删除缓存?
      • 先更新缓存再更新数据库
      • 先更新数据库,再更新缓存
      • 先删除缓存再更新数据库
      • 先更新数据库,再删除缓存
      • 解决方案
        • 1. 重试
        • 2. 异步重试
          • 2.1 使用消息队列实现重试
          • 2.2 Binlog实现异步重试删除
        • 3. 延时双删
      • 总结
      • 参考文章

Redis与MySQL双写一致性是指在使用缓存和数据库同时存储数据的场景下( 主要是存在高并发的情况)如何保证两者的数据一致性(内容相同或者尽可能接近)

正常业务流程
在这里插入图片描述

读没什么问题,关键就在于写(更新)操作,这就会出现几个问题了,这里是先更新数据库,然后对缓存操作。但对于缓存操作,是更新缓存还是删除缓存呢?或者为什么不是先操作(删除、更新)缓存在更新数据库呢

总结一下就是到底先操作缓存再操作数据库,还是先操作数据库再操作缓存

带着这几个问题接着往下讲。

首先讲一下操作缓存,包括两种:更新缓存删除缓存,如何选择?


更新缓存? 删除缓存?

假设都先更新数据库(因为先操作缓存再操作数据库问题较大,后面会讲)

  • 更新缓存

    先更新数据库,再更新缓存。

    如果两个请求同时对同一条数据进行修改,那么可能出现先后顺序颠倒,导致缓存中的数据是旧的。之后的读请求读到的都是旧数据,只有当缓存失效后,才能从数据库中得到正确的值。

    在这里插入图片描述


  • 删除缓存

    先更新数据库,再删除缓存。

    会有这样一种情况:缓存刚好失效,请求B从数据库中查询数据,得到旧值。此时请求A更新数据库,将新值写入数据库,并删除缓存。而请求B又将旧值写入缓存中,导致脏数据

在这里插入图片描述

从上面看出现脏数据的要求要比更新缓存的要求更多,必须满足以下几个条件:

  1. 缓存失效
  2. 读请求 + 写请求并发
  3. 更新数据库 + 删除缓存的时间要比读数据库 + 写缓存时间

前面两个很好满足,我们再看看第三点,这个真的会出现吗?

数据库在更新时一般是加锁的,读操作的速度远快于写操作的,所以第三点发生概率极低(当然也可能发生)

注:这里我其实不是很理解,单纯看确实发生概率低,但如果出现网络延迟等情况呢,不也会发生吗?希望好心人解惑,我反正没理解。

因此,在选择删除缓存时,还需要结合其他技术来优化性能和一致性。例如:

  • 使用消息队列来异步地删除或更新缓存,避免阻塞主线程或者丢失消息。
  • 使用延时双删来增加删除成功率和减少不一致时间窗口。即在更新数据库后立即删除一次缓存,并在一定时间间隔后再次删除一次。

对比

​ 在更新缓存中, 每次去更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很有可能是先查询数据库,再经过一系列「计算」得出一个值,才把这个值才写到缓存中。

​ 由此可见,这种更新缓存的方案,不仅缓存利用率不高,还会造成机器性能的浪费。所以我们一般考虑删除缓存


先更新缓存再更新数据库

在更新数据时,先将新数据写入缓存(Redis),再将新数据写入数据库(MySQL)

但其存在一下问题:

  • 缓存更新成功,但数据库更新失败,导致数据不一致

    :用户修改了自己的昵称,系统先将新的昵称写入缓存,然后再更新数据库。但是在更新数据库的过程中,发生了网络故障或者数据库宕机等异常情况,导致数据库中的昵称没有被修改。这样就会出现缓存中的昵称和数据库中的昵称不一致的情况。

  • 缓存更新成功,但数据库更新延迟,导致其他请求读取到旧的数据

    :用户下单了一个商品,系统先将订单状态写入缓存,然后再更新数据库。但是在更新数据库的过程中,由于并发量大或者其他原因,导致数据库的写入速度慢于缓存的写入速度。这样就会出现其他请求从缓存中读取到订单状态为已支付,而从数据库中读取到订单状态为未支付的情况。

  • 缓存更新成功,但在数据库更新之前有其他请求查询了缓存和数据库,并将旧的数据写回缓存,覆盖了新的数据

    :用户A修改了自己的头像,并上传到服务器上。系统先将新的头像地址写入缓存,并返回给用户A显示。然后再将新的头像地址更新到数据库中。但是在这个过程中,用户B访问了用户A的个人主页,并从缓存中读取到了新的头像地址。由于缓存失效策略或者其他原因(比如重启)导致缓存被清空或者过期。这时候用户B再次访问用户A 的个人主页,并从数据库中读取到了旧的头像地址,并将其写回缓存中。这样就会出现缓存中 的头像地址和 数据库 中 的头像地址不一致 的情况。

上面说了一堆,其实总结就是缓存更新成功了,数据库没更新(更新失败),导致缓存存的是最新值,数据库存的是旧值。如果缓存失效了,就会拿到数据库中的旧值。

​ 后面我自己也搞疑惑了,既然是因为数据库更新失败导致的问题,那我是不是只要保证数据库更新成功就可以解决数据不一致的问题,当数据库更新失败时,不停的重试更新数据库,直到数据库更新完成。

后面发现自己太天真,其中存在很多问题,比如:

  • 如果数据库更新失败的原因是数据库宕机或者网络故障,那么你不停地重试更新数据库可能会造成更大的压力和延迟,甚至导致数据库恢复困难。
  • 如果数据库更新失败的原因是数据冲突或者业务逻辑错误,那么你不停地重试更新数据库可能会导致数据丢失或者数据错乱,甚至影响其他用户的数据。
  • 如果你不停地重试更新数据库,那么你需要考虑如何保证重试的幂等性和顺序性,以及如何处理重试过程中发生的异常情况。

所以,这种方法并不是一个很好的解决方案。


先更新数据库,再更新缓存

当有一个更新操作时,先更新数据库数据,然后再更新对应的缓存数据

但是,这种方案也有一些问题和风险,比如:

  • 如果更新数据库成功了,但是更新缓存失败了,那么就会导致缓存中就会保留旧的数据,而数据库中已经是新的数据,即脏数据。
  • 如果在更新数据库和更新缓存之间,有其他请求查询了同一个数据,并且发现缓存存在,那么就会从缓存中读取旧的数据。这样也会造成缓存和数据库之间的不一致性。

因此,在使用更新缓存操作时,无论谁先谁后,但凡后者发生异常,就会对业务造成影响。(还是上面那张图)

)

那么如何处理异常情况来保证数据一致性呢

​ 这些问题的源头都是多线程并发所导致的,所以最简单的方法就是加锁(分布式锁)。两个线程要修改同一条数据,每个线程在改之前,先去申请分布式锁,拿到锁的线程才允许更新数据库和缓存,拿不到锁的线程,返回失败,等待下次重试。这么做的目的,就是为了只允许一个线程去操作数据和缓存,避免并发问题。

​ 但加锁费时费力,肯定不推荐。并且,每次去更新缓存,但是缓存中的数据不一定会被马上读取,这就会导致缓存中可能存放了很多不常访问的数据,浪费缓存资源。而且很多情况下,写到缓存中的值,并不是与数据库中的值一一对应的,很有可能是先查询数据库,再经过一系列「计算」得出一个值,才把这个值才写到缓存中。

​ 由此可见,这种更新数据库 + 更新缓存的方案,不仅缓存利用率不高,还会造成机器性能的浪费。

所以此时我们需要考虑另外一种方案:删除缓存


先删除缓存再更新数据库

当有一个更新操作时,先删除对应的缓存数据,然后再更新数据库数据

但是,这种方案也有一些问题和风险,比如:

  • 如果删除缓存后,更新数据库失败了,那么就会导致缓存丢失,下次查询时需要重新从数据库加载数据,增加了数据库压力和响应时间。
  • 如果在删除缓存和更新数据库之间,有其他请求查询了同一个数据,并且发现缓存不存在,那么就会从数据库中读取旧的数据,并写入到缓存中。这样就会造成缓存和数据库之间的不一致性。

在这里插入图片描述


先更新数据库,再删除缓存

当有一个更新操作时,先更新数据库数据,再删除缓存

上面其实讲过了,我再重复一遍吧

会有这样一种情况:缓存刚好失效,请求B从数据库中查询数据,得到旧值。此时请求A更新数据库,将新值写入数据库,并删除缓存。而请求B又将旧值写入缓存中,导致脏数据

在这里插入图片描述

从上面看出现脏数据的要求要比更新缓存的要求更多,必须满足以下几个条件:

  1. 缓存失效
  2. 读请求 + 写请求并发
  3. 更新数据库 + 删除缓存的时间要比读数据库 + 写缓存时间

前面两个很好满足,我们再看看第三点,这个真的会出现吗?

数据库在更新时一般是加锁的,读操作的速度远快于写操作的,所以第三点发生概率极低

所以,解决双写问题更适合的方法是先更新数据库,再删除缓存,当然具体场景具体分析,不定说一定就是这个。

讲解了这些操作后会出现的问题,那么为了避免这些问题,如何做呢?

  • 先删除缓存再更新数据库,然后使用异步线程或消息队列来重建缓存。
  • 先更新数据库再删除缓存,并设置一个合理的过期时间来保证缓存的有效性。
  • 使用分布式锁或乐观锁来控制并发访问,并保证每次只有一个请求能够操作缓存和数据库

……

下面讲几种常见的方法以保证双写一致性


解决方案

1. 重试

​ 上面也提到过,当第二步操作失败时,我就重试嘛,尽可能地补救,但重试的成本太大,上面讲过就不重复了。

2. 异步重试

​ 既然重试方法占用资源,那我就做异步。在删除或更新缓存时,如果操作失败,不立即返回错误,而是通过一些机制(如消息队列、定时任务、订阅binlog等)来触发缓存的重试操作。这样可以避免同步重试缓存时的性能损耗和阻塞问题,但也可能导致缓存和数据库的数据不一致的时间较长。

2.1 使用消息队列实现重试
  • 消息队列保证可靠性:写到队列中的消息,成功消费之前不会丢失(重启项目也不担心)
  • 消息队列保证消息成功投递:下游从队列拉取消息,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合我们重试的需求)

使用消息队列异步重试缓存的情况是指,当信息发生变化时,先更新数据库,然后删缓存,如果删除成功就皆大欢喜,如果删除失败,则将需要删除的key发送到消息队列。另外有一个消费者线程从消息队列中获取要删除的key,并根据key删除或更新Redis中的缓存。如果操作失败,则重新发送到消息队列中进行重试。

注:也可以不先尝试删除,直接发送给消息队列,让消息队列

举例来说,假设有一个用户信息表,需要将用户信息缓存在Redis中。如果采用使用消息队列异步重试缓存的方案,可以有以下几个步骤:

  • 当用户信息发生变化时,先更新数据库,并返回成功结果给前端。
  • 尝试去删除缓存,成功则结束操作,失败则将要删除或更新缓存的操作生成一个消息(比如包含key和操作类型),并发送到消息队列中(比如使用Kafka或RabbitMQ)。
  • 另外有一个消费者线程从消息队列中订阅并获取这些消息,并根据消息内容删除或更新Redis中的对应信息。
  • 如果删除或更新缓存成功,则把这个消息从消息队列中移除(丢弃),以免重复操作。
  • 如果删除或更新缓存失败,则执行失败策略,比如设置一个延迟时间或者一个重试次数限制,然后重新发送这个消息到消息队列中进行重试。
  • 如果重试超过一定次数仍然失败,则向业务层发送报错信息,并记录日志。
2.2 Binlog实现异步重试删除

使用binlog实现一致性的基本思路是利用binlog日志来记录数据库的变更操作然后通过主从复制或者增量备份的方式来同步或者恢复数据

举例来说,如果我们有一个主数据库和一个从数据库,我们可以在主数据库上开启binlog日志,并设置从数据库作为它的复制节点。这样,当主数据库上发生任何变更操作时,它会将对应的binlog日志发送给从数据库,从数据库则会根据binlog日志来执行相同的操作,从而保证数据一致性。

另外,如果我们需要恢复某个时间点之前的数据,我们也可以利用binlog日志来实现。首先,我们需要找到对应时间点之前的最近一个全量备份文件,并将其恢复到目标数据库。然后,我们需要找到对应时间点之前的所有增量备份文件(即binlog日志文件),并按照顺序将其应用到目标数据库。这样,我们就可以恢复出目标时间点之前的数据状态了。

在这里插入图片描述

  • 使用 Binlog 实时更新/删除 Redis 缓存。利用 Canal,即将负责更新缓存的服务伪装成一个 MySQL 的从节点,从 MySQL 接收 Binlog,解析 Binlog 之后,得到实时的数据变更信息,然后根据变更信息去更新/删除 Redis 缓存;
  • MQ+Canal 策略,将 Canal Server 接收到的 Binlog 数据直接投递到 MQ 进行解耦,使用 MQ 异步消费 Binlog 日志,以此进行数据同步;

注:binlog日志是MySQL的二进制日志,它记录了对数据库的变更操作,比如插入、更新、删除等。 binlog日志有两个主要作用,一个是主从复制,另一个是增量备份。

主从复制是指在一个主数据库和一个或多个从数据库之间实现数据的同步。主数据库会将自己的binlog日志发送给从数据库,从数据库则会根据binlog日志来执行相同的操作,从而保证数据一致性。这样可以提高数据的可用性和可靠性,也可以实现负载均衡和故障转移。

增量备份是指在全量备份的基础上,定期备份数据库的变更操作。全量备份是指将整个数据库的数据完整地备份到一个文件中。增量备份则是指将每次变更操作对应的binlog日志文件备份到另一个文件中。这样可以减少备份所占用的空间和时间,也可以实现灵活地恢复数据到任意时间点。

至此,我们可以得出结论,想要保证数据库和缓存一致性,推荐采用「先更新数据库,再删除缓存」方案,并配合「消息队列」或「订阅变更日志」的方式来做

3. 延时双删

我们重点在将先更新数据库,在删除缓存。那如果我要先删除缓存,再更新数据库呢?

回顾之前讲的先删除缓存,再更新数据库,它会出现旧值覆盖缓存的问题,那好办,我们直接把这个旧值给删了不就完了吗,延时双删就是这个原理,它的基本思路是:

  1. 先删除缓存
  2. 再更新数据库
  3. 休眠一段时间(根据系统情况确定)
  4. 再次删除缓存

这样做的目的是为了防止在更新数据库后,有其他线程读取到旧的缓存数据,并将其写回缓存,导致数据不一致

举个例子:假设有一个用户信息表,其中有一个字段是用户积分。现在有两个线程A和B同时对用户积分进行操作:

  • 线程A要给用户增加100积分
  • 线程B要给用户减少50积分

如果使用延时双删策略,那么线程A和B的执行过程可能如下:

  • 线程A先删除缓存中的用户信息

  • 线程A再从数据库中读取用户信息,发现用户积分为1000

  • 线程A将用户积分加上100,变为1100,并更新到数据库中

  • 线程A休眠5秒(假设这个时间足够让数据库同步)

  • 线程A再次删除缓存中的用户信息

  • 线程B先删除缓存中的用户信息

  • 线程B再从数据库中读取用户信息,发现用户积分为1100(因为线程A已经更新了)

  • 线程B将用户积分减去50,变为1050,并更新到数据库中

  • 线程B休眠5秒(假设这个时间足够让数据库同步)

  • 线程B再次删除缓存中的用户信息

这样最终结果就是:数据库中的用户积分为1050,缓存中没有该用户信息。当下次有请求查询该用户信息时,就会从数据库中读取并写入到缓存中。这样就保证了数据一致性。

延时双删适用于高并发场景,特别是对数据的修改操作比较频繁,而查询操作比较少的情况。这样可以减轻数据库的压力,提高性能,同时保证数据的最终一致性。延时双删也适用于数据库有主从同步延迟的场景,因为它可以避免在更新数据库后,从库还没有同步完成时,读取到旧的缓存数据,并将其写回缓存。

注: 这个休眠时间 = 读业务逻辑数据的耗时 + 几百毫秒。 为了确保读请求结束,写请求可以删除读请求可能带来的缓存脏数据。


总结

好了,总结一下这篇文章的重点。

Redis与MySQL的双写一致性问题是指在使用缓存和数据库同时存储数据的场景下,如何保证两者的数据一致性。这个问题主要涉及到以下几个方面:

  • 缓存更新策略:缓存更新策略有三种,分别是先更新缓存再更新数据库先更新数据库再更新缓存先删除缓存再更新数据库先更新数据库再删除缓存。每种策略都有可能导致数据不一致的情况。

  • 数据库主从同步延迟:如果使用了主从复制模式来提高数据库的可用性和读写分离能力,那么就可能存在主从同步延迟的问题。也就是说,在主库上执行了写操作后,并不会立即同步到从库上。这样,在读取数据时,如果从主库读取,则可能获取到最新的数据;而如果从从库读取,则可能获取到旧的数据。这样也会导致与缓存中的数据不一致。

为了解决这些问题 , 可以采用以下几种方法 :

  • 采用先删除缓存,再更新数据库方案,在并发场景下依旧有不一致问题,解决方案是延迟双删,但这个延迟时间很难评估。
  • 采用先更新数据库,再删除缓存方案,为了保证两步都成功执行,需配合消息队列订阅变更日志的方案来做,本质是通过重试的方式保证数据最终一致性
  • 采用先更新数据库,再删除缓存方案,读写分离 + 主从库延迟也会导致缓存和数据库不一致,缓解此问题的方案是延迟双删,凭借经验发送延迟消息到队列中,延迟删除缓存,同时也要控制主从库延迟,尽可能降低不一致发生的概率。

总之,根据场景选择适合自己的方案


参考文章

Redis与MySQL双写一致性方案解析 - 知乎 (zhihu.com)

美团二面:Redis与MySQL双写一致性如何保证? - 掘金 (juejin.cn)

缓存和数据库一致性问题,看这篇就够了 - 知乎 (zhihu.com)


终于水完了这篇文章,得去背面经了!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429948.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10万字智慧政务大数据治理平台解决方案(word)

本资料来源公开网络,仅供个人学习,请勿商用,如有侵权请联系删除。 1 项目整体建设规划方案 按照《省人民政府关于推进数字政府建设的指导意见》(鄂政发(2019) 4号)的规则要求,结合XX市“互联网政府服务”建设现状&…

【Android笔记93】Android小案例(三)之模仿小米商城(首页商品展示界面)

这篇文章,主要介绍Android小案例(三)之模仿小米商城(首页商品展示界面)。 一、模仿小米商城(首页布局) 1.1、首页运行效果 这篇文章实现的首页布局界面如下所示: 1.2、实现思路 首页轮播图,这里采用一个Banner组件实现,不知道的可以看下我之前写的一篇文章【【And…

DUET详解草稿

详解VLN动机:流程拓扑图Text EncoderCoarse-scale Cross-modal EncoderNode embeddingGraph-aware cross-modal encodingGlobal action predictionFine-scale Cross-modal EncoderVisual EmbeddingFine-grained cross-modal reasoningLocal action prediction and o…

heic格式怎么改成jpg?

你想知道heic格式怎么改成jpg吗?当我们面对heic格式图片时,很有可能就会遇到无法打开图片的情况。因为heic与JPG相比,heic格式占用空间更少,图像质量更无损。HEIC格式照片支持iOS11和macOS High Sierra(10.13)及更高版本。但是这种…

【数据结构】链表 详解

我们不废话,直入正题。引入什么是链表?来看看百度怎么说:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点&#…

STM32+ESP8266点灯(STA 模式)点灯(2)

1、简介 STM32ESP8266点灯(APSTA 模式)点灯(1)一文已经通过串口助手实现与网络调试助手透传,本文通过STM32单片机替代网络调试助手,实现远程点灯。 2、单片机配置 2.1 cubemax配置 2.1.1 RCC配置 2.1.2…

华为手表开发:WATCH 3 Pro(13)websocket 请求数据到服务器

华为手表开发:WATCH 3 Pro(13)websocket 请求数据到服务器初环境与设备文件夹:文件重点核心代码:app.js新增一个文本输入框index.hmlindex.cssindex.js初 希望能写一些简单的教程和案例分享给需要的人 鸿蒙可穿戴开发…

Detectron2小白教程之安装试用篇

这里写自定义目录标题官方安装说明1、安装python2、安装opencv3、安装nvdia显卡驱动4、安装cuda11.75、安装pytorch6、安装nijia7、clone并安装detectron8、试运行detectronDetectron2是facebook主导的支持图像分类(Image classification),目标检测(Object detectio…

【Java EE】-文件IO

作者:学Java的冬瓜 博客主页:☀冬瓜的主页🌙 专栏:【JavaEE】 分享: 主要内容:文件的认识,绝对路径相对路径,二进制文件文本文件,File的方法的使用,普通文件的…

如何用docker容器部署nuxt3项目

Nuxt3是基于Vue3的一个开发框架,基于服务器端渲染SSR,可以更加方便的用于Vue的SEO优化。 Nuxt 3.0 新特性包括: 更轻量:以现代浏览器为目标的服务器部署和客户端产物最多可缩小 75 倍 更快:基于 nitro 提供动态代码…

CentOS 查找未挂载磁盘,格式化后并挂载

1.查看当前Linux服务器磁盘分区 1 # df -Th 2.查看当前服务器硬盘 1 # fdisk -l 图中磁盘 /dev/sdb为未挂载的磁盘(磁盘符号依次为sda、sdb、sdc……) 后面以sdb为例 3.磁盘分区 fdisk /dev/sdb (依次键入) n 回车 p 回车 1 回车 (此处…

总结822

学习目标: 4月(复习完高数18讲内容,背诵23篇短文,熟词僻义300词基础词) 学习内容: 暴力英语:早上背了《the method of learning 》,之后默写了一遍,还不是很熟练。抄写了前10篇的短…

BVH ==>SMPL for Unified

BVH to SMPL 将BVH文件转换为SMPL模型,需要使用专业的3D建模软件。 例如 Blender或Maya。 Steps 导入BVH文件到建模软件中。将BVH文件应用于一个适当的人体模型。将人体模型转换为SMPL模型。导出SMPL模型文件。 Realization https://github.com/Meshcapade/SMPL_b…

软件质量保证与软件测试复习笔记(第一周总体介绍+黑盒测试详细)

第一周 2.23 (总体性介绍) 软件测试的定义 常用术语解释 错误 缺陷 故障 失效 测试和测试用例、测试过程 出现软件缺陷的原因 软件开发的主要环节 测试过程的生命周期模型 软件测试的本质是针对要测试的内容确定一组测试用例 测试用…

电脑无法正常关机?点了关机又会自动重启

“真木马”相信不少朋友遇到过电脑关机自动重启现象,一点关机,但随后电脑有会进入重启状态,就是一直不会停,属实是很难崩。 目录 一、问题症状 二、问题原因 三、解决方案 方法一: 1.关闭系统发生错误时电脑自动…

生命在于折腾——PicGo+Minio+Typora图床搭建

好久没更新了,前段时间太忙了,还有些摆烂,所以,嗯,懂得都懂,写这篇博客前一天我还在椅子上坐了两个小时,思考人生的意义。 话不多说,开始吧。 一、起因 因为好久没管过博客&#…

显存不够用?一种大模型加载时节约一半显存的方法

Loading huge PyTorch models with linear memory consumption 本文主要介绍了一种用于加载巨大模型权重时节约接近一半显存的方法 首先,创建一个模型: import torch from torch import nnclass BoringModel(nn.Sequential):def __init__(self):super().__init__…

NIFI大数据进阶_实时同步MySql的数据到Hive中去_可增量同步_实时监控MySql数据库变化_实际操作_03---大数据之Nifi工作笔记0035

然后我们来操作一下首先创建一个处理器组名字是: MysqlToHive_timely 创建组以后我们进入这个组 然后我们先去添加CaptureChangeMySql这个处理器 拖入处理器以后,我们配置这个处理器 可以看到很多属性,这里我们配置 首先看这个Distrbuted Map Cache Client 这个客户端,我们先来…

ChatGPT聊天机器人程序

ChatGPT聊天机器人程序是一种基于人工智能技术的智能对话程序,利用ChatGPT等自然语言处理模型和算法实现与用户的交互,回答问题、提供服务等。 ChatGPT聊天机器人程序通常包括以下模块: 输入模块:用于接收用户输入的信息&…

vmware虚拟机上网设置教程(vmware虚拟机设置网络)

安装vmware后,一般都会有虚拟机能连互联网的需求(如虚拟机中Linux想访问百度),vmware为我们提供了几种连接网络的方式,它们分别是:Bridged(桥接模式)、NAT(网络地址转换模…