Nacos源码—Nacos集群高可用分析(二)
4.集群节点的健康状态变动时的数据同步
(1)Nacos后台管理的集群管理模块介绍
在集群管理模块下,可以看到每个节点的状态和元数据。节点IP就是节点的IP地址以及端口,节点状态就是标识当前节点是否可用,节点元数据就是相关的Raft信息。
其中节点元数据示例如下:
{// 最后刷新时间"lastRefreshTime": 1674093895774,// raft 元信息"raftMetaData": {"metaDataMap": {"naming_persistent_service": {// leader IP 地址"leader": "10.0.16.3:7849",// raft 分组节点"raftGroupMember": ["10.0.16.3:7850","10.0.16.3:7848","10.0.16.3:7849"],"term": 1}}},// raft 端口"raftPort": "7849",// Nacos 版本"version": "1.4.1"
}
(2)集群节点启动时开启节点健康检查任务的源码
因为ServerMemberManager这个Bean会监听WebServerInitializedEvent事件,所以Spring启动时会执行ServerMemberManager的onApplicationEvent()方法。该方法会在集群模式下开启一个集群节点的健康检查任务,也就是会执行MemberInfoReportTask的run()方法,即执行Task的run()方法。
由于MemberInfoReportTask类继承了使用模版设计模式的抽象父类Task,所以执行Task的run()方法时:会先执行MemberInfoReportTask的executeBody()方法,然后会执行MemberInfoReportTask的after()方法。
在MemberInfoReportTask的executeBody()方法中:首先会获取除自身以外的其他集群节点List,然后通过对cursor变量自增后取模,来选出本次请求的目标节点Member,最后通过HTTP方式(/v1/core/cluster/report)对目标节点Member发起请求。如果目标节点返回成功,则执行MemberUtil的onSuccess()方法。如果目标节点返回失败,则执行MemberUtil的onFail()方法,并且把目标节点Member的state属性修改为DOWN。
最后在MemberInfoReportTask的after()方法中:又会重新提交这个MemberInfoReportTask健康检查任务,反复执行。
@Component(value = "serverMemberManager")
public class ServerMemberManager implements ApplicationListener<WebServerInitializedEvent> {private final NacosAsyncRestTemplate asyncRestTemplate = HttpClientBeanHolder.getNacosAsyncRestTemplate(Loggers.CORE);//Address information for the local node.private String localAddress;//Broadcast this node element information task.private final MemberInfoReportTask infoReportTask = new MemberInfoReportTask();...//监听Spring启动时发布的WebServerInitializedEvent事件@Overridepublic void onApplicationEvent(WebServerInitializedEvent event) {//设置当前集群节点的状态为默认状态getSelf().setState(NodeState.UP);//集群模式下才启动集群节点的健康检查任务if (!EnvUtil.getStandaloneMode()) {//开启一个延时任务,执行MemberInfoReportTask.run()方法GlobalExecutor.scheduleByCommon(this.infoReportTask, 5_000L);}EnvUtil.setPort(event.getWebServer().getPort());EnvUtil.setLocalAddress(this.localAddress);Loggers.CLUSTER.info("This node is ready to provide external services");}... class MemberInfoReportTask extends Task {private final GenericType<RestResult<String>> reference = new GenericType<RestResult<String>>() { };private int cursor = 0;@Overrideprotected void executeBody() {//获取除自身节点外的其他集群节点List<Member> members = ServerMemberManager.this.allMembersWithoutSelf();if (members.isEmpty()) {return;}//轮询请求:每执行一次executeBody()方法,cursor就加1,然后根据cursor去获取对应的某集群节点Memberthis.cursor = (this.cursor + 1) % members.size();Member target = members.get(cursor);Loggers.CLUSTER.debug("report the metadata to the node : {}", target.getAddress());//获取URL参数:/v1/core/cluster/reportfinal String url = HttpUtils.buildUrl(false, target.getAddress(), EnvUtil.getContextPath(), Commons.NACOS_CORE_CONTEXT, "/cluster/report");try {//通过HTTP发起请求,向某集群节点Member发起健康检查请求asyncRestTemplate.post(url, Header.newInstance().addParam(Constants.NACOS_SERVER_HEADER, VersionUtils.version),Query.EMPTY, getSelf(), reference.getType(), new Callback<String>() {@Overridepublic void onReceive(RestResult<String> result) {if (result.getCode() == HttpStatus.NOT_IMPLEMENTED.value() || result.getCode() == HttpStatus.NOT_FOUND.value()) {Loggers.CLUSTER.warn("{} version is too low, it is recommended to upgrade the version : {}", target, VersionUtils.version);return;}if (result.ok()) {//如果请求成功,则设置集群节点Member的状态为NodeState.UPMemberUtil.onSuccess(ServerMemberManager.this, target);} else {//如果请求失败,则设置集群节点Member的状态为NodeState.DOWNLoggers.CLUSTER.warn("failed to report new info to target node : {}, result : {}", target.getAddress(), result);MemberUtil.onFail(ServerMemberManager.this, target);}}@Overridepublic void onError(Throwable throwable) {Loggers.CLUSTER.error("failed to report new info to target node : {}, error : {}", target.getAddress(), ExceptionUtil.getAllExceptionMsg(throwable));//如果请求失败,则设置集群节点Member的状态为NodeState.DOWNMemberUtil.onFail(ServerMemberManager.this, target, throwable);}@Overridepublic void onCancel() {}});} catch (Throwable ex) {Loggers.CLUSTER.error("failed to report new info to target node : {}, error : {}", target.getAddress(), ExceptionUtil.getAllExceptionMsg(ex));}}@Overrideprotected void after() {//重新提交这个节点健康检查的异步任务,从而实现反复执行GlobalExecutor.scheduleByCommon(this, 2_000L);}}
}//Task使用了模版方法
public abstract class Task implements Runnable {protected volatile boolean shutdown = false;@Overridepublic void run() {if (shutdown) {return;}try {//执行异步任务的核心逻辑,这个方法是一个抽象方法,交给子类去具体实现executeBody();} catch (Throwable t) {Loggers.CORE.error("this task execute has error : {}", ExceptionUtil.getStackTrace(t));} finally {if (!shutdown) {after();}}}protected abstract void executeBody();protected void after() {}public void shutdown() {shutdown = true;}
}public class MemberUtil {...//Successful processing of the operation on the node.public static void onSuccess(final ServerMemberManager manager, final Member member) {final NodeState old = member.getState();manager.getMemberAddressInfos().add(member.getAddress());member.setState(NodeState.UP);member.setFailAccessCnt(0);if (!Objects.equals(old, member.getState())) {manager.notifyMemberChange();}}public static void onFail(final ServerMemberManager manager, final Member member) {onFail(manager, member, ExceptionUtil.NONE_EXCEPTION);}//Failure processing of the operation on the node.public static void onFail(final ServerMemberManager manager, final Member member, Throwable ex) {manager.getMemberAddressInfos().remove(member.getAddress());final NodeState old = member.getState();member.setState(NodeState.SUSPICIOUS);member.setFailAccessCnt(member.getFailAccessCnt() + 1);int maxFailAccessCnt = EnvUtil.getProperty("nacos.core.member.fail-access-cnt", Integer.class, 3);if (member.getFailAccessCnt() > maxFailAccessCnt || StringUtils.containsIgnoreCase(ex.getMessage(), TARGET_MEMBER_CONNECT_REFUSE_ERRMSG)) {member.setState(NodeState.DOWN);}if (!Objects.equals(old, member.getState())) {manager.notifyMemberChange();}}...
}public class GlobalExecutor {private static final ScheduledExecutorService COMMON_EXECUTOR = ExecutorFactory.Managed.newScheduledExecutorService(ClassUtils.getCanonicalName(GlobalExecutor.class), 4,new NameThreadFactory("com.alibaba.nacos.core.common"));...public static void scheduleByCommon(Runnable runnable, long delayMs) {if (COMMON_EXECUTOR.isShutdown()) {return;}//在指定的延迟后执行某项任务COMMON_EXECUTOR.schedule(runnable, delayMs, TimeUnit.MILLISECONDS);}...
}public final class ExecutorFactory {...public static final class Managed {private static final String DEFAULT_NAMESPACE = "nacos";private static final ThreadPoolManager THREAD_POOL_MANAGER = ThreadPoolManager.getInstance();...//Create a new scheduled executor service with input thread factory and register to manager.public static ScheduledExecutorService newScheduledExecutorService(final String group, final int nThreads, final ThreadFactory threadFactory) {ScheduledExecutorService executorService = Executors.newScheduledThreadPool(nThreads, threadFactory);THREAD_POOL_MANAGER.register(DEFAULT_NAMESPACE, group, executorService);return executorService;}...}...
}
(3)集群节点收到健康检查请求后的数据同步源码
集群节点收到某集群节点发来的"/v1/core/cluster/report"请求后,会调用NacosClusterController的report()方法来处理请求。在report()方法中,会把发起请求的来源节点状态直接设置成UP状态,然后调用ServerMemberManager的update()方法来更新来源节点属性。在update()方法中,会把存放在serverList中对应的节点Member进行更新,也就是通过MemberUtil的copy()方法覆盖老对象的属性来实现更新。
注意:因为serverList属性在集群中的每个节点都存在一份,所以节点收到健康检查请求后,要对其serverList属性中的节点进行更新。
@RestController
@RequestMapping(Commons.NACOS_CORE_CONTEXT + "/cluster")
public class NacosClusterController {private final ServerMemberManager memberManager;...//Other nodes return their own metadata information.@PostMapping(value = {"/report"})public RestResult<String> report(@RequestBody Member node) {if (!node.check()) {return RestResultUtils.failedWithMsg(400, "Node information is illegal");}LoggerUtils.printIfDebugEnabled(Loggers.CLUSTER, "node state report, receive info : {}", node);//能够正常请求到该接口的集群节点肯定是健康的,所以直接设置其节点状态为UPnode.setState(NodeState.UP);node.setFailAccessCnt(0);//修改集群节点boolean result = memberManager.update(node);return RestResultUtils.success(Boolean.toString(result));}...
}@Component(value = "serverMemberManager")
public class ServerMemberManager implements ApplicationListener<WebServerInitializedEvent> {//Cluster node list.private volatile ConcurrentSkipListMap<String, Member> serverList;...//member information update.public boolean update(Member newMember) {Loggers.CLUSTER.debug("member information update : {}", newMember);String address = newMember.getAddress();if (!serverList.containsKey(address)) {return false;}//更新serverList中的数据serverList.computeIfPresent(address, (s, member) -> {//如果服务状态不健康,则直接移除if (NodeState.DOWN.equals(newMember.getState())) {memberAddressInfos.remove(newMember.getAddress());}//对比信息是否有做改变boolean isPublishChangeEvent = MemberUtil.isBasicInfoChanged(newMember, member);//修改lastRefreshTime为当前时间newMember.setExtendVal(MemberMetaDataConstants.LAST_REFRESH_TIME, System.currentTimeMillis());//属性覆盖MemberUtil.copy(newMember, member);if (isPublishChangeEvent) {//member basic data changes and all listeners need to be notified//如果有做改变,需要发布相关事件通知notifyMemberChange();}return member;});return true;}...
}
(4)总结
在Nacos集群架构下,集群节点间的健康状态如何进行同步。简单来说,集群节点间是会相互进行通信的。如果通信失败,那么就会把通信节点的状态属性修改为DOWN。
5.集群新增节点时如何同步已有服务实例数据
(1)节点启动时加载服务实例数据的异步任务
Nacos服务端会有一个DistroProtocol类,它是一个Bean对象,在Spring项目启动时会创建这个DistroProtocol类型的Bean。
创建DistroProtocol类型的Bean时,会执行DistroProtocol的构造方法,从而调用DistroProtocol的startLoadTask()方法开启一个加载数据的异步任务。
在DistroProtocol的startLoadTask()方法中,会提交一个异步任务,并且会通过传入一个回调方法来标志是否已初始化成功。其中提交的任务类型是DistroLoadDataTask,所以会执行DistroLoadDataTask的run()方法,接着会执行DistroLoadDataTask的load()方法,然后执行该任务类的loadAllDataSnapshotFromRemote()方法,从而获取其他集群节点上的全部服务实例数据并更新本地注册表。
在loadAllDataSnapshotFromRemote()方法中,首先会遍历除自身节点外的其他集群节点。然后调用DistroHttpAgent的getDatumSnapshot()方法,通过HTTP请求"/v1/ns/distro/datums"获取目标节点的全部服务实例数据。接着再调用DistroConsistencyServiceImpl的processSnapshot()方法,将获取到的全部服务实例数据写入到本地注册表中。其中只要有一个集群节点数据同步成功,那么这个方法就结束。否则就继续遍历下一个集群节点,获取全部服务实例数据然后同步本地。
Nacos服务端在处理服务实例注册时,采用的是内存队列 + 异步任务。异步任务会调用listener的onChange()方法利用写时复制来更新本地注册表。而processSnapshot()方法也会调用listener的onChange()方法来更新注册表,其中listener的onChange()方法对应的实现其实就是Service的onChange()方法。
@Component
public class DistroProtocol {...public DistroProtocol(ServerMemberManager memberManager, DistroComponentHolder distroComponentHolder, DistroTaskEngineHolder distroTaskEngineHolder, DistroConfig distroConfig) {this.memberManager = memberManager;this.distroComponentHolder = distroComponentHolder;this.distroTaskEngineHolder = distroTaskEngineHolder;this.distroConfig = distroConfig;//开启一个异步任务startDistroTask();}private void startDistroTask() {if (EnvUtil.getStandaloneMode()) {isInitialized = true;return;}startVerifyTask();//提交一个加载数据的异步任务startLoadTask();}private void startLoadTask() {//加载数据的回调方法,修改isInitialized属性,标识是否初始化成功DistroCallback loadCallback = new DistroCallback() {@Overridepublic void onSuccess() {isInitialized = true;}@Overridepublic void onFailed(Throwable throwable) {isInitialized = false;}};//提交异步任务GlobalExecutor.submitLoadDataTask(new DistroLoadDataTask(memberManager, distroComponentHolder, distroConfig, loadCallback));}...
}//Distro load data task.
public class DistroLoadDataTask implements Runnable {...@Overridepublic void run() {try {load();if (!checkCompleted()) {GlobalExecutor.submitLoadDataTask(this, distroConfig.getLoadDataRetryDelayMillis());} else {loadCallback.onSuccess();Loggers.DISTRO.info("[DISTRO-INIT] load snapshot data success");}} catch (Exception e) {loadCallback.onFailed(e);Loggers.DISTRO.error("[DISTRO-INIT] load snapshot data failed. ", e);}}private void load() throws Exception {while (memberManager.allMembersWithoutSelf().isEmpty()) {Loggers.DISTRO.info("[DISTRO-INIT] waiting server list init...");TimeUnit.SECONDS.sleep(1);}while (distroComponentHolder.getDataStorageTypes().isEmpty()) {Loggers.DISTRO.info("[DISTRO-INIT] waiting distro data storage register...");TimeUnit.SECONDS.sleep(1);}for (String each : distroComponentHolder.getDataStorageTypes()) {if (!loadCompletedMap.containsKey(each) || !loadCompletedMap.get(each)) {loadCompletedMap.put(each, loadAllDataSnapshotFromRemote(each));}}}private boolean loadAllDataSnapshotFromRemote(String resourceType) {DistroTransportAgent transportAgent = distroComponentHolder.findTransportAgent(resourceType);DistroDataProcessor dataProcessor = distroComponentHolder.findDataProcessor(resourceType);if (null == transportAgent || null == dataProcessor) {Loggers.DISTRO.warn("[DISTRO-INIT] Can't find component for type {}, transportAgent: {}, dataProcessor: {}", resourceType, transportAgent, dataProcessor);return false;}//遍历除自身节点外的其他节点for (Member each : memberManager.allMembersWithoutSelf()) {try {Loggers.DISTRO.info("[DISTRO-INIT] load snapshot {} from {}", resourceType, each.getAddress());//调用DistroHttpAgent.getDatumSnapshot()方法,通过HTTP方式获取其他集群节点的数据DistroData distroData = transportAgent.getDatumSnapshot(each.getAddress());//调用DistroConsistencyServiceImpl.processSnapshot()方法,同步返回结果到自身节点的内存注册表boolean result = dataProcessor.processSnapshot(distroData);Loggers.DISTRO.info("[DISTRO-INIT] load snapshot {} from {} result: {}", resourceType, each.getAddress(), result);//只要有一个集群节点返回全部数据并同步成功则结束if (result) {return true;}} catch (Exception e) {Loggers.DISTRO.error("[DISTRO-INIT] load snapshot {} from {} failed.", resourceType, each.getAddress(), e);}}return false;}...
}public class DistroHttpAgent implements DistroTransportAgent {...@Overridepublic DistroData getDatumSnapshot(String targetServer) {try {//通过NamingProxy发起HTTP请求byte[] allDatum = NamingProxy.getAllData(targetServer);return new DistroData(new DistroKey("snapshot", KeyBuilder.INSTANCE_LIST_KEY_PREFIX), allDatum);} catch (Exception e) {throw new DistroException(String.format("Get snapshot from %s failed.", targetServer), e);}}...
}public class NamingProxy {...//获取目标节点的全部数据public static byte[] getAllData(String server) throws Exception {Map<String, String> params = new HashMap<>(8);RestResult<String> result = HttpClient.httpGet("http://" + server + EnvUtil.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + ALL_DATA_GET_URL,new ArrayList<>(),params);if (result.ok()) {return result.getData().getBytes();}throw new IOException("failed to req API: " + "http://" + server + EnvUtil.getContextPath()+ UtilsAndCommons.NACOS_NAMING_CONTEXT + ALL_DATA_GET_URL + ". code: " + result.getCode() + " msg: "+ result.getMessage());}...
}@DependsOn("ProtocolManager")
@org.springframework.stereotype.Service("distroConsistencyService")
public class DistroConsistencyServiceImpl implements EphemeralConsistencyService, DistroDataProcessor {...@Overridepublic boolean processSnapshot(DistroData distroData) {try {return processData(distroData.getContent());} catch (Exception e) {return false;}}private boolean processData(byte[] data) throws Exception {if (data.length > 0) {//序列化成对象Map<String, Datum<Instances>> datumMap = serializer.deserializeMap(data, Instances.class);//创建空的Servicefor (Map.Entry<String, Datum<Instances>> entry : datumMap.entrySet()) {...}for (Map.Entry<String, Datum<Instances>> entry : datumMap.entrySet()) {if (!listeners.containsKey(entry.getKey())) {// Should not happen:Loggers.DISTRO.warn("listener of {} not found.", entry.getKey());continue;}try {//更新本地注册表for (RecordListener listener : listeners.get(entry.getKey())) {listener.onChange(entry.getKey(), entry.getValue().value);}} catch (Exception e) {Loggers.DISTRO.error("[NACOS-DISTRO] error while execute listener of key: {}", entry.getKey(), e);continue;}//Update data store if listener executed successfully:dataStore.put(entry.getKey(), entry.getValue());}}return true;}...
}
总结:Nacos服务端集群节点启动时,会创建一个DistroProtocol类型的Bean对象,在这个DistroProtocol类型的Bean对象的构造方法会开启一个异步任务。该异步任务的主要逻辑是通过HTTP方式从其他集群节点获取服务数据,然后把获取到的服务实例数据更新到本地的内存注册表,完成数据同步。而且只要成功从某一个集群节点完成数据同步,那整个任务逻辑就结束。
此外,向某个集群节点获取全部服务实例数据时,是向"/v1/ns/distro/datums"接口发起HTTP请求来进行获取的。
(2)节点处理获取全部服务实例数据请求的源码
Nacos集群节点收到"/v1/ns/distro/datums"的HTTP请求后,便会执行DistroController的getAllDatums()方法。也就是调用DistroProtocol的onSnapshot()方法获取数据,然后直接返回。接着会调用DistroDataStorageImpl的getDatumSnapshot()方法。
getDatumSnapshot()方法会从DataStore的getDataMap()方法获取结果。进行服务实例注册时,会把服务实例信息存一份放在DataStore的Map中。进行服务实例同步时,也会把服务实例信息存放到DataStore的Map中。所以在DataStore里,会包含整个服务实例信息的数据。这里获取全部服务实例数据的接口,也是利用DataStore来实现的,而不是从内存注册表中获取。
@RestController
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/distro")
public class DistroController {@Autowiredprivate DistroProtocol distroProtocol;...//Get all datums.@GetMapping("/datums")public ResponseEntity getAllDatums() {DistroData distroData = distroProtocol.onSnapshot(KeyBuilder.INSTANCE_LIST_KEY_PREFIX);return ResponseEntity.ok(distroData.getContent());}...
}@Component
public class DistroProtocol {...//Query all datum snapshot.public DistroData onSnapshot(String type) {DistroDataStorage distroDataStorage = distroComponentHolder.findDataStorage(type);if (null == distroDataStorage) {Loggers.DISTRO.warn("[DISTRO] Can't find data storage for received key {}", type);return new DistroData(new DistroKey("snapshot", type), new byte[0]);}//调用DistroDataStorageImpl.getDatumSnapshot()方法return distroDataStorage.getDatumSnapshot();}...
}public class DistroDataStorageImpl implements DistroDataStorage { private final DataStore dataStore;...@Overridepublic DistroData getDatumSnapshot() {Map<String, Datum> result = dataStore.getDataMap();//对服务实例数据进行序列化byte[] dataContent = ApplicationUtils.getBean(Serializer.class).serialize(result);DistroKey distroKey = new DistroKey("snapshot", KeyBuilder.INSTANCE_LIST_KEY_PREFIX);//封装一个DistroData对象并返回return new DistroData(distroKey, dataContent);}...
}//Store of data. 用于存储所有已注册的服务实例数据
@Component
public class DataStore {private Map<String, Datum> dataMap = new ConcurrentHashMap<>(1024);public void put(String key, Datum value) {dataMap.put(key, value);}public Datum remove(String key) {return dataMap.remove(key);}public Set<String> keys() {return dataMap.keySet();}public Datum get(String key) {return dataMap.get(key);}public boolean contains(String key) {return dataMap.containsKey(key);}public Map<String, Datum> batchGet(List<String> keys) {Map<String, Datum> map = new HashMap<>(128);for (String key : keys) {Datum datum = dataMap.get(key);if (datum == null) {continue;}map.put(key, datum);}return map;}...public Map<String, Datum> getDataMap() {return dataMap;}
}
注意:DataStore数据最后还是存到内存的。通过使用DataStore,可以实现以下功能和好处:
一.数据持久化
DataStore可将节点数据持久化到磁盘或其他介质,以确保数据的持久性。这样即使系统重启或发生故障,节点数据也能够得到恢复和保留。毕竟Datum的key是ServiceName、value是Instance实例列表,而Instance实例中又会包含所属的ClusterName、IP和Port,所以根据DataStore可以恢复完整的内存注册表。
Map<string, map> serviceMap;
Map(namespace, Map(group::serviceName, Service));
二.数据同步
DataStore可以协调和同步节点数据的访问和更新。当多个节点同时注册或更新数据时,DataStore可确保数据的一致性和正确性,避免数据冲突和不一致的情况。
三.数据管理
DataStore提供了对节点数据的管理功能,包括增加、更新、删除等操作。通过使用适当的数据结构和算法,可以高效地管理大量的节点数据,并支持快速的数据访问和查询。
四.数据访问控制
DataStore可以实现对节点数据的访问控制和权限管理,只有具有相应权限的节点或用户才能访问和修改特定的节点数据,提高数据的安全性和保密性。
DataStore在Nacos中充当了节点数据的中央存储和管理器。通过提供持久化 + 同步 + 管理 + 访问控制等功能,确保节点数据的可靠性 + 一致性 + 安全性,是实现节点数据存储和操作的核心组件之一。
(3)总结
Nacos集群架构下新增一个集群节点时,新节点会如何进行服务数据同步:
首先利用了DistroProtocol类的Bean对象的构造方法开启异步任务,通过HTTP方式去请求其他集群节点的全部数据。
当新节点获取全部数据后,会调用Service的onChange()方法,然后利用写时复制机制更新本地内存注册表。
Nacos集群节点在处理获取全部服务实例数据的请求时,并不是从内存注册表中获取的,而是通过DataStore来获取。
文章转载自:东阳马生架构
原文链接:Nacos源码—3.Nacos集群高可用分析 - 东阳马生架构 - 博客园
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
Nacos源码—Nacos集群高可用分析(二)
4.集群节点的健康状态变动时的数据同步 (1)Nacos后台管理的集群管理模块介绍 在集群管理模块下,可以看到每个节点的状态和元数据。节点IP就是节点的IP地址以及端口,节点状态就是标识当前节点是否可用,节点元数据就是相关的Raft信息。 其中节点…...
SRAM详解
一、SRAM基础原理 定义与结构 SRAM(Static Random-Access Memory,静态随机存取存储器)是一种基于触发器(Flip-Flop)结构的易失性内存,通过交叉耦合的反相器(6晶体管,6T单元ÿ…...
JavaWeb:MySQL进阶
多表设计 一对多(多对一) 外键 一对一 多对多 多表查询 内连接 外连接 子查询 -- 查询员工表 select * from emp;-- 查询部门表 select * from dept;-- 查询员工和部门 select * from emp, dept; -- 笛卡尔积select * from emp, dept where emp.dept_i…...
Golang 接口 vs Rust Trait:一场关于抽象的哲学对话
一、引言 在现代编程语言中,接口(Interface) 和 Trait 是实现多态和抽象行为的关键机制。它们允许我们定义行为契约,让不同的类型共享相同的语义接口,从而提升代码的复用性和扩展性。 Go 和 Rust 分别代表了两种截然…...
智算中心的搭建标准
智算中心的搭建标准主要涉及以下几个方面: 开放标准: 硬件与软件开放:从硬件到软件、从芯片到架构,都应采用开放、标准的技术。例如,硬件支持如 OCP、ODCC、Open19 等开放社区标准,软件采用如 OpenStack、K…...
商汤科技前端面试题及参考答案
有没有配置过 webpack,讲一下 webpack 热更新原理,能否自己实现一些插件? Webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。在实际项目中,经常会对其进行配置,以满足项目的各种需求,比如处理不同类型的文件、优化代码、配置开发服务器等。 Webpack 热更…...
windows下docker的使用
找了个docker教程 Windows Docker 安装 | 菜鸟教程Windows Docker 安装 Docker 并非是一个通用的容器工具,它依赖于已存在并运行的 Linux 内核环境。Docker 实质上是在已经运行的 Linux 下制造了一个隔离的文件环境,因此它执行的效率几乎等同于所部署的…...
AI日报 · 2025年5月07日|谷歌发布 Gemini 2.5 Pro 预览版 (I/O 版本),大幅提升编码与视频理解能力
1、谷歌发布 Gemini 2.5 Pro 预览版 (I/O 版本),大幅提升编码与视频理解能力 谷歌于5月6日提前发布 Gemini 2.5 Pro 预览版 (I/O 版本),为开发者带来更强编码能力,尤其优化了前端与UI开发、代码转换及智能体工作流构建,并在WebDe…...
Redis 8.0 正式版发布,新特性很强!
就在前两天,Redis 8.0 正式版 (GA) 来了!这并不是一次简单的更新,Redis 8.0 不仅带来了性能上的进一步提升,还带来一些实用的新特性与功能增强。并且,最重要的是拥抱 AGPLv3 重归开源! 下面,简单…...
MySQL核心机制:日志系统、锁机制与事务管理的深度剖析
一.介绍 MySQL作为世界上最流行的开源关系型数据库之一,其强大的事务处理能力和高并发支持使其在各种复杂应用场景中得到广泛应用。MySQL的核心机制包括日志系统、锁机制和事务管理,这些机制共同确保了数据库的ACID特性,为应用程序提供了可靠…...
Mybatis标签使用 -association 绑定对象,collection 绑定集合
注意 association标签中的 select , column 属性使用 collection 标签中的 ofType 属性使用 Data public class Tours implements Serializable {private static final long serialVersionUID 1L;private Integer touId;private String tourName;private Integer guideId;pri…...
IBM BAW(原BPM升级版)使用教程Toolkit介绍
本部分为“IBM BAW(原BPM升级版)使用教程系列”内容的补充。 一、系统Toolkit 在 IBM Business Automation Workflow (BAW) 中,System Toolkit 是一组预先定义和配置好的工具、功能和组件,旨在帮助流程设计者和开发人员快速构建…...
排列组合算法:解锁数据世界的魔法钥匙
在 C 算法的奇幻世界里,排列和组合算法就像是两把神奇的魔法钥匙,能够帮我们解锁数据世界中各种复杂问题的大门。今天,作为 C 算法小白的我,就带大家一起走进排列和组合算法的奇妙天地。 排列算法:创造所有可能的顺序…...
LVGL -meter的应用
1 meter介绍 lv_meter 是 LVGL v8 引入的一种图形控件,用于创建仪表盘样式的用户界面元素,它可以模拟像速度表、电压表、温度表这类模拟表盘。它通过可视化刻度、指针、颜色弧线等来展示数值信息,是一种非常直观的数据展示控件。 1.1 核心特…...
MCP学习
一、MCP基础理论与核心概念 1.1 协议定义与设计目标 MCP(Model Context Protocol)是Anthropic公司于2024年11月开源的标准化协议,旨在解决大型语言模型(LLM)与外部工具、数据源之间的动态交互问题。其核心目标包括&…...
软件工程(三):模块的内聚模型
模块内聚的7种类型(从低到高) 等级类型描述示例1️⃣ 最低偶然性内聚(Coincidental Cohesion)模块内部的各功能毫无关系,随机拼凑一个模块中既有文件读写,又有图像压缩、还处理用户登录2️⃣逻辑性内聚&am…...
Java中字符转数字的原理解析 - 为什么char x - ‘0‘能得到对应数字
前言 在Java编程中,我们经常需要将字符形式的数字转换为实际的数值。有很多方法可以实现这一转换,比如使用Integer.parseInt()或Character.getNumericValue()等方法。但有一种简便且高效的方式是直接使用char - 0运算,本文将详细解析这种方法…...
View的事件分发机制
(一)为什么要有事件分发机制 安卓界面上面的View的层级结构是树形的,可能出现多个View重叠在一起的现象(如下图),当我们点击的地方为多个View重叠的区域时,这个点击事件应该给谁呢?为…...
【C++】类和对象【下】
目录 一、再探构造函数1、测试题 二、类型转换三、static成员1. 静态成员变量2. 静态成员函数 四、友元五、内部类六、匿名对象七、对象拷贝时的编译器优化 个人主页<—请点击 C专栏<—请点击 一、再探构造函数 之前我们实现构造函数时,初始化成员变量主要使…...
【JS逆向基础】并发爬虫
前言:所谓并发编程是指在一台处理器上“同时”处理多个任务。并发是在同一实体上的多个事件。强调多个事件在同一时间间隔发生。 1,进程、线程以及协程 【1】进程概念 我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算…...
Android组件化 -> 基础组件进行Application,Activity生命周期分发
在lib_common基础组件模块创建上下文持有类,生命周期派发类 object AppContextProvider {private lateinit var application: Applicationprivate var currentActivityRef: WeakReference<Activity>? null// 应用生命周期监听器列表private val appLifecyc…...
42. PCB防静电环设计
PCB防静电环的作用 1. PCB防静电环的作用2. 防静电环设计技术点 1. PCB防静电环的作用 防静电环主要用于生产、运输、售后等环节人体会直接接触电路板的场景。 防静电环只在顶层和底层设计即可。 2. 防静电环设计技术点...
深入理解Java反射机制
java反射是java语言中一个强大而灵活的特性,它允许程序在运行时检查和操作类、接口、字段和方法。 为了方便理解下文,我先给出Cat对象 public class Cat implements jump,Run {private int age;public String name;protected String color;double he…...
嵌入式音视频通话EasyRTC基于WebRTC技术驱动智能带屏音箱:开启智能交互新体验
一、引言 随着智能家居市场的蓬勃发展,智能带屏音箱作为家庭智能交互中心的重要组成部分,其功能需求日益丰富。EasyRTC凭借其低延迟、高稳定性的特点,为智能带屏音箱带来了全新的交互体验,能满足用户在视频通话、远程监控、在线…...
1987-2023年各省进出口总额数据整理(含进口和出口)(无缺失)
1987-2023年各省进出口总额数据整理(含进口和出口)(无缺失) 1、时间:1987-2023年 2、来源:各省年鉴、统计公报 3、指标:进出口总额(万美元)、进口总额(万美…...
paddle ocr 或 rapid ocr umi ocr 只识别了图片的下部分内容 解决方案
如上图,识别的准确率其实很高,但是只识别了下半部分的内容,上半部分的内容就没有识别到,其实是程序设置有点问题,程序设置的解决方案如下: 如上图,识别的准确率其实很高,但是只识别了下半部分的内容,上半部分的内容就没有识别到,其实是程序设置有点问题,程序设置的…...
【深度学习-Day 7】精通Pandas:从Series、DataFrame入门到数据清洗实战
Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...
如何测试 esp-webrtc-solution_solutions_doorbell_demo 例程?
软件准备 esp-webrtc-solution/solutions/doorbell_demo 例程 此例程集成了 WebSocket 传输视频流的应用 硬件准备 ESP32P4-Function-Ev-Board 环境搭建 推荐基于 esp-idf v5.4.1 版本的环境来编译此例程 若编译时出现依赖的组件报错,可进行如下修改ÿ…...
default和delete final和override
1.default和delete default 1.生成默认成员函数 2.仅适用于特殊成员函数(如构造函数、析构函数、拷贝/移动操作等) delete 1.删除函数 2.可应用于任何函数(不限于特殊成员函数) 2.final 和override final 用于类:…...
Nvidia Orin 安装onnxruntime-gpu
在用英伟达边缘设备Nvidia Orin 安装onnxruntime-gpu环境时, 通常会遇到很多问题。 在正常的Nvidia 服务器上安装onnxruntime-gpu 是非常简单的, 直接pip install onnxruntime-gpu即可, 但是在边缘设备上就没有这么简单了。 直接pip install…...
C++ CRTP技术(奇异递归模版模式)
C 的CRTP技术 最近了解到C的CRTP技术,通过博客来这里记录一下。 我们首先可以了解一下什么是CRTP技术。CRTP是C的一种高级模版变成模式。 他主要的用途有以下的几点: 编译时实现多态(静态多态):通过CRTP技术…...
验证es启动成功
1. 查看命令行输出信息 在启动 Elasticsearch 时,命令行窗口会输出一系列日志信息。若启动成功,日志里通常会有类似下面的信息: plaintext [2025-05-06T13:20:00,000][INFO ][o.e.n.Node ] [node_name] started其中 [node_na…...
AI工场全面激活电商创意链
在当今科技飞速发展的时代,北京先智先行科技有限公司凭借其卓越的创新能力,推出了“先知大模型”、“先行AI商学院”以及“先知AIGC超级工场”这三款旗舰产品,在市场上掀起了不小的波澜。 传统电商设计流程,从需求确认到营销策…...
数 学 函数
gcd int gcd(int a,int b){while(a%b){int ca%b;ab;bc;}return b; } 错位排列 typedef long long ll; ll d(int n){if(n1) return 0;if(n2) return 1;return (n-1)*(d(n-1)d(n-2)); } 快速幂 //注意看是否有mod的需求 int q_pow(int a,int b){int ans1,tempa;while(b){if(…...
springboot集成langchain4j记忆对话
流式输出 LLM 一次生成一个标记(token),因此许多 LLM 提供商提供了一种方式,可以逐个标记地流式传输响应,而不是等待整个文本生成完毕。 这显著改善了用户体验,因为用户不需要等待未知的时间,几…...
C语言初阶:数组
目录 0.数组要讲的知识点 1.一维数组的创建和初始化 1.1 数组的创建: 1.2数组实例: 1.3 数组的初识化: 例子: 2.一维数组的使用 例子: 总结: 3.一维数组在内存中的存储 4.二维数组的创建和初始化 4.…...
案例分享 | 攻克ADAS开发测试难题,实现单元动态测试新突破
汽车行业中的代码动态测试:守护智能汽车的安全与质量 在当今汽车行业,智能网联汽车的快速发展让软件成为了汽车的核心竞争力之一。从自动驾驶辅助系统到车载信息娱乐系统,汽车中的软件数量和复杂度都在不断增加。然而,软件的复杂…...
K8S 基于本地存储的持久卷
假设有如下三个节点的 K8S 集群: k8s31master 是控制节点 k8s31node1、k8s31node2 是工作节点 容器运行时是 containerd 一、背景分析 阅读本文,默认您有 PV-PVC、hostPath 相关知识。 由于安全方面的考虑,K8S 官方并不推荐 hostPath …...
LED实验
目录 1.LED介绍 1.1LED原理图: 2.单片机运行代码的流程 3.进制的转换 4.C51数据类型 5.小编的单片机型号:STC89C52RC/LE52RC,最高波特率:9600 6.点亮一个LED 代码 步骤 代码: 7.LED闪烁 在STC内操作&#x…...
python+pytest接口自动化测试--日志记录
前言:代码可以直接复制使用 解决问题: 问题1:日志重复记录的问题,比如运行一个模块日志会记录很多遍(通过handlers是否存在解决的) 问题2:运行测试用例进行多个模块相互调用.日志记录不全的问题(通过共享公共的handlers解决问题) 首先写一个日志记录的工具 # 这个是个日志的…...
Android 蓝牙开发调试总结
Android 蓝牙开发调试总结 文章目录 Android 蓝牙开发调试总结一、前言二、蓝牙开发1、开关和连接控制2、相关日志3、相关广播4、demo示例 三、其他1、Android 蓝牙开发调试小结2、Android14 蓝牙启动流程3、Android14 蓝牙 BluetoothService 启动和相关代码介绍4、Android13 蓝…...
混淆矩阵(Confusion Matrix)
混淆矩阵(Confusion Matrix)是一个用于评估分类模型性能的工具,特别是在机器学习和统计学领域。它展示了模型预测结果与实际结果之间的关系。混淆矩阵通常用于二分类或多分类问题中,但也可以扩展到更多类别的情况。 一、混淆矩阵…...
C语言——操作符
一.操作符的分类 算术操作符: - * / %移位操作符:<< >>位操作符: & | ^赋值操作符: - * / % > & | ^单⽬操作符: ! -- & * - ~ sizeof …...
大数据处理利器:Hadoop 入门指南
一、Hadoop 是什么?—— 分布式计算的基石 在大数据时代,处理海量数据需要强大的技术支撑,Hadoop 应运而生。Apache Hadoop 是一个开源的分布式计算框架,致力于为大规模数据集提供可靠、可扩展的分布式处理能力。其核心设计理念是…...
追踪大型语言模型的思想(上)(来自针对Claude的分析)
概述 像 Claude 这样的语言模型并非由人类直接编程,而是通过大量数据进行训练。在训练过程中,它们会学习解决问题的策略。这些策略被编码在模型为每个单词执行的数十亿次计算中。对于我们这些模型开发者来说,这些策略是难以捉摸的。这意…...
系统 Python 与 Conda 环境的灵活切换
在现代 Python 开发中,经常需要在系统 Python 和 Conda 环境中的 Python 之间切换。无论是处理不同项目的依赖冲突,还是测试代码在不同 Python 版本下的兼容性,灵活切换 Python 环境都是开发者的必备技能。本文将详细介绍如何实现 Python 环境的灵活切换,并提供 Conda 命令…...
【HTTP】《HTTP 全原理解析:从请求到响应的奇妙之旅》
文章目录 一、HTTP 协议1.1、HTTP 是什么1.2、理解 "应用层协议"1.3、理解 HTTP 协议的工作过程1.4、HTTP协议格式1.5、协议格式总结 二、HTTP 请求1.1、认识 URL1.1.1、URL 基本格式1.1.2、关于 URL encode 1.2、认识 "方法"1.2.1 、GET 方法1.2.2、 POST…...
重生之我在2024学Fine-tuning
一、Fine-tuning(微调)概述 Fine-tuning(微调)是机器学习和深度学习中的一个重要概念,特别是在预训练模型的应用上。它指的是在模型已经通过大量数据训练得到一个通用的预训练模型后,再针对特定的任务或数据…...
若依前后端分离项目中可以删除哪些原若依有的?
在若依(RuoYi)前后端分离项目中完成二次开发后,可以删除以下未使用的模块和文件以简化项目结构。以下分模块和风险点说明: --- ### **一、后端(Spring Boot)可删除内容** #### 1. **未使用的功能模块** …...
element-plus中,vue3项目,el-input密码框禁止浏览器自动弹出浏览器历史密码提示框
原代码(密码框是text框): <el-form-item label"用户名" :label-width"formLabelWidth" v-if"!localOrhuawei" prop"userName"><el-input v-model"formDialog.userName" />&l…...