介绍

本文是分析Elasticsearch系列文章中的一篇,是一个译文。共有三个部分,每部分讲解部分Elasticsearch的实现原理。
    在翻译的过程中,也需要查看对应部分的源码,来加深对实现原理的理解。但这里并没有对源码进行分析,源码的分析放到后面的系列文章进行介绍。
本文介绍了Elasticsearch的以下原理:

  • 是Master/Slave架构,还是Master-less架构?

  • 存储模型是什么?

  • 写入操作是如何工作的?

  • 读操作是如何工作的?

  • 搜索结果如何相关?

Elasticsearch介绍

  • Elasticsearch的index

Elasticsearch的索引(index)是用于组织数据的逻辑命名空间(如数据库)。Elasticsearch的索引有一个或多个分片(shard)(默认为5)。分片是实际存储数据的Lucene索引,它本身就是一个搜索引擎。每个分片可以有零个或多个副本(replicas)(默认为1)。Elasticsearch索引还具有“类型”(如数据库中的表),允许您在索引中对数据进行逻辑分区。Elasticsearch索引中给定“类型”中的所有文档(documents)具有相同的属性(如表的模式)。

图a显示了一个由三个主分片组成的Elasticsearch集群,每个主分片分别有一个副本。所有这些分片一起形成一个Elasticsearch索引,每个分片是Lucene索引本身。

图b演示了Elasticsearch索引,分片,Lucene索引和文档(document)之间的逻辑关系。

  • 类比关系数据库术语

  • Elasticsearch Index ~ Database

  • Types ~ Tables

  • Mapping ~ Schema

Elasticsearch集群的节点类型

Elasticsearch的一个实例是一个节点,一组节点形成一个集群。Elasticsearch集群中的节点可以通过三种不同的方式进行配置:

  • Master节点

  • Master节点控制Elasticsearch集群,并负责在集群范围内创建/删除索引,跟踪哪些节点是集群的一部分,并将分片分配给这些节点。主节点一次处理一个集群状态,并将状态广播到所有其他节点,这些节点需要响应并确认主节点的信息。

  • 在elasticsearch.yml中,将nodes.master属性设置为true(默认),可以将节点配置为有资格成为主节点的节点。

  • 对于大型生产集群,建议拥有一个专用主节点来控制集群,并且不服务任何用户请求。

  • Data节点

  • 数据节点用来保存数据和倒排索引。默认情况下,每个节点都配置为一个data节点,并且在elasticsearch.yml中将属性node.data设置为true。如果您想要一个专用的master节点,那么将node.data属性更改为false。

  • Client节点

如果将node.master和node.data设置为false,则将节点配置为客户端节点,并充当负载平衡器,将传入的请求路由到集群中的不同节点。
    若你连接的是作为客户端的节点,该节点称为协调节点(coordinating node)。协调节点将客户机请求路由到集群中对应分片所在的节点。对于读取请求,协调节点每次选择不同的分片来提供请求以平衡负载。
    在我们开始审查发送到协调节点的CRUD请求如何通过集群传播并由引擎执行之前,让我们看看Elasticsearch如何在内部存储数据,以低延迟为全文搜索提供结果。

存储模型

Elasticsearch使用Apache Lucene,它是由Java编写的全文搜索库,由Doug Cutting(Apache Hadoop的创建者)内部开发,它使用称为倒排索引的数据结构,用于提供低延迟搜索结果。
    文档(document)是Elasticsearch中的数据单位,并通过对文档中的术语进行标记来创建倒排索引,创建所有唯一术语的排序列表,并将文档列表与可以找到该词的位置相关联。
    它非常类似于一本书背面的索引,其中包含书中的所有独特的单词和可以找到该单词的页面列表。当我们说一个文档被索引时,我们引用倒排索引。我们来看看下面两个文档的倒排索引如何看待:

如果我们想要找到包含术语“insight”的文档,我们可以扫描倒排的索引(在哪里排序),找到“insight”这个词,并返回包含这个单词的文档ID,这在这种情况下将是文档1和Doc 2号文件。
    为了提高可搜索性(例如,为小写字母和小写字提供相同的结果),首先分析文档并对其进行索引。
    分析由两部分组成:

  • 将句子标记成单词

  • 将单词规范化为标准表单
    默认情况下,Elasticsearch使用标准分析器

  • 标准标记器(Standard tokenizer),用于在单词边界上分割单词

  • 小写令牌过滤器(Lowercase token filter)将单词转换为小写
    还有许多其他分析仪可用,您可以在文档中阅读它们。
    注意:标准分析仪也使用停止令牌过滤器,但默认情况下禁用。

实现原理分析

  • write(写)/create(创建)操作实现原理

当您向协调节点发送请求以索引新文档时,将执行以下操作:

  • 所有在Elasticsearch集群中的节点都包含:有关哪个分片存在于哪个节点上的元数据。协调节点(coordinating node)使用文档ID(默认)将文档路由到对应的分片。Elasticsearch将文档ID以murmur3作为散列函数进行散列,并通过索引中的主分片数量进行取模运算,以确定文档应被索引到哪个分片。
    shard = hash(document_id) % (num_of_primary_shards)

  • 当节点接收到来自协调节点的请求时,请求被写入到translog(我们将在后续的post中间讲解translog),并将该文档添加到内存缓冲区。如果请求在主分片上成功,则请求将并行发送到副本分片。只有在所有主分片和副本分片上的translog被fsync’ed后,客户端才会收到该请求成功的确认。

  • 内存缓冲区以固定的间隔刷新(默认为1秒),并将内容写入文件系统缓存中的新段。此分段的内容更尚未被fsync’ed(未被写入文件系统),分段是打开的,内容可用于搜索。

  • translog被清空,并且文件系统缓存每隔30分钟进行一次fsync,或者当translog变得太大时进行一次fsync。这个过程在Elasticsearch中称为flush。在刷新过程中,内存缓冲区被清除,内容被写入新的文件分段(segment)。当文件分段被fsync’ed并刷新到磁盘,会创建一个新的提交点(其实就是会更新文件偏移量,文件系统会自动做这个操作)。旧的translog被删除,一个新的开始。

  • 下图显示了写入请求和数据流程:

  • Update和Delete实现原理

删除和更新操作也是写操作。但是,Elasticsearch中的文档是不可变的(immutable),因此不能删除或修改。那么,如何删除/更新文档呢?
    磁盘上的每个分段(segment)都有一个.del文件与它相关联。当发送删除请求时,该文档未被真正删除,而是在.del文件中标记为已删除。此文档可能仍然能被搜索到,但会从结果中过滤掉。当分段合并时(我们将在后续的帖子中包括段合并),在.del文件中标记为已删除的文档不会被包括在新的合并段中。
    现在,我们来看看更新是如何工作的。创建新文档时,Elasticsearch将为该文档分配一个版本号。对文档的每次更改都会产生一个新的版本号。当执行更新时,旧版本在.del文件中被标记为已删除,并且新版本在新的分段中编入索引。旧版本可能仍然与搜索查询匹配,但是从结果中将其过滤掉。
    indexed/updated文档后,我们希望执行搜索请求。我们来看看如何在Elasticsearch中执行搜索请求。

  • Read的实现原理

读操作由两个阶段组成:

  • 查询阶段(Query Phase)

  • 获取阶段(Fetch Phase)

  • 查询阶段(Query Phase)

在此阶段,协调节点将搜索请求路由到索引(index)中的所有分片(shards)(包括:主要或副本)。分片独立执行搜索,并根据相关性分数创建一个优先级排序结果(稍后我们将介绍相关性分数)。所有分片将匹配的文档和相关分数的文档ID返回给协调节点。协调节点创建一个新的优先级队列,并对全局结果进行排序。可以有很多文档匹配结果,但默认情况下,每个分片将前10个结果发送到协调节点,协调创建优先级队列,从所有分片中分选结果并返回前10个匹配。

  • 获取阶段(Fetch Phase)

在协调节点对所有结果进行排序,已生成全局排序的文档列表后,它将从所有分片请求原始文档。
    所有的分片都会丰富文档并将其返回到协调节点。

如上所述,搜索结果按相关性排序。我们来回顾一下相关性的定义。

  • 搜索相关性(Search Relevance)

相关性由Elasticsearch给予搜索结果中返回的每个文档的分数确定。用于评分的默认算法为tf / idf(术语频率/逆文档频率)。该术语频率测量术语出现在文档中的次数(更高频率=更高的相关性),逆文档频率测量术语在整个索引中出现的频率占索引中文档总数的百分比(更高的频率
==较少的相关性)。最终得分是tf-idf分数与其他因素(如词语邻近度(短语查询)),术语相似度(用于模糊查询)等的组合。

第二部分

介绍

第1部分分析了Elasticsearch基本的读、写、更新、存储等方面的实现原理,本文档主要介绍Elasticsearch如何实现分布式系统的三个特性(consensus, concurrency和consistency),以及分片的内部概念,例如:translog(Write Ahead Log - WAL)和Lucene segments。
本章主要包括以下内容:

  • Consensus: 脑裂(split-brain)和quorum机制

  • Concurrency(并发)

  • Consistency(一致性):确保一致的写入和读取

  • Translog (Write Ahead Log — WAL)

  • Lucene segments

Consensus(脑裂和quorum机制)

Consensus是一个分布式系统的基本挑战。它要求分布式系统中的所有的进程/节点,对给定的数据的值/状态达成一致。有很多Consensus的算法,如Raft,Paxos等。这些算法在数学上被证明是有效的,但Elasticsearch实现了它自己的consensus系统(zen discovery),是因为Shay Banon(Elasticsearch的创建者)所描述的原因。
    zen discovery模块有两个部分:

  • Ping:执行该过程的节点,用来发现对方。

  • Unicast:该模块包含一个主机名列表,用于控制要ping哪个节点。

Elasticsearch是一种点对点(peer-to-peer)系统,其中所有节点彼此通信,并且只有一个活动的主节点,该节点可以更新和控制群集状态和操作。新的Elasticsearch集群将会进行一次选举,该选举作为ping进程的一部分在节点中运行。在所有有资格选举的节点中,其中有一个节点被选举为主节点(master node),其他节点加入该主节点。
      默认的ping_interval为1秒,ping_timeout为3秒。当节点加入时,他们会发生一个join的请求给master主机,该请求有一个join_timeout时间,默认是参数ping_timeout的20倍。
      若主节点失败,集群中的几点再次开始ping,开始另一次新的选举。如果节点意外地认为主节点发生故障,并且通过其他节点发现主节点,则该ping过程也将有所帮助。
      注意:默认情况下,客户端(client)和数据节点不会对选举过程做出贡献。可以通过改变以下参数的设置,这些参数在elasticsearch.yml 文件中:

discovery.zen.master_election.filter_client:False discovery.zen.master_election.filter_data:False

故障检测的过程是这样的:主节点ping所有的节点来检查这些节点是否存活,而所有的节点回ping主节点来汇报他们是存活的。

若使用默认配置,Elasticsearch会遇到脑裂的问题,如果是网络分区,则节点可以认为主节点已经死机,并将其自身选为主节点,从而导致集群具有多个主节点。这样可能导致数据丢失,可能无法正确合并数据。这种情况可以通过将以下属性设置为主合格节点的法定数量来避免:

discovery.zen.minimum_master_nodes = int(# of master eligible nodes/2)+1

该参数要求法定数量的可参加选举的节点加入新的选举过程来完成新的选举过程,并接受新节点作为新的master节点。这是确保集群稳定性的极其重要的参数,如果集群大小发生变化,可以进行动态更新。图a和b分别显示了在minimum_master_nodes属性被设置,和未被设置的两种情况。
    注意:对于生产环境的集群,建议有3个专用主节点,在任何给定的时间点只有一个处于活动状态,其他的节点不服务任何客户请求。

Concurrency(并发控制)

Elasticsearch是一个分布式系统,支持并发请求。当创建/更新/删除请求命中主分片时,它同时发送到副本分片,但是这些请求到达的顺序是不确定的。在这种情况下,Elasticsearch使用乐观并发控制(optimistic concurrency control)来确保文档的较新版本不会被旧版本覆盖。
    每个被索引的文档都会有一个版本号,每次文档改变时,该版本号就会增加。使用版本号可以确保对文档的改变是顺序进行的。为确保我们的应用程序中的更新不会导致数据丢失,Elasticsearch的API允许您指定,更改应该应用到目前哪一个版本号。如果请求中指定的版本号,早于分片中存在的版本号,则请求失败,这意味着该文档已被其他进程更新。
    如何处理失败的请求?可以在应用程序级别进行控制。还有其他锁定选项可用,您可以在这里阅读。

当我们向Elasticsearch发送并发请求时,下一个问题是 - 我们如何使这些请求保持一致?现在要回答 CAP原则,还不是太清晰,这是接下来要讨论的问题。

但是,我们将回顾如何使用Elasticsearch实现一致的写入和读取。

Consistency (确保一致的写和读)

对于写操作,Elasticsearch支持与大多数其他数据库不同的一致性级别,它允许初步检查以查看有多少个分片可用于允许写入。可用的选项是:quorum的可设置的值为:one和all。默认情况下,它被设置为:quorum,这意味着只有当大多数分片可用时,才允许写入操作。在大部分分片可用的情况下,由于某种原因,写入复制副本分片失败仍然可能发生,在这种情况下,副本被认为是错误的,该分片将在不同的节点上进行重建。

对于读操作,新文档在刷新间隔时间后才能用于搜索。为了确保搜索结果来自最新版本的文档,可以将复制(replication)设置为sync(默认值),当在主分片和副本分片的写操作都完成后,写操作才返回。在这种情况下,来自任何分片的搜索请求将从文档的最新版本返回结果。
      即使你的应用程序为了更快的indexing而设置:replication=async,也可以使用_preference参数,可以为了搜素请求把它设置为primary。这样,查询主要分片就是搜索请求,并确保结果将来自最新版本的文档。
      当我们了解Elasticsearch如何处理consensus,并发性和一致性时,我们来看看分片内部的一些重要概念,这些概念导致了Elasticsearch作为分布式搜索引擎的某些特征。

Translog

自从开发关系数据库以来,数据库世界中一直存在着write ahead log(WAL)或事务日志(translog)的概念。Translog在发生故障的情况下确保数据的完整性,其基本原则是,在数据的实际更改提交到磁盘之前必须先记录并提交预期的更改。
      当新文档被索引或旧文档被更新时,Lucene索引会更改,这些更改将被提交到磁盘以进行持久化。每次写请求之后进行持久化操作是一个非常消耗性能的操作,它通过一次持久化多个修改到磁盘的方式运行(译注:也就是批量写入)。正如我们在之前的一篇博客中描述的那样,默认情况下每30分钟执行一次flush操作(Lucene commit),或者当translog变得太大(默认为512MB)时)。在这种情况下,有可能失去两次Lucene提交之间的所有变化。为了避免这个问题,Elasticsearch使用translog。所有的索引/删除/更新操作都先被写入translog,并且在每个索引/删除/更新操作之后(或者每默认默认为5秒),translog都被fsync’s,以确保更改被持久化。在主文件和副本分片上的translog被fsync’ed后,客户端都会收到写入确认。

在两次Lucene提交或重新启动之间出现硬件故障的情况下,会在最后一次Lucene提交之前重播Translog以从任何丢失的更改中恢复,并将所有更改应用于索引。
      建议在重新启动Elasticsearch实例之前显式刷新translog,因为启动将更快,因为要重播的translog将为空。POST / _all / _flush命令可用于刷新集群中的所有索引。
使用translog刷新操作,文件系统缓存中的段将提交到磁盘,以使索引持续更改。
现在我们来看看Lucene segment:

Lucene Segments

Lucene索引由多个片段(segment)组成,片段本身是完全功能的倒排索引。片段是不可变的,这允许Lucene增量地向索引添加新文档,而无需从头开始重建索引。对于每个搜索请求,搜所有段都会被搜素,每个段消耗CPU周期,文件句柄和内存。这意味着分段数越多,搜索性能就越低。
    为了解决这个问题,Elasticsearch将小段合并成一个更大的段(如下图所示),将新的合并段提交到磁盘,并删除旧的较小的段。

合并操作将会自动发生在后台,而不会中断索引或搜索。由于分段合并可能会浪费资源并影响搜索性能,因此Elasticsearch会限制合并过程的资源使用,以获得足够的资源进行搜索。

第三部分

介绍

在上面的文章中,我们讨论了Elasticsearch如何处理分布式系统的一些基本问题。在这个部分中,我们将会审查Elasticsearch的各个方面,例如接近实时的搜索和权衡,它考虑计算Insight Data Engineering Fellows在构建数据平台时利用的搜索相关性。

  • 近实时搜索

  • 为什么深度分页在分布式搜索可能是危险的?

  • 计算搜索相关性的权衡

近实时的搜索

虽然在Elasticsearch中的更改不是立即可见,但它确实提供了一个接近实时的搜索引擎。如前一篇文章所述,将Lucene更改持久化到磁盘是一项非常耗性能的操作。为了避免在文档搜索可用时提交Lucene修改到磁盘,这里有一个位于内存缓冲区和磁盘之间的文件系统缓存。每秒刷新内存缓冲区(默认情况下),并在文件系统缓存中创建一个具有倒排索引的新段。此片段已打开并可用于搜索。
    文件系统的缓存中,可能有文件句柄、被打开的文件,该文件可以被打开、读取和关闭,但它保存在内存中。然而,默认情况下刷新间隔为1秒,所以更改不能立即可见,因此它只是近实时的。这也有助于CRUD操作的近实时操作。
    文件系统缓存可以具有文件句柄和文件可以被打开,读取和关闭,但是它存在于内存中。
    由于默认情况下,刷新间隔为1秒,所以更改不可见,因此接近实时。由于translog会把没有写入磁盘的更改进行持久化。对于每个请求,在检查相关分段(relevant segments)之前,会对translog进行最近的更改进行搜索,因此客户端可以接近实时访问所有更改。
    您可以在每次创建/更新/删除操作之后显式刷新索引(refresh the index),以使更改立即生效,但不推荐这样做,因为该过程会创建很多小的分段(small segments),从而影响搜索性能。
    对于一个搜索请求,会搜索一个索引中给定分片的所有Lucene分段(Lucene segments) 。然而,在结果页中获取所有匹配的文档或文档深度,对于你的Elasticsearch集群是危险的。
    我们来看看为什么会这样:

为什么深度分页(deep pagination)在分布式搜索可能是危险的?

当你向Elasticsearch发送一个搜索请求,若有大量文档被匹配,默认情况下,只是返回top 10的结果。搜索API有from和size参数,用来确定搜索匹配到的所有结果文档的深度。
    例如,如果要查看与搜索匹配的50到60行的文档,则设置from= 50和size = 10。
    当每个分片接收到搜索请求时,它将创建一个from+size大小的优先队列,以满足搜索结果本身,然后将结果返回给协调节点。

若你想查看从50,000到50,010的搜索结果,那么每个分片将创建一个具有50,010个结果的优先队列,并且协调节点(coordinating node)必须对shards* 50,010个数目的结果在内存中进行排序。
    分片的数目进行排序 shards* 50,010个结果存储在内存中。这种级别的分页是否能够完成,取决于你拥有的硬件资源。但是您需要非常小心深层分页,因为它可以轻易让你的集群崩溃。
    可以通过 scroll API来获取所有的文档,该API的行为很像关系数据库中的游标。通过禁用排序的 scroll API,并且每个分片只要具有与搜索匹配的文档,就会继续发送结果。
    若要获取大量的搜索到的文档,排序得分结果是非常耗性能的。Elasticsearch是分布式系统,计算搜索相关性分数代价是很高的。下面我们看一下计算搜索相关性的一些权衡:

计算搜索相关性的权衡

Elasticsearch使用tf-idf来计算搜索相关性(search relevance),由于它的分布式特性,计算搜索全局的idf(inverse document frequency)的代价是很高的。替代的方式是,让每个分片计算本地分片的idf来分配一个相关性分数给结果文档,仅返回该分片上的文档。同样,所有的分片返回结果文档,这些文档带有由本地idf计算的相关性分数,协调节点把所有的结果排序,并返回top n的结果。
    这在大多数情况下是正常的,除非您的索引在关键字方面有偏差,或者单个分片上没有足够的数据来表示全局分布。
    例如,如果您正在搜索“insight”一词,并且包含术语“insight”的大多数文档都位于一个分片上,则与查询匹配的文档将不会在每个分片上公平排列为本地idf 的值会有很大的不同,搜索结果可能不太相关。
    类似地,如果没有足够的数据,那么局部idf值可能会对某些搜索有很大的不同,并且结果可能不如预期那么相关。在具有足够数据的实际场景中,本地idf值趋向于均匀,并且搜索结果是相关的,因为文档得分相当。
有几种方法来获取本地的idf分数,但是并不推荐用于生产系统。

  • 一种方法是你可以只有一个分片用于索引,然后本地idf是全局idf,但这并不排除并行/缩放的空间,并且对于大的索引是不实用的。

  • 另一种方法是使用参数dfs_query_then_search(dfs =分布式频率搜索)与搜索请求,它首先计算所有分片的本地idf,然后组合这些本地idf值以计算整个索引的全局idf,然后返回结果使用全局idf计算相关性分数。这在生产中不推荐,并且具有足够的数据将确保术语频率分布良好。

参考

  • https://blog.insightdatascience.com/anatomy-of-an-elasticsearch-cluster-part-i-7ac9a13b05db

  • https://blog.insightdatascience.com/anatomy-of-an-elasticsearch-cluster-part-ii-6db4e821b571

  • https://blog.insightdatascience.com/anatomy-of-an-elasticsearch-cluster-part-iii-8bb6ac84488d

  • https://www.elastic.co/blog/index-vs-type

Elasticsearch实现原理分析相关推荐

  1. 搞懂 SQL 查询优化原理分析,秒速处理大数据量查询

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 有一张财务流水表,未分库分表,目前的数据量为9555695,分页查询使用到 ...

  2. Elasticsearch分片原理

    ES集群的基本概念 Cluster 代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的.es的一个概念就是去中心化,字面上理解就是无中心节 ...

  3. Elasticsearch工作原理

    一.关于搜索引擎 各位知道,搜索程序一般由索引链及搜索组件组成. 索引链功能的实现需要按照几个独立的步骤依次完成:检索原始内容.根据原始内容来创建对应的文档.对创建的文档进行索引. 搜索组件用于接收用 ...

  4. 2021年大数据ELK(十一):Elasticsearch架构原理

    全网最详细的大数据ELK文章系列,强烈建议收藏加关注! 新文章都已经列出历史文章目录,帮助大家回顾前面的知识重点. 目录 Elasticsearch架构原理 一.Elasticsearch的节点类型 ...

  5. java signature 性能_Java常见bean mapper的性能及原理分析

    背景 在分层的代码架构中,层与层之间的对象避免不了要做很多转换.赋值等操作,这些操作重复且繁琐,于是乎催生出很多工具来优雅,高效地完成这个操作,有BeanUtils.BeanCopier.Dozer. ...

  6. Select函数实现原理分析

    转载自 http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数.select ...

  7. spring ioc原理分析

    spring ioc原理分析 spring ioc 的概念 简单工厂方法 spirng ioc实现原理 spring ioc的概念 ioc: 控制反转 将对象的创建由spring管理.比如,我们以前用 ...

  8. 一次 SQL 查询优化原理分析(900W+ 数据,从 17s 到 300ms)

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:Muscleape jianshu.com/p/0768eb ...

  9. 原理分析_变色近视眼镜原理分析

    随着眼镜的发展,眼镜的外型变得越来越好看,并且眼镜的颜色也变得多姿多彩,让佩戴眼镜的你变得越来越时尚.变色近视眼镜就是由此产生的新型眼镜.变色镜可以随着阳光的强弱变换不同的色彩. 变色眼镜的原理分析 ...

最新文章

  1. #@python常见的代码自己编写问题
  2. SCALA中类的继承
  3. 《写给大家看的设计书:实例与创意(修订版)》—1你已经知道多少了?
  4. 随机数发生器怎么用_用随机数发生器射击自己的脚
  5. 【转】使用CSS 禁止文本选择
  6. jquery中有.post,.get,$.getJSON为什么没postJSON
  7. Java中为什么使用事务?什么时候使用事务?如何使用事务?
  8. NMS非极大值抑制原理——目标检测
  9. 重庆高清卫星地图(含道路标签数据叠加)
  10. OpenGL ES2.0 的三种变量类型(uniform,attribute和varying)
  11. 7款浏览器新标签页扩展让你的Chrome耳目一新
  12. Codeforces 417D Cunning Gena(状压DP)
  13. 绕过安卓SSL验证证书的常见四种方式
  14. 我的钱包页面HTML,钱包.html
  15. 计算机专业必须知道的东西——C语言的发展
  16. 基于SpringBoot+Vue的前后端分离开发汽车之家资讯论坛系统设计与实现
  17. 数据结构课程设计【C++实现】
  18. 【Web】Hexo+Butterfly+Github+Coding搭建个人博客
  19. Python base64 + AES EBC模式加密
  20. 秋冬饮品研发没思路?带你看新品5大趋势!

热门文章

  1. 吉林大学计算机科学与技术学院王欣,应用改进迭代最近点方法的点云数据配准...
  2. java自定义对话框_Flutter AlertDialog自定义对话框实现示例(确认或取消)
  3. xml 和android脚本之家,AndroidManifest.xml配置文件解析_Android_脚本之家
  4. rust布料怎么弄_布料“难弄”,你需要从这六方面解决!
  5. 交换机登入linux ftp,巧用FTP 实现交换机间配置文件复制
  6. showmodaldialog 为什么不能复制_防复制的门禁读头可以防止UID和FUID读卡器
  7. linux apache 安装 rewrite,Linux主机下Apache如何使用rewrite模块
  8. 微型计算机控制技术黄勤期末,微型计算机控制技术作者黄勤第4章__工控机的抗干扰课案.ppt...
  9. 研华电脑510上电自启_研华工控机怎么设置u盘启动_研华工控机U盘引导方法
  10. linux php不支持crypt,(PHP)如何在CRYPT_BLOWFISH中使用crypt()?