Nacos源码解读10——配置中心的客户端怎么处理服务端推送的配置信息变更

自动装配

SpringBoot 自动装配机制 加载 WEB/INF spring.factories

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration```java
@Configuration(proxyBeanMethods = false)
@ConditionalOnProperty(name = "spring.cloud.nacos.config.enabled", matchIfMissing = true)
public class NacosConfigBootstrapConfiguration {......@Bean@ConditionalOnMissingBeanpublic NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {return new NacosConfigManager(nacosConfigProperties);}......}

创建 ConfigService

构建NacosConfigManagerBean的时候会在实例化的时候调用构造方法 他的构造方法中会创建ConfigService

	public NacosConfigManager(NacosConfigProperties nacosConfigProperties) {this.nacosConfigProperties = nacosConfigProperties;// Compatible with older code in NacosConfigProperties,It will be deleted in the// future.createConfigService(nacosConfigProperties);}
	static ConfigService createConfigService(NacosConfigProperties nacosConfigProperties) {if (Objects.isNull(service)) {synchronized (NacosConfigManager.class) {try {if (Objects.isNull(service)) {service = NacosFactory.createConfigService(nacosConfigProperties.assembleConfigServiceProperties());}}catch (NacosException e) {log.error(e.getMessage());throw new NacosConnectionFailureException(nacosConfigProperties.getServerAddr(), e.getMessage(), e);}}}return service;}
  public static ConfigService createConfigService(Properties properties) throws NacosException {return ConfigFactory.createConfigService(properties);}
  public static ConfigService createConfigService(Properties properties) throws NacosException {try {//反射拿到classClass<?> driverImplClass = Class.forName("com.alibaba.nacos.client.config.NacosConfigService");// 获取带Properties参数的构造函数Constructor constructor = driverImplClass.getConstructor(Properties.class);//通过反射构建实例ConfigService vendorImpl = (ConfigService) constructor.newInstance(properties);return vendorImpl;} catch (Throwable e) {throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);}}

NacosConfigService会在构造方法中 注入Listener接受server配置变更通知。

public NacosConfigService(Properties properties) throws NacosException {ValidatorUtils.checkInitParam(properties);// 设置namespace可以通过properties.setProperty(PropertyKeyConst.NAMESPACE)initNamespace(properties);// 初始化namespace、server地址等信息ServerListManager serverListManager = new ServerListManager(properties);// 启动主要用于endpoint方式定时获取server地址,当本地传入isFixed=trueserverListManager.start();//  clientWorker初始化this.worker = new ClientWorker(this.configFilterChainManager, serverListManager, properties);// 将被废弃HttpAgent,先忽略// will be deleted in 2.0 later versionsagent = new ServerHttpAgent(serverListManager);
}

ClientWorker初始化

public ClientWorker(final ConfigFilterChainManager configFilterChainManager, ServerListManager serverListManager,final Properties properties) throws NacosException {this.configFilterChainManager = configFilterChainManager;// 初始化超时时间、重试时间等init(properties);// gRPC config agent初始化agent = new ConfigRpcTransportClient(properties, serverListManager);// 调度线程池,「处理器核数」ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread t = new Thread(r);t.setName("com.alibaba.nacos.client.Worker");t.setDaemon(true);return t;}});agent.setExecutor(executorService);// 启动grpc agentagent.start();}

初始化超时时间等

private void init(Properties properties) {// 超时时间,默认30秒timeout = Math.max(ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.CONFIG_LONG_POLL_TIMEOUT),Constants.CONFIG_LONG_POLL_TIMEOUT), Constants.MIN_CONFIG_LONG_POLL_TIMEOUT);// 重试时间,默认2秒taskPenaltyTime = ConvertUtils.toInt(properties.getProperty(PropertyKeyConst.CONFIG_RETRY_TIME), Constants.CONFIG_RETRY_TIME);// 开启配置删除同步,默认falsethis.enableRemoteSyncConfig = Boolean.parseBoolean(properties.getProperty(PropertyKeyConst.ENABLE_REMOTE_SYNC_CONFIG));
}

GRPCConfigAgent初始化

public ConfigTransportClient(Properties properties, ServerListManager serverListManager) {// 默认编码UTF-8String encodeTmp = properties.getProperty(PropertyKeyConst.ENCODE);if (StringUtils.isBlank(encodeTmp)) {this.encode = Constants.ENCODE;} else {this.encode = encodeTmp.trim();}// namespace租户,默认空this.tenant = properties.getProperty(PropertyKeyConst.NAMESPACE);this.serverListManager = serverListManager;// 用户名和密码验证this.securityProxy = new SecurityProxy(properties,ConfigHttpClientManager.getInstance().getNacosRestTemplate());}

启动GRPC Config Agent

public void start() throws NacosException {// 简单用户名和密码验证if (securityProxy.isEnabled()) {securityProxy.login(serverListManager.getServerUrls());this.executor.scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {securityProxy.login(serverListManager.getServerUrls());}}, 0, this.securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);}startInternal();
}

这里线程会一直运行从listenExecutebell这个阻塞队列中获取元素
listenExecutebell这里阻塞队列会在服务变更之后发布变更事件最后会往这个阻塞队列中塞元素 如果队列为空等待5秒后执行,如果队列不为空立即执行

        @Overridepublic void startInternal() {executor.schedule(() -> {//线程池没有管理并且所有线程没有运行完while (!executor.isShutdown() && !executor.isTerminated()) {try {// 最长等待5秒listenExecutebell.poll(5L, TimeUnit.SECONDS);//如果线程池已经关闭 或者所有线程运行完直接if (executor.isShutdown() || executor.isTerminated()) {continue;}executeConfigListen();} catch (Exception e) {LOGGER.error("[ rpc listen execute ] [rpc listen] exception", e);}}}, 0L, TimeUnit.MILLISECONDS);}

注册Listener

在Spring启动的时候会在run方法中执行
SpringApplicationRunListeners的running(context)这里面会发送一个ApplicationReadyEvent事件
NacosContextRefresher会监听到ApplicationReadyEvent事件进行nacos监听器的注册

	@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {// many Spring contextif (this.ready.compareAndSet(false, true)) {this.registerNacosListenersForApplications();}}
	private void registerNacosListenersForApplications() {......registerNacosListener(propertySource.getGroup(), dataId);......}
	private void registerNacosListener(final String groupKey, final String dataKey) {.....//添加监听器configService.addListener(dataKey, groupKey, listener);......
}

添加监听器

构建CacheData,并缓存在cacheMap中,key是由「dataId+group+tenant」组成;每个CacheData会绑定了Listener列表,也绑定了taskId,3000个不同的CacheData对应一个taskId,对应一个gRPC通道实例

    @Overridepublic void addListener(String dataId, String group, Listener listener) throws NacosException {worker.addTenantListeners(dataId, group, Arrays.asList(listener));}
    public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners)throws NacosException {// 默认DEFAULT_GROUPgroup = blank2defaultGroup(group);//获取租户默认是空String tenant = agent.getTenant();//构建缓存数据CacheData并放入cacheMap中CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);synchronized (cache) {for (Listener listener : listeners) {cache.addListener(listener);}// cache md5 data是否来自server同步cache.setSyncWithServer(false);//往阻队列中添加数据  listenExecutebell.offer(bellItem);agent.notifyListenConfig();}}

往缓存中添加内容

构建缓存数据CacheData并放入cacheMap中,缓存的key为 「dataId+group+tenant」例如:test+DEFAULT_GROUP。每个CacheData会绑定对应的taskId,每3000个CacheData对应一个taskId。其实从后面的代码中可以看出,每个taskId会对应一个gRPC

    public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) throws NacosException {// 从缓存中获取 如果不是空的直接返回CacheData cache = getCache(dataId, group, tenant);if (null != cache) {return cache;}// 构造缓存key以+连接,test+DEFAULT_GROUPString key = GroupKey.getKeyTenant(dataId, group, tenant);synchronized (cacheMap) {CacheData cacheFromMap = getCache(dataId, group, tenant);// multiple listeners on the same dataid+group and race condition,so// double check again// other listener thread beat me to set to cacheMapif (null != cacheFromMap) { // 再检查一遍cache = cacheFromMap;// reset so that server not hang this checkcache.setInitializing(true); // 缓存正在初始化} else {// 构造缓存数据对象cache = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);// 初始值taskId=0,注意此处每3000个CacheData共用一个taskIdint taskId = cacheMap.get().size() / (int) ParamUtil.getPerTaskConfigSize();cache.setTaskId(taskId);// fix issue # 1317 // 默认falseif (enableRemoteSyncConfig) {ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L, false);cache.setContent(response.getContent());}}Map<String, CacheData> copy = new HashMap<>(this.cacheMap.get());// key = test+DEFAULT_GROUPcopy.put(key, cache);// cacheMap = {test+DEFAULT_GROUP=CacheData [test, DEFAULT_GROUP]}cacheMap.set(copy);}LOGGER.info("[{}] [subscribe] {}", agent.getName(), key);MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.get().size());return cache;}

缓存的内容

public class CacheData {//ConfigTransportClient名称,config_rpc_clientprivate final String name;//filter拦截链条,可以执行一些列拦截器private final ConfigFilterChainManager configFilterChainManager;//dataIdpublic final String dataId;//group名称,默认为DEFAULT_GROUPpublic final String group;//租户名称public final String tenant;//添加的Listener列表,线程安全CopyOnWriteArrayListprivate final CopyOnWriteArrayList<ManagerListenerWrap> listeners;//MD5private volatile String md5;//配置内容private volatile String content; }

配置变更

public void executeConfigListen() {Map<String/*taskId*/, List<CacheData>> listenCachesMap = new HashMap<String, List<CacheData>>(16);Map<String, List<CacheData>> removeListenCachesMap = new HashMap<String, List<CacheData>>(16);long now = System.currentTimeMillis();// 超过5分钟boolean needAllSync = now - lastAllSyncTime >= ALL_SYNC_INTERNAL;for (CacheData cache : cacheMap.get().values()) {synchronized (cache) {//  isSyncWithServer初始为false,在下文代码中校验结束后会设置为true,表示md5 cache data同步来自server。如果为true会校验Md5.if (cache.isSyncWithServer()) { cache.checkListenerMd5(); // 内容有变更通知Listener执行if (!needAllSync) { // 不超过5分钟则不再全局校验continue;}}if (!CollectionUtils.isEmpty(cache.getListeners())) { // 有添加Listeners// get listen config 默认 falseif (!cache.isUseLocalConfigInfo()) {List<CacheData> cacheDatas = listenCachesMap.get(String.valueOf(cache.getTaskId()));if (cacheDatas == null) {cacheDatas = new LinkedList<CacheData>();listenCachesMap.put(String.valueOf(cache.getTaskId()), cacheDatas);}// CacheData [test, DEFAULT_GROUP]cacheDatas.add(cache);}} else if (CollectionUtils.isEmpty(cache.getListeners())) { // 没有添加Listenersif (!cache.isUseLocalConfigInfo()) {List<CacheData> cacheDatas = removeListenCachesMap.get(String.valueOf(cache.getTaskId()));if (cacheDatas == null) {cacheDatas = new LinkedList<CacheData>();removeListenCachesMap.put(String.valueOf(cache.getTaskId()), cacheDatas);}cacheDatas.add(cache);}}}}boolean hasChangedKeys = false;if (!listenCachesMap.isEmpty()) { // 有Listenersfor (Map.Entry<String, List<CacheData>> entry : listenCachesMap.entrySet()) {String taskId = entry.getKey();List<CacheData> listenCaches = entry.getValue();ConfigBatchListenRequest configChangeListenRequest = buildConfigRequest(listenCaches);configChangeListenRequest.setListen(true);try {// 每个taskId构建rpcClient,例如:taskId= config-0-c70e0314-4770-43f5-add4-f258a4083fd7;结合上下文每3000个CacheData对应一个rpcClientRpcClient rpcClient = ensureRpcClient(taskId);// 向server发起configChangeListenRequest,server端由ConfigChangeBatchListenRequestHandler处理,还是比较md5是否变更了,变更后server端返回变更的key列表。ConfigChangeBatchListenResponse configChangeBatchListenResponse = (ConfigChangeBatchListenResponse) requestProxy(rpcClient, configChangeListenRequest);if (configChangeBatchListenResponse != null && configChangeBatchListenResponse.isSuccess()) {Set<String> changeKeys = new HashSet<String>();// handle changed keys,notify listener// 有变化的configContextif (!CollectionUtils.isEmpty(configChangeBatchListenResponse.getChangedConfigs())) {hasChangedKeys = true;for (ConfigChangeBatchListenResponse.ConfigContext changeConfig : configChangeBatchListenResponse.getChangedConfigs()) {String changeKey = GroupKey.getKeyTenant(changeConfig.getDataId(), changeConfig.getGroup(),changeConfig.getTenant());changeKeys.add(changeKey);boolean isInitializing = cacheMap.get().get(changeKey).isInitializing();//当server返回变更key列表时执行refreshContentAndCheck方法。然后回调ListenerrefreshContentAndCheck(changeKey, !isInitializing);}}//handler content configsfor (CacheData cacheData : listenCaches) {String groupKey = GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.getTenant());if (!changeKeys.contains(groupKey)) {// key没有变化的,内容由server同步,设置SyncWithServer=truesynchronized (cacheData) {if (!cacheData.getListeners().isEmpty()) {cacheData.setSyncWithServer(true);continue;}}}cacheData.setInitializing(false);}}} catch (Exception e) {LOGGER.error("Async listen config change error ", e);try {Thread.sleep(50L);} catch (InterruptedException interruptedException) {//ignore}}}}if (!removeListenCachesMap.isEmpty()) {for (Map.Entry<String, List<CacheData>> entry : removeListenCachesMap.entrySet()) {String taskId = entry.getKey();List<CacheData> removeListenCaches = entry.getValue();ConfigBatchListenRequest configChangeListenRequest = buildConfigRequest(removeListenCaches);configChangeListenRequest.setListen(false);try {// 向server发送Listener取消订阅请求ConfigBatchListenRequest#listen为falseRpcClient rpcClient = ensureRpcClient(taskId);boolean removeSuccess = unListenConfigChange(rpcClient, configChangeListenRequest);if (removeSuccess) {for (CacheData cacheData : removeListenCaches) {synchronized (cacheData) {if (cacheData.getListeners().isEmpty()) {// 移除本地缓存ClientWorker.this.removeCache(cacheData.dataId, cacheData.group, cacheData.tenant);}}}}} catch (Exception e) {LOGGER.error("async remove listen config change error ", e);}try {Thread.sleep(50L);} catch (InterruptedException interruptedException) {//ignore}}}if (needAllSync) {lastAllSyncTime = now;}//If has changed keys,notify re sync md5.if (hasChangedKeys) { // key有变化触发下一轮notifyListenConfig();}
}

校验MD5

当CacheData从server同步后,会校验md5是否变更了,当变更时会回调到我们注册的Listener完成通知。通知任务被封装成Runnable任务,执行线程池可以自定义,默认为5个线程。

void checkListenerMd5() {for (ManagerListenerWrap wrap : listeners) {if (!md5.equals(wrap.lastCallMd5)) { // 配置内容有变更时,回调到]Listener中。safeNotifyListener(dataId, group, content, type, md5, wrap);}}
}
private void safeNotifyListener(final String dataId, final String group, final String content, final String type,final String md5, final ManagerListenerWrap listenerWrap) {final Listener listener = listenerWrap.listener;if (listenerWrap.inNotifying) {// ...return;}Runnable job = new Runnable() {@Overridepublic void run() {long start = System.currentTimeMillis();ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();ClassLoader appClassLoader = listener.getClass().getClassLoader();try {if (listener instanceof AbstractSharedListener) {AbstractSharedListener adapter = (AbstractSharedListener) listener;adapter.fillContext(dataId, group);// ...}Thread.currentThread().setContextClassLoader(appClassLoader);ConfigResponse cr = new ConfigResponse();cr.setDataId(dataId);cr.setGroup(group);cr.setContent(content);// filter拦截继续过滤configFilterChainManager.doFilter(null, cr);String contentTmp = cr.getContent();listenerWrap.inNotifying = true;// 回调注册Listener的receiveConfigInfo方法或者receiveConfigChange逻辑listener.receiveConfigInfo(contentTmp);// compare lastContent and contentif (listener instanceof AbstractConfigChangeListener) {Map data = ConfigChangeHandler.getInstance().parseChangeData(listenerWrap.lastContent, content, type);ConfigChangeEvent event = new ConfigChangeEvent(data);// 回调变更事件方法((AbstractConfigChangeListener) listener).receiveConfigChange(event);listenerWrap.lastContent = content;}listenerWrap.lastCallMd5 = md5;// ..} catch (NacosException ex) {// ...} catch (Throwable t) {// ...} finally {listenerWrap.inNotifying = false;Thread.currentThread().setContextClassLoader(myClassLoader);}}};final long startNotify = System.currentTimeMillis();try {// 优先使用我们示例中注册提供的线程池执行job,如果没有设置使用默认线程池「INTERNAL_NOTIFIER」,默认5个线程if (null != listener.getExecutor()) {listener.getExecutor().execute(job);} else {try {INTERNAL_NOTIFIER.submit(job); // 默认线程池执行,为5个线程} catch (RejectedExecutionException rejectedExecutionException) {// ...job.run();} catch (Throwable throwable) {// ...job.run();}}} catch (Throwable t) {// ...}final long finishNotify = System.currentTimeMillis();// ...
}

key有变更

注册Listener后,会构建与server的RPC通道rpcClient;向server发起变更查询请求configChangeListenRequest,server端通过比较缓存的md5值,返回client变更的key列表;client通过变更的key列表向server发起配置查询请求ConfigQueryRequest,获取变更内容,并回调我们注册的Listener。

private void refreshContentAndCheck(CacheData cacheData, boolean notify) {try {// 向server发起ConfigQueryRequest,查询配置内容String[] ct = getServerConfig(cacheData.dataId, cacheData.group, cacheData.tenant, 3000L, notify);//设置最新的内容信息cacheData.setContent(ct[0]);if (null != ct[1]) {cacheData.setType(ct[1]);}if (notify) { // 记录日志// ...}// 回调注册的Listener逻辑cacheData.checkListenerMd5();} catch (Exception e) {//...}
}

总结

客户端在启动的时候会构建一个ConfigService的处理类,然后再ConfigService的构造,方法中会创建一个ClientWorker 用来处理对服务端的网络通信及后续变更处理,
当服务端有配置变更的时候会发送配置变更事件最终会往一个阻塞队列中取offer数据,然后ClientWorker启动的时候会构建一个定时线程去从这个阻塞队列中阻塞拿数据 如果队列为空等待5秒后执行,如果队列不为空立即执行 然后会将3000个CacheDate其实就是配置数据组成一个taskId 然后往服务端发送grpc请求,服务端会检查 md5比较哪些配置发生了变更 然后会返回发生变更的key列表,然后客户端根据服务端返回的key列表 去服务端拉取最新的配置信息 然后缓存到本地

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/212352.shtml

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

相关文章

MongoDB的连接数据库,创建、删除数据库,创建、删除集合命令

本文主要介绍MongoDB的连接数据库&#xff0c;创建、删除数据库&#xff0c;创建、删除集合命令。 目录 MongoDB连接数据库连接到本地 MongoDB 实例连接到远程 MongoDB 实例 MongoDB创建和删除数据库MongoDB创建和删除集合创建集合删除集合 MongoDB连接数据库 连接 MongoDB 数…

P1317 低洼地题解

题目 一组数&#xff0c;分别表示地平线的高度变化。高度值为整数&#xff0c;相邻高度用直线连接。找出并统计有多少个可能积水的低洼地&#xff1f; 如图&#xff1a;地高变化为 [0,1,0,2,1,2,0,0,2,0]。 输入输出格式 输入格式 两行&#xff0c;第一行n, 表示有n个数。第…

Spark DataFrame和Dataset使用例子

文章目录 1、基本操作1.1、创建SparkSession1.2、创建DataFrames1.3、创建Dataset操作1.4、运行sql查询1.5、创建全局临时视图1.6、创建Datasets1.7、与rdd进行互操作1.7.1、使用反射推断模式1.7.2、以编程方式指定模式 2、完整的测试例子 1、基本操作 1.1、创建SparkSession …

openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup

文章目录 openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup151.1 背景信息151.2 前提条件151.3 语法151.4 示例151.5 从备份文件恢复数据 openGauss学习笔记-151 openGauss 数据库运维-备份与恢复-物理备份与恢复之gs_basebackup 151.1 …

NeuralKG运行备忘

环境配置&#xff1a; conda create -n neuralkg python3.8 conda activate neuralkg pip install torch1.9.1cu111 -f https://download.pytorch.org/whl/torch_stable.html pip install dgl-cu111 dglgo -f https://data.dgl.ai/wheels/repo.html pip install neuralkg! co…

基于java swing 药品销售管理系统

大家好&#xff0c;我是DeBug&#xff0c;很高兴你能来阅读&#xff01;作为一名热爱编程的程序员&#xff0c;我希望通过这些教学笔记与大家分享我的编程经验和知识。在这里&#xff0c;我将会结合实际项目经验&#xff0c;分享编程技巧、最佳实践以及解决问题的方法。无论你是…

短视频账号剪辑矩阵+无人直播系统源头开发

抖去推爆款视频生成器&#xff0c;通过短视频矩阵、无人直播&#xff0c;文案引流等&#xff0c;打造实体商家员工矩阵、用户矩阵、直播矩阵&#xff0c;辅助商家品牌曝光&#xff0c;团购转化等多功能赋能商家拓客引流。 短视频矩阵通俗来讲就是批量剪辑视频和批量发布视频&am…

Multisim电路仿真软件使用教程

安装直接参考这篇文章&#xff1a;Multisim 14.0安装教程 软件管家公众号里有很多软件&#xff0c;需要的可以去找下然后安装&#xff0c;这里用的是14.0版本。 这里有个大神的详细教程&#xff0c;可以参考&#xff1a; Multisim软件使用详细入门教程&#xff08;图文全解&…

Java Docker 生产环境部署

1. 引言 随着容器化技术的广泛应用&#xff0c;Docker成为了一种非常流行的容器化解决方案。Java作为一种跨平台的编程语言&#xff0c;在生产环境中也广泛使用。本文将介绍如何使用Docker来部署Java应用程序&#xff0c;并探讨一些最佳实践和注意事项。 2. Docker简介 Dock…

Python房价分析(二)随机森林分类模型

目录 1 数据预处理 1.1 房价数据介绍 1.2 数据预处理 1.2.1 缺失值处理 1.2.2异常值处理 1.2.3 数据归一化 1.2.4 分类特征编码 2 随机森林模型 2.1 模型概述 2.2 建模步骤 2.3 参数搜索过程 3模型评估 3.1 模型评估结果 3.2 混淆矩阵 3.3 绘制房价类别三分类的…

面试官:性能测试瓶颈调优你是真的会吗?

引言&#xff1a;性能瓶颈调优 在实际的性能测试中&#xff0c;会遇到各种各样的问题&#xff0c;比如 TPS 压不上去等&#xff0c;导致这种现象的原因有很多&#xff0c;测试人员应配合开发人员进行分析&#xff0c;尽快找出瓶颈所在。 理想的性能测试指标结果可能不是很高&…

Linux内核--内存管理(六)补充--进程页表

目录 一、引言 二、页表 ------>2.1、页表的大小 ------>2.2、页表起始地址 ------>2.3、CPU调度 ------>2.4、用户态访问虚拟地址 ------>2.5、页表组成部分 ------------>2.5.1、进程用户态页表 ------------>2.5.2、内核态页表 ------>2.…

c++学习之异常

前言 早在c语言的时候&#xff0c;就已经有处理错误的方式了&#xff0c;第一种方式太过暴力&#xff0c;就是断言&#xff0c;程序发生错误&#xff0c;直接终止退出&#xff0c;这样的报错对于真正开发应用等太过暴力。第二种方式&#xff0c;就是返回errno&#xff0c;其实&…

Latex公式中矩阵的方括号和圆括号表示方法

一、背景 在使用Latex写论文时&#xff0c;不可避免的涉及到矩阵公式。有的期刊要求矩阵用方括号&#xff0c;有的期刊要求矩阵用圆括号。因此&#xff0c;特记录一下Latex源码在两种表示方法上的区别&#xff0c;以及数组和方程组的扩展。 二、矩阵的方括号表示 首先所有的…

OpenGLES:glReadPixels()获取相机GLSurfaceView预览数据并保存

Android现行的Camera API2机制可以通过onImageAvailable(ImageReader reader)回调从底层获取到Jpeg、Yuv和Raw三种格式的Image&#xff0c;然后通过保存Image实现拍照功能&#xff0c;但是却并没有Api能直接在上层直接拿到实时预览的数据。 Android Camera预览的实现是上层下发…

Java学习笔记——instanceof关键字

instanceof关键字&#xff1a; 作用&#xff1a;保证对象向下转型的安全性在对象向下转型前判断某一对象实例是否属于某个类 判断时&#xff0c;如果对象是null&#xff0c;则 instanceof 判断结果为 false

Spring Boot 整合kafka:生产者ack机制和消费者AckMode消费模式、手动提交ACK

目录 生产者ack机制消费者ack模式手动提交ACK 生产者ack机制 Kafka 生产者的 ACK 机制指的是生产者在发送消息后&#xff0c;对消息副本的确认机制。ACK 机制可以帮助生产者确保消息被成功写入 Kafka 集群中的多个副本&#xff0c;并在需要时获取确认信息。 Kafka 提供了三种…

ei源刊和ei会议的几个区别

1、含义不同 公开发表论文&#xff0c;可以在期刊上刊登&#xff0c;也可以在会议上宣读。ei源刊对应的是期刊&#xff0c;是指被ei检索收录的工程类的期刊。ei会议对应的是会议&#xff0c;是指被ei检索收录的会议。 2、检索类型不同 期刊和会议都能被ei检索&#xff0c;但…

Tr0ll

信息收集 探测主机存活信息&#xff1a; nmap -sn --min-rate 10000 192.168.182.0/24Starting Nmap 7.94 ( https://nmap.org ) at 2023-11-14 15:45 CST Nmap scan report for 192.168.182.1 Host is up (0.00026s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap…

qt 双缓冲机制

在图形编程中&#xff0c;双缓冲机制是一种常用的技术&#xff0c;用于减少图形绘制时的闪烁和抖动。它的基本思想是将图形绘制到一个后台缓冲中&#xff0c;然后一次性将后台缓冲的内容显示到屏幕上。 在 Qt 中&#xff0c;双缓冲机制可以通过QPainter的begin()和end()方法来实…