本篇主要是根据AnalyticDB的论文,来讨论AnalyticDB出现的背景,各个模块的设计,一些特性的解析。可能还会在一些点上还会穿插一些与当前业界开源实现的比对,希望能够有一个更加深入的探讨。OK,那我们开始吧。

AnalyticDB介绍与背景

要说AnalyticDB,那起码得知道它是干什么的。这里直接贴下百度百科的介绍:

AnalyticDB是阿里云自主研发的一款实时分析数据库,可以毫秒级针对千亿级数据进行即时的多维分析透视。

简单地说,就是实时OLAP型数据库,它的对标产品是Apache Kylin,Apache Druid,Clickhouse这些。然后AnalyticDB的特点,包括高并发实时摄入数据,兼容Mysql协议,无需预计算即可有的极快响应时间,多种数据源接入,大规模集群管理等。好吧,这几个特点都很官方,不急,接下来会逐渐讨论各个点。

然后介绍下AnalyticDB的背景。

首先先说说传统的OLAP型数据仓库,以往构建OLAP型数据仓库通常都是采用离线模式,即在晚上设置定时任务将前一天的数据同步到数据仓库中,第二天数据分析师或报表工具就可以根据数据产出分析结果。但这样的问题是数据延迟太高了,商业瞬息万变,可能今天线上出现了什么订单激增的情况,数据分析师却要等明天才能进行分析,这谁受得了呀。所以近几年的趋势就是实时数仓,简单说就是增加一个实时接收数据以供查询的模块,这也叫做lambda架构。如图,就是用一个Batch层和一个Real-time层共同提供查询结果。[1]

好像有点扯远了,说回AnalyticDB,它就是在大背景下提出的,所以它的一个主要特性就是实时。然后由于它本身是云原生的结构,也就是本身就是根植于阿里云上面的,面向的客户更加广泛,所以是有通用性的要求的。比如传统企业都是使用Mysql,Postgresql等关系型数据库,这些企业也没有人力去搭建和维护Hadoop和Kylin,Druid这些集群。而Postgresql这类关系型数据库可能会有对复杂结构对支持,比如json,vector等,所以AnalyticDB也提供了对这种复杂类型的支持。

在性能方面,AnalyticDB维持所有列的索引,用以快速检索数据。在存储方面,使用行-列混合存储,使得AnalyticDB可以同时对OLAP分析和行级查询快速响应。然后为了高并发的查询和高吞吐的写入,又提出了读,写分离。这几个性能方面的特性,以及这些优化如何与实时查询结合起来,在后面会详细介绍。

总而言之,目前业界对海量数据的OLAP分析查询方案无非两种,通过预计算构建多维立方体,在查询的时候直接读取预计算好的数据做一些简单的合并(因为分区存储)然后返回给用户。这种类型的代表是Kylin和Druid,它们的好处是比较简单,OLAP分析查询速度很快,缺点是不够灵活,比如Kylin一点改动可能就要全部数据rebuild。

另一种是非预计算,充分利用各种资源(CPU,内存,列存储,向量化执行),或是架构尽量优化(如AnalyticDB),来让海量数据快速查询得到结果。比较典型的代表是Clickhouse,查询性能不赖,也相对灵活,但缺点是集群数据量没法拓展到很大。

这两种方案都有办法这实时这个点上进行拓展,只是实现思路也不大一样。第一种是添加一个流式层,OLAP查询的时候分别查询历史数据和流式层数据然后合并返回。第二种则是用微批方式倒入数据仓库中实现流式查询。

整体上,AnalyticDB更加偏向于第二种非预计算的方式实现,不过在很多设计上还考虑了行级查询的实现和性能,所以要比Clickhouse这种要复杂一些。下面我们从几个方面来讨论它的实现。

AnalyticDB详细解析

AnalyticDB是一个能够在PB数据集上高并发,低延迟,实时分析查询,并且能够在2000+云服务器上运行的OLAP数据库。在设计上有多个挑战,需要兼顾多种查询类型的性能要求。这里的多种情境包括全表扫描分析,多表join的点查询操作,多个列的多个筛选条件等等,而这些操作又难以优化。

第二个挑战是要设计一个底层存储,应对不同类型的查询所需要的不同存储结构。比如OLAP查询需要列式存储,而点查询(行级查询)需要行式存储。如何将这两种存储结构(列式,行式)结合起来以供不同查询类型使用,同时还需要考虑到复杂类型,json,vector,text等,这也是一个难点。

第三个是实时方面的,要如何做到每秒数百万数据写入吞吐的同时,呈现给用户低延迟的查询响应和数据延迟。以前的做法将读写操作交由同一进程处理,但这样一来读写操作的性能是互斥的,即高吞吐的写入会影响到查询性能和数据延迟[2]

为了解决上述挑战,AnalyticDB引入以下特性:

  • 高效的索引引擎
  • 混合(列-行)存储引擎
  • 读写分离
  • 高效检索引擎

这些特性暂时就有个映像就好,后面会详细对这部分阐述。

架构设计

要说这块,我们先来看看整体的架构设计图。

前面与说到,AnalyticDB是云原生的,AnalyticDB主要依赖于两个外部结构,任务管理与调度组件Fuxi,和分布式存储系统Pangu,而这几个组件又都是基于阿里云的Apsara(负责管理底层的物理主机并向上层提供服务)。

整体架构上看还是比较简单的,主要就是对外提供JDBC/ODBC接口,内部由多个协调器(Coordinator)负责统一管理写节点和读节点(读写分离)。

  • 协调器(Conrdinator):协调器负责接收JDBC/ODBC的读写请求,分发到不同的读或写节点。
  • 写节点(Write Node):负责处理写请求并将数据写入到Pangu中持久化。
  • 写节点(Read Node):负责处理查询请求并返回。

在具体的处理流程中,Fuxi资源分配和异步调度(类似yarn)。而数据计算则是使用管道的方式进行计算,如下图:

图中,数据按页(Page)进行切分(Pangu的存储特性),数据处理以管道的方式进行处理,且数据流转的不同阶段均在内存中执行。看这张图其实有点像Spark的数据处理流程,当然AnalyticDB本身也是使用DAG模型进行数据处理。

不过按照论文中说的数据完全在内存中处理还是有点不现实,虽然这样能极大提高处理的效率,但遇到数据量太大导致内存装不下的情况,还是需要暂时落到磁盘上,就类似Spark有提供多种persist方案一样。否则查询的并发量势必会受到一些影响,但这样一来可能查询响应又降低了,鱼与熊掌不可兼得啊。

数据分区

在一开始创建表的时候,可以分配数据按照两级分区进行存储,这里通过论文中的小例子阐述两级分区的实现,如以下建表语句:

CREATE TABLE db_name.table_name (
id int,
city varchar,
dob date,
primary key (id)
)
PARTITION BY HASH KEY(id)
PARTITION NUM 50
SUBPARTITION BY LIST (dob)
SUBPARTITION OPTIONS (available_partition_num = 12);

第一级索引,可以让数据按照指定列进行hash分区以及指定分区数,比如上述建表语句指定一级索引为id,分区数是50个。这样可以让数据根据id的hash值分布到不同的50个分区中,这一列通常是使用高基数的列,诸如用户Id等。

第二级索引(SUBPARTITION,可选)可以针对某个列指定最大分区数,用来对数据保留和回收,通常使用日期类型数据。比如如果指定按天进行分区,最大分区为12,那么数据仅会保留12天内的数据。

读写分离和读写流程

大多数传统的OLAP数据库,都是使用一个线程负责处理用户SQL的操作,不管是写请求(Insert)还是读请求(Select)。这在查询和写入的并发量都很高的情况下会出现资源争用的情况,针对这种情况AnalyticDB提出读写分离的解决方案。写节点负责写,读节点负责读数据,两种节点彼此分离,这样就避免了高并发场景下读写资源互斥的情况。

写节点主要是master和worker架构,由zookeeper进行协调管理。写master节点负责分配一张表的分区给不同的写worker节点。在一个SQL到达的时候,Coordinators会首先识别是读还是写SQL语句,若是写,那么会先发送到对应的写worker节点,写worker节点先将数据存到内存,定期以日志的形式持久化到Pangu中形成Pangu日志。当日志一定规模的时候,才会构建真正的数据和全量索引。

而对于读节点,同样每个节点会被实现分配不同的分区。功能上,AnalyticDB有两种读模式,实时读取(real-time read),写入数据立即可读,和延迟读(boundedstaleness),即容忍一定时间的写入数据延迟。延迟读是默认采用的方式,虽然与一定数据延迟,但查询响应更快,通常而言也足够了。

而实时读,那么可以立即查询到刚刚写入的数据。之所以能这么快,其中一个原因是读节点会直接从写节点中获取更新数据,也就是说写节点在某种程度上说充当了缓存。其他的OLAP数据库的做法通常有两种,一种是用一个segment专门存储实时数据,OLAP查询的时候,会扫描实时segment和离线数据,合并后返回用户,比如最新的kylin streaming就是这样实现。一种是微批导入数据到存储引擎中,然后用以检索,这样的话写入频率(微批的间隔)会大大影响检索性能。AnalyticDB的方式可以说是一种比较新颖的方式,借助读写分离的架构和强大的索引能力(下面介绍),可以实现实时写入且低延迟检索。

不过其实这会面临一个问题,数据同步的一致性问题(读写节点数据不一致),AnalyticDB是怎么做的呢?这里也不卖关子,主要是使用一个版本号来处理。Coordinators在分发写请求给写节点,写节点更新后会返回更新后的分区版本号给Coordinators。Coordinators分发读请求给读节点时,也会带上这一个分区版本号,读节点就会与自己缓存的版本号对比,发现自己小的话,就会去拉取写节点的最新数据(写节点有一定的缓存功能)。

可以发现,通过读写分离的机制,以及预先分配好读/写节点的数据分区(hash),能提高数据处理的并行度,并且减少数据计算产生的数据传输网络开销,比如join的shuffle操作就不需要进行大规模的数据再分区。而后有能够将两种请求相互解耦,每种操作关心自身就可以,方便以后的拓展。

OK,到这里系统的架构,数据分区,读写流程就差不多说完了,接下来再讨论下它的其他特性。

其他特性介绍

混合(列-行)存储引擎

先说下背景,OLAP查询一般会有全表扫描操作,所以主流做法是使用列式存储,因为列式存储可以极大减少磁盘IO操作,提高提高全表扫描性能,但这种对点查询(即行级别)查询和更新等不甚友好。而如Mysql这种行级存储,点查询方便,但OLAP操作又会又额外更多开销(数据压缩比低)。许多主流系统的做法是,基本摒弃另一种功能,比如Mysql不适合做大规模OLAP查询,Kylin,或者说hive这种不支持行级别更新(特殊情况下可以,但支持不好),Druid则更加极致,直接就不存明细了。

而AnalyticDB却通过行-列混合存储结构,不仅兼顾OLAP分析和点查询,还实现了复杂类型的存储(json,vector)。不过在介绍它的行-列混合存储结前,先来看看流行的列式存储结构,然后再引出AnalyticDB的行列混合。

我们以开源的列式存储结构Parquet为例来看列式存储是怎么存储数据的。

Parquet本身是hadoop底层使用的存储引擎,其强大毋庸置疑。所谓列式存储,可以简单理解成就是将一整列数据压缩打包,然后按顺序存储。

存储中有三级结构:

  • 行组(Row Group):按照行将数据物理上划分为多个单元,每一个行组包含一定的行数。一个行组包含这个行组对应的区间内的所有列的列块。
  • 列块(Column Chunk):在一个行组中每一列保存在一个列块中,行组中的所有列连续的存储在这个行组文件中。不同的列块可能使用不同的算法进行压缩。一个列块由多个页组成。
  • 页(Page):每一个列块划分为多个页,页是压缩和编码的单元,对数据模型来说页是透明的。在同一个列块的不同页可能使用不同的编码方式[3]

在最后是Footer模块,这里存储的是数据的元数据信息,比如列名,列的类型。还有一些统计信息,min,max,用以提升部分检索的效率。同时Parquet也支持复杂类型的存储,说简单点就是将复杂类型Map,List等转换成schema树,把树的叶子节点当做列数据存储。

简单了解了列式存储,我们再来看AnalyticDB的行-列混合存储。

注意图中左右两部分分别是两个文件,左边的是元数据文件,存储诸如字段名,一些简单的统计信息帮助过滤,这个文件比较小通常驻存在内存中。这部分内容和前面的Parquet的Footer存储内容类似,这里就不多介绍了。主要还是介绍下右边部分,即数据存储方式。

图片右边,数据以row group的形式存储,每个row group中存储固定数量的行。但是在row group中依旧采用列式存储,即同一列的被存储到一起,称为Data bolck,所有的Data block按顺序存储(这点和列式存储一样)。而Data block是最小的操作单元(缓存,读取等)。注意这里不像Parquet那样,每一个Data block再分多个Page。

看上去,它的存储结构和Parquet是类似的,只是没有再将Data block划分成多个Page,这里论文没和Parquet对比,也没论述很清楚。不过最主要的区别应该就是这里了。为什么Data block不需要再划分?因为它没那么多数据呀,在Parquet里,一个row group的数据量是GB级别的,所以一个row group中的列需要再划分。而AnalyticDB中,它的row group明显是小数量级的,可能一个row group仅仅是MB级别的数据量。这一点细微的差别,使AnalyticDB在点查询的时候就可以直接几MB内获取一行全部数据,而Parquet可能需要在1G内才能获取一行数据。这也是为什么AnalyticDB的叫做行-列混合存储结构。

对比Parquet和AnalyticDB,它们的设计分歧可能是天生的,Hadoop适合存储追加的数据,以及非结构化数据,它的场景更多是在大数据存储和加载,所以不会考虑单行查询的场景。而AnalyticDB要考虑各种检索,所以设计上就会要差异。当然AnalyticDB这样也不是没有缺点,比如它在全表扫描性能会有所下降。

说完AnalyticDB的存储结构,再来说说AnalyticDB如何存储复杂类型数据

复杂类型数据(json,vector)存储有个难点,这种复杂类型的数据通常大小是不定的,而且往往会出乎意料的大。如果按照上面提到row group的方式,可能一个block entry会非常大,所以需要一种其他类型的存储结构来存储复杂类型数据。

具体的做法可以说借鉴了hadoop的存储思路。既然复杂类型数据大小不一样,可能大可能小,那就将数据统一用32KB大小的块组织起来,称为FBlock。一个复杂类型数据可能分散在多个FBlock中(超过32KB),多个FBlock按顺序存储。然后使用稀疏索引,方便快速查询。这样的设计无疑可以方便得将复杂数据进行存储,同时通过稀疏索引又能在一定程度上保证检索的速度。

最后再说说如何支持update和delete操作

一般的列式存储是不怎么支持行级更新和删除操作的,因为数据都是压缩成二进制进行存储,如果支持行级更新,那并你需要先解压缩,整块数据,然后删除数据,再压缩存储。要是并发量一上来那简直是灾难。

那hbase的底层是hadoop,它是怎样实现更新和删除的呢?这是因为hbase使用LSM-tree,我之前也过一篇介绍这个东西,也兴趣可以看看数据的存储结构浅析LSM-Tree和B-tree。粗略说就是将更新和删除操作都按key-value的形式追加到文件末尾,然后整个文件定期去重,只保留最新的key的数据,旧的key数据就被删除了。检索的时候如果也多个key,只会认最新的那个key的数据。当然具体细节要复杂得多。

AnalyticDB也是类似的思想,不过做了一些改变。它使用一个存储在内存中的bit-set结构,记录更新和删除的数据id以及对应版本号。同时使用copy-on-write(写时复制)技术提供多版本支持。更新和删除操作都会改变版本号,然后查询的时候会提供一个版本号去查找对应的更新和删除信息,然后在查询结果中和结果进行合并。这样就实现了更新和删除操作。

稍稍总结下AnalyticDB的存储结构,行-列混合存储的优势确实是也的,它算是牺牲一部分OLAP查询的性能,换取一些灵活性。而这样的换取,使得它拥有快速行级检索,更新删除的能力,对AnalyticDB而言是值得的。

索引

索引可以说是一个数据库系统中极为重要的优化设计。目前主流的索引,包括B+tree,倒排索引,稀疏索引等等,但它们都有各种局限,比如B+tree插入分裂代价太大,倒排索引只支持特定类型,有些索引虽然能提供快速检索的能力但对写入性能有负担。那么AnalyticDB的索引是怎样实现的呢?

AnalyticDB重度使用倒排索引加速检索效率,首先,AnalyticDB对每一列都建立一个倒排索引,索引的key是列的值,索引的值的列的行号。前面的存储结构中可以看到,每个row group存储的是固定的行数,所以可以快速检索到对应的行。而针对不同的数据量特点,提供了bitmap和int array两种结构存储倒排索引,达到一定阈值的时候会做相应转化。

而针对复杂类型数据(三种,json,full text,vector),还是通过倒排提供支持,只是针对不同类型做了不同的优化改动

先说json,以往查询json数据的做法,是要先读取json然后解析再然后查询,这样效率很低。AnalyticDB采用空间换时间的思路,将json数据先解析,然后对每个列构建倒排索引(和单列倒排索引类似)。在查询的时候就可以直接根据索引快速定位到对应的json。

full-text的索引方式应该是和ElasticSearch类似的,即词到整个文档的倒排索引,查询时还会按TF/IDF评分将结果返回给用户(ES也是这样)。

第三种类型是vector类型数据,主要采用NNS(nearest neighbour search)方法来加速查询(看名字和KNN算法有点像),大意也是用类似计算临近数据的方式加速检索。

对于增量数据,由于数据是落盘到磁盘上才构建全量索引,索引增量数据和已经落盘的数据有检索性能的区别,所以需要对增量数据额外构建索引来弥补这种差距。而AnalyticDB对增量数据构建的是排序索引。所谓排序索引,本质上是一个数组,存储的是数据的id。具体原理比较难解释清楚,可以理解为就是存储排序后的数据的id。通过排序索引可以将全表检索的复杂度从O(n)降低到O(log n),也是一种空间换时间的思路了。

说完AnalyticDB,我们来对比下其他索引结构。Apache Kylin就不必多说了,基本就是依托于Hbase的row-key索引机制,算是比较弱的索引机制。

和AnalyticDB比较像是应该算是Druid,也是倒排索引,不过是bitmap结构存储的倒排索引,它的倒排索引是经过优化的,叫Roaring bitmap,可以规避存储小数据时候的存储空间问题。相比于AnalyticDB的大而全的索引,Druid可以说是小而美。只对维度数据存储bitmap索引,并且是和数据一起存储在文件中,而非AnalyticDB那样数据和索引分开存。出现这样的原因一个是场景上,Druid毕竟是面向OLAP查询的,索引它只需要对维度索引构建就行。这样的好处在于实现简单,存储也不会占用太多空间。而针对单一OLAP场景,其实这样也已经足够了。

小结

总而言之言而总之,AnalyticDB因为其是云原生,底层存储,资源调度等都是依托于阿里云的其他服务,所以它开源出来是不现实的(毕竟人家还靠这个赚钱),哪怕真的开源,使用者使用开源存储和资源调度方案估计也难以做到它在阿里云生态上那么好。

不过它的架构和一些特性还是很有借鉴意义的,比如读写分离,预先分区,还有行-列混合存储,强大的索引机制,索引机制如何跟底层的存储相互配合等,这些东西目前开源的一些系统可能还没有或没AnalyticDB那么完善。一方面可能是因为这些东西实现起来后,配置上会比较复杂,阿里云的东西不怕复杂,因为后端对用户是不可见的。要是开源系统搞得特别复杂,工程师们就不大会想用这些东西。毕竟为了一些可能用不着的性能提升,引入一个后续可能维护复杂的系统,是否值得也是需要权衡的。

总体上看,AnalyticDB还是走在了业界的前头的,好像也通过了TPC-DS的全流程测试,算是未来可期。未来开源数据库的方向会不会从分化走上AnalyticDB这种全面的道路呢?

以上~


  1. Applying the Kappa architecture in the telco industry

  2. AnalyticDB paper

  3. 深入分析 Parquet 列式存储格式

AnalyticDB实现和特点浅析的更多相关文章

  1. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  2. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  4. netty5 HTTP协议栈浅析与实践

      一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...

  5. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  6. 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler

    熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...

  7. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  8. 浅析匿名函数、lambda表达式、闭包(closure)区别与作用

    浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...

  9. word-break|overflow-wrap|word-wrap——CSS英文断句浅析

    ---恢复内容开始--- word-break|overflow-wrap|word-wrap--CSS英文断句浅析 一 问题引入 今天在再次学习 overflow 属性的时候,查看效果时,看到如下结 ...

随机推荐

  1. Java实现 LeetCode 802 找到最终的安全状态 (DFS)

    802. 找到最终的安全状态 在有向图中, 我们从某个节点和每个转向处开始, 沿着图的有向边走. 如果我们到达的节点是终点 (即它没有连出的有向边), 我们停止. 现在, 如果我们最后能走到终点,那么 ...

  2. Java实现 LeetCode 479 最大回文数乘积

    479. 最大回文数乘积 你需要找到由两个 n 位数的乘积组成的最大回文数. 由于结果会很大,你只需返回最大回文数 mod 1337得到的结果. 示例: 输入: 2 输出: 987 解释: 99 x ...

  3. Java实现 蓝桥杯VIP 算法提高 大数加法

    算法提高 大数加法 时间限制:1.0s 内存限制:256.0MB 问题描述 输入两个正整数a,b,输出a+b的值. 输入格式 两行,第一行a,第二行b.a和b的长度均小于1000位. 输出格式 一行, ...

  4. Java实现 LeetCode 95 不同的二叉搜索树 II(二)

    95. 不同的二叉搜索树 II 给定一个整数 n,生成所有由 1 - n 为节点所组成的二叉搜索树. 示例: 输入: 3 输出: [ [1,null,3,2], [3,2,null,1], [3,1, ...

  5. java实现第七届蓝桥杯机器人塔

    机器人塔 X星球的机器人表演拉拉队有两种服装,A和B. 他们这次表演的是搭机器人塔. 类似: A B B A B A A A B B B B B A B A B A B B A 队内的组塔规则是: A ...

  6. vue的第一个commit分析

    为什么写这篇vue的分析文章? 对于天资愚钝的前端(我)来说,阅读源码是件不容易的事情,毕竟有时候看源码分析的文章都看不懂.每次看到大佬们用了1-2年的vue就能掌握原理,甚至精通源码,再看看自己用了 ...

  7. 利用tcpdump命令统计http的GET和POST请求

    1.搭建的知识库服务器, 需要统计来访者都是哪些人,因为系统不是自己开发的,看不到访问日志.所以考虑从系统层面抓取访问流量来实现. 2.通过tcpdump抓取的数据包,在wireshark中打开发现, ...

  8. (十)HTTP.sys远程代码执行

    01 漏洞描述 上篇文章介绍了Host头攻击,今天我们讲一讲HTTP.sys远程代码执行漏洞. HTTP.sys是Microsoft Windows处理HTTP请求的内核驱动程序,为了优化IIS服务器 ...

  9. 美女面试官问我Python如何优雅的创建临时文件,我的回答....

    [摘要] 本故事纯属虚构,如有巧合,他们故事里的美女面试官也肯定没有我的美,请自行脑补... 小P像多数Python自学者一样,苦心钻研小半年,一朝出师投简历. 这不,一家招聘初级Python开发工程 ...

  10. STL中的set和multiset

    注意: 1.count() 常用来判断set中某元素是否存在,因为一个键值在set只可能出现0或1次. 2.erase()用法 erase(iterator)  ,删除定位器iterator指向的值 ...