从程序员到架构师——缓存层场景

news2024/11/19 4:36:09

读缓存

业务场景

如何将十几秒的查询请求优化成毫秒级?

这次项目针对的系统是一个电商系统。每个电商系统都有个商品详情页。一开始这个页面很简单,只包括商品的图片、介绍、规格、评价等。

刚开始,这个页面打开很快,系统运行平稳可靠。

后来,页面中加了商品推荐,即在商品详情页后面显示一些推荐商品的列表。

再后来,页面中加入了最近成交情况,即显示一下某人在什么时候下单了。

接着,页面中又加入了优惠活动,即显示这个商品都可以参加哪些优惠活动。

当时这个系统里面有5万多条商品数据,数据量并不大,但是每次用户浏览商品详情页时都需要几十条SQL语句,经常出现十几秒才能打开详情页的情况。

项目组开始考虑要怎么优化,重构数据库基本不可能,最好不要改动表结构。大家想到的方案也很通用,就是把大部分商品的详情数据缓存起来,少部分的数据通过异步加载。比如,最近的成交数据通过异步加载,即用户打开商品详情页以后,再在后台加载最近的成交数据,并显示给用户。

本地缓存

本地缓存优缺点:

优点:

  • 读取速度快

    本地缓存不需要远程网络请求去操作内存空间,没有额外的性能消耗,所以读取速度快

  • 不能进行大数据量存储

    本地缓存占用了应用进程的内存空间,比如java进程的jvm内存空间,故不能进行大数据量存储

缺点:

  • 应用程序集群部署时,会存在数据更新问题(数据更新不一致)

    本地缓存一般只能被同一个应用进程的程序访问,不能被其他应用程序进程访问。在单体应用集群部署时,如果数据库有数据需要更新,就要同步更新不同服务器节点上的本地缓存的数据来保证数据的一致性,但是这种操作的复杂度高,容易出错。可以基于redis的发布/订阅机制来实现各个部署节点的数据同步更新。

  • 数据会随着应用程序的重启而丢失

    因为本地缓存的数据是存储在应用进程的内存空间的,所以当应用进程重启时,本地缓存的数据会丢失。

分布式缓存

分布式缓存优缺点

优点:

  • 支持大数据量存储

    分布式缓存是独立部署的进程,拥有自身独自的内存空间,不需要占用应用程序进程的内存空间,并且还支持横向扩展的集群方式部署,所以可以进行大数据量存储。

  • 数据不会随着应用程序重启而丢失

    分布式缓存和本地缓存不同,拥有自身独立的内存空间,不会受到应用程序进程重启的影响,在应用程序重启时,分布式缓存的存储数据仍然存在。

  • 数据集中存储,保证数据的一致性

    当应用程序采用集群方式部署时,集群的每个部署节点都有一个统一的分布式缓存进行数据的读写操作,所以不会存在像本地缓存中数据更新问题,保证了不同服务器节点的 数据一致性。

  • 数据读写分离,高性能,高可用

    分布式缓存一般支持数据副本机制,实现读写分离,可以解决高并发场景中的数据读写性能问题。而且在多个缓存节点冗余存储数据,提高了缓存数据的可用性,避免某个缓存节点宕机导致数据不可用问题。

缺点:

  • 数据跨网络传输,读写性能不如本地缓存

    分布式缓存是一个独立的服务进程,并且和应用程序进程不在同一台机器上,所以数据的读写要通过远程网络请求,这样相对于本地缓存的数据读写,性能要低一些。

缓存中间件技术选型

先将目前比较流行的缓存中间件Memcached、MongoDB、Redis进行简单对比,见表4-1。

在这里插入图片描述

使用MongoDB的公司最少,因为它只是一个数据库,由于它的读写速度与其他数据库相比更快,人们才把它当作类似缓存的存储。

所以接下来就是比较Redis和Memcached,并从中做出选择。

目前,Redis比Memcached更流行,这里总结一下原因,共3点:

  1. 数据结构

    举个例子,在使用Memcached保存List缓存对象的过程中,如果往List中增加一条数据,则首先需要读取整个List,再反序列化塞入数据,接着再序列化存储回Memcached。而对于Redis而言,这仅仅是一个Redis请求,它会直接帮助塞入数据并存储,简单快捷。

  2. 持久化

    对于Memcached来说,一旦系统宕机数据就会丢失。因为Memcached的设计初衷就是一个纯内存缓存。
    通过Memcached的官方文档得知,1.5.18版本以后的Memcached支持Restartable Cache(可重启缓存),其实现原理是重启时CLI先发信号给守护进程,然后守护进程将内存持久化至一个文件中,系统重启时再从那个文件中恢复数据。不过,这个设计仅在正常重启情况下使用,意外情况还是不处理。
    而Redis是有持久化功能的。

  3. 集群

    这点尤为重要。Memcached的集群设计非常简单,客户端根据Hash值直接判断存取的Memcached节点。而Redis的集群因在高可用、主从、冗余、Failover等方面都有所考虑,所以集群设计相对复杂些,属于较常规的分布式高可用架构。

缓存何时存储数据

使用缓存的逻辑如下。

  1. 先尝试从缓存中读取数据。
  2. 若缓存中没有数据或者数据过期,再从数据库中读取数据保存到缓存中。
  3. 最终把缓存数据返回给调用方。

这种逻辑唯一麻烦的地方是,当用户发来大量的并发请求时,它们会发现缓存中没有数据,那么所有请求会同时挤在第2)步,此时如果这些请求全部从数据库读取数据,就会让数据库崩溃。

数据库的崩溃可以分为3种情况:

  1. 单一数据过期或者不存在,这种情况称为缓存击穿
    解决方案:使用互斥锁,异步定时更新

    第一个线程如果发现Key不存在,就先给Key加锁,再从数据库读取数据保存到缓存中,最后释放锁。如果其他线程正在读取同一个Key值,那么必须等到锁释放后才行。关于锁的问题前面已经讲过,此处不再赘述。

    或者

    比如某一个热点数据的过期时间是1小时,那么每59分钟,通过定时任务去更新这个热点key,并重新设置其过期时间。

  2. 数据大面积过期或者Redis宕机,这种情况称为缓存雪崩
    解决方案:设置缓存的过期时间为随机分布或设置永不过期即可

  3. 一个恶意请求获取的Key不在数据库中,这种情况称为缓存穿透
    比如正常的商品ID是从100000到1000000(10万到100万之间的数值),那么恶意请求就可能会故意请求2000000以上的数据。这种情况如果不做处理,恶意请求每次进来时,肯定会发现缓存中没有值,那么每次都会查询数据库,虽然最终也没在数据库中找到商品,但是无疑给数据库增加了负担。这里给出两种解决办法。
    ①在业务逻辑中直接校验,在数据库不被访问的前提下过滤掉不存在的Key。(也可以布隆过滤器)
    ②针对恶意请求的Key存放一个空值在缓存中,防止恶意请求骚扰数据库。

缓存预热

上面这些逻辑都是在确保查询数据的请求已经过来后如何适当地处理,如果缓存数据找不到,再去数据库查询,最终是要占用服务器额外资源的。那么最理想的就是在用户请求过来之前把数据都缓存到Redis中。这就是缓存预热。

其具体做法就是在深夜无人访问或访问量小的时候,将预热的数据保存到缓存中,这样流量大的时候,用户查询就无须再从数据库读取数据了,将大大减小数据读取压力。

如何更新缓存

主要讲的的缓存 与 数据库 双写一致性问题。

更新缓存的步骤特别简单,共两步:更新数据库和更新缓存。但这简单的两步中需要考虑很多问题

  1. 先更新数据库还是先更新缓存?更新缓存时先删除还是直接更新?
  2. 假设第一步成功了,第二步失败了怎么办?
  3. 假设两个线程同时更新同一个数据,A线程先完成第一步,B线程先完成第二步怎么办?

其中,第1个问题就存在5种组合方案,下面逐一进行介绍(以上3个问题因为紧密关联,无法单独考虑,下面就一起说明)。

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

该方案存在的缺陷有两点:

  1. 如果更新数据库失败了,缓存怎么回滚(redis不支持事务回滚);
  2. 事务隔离级别问题

场景举例

  1. 原来缓存中的值是a,两个线程同时更新库存。
  2. 线程A将缓存中的值更新成b,且保存了原来的值a,然后更新数据库。
  3. 线程B将缓存中的值更新成c,且保存了原来的值b,然后更新数据库。
  4. 线程A更新数据库时失败了,它必须回滚,那现在缓存中的值更新成什么呢?理论上应该更新成c,因为数据库中的值是c,但是,线程A里面无从获得c这个值。

如果在线程A更新缓存与数据库的整个过程中,先把缓存及数据库都锁上,确保别的线程不能更新,是否可行?当然是可行的。但是其他线程能不能读取?

假设线程A更新数据库失败回滚缓存时,线程C也加入进来,它需要先读取缓存中的值,这时又返回什么值?

看到这个场景,是不是有点儿熟悉?不错,这就是典型的事务隔离级别场景。所以就不推荐这个组合,因为此处只是需要使用一下缓存,而这个组合就要考虑事务隔离级别的一些逻辑,成本太大。接着考虑别的组合。

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

这种方案的缺陷在于:

  1. 缓存(旧值)与数据库的值(新值)不一致的问题;
  2. 如果加锁的话,大量读请求会卡到锁中;

场景举例

同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

(1)请求A进行写操作,删除缓存

(2)请求B查询发现缓存不存在

(3)请求B去数据库查询得到旧值

(4)请求B将旧值写入缓存

(5)请求A将新值写入数据库

上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

为了解决一致性问题,可以让线程A给Key加锁,因为写操作特别耗时,这种处理方法会导致大量的读请求卡在锁中。

以上描述的是典型的高可用和一致性难以两全的问题,如果再加上分区容错就是CAP(一致性Consistency、可用性Availability、分区容错性Partition Tolerance)了,这里不展开讨论

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

这种方案的缺陷在于:

  1. 更新缓存失败,重试机制存在延时的话,缓存数据跟数据库还是不一致;
  2. 两个线程同时更新存在顺序覆盖问题

场景举例

假设第一步(更新数据库)成功,第二步(更新缓存)失败了怎么办?

因为缓存不是主流程,数据库才是,所以不会因为更新缓存失败而回滚第一步对数据库的更新。此时一般采取的做法是重试机制,但重试机制如果存在延时还是会出现数据库与缓存不一致的情况,不好处理。

假设两个线程A,B

(1)线程A更新了数据库

(2)线程B更新了数据库

(3)线程B更新了缓存

(4)线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

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

假设两个线程同时更新同一个数据,线程A先完成第一步,线程B先完成第二步怎么办?

线程A把值更新成a,线程B把值更新成b,此时数据库中的最新值是b,因为线程A先完成了第一步,所以第二步谁先完成已经不重要了,因为都是直接删除缓存数据。这个问题解决了。

那么,它能解决组合3的第一个问题吗?假设第一步成功,第二步失败了怎么办?这种情况的出现概率与组合3相比明显低不少,因为删除比更新容易多了。虽然这个组合方案不完美,但出现一致性问题的概率较低。

除了组合3会碰到的问题,组合4还会碰到别的问题吗?

是的。假设线程A要更新数据,先完成第一步更新数据库,在线程A删除缓存之前,线程B要访问缓存,那么取得的就是旧数据。这是一个小小的缺陷。那么,以上问题有办法解决吗?

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

延时双删

伪代码:

public void write(String key,Object data){
	redis.delKey(key);
	db.updateData(data);
	Thread.sleep(1000);
	redis.delKey(key);
}

采用这种同步淘汰策略,吞吐量降低怎么办?

ok,那就将第二次删除作为异步的,自己起一个线程,异步删除或者下图的方式

在这里插入图片描述

这个方案其实和先更新数据库,再删除缓存差不多,因为还是会出现类似的问题:假设线程A要更新数据库,先删除了缓存,这一瞬间线程C要读缓存,先把数据迁移到缓存;然后线程A完成了更新数据库的操作,这一瞬间线程B也要访问缓存,此时它访问到的就是线程C放到缓存里面的旧数据。

不过组合5出现类似问题的概率更低,因为要刚好有3个线程配合才会出现问题(比先更新数据库,再删除缓存的方案多了一个需要配合的线程)。

但是相比于组合4,组合5规避了第二步删除缓存失败的问题——组合5是先删除缓存,再更新数据库,假设它的第三步“再删除缓存”失败了,也没关系,因为缓存已经删除了。其实没有一个组合是完美的,它们都有读到脏数据(这里指旧数据)的可能性,只不过概率不同。根据以上分析,组合5相对来说是比较好的选择。

不过这个组合也有一些问题要考虑,具体如下。

  1. 删除缓存数据后变相出现缓存击穿,此时该怎么办?此问题在前面已经给出了方案。
  2. 删除缓存失败如何重试?这个重试可以做得复杂一点,也可以做得简单一点。简单一点就是使用try…catch…,假设删除缓存失败了,在catch里面重试一次即可;复杂一点就是使用一个异步线程不断重试,甚至用到MQ。不过这里没有必要大动干戈。而且异步重试的延时大,会带来更多的读脏数据的可能性。所以仅仅同步重试一次就可以了。
  3. 不可避免的脏数据问题。虽然这个问题在组合5中出现的概率已经大大降低了,但是还是有的。关于这一点就需要与业务沟通,毕竟这种情况比较少见,可以根据实际业务情况判断是否需要解决这个瑕疵。

缓存的高可用设计

设计高可用方案时,需要考虑5个要点。

  1. 负载均衡:是否可以通过加节点的方式来水平分担读请求压力。
  2. 分片:是否可以通过划分到不同节点的方式来水平分担写压力。
  3. 数据冗余:一个节点的数据如果失效,其他节点的数据是否可以直接承担失效节点的职责。
  4. Failover:任何节点失效后,集群的职责是否可以重新分配以保障集群正常工作。
  5. 一致性保证:在数据冗余、Failover、分片机制的数据转移过程中,如果某个地方出了问题,能否保证所有的节点数据或节点与数据库之间数据的一致性(依靠Redis本身是不行的)。

如果对缓存高可用有需求,可以使用Redis的Cluster模式,以上5个要点它都会涉及。关于Cluster的配置方法,可以参考Redis官方文档或其他相关教程。

缓存的监控

在查看缓存使用情况时,一般会监控缓存命中率、内存利用率、慢日志、延迟、客户端连接数等数据。当然,随着问题的深入还可增加其他的指标,这里就不详细说明了。

当时公司采用的是一套自研的管理工具,这套管理工具里包含了监控功能。目前也有很多开源的监控工具,如RedisLive、Redis-monitor。至于最终使用哪种监控工具,则需要根据实际情况而定。

小结

以上方案可以顺利解决读数据请求压垮数据库的问题,目前互联网架构也基本是采取这个方案。

接下来说说不足吧。这个方案主要针对读数据请求量大的情况,或者读数据响应时间很长的情况,而不能应对写数据请求量大的场景。也就是说写请求多时,数据库还是会支撑不住

写缓存

上述讨论了缓存的架构方案,它可以减少数据库读操作的压力,却也存在着不足,比如写操作并发量大时,这个方案不会奏效。那该怎么办呢?

写缓存的思路是后台服务接收到用户请求时,如果请求校验没问题,数据并不会直接落库,而是先存储在缓存层中,缓存层中写请求达到一定数量时再进行批量落库。这里所说的缓存层实际上指的就是写缓存。它的意义在于利用写缓存比数据库高几个量级的吞吐能力来承受洪峰流量,再匀速迁移数据到数据库。

在这里插入图片描述

假设高峰期1秒内有1.5万个预约数据的插入请求。这1.5万个请求如果直接到数据库,那么数据库肯定崩溃。所以把这1.5万个请求落到并发写性能很高的缓存层,然后以2000为单位从缓存层批量落到数据库。数据库如果用批量插入语句,TPS也是可以非常高的,可能达到上万,这样不仅能防止数据库崩溃,还能确保用户的请求得到满足。

业务场景

如何以最小代价解决短期高频写请求

某公司策划了一场超低价预约大型线上活动,在某天9:00~9:15期间,用户可以前往详情页半价预约抢购一款热门商品。根据市场部门的策划方案,这次活动的运营目标是几十万左右的预约量。

为避免活动上线后出现问题,比如数据库被压垮、后台服务器支撑不住(这个倒是小问题,加几台服务器即可)等,项目组必须提前做好预案。这场活动中,领导要求在架构上不要做太大调整,毕竟是一个临时的活动。简单地说就是工期不能太长,修改影响范围不要太大。

项目组分析了一下可能的情况,其他都没问题,唯一没把握的就是数据库。

项目组通过如下逻辑做了一次简单的测算:

假设目标是15分钟完成100万的预约数据插入,并且不是在15分钟内平均插入的。按照以往的经验,有可能在1分钟内就完成90%的预约,也有可能在5分钟内完成80%的预约,这些难以预计。但是峰值流量预估值只能取高,不能取低。所以设计的目标是:用户1分钟内就完成90%的预约量,即90万预约。那么推算出目标的TPS(吞吐量)就是90万/60=1.5万。

原来预约就是个简单的功能,并没有做高并发设计。对它做了一次压力测试,结果最大的TPS是2200左右,与需求值差距较大。

项目组想过分表分库这个方案,不过代码改动的代价太大了,性价比不高。毕竟这次仅仅是临时性市场活动,而且活动运营目标是几十万的预约量,这点数据量采取分表分库的话,未免有些得不偿失。

项目最终采用的方案是不让预约的请求直接插入数据库,而是先存放到性能很高的缓冲地带,以此保证洪峰期间先冲击缓冲地带,之后再从缓冲地带异步、匀速地迁移数据到数据库中。

实现思路

该方案在具体实施过程中要考虑6个问题。

  1. 写请求与批量落库这两个操作同步还是异步?
  2. 如何触发批量落库?
  3. 缓冲数据存储在哪里?
  4. 缓存层并发操作需要注意什么?
  5. 批量落库失败了怎么办?
  6. Redis的高可用配置。

写请求与批量落库这两个操作同步还是异步?

对于同步,写请求提交数据后,当前写操作的线程会等到批量落库完成后才开始启动。这种设计的优点是用户预约成功后,可在“我的预约”页面立即看到预约数据;缺点是用户提交预约后,还需要等待一段时间才能返回结果,且这个时间不定,有可能需要等待一个完整的时间窗

同步会抛出的一些问题:

  1. 用户到底需要等待多久?用户不可能无限期等待下去,此时还需要设置一个时间窗,比如每隔100毫秒批量落库一次。
  2. 如果批量落库超时了怎么办?写请求不可能无限期等待,此时就需要给写请求线程的堵塞设置一个超时时间。
  3. 如果批量落库失败了怎么办?是否需要重试?多久重试一次?
  4. 如果写请求一直堵塞,直到重试成功再返回吗?那需要重试几次?这些逻辑其实与Spring Cloud组件、Hystrix请求合并功能(Hystrix 2018年已经停止更新)等类似。

对于异步,写请求提交数据后,会直接提示用户提交成功。这种设计的优点是用户能快速知道提交结果;缺点是用户提交完成后,如果查看“我的预约”页面,可能会出现没有数据的情况。

如果使用了异步,则上述的第2点和第4点基本不用考虑

异步有两种设计可以选择:

  1. 在“我的预约”页面给用户一个提示:您的预约订单可能会有一定延迟。
  2. 用户预约成功后,直接进入预约完成详情页,此页面会定时发送请求去查询后台批量落库的状态,如果落库成功,则弹出成功提示,并跳转至下一个页面。

其实,第一种方案在实际应用中也经常遇到,不过项目中主要还是使用第二种方案。因为在第二种方案中,大部分情况下用户是感受不到延迟的,用户体验比较好,而如果选择第一种方案,用户还要去思考:这个延迟是什么意思?是不是失败了?这无形中就影响了用户体验。

如何触发批量落库

两种方案:

  1. 写请求满足特定次数后就落库一次,比如10个请求落库一次。
  2. 每隔一个时间窗口落库一次,比如每隔一秒落库一次。

那到底哪种触发方式好呢?当时项目采用的方案是同时使用这两种方式。具体实现逻辑如下。

  1. 每收集一次写请求,就插入预约数据到缓存中,再判断缓存中预约的总数是否达到一定数量,达到后直接触发批量落库。
  2. 开一个定时器,每隔一秒触发一次批量落库。

在这里插入图片描述

通过以上操作,既避免了触发方案一数量不足、无法落库的情况,也避免了方案二因为瞬时流量大而使待插入数据堆积太多的情况。

缓存数据存储在哪里

缓存数据不仅可以存放在本地内存中,也可以存放在分布式缓存中(比如Redis),其中最简单的方式是存放在本地内存中。

但是,Hystrix的请求合并也是存放在本地内存中,为什么不直接使用Hystrix?这是因为写缓存与Hystrix的请求合并有些不一样,请求合并更多考虑的是读请求的情况,不用担心数据丢失,而写请求需要考虑容灾问题:如果服务器宕机,内存数据就会丢失,用户的预约数据也就没有了。

其实也可以考虑使用MQ来当缓存层,MQ的一个主要用途就是削峰,很适合这种场景。不过这个项目选择了Redis,因为服务本身已经依赖Redis了。另外,项目想要使用批量落库的功能,项目组知道如何一次性从Redis中取多个数据项,但是还没有试过批量消费MQ的消息。

在这里插入图片描述

缓存层并发操作需要注意什么

这次并不需要迁移海量数据,因为每隔一秒或数据量凑满10条,数据就会自动迁移一次,所以一次批量插入操作就能轻松解决这个问题,只需要在并发性的设计方案中保证一次仅有一个线程批量落库即可。这个逻辑比较简单,就不赘述了

批量落库失败了怎么办

在这里插入图片描述

Redis的高可用配置

目前,Redis共支持两种备份方式

在这里插入图片描述

另外,Redis还有一个主从功能,这里就不展开了。如果公司已经存在一个统一管理的Redis集群方案,直接复用即可,至少运维有保障。而如果需要从0开始搭建,最简单的解决方案如下。

  1. 先使用简单的主从模式。
  2. 然后在Slave Redis里使用快照(30秒一次)+AOF(一秒一次)的配置。
  3. 如果Master Redis宕机了,千万不要直接启动,先把Slave Redis升级为Master Redis。
  4. 这时代码里已经有预案了,写缓存如果失败直接落库。

不过这个方案有个缺点,即一旦系统宕机,手动恢复时大家就会手忙脚乱,但数据很有保障。

小结

这个项目经过两周左右就上线了,上线之后的某次活动中,后台日志和数据库监控一切正常。活动一共收到几十万的预约量,达到了市场预期的效果。

接下来再说说不足。写缓存这个解决方案可以缓解写数据请求量太大、压垮数据库的问题,但其不足还是比较明显的。

  1. 此方案缓解的只是短时(活动期间)数据库压力大的问题,当写数据量长期非常大时,这个方案是解决不了问题的。
  2. 此方案适合每个写操作都独立的情况,如果写操作之间存在竞争资源,比如商品库存,这个方案就无法覆盖。

数据收集

上个方案虽然可以减少数据库写操作的压力,但是也存在一些缺陷,比如需要长期高频插入数据时这种场景。

业务场景

因业务快速发展,某天某公司的日活用户高达500万,基于当时的业务模式,业务侧要求根据用户的行为做埋点,旨在记录用户在特定页面的所有行为,以便开展数据分析,以及与第三方进行费用结算

当然,在数据埋点的过程中,业务侧还要求在后台能实时查询用户行为数据及统计报表。这里的“实时”并不是严格意义上的实时,对于特定时间内的延迟业务方还是能接受的,为确保描述的准确性,可以称之为准实时。

原始数据结构

在这里插入图片描述

通过以上数据结构,在后台查询原始数据时,业务侧不仅可以将城市(根据经纬度换算)、性别(需要从业务表中抽取)、年龄(需要从业务表中抽取)、目标类型、目标ID、事件动作等作为查询条件来实时查看用户行为数据,还可以从时间(天/周/月/年)、性别、年龄等维度实时查看每个目标ID的总点击数、平均点击次数、每个页面的转化率等作为统计报表数据(当然,关于统计的需求还很多,这里只是列举了一小部分)。

为了实现费用结算这个需求,需要收集的数据结构见表6-2(再次强调,该数据结构只是示例,并非真实的业务场景数据)。

在这里插入图片描述

技术选型思路

根据以上业务场景,项目组提炼出了6点业务需求,并针对业务需求梳理了技术选型相关思路

  1. 原始数据海量:对于这一点,初步考虑使用HBase进行持久化。
  2. 对于埋点记录的请求响应要快:埋点记录服务会把原始埋点记录存放在一个缓存层,以此保证响应快速。关于这一点有多个缓存方案,稍后展开讨论。
  3. 可通过后台查询原始数据:如果直接使用HBase作为查询引擎,查询速度太慢,所以还需要使用Elasticsearch来保存查询页面上作为查询条件的字段和活动ID。
  4. 各种统计报表的需求:数据可视化工具也有很多选择,比如Kibana、Grafana等,考虑到使用过程的灵活性,最终选择自己设计功能。
  5. 能根据埋点日志生成费用结算数据:将费用结算数据保存在MySQL中。
  6. 需要一个框架将缓存中的数据进行处理,并保存到Elasticsearch、HBase和MySQL中:因为业务有准实时查询的需求,所以需要使用实时处理工具。目前流行的实时处理工具主要为Storm、Spark Streaming、Apache Flink这3种,稍后也会展开说明。

在这里插入图片描述

使用什么技术保存埋点数据的第一现场

目前关于快速保存埋点数据的技术主要分为Redis、Kafka、本地日志这3种,针对这里的业务场景,项目组最终选择了本地日志

那么,为什么不使用Redis或Kafka呢?先来说说Redis的AOF机制,这在写缓存那一章也有讲过。

Redis的AOF机制会持久化保存Redis所有的操作记录,用于服务器宕机后的数据还原。那Redis什么时候将AOF落盘呢?

在Redis中存在一个AOF配置项appendfsync,如果appendfsync配置为everysec,则AOF每秒落盘一次,不过这种配置方式有可能会丢失一秒的数据;如果appendfsync配置成always,每次操作请求的记录都是落盘后再返回成功信息给客户端,不过使用这种配置方式系统运行会很慢。因为对埋点记录的请求要求响应快,所们该项目没有选择Redis。

接下来讨论一下Kafka的技术方案。

Kafka的冗余设计是每个分区都有多个副本,其中一个副本是Leader,其他副本都是Follower,Leader主要负责处理所有的读写请求,并同步数据给其他Follower。

那么Kafka什么时候将数据从Leader同步给Follower?Kafka的Producer配置中也有acks配置项,其值有3种。

  1. acks=0:不等Leader将数据落到日志,Kafka直接返回完成信号给客户端。这种方式虽然响应快,但数据持久化没有保障,数据如果没有落到本地日志,系统就会出现宕机,导致数据丢失。
  2. acks=1:等Leader将数据落到本地日志,但是不等Follower同步数据,Kafka就直接返回完成信号给客户端。
  3. acks=all:等Leader将数据落到日志,且等min.insync.replicas个Follower都同步数据后,Kafka再返回完成信号给客户端。这种配置方式下虽然数据有保证,但响应慢。

通过以上分析可以发现,使用Redis与Kafka都会出现问题。

如果想保证数据的可靠性,必然需要牺牲系统性能,那有没有一个方案可以性能和可靠性兼得呢?有。项目组最终决定把埋点数据保存到本地日志中。

使用什么技术收集日志数据到持久化层

关于这个问题,最简单的方式是通过Logstash直接把日志文件中的数据迁移到Elasticsearch,但会有一个问题:业务侧要求存放Elasticsearch中的记录(包含城市、性别、年龄等原始数据,这些字段需要调用业务系统的数据进行抽取),而这些原始数据日志文件中并没有,所以中间需要调用业务系统来获取一些数据跟日志文件的数据合起来加工。基于这个原因,项目组并没有选择直接从Logstash到Elasticsearch。

如果坚持通过Logstash把日志文件的数据迁移到Elasticsearch,这里分享3种实现方式。

  1. 自定义filter:先在Logstash自定义的Filter(过滤器)里封装业务数据,再保存到Elasticsearch。因为Logstash自定义的Filter是使用Ruby语言编写的,也就是说需要使用其他语言编写业务逻辑,所以此次项目中Logstash自定义Filter的方案被排除了。
  2. 修改客户端的埋点逻辑:每次记录埋点的数据发送到服务端之前,先在客户端将业务的相关字段提取出来再上传到服务端。这个方法也直接被业务端否决了,理由是后期业务侧每更新一次后台查询条件,就需要重新发一次版,实在太麻烦了。
  3. 修改埋点服务端的逻辑:每次服务端在记录埋点的数据发送到日志文件之前,先从数据库获取业务字段组合埋点记录。这个方法也被服务端否决了,因为这种操作会直接影响每个请求的效率,间接影响用户体验。

另外,没有选择用Logstash直接保存到持久化层还有两点原因。

  1. 日志文件中的数据需要同时到Elasticsearch和HBase两个输出源,因Logstash的多输出源基于同一个Pipeline(管道),如果一个输出源出错了,另一个输出源也会出错,即两者之间会互相影响。
  2. MySQL中需要生成费用结算数据,而费用结算数据需要通过分析埋点的数据来动态计算,显然Logstash并不适用于这样的业务场景。

在此处的业务场景中,项目组最终决定引入一个计算框架,此时整个解决方案的架构如图6-2所示。

在这里插入图片描述

实际上,引入实时计算框架是为了在原始的埋点数据中填充业务数据,并统计埋点数据生成费用结算数据,最后分别保存到持久层中。

最后,关于Logstash还需要强调几点。

Logstash系统是通过Ruby语言编写的,资源消耗大,所以官方又推出了一个轻量化的Filebeat。系统可以使用Filebeat收集数据,再通过Logstash进行数据过滤。如果不想使用Logstash的强大过滤功能,可以直接使用Filebeat来收集日志数据发送给Kafka。

但问题又来了,Filebeat是使用轮询方式采集文件变动信息的,存在一定延时(有时候很大),不像Logstash那样可直接监听文件变动,所以该项目最终选择继续使用Logstash(资源消耗在可接受范围内)。

为什么使用Kafka

Kafka是LinkedIn推出的开源消息中间件,它天生是为收集日志而设计的,而且具备超高的吞吐量和数据量扩展性,被称作无限堆积。

因此它特别适合处理日志收集这种场景。

有这么一个疑问:

logstash 可以直接把本地日志推送到es里面,中间为什么还要加个 kafka 中间件?

Logstash 是一个数据处理管道,可用于收集、解析和转换来自不同来源的数据,然后再将其发送到 Elasticsearch 进行索引和分析。虽然 Logstash 可以直接将本地日志推送到 Elasticsearch,但使用 Kafka 这样的消息队列作为 Logstash 和 Elasticsearch 之间的缓冲区可以提供几个好处:

  1. 使用 Kafka 的主要好处之一是它可以充当 Logstash 和 Elasticsearch 之间的缓冲区,这有助于解耦两个系统并提供更好的容错能力。如果 Elasticsearch 出现故障或遇到问题,Kafka 可以继续缓冲传入的日志,直到 Elasticsearch 重新上线。这有助于防止数据丢失,并确保最终对所有日志进行索引和分析
  2. 使用 Kafka 的另一个好处是,它可以帮助在多个 Elasticsearch 节点之间分配索引日志的负载。通过使用 Kafka 作为缓冲区,Logstash 可以将日志发送到多个 Kafka 分区,然后这些分区可以被多个 Elasticsearch 节点并行使用。这有助于提高索引吞吐量并降低单个 Elasticsearch 节点过载的风险
  3. 使用 Kafka 可以为日志收集系统提供更好的可扩展性和灵活性。Kafka 可以充当从不同来源收集日志的中心枢纽,然后多个 Logstash 实例可以使用这些日志进行处理和索引。这有助于在多个节点之间分配日志处理的负载,并为系统提供更好的可伸缩性和容错能力

总的来说,虽然 Logstash 可以直接将本地日志推送到 Elasticsearch,但使用 Kafka 作为 Logstash 和 Elasticsearch 之间的缓冲区可以在容错、可扩展性和灵活性方面提供一些好处

使用什么技术把Kafka的数据迁移到持久化层

为了把Kafka的数据迁移到持久层,需要使用一个分布式实时计算框架,原因有两点。

  1. 数据量特别大,为此需要使用一个处理框架来将上亿的埋点数据每天进行快速分析和处理(且必须使用多个节点并发处理才来得及),再存放到Elasticsearch、HBase和MySQL中,即大数据计算,因此它有分布式计算的诉求。
  2. 业务要求实时查询统计报表数据,因此需要一个实时计算框架来处理埋点数据。

目前流行的分布式实时计算框架有3种:Storm、Spark Stream、Apache Flink。那么使用哪个更好呢?

这3种都可以选用,就看公司的具体情况了。比如公司已经使用了实时计算框架,就不再需要考虑这个问题;如果公司还没有使用,那就看个人喜好了。

笔者更喜欢用Apache Flink,不仅因为它性能强(阿里采用这项技术后,活动期间一秒内能够处理17亿条数据),还因为它的容错机制能保证每条数据仅仅处理一次,而且它有时间窗口处理功能。

关于流处理的容错机制、时间窗口这两个概念,具体展开说明一下。

在流处理这个过程中,往往会引发一系列的问题,比如一条消息的处理过程中,如果系统出现故障该怎么办?会重试吗?如果重试会不会出现重复处理?如果不重试,消息是否会丢失?能保证每条消息最多或最少处理几次?

在不同流处理框架中采取不同的容错机制,能够保证不一样的一致性。

1)At-Most-Once:至多一次,表示一条消息不管后续处理成功与否只会被消费处理一次,存在数据丢失的可能。

2)Exactly-Once:精确一次,表示一条消息从其消费到后续的处理成功只会发生一次。

3)At-Least-Once:至少一次,表示一条消息从消费到后续的处理成功可能会发生多次,存在重复消费的可能。

以上3种方式中,Exactly-Once无疑是最优的选择,因为在正常的业务场景中,一般只要求消息处理一次,而Apache Flink的容错机制就可以保证所有消息只处理一次(Exactly-Once)的一致性,还能保证系统的安全性,所以很多人最终都会使用它。

接下来说说Apache Flink的时间窗口计算功能

假定实时计算框架收到消息的时间是2秒后,有一条消息中的事件发生时间是6:30,因接收到消息后处理的时间延后了2秒,即变成了6:32,所以当计算6:01~6:30的数据和时,这条消息并不会计算在内,这就不符合实际的业务需求了。

在实际业务场景中,如果需要按照时间窗口统计数据,往往是根据消息的事件时间来计算。Apache Flink的特性恰恰是使用了基于消息的事件时间,而不是基于计算框架的处理时间,这也是它的另一个撒手锏。

整体方案

在这里插入图片描述

秒杀架构

对于秒杀架构设计而言,其难点在于僧多粥少,因此设计秒杀架构时,一般需要遵循商品不能超卖下单成功的订单数据不能丢失服务器和数据库不能崩溃尽量别让机器人抢走商品这4个原则。

业务场景

某一次公司策划了一场秒杀活动,该活动提供了100件特价商品(商品价格非常低),供用户于当年10月10日22点10分0秒开始秒杀。

当时平台已经积累了几千万的用户量,预计数十万的用户对这些特价商品感兴趣。根据经验,特价商品一般会在1~2秒内被一抢而光,剩余时间涌进来的流量用户只能看到秒杀结束界面,因此预测秒杀开启那一瞬间会出现一个流量峰值。

这也是一场临时性的活动,领导要求别加太多服务器,也别花太多时间重构架构,也就是说需要以最小的技术代价来应对这次秒杀活动。

整体思路

其实秒杀架构的设计方案就是一个不断过滤请求的过程。从系统架构层面来说,秒杀系统的分层设计

思路如图7-1所示。

在这里插入图片描述

在图7-1中发现,秒杀系统的架构设计目标是尽量在上层处理用户请求,不让其往下层游动。那具体如何实现呢?

由于整个秒杀系统涉及多个用户操作步骤,所以解决如何将请求拦截在系统上游这个问题时,需要结合实际业务流程,将用户的每个操作步骤考虑在内。

这里通过一张图来描述秒杀系统的具体业务流程,如图7-2所示

在这里插入图片描述

接下来就按照秒杀系统的业务流程,来一步步讲解如何将请求拦截在系统上游

浏览页面如何将请求拦截在上游

在以往的秒杀系统架构经历中,曾出现过这么一种状况:当时把系统的方方面面都考虑到了,但是活动一上线,第三方监控系统就显示异常,检查后发现所有服务器的性能指标都没问题,唯独出口带宽有问题,它被占满了。

所以在之后的项目中,静态资源尽量使用CDN(内容分发网络),如果涉及PC网站,还必须首先进行前后端分离

那如果是动态的请求该怎么办?有以下3种实现方式。

  1. 评论、商品详情、购买数量等相关的请求,一般都是通过JS(JavaScript)在后台动态调用。在这个场景中,可以把动态的数据与页面进行整合,比如把每个秒杀商品的详情页面变成静态页面,然后再放入CDN。如果觉得改造太大,也可以把它放在Redis缓存中,不过笔者更倾向于CDN;
  2. 判断服务器时间并设置开启秒杀的标识。一般页面中都有JS,它通过访问服务器获取服务器时间,然后根据时间开启秒杀下单的按钮,即判断秒杀开始时,会将下单按钮设置为可用。针对获取服务器时间的这个请求,把它放在静态资源或负载均衡那层即可,这样用户请求就不会进入系统下游。
  3. 判断秒杀结束。具体做法是将秒杀结束的标识放在Cookie中,如果Cookie中没有结束标识,请求就会进入后台服务器,后台服务器判断本地内存没有结束标识,就会进入缓存,如果缓存中也没有结束标识,那就说明秒杀没有结束。

总体来说,对于浏览页面的用户行为,需要把用户请求尽量拦截在CDN、静态资源或负载均衡侧,如果确实做不到,也要拦截在缓存中。

下单页面如何将请求拦截在上游

用户进入下单页面时,主要有两个操作动作:进入下单页面、提交订单。下面讲解如何在这两个环节中将请求拦截在系统上游。

进入下单页面

为了防止别人通过爬虫抓取下单页面信息,从而给服务器增加压力,需要在下单页面做以下两层防护,从而防止恶意请求重复提交。

  1. 页面URL后台动态获取:按照正常的活动设计流程,用户只有在秒杀活动开启后才可进入下单页面,但难免有人在活动开启前直接获取其URL并不断刷新,这样恶意请求就到了后台服务器。虽然后台服务器也可以拦截恶意请求,但是这会给它徒增不少压力。此时主要使用一个特别的URL进行处理(不把它放在静态页面中,而是通过后台动态获取)。前面介绍了JS可以用来判断秒杀开始时间,秒杀时间一到,它便可以通过另一个请求获取这个URL。
  2. 用户点击下单页面的购买按钮后,将此按钮设为Disable(不可用),防止用户不断点击它。

提交订单

秒杀系统架构方案的核心是订单提交,因为这个步骤的逻辑最复杂,而其他步骤仅涉及页面展示的逻辑,针对高并发问题使用缓存或者CDN进行处理难度不大。

因此,在订单提交环节,要想尽一切办法在系统各个分层中把一些不必要的请求过滤掉。

  1. 网关层面过滤请求

    对系统而言,如果可以在网关层面拦截用户请求,那么这个方案的性价比就很高。要是能在这一层过滤95%以上的请求,整个系统也将很稳定。

    那在网关层面如何实现请求过滤呢?可以做3种限制。

    • 限定每个用户的访问频率,比如每5秒下单一次;
    • 限定每个IP的访问频率。这种方式是为了避免有人通过机器人自动下单,导致错杀真实用户。
    • 把一个时间段内的请求拦截掉一定比例,或者只允许特定数量的请求进入后台服务器。这里可以使用限流的漏桶或令牌桶算法,第12章将详细展开。
  2. 后台服务器过滤请求

    请求进入后台服务器后,目标已经不是如何过滤请求了,而是如何保证特价商品不超卖,以及如何保证特价商品订单数据的准确性。

    具体如何实现呢?主要考虑以下4点。

    • 商品库存放入缓存Redis中:如果每个请求都前往数据库查询商品库存,数据库将无法承受,因此需要把商品库存放在缓存中,这样每次用户下单前,就先使用decr操作扣减库存,判断返回值。如果Redis的库存扣减后小于0,说明秒杀失败,将库存用incr操作恢复;如果Redis的库存扣减后不小于0,说明秒杀成功,开始创建订单。

      把库存放入Redis时,下单的逻辑都是基于缓存的库存为第一现场。但是如果这时候有别的服务或者代码修改了数据库里面的库存,怎么办?这时的做法就是确保在秒杀期间不做上架或修改库存之类的业务操作,即不通过技术,而是通过业务流程来保证。

    • 订单写入缓存中:在第5章介绍写缓存时提过一个方案,即订单数据先不放入数据库,而是放到缓存中,然后每隔一段时间(比如100毫秒)批量插入一批订单。用户下单后,首先进入一个等待页面,然后这个页面向后台定时轮询订单数据。轮询过程中,后台先在Redis中查询订单数据,查不到就说明数据已经落库,再去数据库查询订单数据,查到后直接返回给用户,用户收到消息通知后可以直接进入付款页面支付;在数据库查询订单数据时,查不到说明秒杀失败(理论上不会查不到,如果一直查不到就需要抛出异常并跟踪处理)。

    • 订单批量落库:需要定期将订单批量落库,且在订单落库时扣减数据库中的库存。这个做法和第6章中的写缓存一样,这里不再重复。

    • Redis停止工作(挂掉)怎么办:虽然讲了这么多关于后台服务器的逻辑,在秒杀架构里面,最重要的反而是网关层的限流,它挡住了大部分的流量,进入后台服务器的流量并不多。不过仍然要考虑针对Redis停止工作的情况,分别处理前面的3种状况。

      比如读Redis中的库存时,如果失败了,那就让它直接去数据库扣减库存,把那些incr和decr的逻辑放到数据库去;若是把订单写入缓存的时候失败了,那就直接将订单数据写入数据库中,然后就不需要处理后面批量落库的逻辑了。

以上就是订单提交操作的架构设计,不难看出它主要是在网关层和后台服务器进行相关设计。

付款页面如何将请求拦截在上游

在付款页面不需要再过滤用户请求了。在这个环节,除了保障数据的一致性外,还有一个要点:如果业务逻辑中出现了一个订单未及时付款而被取消的情况,记得把数据库及Redis的库存加回去。关于数据的一致性,后面的章节会专门展开,这里就不单独讨论了。

整体服务器架构

再来回顾一下秒杀系统的分层思路,这也是秒杀系统的整体服务器架构方案

在这里插入图片描述

为了保障秒杀系统的高可用性,在整体服务器架构中,需要保证图7-1中所有的层级都是高可用的。因此,静态资源服务器、网关、后台服务器均需要配置负载均衡,而缓存Redis和数据库均需要配置集群模式。

整体服务器架构中还有一个重要组成部分——MQ,因为这次的秒杀架构方案中不涉及它的设计逻辑,所以并未在上面的分层中提及它。不过,服务间触发通知时,就需要使用它了,因此也需要保证它是高可用的(这里要把主从、分片、Failover机制都考虑进去)。

小结

表7-1中整理了一份秒杀系统设计Checklist,供大家参考。

在这里插入图片描述

这个场景中还有以下3个要点需要注意。

  1. 假设后台某服务因秒杀崩溃了,如何避免其他服务雪崩?这一点会在第10章详细展开。
  2. 网关层的限流。这一点会在第11章详细展开。
  3. 付款的数据一致性。这一点会在第13章详细展开。

其实笔者之前也做过秒杀架构,但是那时候的逻辑是,必须保证前面100名的客户可以抢到商品,并没有做限流等措施,因此后台服务器的压力很大,经常出问题。

虽然要保证前100名客户的订单成功,但是前面100名不一定就是第一时间点击秒杀按钮的客户,有些人网速快,有些人网速慢。另外,普通客户肯定没有专门“薅羊毛”的人操作快。对于普通客户来说,随机决定比单纯比快要更有机会下单成功。

后来,基本上秒杀架构都会设计限流。有一些秒杀的代码是在前端的JS中随机抛弃掉一些请求,这其实也是某种意义上的限流,只不过不太合理,应尽量避免。

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

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

相关文章

控制台里的神秘代码 \033[

“\033[”这串字符在控制台里有特殊的应用。 在串口工具里&#xff08;如sscom&#xff09;看着是一串字符。 在控制台里却可以改变字体颜色&#xff0c;显示进度条&#xff0c;甚至字符动画。 1 字体控制 #include <stdio.h>int main() {printf("以下是测试文字&…

MM 采购凭证的交货成本 表

如上图的交货成本表再 EKBZ表里面

9.1 I/O模型

目录 I/O基本概念 同步和异步 阻塞和非阻塞 五种I/O模型 五种I/O模型比较 I/O基本概念 I/O即数据的读取&#xff08;接收&#xff09;或写入&#xff08;发送&#xff09;操作 通常用户进程中的一个完整I/O分为两个阶段 用户进程空间<-->内核空间 内核空间<--&…

【GPT】中文通用大模型梳理与测评(C-Eval 、AGIEval、MMLU、SuperCLUE)

文章目录 概述申请后直接使用大模型开源可本地部署 通识数据集测评&#xff08;C-Eval 、AGIEval、MMLU、SuperCLUE&#xff09;自媒体报道SuperCLUE&#xff1a;中文通用大模型综合性基准C-Eval&#xff1a;中英测评&#xff08;清华上交提出&#xff09;当前排名&#xff08;…

【Unity编辑器扩展】(三)PSD转UGUI Prefab, 一键拼UI/同步字体/自动9宫切图(完结篇)

工具效果&#xff1a; 第一步&#xff0c;把psd图层转换为可编辑的节点树&#xff0c;并自动解析UI类型、自动绑定UI子元素&#xff1a; 第二步, 点击“生成UIForm"按钮生成UI预制体 (若有UI类型遗漏可在下拉菜单手动点选UI类型)&#xff1a; 验证一键生成UI效果: 书接上…

微信支付接口常用参数及证书区分

注意&#xff1a;服务商模式下&#xff0c;均是使用服务商的以下信息 1. 证书 1.1商户api证书&#xff08;v2和v3接口都需要使用&#xff09; 1.1.1获取方式&#xff1a; 什么是商户API证书&#xff1f;如何获取商户API证书&#xff1f; &#xff08;商户api证书 &#xff…

【Spring AOP】面向切面编程,面向切面编程是面向对象编程的孪生兄弟嘛?且听我细细道来! ! !

前言: 大家好,我是良辰丫,面向切面编程和面向对象编程是两种几乎不同的编程方式,并不是所谓的孪生兄弟,但是我们可以说面向切面编程是面向对象编程的一种补充和完善,到底是什么意思呢?请跟随良辰的步伐往下瞧! ! !&#x1f48c;&#x1f48c;&#x1f48c; &#x1f9d1;个人主…

机器学习7:特征工程

在传统的软件工程中&#xff0c;核心是代码&#xff0c;然而&#xff0c;在机器学习项目中&#xff0c;重点则是特征——也就是说&#xff0c;开发人员优化模型的方法之一是增加和改进其输入特征。很多时候&#xff0c;优化特征比优化模型带来的增益要大得多。 笔者曾经参与过一…

【初识 Docker | 中级篇】 Docker 中使用 docker-compose 安装 Nacos

文章目录 前言一、安装 docker1、安装docker2、安装docker-compose 二、Nacos 单机安装1.创建配置文件1.1.创建目录1.2.创建nacos-logback.xml1.3.创建application.properties1.4.创建docker-compose.yml 2.nacos数据库表结构3.启动Nacos容器 总结 前言 可以按照以下步骤在 Do…

Text2Video-Zero:Text-to-Image扩散模型是Zero-Shot视频生成器

Text2Video-Zero: Text-to-Image Diffusion Models are Zero-Shot Video Generators Paper: https://arxiv.org/abs/2303.13439 Project: https://github.com/Picsart-AI-Research/Text2Video-Zero 原文链接&#xff1a;Text2Video-Zero:Text-to-Image扩散模型是Zero-Shot视频…

Splashtop 让按需远程支持流程更加流畅

Splashtop 一直采用9位 SOS 会话码的形式为用户提供按需支持&#xff1a; 1、技术员引导最终用户访问网站 sos.splashtop.com&#xff1b; 2、最终用户下载并运行小程序&#xff0c;然后发送9位会话码给技术员。 自2015年 Splashtop SOS 产品推出以来&#xff0c;我们一直采…

PLC数字量与模拟量信号输入/输出接线

西门子S7-1200 具有用于进行计算和测量、闭环回路控制和运动控制的集成技术&#xff0c;是一个功能非常强大的系统&#xff0c;可以实现多种类型的自动化任务。下面分享S7-1200系列数字量与模拟量信号输入/输出接线图给大家。 数字量信号模块输入输出接线 SM 1221 数字量输入…

分布式机器学习(Parameter Server)

分布式机器学习中&#xff0c;参数服务器(Parameter Server)用于管理和共享模型参数&#xff0c;其基本思想是将模型参数存储在一个或多个中央服务器上&#xff0c;并通过网络将这些参数共享给参与训练的各个计算节点。每个计算节点可以从参数服务器中获取当前模型参数&#xf…

高速电路设计系列分享-信号链精度分析(下)

文章目录 概要整体架构流程技术名词解释技术细节小结 概要 提示&#xff1a;这里可以添加技术概要 在任何设计中&#xff0c;信号链精度分析都可能是一项非常重要的任务&#xff0c;必须充分了解。之前&#xff0c; 我们讨论了在整个信号链累积起来并且最终会影响到转换器的多…

嵌入式软件测试笔记7 | 嵌入式软件测试中基于风险的测试策略如何开展?

7 | 嵌入式软件测试中基于风险的测试策略如何开展&#xff1f; 1 风险评估1.1 分析风险1.2 如何估计故障几率&#xff1f;1.3 导致故障几率较高的因素1.4 估计可能的损失1.5 风险评估的来源1.6 风险的处理 2 主测试计划中的策略2.1 目标2.2 制定策略的步骤2.3 选择质量特性2.4 …

美国签证办理需要户口本吗?

在申请美国签证时&#xff0c;有关所需文件的问题常常令人困惑。关于是否需要提供户口本&#xff0c;知识人网可以向您解释一下相关情况。 首先&#xff0c;需要明确的是&#xff0c;美国签证申请并不要求申请人提供户口本。美国领事馆和大使馆在签证申请过程中通常要求申请人提…

零知识证明(Sigma和Flat-shamir)

概述 定义&#xff1a;大概的定义就是prover可以向verifier证明自己给定的信息是大概率正确的&#xff0c;但是不泄露任何附加信息&#xff0c;包含信息本身。 举例 这里以一个比较经典的例子&#xff0c;即向红绿色盲&#xff08;无法区分红色和绿色&#xff0c;看红色和绿色…

基于RFID技术的并列式挤奶厅方案

随着现代农业的不断发展&#xff0c;RFID技术已经广泛应用于畜牧业生产中。在奶牛养殖领域&#xff0c;RFID技术可以帮助养殖场管理人员实现奶牛的精准管理&#xff0c;提高生产效率。本文将介绍一种基于RFID技术的并列式挤奶厅方案&#xff0c;该方案可以实现对每头奶牛的精准…

网络安全(黑客)必备工具包

1. NMap 作为Network Mapper的缩写&#xff0c;NMap是一个开源的免费安全扫描工具&#xff0c;可用于安全审计和网络发现。它适用于Windows、Linux、HP-UX、Solaris、BSD变体(包括Mac OS)以及AmigaOS。Nmap可用于探测网络上哪些主机可访问&#xff0c;它们正在运行的操作系统类…

Keil MDK编程环境下的 STM32 IAP下载(学习笔记)

IAP的引入 不同的程序下载方式 ICP ICP(In Circuit Programing)。在电路编程&#xff0c;可通过 CPU 的 Debug Access Port 烧录代码&#xff0c;比如 ARM Cortex 的 Debug Interface 主要是 SWD(Serial Wire Debug) 或 JTAG(Joint Test Action Group)&#xff1b; ISP ISP(I…