前言

引入快照机制主要是为了解决两个问题:

  1. JRaft新节点加入后,如何快速追上最新的数据
  2. Raft 节点出现故障重新启动后如何高效恢复到最新的数据

Snapshot 源码分析

生成 Raft 节点的快照文件

如果用户需开启 SOFAJRaft 的 Snapshot 机制,则需要在其客户端中设置配置参数类 NodeOptions 的“snapshotUri”属性(即为:Snapshot 文件的存储路径),配置该属性后,默认会启动一个定时器任务(“JRaft-SnapshotTimer”)自动去完成 Snapshot 操作,间隔时间通过配置类 NodeOptions 的“snapshotIntervalSecs”属性指定,默认 3600 秒。定时任务启动代码如下:

NodeImpl#init

this.snapshotTimer = new RepeatedTimer("JRaft-SnapshotTimer", this.options.getSnapshotIntervalSecs() * 1000) {

    @Override
protected void onTrigger() {
handleSnapshotTimeout();
}
}; private void handleSnapshotTimeout() {
this.writeLock.lock();
try {
if (!this.state.isActive()) {
return;
}
} finally {
this.writeLock.unlock();
}
// do_snapshot in another thread to avoid blocking the timer thread.
//异步调用doSnapshot
Utils.runInThread(() -> doSnapshot(null));
} private void doSnapshot(final Closure done) {
if (this.snapshotExecutor != null) {
this.snapshotExecutor.doSnapshot(done);
} else {
if (done != null) {
final Status status = new Status(RaftError.EINVAL, "Snapshot is not supported");
Utils.runClosureInThread(done, status);
}
}
}

最后这里会调用快照执行器的doSnapshot方法,我们往下看。

SnapshotExecutorImpl#doSnapshot

public void doSnapshot(final Closure done) {
boolean doUnlock = true;
this.lock.lock();
try {
//正在停止
if (this.stopped) {
Utils.runClosureInThread(done, new Status(RaftError.EPERM, "Is stopped."));
return;
}
//正在下载镜像
if (this.downloadingSnapshot.get() != null) {
Utils.runClosureInThread(done, new Status(RaftError.EBUSY, "Is loading another snapshot."));
return;
}
//正在保存镜像
if (this.savingSnapshot) {
Utils.runClosureInThread(done, new Status(RaftError.EBUSY, "Is saving another snapshot."));
return;
}
//当前业务状态机已经提交的 Index 索引是否等于 Snapshot 最后保存的日志 Index 索引
//如果两个值相等则表示,业务数据没有新增,无需再生成一次没有意义的 Snapshot
if (this.fsmCaller.getLastAppliedIndex() == this.lastSnapshotIndex) {
// There might be false positive as the getLastAppliedIndex() is being
// updated. But it's fine since we will do next snapshot saving in a
// predictable time.
doUnlock = false; this.lock.unlock();
this.logManager.clearBufferedLogs();
Utils.runClosureInThread(done);
return;
}
//创建一个快照存储器,用来写数据
final SnapshotWriter writer = this.snapshotStorage.create();
if (writer == null) {
Utils.runClosureInThread(done, new Status(RaftError.EIO, "Fail to create writer."));
reportError(RaftError.EIO.getNumber(), "Fail to create snapshot writer.");
return;
}
this.savingSnapshot = true;
//封装了回调方法和快照存储器
final SaveSnapshotDone saveSnapshotDone = new SaveSnapshotDone(writer, done, null);
//交给状态机来保存快照
if (!this.fsmCaller.onSnapshotSave(saveSnapshotDone)) {
Utils.runClosureInThread(done, new Status(RaftError.EHOSTDOWN, "The raft node is down."));
return;
}
this.runningJobs.incrementAndGet();
} finally {
if (doUnlock) {
this.lock.unlock();
}
}
}

doSnapshot方法首先会去进行几个校验,然后会调用状态机的onSnapshotSave方法去保存快照

FSMCallerImpl#onSnapshotSave

public boolean onSnapshotSave(final SaveSnapshotClosure done) {
//发布事件到ApplyTaskHandler中处理
return enqueueTask((task, sequence) -> {
task.type = TaskType.SNAPSHOT_SAVE;
task.done = done;
});
}

状态机的onSnapshotSave方法会将事件发布到Disruptor中,交给ApplyTaskHandler处理。

最后会调用doSnapshotSave方法进行处理

private void doSnapshotSave(final SaveSnapshotClosure done) {
Requires.requireNonNull(done, "SaveSnapshotClosure is null");
//设置最新的任期和index到metaBuilder中
final long lastAppliedIndex = this.lastAppliedIndex.get();
final RaftOutter.SnapshotMeta.Builder metaBuilder = RaftOutter.SnapshotMeta.newBuilder() //
.setLastIncludedIndex(lastAppliedIndex) //
.setLastIncludedTerm(this.lastAppliedTerm);
//设置当前配置到metaBuilder
final ConfigurationEntry confEntry = this.logManager.getConfiguration(lastAppliedIndex);
if (confEntry == null || confEntry.isEmpty()) {
LOG.error("Empty conf entry for lastAppliedIndex={}", lastAppliedIndex);
Utils.runClosureInThread(done, new Status(RaftError.EINVAL, "Empty conf entry for lastAppliedIndex=%s",
lastAppliedIndex));
return;
}
for (final PeerId peer : confEntry.getConf()) {
metaBuilder.addPeers(peer.toString());
}
if (confEntry.getOldConf() != null) {
for (final PeerId peer : confEntry.getOldConf()) {
metaBuilder.addOldPeers(peer.toString());
}
}
//设置元数据到done实例中
final SnapshotWriter writer = done.start(metaBuilder.build());
if (writer == null) {
done.run(new Status(RaftError.EINVAL, "snapshot_storage create SnapshotWriter failed"));
return;
}
//调用状态机的实例生成快照
this.fsm.onSnapshotSave(writer, done);
}

这个方法会将配置参数全部都设置到metaBuilder中,然后调用状态机实例onSnapshotSave方法,我们这里可以看官方的例子Counter 计数器示例:https://www.sofastack.tech/projects/sofa-jraft/counter-example/ ,看看是怎么使用的。

CounterStateMachine#onSnapshotSave

public void onSnapshotSave(final SnapshotWriter writer, final Closure done) {
final long currVal = this.value.get();
//异步将数据落盘
Utils.runInThread(() -> {
final CounterSnapshotFile snapshot = new CounterSnapshotFile(writer.getPath() + File.separator + "data");
if (snapshot.save(currVal)) {
if (writer.addFile("data")) {
done.run(Status.OK());
} else {
done.run(new Status(RaftError.EIO, "Fail to add file to writer"));
}
} else {
done.run(new Status(RaftError.EIO, "Fail to save counter snapshot %s", snapshot.getPath()));
}
});
}

这个方法会将数据获取之后写到文件内,然后在保存快照文件后调用传入的参数 closure.run(status) 通知调用者保存成功或者失败。

由于我们这里传入的回调实例是SaveSnapshotDone实例,所以会调用SaveSnapshotDone的run方法中:

SaveSnapshotDone

public void run(final Status status) {
Utils.runInThread(() -> continueRun(status));
} void continueRun(final Status st) {
//校验index、设置index和任期,更新状态为已保存快照完毕
final int ret = onSnapshotSaveDone(st, this.meta, this.writer);
if (ret != 0 && st.isOk()) {
st.setError(ret, "node call onSnapshotSaveDone failed");
}
if (this.done != null) {
Utils.runClosureInThread(this.done, st);
}
}

run方法会异步的调用continueRun方法,然后调用到onSnapshotSaveDone,里面校验index、设置index和任期,更新状态为已保存快照完毕。

安装快照

Jraft在发送日志到Follower的时候会判断一下需要发送快照,以便让 Follower 快速跟上 Leader 的日志进度,不再回放很早以前的日志信息,即缓解了网络的吞吐量,又提升了日志同步的效率。

Replicator#sendEntries

private boolean sendEntries(final long nextSendingIndex) {
final AppendEntriesRequest.Builder rb = AppendEntriesRequest.newBuilder();
//填写当前Replicator的配置信息到rb中
if (!fillCommonFields(rb, nextSendingIndex - 1, false)) {
// unlock id in installSnapshot
installSnapshot();
return false;
}
....//省略
}

这里会调用installSnapshot发送rpc请求给Follower

Replicator#installSnapshot

void installSnapshot() {
//正在安装快照
if (this.state == State.Snapshot) {
LOG.warn("Replicator {} is installing snapshot, ignore the new request.", this.options.getPeerId());
this.id.unlock();
return;
}
boolean doUnlock = true;
try {
Requires.requireTrue(this.reader == null,
"Replicator %s already has a snapshot reader, current state is %s", this.options.getPeerId(),
this.state);
//初始化SnapshotReader
this.reader = this.options.getSnapshotStorage().open();
//如果快照存储功能没有开启,则设置错误信息并返回
if (this.reader == null) {
final NodeImpl node = this.options.getNode();
final RaftException error = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_SNAPSHOT);
error.setStatus(new Status(RaftError.EIO, "Fail to open snapshot"));
this.id.unlock();
doUnlock = false;
node.onError(error);
return;
}
//生成一个读uri连接,给其他节点读取快照
final String uri = this.reader.generateURIForCopy();
if (uri == null) {
final NodeImpl node = this.options.getNode();
final RaftException error = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_SNAPSHOT);
error.setStatus(new Status(RaftError.EIO, "Fail to generate uri for snapshot reader"));
releaseReader();
this.id.unlock();
doUnlock = false;
node.onError(error);
return;
}
//获取从文件加载的元数据信息
final RaftOutter.SnapshotMeta meta = this.reader.load();
if (meta == null) {
final String snapshotPath = this.reader.getPath();
final NodeImpl node = this.options.getNode();
final RaftException error = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_SNAPSHOT);
error.setStatus(new Status(RaftError.EIO, "Fail to load meta from %s", snapshotPath));
releaseReader();
this.id.unlock();
doUnlock = false;
node.onError(error);
return;
}
//设置请求参数
final InstallSnapshotRequest.Builder rb = InstallSnapshotRequest.newBuilder();
rb.setTerm(this.options.getTerm());
rb.setGroupId(this.options.getGroupId());
rb.setServerId(this.options.getServerId().toString());
rb.setPeerId(this.options.getPeerId().toString());
rb.setMeta(meta);
rb.setUri(uri); this.statInfo.runningState = RunningState.INSTALLING_SNAPSHOT;
this.statInfo.lastLogIncluded = meta.getLastIncludedIndex();
this.statInfo.lastTermIncluded = meta.getLastIncludedTerm(); final InstallSnapshotRequest request = rb.build();
this.state = State.Snapshot;
// noinspection NonAtomicOperationOnVolatileField
this.installSnapshotCounter++;
final long monotonicSendTimeMs = Utils.monotonicMs();
final int stateVersion = this.version;
final int seq = getAndIncrementReqSeq();
//发起InstallSnapshotRequest请求
final Future<Message> rpcFuture = this.rpcService.installSnapshot(this.options.getPeerId().getEndpoint(),
request, new RpcResponseClosureAdapter<InstallSnapshotResponse>() { @Override
public void run(final Status status) {
onRpcReturned(Replicator.this.id, RequestType.Snapshot, status, request, getResponse(), seq,
stateVersion, monotonicSendTimeMs);
}
});
addInflight(RequestType.Snapshot, this.nextIndex, 0, 0, seq, rpcFuture);
} finally {
if (doUnlock) {
this.id.unlock();
}
}
}

在发送InstallSnapshotRequest请求之前,先会做几个校验:

  1. 校验用户是否设置配置参数类 NodeOptions 的“snapshotUri”属性,如果没有设置就不会开启快照,返回reader就为空
  2. 是否可以返回一个获取快照的uri
  3. 能否从获取从文件加载的元数据信息

    如果上面的校验都通过的话,那么就会发送一个InstallSnapshotRequest请求到Follower,交给InstallSnapshotRequestProcessor处理器处理,最后会跳转到NodeImpl的handleInstallSnapshot方法执行具体逻辑。

NodeImpl#handleInstallSnapshot

public Message handleInstallSnapshot(final InstallSnapshotRequest request, final RpcRequestClosure done) {
// 如果快照安装执行器不存在,则抛出异常不支持快照操作
if (this.snapshotExecutor == null) {
return RpcResponseFactory.newResponse(RaftError.EINVAL, "Not supported snapshot");
}
// 根据请求携带的 serverId 序列化 PeerId
final PeerId serverId = new PeerId();
if (!serverId.parse(request.getServerId())) {
LOG.warn("Node {} ignore InstallSnapshotRequest from {} bad server id.", getNodeId(),
request.getServerId());
return RpcResponseFactory.newResponse(RaftError.EINVAL, "Parse serverId failed: %s", request.getServerId());
} this.writeLock.lock();
try {
// 判断当前节点的状态
if (!this.state.isActive()) {
LOG.warn("Node {} ignore InstallSnapshotRequest as it is not in active state {}.", getNodeId(),
this.state);
return RpcResponseFactory.newResponse(RaftError.EINVAL, "Node %s:%s is not in active state, state %s.",
this.groupId, this.serverId, this.state.name());
}
// 判断 request 携带的 term 比当前节点的 trem,比较 term 的合法性
if (request.getTerm() < this.currTerm) {
LOG.warn("Node {} ignore stale InstallSnapshotRequest from {}, term={}, currTerm={}.", getNodeId(),
request.getPeerId(), request.getTerm(), this.currTerm);
return InstallSnapshotResponse.newBuilder() //
.setTerm(this.currTerm) //
.setSuccess(false) //
.build();
}
//判断当前节点leader的合法性
checkStepDown(request.getTerm(), serverId); if (!serverId.equals(this.leaderId)) {
LOG.error("Another peer {} declares that it is the leader at term {} which was occupied by leader {}.",
serverId, this.currTerm, this.leaderId);
// Increase the term by 1 and make both leaders step down to minimize the
// loss of split brain
stepDown(request.getTerm() + 1, false, new Status(RaftError.ELEADERCONFLICT,
"More than one leader in the same term."));
return InstallSnapshotResponse.newBuilder() //
.setTerm(request.getTerm() + 1) //
.setSuccess(false) //
.build();
} } finally {
this.writeLock.unlock();
}
final long startMs = Utils.monotonicMs();
try {
if (LOG.isInfoEnabled()) {
LOG.info(
"Node {} received InstallSnapshotRequest from {}, lastIncludedLogIndex={}, " +
"lastIncludedLogTerm={}, lastLogId={}.",
getNodeId(), request.getServerId(), request.getMeta().getLastIncludedIndex(), request.getMeta()
.getLastIncludedTerm(), this.logManager.getLastLogId(false));
}
// 执行快照安装
this.snapshotExecutor.installSnapshot(request, InstallSnapshotResponse.newBuilder(), done);
return null;
} finally {
this.metrics.recordLatency("install-snapshot", Utils.monotonicMs() - startMs);
}
}

这个方法进过一系列的校验后会调用快照执行器的installSnapshot执行快照安装

SnapshotExecutorImpl#installSnapshot

public void installSnapshot(final InstallSnapshotRequest request, final InstallSnapshotResponse.Builder response,
final RpcRequestClosure done) {
final SnapshotMeta meta = request.getMeta();
// 创建一个下载快照的任务对象
final DownloadingSnapshot ds = new DownloadingSnapshot(request, response, done);
//DON'T access request, response, and done after this point
//as the retry snapshot will replace this one.
// 将下载快照任务进行注册
if (!registerDownloadingSnapshot(ds)) {
LOG.warn("Fail to register downloading snapshot");
// This RPC will be responded by the previous session
return;
}
Requires.requireNonNull(this.curCopier, "curCopier");
try {
// 阻塞等待 copy 任务完成
this.curCopier.join();
} catch (final InterruptedException e) {
// 中断补偿,如果 curCopier 任务被中断过,表明有更新的 snapshot 在接受了,旧的 snapshot 被停止下载
Thread.currentThread().interrupt();
LOG.warn("Install snapshot copy job was canceled.");
return;
}
// 装载下载好的 snapshot 文件
loadDownloadingSnapshot(ds, meta);
}

这个方法会调用registerDownloadingSnapshot方法将快照进行下载注册,然后调用join方法阻塞直到下载完成,然后调用loadDownloadingSnapshot方法装载下载好的文件

SnapshotExecutorImpl#loadDownloadingSnapshot

void loadDownloadingSnapshot(final DownloadingSnapshot ds, final SnapshotMeta meta) {
SnapshotReader reader;
this.lock.lock();
try {
// 获取快照任务的结果,如果不相等则表示新的 snapshot 在接收
if (ds != this.downloadingSnapshot.get()) {
//It is interrupted and response by other request,just return
return;
}
Requires.requireNonNull(this.curCopier, "curCopier");
reader = this.curCopier.getReader();
//校验复制机状态是否正常
if (!this.curCopier.isOk()) {
if (this.curCopier.getCode() == RaftError.EIO.getNumber()) {
reportError(this.curCopier.getCode(), this.curCopier.getErrorMsg());
}
Utils.closeQuietly(reader);
ds.done.run(this.curCopier);
Utils.closeQuietly(this.curCopier);
this.curCopier = null;
this.downloadingSnapshot.set(null);
this.runningJobs.countDown();
return;
}
Utils.closeQuietly(this.curCopier);
this.curCopier = null;
//校验reader状态是否正常
if (reader == null || !reader.isOk()) {
Utils.closeQuietly(reader);
this.downloadingSnapshot.set(null);
ds.done.sendResponse(RpcResponseFactory.newResponse(RaftError.EINTERNAL,
"Fail to copy snapshot from %s", ds.request.getUri()));
this.runningJobs.countDown();
return;
}
this.loadingSnapshot = true;
this.loadingSnapshotMeta = meta;
} finally {
this.lock.unlock();
}
// 下载 snapshot 成功,进入状态机进行 snapshot 安装
final InstallSnapshotDone installSnapshotDone = new InstallSnapshotDone(reader);
// 送入状态机执行快照安装事件
if (!this.fsmCaller.onSnapshotLoad(installSnapshotDone)) {
LOG.warn("Fail to call fsm onSnapshotLoad");
installSnapshotDone.run(new Status(RaftError.EHOSTDOWN, "This raft node is down"));
}
}

在进行各种校验之后会调用到状态机的onSnapshotLoad方法,执行快照安装

FSMCallerImpl#onSnapshotLoad

public boolean onSnapshotLoad(final LoadSnapshotClosure done) {
return enqueueTask((task, sequence) -> {
task.type = TaskType.SNAPSHOT_LOAD;
task.done = done;
});
}

onSnapshotLoad方法会发送一个状态为TaskType.SNAPSHOT_LOAD任务到Disruptor队列中,然后会ApplyTaskHandler中处理,最后调用到doSnapshotLoad方法中进行处理。

FSMCallerImpl#doSnapshotLoad

private void doSnapshotLoad(final LoadSnapshotClosure done) {
....//省略
if (!this.fsm.onSnapshotLoad(reader)) {
done.run(new Status(-1, "StateMachine onSnapshotLoad failed"));
final RaftException e = new RaftException(EnumOutter.ErrorType.ERROR_TYPE_STATE_MACHINE,
RaftError.ESTATEMACHINE, "StateMachine onSnapshotLoad failed");
setError(e);
return;
}
....//省略
done.run(Status.OK());
}

doSnapshotLoad方法最后调用到状态机的实现的onSnapshotLoad方法上,我们这里以CounterStateMachine为例:

CounterStateMachine#onSnapshotLoad

public boolean onSnapshotLoad(final SnapshotReader reader) {
if (isLeader()) {
LOG.warn("Leader is not supposed to load snapshot");
return false;
}
if (reader.getFileMeta("data") == null) {
LOG.error("Fail to find data file in {}", reader.getPath());
return false;
}
final CounterSnapshotFile snapshot = new CounterSnapshotFile(reader.getPath() + File.separator + "data");
try {
this.value.set(snapshot.load());
return true;
} catch (final IOException e) {
LOG.error("Fail to load snapshot from {}", snapshot.getPath());
return false;
} }

onSnapshotLoad方法会将文件内容加载出来然后将值设置到value中,这就表示数据加载完毕了。

9. SOFAJRaft源码分析— Follower如何通过Snapshot快速追上Leader日志?的更多相关文章

  1. 3. SOFAJRaft源码分析— 是如何进行选举的?

    开篇 在上一篇文章当中,我们讲解了NodeImpl在init方法里面会初始化话的动作,选举也是在这个方法里面进行的,这篇文章来从这个方法里详细讲一下选举的过程. 由于我这里介绍的是如何实现的,所以请大 ...

  2. 6. SOFAJRaft源码分析— 透过RheaKV看线性一致性读

    开篇 其实这篇文章我本来想在讲完选举的时候就开始讲线性一致性读的,但是感觉直接讲没头没尾的看起来比比较困难,所以就有了RheaKV的系列,这是RheaKV,终于可以讲一下SOFAJRaft的线性一致性 ...

  3. 8. SOFAJRaft源码分析— 如何实现日志复制的pipeline机制?

    前言 前几天和腾讯的大佬一起吃饭聊天,说起我对SOFAJRaft的理解,我自然以为我是很懂了的,但是大佬问起了我那SOFAJRaft集群之间的日志是怎么复制的? 我当时哑口无言,说不出是怎么实现的,所 ...

  4. 4. SOFAJRaft源码分析— RheaKV初始化做了什么?

    前言 由于RheaKV要讲起来篇幅比较长,所以这里分成几个章节来讲,这一章讲一讲RheaKV初始化做了什么? 我们先来给个例子,我们从例子来讲: public static void main(fin ...

  5. 2. SOFAJRaft源码分析—JRaft的定时任务调度器是怎么做的?

    看完这个实现之后,感觉还是要多看源码,多研究.其实JRaft的定时任务调度器是基于Netty的时间轮来做的,如果没有看过Netty的源码,很可能并不知道时间轮算法,也就很难想到要去使用这么优秀的定时调 ...

  6. 7. SOFAJRaft源码分析—如何实现一个轻量级的对象池?

    前言 我在看SOFAJRaft的源码的时候看到了使用了对象池的技术,看了一下感觉要吃透的话还是要新开一篇文章来讲,内容也比较充实,大家也可以学到之后运用到实际的项目中去. 这里我使用Recyclabl ...

  7. Android源码分析(一)-----如何快速掌握Android编译文件

    一 : Android.mk文件概述 主要向编译系统指定相应的编译规则.会被解析一次或多次.因此尽量减少源码中声明变量,因为这些变量可能会被多次定义从而影响到后面的解析.这个文件的语法会把源代码组织成 ...

  8. 1. SOFAJRaft源码分析— SOFAJRaft启动时做了什么?

    我们这次依然用上次的例子CounterServer来进行讲解: 我这里就不贴整个代码了 public static void main(final String[] args) throws IOEx ...

  9. 5. SOFAJRaft源码分析— RheaKV中如何存放数据?

    概述 上一篇讲了RheaKV是如何进行初始化的,因为RheaKV主要是用来做KV存储的,RheaKV读写的是相当的复杂,一起写会篇幅太长,所以这一篇主要来讲一下RheaKV中如何存放数据. 我们这里使 ...

随机推荐

  1. Libevent:7Bufferevents基本概念

    很多时候,应用程序除了能响应事件之外,还希望能够处理一定量的数据缓存.比如,当写数据的时候,一般会经历下列步骤: l  决定向一个链接中写入一些数据:将数据放入缓冲区中: l  等待该链接变得可写: ...

  2. SQL Server —— 主键和外键

    一.定义 1.1.什么是主键和外键 关系型数据库中的一条记录中有若干个属性,若其中某一个属性组(注意是组)能唯一标识一条记录,该属性组就可以成为一个主键. 比如: 学生表(学号,姓名,性别,班级)其中 ...

  3. 远程控制工具&&驱动安装仍然没有声音

    1. 2.下面是一个远程控制工具 TeamViewer

  4. 中国联通与阿里云达成合作,推动5G+新媒体产业发展

    4月24日在中国联通合作伙伴大会上,阿里云与中国联通签署合作协议,未来双方将基于各自优势,聚焦5G时代下的超高清视频发展. 随着5G时代到来,视频不再被网速制约,超短延时.计算节点下沉等特性将更高清. ...

  5. LeetCode80 Remove Duplicates from Sorted Array II

    题目: Follow up for "Remove Duplicates":What if duplicates are allowed at most twice? (Mediu ...

  6. 详解Python中内置的NotImplemented类型的用法

    它是什么? ? 1 2 >>> type(NotImplemented) <type 'NotImplementedType'> NotImplemented 是Pyth ...

  7. Project Euler Problem 23-Non-abundant sums

    直接暴力搞就行,优化的地方应该还是计算因子和那里,优化方法在这里:http://www.cnblogs.com/guoyongheng/p/7780345.html 这题真坑,能被写成两个相同盈数之和 ...

  8. H3C 常用信息查看命令

  9. Pytorch实现MNIST(附SGD、Adam、AdaBound不同优化器下的训练比较) adabound实现

     学习工具最快的方法就是在使用的过程中学习,也就是在工作中(解决实际问题中)学习.文章结尾处附完整代码. 一.数据准备  在Pytorch中提供了MNIST的数据,因此我们只需要使用Pytorch提供 ...

  10. poj 2689 Prime Distance (素数二次筛法)

    2689 -- Prime Distance 没怎么研究过数论,还是今天才知道有素数二次筛法这样的东西. 题意是,要求求出给定区间内相邻两个素数的最大和最小差. 二次筛法的意思其实就是先将1~sqrt ...