Elasticsearch底层原理分析——新建、索引文档
es版本
8.1.0
重要概念回顾
Elasticsearch Node的角色
与下文流程相关的角色介绍:
| Node Roles | 配置 | 主要功能说明 |
|---|---|---|
| master | node.roles: [ master ] | 有资格参与选举成为master节点,从而进行集群范围的管理工作,如创建或删除索引、跟踪哪些节点是集群的一部分以及决定将哪些分片分配给哪些节点等 |
| data | node.roles: [ data ] | 数据节点保存已索引的文档的分片。处理数据相关操作,例如 CRUD、搜索和聚合。 |
| node.roles: [ ] | 节点不填任何角色,则是协调节点,换言之每个节点,也都有协调节点功能。具备路由请求、对搜索结果合并和分发批量索引等功能。本质上,协调节点的行为就像智能负载均衡器 |
详见:https://www.elastic.co/guide/en/elasticsearch/reference/8.9/modules-node.html
分片
- 一个分片是一个 Lucene 的实例,是一个完整的搜索引擎
- 主分片的数量决定了索引最多能存储多少数据,多分片机制,带来存储量级提升
- 主分片数不可更改,和数据路由算法有关
- 副本分片可以防止硬件故障导致的数据丢失,同时可以提供读请求,增大能处理的搜索吞吐量
- 对文档的新建、索引和删除请求都是写操作,必须在主分片上面完成,之后才能被复制到相关的副本分片
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_add-an-index.html
新建、索引和删除文档
以官网(https://www.elastic.co/guide/cn/elasticsearch/guide/current/distrib-write.html)例子分析,es集群有3个节点,其中有个索引有两分片(P0、P1),两副本(P0、R0、R0,P1、R1、R1),如创建索引时:
PUT /blogs
{"settings" : {"number_of_shards" : 2,"number_of_replicas" : 2}
}

再对一些前提知识回顾一下:
- 每个节点都具备协调节点功能,也即路由请求、对搜索结果合并和分发批量索引等功能
- 对文档的新建、索引和删除请求等写操作,必须在主分片上面完成,之后才能被复制到相关的副本分片
这个例子中的两个假设:
- 请求集群时,es采用的是随机轮询方法进行负载均衡,每个节点都有可能被请求到。在这个例子中,假设先请求到node1
- 节点使用文档的 _id 确定文档属于分片 0
所以(直接引用官网步骤):
- 客户端向 Node 1 发送新建、索引或者删除请求。
- 节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。
- Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功。
源码理解
如何确定文档属于哪个分片,请求转发哪个节点
获取分片ID是从TransportBulkAction类中开始调用开始
int shardId = docWriteRequest.route(indexRouting);
具体实现在IndexRouting类中。简述步骤就是:
- 对routing值进行Murmur3Hash运算(如果没有设置routing,值默认是doc id值)
- 对hash后的值进行取模运算,routingNumShards默认1024,routingFactor默认512
protected int shardId(String id, @Nullable String routing) {return hashToShardId(effectiveRoutingToHash(routing == null ? id : routing));
}protected final int hashToShardId(int hash) {return Math.floorMod(hash, routingNumShards) / routingFactor;
}private static int effectiveRoutingToHash(String effectiveRouting) {return Murmur3HashFunction.hash(effectiveRouting);
}
为何需要路由,以及路由带来什么问题
- 为何需要路由
总的来说,就是多分片设计,可以承载更大量级数据,而分片预分配设计,可以简单的获取文档位置,能减少数据分裂风险,以及对数据重新索引友好
https://www.elastic.co/guide/cn/elasticsearch/guide/current/overallocation.html - 带来的问题:
创建索引的时候就需要确定好主分片的数量,并且永远不会改变这个数量。因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
https://www.elastic.co/guide/cn/elasticsearch/guide/current/routing-value.html
如何根据分片ID确定节点
代码在TransportReplicationAction#doRun方法中,简单概括就是state中存有集群信息,通过传入分片ID,先获取主分片信息,再通过主分片节点ID,获取对应节点信息。
final ShardRouting primary = state.getRoutingTable().shardRoutingTable(request.shardId()).primaryShard();
if (primary == null || primary.active() == false) {logger.trace("primary shard [{}] is not yet active, scheduling a retry: action [{}], request [{}], "+ "cluster state version [{}]",request.shardId(),actionName,request,state.version());retryBecauseUnavailable(request.shardId(), "primary shard is not active");return;
}
if (state.nodes().nodeExists(primary.currentNodeId()) == false) {logger.trace("primary shard [{}] is assigned to an unknown node [{}], scheduling a retry: action [{}], request [{}], "+ "cluster state version [{}]",request.shardId(),primary.currentNodeId(),actionName,request,state.version());retryBecauseUnavailable(request.shardId(), "primary shard isn't assigned to a known node.");return;
}
final DiscoveryNode node = state.nodes().get(primary.currentNodeId());
if (primary.currentNodeId().equals(state.nodes().getLocalNodeId())) {performLocalAction(state, primary, node, indexMetadata);
} else {performRemoteAction(state, primary, node);
}
主分片执行流程
1. 写一致性
默认写成功一个主分片即可,源码在ActiveShardCount#enoughShardsActive方法中
https://www.elastic.co/guide/en/elasticsearch/client/curator/current/option_wait_for_active_shards.html
public boolean enoughShardsActive(final IndexShardRoutingTable shardRoutingTable) {final int activeShardCount = shardRoutingTable.activeShards().size();if (this == ActiveShardCount.ALL) {// adding 1 for the primary in addition to the total number of replicas,// which gives us the total number of shard copiesreturn activeShardCount == shardRoutingTable.replicaShards().size() + 1;} else if (this == ActiveShardCount.DEFAULT) {return activeShardCount >= 1;} else {return activeShardCount >= value;}}
2. 具体写流程
参考官网(https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html)理解。图片所示是一个lucene索引,lucene索引下面有三个段(segment),图中Searchable表示从内存(In-memory buffer,也叫Indexing Buffer)刷新到磁盘,写入物理文件,不可更改,其中fsync操作将新文档刷新到磁盘的操作,性能代价是很大的。所以会先将文档写入文件系统缓存中,也即图中In-memory buffer中,对应的是 Indexing Buffer(https://www.elastic.co/guide/en/elasticsearch/reference/8.10/indexing-buffer.html)。
所以流程是:
- 将文档写入Indexing Buffer中
- 将操作追加写入 translog 中,以便确保即便在刷盘时异常,也能从失败中恢复数据
- 将内存中的数据刷新持久化到磁盘中(默认情况下每个分片会每秒自动刷新一次)
- 在刷新(flush)之后,段被全量提交,并且事务日志被清空


相关源码
主要在InternalEngine类中:
- index方法包含写入In-memory buffer对应生成IndexResult(?)和写translog;
- 将内存中的数据刷新持久化到磁盘中在refresh方法;
- 段被全量提交,和事务日志被清空在flush方法。
index方法
public IndexResult index(Index index) throws IOException {// 确保传入的文档的唯一标识是 IdFieldMapperassert Objects.equals(index.uid().field(), IdFieldMapper.NAME) : index.uid().field();// 检查 index 的来源是否不是恢复操作final boolean doThrottle = index.origin().isRecovery() == false;// 获取读锁try (ReleasableLock releasableLock = readLock.acquire()) {// 确保引擎处于打开状态ensureOpen();// 断言传入的 index 的序列号符合预期assert assertIncomingSequenceNumber(index.origin(), index.seqNo());int reservedDocs = 0;try (Releasable ignored = versionMap.acquireLock(index.uid().bytes());Releasable indexThrottle = doThrottle ? throttle.acquireThrottle() : () -> {}) {lastWriteNanos = index.startTime();// 代码中有一段注释,描述了关于追加(append-only)优化的注意事项。根据注释所述,如果引擎接收到一个带有自动生成的ID的文档,// 可以优化处理并直接使用 addDocument 而不是 updateDocument,从而跳过版本和索引查找。此外,还使用文档的时间戳来检测是否可能已经添加过该文档。// 获取索引策略final IndexingStrategy plan = indexingStrategyForOperation(index);reservedDocs = plan.reservedDocs;final IndexResult indexResult;if (plan.earlyResultOnPreFlightError.isPresent()) {assert index.origin() == Operation.Origin.PRIMARY : index.origin();indexResult = plan.earlyResultOnPreFlightError.get();assert indexResult.getResultType() == Result.Type.FAILURE : indexResult.getResultType();} else {// generate or register sequence number// 生成或注册文档的序列号。对于主分片的操作,会生成新的序列号。if (index.origin() == Operation.Origin.PRIMARY) {index = new Index(index.uid(),index.parsedDoc(),// 生成新的序列号generateSeqNoForOperationOnPrimary(index),index.primaryTerm(),index.version(),index.versionType(),index.origin(),index.startTime(),index.getAutoGeneratedIdTimestamp(),index.isRetry(),index.getIfSeqNo(),index.getIfPrimaryTerm());// 检查了当前操作是否应该追加到 Lucene 索引中final boolean toAppend = plan.indexIntoLucene && plan.useLuceneUpdateDocument == false;if (toAppend == false) {// 更新主分片的最大序列号advanceMaxSeqNoOfUpdatesOnPrimary(index.seqNo());}} else {// 对于副本分片的操作,会标记已经见过的序列号,序列号已经被使用。markSeqNoAsSeen(index.seqNo());}assert index.seqNo() >= 0 : "ops should have an assigned seq no.; origin: " + index.origin();if (plan.indexIntoLucene || plan.addStaleOpToLucene) {// 写到 Lucene 中indexResult = indexIntoLucene(index, plan);} else {indexResult = new IndexResult(plan.versionForIndexing,index.primaryTerm(),index.seqNo(),plan.currentNotFoundOrDeleted);}}// 判断索引操作是否来自 Translog。如果是来自 Translog 的操作,就不再处理,因为这已经是一个已经被记录的操作if (index.origin().isFromTranslog() == false) {final Translog.Location location;if (indexResult.getResultType() == Result.Type.SUCCESS) {// 如果索引操作成功, 将该操作添加到 Translog 中,并获取 Translog 的位置location = translog.add(new Translog.Index(index, indexResult));} else if (indexResult.getSeqNo() != SequenceNumbers.UNASSIGNED_SEQ_NO) {// if we have document failure, record it as a no-op in the translog and Lucene with the generated seq_no// 如果索引操作失败,并且具有序列号, 则将失败的操作记录为一个 no-op 操作final NoOp noOp = new NoOp(indexResult.getSeqNo(),index.primaryTerm(),index.origin(),index.startTime(),indexResult.getFailure().toString());location = innerNoOp(noOp).getTranslogLocation();} else {// 如果索引操作失败,并且没有序列号,将 location 设置为 nulllocation = null;}// 设置Translog 位置indexResult.setTranslogLocation(location);}// 如果索引操作成功且需要写入 Lucene, 则获取 Translog 的位置信息,用于更新版本映射if (plan.indexIntoLucene && indexResult.getResultType() == Result.Type.SUCCESS) {final Translog.Location translogLocation = trackTranslogLocation.get() ? indexResult.getTranslogLocation() : null;versionMap.maybePutIndexUnderLock(index.uid().bytes(),new IndexVersionValue(translogLocation, plan.versionForIndexing, index.seqNo(), index.primaryTerm()));}// 本地 Checkpoint 的更新, 标记当前序列号已经被处理localCheckpointTracker.markSeqNoAsProcessed(indexResult.getSeqNo());if (indexResult.getTranslogLocation() == null) {// the op is coming from the translog (and is hence persisted already) or it does not have a sequence number// 如果 Translog 的位置信息为 null,说明该操作来自于 Translog,已经被持久化,或者该操作没有序列号。// 在这种情况下,标记当前序列号已经被持久化assert index.origin().isFromTranslog() || indexResult.getSeqNo() == SequenceNumbers.UNASSIGNED_SEQ_NO;localCheckpointTracker.markSeqNoAsPersisted(indexResult.getSeqNo());}indexResult.setTook(System.nanoTime() - index.startTime());// 将操作结果冻结,确保其不可变indexResult.freeze();return indexResult;} finally {releaseInFlightDocs(reservedDocs);}} catch (RuntimeException | IOException e) {try {if (e instanceof AlreadyClosedException == false && treatDocumentFailureAsTragicError(index)) {failEngine("index id[" + index.id() + "] origin[" + index.origin() + "] seq#[" + index.seqNo() + "]", e);} else {maybeFailEngine("index id[" + index.id() + "] origin[" + index.origin() + "] seq#[" + index.seqNo() + "]", e);}} catch (Exception inner) {e.addSuppressed(inner);}throw e;}}
自动sync条件translog条件:
相关配置:
- index.translog.sync_interval: 默认5s
- index.translog.durability:默认配置的是request,即每次写请求完成之后执行(e.g. index, delete, update, bulk)
- index.translog.flush_threshold_size:默认512mb
https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html
https://www.elastic.co/guide/en/elasticsearch/reference/8.11/index-modules-translog.html
private void maybeSyncTranslog(final IndexShard indexShard) throws IOException {if (indexShard.getTranslogDurability() == Translog.Durability.REQUEST&& indexShard.getLastSyncedGlobalCheckpoint() < indexShard.getLastKnownGlobalCheckpoint()) {indexShard.sync();}
}
refresh源码:
final boolean refresh(String source, SearcherScope scope, boolean block) throws EngineException {// both refresh types will result in an internal refresh but only the external will also// pass the new reader reference to the external reader manager.// 获取当前的本地检查点final long localCheckpointBeforeRefresh = localCheckpointTracker.getProcessedCheckpoint();boolean refreshed;try {// refresh does not need to hold readLock as ReferenceManager can handle correctly if the engine is closed in mid-way.// 尝试增加存储的引用计数,以确保在刷新期间没有人关闭存储if (store.tryIncRef()) {// increment the ref just to ensure nobody closes the store during a refreshtry {// even though we maintain 2 managers we really do the heavy-lifting only once.// the second refresh will only do the extra work we have to do for warming caches etc.ReferenceManager<ElasticsearchDirectoryReader> referenceManager = getReferenceManager(scope);// it is intentional that we never refresh both internal / external togetherif (block) {referenceManager.maybeRefreshBlocking();refreshed = true;} else {refreshed = referenceManager.maybeRefresh();}} finally {// 减少存储的引用计数store.decRef();}if (refreshed) {lastRefreshedCheckpointListener.updateRefreshedCheckpoint(localCheckpointBeforeRefresh);}} else {refreshed = false;}} catch (AlreadyClosedException e) {failOnTragicEvent(e);throw e;} catch (Exception e) {try {failEngine("refresh failed source[" + source + "]", e);} catch (Exception inner) {e.addSuppressed(inner);}throw new RefreshFailedEngineException(shardId, e);}assert refreshed == false || lastRefreshedCheckpoint() >= localCheckpointBeforeRefresh: "refresh checkpoint was not advanced; "+ "local_checkpoint="+ localCheckpointBeforeRefresh+ " refresh_checkpoint="+ lastRefreshedCheckpoint();// TODO: maybe we should just put a scheduled job in threadPool?// We check for pruning in each delete request, but we also prune here e.g. in case a delete burst comes in and then no more deletes// for a long time:maybePruneDeletes();mergeScheduler.refreshConfig();return refreshed;}
flush源码:
执行条件主要在这段注释里面:
// Only flush if (1) Lucene has uncommitted docs, or (2) forced by caller, or (3) the
// newly created commit points to a different translog generation (can free translog),
// or (4) the local checkpoint information in the last commit is stale, which slows down future recoveries.
@Overridepublic void flush(boolean force, boolean waitIfOngoing) throws EngineException {// 确保引擎是打开的ensureOpen();if (force && waitIfOngoing == false) {// 如果强制执行 flush 但不等待正在进行的 flush 操作,抛出异常assert false : "wait_if_ongoing must be true for a force flush: force=" + force + " wait_if_ongoing=" + waitIfOngoing;throw new IllegalArgumentException("wait_if_ongoing must be true for a force flush: force=" + force + " wait_if_ongoing=" + waitIfOngoing);}// 获取读锁try (ReleasableLock lock = readLock.acquire()) {ensureOpen();if (flushLock.tryLock() == false) {// if we can't get the lock right away we block if needed otherwise barfif (waitIfOngoing == false) {return;}logger.trace("waiting for in-flight flush to finish");flushLock.lock();logger.trace("acquired flush lock after blocking");} else {logger.trace("acquired flush lock immediately");}try {/*** 1. Lucene 有未提交的文档: 如果 Lucene 索引中存在未提交的文档,即有尚未写入磁盘的更改。* 2. 被调用者强制执行: 如果调用者明确要求执行 flush 操作,即 force 参数为 true。* 3. 新创建的提交指向不同的 translog 生成: 当新创建的提交(commit)指向不同的 translog 生成时,执行 flush 操作。* 这可能是因为 translog 已经占用了一定的空间,需要释放这些旧的 translog。* 4. 上一次提交的本地检查点信息已过期: 如果上一次提交的段信息中的本地检查点信息已过期,这可能会导致未来的恢复操作变慢。* 因此,需要执行 flush 操作来更新本地检查点信息。*/// 检查 Lucene 是否有未提交的更改。boolean hasUncommittedChanges = indexWriter.hasUncommittedChanges();// 检查是否应定期执行 flush 操作boolean shouldPeriodicallyFlush = shouldPeriodicallyFlush();if (hasUncommittedChanges|| force|| shouldPeriodicallyFlush// 检查是否本地检查点信息在上一次提交的段信息中过期,如果是,则触发 flush|| getProcessedLocalCheckpoint() > Long.parseLong(lastCommittedSegmentInfos.userData.get(SequenceNumbers.LOCAL_CHECKPOINT_KEY))) {ensureCanFlush();try {// 滚动 translog 的生成translog.rollGeneration();logger.trace("starting commit for flush; commitTranslog=true");// 提交索引写入器,包括在 Lucene 中提交未提交的文档,并将 translog 提交到持久存储。commitIndexWriter(indexWriter, translog);logger.trace("finished commit for flush");// a temporary debugging to investigate test failure - issue#32827. Remove when the issue is resolvedlogger.debug("new commit on flush, hasUncommittedChanges:{}, force:{}, shouldPeriodicallyFlush:{}",hasUncommittedChanges,force,shouldPeriodicallyFlush);// we need to refresh in order to clear older version values// 强制刷新索引以清除旧的版本信息。refresh("version_table_flush", SearcherScope.INTERNAL, true);translog.trimUnreferencedReaders();} catch (AlreadyClosedException e) {failOnTragicEvent(e);throw e;} catch (Exception e) {throw new FlushFailedEngineException(shardId, e);}// 刷新最后提交的段信息refreshLastCommittedSegmentInfos();}} catch (FlushFailedEngineException ex) {maybeFailEngine("flush", ex);throw ex;} finally {flushLock.unlock();}}// We don't have to do this here; we do it defensively to make sure that even if wall clock time is misbehaving// (e.g., moves backwards) we will at least still sometimes prune deleted tombstones:if (engineConfig.isEnableGcDeletes()) {pruneDeletedTombstones();}}
protected void commitIndexWriter(final IndexWriter writer, final Translog translog) throws IOException {// 确保引擎的状态是允许刷新的ensureCanFlush();try {// 获取已处理的本地检查点final long localCheckpoint = localCheckpointTracker.getProcessedCheckpoint();writer.setLiveCommitData(() -> {final Map<String, String> commitData = new HashMap<>(8);// 添加 translog 的 UUID 到提交数据中commitData.put(Translog.TRANSLOG_UUID_KEY, translog.getTranslogUUID());// 添加本地检查点到提交数据中commitData.put(SequenceNumbers.LOCAL_CHECKPOINT_KEY, Long.toString(localCheckpoint));// 添加最大序列号到提交数据中commitData.put(SequenceNumbers.MAX_SEQ_NO, Long.toString(localCheckpointTracker.getMaxSeqNo()));// 添加最大不安全自动生成的 ID 时间戳到提交数据中commitData.put(MAX_UNSAFE_AUTO_ID_TIMESTAMP_COMMIT_ID, Long.toString(maxUnsafeAutoIdTimestamp.get()));// 添加历史 UUID 到提交数据中commitData.put(HISTORY_UUID_KEY, historyUUID);final String currentForceMergeUUID = forceMergeUUID;if (currentForceMergeUUID != null) {// 如果强制合并 UUID 存在,则添加到提交数据中commitData.put(FORCE_MERGE_UUID_KEY, currentForceMergeUUID);}// 添加最小保留序列号到提交数据中commitData.put(Engine.MIN_RETAINED_SEQNO, Long.toString(softDeletesPolicy.getMinRetainedSeqNo()));commitData.put(ES_VERSION, Version.CURRENT.toString());logger.trace("committing writer with commit data [{}]", commitData);return commitData.entrySet().iterator();});shouldPeriodicallyFlushAfterBigMerge.set(false);// 调用Lucene 会将所有未提交的文档写入磁盘,生成新的段writer.commit();} catch (final Exception ex) {try {failEngine("lucene commit failed", ex);} catch (final Exception inner) {ex.addSuppressed(inner);}throw ex;} catch (final AssertionError e) {/** If assertions are enabled, IndexWriter throws AssertionError on commit if any files don't exist, but tests that randomly* throw FileNotFoundException or NoSuchFileException can also hit this.*/if (ExceptionsHelper.stackTrace(e).contains("org.apache.lucene.index.IndexWriter.filesExist")) {final EngineException engineException = new EngineException(shardId, "failed to commit engine", e);try {failEngine("lucene commit failed", engineException);} catch (final Exception inner) {engineException.addSuppressed(inner);}throw engineException;} else {throw e;}}}
写副本
副本在写入数据到 translog 后就可以返回了。源码主要在ReplicationOperation类中
@Override
public void tryAction(ActionListener<ReplicaResponse> listener) {replicasProxy.performOn(shard, replicaRequest, primaryTerm, globalCheckpoint, maxSeqNoOfUpdatesOrDeletes, listener);
}
处理结束给协调节点返回消息
@Overridepublic void onResponse(Void aVoid) {successfulShards.incrementAndGet();try {updateCheckPoints(primary.routingEntry(), primary::localCheckpoint, primary::globalCheckpoint);} finally {decPendingAndFinishIfNeeded();}}
参考:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html
https://www.golangblogs.com/read/elasticsearch/date-2023.05.24.16.58.36?wd=Elasticsearch
《Elasticsearch源码解析与优化实战》
相关文章:
Elasticsearch底层原理分析——新建、索引文档
es版本 8.1.0 重要概念回顾 Elasticsearch Node的角色 与下文流程相关的角色介绍: Node Roles配置主要功能说明masternode.roles: [ master ]有资格参与选举成为master节点,从而进行集群范围的管理工作,如创建或删除索引、跟踪哪些节点是…...
ts实现合并数组对象中key相同的数据
背景 在平常的业务中,后端同学会返回以下类似的结构数据 // 后端返回的数据结构 [{ id: 1, product_id: 1, pid_name: "Asia", name: "HKG01" },{ id: 2, product_id: 1, pid_name: "Asia", name: "SH01" },{ id: 3, pro…...
C语言--根据成绩判断等级
一.题目描述 如果学生的成绩小于60分,那么输出不及格 如果学生的成绩大于60分小于85分,那么输出良好 如果学生的成绩大于85分,那么输出优秀 二.代码实现 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> //根据成绩打印等级 //scor…...
Rust多线程任务,发现有些线程一直获取不到锁【已解决】
问题描述 项目中用到rust,其中在多线程中用到了同一个对象的锁,然而发现其中一个线程一直拿不到这个锁。 解决过程 我先是在线程A中加入了sleep方法,这样做的效果就是,比最初好一些,但是拿到锁还是要较长时间…...
【区块链】产品经理的NFT初探
常见的FT如比特币(BTC),以太币(ETH)等,两个代币之间是完全可替换的。而NFT具有唯一性,不可以互相替换。本文作者对NET的发展现状、相关协议、应用场景等方面进行了分析,一起来看一下…...
香港服务器减少延迟的几种方法
我们在租用香港服务器时,总觉得网站程序反应太慢。选择了香港服务器的开发商和企业对香港服务器目前的访问速度不满意 怎么办?第一点是换服务器。更换配置更大、带宽更高的服务器,可以更好的解决网站访问速度。如何减少香港服务器的延时 速度…...
PowerShell命令小记
1. 使用命令删除指定文件或文件夹 在 PowerShell 中,你可以使用 Remove-Item 命令递归删除文件夹下的指定文件。以下是一条命令的示例,该命令删除指定文件夹及其子文件夹中的所有 .txt 文件: Remove-Item -Path "D:\test" -Recur…...
C语言小练
目录 打印斐波那契数列指定位置的值 给定两个数,求这两个数的最大公约数 三个数从大到小输出 模拟用户登陆情况,且只能输如三次 采用二分法查找数组中的指定元素 打印输出九九乘法表 数一下1-100中所有整数出现多少个数字9 打印1-200之间的素数&…...
Webhook端口中的自签名身份验证
概述 有时,可能需要通过 Webhook 端口从交易伙伴处接收数据,但该交易伙伴可能需要更多的安全性,而不仅仅是用于验证入站 Webhook 请求的基本身份验证用户名/密码 – 或者您可能只想在入站 Webhook 消息上添加额外的安全层。 使用 Webhook 端…...
CSS预处理器(如Sass或Less):变量、嵌套规则和混合器等高级功能
在Vue项目中,可以使用CSS预处理器(如Sass或Less)来编写样式。 这些预处理器提供了一些高级功能,如变量、嵌套规则和混合器等。 1. 变量 在Sass中,我们可以使用$符号定义变量。这些变量方便我们在多个地方重复使用&a…...
【Linux】Linux第一个小程序 --- 进度条
👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和Linux还有算法 ✈️专栏:Linux 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵,希望大佬指点一二 …...
每日一练:约瑟夫生者死者小游戏
1. 问题描述 约瑟夫问题(Josephus problem)是一个经典的数学和计算机科学问题,源于犹太历史学家弗拉维奥约瑟夫斯(Flavius Josephus)的著作《犹太战记》。问题的描述如下: 在这个问题中,有n…...
双指针算法(题目与答案讲解)
文章目录 题目移动零复写零两数之和N数之和(>2个数) 答案讲解移动零复写零两数之和N数之和 题目 力扣 移动零 1、移动零:题目链接 复写零 2、复写零:题目链接 两数之和 3、两数之和题目链接 N数之和(>2个数) 4、N数之和(三个数、四个数) 三个数:题目链接 四个数题目链接…...
python服装电商系统vue购物商城django-pycharm毕业设计项目推荐
系统面向的使用群体为商家和消费者,商家和消费者所承担的功能各不相同,所对象的权限也各不相同。对于消费者和商家设计的功能如下: 对于消费者设计了五大功能模块: (1) 商品信息:用户可在商品…...
数据治理技术:研究现状与数据规范
随着信息技术的迅速发展,数据规模逐渐扩大,与此同时,劣质数据也随之而来,极大地降低了数据挖掘的质量,对信息社会造成了严重的困扰,劣质数据大量存在于很多领域和机构,国外权威机构的统计表明:美…...
一文彻底理解索引下推
了解索引下推吗?二级索引取出的数据是依次回表还是一次回表?索引下推是为了什么发明的? 看完这个文章你将知道上面的问题。 索引下推的概念 从MySQL5.6开始引入的一个特性,索引下推通过减少回表的次数来提高数据库的查询效率; 注意&#…...
Springboot3+vue3从0到1开发实战项目(一)
一. 可以在本项目里面自由发挥拓展 二. 知识整合项目使用到的技术 后端开发 : Validation, Mybatis,Redis, Junit,SpringBoot3 ,mysql,Swagger, JDK17 ,JWT,项目部署 前端开发: Vue3,Vite&am…...
[字符串操作] 有年代的病历单
有年代的病历单 题目描述 小英是药学专业大三的学生,暑假期间获得了去医院药房实习的机会。 在药房实习期间,小英扎实的专业基础获得了医生的一致好评,得知小英在计算概论中取得过好成绩后,主任又额外交给她一项任务,…...
怎么批量提取文件名字到Excel中?
怎么批量提取文件名字到Excel中?Excel是由微软公司开发的一种电子表格软件,它是Microsoft Office办公套件的一部分。Excel提供了强大的数据处理和分析功能,用户可以使用Excel创建、编辑和管理电子表格,进行各种计算、数据分析、图…...
QT搭建的Ros/librviz的GUI软件
1.前言 开发初期学习了下面博主的文章,也报了他在古月局的课,相当于感谢吧。 ROS Qt5 librviz人机交互界面开发一(配置QT环境)-CSDN博客r 软件前期也是参考他的开源项目 GitHub - chengyangkj/Ros_Qt5_Gui_App …...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
