百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

如何实现一个高性能的对象存储(OSS)

zhezhongyun 2025-05-08 22:24 5 浏览

1 简介

对象存储服务是当前云计算厂商非常重要的基础PASS业务。除了各种用户的业务需要依赖对象存储保存各种各样的数据,对象存储同时也是云计算内部其他服务依赖的重要的存储基础设施(比如容器镜像、主机镜像等等、作为大数据分析数据湖),更多的介绍可以参看各大云计算厂商的,如下为aws s3的简介。

 Amazon Simple Storage Service (Amazon S3) 是一种对象存储服务,提供行业领先的可
扩展性、数据可用性、安全性和性能。这意味着各种规模和行业的客户都可以使用它来存储和保护各种用例
(如网站、移动应用程序、备份和还原、存档、企业应用程序、IoT 设备和大数据分析)的任意数量的数据
。Amazon S3 提供了易于使用的管理功能,因此您可以组织数据并配置精细调整过的访问控制以满足特定的
业务、组织和合规性要求。Amazon S3 可达到 99.999999999%(11 个 9)的持久性,并为全球各地的公
司存储数百万个应用程序的数据。                                        by  aws s3            

打造对象存储的整个业务形态涉及到非常多的内容包括,认证、授权、acl-认证授权体系、限流、容灾、流式统计等等。本文主要抽丝剥茧,分析对象存储的核心(存储)。如果从0开始打造一个对象存储,可以有怎样的架构选择,如何去打造一款在简洁性、可扩展性、可用性等方面都比较优秀的对象存储架构。使得一方面:使核心架构从长远看不会拖业务快速发展的后腿;另一方面:在聚焦核心的同时,能够使得后续的业务拓展是足够灵活的,既保持核心业务的简洁和外围业务的灵活性。

注:本文假设阅读对象对分布式Table存储(如HBASE,BIGTABLE)、分布式文件系统(如HDFS,GFS)、对象存储等有一定基础的了解。

2 核心逻辑

对象存储的业务模型有哪些特点?以下列举几点:

  1. KV存储:抽象上来说,对象存储业务提供的是一个KV存储服务。向业务提供Put/Get/Delete/Scan 接口(分块上传)
PUT   /Bucket/ObjectName
Get   /Bucket/ObjectName
Delete /Bucket/ObjectName
SCAN 
详见更多详细的对象存储的API(aws s3):
https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html
  1. 业务模型多样:与普通KV存储很不一样的点是,Value 大小范围特别大,从1KB ~ 100TB 级别。
  2. 规模巨大:元数据记录数量巨大(千亿/万亿记录),数据量巨大(EB级别)。

所以从逻辑数据模型来说,对象存储其实就是类似一张大Map, 支持能够各种value大小[(From KB ~ TB);也就是说 按照类BigTable的说法来定义的话,ObjectStorage is a distributed,persistent sorted map that store variable values

可以看到,基本的数据模型其实跟Bigtable非常相似(PS:BigTable is a sparse, distributed, persistent multidimensional sorted map

逻辑视图

事实上,在BigTable的开源实现HBASE中,其在2017年就考虑过想在HBASE实现MOB(Moderate Object)即100K~10MB大小对象的存储,使得能够在一些业务场景下能够在简单的一个系统中保存PDF、文档、照片等等。其在设计MOB方案中的几种架构对比中,基本就是当前打造对象存储的基本思路。以下基本按照这个思路,对对象存储架构的架构选型和一些优缺点进行分析。

3 实现方案

  • BigTable 的模型存储:直接使用较为直接的BigTable 稀疏大表模型来实现对象存储 key、元数据和数据,一套系统搞定。
  • BigTable + FileSystem: key 和元数据使用Bigtable模型存储,value的数据使用分布式文件系存储
  • BigTable MOB Design:采用类似HBase HBASE 实现在为了存储 100KB ~10MB 对象的存储方案

以上几种方案可以说代表了当前对象存储的基本思路。

Meta Storage:在元数据存储这块,当前各大公司都根据自身的基础设施能力和业务选择使用自身的方案,比如分库分表的Mysql方案、类似HBASE Table Store存放元数据,比如京东直接使用TIKV的方案进行存放,亦或自研的分布式KV方案。

Data Storage: 数据存储这块基本也有类似的选择,为大Value存储优化的表格存储、直接使用HDFS、CEPH、或者自研的分布式文件系统等等。

术语说明

  • DFS:Distribute File System代表了典型的分布式文件系统,比如GFS、HDFS、CEPH、GlusterFS等等
  • TableStore: 分布式表格存储服务,可以是类似Bigtable模型的Hbase,也可指分布式KV系统(TIKV等等)。虽外提供PUT/GET/DELETE/SCAN等基础的kv存储接口。
  • LOB: Large Object, 大对象,10MB+、GB+ 、TB级别大小的对象。
  • MOB:Moderate Object,中等大小对象, 100KB ~ 10MB
  • MetaData: 1K~100KB,小对象,甚至基本可以作为元数据存储。

3.1 Write Data in DFS and Metadata In TableStore

顾名思义,这种方案就是单独使用DFS保存文件(BlobData),然后把文件名直接作为索引直接保存在Table 对应Record的字段中。

这种方案对于LOB比较友好。但是对MOB/Meta不是非常友好,因为这会导致DFS NameServer 消耗大量的元数据内存,如果底层文件系统也是一个文件一个文件保存,那么在本地文件系统层面也会产生海量的小文件。

比如: file 的大小在 500KB,一个文件占用内存300byte,那么48GB只能存放160w 文件,也就是只能存储80TB 空间, 而现在高密度存储机器,典型比如36盘位,8TB盘,一个机器就可以提供近96TB的逻辑数据空间。显然这样的管理效率是比较差的,当前一个分布式文件系统管理100PB数据非常常见,常见如HDFS的方案,一组高可用的元数据服务器,管理500~1K左右的机器是非常普遍的方案。

所以如果使用类似这种方案来解决Blob内容的存储,通常都需要采用小文件合并大文件的方式。比如在TableStore Table中保存文件名、offset、length。

但是这会导致需要一整套配套系统去管理一个大文件中的中所有Blob记录会比较麻烦,比如删除,回收空间等。因为一般的分布式文件系统当前都只支持Append(比如HDFS、比如Window Azure Stream Layer 等等),不支持随机地址空间的删除和写入(特别是在引入EC之后,需要进行条带对齐操作,更不支持随机写入和DigHole)。

所以接下来怎么玩,怎么进行后续空间的收敛,还是一样。基本是结合Meta Storage 和Data Storage 实现类似LSM Tree Compaction的方式实现对删除记录回收。基本思路为2条

  1. 建立DataStore 到MetaStore的索引
  2. 文件中有效数据重写+ 调整元数据Index的方式实现垃圾数据的回收

基本原理如下图所示。

当然具体实现上面可以有很多优化和考虑的地方

  1. 如何实现快速的有效的索引机制实现对文件有效数据率的分析
  2. compaction重写的时候如何结合TableStore 的Batch 等特性实现更加高效的元数据写入(例如类似HBase的Bulk Load 机制等等)
  3. 等等

这里不做过多的张开,纸上得来终是粗浅,在具体面对业务做深度实现的时候会铺开所有的细节。

  • 方案优点

系统是比较flexible,对于技术架构不明确的团队,在没有靠谱的一体化的方案,但是也希望实现很多feature来说,在找方向的过程中,业务通过好的技术管理、相关成熟的内部组件可以做到持续演进。

  • 方案缺点

这样一个系统的代价是比较明确的,元数据层面解决扩展性、一致性、性能。数据层面单独解决一致性、扩展性、性能。还要需要一套外部系统进行粘合,系统失去完整性。

3.2 Directly Store Meta And Data in the TableStore Tables

也就是如上图(逻辑视图)所示的,将数据和元数据存储在一行的不同的Colume Family中,如上图所示,将对象的Data部分保存在单独的ColumeFamily(BlobData)中。同时减少data colume family的 compaction 频次。

该方案问题:

  • MemStore频繁Flush:会导致TableStore memstore 更加频繁得flush(比如设置MemStore 的大小为128MB),从而导致更加频繁得compaction(为了减少读放大,文件数量超过一定数据会进行Compaction)。
  • Blob无法直接存储:在一些较大的Value的情况下,比如1个GB的value,基本没法直接存储,必须得拆分。
  • 写入放大:MOB/LOB由于数据量比较大,所以会导致compaction的写入放大会非常大,导致compaciton 的速度降低。另外由于数据都得先写入 WAL,所以大量的Data 写入WAL,然后再dump 到SSTable。会使得写入带宽放大2倍。
  • Splite频繁: Blob storage 会导致region split 更加频繁,split同样会导致block update。

如果使用 less compaction的策略,文件数量会不断增多。会导致打开文件句柄的数量太大。所以直接使用原生BigTable模型的TableStore基本是没办法直接使用的。

3.3 HBASE MOB

HBASE MOB 方案的实现基本是与第一种方案3.1 (Write Data In DFS &Metadata in TableStore )一致。不同的是作为Server全服务端实现,从而实现与当前HBase基本一致的API体系(最小化对API的修正);同样的能够在数据一致性;支持当前HBASE的快照,克隆等所有体系内的功能做得比较好。

基于HBASE 的内部实现,对于外部接口没有特别的变化,只是设置某个ColumeFamily为MOB字段,并且在MOB字段大于预先设定的值(比如大于100KB)的情况下会在原来的StoreFile 中保存索引,将value字段保存在另外一个Region中MOBFile中(PS:这个Region的MOBFile垃圾回收等机制是独立的,不在当前常规的HBASE的compaction等的管辖范围内), 对于小于设定的值,则保存在原本的StoreFile中。(如下图所示)

ps:MOB为单独的colume family,如上为逻辑视图。

  • 写入流程
  1. 首先将所有的记录数据写入HLOG 保障写入/删除/更新的一致性,然后写入memstore
  2. 在memTable 中的数据超过一定大小之后(比如256MB)之后,将MOB value 刷入单独的MOBFile中,在原来的StoreFile中只保留只想指向对应MOB文件的文件名(不保存offset,在MOBfile 同样记录了对应记录的 key,在查找的时候需要在MOB File 中进行查找)(TIKV 的Titan 引擎是保存了offset。保存offset 会一定程度导致storefile 的写入与 MOBfile 的写入一定程度耦合),数据单独刷入MOB file。

ps: MOB记录保存在MOB-File中,MOB 记录核心上的主要读写模式主要就是single 的随机读取,与普通的HFile的扫描不太一样。对于持续的排序没有需求,所以不需要进行经常的compaction。也就是可以通过减少compaction,延迟的compaction,等到一定的空间开销(也就是说等到文件充分删除之后,比如50%+ 删除率)的时候时候进行Compaction重写。

  • 读取流程
  1. 扫描memtable,发现则直接返回
  2. 通过storefile扫描对应的记录,如内容在对应的StoreFile中则直接返回
  3. 否则通过storefile中记录得索引指向的MOBFile进行读取

(PS:从优化层面,可以通过类似Titan的方式避免RegionServer 带宽的放大(即在读取到MOB CELL 之后直接可以去记录文件的 HDFS 文件和 offset进行读取实际文件内容)

  • compaction机制

MOB支持2种删除数据的方式

  1. TTL删除:一种是使用 用户自定义TTL的方式(这种比较适合于视频等周期性rotate的场景,比如数据只需要保存6个月,删除的方式是非常的简单高效)
  2. 非TTL方式:没有TTL删除规则,记录的删除操作保存在HBASE StoreFile 中的。StoreFile在compaction(major)过程中,对于已经删除的包含MOB字段的Record的删除记录并不会直接进行删除,而是会记录在单独的deletefile中。会根据删除记录,判断MOB file还有效的记录进行重写。然后重写一个新的MOB-File,并且将新的有效的数据记录到新的StoreFile。 Bulk Load 对应的Region中。

HBase 核心上来说还是LSMTree,这里又回到LSMTree 设计的初衷。提高写性能,并且通过定期compaction的方式来避免文件过多导致的读取放大。

如上MOB核心上还是为了解决compaction带来的写放大等各方面问题详见3.2节说明。所以MOB的设计的使用场景也比较明确,修改和删除较少的场景。

并且,MOB既然生于HDFS,主要解决的问题解决HBASE本省的问题之外,也一并在适应其所处的环境。比如会经常得对Dump出来诸多相对的小MobFile(直接从MemTable中dump出来的Mob File)进行合并成大文件,以减小HDFS小文件的数量。对于大的MobFile,直接通过判断其实际记录删除率的方式,进行回收数据。

类HBASE MOB方案,应对的场景,相对来说还是其所声明的,文件大小100KB~10MB,删除相对比较少。主要解决的问题是LSM-Tree Compaction 导致的IO放大问题。

如果删除较多,那么需要控制文件在多少百分比的时候进行ReWrite。有效数据在70%的时候进行回收,那么需要ReWrite 近 70%的有效数据量。(ps:但是总体比放在原来的StoreFile中的带来的写放大要好一些)。

3.4 总结

如上分析了典型的几种模式,在对象存储架构上采用哪种方案比较友好?其实没有放之四海而皆准的完美的架构,只有适合特定业务的架构。从通用对象存储系统(不预设各种业务假设)设计来说,使用3.1 与3.2融合的方案相对来说是一种较为合适的方案。

总结架构层面可以做如下选择

  1. Meta、MOB存储 :也就是小于10MB以内的中小对象,可以采用类似HBASE MOB的方案,核心思想是小文件合并成大文件,将Meta的Compaction 与Data的Compaction 独立开来,减少元数据的查找放大,同时尽量减小无效的Compaction。并且当前在线数据的存储也基本都在使用EC 纠删码方案,小文件合并为大文件会EC会更加友好(PS:以减小小文件需要的pading开销、并且使得EC分片大小足够大,最大化利用EC优势,屏蔽缺点等)。
  2. LOB存储:对于较大的文件,比如10MB以上的文件(可以使得底层实际文件大小适中),可以使用类似方案1的方式来解决。这样既可以使得删除速度(避免使用Compaction机制引入垃圾回收)相对比较快一些,并且也能够充分利用满条带的EC进行Append写入等等,典型的1.5副本的EC策略相比三副本的写入带宽相比降低50%。

元数据采用分布式TableStore方案(典型如HBASE),总体来说会是不错的方案,如果具备以下2个方面的特性,相对来说就会更好。

  1. Range Based Spilt:不仅可以保证扩展性,甚至一定程度实现很好的Range Based 的业务隔离。
  2. SchemeLess:也就是采用Scheme On Read not OnWrite,可以使得Meta层面是可以非常好的适应业务需求的一些变化,比如AWS S3持续拓展的功能,比如对象的ACL、tagging等等特性。如果是采用传统的Mysql SchemeOnWrite的方案,谁用谁知道,产品经理和开发团队就提前准备开始掐架吧。
  3. WAL友好导出:支持按照Region级别导出WAL内容(the source of truth)到类似消息队列等方案。那么结合流式计算实现很多小时粒度峰值计费、实现事件通知等等,各种业务层面的需求和拓展就都可以玩了,一点不别扭。

数据层面选择:

数据层面的架构选择,典型的分布式文件系统,比如HDFS、比如类似Window Azure Stream Layer的方案、CEPH等等。在选型或者自研开发的时候在以下方面需要比较关注

  • Append:支持高性能高可用Append接口、支持多副本、支持灵活的EC策略(HDFS的实现其实不太灵活,对于实现28+4等更低成本的EC当前比较无力(HDFS使用4bit存放EC分片信息,分片数量最大16片))。
  • 元数据收敛性:NameServer元数据收敛性, 尽量减少元数据的footprint(特定情况下可以借鉴类似CEPH的元数据管理方案)。使得系统能够使用单组元数据服务器能够Cover100PB以上的数据集群。
  • 对外接口: 对外接口层面实现singleWrite /MultiReader ,可以提供类似本地文件系统的sync/close的持久化语义以最大化底层写入吞吐。
  • 灵活性:如果业务比较灵活,某些情况下需要针对特定的大块业务定制,底层引擎层面支持灵活的文件级别的可靠性选择:比如单副本,多副本,EC策略等等,甚至可选sync语义(可选不sync)(在一些业务场景下:机制的可靠性不是业务的一种选择,比当前其实很多机遇HBASE业务存储歌单、购物车数据等等,丢失一些数据换取更高的性能是典型的互联网业务的选择)。

4 更多的拓展

对象存储不仅仅是PUT/GET/DELETE/SCAN,所以存储系统架构层面最好也能够使得在如下一些方面能够有更加简单的选择,不脱业务后退。

  1. 统计计费

统计、计费等,比如小时粒度的峰值存储计费。这些如果要准确得统计需要有 the source of truth。需要有类似数据库binlog的方案,进行持续得增量的统计(不然就得依靠数据库的整体的MVCC的机制才能做到,很麻烦的)。

  1. 分块上传

分块上传比较特殊,涉及到多个元数据上传记录的最终合并成一个对象记录的情况。

  • Initupload: 生成一个唯一的ID标识本次分块上传的ID
  • uploadpart:可并行得上传每一个分片
  • complete: 将所有上传的分片按序合并,生成外部可访问完整对象。

更多详见,比如对象存储分块上传API。

一般来说由于RowKey的不同,如果需要做到原子性,则需要TableServer提供跨行的分布式事务支持,但是一般的TableServer(比如HBASE)一般来说都不提供跨Region跨行级别的事务(往往在TableServer 之上套一层类似Percolator模型才能够支持 omid,方案有点重,性能也不太行),当然可选的方式可以通过双向索引方式、 依赖后台垃圾处理的方式,保障业务的正确性和收敛性。

如下是一个简单非完全的示例(以下以类似关系型数据库schema的方式进行说明)

// 对象表,用于记录每一个对象
Object {
  `BucketID`    bigint(20) unsigned NOT NULL       // 对应用户的BucketName 对应的BucketID
  `ObjectName`  varchar(1000) unsigned NOT NULL    // 对应用户的对象名
  `Meta`        json Not NULL                      // 保存用户的meta和一些sys的meta
  `Data`     blob NOT NULL                       // 保存用户的数据
   PRIMARY KEY (`BucketID`, `ObjectName`)  
} 

PS: Meta 中保存所有用户层面的元数据信息(user-meta)和系统层面的元信息(upload part info)
user-meta: 可以保存比如:content-type:jpeg; content-length:1024 ; x-user-key:value 等
sys-meta: 比如: uploadPartInfo: {UploadId:12312,Parts[1,2,3,5] } x-acl等等
Data:可以是MOB方式,也可以是指向外部分布式文件系统的文件。

// 分块上传信息表,用于记录每次分块上传的信息。
InitUploadParts {
  `BucketID` bigint(20) unsigned NOT NULL  // 分块上传对应的Bucket
  `UploadID` bigint(20) unsigned NOT NULL  // 标识本次上传
  `Metas` json  NOT NULL                  // 记录桶ID、对象名等
  PRIMARY KEY (`BucketID`, `UploadID`)
}
InitUploadParts 的Metas中可以包含 BucketID、ObjectName、CreateTime等等。

// 记录每一次上传的分片
UploadPart {
    `UploadID`   bigint(20)  unsigned NOT NULL   // 标识这次上传的唯一ID
    `PartNumber` smallint(5) unsigned NOT NULL   // 那个Part
    `Meta`      json Not NULL                  // 记录对应的BucketID 和ObjectName
    `Data`      blob Not NULL                  // 保存用户的数据
    PRIMARY KEY(`UploadID`、`PartNumber`)        
}
UploadPart的Meta 可以包含:BucketId、ObjectName、ContentLength、Etag等等。

**CompleteMultiUpload中断 如何保障对外原子语义:要么成功,要么失败**在complete的时候,一般
来说得向Object表插入记录。然后再删除UploadPart表。如果不能放在事务里头的话,可以先生成Object
记录,InitUploadParts表中对应的记录可以延迟删除。(查询UploadParts信息的时候,可以通过
BucketID和对象名反查对应的分块上传是否合并到Object表中。如果已经合并,可以将这个UploadParts
记录异步删除)。

**Abort UploadPart(如何保障对外原子的成功、失败)**
可以先删除InitUploadParts。如果这个时候失败,后台可以周期性分析UploadPart表,分析对应的
UploadID对应的InitUploadParts表中是否存在, 存在就ok,不存在可以异步删除”,当然删除的前提还得
查找是否被对应的Object引用。

总结:说明了可以通过唯一ID + 双向索引的方式实现异步的垃圾清理的方式实现摆脱对分布式事务的依赖。
另外如果分块上传UploadParts中Data 字段太大。或者 PutObject 对应的Data内容太大。
也可以采用这个方法,使用唯一ID + 双向索引的方式实现 阶段性的分块上传。实现大对象的上传。
  1. 数据收敛性

对于大文件有可能会使用直接写底层大文件存储引擎,最后写文件索引(此种场景,一般都依赖元数据的原子性实现对业务原子写入)。如果数据和元数据不在同一个引擎,同时还得解决数据写了之后元数据没有成功之后的数据收敛问题(垃圾回收)。这个如果在数据存储这一侧建立反向索引支持(attr:编码在Header或者分布式文件系统支持扩展属性)是可以在一定程度快捷的解决此类问题。

  1. 跨机房数据同步

跨机房数据同步的很重要一点是需要做到最终一致性,那么对于对象的增删改查之类的需要做到记录所有的操作并且是有序的,并且在操作上做结合幂等、exactly once 等机制做到尽量简单的同步。这个可以借助WAL(the source of truth)中过滤出来对对象的增删改实现。

5 基本功思考

如上对外的数据模型、架构设计等等,在大方向上讨论了对象存储可以采用的架构和解决核心问题的思路。但是在实际落地过程中是否能向业务交付满意的产品,其实还有非常多的需要注意的点,这些是真正考验一款存储产品从可用到优秀的示金石,以下做简单的总结:

  1. 可用性:架构设计是否能够支撑快速FailOVer,支持多副本在Rack、Zone 层面的隔离,减少硬件故障的影响。同时在软件设计层面如何所谓减少Blast Radio(爆炸半径),以提高更高的可用性,减少局部故障对上层业务的影响范围。
  2. 可靠性:是否多副本/EC,是否支持更高压缩比的EC策略。数据分片放置等方面是否足够优秀(同时可以考虑到恢复速度、打散度、可用域:能够使得数据进行分布式快速恢复而对上层业务的影响要足够的小,实现N个9的可靠性。可参考分布式存储系统可靠性<一>:如何量化。
  3. 性能:包括Latency/Throughoutput。是否引入更好的Backup Request来优化 tail latency。是否在读写方面能够充分发挥HDD/SSD等磁盘的优势。另外是否提供在性能和可靠性方面的Tradeoff又不影响系统的可用性:比如类似是否允许HBASE的WAL不进行fsyn而保持故障后系统依然是可以用的,只是影响小部分的写入(小部分的写入丢失)。
  4. 成本:这个是多方面的(除了上层的EC降低副本率)。对CPU/内存/Disk等方面资源的需求是否足够低。对功耗的要求比较低,是否接受更高密度的存储?在大规模下10%的成本的差距可能是非常大的差距,白热化竞争情况下15%可能是毛利率点。
  5. 隔离性:隔离型也是多方面的,比如是否支持业务层面逻辑层面的隔离、物理层面的隔离。逻辑层面的隔离比在同一份资源上进行资源Quota的限制(比如QPS、带宽、底层存储文件的隔离等)、物理的隔离,服务层的隔离、存储底层资源的隔离等做到彻底的隔离等等。在多租户的系统中,一方面隔离可以减少各个业务之间在异常情况下的相对影响。另外为了更好的support特定具备一定规模的业务场景,比如视频直播的存储之类的定期删除就非常合适作为在如上的Table 层面隔离做TTL删除策略,以备快速进行空间的回收。
  6. 可运维性:故障在绝大多数情况下都是人为引入,运维周边系统是否足够完善,是否容易运维。
  7. 高级特性:更多的冷热分层等等。

Notes

作者:网易存储团队工程师 TOM。限于作者水平,难免有理解和描述上有疏漏或者错误的地方,欢迎共同交流;部分参考已经在正文和参考文献中列表注明,但仍有可能有疏漏的地方,有任何侵权或者不明确的地方,欢迎指出,必定及时更正或者删除;文章供于学习交流,转载注明出处

5 参考资料

HBASE MOB

  1. MOB Design
  2. HBASE 写入流程
  3. HBASE 原理与实践
  4. 如何优雅得通过key/value分离降低写放大难题

隔离

  1. HBASE quota
  2. Region Group
  3. How AWS Minimizes the Blast Radio of Failure
  4. Tail At the Scale

相关推荐

「layui」表单验证:验证注册

注册界面手动验证获取短信验证码代码原文<!DOCTYPEhtml><htmllang="zh"><head>&...

Full text: Joint statement between China and Kenya on creating an inspiring example in the all-weather China-Africa community with a shared future for the new era

JointStatementBetweenthePeople'sRepublicofChinaandtheRepublicofKenyaonCreatinganInspi...

国际组织最新岗位信息送给你

国际刑警组织PostingTitleITLogisticsManagerGrade5DutyStationAbidjan,IvoryCoastDeadlineforApplicatio...

【新功能】Spire.PDF 8.12.5 支持设置表单域的可见与隐藏属性

Spire.PDF8.12.5已发布。该版本新增支持设置表单域的可见与隐藏属性、添加自定义的元数据以及给PDF文档的元数据添加新的命名空间。本次更新还增强了PDF到DOCX和图片的转换...

AI curbs show Biden&#39;s rejection of cooperation

AIcurbsshowBiden'srejectionofcooperation:ChinaDailyeditorial-Opinion-Chinadaily.com.cnT...

“煤气灯效应”上热搜,这几种有毒的“情感关系”也要注意了……

近日,“煤气灯效应”(theGaslightEffect)再次进入公众视野并登上热搜,引发网友广泛关注。那么,什么是“煤气灯效应”?以“爱”之名进行情绪控制在心理学中,通过“扭曲受害者眼中的真实”...

Qt编写推流程序/支持webrtc265/从此不用再转码/打开新世界的大门

一、前言在推流领域,尤其是监控行业,现在主流设备基本上都是265格式的视频流,想要在网页上直接显示监控流,之前的方案是,要么转成hls,要么魔改支持265格式的flv,要么265转成264,如果要追求...

写给运维的Nginx秘籍

要说Web服务器、代理服务器和调度服务器层面,目前使用最大的要数Nginx。对于一个运维工程师日常不可避免要和Nginx打交道。为了更好地使用和管理Nginx,本文就给大家介绍几个虫虫日常常用的秘籍。...

突破亚马逊壁垒,Web Unlocker API 助您轻松获取数据

在数据驱动决策的时代,电商平台的海量数据是十足金贵的。然而,像亚马逊这样的巨头为保护自身数据资产,构建了近乎完美的反爬虫防线,比如IP封锁、CAPTCHA验证、浏览器指纹识别,常规爬虫工具在这些防线面...

每日一库之 logrus 日志使用教程

golang日志库golang标准库的日志框架非常简单,仅仅提供了print,panic和fatal三个函数对于更精细的日志级别、日志文件分割以及日志分发等方面并没有提供支持.所以催生了很多第三方...

对比测评:为什么AI编程工具需要 Rules 能力?

通义灵码ProjectRules在开始体验通义灵码ProjectRules之前,我们先来简单了解一下什么是通义灵码ProjectRules?大家都知道,在使用AI代码助手的时候,有时...

python 面向对象编程

Python的面向对象编程(OOP)将数据和操作封装在对象中,以下是深度解析和现代最佳实践:一、核心概念重构1.类与实例的底层机制classRobot:__slots__=['...

Windows系统下常用的Dos命令介绍(一)

DOS是英文DiskOperatingSystem的缩写,意思是“磁盘操作系统”。DOS主要是一种面向磁盘的系统软件,说得简单些,DOS就是人给机器下达命令的集合,是存储在操作系统中的命令集。主要...

使用 Flask-Admin 快速开发博客后台管理系统:关键要点解析

一、为什么选择Flask-Admin?Flask-Admin是Flask生态中高效的后台管理框架,核心优势在于:-零代码生成CRUD界面:基于数据库模型自动生成增删改查功能-高度可定制...

Redis淘汰策略导致数据丢失?

想象一下,你的Redis服务器是一个合租宿舍,内存就是床位。当新数据(新室友)要住进来,但床位已满时,你作为宿管(淘汰策略)必须决定:让谁卷铺盖走人?Redis提供了8种"劝退"方案,...