【转载】OpenStack Swift学习笔记



免责声明:
    本文转自网络文章,转载此文章仅为个人收藏,分享知识,如有侵权,请联系博主进行删除。
    原文作者:崔炳华 
    原文地址:http://blog.csdn.net/i_chips/article/details/17787017

1       概述

OpenStack Object Storage(Swift)是OpenStack开源云计算项目的子项目之一。Swift的目的是使用普通硬件来构建冗余的、可扩展的分布式对象存储集群,存储容量可达PB级。

Swift并不是文件系统或者实时的数据存储系统,它是对象存储,用于永久类型的静态数据的长期存储,这些数据可以检索、调整,必要时进行更新。最适合存储的数据类型的例子是虚拟机镜像、图片存储、邮件存储和存档备份。

Swift无需采用RAID(磁盘冗余阵列),也没有中心单元或主控结点。Swift通过在软件层面引入一致性哈希技术和数据冗余性,牺牲一定程度的数据一致性来达到高可用性(High Availability,简称HA)和可伸缩性,支持多租户模式、容器和对象读写操作,适合解决互联网的应用场景下非结构化数据存储问题。

2       技术特性

2.1        Swift的主要特征

Swift的主要特性如下:

  • 极高的数据持久性(Durability)。
  • 完全对称的系统架构:“对称”意味着Swift中各节点可以完全对等,能极大地降低系统维护成本。
  • 无限的可扩展性:一是数据存储容量无限可扩展;二是Swift性能(如QPS、吞吐量等)可线性提升。
  • 无单点故障:Swift的元数据存储是完全均匀随机分布的,并且与对象文件存储一样,元数据也会存储多份。整个Swift集群中,也没有一个角色是单点的,并且在架构和设计上保证无单点业务是有效的。
  • 简单、可依赖。

2.2        Swift和HDFS的技术差异

Swift和Hadoop分布式文件系统(HDFS)都有着相似的目的:实现冗余、快速、联网的存储,它们的技术差异如下:

  • 在Swift中,元数据呈分布式,跨集群复制。而在HDFS使用了中央系统来维护文件元数据(Namenode,名称节点),这对HDFS来说无异于单一故障点,因而扩展到规模非常大的环境显得更困难。
  • Swift在设计时考虑到了多租户架构,而HDFS没有多租户架构这个概念。
  • 在Swift中,文件可以写入多次;在并发操作环境下,以最近一次操作为准。而在HDFS中,文件写入一次,而且每次只能有一个文件写入。
  • Swift用Python来编写,而HDFS用Java来编写。
  • Swift被设计成了一种比较通用的存储解决方案,能够可靠地存储数量非常多的大小不一的文件;而HDFS被设计成可以存储数量中等的大文件(HDFS针对更庞大的文件作了优化),以支持数据处理。

3       关键技术

3.1        一致性哈希(ConsistentHashing)

在分布式对象存储中,一个关键问题是数据该如何存放。Swift是基于一致性哈希技术,通过计算可将对象均匀分布到虚拟空间的虚拟节点上,在增加或删除节点时可大大减少需移动的数据量;虚拟空间大小通常采用2的n次幂,便于进行高效的移位操作;然后通过独特的数据结构 Ring(环)再将虚拟节点映射到实际的物理存储设备上,完成寻址过程。

图1 一致性哈希环结构

衡量一致性哈希的4个指标:

  • 平衡性(Balance):平衡性是指Hash的结果能够尽可能分布均匀,充分利用所有缓存空间。
  • 单调性(Monotonicity):单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲中去,而不会被映射到旧的缓冲集合中的其他缓冲区。
  • 分散性(Spread):分散性定义了分布式环境中,不同终端通过Hash过程将内容映射至缓存上时,因可见缓存不同,Hash结果不一致,相同的内容被映射至不同的缓冲区。
  • 负载(Load):负载是对分散性要求的另一个纬度。既然不同的终端可以将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。

Swift使用该算法的主要目的是在改变集群的node数量时(增加/删除服务器),能够尽可能少地改变已存在key和node的映射关系,以满足单调性。

考虑到哈希算法在node较少的情况下,改变node数会带来巨大的数据迁移。为了解决这种情况,一致性哈希引入了“虚拟节点”(vnode,也称为partition)的概念: “虚拟节点”是实际节点在环形空间的复制品,一个实际节点对应了若干个“虚拟节点”,“虚拟节点”在哈希空间中以哈希值排列。

总的来说,Swift中存在两种映射关系,对于一个文件,通过哈希算法(MD5)找到对应的虚节点(一对一的映射关系),虚节点再通过映射关系(ring文件中二维数组)找到对应的设备(多对多的映射关系),这样就完成了一个文件存储在设备上的映射。

图2 对象、虚结点、节点间的映射关系

在设置虚结点数的时候,需要对系统预期的规模做充分考虑,假如集群的规模不会超过6000个结点,那么可以将虚结点数设置为结点数的100倍。这样,变动任意一个结点的负载仅影响1%的数据项。此时有6百万个vnode数,使用2bytes来存储结点数(0~65535)。基本的内存占用是6*(10^6)*2bytes=12Mb,对于服务器来说完全可以承受。

假设有65536(2^16)个node,有128(2^7)倍的partition数(2^23,则PARTITION_POWER=23)。由于MD5码是32位的,使用PARTITION_SHIFT(等于32- PARTITION_POWER)将数据项的MD5哈希值映射到partition的2^23的空间中。

3.2        数据一致性模型(ConsistencyModel)

按照Eric Brewer的CAP(Consistency,Availability,PartitionTolerance)理论,无法同时满足3个方面,Swift放弃严格一致性(满足ACID事务级别),而采用最终一致性模型(Eventual Consistency),来达到高可用性和无限水平扩展能力。

为了实现这一目标,Swift采用Quorum仲裁协议(Quorum有法定投票人数的含义):

  • 定义:N:数据的副本总数;W:写操作被确认接受的副本数量;R:读操作的副本数量。
  • 强一致性:R+W>N,以保证对副本的读写操作会产生交集,从而保证可以读取到最新版本;如果 W=N,R=1,则需要全部更新,适合大量读少量写操作场景下的强一致性;如果 R=N,W=1,则只更新一个副本,通过读取全部副本来得到最新版本,适合大量写少量读场景下的强一致性。
  • 弱一致性:R+W<=N,如果读写操作的副本集合不产生交集,就可能会读到脏数据;适合对一致性要求比较低的场景。

Swift针对的是读写都比较频繁的场景,所以采用了比较折中的策略,即写操作需要满足至少一半以上成功W>N/2,再保证读操作与写操作的副本集合至少产生一个交集,即R+W>N。

在分布式系统中,数据的单点是不允许存在的。线上正常存在的replica数量是1的话将非常危险的,因为一旦这个replica再次错误,就可能发生数据的永久性错误。假如我们把N设置成为2,那么,只要有一个存储节点发生损坏,就会有单点的存在。所以N必须大于2。但N越高,系统的维护和整体成本就越高。所以,工业界通常把N设置为3。

Swift默认配置是N=3,W=2>N/2,R=1或2,即每个对象会存在3个副本,这些副本会被尽量存储在不同区域的节点上;W=2表示至少需要更新2个副本才算写成功。

当R=1时,意味着某一个读操作成功便立刻返回,此种情况下可能会读取到旧版本(弱一致性模型)。

当R=2时,需要通过在读操作请求头中增加x-newest=true参数来同时读取2个副本的元数据信息,然后比较时间戳来确定哪个是最新版本(强一致性模型)。

如果数据出现了不一致,后台服务进程会在一定时间窗口内通过检测和复制协议来完成数据同步,从而保证达到最终一致性。

图3 Quorum协议示例

3.3        环(Ring)

Ring是Swift中最重要的组件,用于记录存储对象与物理位置间的映射关系。在涉及查询Account、Container、Object信息时就需要查询集群的Ring信息。

环是为了将虚拟节点(partition,分区)均衡地映射到一组物理存储设备上,并提供一定的冗余度而设计的,其数据结构由以下信息组成:

存储设备列表、设备信息包括唯一标识号(id)、区域号(zone)、权重(weight)、IP 地址(ip)、端口(port)、设备名称(device)、元数据(meta)。

Swift为账户、容器和对象分别定义了的Ring,其查找过程是相同的。Ring中每个partition在集群中都默认有3个replica。每个partition的位置由ring来维护,并存储在映射中。

Ring使用zone来保证数据的物理隔离。每个partition的replica都确保放在了不同的zone中。Zone只是个抽象概念,它可以是一个磁盘(disk drive),一个服务器(server),一个机架(cabinet),一个交换机(switch),甚至是一个数据中心(datacenter),以提供最高级别的冗余性,建议至少部署5个zone。

权重参数是个相对值,可以来根据磁盘的大小来调节,权重越大表示可分配的空间越多,可部署更多的分区。

当集群中发生存储节点宕机、新增(删)存储节点、新增(删)zone等必须改变partition和node间的映射关系时,还可以对Ring文件通过重新平衡(rebalance)来进行更新。当虚节点需要移动时,环会确保一次移动最少数量的虚节点数,并且一次只移动一个虚节点的一个副本。

总的来说,Ring引入一致性哈希的原因是为了减少由于增加结点导致数据项移动的数量来提高单调性;引入partition的原因是为了减少由于节点数过少导致移动过多的数据项;引入replica的原因是防止数据单点、提高冗余性;引入zone的原因是为了保证分区容忍性;引入weight的原因是为了保证partition分配的均衡。

4       架构设计

4.1        Swift数据模型

Swift采用层次数据模型,共设三层逻辑结构:Account/Container/Object(账户/容器/对象)。每层节点数均没有限制,可以任意扩展。这里的账户和个人账户不是一个概念,可理解为租户,用来做顶层的隔离机制,可以被多个个人账户所共同使用;容器类似文件夹,代表封装一组对象;对象由元数据和数据两部分组成。

4.2        Swift系统架构

Swift采用完全对称、面向资源的分布式系统架构设计,所有组件都可扩展,避免因单点失效而扩散并影响整个系统运转;通信方式采用非阻塞式 I/O 模式,提高了系统吞吐和响应能力。

Swift组件包括:

  • 代理服务(ProxyServer):Swift通过Proxy Server向外提供基于HTTP的REST服务接口,会根据环的信息来查找服务地址并转发用户请求至相应的账户、容器或者对象,进行CRUD(增删改查)等操作。由于采用无状态的REST请求协议,可以进行横向扩展来均衡负载。在访问Swift服务之前,需要先通过认证服务获取访问令牌,然后在发送的请求中加入头部信息 X-Auth-Token。代理服务器负责Swift架构的其余组件间的相互通信。代理服务器也处理大量的失败请求。例如,如果对于某个对象PUT请求时,某个存储节点不可用,它将会查询环可传送的服务器并转发请求。对象以流的形式到达(来自) 对象服务器,它们直接从代理服务器传送到(来自)用户—代理服务器并不缓冲它们。
  • 认证服务(AuthenticationServer):验证访问用户的身份信息,并获得一个对象访问令牌(Token),在一定的时间内会一直有效;验证访问令牌的有效性并缓存下来直至过期时间。
  • 缓存服务(CacheServer):缓存的内容包括对象服务令牌,账户和容器的存在信息,但不会缓存对象本身的数据;缓存服务可采用Memcached集群,Swift会使用一致性哈希算法来分配缓存地址。
  • 账户服务(AccountServer):提供账户元数据和统计信息,并维护所含容器列表的服务,每个账户的信息被存储在一个SQLite数据库中。
  • 容器服务(ContainerServer):提供容器元数据和统计信息(比如对象的总数,容器的使用情况等),并维护所含对象列表的服务。容器服务并不知道对象存在哪,只知道指定容器里存的哪些对象。 这些对象信息以SQLite数据库文件的形式存储,和对象一样在集群上做类似的备份。 
  • 对象服务(ObjectServer):提供对象元数据和内容服务,可以用来存储、检索和删除本地设备上的对象。在文件系统中,对象以二进制文件的形式存储,它的元数据存储在文件系统的扩展属性(xattr)中,建议采用默认支持扩展属性(xattr)的XFS文件系统。每个对象使用对象名称的哈希值和操作的时间戳组成的路径来存储。最后一次写操作总可以成功,并确保最新一次的对象版本将会被处理。删除也被视为文件的一个版本(一个以".ts"结尾的0字节文件,ts表示墓碑)。
  • 复制服务(Replicator):会检测本地分区副本和远程副本是否一致,具体是通过对比哈希文件和高级水印来完成,发现不一致时会采用推式(Push)更新远程副本:对于对象的复制,更新只是使用rsync同步文件到对等节点。帐号和容器的复制通过HTTP或rsync来推送整个数据库文件上丢失的记录;另外一个任务是确保被标记删除的对象从文件系统中移除:当有一项(对象、容器、或者帐号)被删除,则一个墓碑文件被设置作为该项的最新版本。复制器将会检测到该墓碑文件并确保将它从整个系统中移除。
  • 更新服务(Updater):当对象由于高负载或者系统故障等原因而无法立即更新时,任务将会被序列化到在本地文件系统中进行排队,以便服务恢复后进行异步更新;例如成功创建对象后容器服务器没有及时更新对象列表,这个时候容器的更新操作就会进入排队中,更新服务会在系统恢复正常后扫描队列并进行相应的更新处理。
  • 审计服务(Auditor):在本地服务器上会反复地爬取来检查对象,容器和账户的完整性,如果发现比特级的错误,文件将被隔离,并复制其他的副本以覆盖本地损坏的副本;其他类型的错误(比如在任何一个容器服务器中都找不到所需的对象列表)会被记录到日志中。
  • 账户清理服务(AccountReaper):移除被标记为删除的账户,删除其所包含的所有容器和对象。删除账号的过程是相当直接的。对于每个账号中的容器,每个对象先被删除然后容器被删除。任何失败的删除请求将不会阻止整个过程,但是将会导致整个过程最终失败(例如,如果一个对象的删除超时,容器将不能被删除,因此账号也不能被删除)。整个处理过程即使遭遇失败也继续执行,这样它不会因为一个麻烦的问题而中止恢复集群空间。账号收割器将会继续不断地尝试删除账号直到它最终变为空,此时数据库在db_replicator中回收处理,最终移除这个数据库文件。

图4 Swift系统架构

Swift支持的所有操作可以总结为下表:

表1 SwiftRESTful API总结

image

4.3        Ring的数据结构

Ring 的数据结构由三个顶层域构成,其中:

  • List of Devices,表示集群中设备的列表,在Ring类内部被称为devs;
  • Partition Assignment List,用于存放每个replica与device间映射关系,在Ring类内部被称为_replica2part2dev_id;
  • Partition Shift Value,表示计算数据hash的移位量,在Ring类内部称为_part_shift。

使用python读取/etc/swift/object.ring.gz存放的数据,可以获得以devs、 part_shift、 replica2part2dev_id 为key的dict类数据。

4.4        Swift存储结构

在Storage Node上运行着Linux系统并使用了XFS文件系统,逻辑上使用一致性哈希算法将固定总数的partition映射到每个Storage Node上,每个data也使用同样的哈希算法映射到partition上。

存储内容一般放在/srv/node/sdb1之类的路径下,其目录结构如下所示:accounts、async_pending、containers、objects、quarantined和tmp。其中accounts、containers、objects分别是账号、容器、对象的存储目录,async_pending是异步待更新目录,quarantined是隔离目录,tmp是临时目录。

  • objects:在objects目录下存放的是各个partition目录,其中每个partition目录是由若干个suffix_path名的目录和一个hashes.pkl文件组成,suffix_path目录下是由object的hash_path名构成的目录,在hash_path目录下存放了关于object的数据和元数据;object的数据存放在后缀为.data的文件中,它的metadata存放在以后缀为.meta的文件中,将被删除的Object以一个0字节后缀为.ts的文件存放。
  • accounts:在accounts目录下存放的是各个partition,而每个partition目录是由若干个suffix_path目录组成,suffix_path目录下是由account的hsh名构成的目录,在hsh目录下存放了关于account的sqlite db;在account的db文件中,包含了account_stat、container、incoming_sync 、outgoing_sync 4张表;其中,表account_stat是记录关于account的信息,如名称、创建时间、container数统计等等;表container记录关于container的信息;表incoming_sync记录到来的同步数据项;表outgoing_sync表示推送出的同步数据项。
  • containers:containers目录结构和生成过程与accounts类似,containers的db中共有5张表,其中incoming_sync和outgoing_sync的schema与accounts中的相同。其他3张表分别为container_stat、object、sqlite_sequence;表container_stat与表account_stat相似,其区别是container_stat存放的是关于container信息。
  • tmp:tmp目录作为account/container/object server向partition目录内写入数据前的临时目录。例如,client向server上传某一文件,object server调用DiskFile类的mkstemp方法创建在路径为path/device/tmp的目录。在数据上传完成之后,再调用put()方法,将数据移动到相应路径。
  • async_pending:async_pending存放未能及时更新而被加入更新队列的数据。本地server在与remote server建立HTTP连接或者发送数据时超时导致更新失败时,将把文件放入async_pending目录。这种情况经常发生在系统故障或者是高负荷的情况下。如果更新失败,本次更新被加入队列,然后由Updater继续处理这些失败的更新工作;account与container的db和object两者的pending文件处理方式有所不同:db的pending文件在更新完其中的一项数据之后,删除pending文件中的相应的数据项,而object的数据在更新完成之后,移动pending文件到目标目录。
  • quarantined:quarantined路径用于隔离发生损坏的数据。Auditor进程会在本地服务器上每隔一段时间就扫描一次磁盘来检测account、container、object的完整性。一旦发现不完整的数据,该文件就会被隔离,该目录就称为quarantined目录。为了限制Auditor消耗过多的系统资源,其默认扫描间隔是30秒,每秒最大的扫描文件数为20,最高速率为10Mb/s。account和container的Auditor的扫描间隔比object要长得多。

图5 隔离对象的处理流程

5       小结

Swift牺牲一定程度的数据一致性,来达到高可用性和可伸缩性,支持多租户模式、容器和对象读写操作,适合解决互联网的应用场景下非结构化数据存储问题。

有理由相信,因为其完全的开放性、广泛的用户群和社区贡献者,Swift可能会成为云存储的开放标准,从而打破Amazon S3在市场上的垄断地位,推动云计算在朝着更加开放和可互操作的方向前进。

6       参考资料

1) 《Openstack Swift 原理、架构与 API 介绍》,http://www.kankanews.com/ICkengine/archives/66411.shtml

2) 《深入云存储系统Swift核心组件:Ring实现原理剖析》,http://www.cnblogs.com/yuxc/archive/2012/06/22/2558312.html

3) 《深入云存储系统Swift核心组件:Ring数据结构及构建、重平衡操作》,http://www.cnblogs.com/yuxc/archive/2012/06/28/2568584.html

4) 《深入云存储系统Swift存储节点:存储实现分析》,http://www.cnblogs.com/yuxc/archive/2012/07/04/2575536.html

5) 《OpenStack对象存储——Swift开源云计算》,http://dev.yesky.com/244/33228744.shtml

6) 《讨论:HDFS和OpenStack对象存储的技术差异》,http://os.51cto.com/art/201202/314254.htm

转载于:https://www.cnblogs.com/sdjnzqr/p/3909498.html

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

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

相关文章

Android Studio 选项菜单和动画结合_Android 应用与iOS 应用之间的设计差异对比!

同一个App&#xff0c;为什么iOS 和Android 的交互操作有那么大的区别&#xff1f;本文将用大量原生设计案例&#xff0c;为你一一说明它们为什么应该这样做&#xff0c;赶紧学起来&#xff01;了解并适当结合平台规范与优势&#xff0c;才能做到最佳的用户体验。为了创建最佳的…

图片操作案例:python 批量更改图像尺寸到统一大小的方法

一、需求&#xff1a; 批量修改该图片长与宽尺寸 二、素材&#xff1a; 三、代码&#xff1a; from PIL import Image import os.path import globdef convertjpg(jpgfile,outdir,width512,height512):imgImage.open(jpgfile)try:new_imgimg.resize((width,height),Image.BI…

LeetCode 753. 破解保险箱(DFS)

文章目录1. 题目2. 解题1. 题目 有一个需要密码才能打开的保险箱。 密码是 n 位数, 密码的每一位是 k 位序列 0, 1, …, k-1 中的一个 。 你可以随意输入密码&#xff0c;保险箱会自动记住最后 n 位输入&#xff0c;如果匹配&#xff0c;则能够打开保险箱。 举个例子&#x…

windows副本不是正版怎么解决_解决Windows沙盒怎么联网问题

windows 沙盒简单来讲就是一款沙盒虚拟化的Windows系统&#xff0c;而最近还蛮多小伙伴来问小编说&#xff0c;Windows沙盒能够联网吗&#xff1f;Windows沙盒怎么联网&#xff1f;针对这个问题&#xff0c;接下来小编就来和大家好好的说说关于windows沙盒的情况吧。1、首先我们…

西瓜视频(头条)解析并利用IDM工具下载

一、西瓜视频网址解析完整代码&#xff1a; import requests import urllib3 urllib3.disable_warnings() import re import json import base64cookie你的cookie headers{"user-agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like…

天池 在线编程 寻找字母(计数)

文章目录1. 题目2. 解题1. 题目 给定一个字符串str&#xff0c;返回字符串中字母顺序最大的而且同时在字符串中出现大写和小写的字母。 如果不存在这样的字母&#xff0c;返回‘~‘。 please return uppercase |str|<1000示例 例 1&#xff1a; 输入:"aAbBcD" 输…

电脑硬件检测_【学无止境】电脑硬件维修测试学习资料(附送各类PC检修资源)...

大橙子资源驿站『 电脑硬件维修测试学习资料』多年的PC硬件检修工具和资料&#xff0c;都是本人一年年亲测积累起来的&#xff0c;安全无毒。打包上传分享给大家。—— 资源分享者语关于『 电脑硬件维修学习资料 』这是甛橙从论坛搬运的&#xff0c;因为看着评分挺高&#xff0…

加速国内 Github 访问,下载的9种方案!

1、GitHub 镜像访问 ​​​​​​​这里提供两个最常用的镜像地址&#xff1a; https://github.com.cnpmjs.org https://hub.fastgit.org 也就是说上面的镜像就是一个克隆版的Github&#xff0c;你可以访问上面的镜像网站&#xff0c;网站的内容跟Github是完整同步的镜像&…

天池 在线编程 三等分(模拟)

文章目录1. 题目2. 解题1. 题目 给定一个由 0 和 1 组成的数组 A&#xff0c;将数组分成 3 个非空的部分&#xff0c;使得所有这些部分表示相同的二进制值。 如果可以做到&#xff0c;请返回任何 [i, j]&#xff0c;其中 i1 < j&#xff0c;这样一来&#xff1a; A[0], A…

Python设计模式之模板方法模式实例详解

1、模板方法模式定义 定义一个操作中的算法的框架&#xff0c;而将一些步骤延迟到子类中&#xff0c;使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定的步骤。 子类实现的具体方法叫作基本方法&#xff0c;实现对基本方法高度的框架方法&#xff0c;叫作模板方…

小米2s自带rec刷root_刷Magisk模块开机卡Logo了怎么办?两种方法教你轻松解决

虽然&#xff0c;Magisk由于工作原理的不同&#xff0c;其模块对系统的兼容性相比较于此前的Xposed框架模块要好得多&#xff0c;但是依旧有可能出现刷了模块之后&#xff0c;卡Logo&#xff0c;无法开机的情况。今天&#xff0c;小编就提供两种方式解决刷Magisk模块卡开机Logo…

proteus如何添加stm32_电路仿真软件详谈(六),Proteus电路仿真软件的超级应用

一款优秀的电路仿真软件&#xff0c;可帮助用户更好实现相应功能&#xff0c;而Proteus电路仿真软件便是这样一款利器。本文中&#xff0c;将讲解基于Proteus电路仿真软件的SPI接口的设计与实现。通过本文&#xff0c;希望大家对Proteus电路仿真软件的应用具备深层次的理解。1、…

LeetCode 803. 打砖块(并查集)

文章目录1. 题目2. 解题1. 题目 有一个 m x n 的二元网格&#xff0c;其中 1 表示砖块&#xff0c;0 表示空白。 砖块 稳定&#xff08;不会掉落&#xff09;的前提是&#xff1a; 一块砖直接连接到网格的顶部&#xff0c;或者至少有一块相邻&#xff08;4 个方向之一&#x…

python设计模式之享元模式

一、享元模式定义&#xff1a; 享元模式是一种用于解决资源和性能压力时会使用到的设计模式&#xff0c;它的核心思想是通过引入数据共享来提升性能。 在开发3D游戏时&#xff0c;例如有成千上万的士兵或者有成千上万棵树&#xff0c;如果一个3D地带的每个对象都单独创建&…

allegro多版本安装_Homebrew 安装旧版本软件包马克

大家好我叫乌图米&#xff0c;我会在这里陆续跟大家分享一些有的没的数码体验、软件技巧、系统知识&#xff0c;欢迎大家留言与我交流&#xff5e;如果你喜欢文章的内容&#xff0c;可以在文末点个赞 &#xff0c;你的支持就是我最大的动力 &#xff01;这篇文章介绍一下 Homeb…

利用Python实现PDF转文本,就是如此简单!

一、前言 对很多人来说&#xff0c;将PDF转换为可编辑的文本是个刚需&#xff0c;却苦于没有简单的方法。发现 pdf 幻灯片&#xff0c;效果还不错。 传统的讲座通常伴随有很多pdf幻灯片。一般来说&#xff0c;想要对自己的讲座做笔记&#xff0c;需要从pdf复制、补充大量内容…

LeetCode 1725. 可以形成最大正方形的矩形数目

文章目录1. 题目2. 解题1. 题目 给你一个数组 rectangles &#xff0c;其中 rectangles[i] [li, wi] 表示第 i 个矩形的长度为 li 、宽度为 wi 。 如果存在 k 同时满足 k < li 和 k < wi &#xff0c;就可以将第 i 个矩形切成边长为 k 的正方形。 例如&#xff0c;矩形…

Python实现给指定的微信朋友发信息

一、环境准备 1、Python3.6 2、itchat第三方库 pip install itchat-uos 3、pyinstaller第三方库 pip install pyinstaller 二、核心代码 import itchatimport timeprint("请扫描弹出的扫二维码")itchat.auto_login(hotReloadTrue)boom_name input("请输…

LeetCode 1726. 同积元组(排列组合)

文章目录1. 题目2. 解题1. 题目 给你一个由 不同 正整数组成的数组 nums &#xff0c;请你返回满足 a * b c * d 的元组 (a, b, c, d) 的数量。其中 a、b、c 和 d 都是 nums 中的元素&#xff0c;且 a ! b ! c ! d 。 示例 1&#xff1a; 输入&#xff1a;nums [2,3,4,6] 输…

vuedraggle choose_如何拆分员工工资条,教你一招一学就会。(五)函数CHOOSE

作为人事管理者&#xff0c;我们最重要的一个工作就是工资表&#xff0c;那么如何将工资表拆分成工资条呢&#xff1f;我们最后看一下函数CHOOSE对函数MOD、ROW、OFFSET 、INT的嵌套使用。第一步&#xff0c;我们输入函数CHOOSE&#xff0c;第一个值是序号&#xff0c;我们将之…