Nacos源码—8.Nacos升级gRPC分析六

大纲

7.服务端对服务实例进行健康检查

8.服务下线如何注销注册表和客户端等信息

9.事件驱动架构源码分析

一.处理ClientChangedEvent事件

也就是同步数据到集群节点:

public class DistroClientDataProcessor extends SmartSubscriber implements DistroDataStorage, DistroDataProcessor {...@Overridepublic void onEvent(Event event) {...if (event instanceof ClientEvent.ClientVerifyFailedEvent) {syncToVerifyFailedServer((ClientEvent.ClientVerifyFailedEvent) event);} else {syncToAllServer((ClientEvent) event);}}private void syncToAllServer(ClientEvent event) {Client client = event.getClient();//Only ephemeral data sync by Distro, persist client should sync by raft.//临时实例使用Distro协议,持久化实例使用Raft协议//ClientManager.isResponsibleClient()方法,判断只有该client的责任节点才能进行集群数据同步if (null == client || !client.isEphemeral() || !clientManager.isResponsibleClient(client)) {return;}if (event instanceof ClientEvent.ClientDisconnectEvent) {//如果event是客户端注销实例时需要进行集群节点同步的事件DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);distroProtocol.sync(distroKey, DataOperation.DELETE);} else if (event instanceof ClientEvent.ClientChangedEvent) {//如果event是客户端注册实例时需要进行集群节点同步的事件DistroKey distroKey = new DistroKey(client.getClientId(), TYPE);distroProtocol.sync(distroKey, DataOperation.CHANGE);}}...
}@Component
public class DistroProtocol {private final ServerMemberManager memberManager;private final DistroTaskEngineHolder distroTaskEngineHolder;...//Start to sync by configured delay.public void sync(DistroKey distroKey, DataOperation action) {sync(distroKey, action, DistroConfig.getInstance().getSyncDelayMillis());}//Start to sync data to all remote server.public void sync(DistroKey distroKey, DataOperation action, long delay) {//遍历集群中除自身节点外的其他节点for (Member each : memberManager.allMembersWithoutSelf()) {syncToTarget(distroKey, action, each.getAddress(), delay);}}//Start to sync to target server.public void syncToTarget(DistroKey distroKey, DataOperation action, String targetServer, long delay) {//先把要同步的集群节点targetServer包装成DistroKey对象DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(), targetServer);//然后根据DistroKey对象创建DistroDelayTask任务DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);//接着调用NacosDelayTaskExecuteEngine.addTask()方法//往延迟任务执行引擎DistroDelayTaskExecuteEngine中添加延迟任务DistroDelayTaskdistroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);if (Loggers.DISTRO.isDebugEnabled()) {Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, targetServer);}}...
}

二.处理ClientDeregisterServiceEvent事件

也就是移除注册表 + 订阅表的服务实例:

@Component
public class ClientServiceIndexesManager extends SmartSubscriber {//注册表(服务提供者),一个Service服务对象,对应多个服务实例的clientIdprivate final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();//订阅者列表(服务消费者),一个Service服务对象,对应多个订阅者的clientIdprivate final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();...@Overridepublic void onEvent(Event event) {if (event instanceof ClientEvent.ClientDisconnectEvent) {handleClientDisconnect((ClientEvent.ClientDisconnectEvent) event);} else if (event instanceof ClientOperationEvent) {handleClientOperation((ClientOperationEvent) event);}}private void handleClientOperation(ClientOperationEvent event) {Service service = event.getService();String clientId = event.getClientId();if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {//处理客户端注册事件ClientRegisterServiceEventaddPublisherIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {//处理客户端注销事件ClientDeregisterServiceEventremovePublisherIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {//处理客户端订阅服务事件ClientSubscribeServiceEventaddSubscriberIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {//处理客户端取消订阅事件ClientUnsubscribeServiceEventremoveSubscriberIndexes(service, clientId);}}private void removePublisherIndexes(Service service, String clientId) {if (!publisherIndexes.containsKey(service)) {return;}//移除注册表中的服务实例publisherIndexes.get(service).remove(clientId);//发布服务改变事件ServiceChangedEventNotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));}...
}

三.处理ServiceChangeEvent事件

也就是通知订阅了该服务的客户端:

@org.springframework.stereotype.Service
public class NamingSubscriberServiceV2Impl extends SmartSubscriber implements NamingSubscriberService {...@Overridepublic void onEvent(Event event) {if (!upgradeJudgement.isUseGrpcFeatures()) {return;}if (event instanceof ServiceEvent.ServiceChangedEvent) {//If service changed, push to all subscribers.//如果服务变动,会向Service服务的所有订阅者推送Service服务的实例信息,让订阅者(客户端)更新本地缓存ServiceEvent.ServiceChangedEvent serviceChangedEvent = (ServiceEvent.ServiceChangedEvent) event;Service service = serviceChangedEvent.getService();//调用NacosDelayTaskExecuteEngine.addTask()方法,往延迟任务执行引擎添加任务delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay()));} else if (event instanceof ServiceEvent.ServiceSubscribedEvent) {//If service is subscribed by one client, only push this client.//如果Service服务被一个客户端订阅,则只推送Service服务的实例信息给该客户端ServiceEvent.ServiceSubscribedEvent subscribedEvent = (ServiceEvent.ServiceSubscribedEvent) event;Service service = subscribedEvent.getService();//调用NacosDelayTaskExecuteEngine.addTask()方法,往延迟任务执行引擎添加任务delayTaskEngine.addTask(service, new PushDelayTask(service, PushConfig.getInstance().getPushTaskDelay(), subscribedEvent.getClientId()));}}...
}

(3)总结

9.事件驱动架构源码分析

(1)如何使用Nacos的事件发布

(2)Nacos通知中心的事件发布源码

(3)Nacos通知中心注册订阅者的源码

Nacos 2.x大量使用了事件发布的动作,比如客户端注册服务实例、客户端下线服务实例、服务改变、服务订阅等。

(1)如何使用Nacos的事件发布

一.首先自定义一个事件

下面定义了一个名为TestEvent的事件,继承自Nacos的Event类。

import com.alibaba.nacos.common.notify.Event;public class TestEvent extends Event {}

二.然后定义一个订阅者

有了事件之后,还需要一个订阅者,这样发布的事件才能被这个订阅者进行处理。

自定义的订阅者需要继承Nacos的SmartSubscriber抽象类,自定义的订阅者需要实现三个方法。

方法一:构造方法

需要将自定义的订阅者注册到Nacos的通知中心NotifyCenter里,这样NotifyCenter在发布自定义事件时,才能让自定义的订阅者进行响应。

方法二:subscribeTypes()方法

实现该方法时,需要把自定义的事件添加到方法的返回结果中,所以可以通过该方法获取自定义订阅者监听了哪些事件。

方法三:onEvent()方法

Nacos的通知中心NotifyCenter在发布自定义事件时,便会调用该方法,所以该方法中需要实现自定义订阅者对自定义事件的处理。

import com.alibaba.nacos.common.notify.Event;
import com.alibaba.nacos.common.notify.NotifyCenter;
import com.alibaba.nacos.common.notify.listener.SmartSubscriber;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.List;//自定义的订阅者需要继承Nacos的SmartSubscriber抽象类
@Component
public class TestSubscriber extends SmartSubscriber {//构造方法中需要将自定义的订阅者TestSubscriber注册到Nacos的通知中心NotifyCenterpublic TestSubscriber() {NotifyCenter.registerSubscriber(this);}//实现subscribeTypes()方法时,把自定义的事件TestEvent添加进去返回@Overridepublic List<Class<? extends Event>> subscribeTypes() {List<Class<? extends Event>> result = new LinkedList<>();result.add(TestEvent.class);return result;}//实现onEvent()方法//当Nacos的通知中心NotifyCenter发布一个TestEvent事件时,就会响应该方法处理订阅者的逻辑@Overridepublic void onEvent(Event event) {System.out.println("TestSubscriber onEvent");}
}

三.最后通过Nacos的通知中心NotifyCenter发布自定义事件

这样便完成了自定义事件、自定义订阅者通过Nacos实现发布订阅功能。

@RestController
@RequestMapping("/sub/")
public class SubscriberController {@GetMapping("/test")public void test() {NotifyCenter.publishEvent(new TestEvent());    }
}

(2)Nacos通知中心的事件发布源码

通知中心NotifyCenter执行publishEvent()方法发布事件时,比如会调用DefaultPublisher的publish()方法来发布事件。

DefaultPublisher的publish()方法会先把事件放入到一个阻塞队列queue中,而在DefaultPublisher创建时会启动一个线程从阻塞队列取出事件来处理。处理时就会调用到DefaultPublisher的receiveEvent()方法通知事件订阅者,也就是执行DefaultPublisher的notifySubscriber()方法通知事件订阅者。

在DefaultPublisher的notifySubscriber()方法中,首先会创建一个调用订阅者的onEvent()方法的任务,然后如果订阅者有线程池,则将任务提交给订阅者的线程池去执行。如果订阅者没有线程池,则直接执行该任务。

可见事件的发布也使用了阻塞队列 + 异步任务,来实现对订阅者的通知。

public class NotifyCenter {private static final NotifyCenter INSTANCE = new NotifyCenter();//key是事件Class的canonicalName,value是EventPublisher对象,一个事件对应一个EventPublisher对象//在EventPublisher对象中就会包含订阅了该事件的所有订阅者//EventPublisher的实现类有DefaultPublisher、NamingEventPublisherprivate final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);...//Request publisher publish event Publishers load lazily, calling publisher. Start () only when the event is actually published.public static boolean publishEvent(final Event event) {try {return publishEvent(event.getClass(), event);} catch (Throwable ex) {LOGGER.error("There was an exception to the message publishing : ", ex);return false;}}//Request publisher publish event Publishers load lazily, calling publisher.private static boolean publishEvent(final Class<? extends Event> eventType, final Event event) {if (ClassUtils.isAssignableFrom(SlowEvent.class, eventType)) {return INSTANCE.sharePublisher.publish(event);}//获取发布的事件的Class的canonicalNamefinal String topic = ClassUtils.getCanonicalName(eventType);//根据发布事件类型获取EventPublisher对象,该对象中会包含所发布事件的所有订阅者信息EventPublisher publisher = INSTANCE.publisherMap.get(topic);if (publisher != null) {//比如调用DefaultPublisher.publish()方法发布事件return publisher.publish(event);}LOGGER.warn("There are no [{}] publishers for this event, please register", topic);return false;}...
}//The default event publisher implementation.
//一个事件只会对应一个DefaultPublisher
public class DefaultPublisher extends Thread implements EventPublisher {private Class<? extends Event> eventType;//阻塞队列存放待发布的事件private BlockingQueue<Event> queue;//Class为eventType的事件的所有订阅者protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();@Overridepublic void init(Class<? extends Event> type, int bufferSize) {...start();}@Overridepublic synchronized void start() {if (!initialized) {super.start();...}}@Overridepublic void run() {openEventHandler();}void openEventHandler() {try {...for (; ;) {...//从阻塞队列取数据final Event event = queue.take();//处理事件receiveEvent(event);...}} catch (Throwable ex) {LOGGER.error("Event listener exception : ", ex);}}...@Overridepublic boolean publish(Event event) {checkIsStart();//把事件放入到了一个阻塞队列queue中,由DefaultPublisher创建时启动的线程来处理boolean success = this.queue.offer(event);if (!success) {//如果事件放入阻塞队列失败,则直接处理LOGGER.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);//通知事件的订阅者去进行事件处理receiveEvent(event);return true;}return true;}//通知事件的订阅者去进行事件处理void receiveEvent(Event event) {...//遍历当前事件的订阅者,对订阅者执行notifySubscriber()方法,实际上就是执行订阅者的onEvent()方法for (Subscriber subscriber : subscribers) {...//触发执行订阅者的onEvent()方法,实现对订阅者的通知notifySubscriber(subscriber, event);}}@Overridepublic void notifySubscriber(final Subscriber subscriber, final Event event) {//创建一个任务,该任务会调用订阅者的onEvent方法final Runnable job = () -> subscriber.onEvent(event);final Executor executor = subscriber.executor();if (executor != null) {//将任务提交给订阅者的线程池去执行executor.execute(job);} else {try {//如果订阅者没有线程池,则直接执行该任务job.run();} catch (Throwable e) {LOGGER.error("Event callback exception: ", e);}}}...
}

(3)Nacos通知中心注册订阅者的源码

在执行NotifyCenter的registerSubscriber()方法注册订阅者时,会调用订阅者实现的subscribeTypes()方法获取订阅者要监听的所有事件,然后遍历这些事件并调用NotifyCenter的addSubscriber()方法。

执行NotifyCenter的addSubscriber()方法时会为这些事件添加订阅者。由于每个事件都会对应一个EventPublisher对象,所以会先从NotifyCenter.publisherMap中获取EventPublisher对象,然后调用EventPublisher的addSubscriber()方法向EventPublisher添加订阅者,从而完成向通知中心注册订阅者。

public class NotifyCenter {private static final NotifyCenter INSTANCE = new NotifyCenter();//key是事件Class的canonicalName,value是EventPublisher对象,一个事件对应一个EventPublisher对象//在EventPublisher对象中就会包含订阅了该事件的所有订阅者//EventPublisher的实现类有DefaultPublisher、NamingEventPublisherprivate final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap<>(16);...public static void registerSubscriber(final Subscriber consumer) {//注册订阅者registerSubscriber(consumer, DEFAULT_PUBLISHER_FACTORY);}public static void registerSubscriber(final Subscriber consumer, final EventPublisherFactory factory) {if (consumer instanceof SmartSubscriber) {//调用subscribeTypes()方法获取订阅者consumer需要监听的事件,然后对这些事件进行遍历for (Class<? extends Event> subscribeType : ((SmartSubscriber) consumer).subscribeTypes()) {//For case, producer: defaultSharePublisher -> consumer: smartSubscriber.if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {//添加订阅者INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);} else {//For case, producer: defaultPublisher -> consumer: subscriber.//添加订阅者addSubscriber(consumer, subscribeType, factory);}}return;}final Class<? extends Event> subscribeType = consumer.subscribeType();if (ClassUtils.isAssignableFrom(SlowEvent.class, subscribeType)) {INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);return;}addSubscriber(consumer, subscribeType, factory);}//Add a subscriber to publisher.private static void addSubscriber(final Subscriber consumer, Class<? extends Event> subscribeType, EventPublisherFactory factory) {//获取订阅的事件的Class的canonicalNamefinal String topic = ClassUtils.getCanonicalName(subscribeType);synchronized (NotifyCenter.class) {//MapUtils.computeIfAbsent is a unsafe method.//创建EventPublisher对象,一个事件会对应一个EventPublisher对象MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, factory, subscribeType, ringBufferSize);}//获取事件对应的EventPublisher对象,比如DefaultPublisher对象EventPublisher publisher = INSTANCE.publisherMap.get(topic);if (publisher instanceof ShardedEventPublisher) {((ShardedEventPublisher) publisher).addSubscriber(consumer, subscribeType);} else {//往EventPublisher对象添加订阅者信息,比如调用DefaultPublisher.addSubscriber()方法publisher.addSubscriber(consumer);}}...
}//一个事件只会对应一个DefaultPublisher
public class DefaultPublisher extends Thread implements EventPublisher {private Class<? extends Event> eventType;//Class为eventType的事件的所有订阅者protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet<>();...@Overridepublic void addSubscriber(Subscriber subscriber) {//添加订阅者subscribers.add(subscriber);}...
}

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

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

相关文章

设计杂谈-工厂模式

“工厂”模式在各种框架中非常常见&#xff0c;包括 MyBatis&#xff0c;它是一种创建对象的设计模式。使用工厂模式有很多好处&#xff0c;尤其是在复杂的框架中&#xff0c;它可以带来更好的灵活性、可维护性和可配置性。 让我们以 MyBatis 为例&#xff0c;来理解工厂模式及…

AI与IoT携手,精准农业未来已来

AIoT&#xff1a;农业领域的变革先锋 在科技飞速发展的当下&#xff0c;人工智能&#xff08;AI&#xff09;与物联网&#xff08;IoT&#xff09;的融合 ——AIoT&#xff0c;正逐渐成为推动各行业变革的关键力量&#xff0c;农业领域也不例外。AIoT 技术通过将 AI 的智能分析…

排错-harbor-db容器异常重启

排错-harbor-db容器异常重启 环境&#xff1a; docker 19.03 , harbor-db(postgresql) goharbor/harbor-db:v2.5.6 现象&#xff1a; harbor-db 容器一直restart&#xff0c;查看日志发现 报错 initdb: error: directory "/var/lib/postgresql/data/pg13" exists…

Docker容器启动失败?无法启动?

Docker容器无法启动的疑难杂症解析与解决方案 一、问题现象 Docker容器无法启动是开发者在容器化部署中最常见的故障之一。尽管Docker提供了丰富的调试工具&#xff0c;但问题的根源往往隐藏在复杂的配置、环境依赖或资源限制中。本文将从环境变量配置错误这一细节问题入手&am…

查看购物车

一.查看购物车 查看购物车使用get请求。我们要查看当前用户的购物车&#xff0c;就要获取当前用户的userId字段进行条件查询。因为在用户登录时就已经将userId封装在token中了&#xff0c;因此我们只需要解析token获取userId即可&#xff0c;不需要前端再传入参数了。 Control…

C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )

一、引言&#xff1a;理解内存管理的核心价值 在系统级编程领域&#xff0c;内存管理是决定程序性能、稳定性和安全性的关键因素。C/C 作为底层开发的主流语言&#xff0c;赋予开发者直接操作内存的能力&#xff0c;却也要求开发者深入理解内存布局与生命周期管理。本文将从内…

使用Stable Diffusion(SD)中CFG参数指的是什么?该怎么用!

1.定义&#xff1a; CFG参数控制模型在生成图像时&#xff0c;对提示词&#xff08;Prompt&#xff09;的“服从程度”。 它衡量模型在“完全根据提示词生成图像”和“自由生成图像”&#xff08;不参考提示词&#xff09;之间的权衡程度。 数值范围&#xff1a;常见范围是 1 …

【GESP】C++三级练习 luogu-B2156 最长单词 2

GESP三级练习&#xff0c;字符串练习&#xff08;C三级大纲中6号知识点&#xff0c;字符串&#xff09;&#xff0c;难度★★☆☆☆。 题目题解详见&#xff1a;https://www.coderli.com/gesp-3-luogu-b2156/ 【GESP】C三级练习 luogu-B2156 最长单词 2 | OneCoderGESP三级练…

Linux网络基础 -- 局域网,广域网,网络协议,网络传输的基本流程,端口号,网络字节序

目录 1. 计算机网络背景 1.1 局域网 1.1.2 局域网的组成 1.2 广域网 1.1.2 广域网的组成 2. 初始网络协议 2.1 网络协议的定义和作用 2.2 网络协议的分层结构 2.2.1 OSI七层模型 2.2.2 TCP/IP 五层&#xff08;四层&#xff09;模型 3. 再识网络协议 3.1 为什么要有…

【PostgreSQL】超简单的主从节点部署

1. 启动数据库 启动主节点 docker run --name postgres-master -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres启动从节点 docker run --name postgres-slave -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres需要配置挂载的存储卷 2. 数据…

c#修改ComboBox当前选中项的文本

对于一个C#的Combobox列表&#xff0c;类型设置为下拉样式&#xff0c;不允许输入&#xff0c;只能选择&#xff0c;样子如下&#xff1a; 该控件的名字为 cbb1&#xff0c;如果要修改当前这个“A1区”的文件&#xff0c;则用如下方式&#xff1a; cbb1.Items[cbb1.SelectedInd…

Java设计模式之适配器模式:从入门到精通

适配器模式(Adapter Pattern)是Java中最常用的结构型设计模式之一,它像一座桥梁连接两个不兼容的接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。本文将全面深入地解析适配器模式,从基础概念到高级应用,包含丰富的代码示例、详细注释、使用场景分析以及多维对…

中国黄土高原中部XF剖面磁化率和粒度数据

时间分辨率&#xff1a;1000年 < x空间分辨率为&#xff1a;空共享方式&#xff1a;申请获取数据大小&#xff1b;35.75 KB数据时间范围&#xff1a;743-0 ka元数据更新时间&#xff1a;2023-08-15 数据集摘要 该数据集包括中国黄土高原中部XF剖面磁化率和粒度数据。将所有…

【Python训练营打卡】day23 @浙大疏锦行

test pipeline管道 知识回顾: 1. 转化器和估计器的概念 2. 管道工程 3. ColumnTransformer和Pipeline类 作业&#xff1a; 整理下全部逻辑的先后顺序&#xff0c;看看能不能制作出适合所有机器学习的通用pipeline 伪代码 # 适合所有机器学习的通用pipeline #伪代码 import p…

【android bluetooth 框架分析 02】【Module详解 13】【CounterMetrics 模块介绍】

1. CounterMetrics 介绍 CounterMetrics 模块代码很少&#xff0c; 我简单介绍一下。 // system/gd/metrics/counter_metrics.cc #define LOG_TAG "BluetoothCounterMetrics"#include "metrics/counter_metrics.h"#include "common/bind.h" #i…

QMK键盘固件配置详解

QMK键盘固件配置详解 前言 大家好&#xff01;今天给大家带来QMK键盘固件配置的详细指南。如果你正在DIY机械键盘或者想要给自己的键盘刷固件&#xff0c;这篇文章绝对不容错过。QMK是目前最流行的开源键盘固件框架之一&#xff0c;它允许我们对键盘进行高度自定义。接下来&a…

基于STM32、HAL库的DPS368XTSA1气压传感器 驱动程序设计

一、简介: DPS368XTSA1 是 InvenSense(TDK 集团旗下公司)生产的一款高精度数字气压传感器,专为需要精确测量气压和温度的应用场景设计。它具有超低功耗、高精度、快速响应等特点,非常适合物联网、可穿戴设备和无人机等应用。 二、硬件接口: DPS368XTSA1 引脚STM32L4XX 引…

因子分析——数学原理及R语言代码

正交因子分析 目的数学原理参数估计方法主成分法主因子法极大似然法 因子旋转模型检验因子得分加权最小二乘法回归法 代码实现注意事项例子 Reference 目的 FactorAnalysis的目的是从多个高度相关的观测变量中提取出少数几个LatentFactor&#xff0c;这些因子代表了变量背后的…

ACL访问控制列表:access-list 10 permit 192.168.10.1

ACL访问控制列表 标准ACL语法 1. 创建ACL access-list <编号> <动作> <源IP> <通配符掩码> // 编号范围 1-99 // 动作&#xff1a;permit 允许 、 deny 拒绝2. 示例 //允许192.168.1.0/24g整个网络,0.0.0.255 反掩码 access-list 10 permit 192.1…

解决社区录音应用横屏状态下,录音后无法播放的bug

最近看到社区有小伙伴反映&#xff0c;社区录音应用横屏时&#xff0c;录音后无法播放的问题。现分享解决办法。 社区录音应用的来源&#xff1a;https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-5.0.2-Release/code/SystemFeature/Media/Recorder …