最近WPaxos框架已经开源,WPaxos开源地址。作为旁观者之一,大致简单的梳理一下整体的代码结构,做一个源码浅析,后面有时间再对每一部分做深入的分析。

Paxos算法是一个强一致性的算法,最精简的描述应当是Paxos原作者在《Paxos Made Simple》中的描述:

Phase 1

(a) A proposer selects a proposal number n and sends a prepare request with number n to a majority of acceptors.

(b) If an acceptor receives a prepare request with number n greater than that of any prepare request to which it has already responded, then it responds to the request with a promise not to accept any more proposals numbered less than n and with the highest-numbered pro-posal (if any) that it has accepted.

Phase 2

(a) If the proposer receives a response to its prepare requests (numbered n) from a majority of acceptors, then it sends an accept request to each of those acceptors for a proposal numbered n with a value v , where v is the value of the highest-numbered proposal among the responses, or is any value if the responses reported no proposals.

(b) If an acceptor receives an accept request for a proposal numbered n, it accepts the proposal unless it has already responded to a prepare request having a number greater than n.

在Paxos算法中主要有三个角色,proposer、acceptor、leaner,其中learner是不参与协商过程,主要是proposer与acceptor交互。简单来说整个协商过程分为两个阶段:

Prepare阶段:

由propose发起提议,提议要携带一个全局递增的编号n,该提议并不携带提议内容,只是有编号n。当acceptor接受到该提议时,如果是第一次收到提议或者接收到的提议比自身已经接受的提议编号大,则接受该提议,设置接受的最大提议编号n,并不再接受所有小于等于n的提议编号,回复给proposer接受。否则的话,回复拒绝该提议。

Accept阶段:

当proposer接收到超过半数节点同意该提议,propose正式发起提议[n,value]。同样acceptor收到该提议时,如果是第一次收到提议或者接收到的提议比自身已经接受的提议编号大,则接受该提议,设置接受的最大提议编号n,并不再接受所有小于等于n的提议编号,回复给proposer接受。否则的话,回复拒绝该提议。

太多经典的原理讲解,这里从工程实现,来简单理解paxos算法过程和原理。

一切的propose的源头来源于如下代码:

public int newValue(byte[] value) {if(this.canSkipPrepare && !this.wasRejectBySomeone) {accept();} else {prepare(this.wasRejectBySomeone);}return 0;
}

在工程实现中,如果当前节点发起过propose,即已经执行过prepare并且被接受了,就可以跳过prepare阶段直接进行accept阶段,因为每个阶段都会有网络消耗,所以为了减少网络消耗,在保证paxos算法核心原理的情况下,有条件的跳过了prepare阶段。

Prepare阶段如下代码:

public void prepare(boolean needNewBallot) {exitAccept();this.isPreparing = true;		this.canSkipPrepare = false;this.wasRejectBySomeone = false;	this.proposerState.resetHighestOtherPreAcceptBallot();if(needNewBallot) {this.proposerState.newPrepare();}PaxosMsg paxosMsg = new PaxosMsg();paxosMsg.setMsgType(PaxosMsgType.paxosPrepare.getValue());paxosMsg.setInstanceID(getInstanceID());paxosMsg.setNodeID(this.pConfig.getMyNodeID());paxosMsg.setProposalID(this.proposerState.getProposalID());this.msgCounter.startNewRound();// 将当前的 prepare 加入到定时器当中去addPrepareTimer(0);// 广播给所有的节点尝试 prepare int runSelfFirst = BroadcastMessageType.BroadcastMessage_Type_RunSelf_First.getType();int sendType = MessageSendType.UDP.getValue();paxosMsg.setTimestamp(System.currentTimeMillis());broadcastMessage(paxosMsg, runSelfFirst, sendType);
}

可以看到,prepare首先是设置当前节点状态,即设置为prepare状态。如果之前被其他节点拒绝过,则需要重新设置提议的编号,被其他节点拒绝是因为有其他节点发起的提议编号要大于自己发起的提议编号,而且可能会成功,如果再用相同的提议编号,则还会被其他节点拒绝。所以这里设置新的提议编号如代码:

public void newPrepare() {long maxProposalId = this.proposalID > this.highestOtherProposalID ? this.proposalID : this.highestOtherProposalID;this.proposalID = maxProposalId + 1;
}

即再次发起prepare时候的提议编号一定要大于已经知晓的最大提议编号,即原提议编号与已知晓最大提议编号最大值再加1。

然后对这一次prepare增加超时任务,因为网络延迟等原因,prepare可能会长时间没有结果,所以要对每次prepare增加一个定时检测任务,当超过超时时间后,对该次prepare重新处理。处理的方式相对比较简单,就是重新执行一次prepare。

最后把这一次propose的内容首先由自己处理,然后再发送给其他节点。即设置的RunSelf_First标志所起的作用。

当节点收到prepare消息后的处理逻辑如代码:

public int onPrepare(PaxosMsg paxosMsg) {PaxosMsg replyPaxosMsg = new PaxosMsg();replyPaxosMsg.setInstanceID(getInstanceID());replyPaxosMsg.setNodeID(this.pConfig.getMyNodeID());replyPaxosMsg.setProposalID(paxosMsg.getProposalID());replyPaxosMsg.setMsgType(PaxosMsgType.paxosPrepareReply.getValue());BallotNumber ballot = new BallotNumber(paxosMsg.getProposalID(), paxosMsg.getNodeID());BallotNumber pbn = this.acceptorState.getPromiseBallot();if(ballot.ge(pbn)) {int ret = updateAcceptorState4Prepare(replyPaxosMsg, ballot);if(ret != 0) return ret;} else {replyPaxosMsg.setRejectByPromiseID(this.acceptorState.getPromiseBallot().getProposalID());}long replyNodeId = paxosMsg.getNodeID();sendMessage(replyNodeId, replyPaxosMsg);return 0;
}

可以看到这里就是paxos算法保证一致性的关键点,即只有收到的提议编号大于自身已经接受的提议编号,才会更新自身已经接受的提议编号,否则拒绝这一次提议,并告知自己已经接受的提议编号。然后回复消息给发起提议的节点。

接下来看一下收到回复后的处理逻辑,如代码:

public void onPrepareReply(PaxosMsg paxosMsg) {if(!this.isPreparing) {return ;}if(paxosMsg.getProposalID() != this.proposerState.getProposalID()) {return ;}this.msgCounter.addReceive(paxosMsg.getNodeID());if(paxosMsg.getRejectByPromiseID() == 0) {this.msgCounter.addPromiseOrAccept(paxosMsg.getNodeID());this.proposerState.addPreAcceptValue(ballot, paxosMsg.getValue());	} else {this.msgCounter.addReject(paxosMsg.getNodeID());this.wasRejectBySomeone = true;this.proposerState.setOtherProposalID(paxosMsg.getRejectByPromiseID());}if(this.msgCounter.isPassedOnThisRound()) {int useTimeMs = this.timeStat.point();this.canSkipPrepare = true;accept();} else if(this.msgCounter.isRejectedOnThisRound() || this.msgCounter.isAllReceiveOnThisRound()) {addPrepareTimer(OtherUtils.fastRand() % 30 + 10);}
}

如果当前节点已经不在prepare阶段了,则直接返回。因为只需要接收到超过半数节点的同意,就可以认为这次prepare成功,可以进入accept阶段了,所以返回慢的节点的消息就可以忽略了。如果返回的提议的编号和当前节点发起提议的编号不一致,也直接返回。如果接受到的是同意该提议,则统计同意的数量,并且要更新接受到的提议编号和提议值。如果是接受到的是拒绝该提议,则设置这次提议被其他节点拒绝过,更新拒绝节点所承诺的提议编号,目的是设置当这一次prepare被拒绝之后,再一次发起prepare时重新选择提议编号时的最大提议编号值。最后判断这一次prepare是否通过,即同意的节点数量是否超过半数,如果超过半数,则设置可以跳过prepare阶段的标识,然后进入accept阶段。如果被拒绝后,则在一段时间后重新发起prepare。

接下来看accept阶段,如下代码:

public void accept() {exitPrepare();this.isAccepting = true;PaxosMsg paxosMsg = new PaxosMsg();paxosMsg.setMsgType(PaxosMsgType.paxosAccept.getValue());paxosMsg.setInstanceID(getInstanceID());paxosMsg.setNodeID(this.pConfig.getMyNodeID());paxosMsg.setProposalID(this.proposerState.getProposalID());paxosMsg.setValue(this.proposerState.getValue());paxosMsg.setLastChecksum(getLastChecksum());this.msgCounter.startNewRound();addAcceptTimer(0);int runSelfFirst = BroadcastMessageType.BroadcastMessage_Type_RunSelf_Final.getType();int sendType = MessageSendType.UDP.getValue();paxosMsg.setTimestamp(System.currentTimeMillis());broadcastMessage(paxosMsg, runSelfFirst, sendType);
}

可见逻辑是一样的,首先是退出prepare阶段,并设置当前节点处于accept阶段,同样设置accept过期任务,因为accept同样也会因为网络等原因,导致无法结束或很长时间无法结束,所以增加一个过期任务,当超过时间阈值后,重新进入prepare阶段,而不再是accept阶段。接着发送消息到所有的节点上。注意这里的消息内容与prepare阶段不太一样,在prepare阶段的消息并没有携带消息内容,而accept阶段携带了消息内容,与前面原理内容一致。

当节点接收到accept消息后,逻辑与收到prepare几乎一样,也是判断当前承诺的提议编号是否小于接收到的提议编号,则保存这一次提议,这里保存消息就涉及到了前面所说的消息存储,这里会保存实际的消息内容以及索引文件,为了提高写的效率,这里写消息内容是顺序追加写,即使这一次提议最终没有成功,也不会做修改。但是写索引却是根据instanceId值计算好待写入的位置,可以覆盖已写的内容。否则回复拒绝。然后回复消息,当接受到回复的消息后,处理逻辑如代码:

public void onAcceptReply(PaxosMsg paxosMsg) {if(!this.isAccepting) {return ;}if(paxosMsg.getProposalID() != this.proposerState.getProposalID()) {return ;}this.msgCounter.addReceive(paxosMsg.getNodeID());if(paxosMsg.getRejectByPromiseID() == 0) {this.msgCounter.addPromiseOrAccept(paxosMsg.getNodeID());} else {this.msgCounter.addReject(paxosMsg.getNodeID());this.wasRejectBySomeone = true;this.proposerState.setOtherProposalID(paxosMsg.getRejectByPromiseID());}if(this.msgCounter.isPassedOnThisRound()) {int useTimeMs = this.timeStat.point();exitAccept();this.learner.proposerSendSuccess(getInstanceID(), this.proposerState.getProposalID());} else if(this.msgCounter.isRejectedOnThisRound()|| this.msgCounter.isAllReceiveOnThisRound()) {addAcceptTimer(OtherUtils.fastRand() % 30 +10);}
}

同样也是先判断节点状态和提议编号是否相同,如果接收到同意,则统计同意的节点数量。如果接收到拒绝,同样设置当前节点被拒绝状态,然后设置被拒绝的提议编号。当超过半数节点拒绝的时候,在一定时间后重新发起prepare。当超过半数节点同意后,发起propose成功的操作proposerSendSuccess。当每个节点收到SendSuccess消息后,会更新内存中的值,不需要再写文件,因为在accept阶段已经写过。然后再执行状态机。全部执行完后,增加instanceId,重置所有阶段承诺的提议编号。

这里可能会有疑问,如果发起提议的节点状态机执行失败了还怎么保证消息的一致性。其实可以看到这里发起proposerSendSuccess的时候,首先是发送给了本节点,只有当本节点执行成功后,才会再发送给其他节点,这就保证了发送提议的节点一定会执行成功。当其他节点执行失败时,又怎么办?这就解涉及到了Learn的内容,即当其他节点执行失败时,对应的instanceId不会增加,具体可以看收到proposerSendSuccess的操作,如代码:

public void onProposerSendSuccess(PaxosMsg paxosMsg) {if(paxosMsg.getInstanceID() != getInstanceID()) {return ;}if(this.acceptor.getAcceptorState().getAcceptedBallot().getProposalID() == 0) {return ;}BallotNumber ballot = new BallotNumber(paxosMsg.getProposalID(), paxosMsg.getNodeID());BallotNumber thisBallot = this.acceptor.getAcceptorState().getAcceptedBallot();if(thisBallot.getProposalID() != ballot.getProposalID() || thisBallot.getNodeId() != ballot.getNodeId()) {return ;}this.learnerState.learnValueWithoutWrite(paxosMsg.getInstanceID(), this.acceptor.getAcceptorState().getAcceptedValue(), this.acceptor.getAcceptorState().getCheckSum());transmitToFollower();}

可以看到,因为执行失败的时候,instanceId不会增加,在代码第二行会比较,如果当前的instanceId不等于消息的instanceId,则会直接返回,这就保证了所有节点的instanceId会保证一致。那么如果节点落后了,落后节点就不会在参与到提议过程中了,此时会通过Learn机制学习落后的instanceId,当追齐后重新参与到提议过程中。

在启动paxos,会启动定时任务,定时的发送learn请求到每个节点,当收到learn请求之后处理逻辑如代码:

public void onAskforLearn(PaxosMsg paxosMsg) {setSeenInstanceID(paxosMsg.getInstanceID(), paxosMsg.getNodeID());if(paxosMsg.getProposalNodeID() == this.pConfig.getMyNodeID()) {this.pConfig.addFollowerNode(paxosMsg.getNodeID());}if(paxosMsg.getInstanceID() >= getInstanceID()) return;if(paxosMsg.getInstanceID() >= this.checkpointMgr.getMinChosenInstanceID()) {if(!this.learnerSender.prepare(paxosMsg.getInstanceID(), paxosMsg.getNodeID())) {if(paxosMsg.getInstanceID() == (getInstanceID() - 1)) {//send one valueAcceptorStateData state = new AcceptorStateData();int ret = this.paxosLog.readState(this.pConfig.getMyGroupIdx(), paxosMsg.getInstanceID(), state);if(ret == 0) {BallotNumber ballot = new BallotNumber(state.getPromiseID(), state.getAcceptedNodeID());sendLearnValue(paxosMsg.getNodeID(), paxosMsg.getInstanceID(), ballot, state.getAcceptedValue(), 0, false);}}return ;}}sendNowInstanceID(paxosMsg.getInstanceID(), paxosMsg.getNodeID());}

首先是设置可以看见的最大的instanceId,这个值在处理消息收到的prepare和accept消息时,如果消息的instanceId大于自身的的instanceId,并且大于这里设置的值,则会把这个消息放到重试队列中。

接着是如果自身没有可以学习的内容,就直接返回。如果有学习的数据,即请求的instanceId大于节点最小的instanceId,则判断是否已经在处理其他节点的learn请求,如果没有,则通过learnSender发送一些信息给对方节点。如果只差了一个instanceId,则直接把差的这个intanceId内容发送给请求节点即可,不需要再走learnSender。

接下来具体看发送了什么内容,如代码:

public void sendNowInstanceID(long instanceID, long sendNodeId) {PaxosMsg msg = new PaxosMsg();msg.setInstanceID(instanceID);msg.setNodeID(this.pConfig.getMyNodeID());msg.setMsgType(PaxosMsgType.paxosLearnerSendNowInstanceID.getValue());msg.setNowInstanceID(getInstanceID());msg.setMinchosenInstanceID(this.checkpointMgr.getMinChosenInstanceID());if(getInstanceID() - instanceID > 50) {//instanceid too close not need to send vsm/master checkpoint.byte[] systemVariablesCPBuffer = this.pConfig.getSystemVSM().getCheckpointBuffer();msg.setSystemVariables(systemVariablesCPBuffer);if(this.pConfig.getMasterSM() != null) {byte[] masterVariableCPBuffer = this.pConfig.getMasterSM().getCheckpointBuffer();msg.setMasterVariables(masterVariableCPBuffer);}}sendMessage(sendNodeId, msg);}

发送了自身节点信息,现在的instanceId,最小的instanceId,和请求的instanceId。如果instanceId相差超过50,则认为两个节点信息不一致,携带当前节点的master信息和成员信息给请求节点。

当请求节点收到回复信息后,处理逻辑如代码:

public void onSendNowInstanceID(PaxosMsg paxosMsg) {long receiveNowInstanceId = paxosMsg.getNowInstanceID();long currInstanceId = this.getInstanceID();setSeenInstanceID(receiveNowInstanceId, paxosMsg.getNodeID());UpdateCpRet updateCpRet = this.pConfig.getSystemVSM().updateByCheckpoint(paxosMsg.getSystemVariables());int ret = updateCpRet.getRet();boolean isChanged = updateCpRet.isChange();if(ret == 0 && isChanged) {return ;}if(this.pConfig.getMasterSM() != null) {UpdateCpRet masterUpdateCpRet = this.pConfig.getMasterSM().updateByCheckpoint(paxosMsg.getMasterVariables());ret = masterUpdateCpRet.getRet();boolean isMasterChanged = masterUpdateCpRet.isChange();if(ret == 0 && isMasterChanged) {logger.warn("MasterVariables changed!");}}if(paxosMsg.getInstanceID() != getInstanceID()) {			return ;}		if(paxosMsg.getNowInstanceID() <= getInstanceID()) {return ;}if(paxosMsg.getMinChosenInstanceID() > getInstanceID()) {askforCheckpoint(paxosMsg.getNodeID());} else if(!this.isIMLearning) {comfirmAskForLearn(paxosMsg.getNodeID());}}

首先同样是更新seenInstanceId,然后根据接收到的信息更新master与成员信息,如果与本地不一致,则直接返回。在判断当前的instanceId与发起learn请求时发送的instanceId是否还相同,如果不相同,则认为已经从别的节点学习了,也直接返回。如果其他节点的最小instanceId都要大于自己的instanceId了,那么就进入checkpoint模式。否则回复节点开始学习。当节点收到确认信息之后,正式启动learnSender开始发送内容。如代码:

public void sendLearnedValue(long beginInstanceID, long sendToNodeID) {long sendInstanceId = beginInstanceID;int ret = 0;//control send speed to avoid affecting the network too much.int sendQps = InsideOptions.getInstance().getLearnerSenderSendQps();int sleepMs = sendQps > 1000 ? 1 : 1000 / sendQps;int sendInterval = sendQps > 1000 ? sendQps / 1000 + 1 : 1;int sendCount = 0;JavaOriTypeWrapper<Integer> lastCheckSumWrap = new JavaOriTypeWrapper<Integer>(0);while(sendInstanceId < this.learner.getInstanceID()) {ret = sendOne(sendInstanceId, sendToNodeID, lastCheckSumWrap);if(ret != 0) {return ;}if(!checkAck(sendInstanceId)) return ;sendCount ++;sendInstanceId ++;releshSending();if(sendCount >= sendInterval) {sendCount = 0;Time.sleep(sleepMs);}}//succ send, reset ack lead.this.ackLead = InsideOptions.getInstance().getLearnerSenderAckLead();}

逻辑很简单,做了一下限速,防止发送速度过快占用网络资源,影响正常的通信。这里注意看,只发送到当前节点instanceId的前一条,这是因为当前这个instanceId可能处于提案的过程中,已经被选定,但是并没有完成。

发送学习内容如代码:

public int sendOne(long sendInstanceID, long sendToNodeID, JavaOriTypeWrapper<Integer> lastChecksum) {AcceptorStateData state = new AcceptorStateData();int ret = this.paxosLog.readState(this.config.getMyGroupIdx(), sendInstanceID, state);if(ret != 0) {return ret;}byte[] sendValue = state.getAcceptedValue();if(sendValue != null && sendValue.length > 0) {limiter.acquire(sendValue.length);}BallotNumber ballot = new BallotNumber(state.getPromiseID(), state.getAcceptedNodeID());ret = this.learner.sendLearnValue(sendToNodeID, sendInstanceID, ballot, state.getAcceptedValue(), lastChecksum.getValue(), true);lastChecksum.setValue(state.getCheckSum());return ret;}

根据instanceId读取内容,然后发送给学习节点。发送完后,节点暂停learnSender。

学习过程讲述完毕,前面还留了一个内容checkpoint模式。Checkpoint是paxos中重要的一个组成部分,为了防止数据文件无限的增长,肯定要定期的清理无用的数据内容。那么哪些数据可以被删除就是有checkpoint所决定。Checkpoint有三个重要的数据,minChosenInstanceId,maxChosenInstanceId,checkpointInstanceId。

minChosenInstanceId表示当前group的最小的instanceId,maxChosenInstanceId表示当前选定的最大的instanceId,checkpointInstanceId表示已经归档完毕的最大instanceId,checkpointInstanceId前面的数据都可以删除。当节点重启恢复的时候,则从checkpointInstanceId开始恢复即可。

讲述checkPoint之前还需要理解paxos中一个重要的概念——状态机。状态机可以看成是paxos数据的消费逻辑,当每次议案通过并保存成功后,都会交给状态机来执行。具体的执行逻辑由使用方自行定义。Paoxs中也定义了一些默认的状态机。paxos算法中会有多种类型的状态机同时存在,但是一份paxos数据只会被一种状态机执行,并且每个节点执行状态机后状态及数据要一致。状态机具体的方法如代码:

public interface StateMachine {public int getSMID();public boolean execute(int groupIdx, long instanceID, byte[] paxosValue, SMCtx smCtx);public boolean executeForCheckpoint(int groupIdx, long instanceID, byte[] paxosValue);public long getCheckpointInstanceID(int groupIdx);public int lockCheckpointState();public int getCheckpointState(int groupIdx, JavaOriTypeWrapper<String> dirPath, List<String> fileList); public void unLockCheckpointState();public int loadCheckpointState(int groupIdx, String checkpointTmpFileDirPath, List<String> fileList, long checkpointInstanceID);public byte[] beforePropose(int groupIdx, byte[] sValue);public boolean needCallBeforePropose();public void fixCheckpointByMinChosenInstanceId(long minChosenInstanceID);
}

依次看每个方法的作用:

getSMID:返回每个状态机唯一标识,每次提交议案的时候都会携带一个SMID,表示最终由那个状态机来执行。

execute:执行状态机的方法

executeForCheckpoint:在执行checkpoint时的方法,会面讲述checkpoint时会用到

getCheckpointInstanceIdD:返回当前状态机的checkpointInstanceID,在后面讲述checkpoint会用到,在发送learn数据和、Replayer和Cleaner时会频繁调用

lockCheckpointState和unLockCheckpointState:在checkpoing模式下,发送checkpoint文件会使用,锁定checkpoint文件与解锁。

getCheckpointState和loadCheckpointState:在checkpint模式下,发送方获取checkpoint文件与接收方加载恢复checkpoint文件

beforeProcess和needCallBeforeProcess:状态机器的前置执行方法

fixCheckpointByMinchosenInstanceID:根据minChosenInstanceID修复checkpointInstanceID

接着看checkpoint,整个checkpoint有两个关键线程,Replayer和Cleaner,Replayer是用来做checkpoint执行的,只有被Replayer执行过的instanceId才可以被删除。Cleaner就是清除数据的。

Replayer代码:

public void run() {long instanceId = this.smFac.getCheckpointInstanceID(this.config.getMyGroupIdx()) + 1;while(true) {try {if(this.isEnd) {logger.debug("Checkpoint.Replayer [END]");return ;}if(!this.canRun) {this.isPaused = true;Time.sleep(1000);continue ;}if(instanceId >= this.checkpointMgr.getMaxChosenInstanceID()) {Time.sleep(1000);continue ;}boolean playRet = playOne(instanceId);if(playRet) {logger.info("Play one done, instanceid [" + instanceId + "]");instanceId ++;} else {logger.info("Play one fail, instanceid [" + instanceId + "]");Time.sleep(500);}} catch (Exception e) {logger.error("run throws exception", e);}}}

首先通过状态机获取到当前的checkpoint位置,满足条件后对每个instanceId执行checkpoint。如代码:

 public boolean playOne(long instanceID) {AcceptorStateData state = new AcceptorStateData();int ret = this.paxosLog.readState(this.config.getMyGroupIdx(), instanceID, state);if(ret != 0) return false;boolean executeRet = this.smFac.executeForCheckpoint(this.config.getMyGroupIdx(), instanceID, state.getAcceptedValue());if(!executeRet) {logger.error("Checkpoint sm excute fail, instanceid [" + instanceID + "]");}return executeRet;}

根据instanceId读取内容,然后让对应的状态机执行checkpoint。

Cleaner如代码:

 public void run() {this.isStart = true;toContinue();int deleteQps = InsideOptions.getInstance().getCleanerDeleteQps();int sleepMs = deleteQps > 1000 ? 1 : 1000 / deleteQps;int deleteInterval = deleteQps > 1000 ? deleteQps / 1000 + 1 : 1;while(true) {try {if(this.isEnd) {logger.info("Checkpoint.Cleaner [END]");return ;}if(!this.canRun) {this.isPaused = true;Time.sleep(1000);continue ;}long instanceId = this.checkpointMgr.getMinChosenInstanceID();long cpInstanceId = this.smFac.getCheckpointInstanceID(this.config.getMyGroupIdx()) + 1;long maxChosenInstanceId = this.checkpointMgr.getMaxChosenInstanceID();int deleteCount = 0;while((instanceId + this.holdCount < cpInstanceId) && (instanceId + this.holdCount < maxChosenInstanceId)) {boolean deleteRet = deleteOne(instanceId);if(deleteRet) {instanceId ++;deleteCount ++;if(deleteCount >= deleteInterval) {deleteCount = 0;Time.sleep(sleepMs);}} else {logger.warn("delete system fail, instanceid [" + instanceId + "]");break;}}persistMinChosenInstanceID(instanceId);Time.sleep(OtherUtils.fastRand() % 500 + 500);} catch (Exception e) {logger.error("run throws exception", e);}}}

这里有一个holdCount,只有当前的instanceId+holdCount<checkpoint,才可以删除,这是为了前面所讲Learn所用,防止有节点落后时,可以快速学习,而不用进入checkpoint模式。这里删除之后,更新minInstanceId。那么整个过程串联起来就是,每次执行提议后,更新maxInstanceId,由Replayer定时校验当前的checkpoint是否小于maxInstanceId,如果小于则执行状态记得checkpoint操作,更新checkpoint。Cleaner定时清理已经执行过checkpoint操作的instanceId,定时检测minInstanceId+holdCount是否小于checkpoint,如果小于则删除instanceId,并更新minInstanceId。

Checkpoint描述完毕,接着看前面Lean阶段遗漏的checkpoint模式是做什么的。当发现自己的instanceId已经小于其他节点minInstanceId了,则进入checkpoint模式,发送askForCheckpoint到所有的节点上,当节点接收到该请求后,如果没有处于方checkpoint模式中,则开启CheckpointSender线程,如代码:

public void run() {try {this.isStarted = true;this.absLastAckTime = Time.getSteadyClockMS();//pause checkpoint replayerboolean needContinue = false;while(!this.checkpointMgr.getRelayer().isPaused()) {if(this.isEnd) {this.isEnded = true;return ;}needContinue = true;this.checkpointMgr.getRelayer().pause();logger.debug("wait replayer paused.");Time.sleep(100);}int ret = lockCheckpoint();if(ret == 0) {try {sendCheckpoint();} finally {unlockCheckpoint();}}//continue checkpoint replayerif(needContinue) {this.checkpointMgr.getRelayer().toContinue();}logger.info("Checkpoint.Sender [END]");} catch (Exception e) {logger.error("CheckpointSender run error", e);} finally {this.isEnded = true;}}

首先是关闭replayer线程,防止发送checkpoint时又对其进行更新。然后对checkpoint进行锁定,这里会执行每个状态机的lockCheckpoint方法,锁定checkpoint文件。发送checkpoint文件分为三个状态,begin、ing、end。节点收到begin时处理逻辑为清除当前节点的所有日志文件,包括索引,设置minInstanceId为接收到的节点的minInstanceId,设置学习节点为目标节点。发送ing逻辑如代码:

public int sendCheckpointFaSM(StateMachine sm) {JavaOriTypeWrapper<String> dirPath = new JavaOriTypeWrapper<String>();List<String> fileList = new ArrayList<String>();int ret = sm.getCheckpointState(this.config.getMyGroupIdx(), dirPath, fileList);if(ret != 0) {logger.error("GetCheckpointState fail ret [" + ret +"], smid [" + sm.getSMID() + "]");return -1;}String oriDirPath = dirPath.getValue();if(oriDirPath == null || oriDirPath.length() == 0) {logger.info("No Checkpoint, smid [" + sm.getSMID() + "]");return 0;}if(!oriDirPath.endsWith("/")) {oriDirPath += "/";}for(String filePath : fileList) {if(filePath == null || "".equals(filePath)) continue;ret = sendFile(sm, oriDirPath, filePath);if(ret != 0) {logger.error("SendFile fail, ret " + ret + " smid " + sm.getSMID());return -1;}}logger.info("END, send ok, smid [" + sm.getSMID() + "] filelistcount [" + fileList.size() + "]");return 0;}

会通过状态的getCheckpointState方法获取到checkpoint文件的位置,然后发送文件给学习节点。当学习节点收到ing状态时,处理逻辑代码:

public int receiveCheckpoint(CheckpointMsg checkpointMsg) {if(checkpointMsg.getNodeID() != this.sendNodeID || checkpointMsg.getUuid() != this.uuid) {return -2;}if(checkpointMsg.getSequenceID() == this.sequenceID) {return 0;}if(checkpointMsg.getSequenceID() != this.sequenceID + 1) {return -2;}String fileDir = getTmpDirPath(checkpointMsg.getSmID());JavaOriTypeWrapper<String> ffpWrap = new JavaOriTypeWrapper<String>();initFilePath(fileDir, ffpWrap);String filePath = fileDir + "/" + checkpointMsg.getFilePath();File file = new File(filePath);FileOutputStream out = null;try {if(!file.exists()) {file.createNewFile();}file.setReadable(true);file.setWritable(true);if(file.length() != checkpointMsg.getOffset()) {return -2;}out = new FileOutputStream(file, true);out.write(checkpointMsg.getBuffer());out.flush();this.sequenceID ++;} catch (Exception e) {logger.error("receiveCheckpoint error", e);return -2;} finally {if(out != null) {try {out.close();} catch (Exception e) {logger.error("receiveCheckpoint close file error", e);}}}return 0;}

为了防止占用网络,每次只会发一部分文件,所以每次发送都会携带一个递增的序号。当接受到消息后,会进行一系列判断,包括收到的节点是否是前面记录的节点,序号是否是已经收序号加1。都满足条件后,则把文件保存到本地的一个临时路径中。

当所有文件发送完后,会再发送一个end消息,告知学习节点已经发送完毕。当接收到end消息后,会对每个状态机执行lockCheckpointState操作,然后设置minInstanceId、maxInstanceId等,然后退出checkpoint模式

总结一下整个checkpoint学习过程,首先当进入checkpoint模式后,发送学习请求给所有节点,当收到某个节点的checkpoint start信息后,就只接受该节点的checkpoint信息。被学习的节点会开启sender线程,通过每个状态机的getCheckpointState方法获取所有状态机的checkpoint文件,把文件发送给学习节点。当发送完毕后,学习节点根据保存的所有文件,执行loadCheckpointState方法恢复数据。

以上就是WPaxos中一次完整的paxos流程,只是简单的顺序撸了一遍代码,具体还有很多细节,每一部分的细节后面会再单独详细解析。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. B站 Bilibili 搜索太烂?重运营轻技术?B站 问题 困局 未来 发展 浅谈

    B站 Bilibili 搜索太烂?重运营轻技术? B站 问题 困局 未来 发展 浅谈 (可直接略过前两节无用的铺垫部分,直接跳入第三节阅读) 一个值得思考的问题是。如果B站用户数持续增长,总视频量、日均/月均原创作品量到新的量级…… 技术层面: 现有的技术储备能否应对?用户能否精…...

    2024/4/28 11:19:35
  2. Nginx反向代理及配置

    什么是反向代理?反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。反向代理的好处1、保护了真实…...

    2024/4/28 6:36:42
  3. OSPF理论+实验,OSPF路由协议概述,工作过程,基本概念,和数据包以及建立完全邻接关系过程

    OSPF理论+实验,OSPF路由协议概述,工作过程,基本概念,和数据包以及建立完全邻接关系过程前言:一:OSPF的基本概念和工作过程1.1:OSPF路由协议概述1.1.1:自治系统(AS)1.1.2:内部网关协议(IGP)1.1.3:外部网关协议(EGP)1.1.4:OSPF是链路状态路由协议1.2:OSPF原理与…...

    2024/4/27 21:52:44
  4. windows 10+ubuntu16.04全程配置CPU版本的caffe ssd并训练自己的数据研究报告

    本人花了好几天,顺利完成windows版本caffe-ssd和ubuntu版本的caffe-ssd环境配置、脚本修改、最终独创一套高速自定义训练的项目包,可以在配置caffe ssd环境后可以迅速训练自己的数据,而不需要眼花缭乱的搞各种脚本,进行各种转换,这么麻烦了。虽然谈不上是专家,但是已经比…...

    2024/4/27 21:36:14
  5. 新建群发 量化投资:研报文本挖掘选股策略

    核心观点:●借力研报,打造开放的量化选股模型传统的多因子量化选股模型是封闭的,缺乏捕捉市场热点的能力,通过大数据技术,从财经媒体和分析师研报中捕捉热点和政策的变化,可以打造开放的量化选股模型。分析师个股类研报每年大约5万份,相比财经媒体,分析师研报在专业度、…...

    2024/4/15 5:52:30
  6. JS关于时间戳Date(YYYY-MM-DD HH:MM:SS)格式Firefox,IE浏览器不兼容问题

    前段时间做一个表单输入,关于一个开始-结束时间范围的表单输入,前端准备做一个校验,就是结束时间必须大于开始时间。逻辑很简单,就是时间戳转换一下格式,用getTime去比较大小。表单输入是这样的:本人在chrome上做的开发,由于项目比较紧,没有做多浏览器的自测,加上自身…...

    2024/4/28 6:58:02
  7. 【Docker】真小白实战之安装Docker部署SpringBoot项目

    以下操作在centos7下执行安装 1.docker依赖于系统的一些必要的工具,安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm22.添加软件源信息 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo3.更新yum缓存…...

    2024/4/28 14:56:17
  8. 软件测试实验——对新冠肺炎病毒自助检测等(1)

    对新冠肺炎病毒自助检测判断逻辑,采用决策表方法设计测试用例,并编写程序测试之。对于实现的程序,生成控制流图,并确定其圈复杂度。 ​public class work4 {public static int classify(int[][] a) {String[] b= {"建议立刻发热门诊就诊,并且佩戴好口罩做好防护。佩戴…...

    2024/4/24 10:05:01
  9. 常用正则

    1.只能输入数字、大小写、汉字 str.replace(/[^\a-\z\A-\Z0-9\u4E00-\u9FA5]/g, "")2.金额栏位常用val.replace(/[^\d.]/g, "") // 清除“数字”和“.”以外的字符.replace(/\.{2,}/g, ".") // 只保留第一个. 清除多余的.replace(".",…...

    2024/4/24 10:05:02
  10. 微信公众号开发Day01: 消息回复 英文励志语录回复实战

    目录0x01 订阅号 和 服务号之间的区别0x02 申请条件0x03 开发前的准备0x04微信公众号后台的简单使用0x05 接入到微信服务器0x06 微信发送接受消息实现的原理#消息传输的形式#用户发送文本消息并回复#用户发送图片消息回复#语音消息的回复:#多图文消息回复#用户关注后回复微信公…...

    2024/4/24 10:04:58
  11. jQuery事件与常用插件

    jQuery事件与常用插件jQuery事件与常用插件1 jQuery 事件注册与处理1.1 jQuery 事件普通注册1.2 事件处理 on() 绑定事件1.3 事件处理 off() 解绑事件1.4 事件处理 trigger() 自动触发事件2 jQuery 事件对象2.1 jQuery 事件对象介绍2.2 jQuery 拷贝对象2.3 jQuery 多库共存3 j…...

    2024/4/24 10:05:00
  12. 行情怎么看?

    行情怎么看? 今日的行情,走的比较纠结,不过也是符合预期。 昨天就和大家说过了,短期问题不大,但是要天天大涨却是不可能。 今天明显就是昨天用力过猛,需要休整一下,那么震荡过后,还会有冲高。 关键还是看看量能,只要成交量能在3000亿附近,市场的行情就不会太差。 … …...

    2024/4/24 10:05:00
  13. 5G基站电源配置如何估算?

    5G基站建设,配套先行。随着三大运营商2020年5G集采落地,50万5G基站建设已在路上。但由于原4G基站站点新增5G设备后,整站功耗上升,相应的基站电源配套需首先进行升级改造,以保障5G基站稳定运行。 那5G基站电源配套如何改造?是怎样计算的? 5G基站由BBU和AAU组成,本文按一…...

    2024/4/24 10:04:56
  14. 5.8、周知端口

    TCP 1=TCP Port Service Multiplexer TCP 2=Death TCP 5=Remote Job Entry,yoyo TCP 7=Echo TCP 11=Skun TCP 12=Bomber TCP 16=Skun TCP 17=Skun TCP 18=消息传输协议,skun TCP 19=Skun TCP 20=FTP Data,Amanda(传输数据) TCP 21=文件传输,(控制连接)Back Construction,Blad…...

    2024/4/25 6:48:47
  15. 源码阅读技巧总结

    源码阅读技巧总结 源码准备工作 利用码云gitee创建一个github上的镜像仓库,然后下载或者克隆这个镜像仓库,就是正常的网速了优点: 是可以下载快点,防止没有科学上网,下载不下来缺点:很明显,不能更新代码了然后 git clone 到本地 git clone git@gitee.com:yanlong10829/s…...

    2024/4/18 9:28:58
  16. 阿里云平台短信服务使用详细版Demo(java)

    一、阿里云平台账号注册1.阿里云官网地址:https://www.aliyun.com/2.注册并实名认证,使用阿里云短信服务必须进行实名认证。二、开通短信服务1.点击右上角控制台进入控制台界面。2.搜索短信服务,点击立即开通,开通短信服务为免费,后续发短信的时候才需要充值。三、获取Acc…...

    2024/4/24 8:31:49
  17. 与Linux的交织从此处开始

    Linux背景发展史UNIX发展的历史Linux发展历史开源官网企业应用现状发行版本os概念,定位守得云开见月明,静待花开终有时发展史 学习Linux系统编程,首先得知道Linux从哪里来?它是怎么发展的?在这里简要介绍Linux的发展史。要说Linux,还得从UNIX说起。 UNIX发展的历史1968年…...

    2024/4/16 13:19:38
  18. GitHub:非科班出生如何自学计算机科学?

    如果你是一个自学成才的工程师,或者从编程培训班毕业,那么你很有必要学习计算机科学。幸运的是,不必为此花上数年光阴和不菲费用去攻读一个学位:仅仅依靠自己,你就可以获得世界一流水平的教育💸。 互联网上,到处都有许多的学习资源,然而精华与糟粕并存。你所需要的,不…...

    2024/4/17 15:58:01
  19. xmind8安装,白嫖党进

    找到 XMind 安装目录, 如: D:\Program Files (x86)\XMind,将下载的破解补丁剪切到此目录中。 以文本格式打开安装目录中 XMind.ini , 在 XMind.ini 最后追加一个字段-javaagent,然后加上 XMindCrack.jar 的绝对路径。 例如: “-javaagent:D:\Application\XMind\XMindCrack.ja…...

    2024/4/16 8:32:58
  20. webrtc穿透服务器及会议服务器内网部署疑问

    当前面临的问题描述: 目前我们采用的webrtc 服务器模型为Janus + coturn,此模型在金山云服务器上已经成功部署运行(带宽只有5M),由于 janus 是 sfu 模型,未做视频融合。意味着如果9个人开会,(每个人上传1路+下拉8路)*9=81路视频流,非常耗费流量,于是我们尝试在移动固…...

    2024/4/28 4:56:49

最新文章

  1. 小龙虾优化算法(Crayfish Optimization Algorithm,COA)

    小龙虾优化算法&#xff08;Crayfish Optimization Algorithm&#xff0c;COA&#xff09; 前言一、小龙虾优化算法的实现1.初始化阶段2.定义温度和小龙虾的觅食量3.避暑阶段&#xff08;探索阶段&#xff09;4.竞争阶段&#xff08;开发阶段&#xff09;5.觅食阶段&#xff08…...

    2024/4/28 15:49:59
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. Java项目:基于SSM+vue框架实现的人力资源管理系统设计与实现(源码+数据库+毕业论文+任务书)

    一、项目简介 本项目是一套基于SSM框架实现的人力资源管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能…...

    2024/4/27 9:20:32
  4. 校园局域网钓鱼实例

    Hello &#xff01; 我是"我是小恒不会java" 本文仅作为针对普通同学眼中的网络安全&#xff0c;设计的钓鱼案例也是怎么简陋怎么来 注&#xff1a;本文不会外传代码&#xff0c;后端已停止使用&#xff0c;仅作为学习使用 基本原理 内网主机扫描DNS劫持前端模拟后端…...

    2024/4/26 1:15:21
  5. git总结

    建议配合gitlab实操 git分区&#xff1a;工作区(修改的地方)&#xff0c;暂存区(add)&#xff0c;本地仓库(commit) 常用命令 初次使用配置 git config --global user.name "xxx" git config --global user.email "xxxqq.com" 这2个是针对整个主机的全局…...

    2024/4/26 0:33:49
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/28 13:52:11
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/28 3:28:32
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/28 13:51:37
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/27 9:01:45
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/28 1:22:35
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/27 11:43:08
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/27 8:32:30
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57