SegmentFault 最新的文章 |
- 从“智能湖仓”架构的技术演进,看现代化数据平台的发展方向
- 彻底搞懂 Kubernetes 中的 Events
- 电子表格实战锦囊:巧用稀疏数组是关键!
- 难搞的偏向锁终于被 Java 移除了
- 平平淡淡又一年 | 边城在思否的 2021 年总结
- 一看就懂,一写就懵?搞懂回溯算法,一口气刷了20多道题
- 使用 Nginx 构建前端日志统计服务
- React Native新架构剖析
- 从微服务到云原生
- 1.2w字 | 从 0 到 1 上手 Web Components 业务组件库开发
- 备战2022春招,这十道题必会!
- 一次说清,为什么在Antd Modal中调resetFields调了个寂寞
- Redis 分布式锁的正确实现原理演化历程与 Redission 实战总结
Posted: 30 Dec 2021 01:56 AM PST 在 2021 年初全年技术趋势展望中,数据湖与数据仓库的融合,成为大数据领域的趋势重点。直至年末,关于二者的讨论依然热烈,行业内的主要分歧点在于数据湖、数据仓库对存储系统访问、权限管理等方面的把控;行业内的主要共识点则是二者结合必能降低大数据分析的成本,提高易用性。 而此类争论,又反映了行业在大数据处理领域的核心诉求:如何通过数据湖、数据仓库的设计,有效满足现代化应用的数据架构要求。亚马逊云科技作为行业头部云厂商,也推出了与数据湖、数据仓库融合相关的"智能湖仓"。为什么"智能湖仓"可以更智能地集成数据湖、数据仓库和其他数据处理服务?"智能湖仓"架构备受关注意味着什么?在技术行业风向标的 2021 亚马逊云科技 re:Invent 大会上,我们看到了"智能湖仓"架构的现在和未来构想。 被广泛关注的"智能湖仓"架构理解"智能湖仓"架构的现在和未来,需要先了解它的过去。早在 2017 年,"智能湖仓"架构就已初具雏形。当时,亚马逊云科技发布了 Amazon Redshift Spectrum,让 Amazon Redshift 具备了打通数据仓库和数据湖的能力,实现了跨数据湖、数据仓库的数据查询。 这件事情启发了"智能湖仓"架构的形成。在 2020 年的亚马逊云科技 re:Invent 大会上,亚马逊云科技正式发布"智能湖仓"。如果从早期的技术探索开始算起,在 2021 亚马逊云科技 re:Invent 大会上发布的 Serverless 能力,代表了"智能湖仓"架构的第 8 轮技术演进。如今,"智能湖仓"基于 Amazon S3 构建数据湖,绕湖集成数据仓库、大数据处理、日志分析、机器学习数据服务,利用 Amazon Lake Formation、Amazon Glue 等工具可以实现数据的自由流动与统一治理。 具体而言,"智能湖仓"架构下,首先需要打破数据孤岛形成一个数据湖;其次,需要围绕着数据湖,在不同应用场景为用户提供相应的分析工具;另外,需要确保数据在湖、仓以及专门的服务之间能够自由移动;此外,需要确保用统一的方式去管理湖里面数据的安全性、访问控制和审计;最终,需要能够采用低成本的方法将湖、仓各自的优势有效利用起来,并利用人工智能等创新手段进行创新。 就像 Amazon Redshift 在 2012 年发布时,引导了云原生数仓的发展方向一样,"智能湖仓"架构一经发布就引发业内广泛关注,一方面是因为亚马逊云科技作为头部云厂商的行业地位,另一方面是因为此架构在技术上的创新思路能够为行业带来一些新的思考。 "智能湖仓"更强调"架构"而非"产品",更强调数据的自由流动与统一治理,以及基于湖仓的"智能创新"。如今,"智能湖仓"架构不是简单地将湖与仓打通,而是将湖、仓与专门构建的数据服务连接成为一个整体,让数据在其间无缝移动。面对向 TB 级、PB 级,甚至 EB 级增长的数据,"如何存"和"如何用"不再是相对孤立的话题。"智能湖仓"向行业传递了一个信号:企业需要统一数据分析工具,实现数据在整个数据平台的自由流转。 不管是企业数据管理理念的视角,还是在技术视角下,"智能湖仓"架构被广泛关注也意味着,随着数据湖和数据仓库的边界在逐渐淡化,基于两者的大数据处理体系的架构正在被重构。 "智能湖仓"架构下,重构中的大数据基础设施这种重构大概可以分为几个维度来理解,其中最重要的是更强的数据安全、治理和数据共享能力,更敏捷的构建方式,更智能的创新手段。 数据安全、治理和共享,重点聚焦跨湖、跨仓库甚至跨企业的数据流通和治理,致力于实现真正意义上的数据跨域互通;更敏捷的构建方式则要将企业的敏态追求提升到极致,Serverless 能力的应用是其关键;更智能的创新手段则把 AI/ML 能力和大数据治理并入统一范畴,避免走入"为了大数据而大数据"的误区。 在 2022 年,当我们再次谈起数据湖和数据仓库的融合问题时,包含以上关键点的"智能湖仓"架构,很可能成为被业内重点参考的构建思路之一。 更强的数据安全、治理和数据共享能力数据的安全、治理和共享,原是大数据的本职任务,但当数据达到 PB 乃至 EB 级,需要跨多个区域、组织、账户进行数据共享或数据交互时,企业有些时候并非不想细颗粒度管理数据,而是无法管理。这种颗粒度的权限控制往往比单机系统设计或者单一的分布式系统要复杂得多。所以,数据治理成为了"智能湖仓"重要的发力点。 在 2021 亚马逊云科技 re:Invent 大会上,支撑数据统一治理和自由流动能力的"智能湖仓"组件 Amazon Lake Formation 发布了多项新功能。除了之前早已支持的表和列级安全,Amazon Lake Formation 现在支持行和单元级权限,通过只限制用户对部分数据的访问权限,让限制访问敏感信息变得更加简单。 此外,Data mesh 的概念在 2021 亚马逊云科技 re:Invent 大会上也被提及。Data mesh 概念也是 Gartner 提出的十大数据技术趋势之一。在 Data mesh 模式下,"智能湖仓"能够实现领域数据成为产品、轻松启用细粒度授权、数据更容易被使用、数据调用跨企业可见和联邦的数据管控与合规。这意味着,"智能湖仓"架构下,Data mesh 可以实现跨数据湖的数据共享和计算。亚马逊云科技借助自身数据湖安全、tag 级别的访问控制和共享能力,为 Data mesh 提供了实现方式与手段,让 Data mesh 概念走向落地。 更敏捷的构建方式除了更强的数据安全、治理和数据共享能力,更敏捷的构建方式也是绝大多数企业当下主要关注的技术创新之一。敏捷在企业间的认可度和应用程度越来越高,而"智能湖仓"原本就是敏捷的架构。在"智能湖仓"架构中,Amazon Lake Formation 能够将建立数据湖的时间从数月缩短到数天。用户可以使用像 Amazon Glue 这样的 Serverless 数据集成工具快速实现数据入湖;使用 Amazon Athena 这样的 Serverless 查询引擎直接实现基于 SQL 语言的湖上数据查询分析。无论是超大型公司还是工作室,都可以从这种敏捷的构建方式中快速获益,提取数据的价值。 为了让构建方式更敏捷,在 2021 亚马逊云科技 re:Invent 大会上,亚马逊云科技宣布推出更多数据分析服务的无服务器版,借助无服务器的能力,让用户可以更敏捷地构建自己的数据存储、分析、智能应用解决方案。
来自亚马逊云科技的数据显示,现在每天有数以万计的用户每天在使用 Amazon Redshift 处理超过 2EB 的数据。全球最大的制药公司之一罗氏制药(Roche)首席云平台和机器学习工程师 Yannick Misteli 博士表示:"Amazon Redshift Serverless 可减轻运营负担,降低成本,并帮助罗氏制药规模化实践 Go-to-Market 策略。这种极简的方式改变了游戏规则,帮助我们快速上手并支持各种繁重的分析场景。" 更智能的创新手段正如 Yannick Misteli 提到的一样,近些年来,底层的技术创新推动业务层的改变,而业务层的诉求也倒逼底层技术的进步。游戏规则正在技术升级中改变。如今,"智能"是绝大多数技术的演进目标。在亚马逊云科技的"智能湖仓"架构中,也将"智能"提到了一个相当重要的位置。 "智能湖仓"架构下,数据库服务与人工智能和机器学习深度集成。在具体的产品上,亚马逊云科技提供了 Amazon Aurora ML、Amazon Neptune ML、Amazon Redshift ML 等诸多数据库原生的机器学习服务。 同时,在"智能湖仓"架构中,还有云原生人工智能平台 Amazon SageMaker ,它提供了多类机器学习库和开发工具包,帮助用户快速构建人工智能应用。当用户需要面对大量数据处理场景时,可以使用 Amazon SageMaker 内置的工具轻松快速连接到 Amazon EMR 集群进行大数据处理。而 Amazon EMR Serverless,也帮助人工智能相关的数据处理与分析变得足够敏捷。 在 Gartner 2021 年发布的报告《Magic Quadrant for Cloud Database Management Systems》中,亚马逊云科技连续 7 年被评为"领导者",这项报告面向的主要是对各大厂商提供的云数据库、云数据分析工具进行全景评估,并给出最终位置的"测评报告",含金量可见一斑。亚马逊云科技参与评测的产品均为"智能湖仓"架构中的代表产品,这个"领导者地位"背后代表的技术成熟度不言自明。 我们可以看到,"智能湖仓"提供的每一款服务工具的迭代,都在向更敏捷、更安全、更智能的数据架构目标迈进。数据架构作为企业数字化转型的最底层,也是应用现代化的底层动力。"智能湖仓"带来的数据管理方式的变革,也承载着亚马逊云科技对应用现代化的构想。 写在最后回到文章开篇提到的问题,目前行业内已经形成了数据湖和数据仓库的融合必将降低大数据分析成本的共识,主要分歧点在于数据湖、数据仓库对存储系统访问、权限管理等方面的把控。在这些方面,亚马逊云科技的"智能湖仓"架构围绕这些问题都提供了相关的工具或服务。 无论是在数据基础架构、统一分析还是业务创新上,从连接数据湖和数据仓库到跨数据库、跨域共享,"智能湖仓"在实际的业务场景中并非孤立存在,而是与应用程序紧密相连。 底层数据架构的现代化演进,也将为企业乃至全行业带来更大的价值。数据,作为与土地、劳动力、资本、技术并列的"第五大生产要素",重要性不言而喻。如今,亚马逊云科技"智能湖仓"架构在企业中的实践,已经为企业构建现代化数据平台提供了一条可供遵循的路径。 活动推荐 对于技术圈而言,这一年中,各种技术与领域的发展,既站高峰,也历跌宕。在 2021 年最后时刻,我们也想聆听来自云计算领域开发者的声音,为此,云计算开发者有奖调研正式开启,诚邀各位伙伴参与,多重好礼等你来领! | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 28 Dec 2021 08:58 PM PST 大家好,我是张晋涛。 之前我写了一篇《更优雅的 Kubernetes 集群事件度量方案》,利用 Jaeger 利用 tracing 的方式来采集 Kubernetes 集群中的 events 并进行展示。最终效果如下: 写那篇文章的时候,立了个 flag 要详细介绍下其中的原理,鸽了很久,现在年底了,也该发出来了。 Eents 概览我们先来做个简单的示例,来看看 Kubernetes 集群中的 events 是什么。 创建一个新的名叫 moelove 的 namespace ,然后在其中创建一个叫做 redis 的 deployment。接下来查看这个 namespace 中的所有 events。
但是我们会发现默认情况下 这也是为何 Kubernetes v1.23 版本中会新增 按时间排序后可以看到如下结果:
通过以上的操作,我们可以发现 events 实际上是 Kubernetes 集群中的一种资源。当 Kubernetes 集群中资源状态发生变化时,可以产生新的 events。 深入 Events单个 Event 对象既然 events 是 Kubernetes 集群中的一种资源,正常情况下它的 metadata.name 中应该包含其名称,用于进行单独操作。所以我们可以使用如下命令输出其 name :
选择其中的任意一条 event 记录,将其输出为 YAML 格式进行查看:
可以看到其中包含了很多信息, 这里我们先不展开。我们看另一个例子。
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 28 Dec 2021 07:18 PM PST 前文中我们详细介绍过稀疏数组的那些事儿,以及在实际项目中,稀疏数组如何在前端电子表格中发挥出它最大的效果。而这次,我们将从实战应用出发,为大家介绍稀疏数组在前端中的具体应用。 我们都知道在Javascript中是通过Array()构造函数构件稀疏矩阵,或者通过数组,设定数组的索引长度大于当前数组长度的方式来创建稀疏矩阵。
稀疏数组中,没有元素的结点为empty,获取这些结点将返回结果undefined。通过使用index in array可以判断一个结点是否有元素。例如下面代码中,a[0]和a[1]的返回都为undefined,但是a[1]其实为空。 JS中已经支持稀疏数组的存储,但在实际情况中,我们保存稀疏数组的保存并不是直接进行,而是会根据实际情况构建其他存储方式保存稀疏数组。想了解为什么要多此一举,这里就需要大家了解一个概念——数据持久化。 我们在前端进行许多操作时,会产生许多数据,例如在前端表格进行多人填报、协同的时候,会出现很多需要长期保存的数据,有些数据还要转移到其它位置中便于人们存储、管理、操作等。而实现这一目标的关键点就是数据的持久化,我们需要将内存中数据序列化为json等存储格式保存到数据库并还能反序列化到内存。在之前的文章详解电子表格中的json数据:序列化与反序列化已经具体介绍了,大家有兴趣可以查看。 看到这里,你以为问题彻底解决了吗,图样图森破。 为了解决数据持久化,我们使用了JSON,但这时新的问题也随之出现,JSON存储中没有undefined。我们对数组进行操作的时候,数组中empty字段都会序列化为null,如下图所示。
再次parse后,数组便不再是稀疏数组了。
这种情况下,为了解决JSON数据在转化过程中上述出现的情况,我们就需要构建一些其他存储方式,来更好的地解决这个问题~而这些存储方式又有哪些特点,让我们一起看看。 1、对象存储 在前端利用JS的语言特点,我们可以通过Object可以轻松实现Sparse Array。例如在Spread JS中,对象属性名称对应所在单元格的行列,value属性保存单元格的值,同样可以拓展出formula和style等属性保存单元格公式和样式。使用Sparse Array不用初始化大小也不用关心数据的扩容,需要做行列操作时也只需要改变行列属性的引用即可。 上图中的数据存储结果如下
需要存取数据时候直接通过对象属性访问。下面是JS Sparse Array的一个简单对象实现。
2、三元组 在矩阵中每一个元素有行标,列标,元素值三个信息,将元素按需放入数组中便是三元组存储。存储结构可以是一个包含元素信息对象,也可以直接简化为一个长度为3的数组。三元组的存储方式可以方便记录类似下图的轨迹信息或者自由曲线信息,通过对数组进行push和pop,可以方便进行回退和前进。 上图中的轨迹信息,以数组三元组存储后如下,元素value代表当前已元素数量,也可以使用对象记录时间等更多信息。
下面,我们就用这种方式建立一个undoStack记录回退。
除了以上两种方式,还可以将上述方式结合,建立十字链表以应对更复杂的场景。大家如果感兴趣点个赞我们下次继续说。 在后续的内容中,我们还会继续为大家带来其他前端电子表格技术中的深度解密,走过路过不要错过。 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 28 Dec 2021 04:58 PM PST 背景在 JDK1.5 之前,面对 Java 并发问题, synchronized 是一招鲜的解决方案:
拿同步块来举例:
经过
当另外一个线程执行到同步块的时候,由于它没有对应 锁的演变来到 JDK1.6,要怎样优化才能让锁变的轻量级一些? 答案就是: 轻量级锁:CPU CAS如果 CPU 通过简单的 CAS 能处理加锁/释放锁,这样就不会有上下文的切换,较重量级锁而言自然就轻了很多。但是当竞争很激烈,CAS 尝试再多也是浪费 CPU,权衡一下,不如升级成重量级锁,阻塞线程排队竞争,也就有了轻量级锁升级成重量级锁的过程 程序员在追求极致的道路上是永无止境的,HotSpot 的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,同一个线程反复获取锁,如果还按照轻量级锁的方式获取锁(CAS),也是有一定代价的,如何让这个代价更小一些呢? 偏向锁偏向锁实际就是锁对象潜意识「偏心」同一个线程来访问,让锁对象记住线程 ID,当线程再次获取锁时,亮出身份,如果同一个 ID 直接就获取锁就好了,是一种 可是多线程环境,也不可能只是同一个线程一直获取这个锁,其他线程也是要干活的,如果出现多个线程竞争的情况,也就有了偏向锁升级的过程 这里可以先思考一下:偏向锁可以绕过轻量级锁,直接升级到重量级锁吗? 都是同一个锁对象,却有多种锁状态,其目的显而易见: 占用的资源越少,程序执行的速度越快 偏向锁,轻量锁,它俩都不会调用系统互斥量(Mutex Lock),只是为了提升性能,多出的两种锁的状态,这样可以在不同场景下采取最合适的策略,所以可以总结性的说:
到这里,大家应该理解了全局大框,但仍然会有很多疑问:
想理解这些问题,需要先知道 Java 对象头的结构 认识 Java 对象头按照常规理解,识别线程 ID 需要一组 mapping 映射关系来搞定,如果单独维护这个 mapping 关系又要考虑线程安全的问题。奥卡姆剃刀原理,Java 万物皆是对象,对象皆可用作锁,与其单独维护一个 mapping 关系,不如中心化将锁的信息维护在 Java 对象本身上 Java 对象头最多由三部分构成:
其中 有了这些基本信息,接下来我们就只需要弄清楚,MarkWord 中的锁信息是怎么变化的 认识偏向锁单纯的看上图,还是显得十分抽象,作为程序员的我们最喜欢用代码说话,贴心的 openjdk 官网提供了可以查看对象内存布局的工具 JOL (java object layout) Maven Package
Gradle Package
接下来我们就通过代码来深入了解一下偏向锁吧 注意:
来看测试代码 场景1
来看输出结果: 上面我们用到的 JOL 版本为 看到这个结果,你应该是有疑问的,JDK 1.6 之后默认是开启偏向锁的,为什么初始化的代码是无锁状态,进入同步块产生竞争就绕过偏向锁直接变成轻量级锁了呢? 虽然默认开启了偏向锁,但是开启有延迟,大概 4s。原因是 JVM 内部的代码有很多地方用到了synchronized,如果直接开启偏向,产生竞争就要有锁升级,会带来额外的性能损耗,所以就有了延迟策略 我们可以通过参数 场景2那我们就代码延迟 5 秒来创建对象,来看看偏向是否生效
重新查看运行结果: 这样的结果是符合我们预期的,但是结果中的 这样当有线程进入同步块:
那问题又来了,现在锁对象有具体偏向的线程,如果新的线程过来执行同步块会偏向新的线程吗? 场景3
来看运行结果,奇怪的事情发生了:
至此,场景一二三可以总结为一张图: 从这样的运行结果上来看,偏向锁像是"一锤子买卖",只要偏向了某个线程,后续其他线程尝试获取锁,都会变为轻量级锁,这样的偏向非常有局限性。事实上并不是这样,如果你仔细看标记2(已偏向状态),还有个 epoch 我们没有提及,这个值就是打破这种局限性的关键,在了解 epoch 之前,我们还要了解一个概念——偏向撤销 偏向撤销在真正讲解偏向撤销之前,需要和大家明确一个概念——偏向锁撤销和偏向锁释放是两码事
何为偏向撤销? 从偏向状态撤回原有的状态,也就是将 MarkWord 的第 3 位(是否偏向撤销)的值, 如果只是一个线程获取锁,再加上「偏心」的机制,是没有理由撤销偏向的,所以偏向的撤销只能发生在有竞争的情况下 想要撤销偏向锁,还不能对持有偏向锁的线程有影响,所以就要等待持有偏向锁的线程到达一个 在这个安全点,线程可能还是处在不同状态的,先说结论(因为源码就是这么写的,可能有疑惑的地方会在后面解释)
这个和 epoch 貌似还是没啥关系,因为这还不是全部场景。偏向锁是特定场景下提升程序效率的方案,可并不代表程序员写的程序都满足这些特定场景,比如这些场景(在开启偏向锁的前提下):
很显然,这两种场景肯定会导致偏向撤销的,一个偏向撤销的成本无所谓,大量偏向撤销的成本是不能忽视的。那怎么办?既不想禁用偏向锁,还不想忍受大量撤销偏向增加的成本,这种方案就是设计一个有阶梯的底线 批量重偏向(bulk rebias)这是第一种场景的快速解决方案,以 class 为单位,为每个 class 维护一个偏向锁撤销计数器,每一次该class的对象发生偏向撤销操作时,该计数器
JVM 就认为该class的偏向锁有问题,因此会进行批量重偏向, 它的实现方式就用到了我们上面说的
这样下次获得锁时,发现当前对象的 如果 批量重偏向是第一阶梯底线,还有第二阶梯底线 批量撤销(bulk revoke)当达到重偏向阈值后,假设该 class 计数器继续增长,当其达到批量撤销的阈值后(默认40)时,
JVM就认为该 class 的使用场景存在多线程竞争,会标记该 class 为不可偏向。之后对于该 class 的锁,直接走轻量级锁的逻辑 这就是第二阶梯底线,但是在第一阶梯到第二阶梯的过渡过程中,也就是在彻底禁用偏向锁之前,还给一次改过自新的机会,那就是另外一个计时器:
大家有兴趣可以写代码测试一下临界点,观察锁对象 至此,整个偏向锁的工作流程可以用一张图表示: 到此,你应该对偏向锁有个基本的认识了,但是我心中的好多疑问还没有解除,咱们继续看: HashCode 哪去了上面场景一,无锁状态,对象头中没有 hashcode;偏向锁状态,对象头还是没有 hashcode,那我们的 hashcode 哪去了? 首先要知道,hashcode 不是创建对象就帮我们写到对象头中的,而是要经过第一次调用 场景一
来看运行结果 结论就是:即便初始化为可偏向状态的对象,一旦调用 场景二假如已偏向某一个线程,然后生成 hashcode,然后同一个线程又进入同步块,会发生什么呢?来看代码:
查看运行结果: 结论就是:同场景一,会直接使用轻量级锁 场景三那假如对象处于已偏向状态,在同步块中调用了那两个方法会发生什么呢?继续代码验证:
来看运行结果: 结论就是:如果对象处在已偏向状态,生成 hashcode 后,就会直接升级成重量级锁 最后用书中的一段话来描述 锁和hashcode 之前的关系 调用 Object.wait() 方法会发生什么?Object 除了提供了上述 hashcode 方法,还有
查看运行结果: 结论就是,wait 方法是互斥量(重量级锁)独有的,一旦调用该方法,就会升级成重量级锁(这个是面试可以说出的亮点内容哦) 最后再继续丰富一下锁对象变化图: 告别偏向锁看到这个标题你应该是有些慌,为啥要告别偏向锁,因为维护成本有些高了,来看 Open JDK 官方声明,JEP 374: Deprecate and Disable Biased Locking,相信你看上面的文字说明也深有体会 这个说明的更新时间距离现在很近,在 JDK15 版本就已经开始了 一句话解释就是维护成本太高 最终就是,JDK 15 之前,偏向锁默认是 enabled,从 15 开始,默认就是 disabled,除非显示的通过 其中在 quarkus 上的一篇文章说明的更加直接 偏向锁给 JVM 增加了巨大的复杂性,只有少数非常有经验的程序员才能理解整个过程,维护成本很高,大大阻碍了开发新特性的进程(换个角度理解,你掌握了,是不是就是那少数有经验的程序员了呢?哈哈) 总结偏向锁可能就这样的走完了它的一生,有些同学可能直接发问,都被 deprecated 了,JDK都 17 了,还讲这么多干什么?
之前对于偏向锁我也只是单纯的理论认知,但是为了写这篇文章,我翻阅了很多资料,包括也重新查看 Hotspot 源码,说的这些内容也并不能完全说明偏向锁的整个流程细节,还需要大家具体实践追踪查看,这里给出源码的几个关键入口,方便大家追踪:
文中有疑问的地方欢迎留言讨论,有错误的地方还请大家帮忙指正 灵魂追问
参考资料感谢各路前辈的精华总结,可以让我参考理解:
日拱一兵 | 原创 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 28 Dec 2021 04:00 PM PST 又到了年末写总结的时候。我不断地提醒自己过去这一年是 2021,不是 2022 —— 是的,作为一个使用了 VS2022 好几个月的人来说,感觉已经在 2022 好久了。然而, 2022 只是即将来临。回往昔,我都干了啥? 说起来,只记得最近为了迎接元宇宙,我去复习了《Matrix I、II、III》 —— 好吧,说实话,是为了《Matrix IV》。 不过为了做这个 2021 的思否社区总结,我是真的下了功夫 —— 去扒了下 SF 的个人数据:
要说怎么扒的,当然是使用传说中的"爬虫技术"…… 的入门技术:找到 API,找到 Token,写段代码迭代拉取数据,直到把今年的拉完。这不是重点,重点是 —— 2021 年间我写了 20 篇博文,收获 216 个赞。其中获赞最多的是《安全地在前后端之间传输数据 - 「1」技术预研》,有 34 个。回答问题 338 个,其中 113 个答案被接受,占比 33.43%;回答获赞 307 个,比博文多,但平均下来看还是博文更值钱。 除了统计 2021 年的文章和问题之外,我也想知道往年的文章在今年获赞的情况。想了一下,可能这个信息要从提醒消息里去拉。结果拉出来 1900 多条提醒。其中邀请回答 210 条,占 2021 年所有回答的 60%。确实,回想起来,整个 2021 年间,有超过半年的时间没有什么创作动力,也不怎么想回答问题,基本上是有邀请才答,没邀请拉倒。 不过爬出来的数据有效性不好说。你看,从提醒数据中提取出来文章共获得 227 个赞,除去前面统计的 2021 年文章的 216 个,只有 16 个赞在以前的文章上。然而,仅《理解 JavaScript 的 async/await》(发于 2016 年)一篇就占了 115 个赞,这不合理。然后我想,是不是"收藏"的也算了赞,但是没有单独出现"赞"的提醒呢,所以重新统计了一下:
结果看起来更接近一些,但是仍然存在差异。可能"有人收藏了你的文章"这里提到的"有人"有时候不止一个吧 —— 也只能这么解释了!但不管怎么说,这个统计数据已经很说明问题了,2021 一年的博文,比不过曾经一篇的余热 😰! 差点忘了,2021 年还有一点现实的收入,3 个付费问题,共计收入 ¥148.8 😁。 除了统计结果,我还想聊点细节。 总的来说,2021 年回答的问题都不是很难,但从我的感觉来看,同学们在数据处理上的问题比较多。怎么说呢 —— 还是得加强数据结构和算法和深入学习和理解,以及多运用,多实践,多积累经验。大概总结了一下,我回答过的问题有这么一些分类:
还有一些其他的分类,比如框架类(Vue 等)的,工作类的,正则表达式类的 …… 其中最让我感到高兴的是有不少代码优化类的问题,这充分说明了越来越多的人开始关注代码的可读性和可维护性,而不再只是简单地追求"能跑"了 —— 别想多了,代码真的是给人读的!要不然人类终将会输给机器。 我的 2021 过得很平淡,平淡得不知不觉就过完了。本以为也没什么好写的,没想到还是写了一大篇。 牛年,我就像老水牛一般低调;虎年,我是不是该站出来咆哮!—— 也许是吧,但那不是我的性格。2022 年又会怎么过呢? 本文参与了 SegmentFault 思否征文「2021 总结」,欢迎正在阅读的你也加入。 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 26 Dec 2021 12:36 AM PST 一、回溯算法1.1什么是回溯?回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就"回溯"返回,尝试别的路径。——摘自《百度百科》 1.1 一般步骤:
1.2 如何理解回溯算法?
1.3 解决那些问题?
1.4递归与回溯首先先说明一下对递归 (Recursive)与回溯 (Backtrack)的理解。 1.4.1 递归 (Recursive)程序调用自身的编程技巧称为递归。 通常来说,为了描述问题的某一状态,必须用到该状态的上一个状态;而如果要描述上一个状态,又必须用到上一个状态的上一个状态…… 这样用自己来定义自己的方法就是递归。 1.4.2. 回溯 (Backtrack)回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就"回溯"返回,尝试别的路径。 ——摘自《百度百科》 在这种思想下,我们需要清晰的找出三个要素:选择 (Options),限制 (Restraints),结束条件 (Termination)。 1.5.递归与回溯的区别递归是一种算法结构。递归会出现在子程序中,形式上表现为直接或间接的自己调用自己。典型的例子是阶乘,计算规律为:n!=n×(n−1)!n!=n \times (n-1)!,基本如下所示:
回溯是一种算法思想,它是用递归实现的。回溯的过程类似于穷举法,但回溯有"剪枝"功能,即自我判断过程。 二、Leetcode回溯题目2.1- 22. 括号生成数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。示例 1:
示例 2:
提示: 思路分析
简答绘制图形解题代码
2.2 - 46. 全排列给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。示例 1:
示例 2:
示例 3:
提示: 解题思路
解题代码
[图片上传失败...(image-40cdd5-1639281547994)] 2.3 - n 皇后问题研究的是如何将 n 个皇后放置在 n × n 的棋盘上,并且使皇后彼此之间不能相互攻击。给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。* 皇后走法规则皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。 示例示例 1:
解释:如上图所示,4 皇后问题存在两个不同的解法。 示例 2:
提示: 解题思路
使用回溯的具体做法是:依次在每一行放置一个皇后,每次新放置的皇后都不能和已经放置的皇后之间有攻击,即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上。当 NNN 个皇后都放置完毕,则找到一个可能的解,将可能的解的数量加 111。解题代码
2.4 - 78. 子集给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1:
示例 2:
提示: 解题思路
解题代码
2.5 - 77. 组合给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。你可以按 任何顺序 返回答案。 示例 1:
示例 2:
提示:
解题思路
解题代码
2.6 - 081. 允许重复选择元素的组合给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。 示例 1:
示例 2:
示例 3:
示例 4:
示例 5:
提示: 解题思路
解题代码
2.7 - 216. 组合总和 III找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。说明: 示例 1:
示例 2:
解题思路同组合1 解题代码
2.8 - 17. 电话号码的字母组合给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 示例 1:
示例 2:
示例 3:
提示: 解题思路
解题代码
2.9 - 08.01. 三步问题三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。示例1:
示例2:
提示: 解题代码(会超时)
动态规划解法
2-10 - 40. 组合总和 II给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。 示例 1:
示例 2:
提示: 解题思路思路同组合1 解题代码
2-11 - 47. 全排列 II给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。示例 1:
示例 2:
提示: ## 解题思路 ## 解题代码
三、总结主要运用了回溯算法;而解决一个回溯问题,实际上就是一个决策树的遍历过程。3.1 模板
即:
3.2 剪枝函数
3.3 回溯法的一般步骤:
继续加油!!! # 四、参考文献
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 26 Dec 2021 07:15 PM PST 背景之前的几篇文章都是关于之前提到的 这个大的项目以 low code 为核心,囊括了编辑器前端、编辑器后端、C 端 H5、组件库、组件平台、后台管理系统前端、后台管理系统后台、统计服务、自研 CLI 九大系统。 今天就来说一下其中的 先放一下整体流程图吧: 日志收集常见的日志收集方式有手动埋点和自动埋点,这里我们不关注于如何收集日志,而是如何将收集的日志的发送到服务器。 在常见的埋点方案中,通过图片来发送埋点请求是一种经常被采纳的,它有很多优势:
这里的方案就是在 iOS 上会限制 get 请求的 url 长度,但我们这里真实场景发送的数据不会太多,所以目前暂时采用这种方案 这里简单阐述一下为什么图片地址的
之后每次访问页面, 有了日志,下面我们来看下如何来对其进行拆分。 日志拆分为何要拆分日志
日志文件内容过多,对于后续的问题排查和分析也会变得很困难。 所以日志的拆分是有必要也是必须的。 如何拆分日志我们这里拆分日志的核心思路是:将当前的 视流量情况(流量越大日志文件积累的越快),按天、小时、分钟来拆分。可以把
但上面的 定时任务其实定时任务不仅在日志拆分的时候会用到,在后面的日志分析和日志清除都会用到,这里先简单介绍一下,最终会整合拆分、分析和清除。
这里使用的是cron:
具体使用方式就不展开说明了。 编码有了上面这些储备,下面我就来写一下这块代码,首先梳理下逻辑: 1️⃣ 读取源文件 access.log 2️⃣ 创建拆分后的文件夹(不存在时需自动创建) 3️⃣ 创建日志文件(天维度,不存在时需自动创建) 4️⃣ 拷贝源日志至新文件 5️⃣ 清空 access.log
日志分析日志分析就是读取上一步拆分好的文件,然后按照一定规则去处理、落库。这里有一个很重要的点要提一下:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 26 Dec 2021 06:18 AM PST 目前 React Native 新架构所依赖的 React 18 已经发了 beta 版,React Native 新架构面向生态库和核心开发者的文档也正式发布,React Native 团队成员 Kevin Gozali 也在最近一次访谈中谈到新架构离正式发版还差最后一步延迟初始化,而最后一步工作大约会在 2022 年上半年完成。种种迹象表明,React Native 新架构真的要来了。 前面,RN官方宣布:Hermes将成为React Native默认的JS引擎。在文章中,我们简单的介绍了即将发布的新渲染器 Fabric,那么我们重点来认识下这个新的渲染器 Fabric 。 一、Fabric1.1 基本概念Fabric 是 React Native 新架构的渲染系统,是从老架构的渲染系统演变而来的。核心原理是在 C++ 层统一更多的渲染逻辑,提升与宿主平台(host platforms)互操作性,即能够在 UI 线程上同步调用JavaScript代码,渲染效率得到明显的提高。Fabric研发始于 2018 年,Facebook 内部的很多React Native 应用使用的就是新的渲染器Fabric。 在简介新渲染器(new renderer)之前,我们先介绍几个单词和概念:
在更换了底层的渲染流程之后,Fabric 渲染器使得渲染宿主视图变得可行。Fabric 让 React 与各个平台直接通信并管理其宿主视图实例。Fabric 渲染器存在于 JavaScript 中,并且它调用的是由 C++ 代码暴露的接口。 1.2 新渲染器的初衷开发新的渲染架构的初衷是为了更好的提升用户体验,而这种新体验是在老架构上是不可能实现的。主要体现为:
除此之外,新的Fabric渲染器在代码质量、性能、可扩展性方面也是有了质的飞升。
二、渲染流程2.1 渲染流程React Native 渲染器通过一系列加工处理,将 React 代码渲染到宿主平台。这一系列加工处理就是渲染流水线(pipeline),它的作用是初始化渲染和 UI 状态更新。接下来,我们重点介绍一下React Native 渲染流水线,及其在各种场景中的不同之处。 渲染流水线可大致分为三个阶段:
这里有几个名词需要解释下: React元素树React 元素树是通过 JavaScript 中的 React 创建的,该树由一系类 React 元素组成。一个 React 元素就是一个普通的 JavaScript 对象,它描述了需要在屏幕中展示的内容。一个元素包括属性 props、样式 styles、子元素 children。React 元素分为两类:React 复合组件实例(React Composite Components)和 React 宿主组件(React Host Components)实例,并且它只存在于 JavaScript 中。 React 影子树React 影子树是通过 Fabric 渲染器创建的,树由一系列 React 影子节点组成。一个 React 影子节点是一个对象,代表一个已经挂载的 React 宿主组件,其包含的属性 props 来自 JavaScript。它也包括布局信息,比如坐标系 x、y,宽高 width、height。在新渲染器 Fabric 中,React 影子节点对象只存在于 C++ 中。而在老架构中,它存在于手机运行时的堆栈中,比如 Android 的 JVM。 宿主视图树宿主视图树就是一系列的宿主视图,宿主平台有 Android 平台、iOS 平台等等。在 Android 上,宿主视图就是 android.view.ViewGroup实例、 android.widget.TextView实例等等。宿主视图就像积木一样地构成了宿主视图树。每个宿主视图的大小和坐标位置基于的是 LayoutMetrics,而 LayoutMetrics是通过React Native得布局引擎 Yoga 计算出来的。宿主视图的样式和内容信息,是从 React 影子树中得到的。 React Native渲染流水线的各个阶段可能发生在不同的线程中,参考线程模型部分。
2.2 初始化渲染2.2.1 渲染阶段假如,有下面一个组件需要执行渲染:
在上面的例子中,
在元素简化的过程中,每调用一个 React 元素,渲染器同时会同步地创建 React 影子节点。这个过程只发生在 React 宿主组件上,不会发生在 React 复合组件上。比如,一个 在 React 为两个 React 元素节点创建一对父子关系的同时,渲染器也会为对应的 React 影子节点创建一样的父子关系。上面代码,各个渲染阶段的产物如下图所示。 2.2.2 提交阶段在 React 影子树创建完成后,渲染器触发了一次 React 元素树的提交。 布局计算这一步会计算每个 React 影子节点的位置和大小。在 React Native 中,每一个 React 影子节点的布局都是通过 Yoga 布局引擎来计算的。实际的计算需要考虑每一个 React 影子节点的样式,该样式来自于 JavaScript 中的 React 元素。计算还需要考虑 React 影子树的根节点的布局约束,这决定了最终节点能够拥有多少可用空间。 树提升从新树到下一棵树(Tree Promotion,New Tree → Next Tree),这一步会将新的 React 影子树提升为要挂载的下一棵树。这次提升代表着新树拥有了所有要挂载的信息,并且能够代表 React 元素树的最新状态,下一棵树会在 UI 线程下一个"tick"进行挂载(译注:tick 是 CUP 的最小时间单元)。 并且,绝大多数布局计算都是 C++ 中执行,只有某些组件,比如 Text、TextInput 组件等的布局计算是在宿主平台执行的。文字的大小和位置在每个宿主平台都是特别的,需要在宿主平台层进行计算。为此,Yoga 布局引擎调用了宿主平台的函数来计算这些组件的布局。 2.2.3 挂载阶段
站在更高的抽象层次上,React Native 渲染器为每个 React 影子节点创建了对应的宿主视图,并且将它们挂载在屏幕上。在上面的例子中,渲染器为
同时,挂载阶段的所有操作都是在 UI 线程同步执行的。如果提交阶段是在后台线程执行,那么在挂载阶段会在 UI 线程的下一个"tick"执行。另外,如果提交阶段是在 UI 线程执行的,那么挂载阶段也是在 UI 线程执行。挂载阶段的调度和执行很大程度取决于宿主平台。例如,当前 Android 和 iOS 挂载层的渲染架构是不一样的。 2.3 React 状态更新接下来,我们继续看 React 状态更新时,渲染流水线的各个阶段的情况。假设,在初始化渲染时渲染的是如下组件。
通过初始化渲染部分学的知识,我们可以得到如下的三棵树:
此时,我们或许会有一个疑问:React Native 是如何处理这个更新的呢? 从概念上讲,当发生状态更新时,为了更新已经挂载的宿主视图,渲染器需要直接更新 React 元素树。但是为了线程的安全,React 元素树和 React 影子树都必须是不可变的(immutable)。这意味着 React 并不能直接改变当前的 React 元素树和 React 影子树,而是必须为每棵树创建一个包含新属性、新样式和新子节点的新副本。 2.3.1 渲染阶段
React Native 渲染器利用结构共享的方式,将不可变特性的开销变得最小。为了更新 React 元素的新状态,从该元素到根元素路径上的所有元素都需要复制。但 React 只会复制有新属性、新样式或新子元素的 React 元素,任何没有因状态更新发生变动的 React 元素都不会复制,而是由新树和旧树共享。 在上面的例子中,React 创建新树使用了下面这些操作:
操作完成后,节点 1'(Node 1') 就是新的 React 元素树的根节点,我们用 T 代表"先前渲染的树",用 T' 代表"新树"。 2.3.2 提交阶段
在上面的例子中,这些操作包括:UpdateView('Node 3', {backgroundColor: 'yellow'}) 2.3.3 挂载阶段
2.4 渲染器状态更新对于影子树中的大多数信息而言,React 是唯一所有方也是唯一事实源。并且所有来源于 React 的数据都是单向流动的。 但有一个例外。这个例外是一种非常重要的机制:C++ 组件可以拥有状态,且该状态可以不直接暴露给 JavaScript,这时候 JavaScript (或 React)就不是唯一事实源了。通常,只有复杂的宿主组件才会用到 C++ 状态,绝大多数宿主组件都不需要此功能。 例如,ScrollView 使用这种机制让渲染器知道当前的偏移量是多少。偏移量的更新是宿主平台的触发,具体地说是 ScrollView 组件。这些偏移量信息在 React Native 的 measure 等 API 中有用到。因为偏移量数据是由 C++ 状态持有的,所以源于宿主平台更新,不影响 React 元素树。 从概念上讲,C++ 状态更新类似于我们前面提到的 React 状态更新,但有两点不同:
三、跨平台实现在上一代 React Native 渲染器中,React 影子树、布局逻辑、视图拍平算法是在各个平台单独实现的。当前的渲染器的设计上采用的是跨平台的解决方案,共享了核心的 C++ 实现。而Fabric渲染器直接使用 C++ core 渲染实现了跨平台共享。 使用 C++ 作为核心渲染系统有以下几个优点。
同时,React Native 团队还使用了强制不可变的 C++ 特性,来确保并发访问时共享资源即便不加锁保护,也不会有问题。但在 Android 端还有两种例外,渲染器依然会有 JNI 的开销:
React Native 团队在探索使用 ByteBuffer 序列化数据这种新的机制,来替换 ReadableMap,减少 JNI 的开销,目标是将 JNI 的开销减少 35~50%。 渲染器提供了 C++ 与两边通信的 API:
关于 React 与渲染器的通信,包括 渲染(render) React 树和监听 事件(event),比如 onLayout、onKeyPress、touch 等。而React Native 渲染器与宿主平台的通信,包括在屏幕上 挂载(mount) 宿主视图,包括 create、insert、update、delete 宿主视图,和监听用户在宿主平台产生的 事件(event)。 四、视图拍平视图拍平(View Flattening)是 React Native 渲染器避免布局嵌套太深的优化手段。React API 在设计上希望通过组合的方式,实现组件声明和重用,这为更简单的开发提供了一个很好的模型。但是在实现中,API 的这些特性会导致一些 React 元素会嵌套地很深,而其中大部分 React 元素节点只会影响视图布局,并不会在屏幕中渲染任何内容。这就是所谓的 "只参与布局" 类型节点。 从概念上讲,React 元素树的节点数量和屏幕上的视图数量应该是 1:1 的关系。但是,渲染一个很深的"只参与布局"的 React 元素会导致性能变慢。假如,有一个应用,应用中拥有外边距 ContainerComponent的容器组件,容器组件的子组件是 TitleComponent 标题组件,标题组件包括一个图片和一行文字。React 代码示例如下:
React Native 在渲染时,会生成以下三棵树: 为了提升 React 元素树中"只参与布局"类型的性能,渲染器实现了一种视图拍平的机制来合并或拍平这类节点,减少屏幕中宿主视图的层级深度。该算法考虑到了如下属性,比如 margin、padding、backgroundColor和opacity等等。 视图拍平算法是渲染器的对比(diffing)阶段的一部分,这样设计的好处是我们不需要额外的 CUP 耗时,来拍平 React 元素树中"只参与布局"的视图。此外,作为 C++ 核心的一部分,视图拍平算法默认是全平台共用的。 在前面的例子中,视图 2 和视图 3 会作为"对比算法"(diffing algorithm)的一部分被拍平,而它们的样式结果会被合并到视图 1 中。
五、线程模型React Native 渲染器是线程安全的。从更高的视角看,在框架内部线程安全是通过不可变的数据结果保障的,其使用的是 C++ 的 const correctness 特性。这意味着,在渲染器中 React 的每次更新都会重新创建或复制新对象,而不是更新原有的数据结构。这是框架把线程安全和同步 API 暴露给 React 的前提。 在React Native中,渲染器使用三个不同的线程:
下图描述了React Native渲染的完整流程: 5.1 渲染场景在后台线程中渲染这是最常见的场景,大多数的渲染流水线发生在 JavaScript 线程和后台线程。 在主线程中渲染当 UI 线程上有高优先级事件时,渲染器能够在 UI 线程上同步执行所有渲染流水线。 默认或连续事件中断在这个场景中,UI 线程的低优先级事件中断了渲染步骤。React 和 React Native 渲染器能够中断渲染步骤,并把它的状态和一个在 UI 线程执行的低优先级事件合并。在这个例子中渲染过程会继续在后台线程中执行。 不相干的事件中断渲染步骤是可中断的。在这个场景中, UI 线程的高优先级事件中断了渲染步骤。React 和渲染器是能够打断渲染步骤的,并把它的状态和 UI 线程执行的高优先级事件合并。在 UI 线程渲染步骤是同步执行的。 来自 JavaScript 线程的后台线程批量更新在后台线程将更新分派给 UI 线程之前,它会检查是否有新的更新来自 JavaScript。这样,当渲染器知道新的状态要到来时,它就不会直接渲染旧的状态。 C++ 状态更新更新来自 UI 线程,并会跳过渲染步骤。 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 24 Dec 2021 08:16 PM PST 从微服务到云原生---入门[TOC] 微服务微服务架构(Microservice Architecture)是一种架构概念,旨在通过将功能分解到各个离散的服务中以实现对解决方案的解耦。 微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。 架构演进微服务架构区别于传统的单体软件架构,是一种为了适应当前互联网后台服务的「三高需求:高并发、高性能、高可用」而产生的的软件架构。 单体架构单体应用程序的优点
单体应用程序的缺点
微服务架构2014年,Martin Fowler 与 James Lewis 共同提出了微服务的概念,定义了微服务是由以单一应用程序构成的小服务,自己拥有自己的行程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 API 通信。同时服务会使用最小的规模的集中管理能力,服务可以用不同的编程语言与数据库等组件实现 。「维基百科」 鉴于「单体应用程序」有上述的缺点,单个应用程序被划分成各种小的、互相连接的微服务,一个微服务完成一个比较单一的功能,相互之间保持独立和解耦合,这就是微服务架构。 微服务架构的优点
微服务架构的缺点
微服务现状 为了解决上面微服务架构缺点「服务治理」就出现了。出现了众多的组件,如:服务注册、配置中心、服务监控、服务容错(降级、熔断、限流、超时、重试)等。幸好,有巨人的肩膀可以借给我们站上去,通过引入「微服务框架」来帮助我们完成服务治理。 SpringCloudDubbo阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。 Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现 。2011 年末对外开源,仅支持 Java 语言。 下一代微服务 在微服务架构下,基础架构团队一般会为应用提供一个封装了各种服务治理能力的 SDK,这种做法虽然保障了应用的正常运行,但缺点也非常明显,每次基础架构团队迭代一个新功能都需要业务方参与升级才能使用,尤其是 bugfix 版本,往往需要强推业务方升级,这里面的痛苦程度每一个基础架构团队成员都深有体会。 随之而来的就是应用使用的 SDK 版本差别非常大,生产环境同时跑着各种版本的 SDK,这种现象又会让新功能的迭代必须考虑各种兼容,就好像带着枷锁前进一般,这样随着不断迭代,会让代码维护非常困难,有些祖传逻辑更是一不小心就会掉坑里。 同时这种 "重" SDK 的开发模式,导致异构语言的治理能力非常薄弱,如果想为各种编程语言都提供一个功能完整且能持续迭代的 SDK 其中的成本可想而知。 2018 年的时候,Service Mesh 在国内持续火爆,这种架构理念旨在把服务治理能力跟业务解耦,让两者通过进程级别的通信方式进行交互。在这种架构模式下,服务治理能力从应用中剥离,运行在独立的进程中,迭代升级跟业务进程无关,这就可以让各种服务治理能力快速迭代,并且由于升级成本低,因此每个版本都可以全部升级,解决了历史包袱问题,同时 SDK 变 "轻" 直接降低了异构语言的治理门槛,再也不用为需要给各个语言开发相同服务治理能力的 SDK 头疼了。 Service Mesh(服务网格)被认为是下一代微服务架构,Service Mesh并没有给我们带来新的功能,它是用于解决其他工具已经解决过的服务网络调用、限流、熔断和监控等问题,只不过这次是在Cloud Native 的 kubernetes 环境下的实现。 Service Mesh 有如下几个特点:
目前几款流行的 Service Mesh 开源软件 Istio) 、 Linkerd)和MOSN都可以直接在kubernetes 中集成,其中Linkerd已经成为云原生计算基金会 CNCF (Cloud Native Computing Foundation) 成员。 Service Mesh之于微服务,就像TCP/IP之于互联网,TCP/IP为网络通信提供了面向连接的、可靠的、基于字节流的基础通信功能,你不再需要关心底层的重传、校验、流量控制、拥塞控制。 云原生 云原生是一种构建和运行应用程序的方法,是一套技术体系和方法论。云原生(CloudNative)是一个组合词,Cloud+Native。Cloud表示应用程序位于云中,而不是传统的数据中心; Native表示应用程序从设计之初即考虑到云的环境,原生为云而设计,在云上以最佳姿势运行,充分利用和发挥云平台的弹性+分布式优势。Pivotal公司的Matt Stine于2013年首次提出云原生(CloudNative)的概念; 云原生架构总结为:微服务+容器化+DevOps+持续交付。CNCF,全称为Cloud Native Computing Foundation,中文译为"云原生计算基金会"。 容器技术(Docker)简介2010年一位年轻小伙子在美国旧金山成立了一家名叫【dotCloud】的公司, 开发了 Docker的核心技术,从此开启了容器技术的时代。 Docker是一个基于LXC的高级容器引擎。简单地说,docker是一个轻量级的虚拟化解决方案,或者说它是一个超轻量级的虚拟机(容器)。 Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。当前应用的运行时也从物理机时代、虚拟机时代向容器化时代演进。 Docker入门请读: 理念Docker 的理念为"Build, Ship and Run Any App, Anywhere" 架构容器化解决了软件开发过程中一个令人非常头疼的问题,用一段对话描述:
开发人员编写代码,在自己本地环境测试完成后,将代码部署到测试或生产环境中,经常会遇到各种各样的问题。明明本地完美运行的代码为什么部署后出现很多 bug,原因有很多:不同的操作系统、不同的依赖库等,总结一句话就是因为本地环境和远程环境不一致。 容器化技术正好解决了这一关键问题,它将软件程序和运行的基础环境分开。开发人员编码完成后将程序打包到一个容器镜像中,镜像中详细列出了所依赖的环境,在不同的容器中运行标准化的镜像,从根本上解决了环境不一致的问题。 问题与挑战在业务发展初期只有几个微服务,这时用 Docker 就足够了,但随着业务规模逐渐扩大,容器越来越多,运维人员的工作越来越复杂,这个时候就需要编排系统解救运维同学。尽管Docker为容器化的应用程序提供了开放标准,但随着容器越来越多出现了一系列新问题:
为解决这些问题出现了很很多容器编排技术,现在业界比较流行的有:K8S、Mesos、Docker Swarm。 一个成熟的容器编排系统需要具备以下能力:
容器编排(K8S)简介Kubernetes 是用于自动部署,扩展和管理容器化应用程序的开源系统。 K8S(Kubernetes)是Google研发的容器协调器,已捐赠给CNCF,现已开源。脱胎于Google内部久负盛名的大规模集群管理系统Borg,是Google在容器化基础设施领域十余年实践经验的沉淀和升华。 采用了非常优雅的软件工程设计和开源开放的态度,使得用户可以根据自己的使用场景、通过灵活插拔的方式,采用自定义的网络、存储、调度、监控、日志等模块。 Kubernetes在容器编排领域已经成为无可争辩的事实标准。 理念自动化的容器部署、扩展和管理 架构在K8S中,由Master控制节点和Worker节点共同构成一个集群,如下图所示: Master用于管理、监控K8S集群的节点,包含如下组件
Worker(Node)用于运行应用的节点,包含组件如下:
核心功能K8S 提供功能如下:
核心概念PodPod本意是豌豆荚的意思,此处指的是K8S中资源调度的最小单位,豌豆荚里面的小豆子就像是Container,豌豆荚本身就像是一个Pod
DeploymentDeployment 是在 Pod 这个抽象上更为上层的一个抽象,它可以定义一组 Pod 的副本数目、以及这个 Pod 的版本。一般大家用 Deployment 这个抽象来做应用的真正的管理,而 Pod 是组成 Deployment 最小的单元。
ServicePod是不稳定的,IP是会变化的,所以需要一层抽象来屏蔽这种变化,这层抽象叫做Service
Service的工作方式: 不论哪种,kube-proxy都通过watch的方式监控着kube-APIServer写入etcd中关于Pod的最新状态信息,它一旦检查到一个Pod资源被删除了 或 新建,它将立即将这些变化,反应再iptables 或 ipvs规则中,以便iptables和ipvs在调度Clinet Pod请求到Server Pod时,不会出现Server Pod不存在的情况。 userspace Client Pod要访问Server Pod时,它先将请求发给本机内核空间中的service规则,由它再将请求,转给监听在指定套接字上的kube-proxy,kube-proxy处理完请求,并分发请求到指定Server Pod后,再将请求递交给内核空间中的service,由service将请求转给指定的Server Pod。由于其需要来回在用户空间和内核空间交互通信,因此效率很差。 iptables此工作方式是直接由内核中的iptables规则,接受Client Pod的请求,并处理完成后,直接转发给指定ServerPod。 ipvs它是直接有内核中的ipvs规则来接受Client Pod请求,并处理该请求,再有内核封包后,直接发给指定的Server Pod。 VolumeVolume就是存储卷,在Pod中可以声明卷来问访问文件系统,同时Volume也是一个抽象层,其具体的后端存储可以是本地存储、NFS网络存储、云存储(阿里云盘、AWS云盘、Google云盘等)、分布式存储(比如说像 ceph、GlusterFS )
IngressIngress 公开了从集群外部到集群内服务的 HTTP 和 HTTPS 路由。对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。 Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。 下面是一个将所有流量都发送到同一 Service 的简单 Ingress 示例: NamespaceNamespace(命令空间)是用来做资源的逻辑隔离的,比如上面的Pod、Deployment、Service都属于资源,不同Namespace下资源可以重名。同一Namespace下资源名需唯一
问题与挑战
服务网格(Istio)简介 现代应用程序通常被设计成微服务的分布式集合,每个服务执行一些离散的业务功能。服务网格是专门的基础设施层,包含了组成这类体系结构的微服务网络。 服务网格不仅描述了这个网络,而且还描述了分布式应用程序组件之间的交互。所有在服务之间传递的数据都由服务网格控制和路由。 随着分布式服务的部署——比如基于 Kubernetes 的系统——规模和复杂性的增长,它可能会变得更加难以理解和管理。需求可以包括发现、负载平衡、故障恢复、度量和监视。微服务体系结构通常还有更复杂的操作需求,比如 A/B 测试、canary 部署、速率限制、访问控制、加密和端到端身份验证。 服务到服务的通信使分布式应用成为可能。在应用程序集群内部和跨应用程序集群路由这种通信变得越来越复杂。 Istio 有助于减少这种复杂性,同时减轻开发团队的压力。 Istio简介Istio 是一个开源服务网格,它透明地分层到现有的分布式应用程序上。 Istio 强大的特性提供了一种统一和更有效的方式来保护、连接和监视服务。 Istio 是实现负载平衡、服务到服务身份验证和监视的路径——只需要很少或不需要更改服务代码。它强大的控制平面带来了重要的特点,包括:
Istio为微服务应用提供了一个完整的解决方案,可以以统一的方式去检测和管理微服务。同时,它还提供了管理流量、实施访问策略、收集数据等功能,而所有这些功能都对业务代码透明,即不需要修改业务代码就能实现。 有了Istio,就几乎可以不需要其他的微服务框架,也不需要自己去实现服务治理等功能,只要把网络层委托给Istio,它就能帮助完成这一系列的功能。简单来说,Istio就是一个提供了服务治理能力的服务网格,是Kubernetes的好帮手。 Istio的多样化特性可以让你高效地运行分布式微服务架构,并提供一种统一的方式来保护、连接和监控微服务。 理念连接、安全、控制和可观测服务 Service Mesh 是微服务时代的 TCP/IP 协议。 架构Istio的架构从逻辑上分成数据平面(Data Plane)和控制平面(Control Plane),Kubernetes的架构也具有相似的结构,分为控制节点和计算节点。毫无疑问,这样的设计可以很好地解耦各个功能组件。
Istio的网络 核心功能
流量治理 Istio的流量管理是通过Pilot和Envoy这两个组件实现的,将流量和基础设施进行了解耦。Pilot负责配置规则,并把规则分发到Envoy代理去实施;而Envoy按照规则执行各种流量管理的功能,比如动态请求路由,超时、重试和熔断,还可以通过故障注入来测试服务之间的容错能力。下面对这些具体的功能进行逐一介绍。 路由 不同需求开发在测试、发布阶段往往需要将特殊 pod 与基准服务 pod 进行联合调试,通过 virtual rule 和 destination rule 的配置规则,可以精确的将特性流量转到不同的微服务的指定特性 pod 上。 通过 istio 可以快速构建下图所示的基准环境、分支特性环境的通信过程,适应业务的需要。将请求动态路由到微服务的多个版本。 超时可使用 Istio 在 Envoy 中设置请求超时时间,方便的控制应用间调用的超时时间。 重试可设置请求重试次数与重试间隔时间,方便恢复服务。 熔断熔断,是创建弹性微服务应用程序的重要模式。熔断能够使您的应用程序具备应对来自故障、潜在峰值和其他未知网络因素影响的能力。 流量镜像 流量镜像,也称为影子流量,镜像会将实时流量的副本发送到镜像服务。镜像流量发生在主服务的关键请求路径之外。 网关 服务间通信是通过Envoy代理进行的。同样,我们也可以在整个系统的入口和出口处部署代理,使得所有流入和流出的流量都由代理进行转发,而这两个负责入口和出口的代理就叫作入口网关和出口网关。它们相当于整个微服务应用的边界代理,把守着进入和流出服务网格的流量。图2-6展示了Ingress和Egress在请求流中的位置,通过设置Envoy代理,出入服务网格的流量也得到了控制。 入口网关(Ingress) 除了支持 Kubernetes Ingress,Istio还提供了另一种配置模式,Istio Gateway。与 出口网关(Egress) 由于默认情况下,来自Pod 的所有出站流量都会重定向到其 Sidecar 代理,集群外部 URL 的可访问性取决于代理的配置。默认情况下,Istio 将 Envoy 代理配置为允许访问未知服务的请求。尽管这为入门 Istio 带来了方便,但是,通常情况下,应该配置更严格的控制策略来管理和管制对外访问。 故障处理 Istio的故障处理都由Envoy代理完成。Envoy提供了一整套现成的故障处理机制,比如超时、重试、限流和熔断等。这些功能都能够以规则的形式进行动态配置,并且执行运行时修改。这使得服务具有更好的容错能力和弹性,并保证服务的稳定性。 故障注入 简单来说,故障注入就是在系统中人为地设置一些故障,来测试系统的稳定性和系统恢复的能力。比如为某服务设置一个延迟,使其长时间无响应,然后检测调用方是否能处理这种超时问题而自身不受影响(如及时终止对故障发生方的调用,避免自己受到影响且使故障扩展)。 Isito支持注入两种类型的故障:延迟和中断。延迟是模拟网络延迟或服务过载的情况;中断是模拟上游服务崩溃的情况,表现为HTTP的错误码和TCP连接失败。 服务管理服务发现与负载均衡服务发现的前提条件是具有服务注册的能力。目前Kubernetes这类容器编排平台也提供了服务注册的能力。Istio基于平台实现服务发现和负载均衡时,需要通过Pilot和Envoy协作完成,如图2-7所示。Pilot组件会从平台获取服务的注册信息,并提供服务发现的接口,Envoy获得这些信息并更新到自己的负载均衡池。Envoy会定期地对池中的实例进行健康检查,剔除离线的实例,保证服务信息的实时性。 可观测性1.策略在微服务应用中,除了流量管理以外,常常还需要进行一些额外的控制,比如限流(对调用频率、速率进行限制)、设置白名单和黑名单等。 Istio中的策略控制是依靠Mixer完成的。Envoy代理在每次网络请求时,都会调用Mixer进行预先检查,确定是否满足对应的策略。同时,Mixer又可以根据这些来自流量的数据,进行指标数据的采集和汇总,这就是遥测功能。 2.遥测(Telemetry)遥测是工业上常用的一种技术,它是指从远程设备中收集数据,并传输到接收设备进行监测。在软件开发中,遥测的含义引申为对各种指标(metric)数据进行收集,并监控、分析这些指标,比如我们经常听到的BI数据分析。 Mixer的一大主要功能就是遥测。前面已经说过,Envoy代理会发送数据给Mixer,这就使得Mixer具有了数据收集的能力。在本章2.3节对Mixer的介绍中读者已经了解到Mixer的插件模型,也就是适配器。Mixer可以接入不同的后端设施作为适配器,来处理收集到的指标数据,比如日志分析系统、监控系统等。 核心组件EnvoyIstio的核心原理,是网络代理,拦截下所有想拦截的TCP流量。通过对拦截下来的流量的解析和修改。 Envoy本质上是一个为面向服务的架构而设计的7层代理和通信总线。Envoy基于C++11开发而成,性能出色。Envoy包括但不限于以下功能: • 动态服务发现 Pilot Pilot是为我们提供Istio管理配置台,如智能路由(如A/B测试、金丝雀发布等)、弹性(超时、重发、熔断等)等功能的管理系统,它提供了一系列rules api,允许运维人员指定一系列高级的流量管理规则。Pilot负责将我们的配置转换并写入到每个sidecar(Enovy)。 简单来说,Pilot的主要任务有两个。
Mixer Mixer混合了各种策略以及后端数据采集或遥测系统的适配器,从而实现了前端Proxy与后端系统的隔离与汇合。Mixer是一个灵活的插件模型(无论是k8s还是Istio,实现上都很青睐于插件模型,这是一个很灵活的实现方式),它一端连着Envoy,同时我们可以将日志、监控、遥测等各种系统"插入"到Mixer的另一端中,从而得到我们想要的数据或结果。 Citadel 它管理着集群的密钥和证书,是集群的安全部门。典型的如果我们的服务是跨网络通讯(Istio允许我们建立一个安全的集群的集群网络),开发人员想省事懒得对通讯数据进行加解密和身份认证,这事就可以交给Citadel来处理了。更详细的说,Istio各个模块在安全性上分别扮演以下角色: Galley 目前这个组件的作用是验证用户编写的Istio api配置。从官网的说法来看,后面这个组件会逐步接管获取配置、处理和分配的工作,比如从k8s的数据中心(etcd)获取集群信息的活,理论上应该交给Galley。Galley的定位类似于k8s的api server组件,提供集群内统一的配置信息接口,从而将用户配置的细节隔离开来。 其它组件Kiali : 专用于 istio 系统的可视化的APM软件。 Jaeger: 是一个用于分布式链路追踪的开源软件,提供原生 性能评估官方给出了对最新版本V1.1.4的性能测试结果。在由1000个服务和2000个sidecar组成,每秒产生70000个网格范围内的请求的网格中,得到以下结果: 问题与挑战
下一代云原生?WASMWebAssembly是一种新的编码方式,可以在现代的网络浏览器中运行 ,是一个可移植、体积小、加载快并且兼容 Web 的全新格式程序规范。WebAssembly 是由主流浏览器厂商组成的 W3C 社区团体 制定的一个新的规范。 高效:WebAssembly 有一套完整的语义,实际上 wasm 是体积小且加载快的二进制格式, 其目标就是充分发挥硬件能力以达到原生执行效率 安全:WebAssembly 运行在一个沙箱化的执行环境中,甚至可以在现有的 JavaScript 虚拟机中实现。在web环境中,WebAssembly将会严格遵守同源策略以及浏览器安全策略。 开放:WebAssembly 设计了一个非常规整的文本格式用来、调试、测试、实验、优化、学习、教学或者编写程序。可以以这种文本格式在web页面上查看wasm模块的源码。 标准:WebAssembly 在 web 中被设计成无版本、特性可测试、向后兼容的。WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非web环境下。 Serverless2019 年,Serverless 被 Gartner 称为最有潜力的云计算技术发展方向,并被赋予是必然性的发展趋势。从行业趋势看,Serverless 是云计算必经的一场革命。 Serverless ,被称为无服务器,可以解读为一种软件系统架构方法,通常称为 Serverless 架构。代表的是无需理解、管理服务器,按需使用,按使用付费的产品。Serverless 产品中,其中可以包含存储、计算等多种类型的产品,而典型的计算产品,就是云函数这种形态。
分布式应用运行时(Dapr)Dapr 是 Distributed Application Runtime (分布式应用运行时)的缩写。是一种可移植的,事件驱动的运行时,用于构建跨云和边缘的分布式应用。号称:"Any language, any framework, anywhere" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1.2w字 | 从 0 到 1 上手 Web Components 业务组件库开发 Posted: 23 Dec 2021 06:31 AM PST 组件化是前端发展的一个重要方向,它一方面提高开发效率,另一方面降低维护成本。主流的 Vue.js、React 及其延伸的 Ant Design、uniapp、Taro 等都是组件框架。 本文将带大家回顾 Web Components 核心 API,并从 0 到 1 实现一个基于 Web Components API 开发的业务组件库。 最终效果:https://blog.pingan8787.com/exe-components/demo.html 一、回顾 Web Components在前端发展历史中,从刚开始重复业务到处复制相同代码,到 Web Components 的出现,我们使用原生 HTML 标签的自定义组件,复用组件代码,提高开发效率。通过 Web Components 创建的组件,几乎可以使用在任何前端框架中。 1. 核心 API 回顾Web Components 由 3 个核心 API 组成:
另外,还有 HTML imports,但目前已废弃,所以不具体介绍,其作用是用来控制组件的依赖加载。 2. 入门示例接下来通过下面简单示例快速了解一下如何创建一个简单 Web Components 组件。
上面代码主要做 3 件事:
通过实现
将组件的标签和组件类作为参数,通过
导入组件后,跟使用普通 HTML 标签一样直接使用自定义组件 随后浏览器访问 3. 兼容性介绍在 MDN | Web Components 章节中介绍了其兼容性情况:
关于兼容性,可以看下图: 这个网站里面,有很多关于 Web Components 的优秀项目可以学习。 4. 小结这节主要通过一个简单示例,简单回顾基础知识,详细可以阅读文档: 二、EXE-Components 组件库分析设计1. 背景介绍假设我们需要实现一个 EXE-Components 组件库,该组件库的组件分 2 大类:
以通用简单组件为主,如
以复杂、组合组件为主,如 详细可以看下图: 接下来我们会基于上图进行 EXE-Components 组件库设计和开发。 2. 组件库设计在设计组件库的时候,主要需要考虑以下几点:
当然,这几个是最基础需要考虑的点,随着实际业务的复杂,还需要考虑更多,比如:工程化相关、组件解耦、组件主题等等。 针对前面提到这 3 点,这边约定几个命名规范:
3. 组件库组件设计这边我们主要设计 这边属性命名看着会比较复杂,大家可以按照自己和团队的习惯进行命名。 这样我们思路就清晰很多,实现对应组件即可。 三、EXE-Components 组件库准备工作本文示例最终将对实现的组件进行组合使用,实现下面「用户列表」效果: 体验地址:https://blog.pingan8787.com/exe-components/demo.html 1. 统一开发规范首先我们先统一开发规范,包括:
组件开发模版分
2. 开发环境搭建和工程化处理为了方便使用 EXE-Components 组件库,更接近实际组件库的使用,我们需要将组件库打包成一个 UMD 类型的 js 文件。这边我们使用 rollup 进行构建,最终打包成
接下来通过
然后在
其中:
这样就完成简单的本地开发和组件库构建的工程化配置,接下来就可以进行开发了。 四、EXE-Components 组件库开发1. 组件库入口文件配置前面 三个入口文件内容分别如下:
2. 开发 exe-avatar 组件 index.js 文件通过前面的分析,我们可以知道
接着按照之前的模版,开发入口文件
其中有几个方法是抽取出来的公用方法,大概介绍下其作用,具体可以看源码:
来自 template.js 暴露的方法,传入配置 config,来生成 HTML 模版。
传入一个 HTMLElement 元素,返回该元素上所有属性键值对,其中会对
由于通过属性传递进来的方法,是个字符串,所以进行封装,传入 另外,Web Components 生命周期可以详细看文档:使用生命周期回调函数。 3. 开发 exe-avatar 组件 template.js 文件该文件暴露一个方法,返回组件 HTML 模版:
最终实现效果如下: 开发完第一个组件,我们可以简单总结一下创建和使用组件的步骤: 4. 开发 exe-button 组件按照前面
模版定义如下:
最终效果如下: 5. 开发 exe-user-avatar 组件该组件是将前面
主要内容在 template.js 中:
其中
最终效果如下: 6. 实现一个用户列表业务接下来我们通过一个实际业务,来看看我们组件的效果:
我们就可以通过简单 for 循环拼接 HTML 片段,然后添加到页面某个元素中:
到这边我们就实现了一个用户列表的业务,当然实际业务可能会更加复杂,需要再优化。 五、总结本文首先简单回顾 Web Components 核心 API,然后对组件库需求进行分析设计,再进行环境搭建和开发,内容比较多,可能没有每一点都讲到,还请大家看看我仓库的源码,有什么问题欢迎和我讨论。
最后看完本文,大家是否觉得用 Web Components 开发组件库,实在有点复杂?要写的太多了。 拓展阅读 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Posted: 23 Dec 2021 11:03 PM PST 大家好,我是bigsai。 最近不少小伙伴跟我交流刷题肿么刷,我给的建议就是先剑指offer和力扣hot100,在这些题中还有些重要程度和出现频率是非常非常高的,今天给大家分享当今出现频率最高的10道算法题,学到就是赚到。 0X01翻转链表力扣206和剑指offer24原题,题意为: 给你单链表的头节点 分析: 翻转链表,本意是不创建新的链表节点然后在原链表上实现翻转,但是这个图有点会误导人的思维,其实更好的理解你可以看下面这幅图: 具体实现上两个思路,非递归和递归的实现方式,非递归的实现方式比较简单,利用一个pre节点记录前驱节点,向下枚举的时候改变指针指向就可以,实现代码为:
而递归的方式比较巧妙,借助递归归来的过程巧妙改变指针指向和返回值传递,代码虽然精简但是理解起来有一定难度的,这里用一张图帮助大家理解: 具体代码为:
0X02设计LRU对应力扣146LRU缓存机制,题目要求为: 运用你所掌握的数据结构,设计和实现一个 LRU 缓存机制 。实现 LRUCache 类: LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 进阶:在 O(1) 时间复杂度内完成这两种操作 详细分析:一次倒在LRU上的经历 LRU的核心就是借助哈希+双链表,哈希用于查询,双链表实现删除只知道当前节点也能O(1)的复杂度删除,不过双链表需要考虑的头尾指针特殊情况。 具体实现的代码为:
0X03环形链表对应力扣141和力扣142,力扣141环形链表要求为: 给定一个链表,判断链表中是否有环,用O(1)内存解决。 详细分析:环形链表找入口,真的太妙了 这个问题利用快慢双指针比较高效,快指针fast每次走2步,slow每次走1步,慢指针走n步到尾时候快指针走了2n步,而环的大小一定小于等于n所以一定会相遇,如果相遇那么说明有环,如果不相遇fast先为null说明无环。 具体代码为:
力扣142是在力扣141拓展,如有有环,返回入环的那个节点,就想下图环形链表返回节点2。 这个问题是需要数学转换的,具体的分析可以看上面的详细分析,这里面提一下大题的步骤。 如果找到第一个交汇点,其中一个停止,另一个继续走,下一次交汇时候刚好走一圈,可以算出循环部分长度为y。 所以我们知道的东西有:交汇时候fast走2x步,slow走x步,环长为y。并且快指针和慢指针交汇时候,多走的步数刚好是换长y的整数倍(它两此刻在同一个位置,快指针刚好多绕整数倍圈数才能在同一个位置相聚),可以得到2x=x+ny(x=ny)。其中所以说慢指针走的x和快指针多走的x是圈长y的整数倍。 也就是说,从开头走到这个点共计x步,从这个点走x步也就是绕了几圈也回到这个点。如果说slow从起点出发,fast从这个点出发(每次走一步,相当于之前两步抵消slow走的路程),那么走x步还会到达这个点,但是这两个指针这次都是每次走一步,所以一旦slow到达循环圈内,两个指针就开始汇合了。 实现代码为:
0X04两个栈实现队列对应剑指offer09,题意为: 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 ) 分析: 解决这个问题,要知道栈是什么,队列是什么,两种常见数据结构格式很简单,栈的特点就是:后进先出,队列的特点就是:先进先出,栈可以想象成一堆书本,越在上面的取的越早,上面来上面出(比喻一下);队列就是想象成排队买东西,只能后面进前面出,所以两者数据结构还是有区别的,虽然都是单个入口进出,但是栈进出口相同,而队列不同。 上面描述的是一个普通栈和队列的数据结构,这里面让我们用两个栈实现一个队列的操作,这里比较容易想的方案就是其中一个栈stack1用作数据存储,插入尾时候直接插入stack1,而删除头的时候将数据先加入到另一个栈stack2中,返回并删除栈顶元素,将stack2顺序加入stack1中实现一个复原,但是这样操作插入时间复杂度为O(1),删除时间复杂度为O(n)比较高。 实现方式也给大家看下:
这样的时间复杂度是不被喜欢的,因为删除太鸡儿耗时了,每次都要折腾一番,有没有什么好的方法能够让删除也方便一点呢? 有啊,stack1可以顺序保证顺序插入,stack1数据放到stack2中可以保证顺序删除,所以用stack1作插入,stack2作删除,因为题目也没要求数据必须放到一个容器中,所以就这样组合使用,完美perfect! 具体实现的时候,插入直接插入到stack1中,如果需要删除从stack2中栈顶删除,如果stack2栈为空那么将stack1中数据全部添加进来(这样又能保证stack2中所有数据是可以顺序删除的了),下面列举几个删除的例子 其实就是将数据分成两个部分,一部分用来插入,一部分用来删除,删除的那个栈stack2空了添加所有stack1中的数据继续操作。这个操作插入删除的时间复杂度是O(1),具体实现的代码为:
0X05二叉树层序(锯齿)遍历二叉树的遍历,对应力扣102,107,103. 详细分析:一次面试,被二叉树层序遍历打爆了 如果普通二叉树层序遍历,也不是什么困难的问题,但是它会有个分层返回结果的操作,就需要你详细考虑了。 很多人会用两个容器(队列)进行分层的操作,这里其实可以直接使用一个队列,我们首先记录枚举前队列大小len,然后根据这个大小len去枚举遍历就可以得到完整的该层数据了。 还有一个难点就是二叉树的锯齿层序(也叫之字形打印),第一趟是从左往右,第二趟是从右往左,只需要记录一个奇偶层数进行对应的操作就可以了。 这里就拿力扣103二叉树的锯齿形层序遍历作为题板给大家分享一下代码:
0X06 二叉树中后序遍历(非递归)二叉树的非递归遍历也是考察的重点,对于中序后序遍历递归实现很简单,非递归实现起来还是要点技巧的哦。 详细分析:二叉树的各种遍历(递归、非递归) 对于二叉树的中序遍历,其实就是正常情况第二次访问该节点的时候才抛出输出(第一次数前序),这样我们枚举每个节点第一次不能删除,需要先将它存到栈中,当左子节点处理完成的时候在抛出访问该节点。 核心也就两步,叶子节点左右都为null,也可满足下列条件:
实现代码为:
而后序遍历按照递归的思路其实一般是第三次访问该节点是从右子节点回来才抛出输出,这个实现起来确实有难度。但是具体的实现,我们使用一个pre节点记录上一次被抛出访问的点,如果当前被抛出的右孩子是pre或者当前节点右为null,那么就将这个点抛出,否则说明它的右侧还未被访问需要将它"回炉重造",后面再用!如果不理解可以看前面的详细介绍。 具体实现的代码为:
当然,后序遍历也有用前序(根右左)的前序遍历结果最后翻转一下的,但面试官更想考察的还是上面提到的方法。 0X07 跳台阶(斐波那契、爬楼梯)爬楼梯、跳台阶是一个经典问题,对应剑指offer10和力扣70题,题目的要求为: 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。 分析: 这个问题入门级别dp,分析当前第k阶的结果,每个人可以爬1个或者2个台阶,那么说明它可能是由k-1或者k-2来的,所以就是两个子情况的叠加(需要特殊考虑一下初始情况),这个思路有人会想到递归,没错用递归确实可以解决但是用递归效率较低(因为这个是个发散的递归一个拆成两个),使用记忆化搜索会稍微好一些。 但是dp是比较好的方法,核心状态转移方程为:
当然,有的数据很大求余的跳台阶,可以用矩阵快速幂解决,但是这里就不介绍啦,有兴趣可以详细看看。 0X08 TOPK问题TOPK问题真的非常经典,通常问的有最小的K个数,寻找第K大都是TOPK这种问题,这里就用力扣215寻找数组第K大元素作为板子。 详细分析:一文拿捏TOPK TOPK的问题解决思路有很多,如果优化的冒泡或者简单选择排序,时间复杂度为O(nk),使用优化的堆排序为O(n+klogn),不过掌握快排的变形就可以应付大体上的所有问题了(面试官要是让你手写堆排序那真是有点难为你了)。 快排每次确定一个数pivot位置,将数分成两部分:左面的都比这个数pivot小,右面的都比这个数pivot大,这样就可以根据这个k去判断刚好在pivot位置,还是左侧还是右侧?可以压缩空间迭代去调用递归最终求出结果。 很多人为了更快过测试样例将这个pivot不选第一个随机选择(为了和刁钻的测试样例作斗争),不过这里我就选第一个作为pivot了,代码可以参考:
0X09 无重复的最长子串(数组)这个问题可能是个字符串也可能是数组,但是道理一致,无重复字符的最长子串和最长无重复子数组本质一致。 题目要求为:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 分析: 此题就是给一个字符串让你找出最长没有重复的一个子串。 要搞清子串和子序列的区别: 子串:是连续的,可以看成原串的一部分截取。 那么我们如何处理呢? 暴力查找,暴力查找当然是可以的,但是复杂度过高这里就不进行讲解了。这里选择的思路是滑动窗口,滑动窗口,就是用一个区间从左往右,右侧先进行试探,找到区间无重复最大值,当有重复时左侧再往右侧移动一直到没重复,然后重复进行到最后。在整个过程中找到最大子串即可。 具体实现时候可以用数组替代哈希表会快很多:
0X10 排序不会真的有人以为用个Arrays.sort()就完事了吧,手写排序还是很高频的,像冒泡、插入这些简单的大家相比都会,像堆排序、希尔、基数排序等考察也不多,比较高频的就是快排了,这里额外奖励一个也很高频的归并排序,两个都是典型分治算法,也可以将快排和前面的TOPK问题比较一番。 排序详细的十大排序都有详细讲过,大家可以自行参考:程序员必知必会十大排序 快排: 具体实现:
归并排序: 实现代码为:
结语好了,今天给大家分享的10个问题,是真的在面试中非常非常高频,我敢说平均每两次面试就得遇到这里面的其中一个题(毫不夸张)! 虽说题海很深学不完,但是学过缓存的都知道要把热点数据放缓存,考过试的都知道要把必考点掌握……这十个问题已经送到嘴边。 当然,这只是非常非常高频的问题,要想拿捏笔试,肯定还要不断积累、刷题,也欢迎各位加入我的力扣打卡群坚持刷题! 原创不易,求个三连! 本文首发个人技术公众号「 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
一次说清,为什么在Antd Modal中调resetFields调了个寂寞 Posted: 23 Dec 2021 06:21 PM PST 背景在干了大半年增删查改后(node,mysql,serverless),业务端人手短缺,老板开恩让我支持其他团队写几个页面。 久了不摸手生,除了react依稀记得,antd基本只能看着官方demo一行一行写,感觉一天能写完的,结果两天了还没联调完。中间还遇到一些似曾相识的问题,可惜以前的经验已经不管用了。 demo地址: https://codesandbox.io/s/antd... 这些问题在antd的仓库issue都反复被提及,看了下文,包括但不限于以下问题都将得到答案: 概括一下:
有事说事语言描述显得太苍白,所以直接看动图吧: 这是一个简单的增删查改页面,新增和编辑共享了同一个组件,期望在打开弹窗编辑表单关闭后,重新打开时,能根据initialValues重新渲染表单, 但得到的结果是,第二次打开,编辑框没有刷新. 实现的伪代码大致是这样:
相信出现问题的盆友们,大多都是和我一样,如上面这样的代码这样实现。 具体问题,具体分析先给结论,之所以会出现上面的那些问题,主要是三个问题导致:
initialValues初始化数据时候,第二次、第三次……传递新值,表单没有更新?因为initialValues只在表单首次初始化时有效,只要表单没有卸载并重新挂载,改变initialValues都不会刷新表单的值,form最初的设计就如此;以下是initialValues初始化存到store的完整实现:
this.store 是存放在form实例中的,只要实例不销毁,store的值就不会变化。 destroyOnClose,弹出层新建表单重新设置值不起作用?首先这里有个概念,initialValues 在Form表单实例挂载时,这个值是被存在了用hooks生成的form实例中。 所以当我们使用了destroyOnClose,虽然销毁了Modal 以及Modal框中的Form,但这个form实例仍然存在,这个hook实例是挂载在EditModal元素上的,并没有被一起销毁,所以当弹窗再次打开,Form表单又会根据这个form的store再次渲染(原因见上)。 Modal 用了destroyOnClose,里面有 Form,并使用 form.resetFields,为什么会失效?当我们意识到form实例没有被销毁,可能保存了上一个表单编辑状态时,我们会想到使用useEffect钩子,去观察初始值,采用form.resetFields去重置实例,但最后发现这并没有起作用(我也踩到了这个坑上)。 当我去掉destroyOnClose,我发现生效了,后面我去看了一下form.resetFields的实现源码:
这个实现和initialValues 一样简单明了,所以问题不在resetFields。问题是出在Modal身上,简单来讲Moda的创建有一个异步过程,所以子组件的渲染并不是同步的。正常的组件渲染是下面这样的: 只需和我上面一样,在resetFields加一句console, 就会发现_this.initialValues是上一次的初始值,而不是新传入的(因为Form元素还未挂载),所以这里resetFields调了个寂寞。 还有一种简单的方法证明Modal组件的子组件挂载是异步的,就是如下面这样去玩:
这个实现,你会发现resetFields居然生效了,因为一个宏任务后,Form元素已经挂载上。 所以这里告诉我们,要尽量少用destroyOnClose,因为Modal的渲染是耗时的且费力的。 Modal使用了form.resetFields初始化,要连续打开两次才生效?相信经过上面的一系列解释,你的心中已经有了答案;destroyOnClose 确实不适合在Modal中写表单时用。 所以,Modal中重置Form initalValues的正确姿势了吗? 吃一堑,长一智这一次经历后,我记住了:
欢迎关注我的前端公众号:前端黑洞 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Redis 分布式锁的正确实现原理演化历程与 Redission 实战总结 Posted: 23 Dec 2021 06:59 PM PST Redis 分布式锁使用 分布式锁的门道可没那么简单,我们在网上看到的分布式锁方案可能是有问题的。 「码哥」一步步带你深入分布式锁是如何一步步完善,在高并发生产环境中如何正确使用分布式锁。 在进入正文之前,我们先带着问题去思考:
什么时候用分布式锁?码哥,说个通俗的例子讲解下什么时候需要分布式锁呢? 诊所只有一个医生,很多患者前来就诊。 医生在同一时刻只能给一个患者提供就诊服务。如果不是这样的话,就会出现医生在就诊肾亏的「肖菜鸡」准备开药时候患者切换成了脚臭的「谢霸哥」,这时候药就被谢霸哥取走了。 治肾亏的药被有脚臭的拿去了。 当并发去读写一个【共享资源】的时候,我们为了保证数据的正确,需要控制同一时刻只有一个线程访问。 分布式锁就是用来控制同一时刻,只有一个 JVM 进程中的一个线程可以访问被保护的资源。 分布式锁入门65 哥:分布式锁应该满足哪些特性?
码哥,我可以使用 这个命令来自于 命令的返回值:
如下场景: 敲代码一天累了,想去放松按摩下肩颈。 168 号技师最抢手,大家喜欢点,所以并发量大,需要分布式锁控制。 同一时刻只允许一个「客户」预约 168 技师。 肖菜鸡申请 168 技师成功:
谢霸哥后面到,申请失败:
此刻,申请成功的客户就可以享受 168 技师的肩颈放松服务「共享资源」。 享受结束后,要及时释放锁,给后来者享受 168 技师的服务机会。 肖菜鸡,码哥考考你如何释放锁呢? 很简单,使用
码哥,你见过「龙」么?我见过,因为我被一条龙服务过。 肖菜鸡,事情可没这么简单。 这个方案存在一个存在造成锁无法释放的问题,造成该问题的场景如下:
这样,这个锁就会一直占用,锁在我手里,我挂了,这样其他客户端再也拿不到这个锁了。 超时设置码哥,我可以在获取锁成功的时候设置一个「超时时间」 比如设定按摩服务一次 60 分钟,那么在给这个
这样,到点后锁自动释放,其他客户就可以继续享受 168 技师按摩服务了。 谁要这么写,就糟透了。 「加锁」、「设置超时」是两个命令,他们不是原子操作。 如果出现只执行了第一条,第二条没机会执行就会出现「超时时间」设置失败,依然出现锁无法释放。 码哥,那咋办,我想被一条龙服务,要解决这个问题 Redis 2.6.X 之后,官方拓展了
这样写还不够,我们还要防止不能释放不是自己加的锁。我们可以在 value 上做文章。 继续往下看…… 释放了不是自己加的锁这样我能稳妥的享受一条龙服务了么? No,还有一种场景会导致释放别人的锁:
有个关键问题需要解决:自己的锁只能自己来释放。 我要如何删除是自己加的锁呢? 在执行 解铃还须系铃人
伪代码如下:
有没有想过,这是 我们可以通过
这样通过唯一值设置成 value 标识加锁的客户端很重要,仅使用 DEL 是不安全的,因为一个客户端可能会删除另一个客户端的锁。 使用上面的脚本,每个锁都用一个随机字符串"签名",只有当删除锁的客户端的"签名"与锁的 value 匹配的时候,才会删除它。 官方文档也是这么说的:https://redis.io/topics/distlock 这个方案已经相对完美,我们用的最多的可能就是这个方案了。 正确设置锁超时锁的超时时间怎么计算合适呢? 这个时间不能瞎写,一般要根据在测试环境多次测试,然后压测多轮之后,比如计算出平均执行时间 200 ms。 那么锁的超时时间就放大为平均执行时间的 3~5 倍。 为啥要放放大呢? 因为如果锁的操作逻辑中有网络 IO操作、JVM FullGC 等,线上的网络不会总一帆风顺,我们要给网络抖动留有缓冲时间。 那我设置更大一点,比如设置 1 小时不是更安全? 不要钻牛角,多大算大? 设置时间过长,一旦发生宕机重启,就意味着 1 小时内,分布式锁的服务全部节点不可用。 你要让运维手动删除这个锁么? 只要运维真的不会打你。 有没有完美的方案呢?不管时间怎么设置都不大合适。 我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁「续航」。 加锁的时候设置一个过期时间,同时客户端开启一个「守护线程」,定时去检测这个锁的失效时间。 如果快要过期,但是业务逻辑还没执行完成,自动对这个锁进行续期,重新设置过期时间。 这个道理行得通,可我写不出。 别慌,已经有一个库把这些工作都封装好了他叫 Redisson。 在使用分布式锁时,它就采用了「自动续期」的方案来避免锁过期,这个守护线程我们一般也把它叫做「看门狗」线程。 一路优化下来,方案似乎比较「严谨」了,抽象出对应的模型如下。
这个方案实际上已经比较完美,能写到这一步已经打败 90% 的程序猿了。 但是对于追求极致的程序员来说还远远不够:
加解锁代码位置有讲究根据前面的分析,我们已经有了一个「相对严谨」的分布式锁了。 于是「谢霸哥」就写了如下代码将分布式锁运用到项目中,以下是伪代码逻辑:
有没有想过:一旦执行业务逻辑过程中抛出异常,程序就无法执行释放锁的流程。 所以释放锁的代码一定要放在 加锁的位置也有问题,放在 try 外面的话,如果执行 所以 综上所述,正确代码位置如下 :
实现可重入锁65 哥:可重入锁要如何实现呢? 当一个线程执行一段代码成功获取锁之后,继续执行时,又遇到加锁的代码,可重入性就就保证线程能继续执行,而不可重入就是需要等待锁释放之后,再次获取锁成功,才能继续往下执行。 用一段代码解释可重入:
假设 X 线程在 a 方法获取锁之后,继续执行 b 方法,如果此时不可重入,线程就必须等待锁释放,再次争抢锁。 锁明明是被 X 线程拥有,却还需要等待自己释放锁,然后再去抢锁,这看起来就很奇怪,我释放我自己~ Redis Hash 可重入锁Redisson 类库就是通过 Redis Hash 来实现可重入锁 当线程拥有锁之后,往后再遇到加锁方法,直接将加锁次数加 1,然后再执行方法逻辑。 退出加锁方法之后,加锁次数再减 1,当加锁次数为 0 时,锁才被真正的释放。 可以看到可重入锁最大特性就是计数,计算加锁的次数。 所以当可重入锁需要在分布式环境实现时,我们也就需要统计加锁次数。 加锁逻辑我们可以使用 Redis hash 结构实现,key 表示被锁的共享资源, hash 结构的 fieldKey 的 value 则保存加锁的次数。 通过 Lua 脚本实现原子性,假设 KEYS1 = 「lock」, ARGV「1000,uuid」:
加锁代码首先使用 Redis 如果锁不存在的话,直接使用 如果当前锁存在,则使用 最后如果上述两个逻辑都不符合,直接返回。 解锁逻辑
首先使用 如果 lock 对应 Hash 表不存在,或者 Hash 表不存在 uuid 这个 key,直接返回 若存在的情况下,代表当前锁被其持有,首先使用 解锁代码执行方式与加锁类似,只不过解锁的执行结果返回类型使用
主从架构带来的问题码哥,到这里分布式锁「很完美了」吧,没想到分布式锁这么多门道。 路还很远,之前分析的场景都是,锁在「单个」Redis 实例中可能产生的问题,并没有涉及到 Redis 主从模式导致的问题。 我们通常使用「Cluster 集群」或者「哨兵集群」的模式部署保证高可用。 这两个模式都是基于「主从架构数据同步复制」实现的数据同步,而 Redis 的主从复制默认是异步的。 以下内容来自于官方文档 https://redis.io/topics/distlock 我们试想下如下场景会发生什么问题:
虽然这个概率极低,但是我们必须得承认这个风险的存在。 Redis 的作者提出了一种解决方案,叫 Redlock(红锁) Redis 的作者为了统一分布式锁的标准,搞了一个 Redlock,算是 Redis 官方对于实现分布式锁的指导规范,https://redis.io/topics/distlock,但是这个 Redlock 也被国外的一些分布式专家给喷了。 因为它也不完美,有"漏洞"。 什么是 Redlock红锁是不是这个? 泡面吃多了你, 大家可以看官方文档(https://redis.io/topics/distlock),以下来自官方文档的翻译。 想用使用 Redlock,官方建议在不同机器上部署 5 个 Redis 主节点,节点都是完全独立,也不使用主从复制,使用多个节点是为容错。 一个客户端要获取锁有 5 个步骤:
另外部署实例的数量要求是奇数,为了能很好的满足过半原则,如果是 6 台则需要 4 台获取锁成功才能认为成功,所以奇数更合理 事情可没这么简单,Redis 作者把这个方案提出后,受到了业界著名的分布式系统专家的质疑。 两人好比神仙打架,两人一来一回论据充足的对一个问题提出很多论断……
Redlock 是与非Martin Kleppmann 认为锁定的目的是为了保护对共享资源的读写,而分布式锁应该「高效」和「正确」。
出于这两点,我们没必要承担 Redlock 的成本和复杂,运行 5 个 Redis 实例并判断加锁是否满足大多数才算成功。 主从架构崩溃恢复极小可能发生,这没什么大不了的。使用单机版就够了,Redlock 太重了,没必要。 Martin 认为 Redlock 根本达不到安全性的要求,也依旧存在锁失效的问题! Martin 的结论
Redis 作者 Antirez 的反驳在 Redis 作者的反驳文章中,有 3 个重点:
关于 Redlock 的争论我们下期再见,现在进入 Redisson 实现分布式锁实战部分。 Redisson 分布式锁基于 SpringBoot starter 方式,添加 starter。
不过这里需要注意 springboot 与 redisson 的版本,因为官方推荐 redisson版本与 springboot 版本配合使用。 将 Redisson 与 Spring Boot 库集成,还取决于 Spring Data Redis 模块。 「码哥」使用 SpringBoot 2.5.x 版本, 所以需要添加 redisson-spring-data-25。
添加配置文件
就这样在 Spring 容器中我们拥有以下几个 Bean可以使用:
基于Redis的Redisson分布式可重入锁 失败无限重试
拿锁失败时会不停的重试,具有Watch Dog 自动延期机制,默认续30s 每隔30/3=10 秒续到30s。 失败超时重试,自动续命
超时自动释放锁
超时重试,自动解锁
Watch Dog 自动延时如果获取分布式锁的节点宕机,且这个锁还出于锁定状态,就会出现死锁。 为了避免这个情况,我们都会给锁设置一个超时自动释放时间。 然而,还是会存在一个问题。 假设线程获取锁成功,并设置了 30 s 超时,但是在 30s 内任务还没执行完,锁超时释放了,就会导致其他线程获取不该获取的锁。 所以,Redisson 提供了 watch dog 自动延时机制,提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。 也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁超时时间,锁不会因为超时而被释放。 默认情况下,看门狗的续期时间是30s,也可以通过修改Config.lockWatchdogTimeout来另行指定。 另外Redisson 还提供了可以指定leaseTime参数的加锁方法来指定加锁的时间。 超过这个时间后锁便自动解开了,不会延长锁的有效期。 原理如下图: 有两个点需要注意:
源码导读在调用lock方法时,会最终调用到tryAcquireAsync。调用链为:lock()->tryAcquire->tryAcquireAsync`,详细解释如下:
scheduleExpirationRenewal 中会调用renewExpiration启用了一个timeout定时,去执行延期动作。
scheduleExpirationRenewal 会调用到 renewExpirationAsync,执行下面这段 lua脚本。 他主要判断就是 这个锁是否在redis中存在,如果存在就进行 pexpire 延期。
总结完工,我建议你合上屏幕,自己在脑子里重新过一遍,每一步都在做什么,为什么要做,解决什么问题。 我们一起从头到尾梳理了一遍 Redis分布式锁中的各种门道,其实很多点是不管用什么做分布式锁都会存在的问题,重要的是思考的过程。 对于系统的设计,每个人的出发点都不一样,没有完美的架构,没有普适的架构,但是在完美和普适能平衡的很好的架构,就是好的架构。 |
You are subscribed to email updates from SegmentFault 最新的文章. To stop receiving these emails, you may unsubscribe now. | Email delivery powered by Google |
Google, 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States |
No comments:
Post a Comment