Nacos源码解读04——服务发现

Nacos服务发现的方式

1.客户端获取
1.1:先是故障转移机制判断是否去本地文件中读取信息,读到则返回
1.2:再去本地服务列表读取信息(本地缓存),没读到则创建一个空的服务,然后立刻去nacos中读取更新
1.3:读到了就返回,同时开启定时更新,定时向服务端同步信息 (正常1s,异常最多60s一次)
2.服务端通过GRPC推送
建立长连接、当服务发现变更的时候往订阅了服务的客户端推送事件

SpringBoot自动注入

项目启动的时候会通过自动注入的机制将
NacosDiscoveryClientConfiguration注入在这里插入图片描述
当注入NacosDiscoveryClientConfiguration的时候会将DiscoveryClient一起注入Bean
DiscoveryClient实现了SpringCloud的DiscoveryClient接口,重点是getInstances和getServices方法,而且都是由NacosServiceDiscovery实现
在这里插入图片描述

获取实例信息

NacosDiscoveryClient

	private NacosServiceDiscovery serviceDiscovery;public NacosDiscoveryClient(NacosServiceDiscovery nacosServiceDiscovery) {this.serviceDiscovery = nacosServiceDiscovery;}@Overridepublic List<ServiceInstance> getInstances(String serviceId) {try {return serviceDiscovery.getInstances(serviceId);}catch (Exception e) {throw new RuntimeException("Can not get hosts from nacos server. serviceId: " + serviceId, e);}}
	public List<ServiceInstance> getInstances(String serviceId) throws NacosException {String group = discoveryProperties.getGroup();List<Instance> instances = namingService().selectInstances(serviceId, group,true);return hostToServiceInstanceList(instances, serviceId);}

NacosServiceDiscovery

	public List<ServiceInstance> getInstances(String serviceId) throws NacosException {//获取分组String group = discoveryProperties.getGroup();//查询服务下的实例List<Instance> instances = namingService().selectInstances(serviceId, group,true);//填充返回的实例信息数据return hostToServiceInstanceList(instances, serviceId);}
    @Overridepublic List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {return selectInstances(serviceName, groupName, healthy, true);}
    @Overridepublic List<Instance> selectInstances(String serviceName, String groupName, boolean healthy, boolean subscribe)throws NacosException {return selectInstances(serviceName, groupName, new ArrayList<String>(), healthy, subscribe);}
    @Overridepublic List<Instance> selectInstances(String serviceName, String groupName, List<String> clusters, boolean healthy,boolean subscribe) throws NacosException {ServiceInfo serviceInfo;String clusterString = StringUtils.join(clusters, ",");// 默认是订阅的if (subscribe) {//从缓存中获取实例信息serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString);//如果获取不到则从服务端拉取if (null == serviceInfo) {serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString);}} else {// 如果未订阅服务信息,则直接从服务器进行查询serviceInfo = clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, 0, false);}//获取服务中的实例信息return selectInstances(serviceInfo, healthy);}

从缓存中拿数据

    public ServiceInfo getServiceInfo(final String serviceName, final String groupName, final String clusters) {NAMING_LOGGER.debug("failover-mode: {}", failoverReactor.isFailoverSwitch());String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);String key = ServiceInfo.getKey(groupedServiceName, clusters);if (failoverReactor.isFailoverSwitch()) {return failoverReactor.getService(key);}return serviceInfoMap.get(key);}

获取服务的实例信息

    private List<Instance> selectInstances(ServiceInfo serviceInfo, boolean healthy) {List<Instance> list;if (serviceInfo == null || CollectionUtils.isEmpty(list = serviceInfo.getHosts())) {return new ArrayList<Instance>();}Iterator<Instance> iterator = list.iterator();while (iterator.hasNext()) {Instance instance = iterator.next();// 保留 健康、启用、权重大于0 的实例if (healthy != instance.isHealthy() || !instance.isEnabled() || instance.getWeight() <= 0) {iterator.remove();}}return list;}

GRPC请求拉取服务实例信息

    @Overridepublic ServiceInfo subscribe(String serviceName, String groupName, String clusters) throws NacosException {NAMING_LOGGER.info("[SUBSCRIBE-SERVICE] service:{}, group:{}, clusters:{} ", serviceName, groupName, clusters);String serviceNameWithGroup = NamingUtils.getGroupedName(serviceName, groupName);String serviceKey = ServiceInfo.getKey(serviceNameWithGroup, clusters);//定时同步服务端serviceInfoserviceInfoUpdateService.scheduleUpdateIfAbsent(serviceName, groupName, clusters);//获取ServiceInfo 信息ServiceInfo result = serviceInfoHolder.getServiceInfoMap().get(serviceKey);//如果没有则从服务端拿if (null == result || !isSubscribed(serviceName, groupName, clusters)) {//GRPC请求result = grpcClientProxy.subscribe(serviceName, groupName, clusters);}//填充进Map中 这里可以看服务注册最后那部分代码最后也是调用serviceInfoHolder保存的serviceInfoHolder.processServiceInfo(result);return result;}

定时同步服务端ServiceInfo

    public void scheduleUpdateIfAbsent(String serviceName, String groupName, String clusters) {String serviceKey = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);if (futureMap.get(serviceKey) != null) {return;}synchronized (futureMap) {if (futureMap.get(serviceKey) != null) {return;}//构建任务放到ScheduledFuture执行ScheduledFuture<?> future = addTask(new UpdateTask(serviceName, groupName, clusters));futureMap.put(serviceKey, future);}}

缓存订阅信息

    public void cacheSubscriberForRedo(String serviceName, String groupName, String cluster) {//拿服务当keyString key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), cluster);//构建需要缓存的订阅信息SubscriberRedoData redoData = SubscriberRedoData.build(serviceName, groupName, cluster);//缓存订阅信息synchronized (subscribes) {subscribes.put(key, redoData);}}

执行订阅

    public ServiceInfo doSubscribe(String serviceName, String groupName, String clusters) throws NacosException {//构建订阅请求SubscribeServiceRequest request = new SubscribeServiceRequest(namespaceId, groupName, serviceName, clusters,true);//执行订阅SubscribeServiceResponse response = requestToServer(request, SubscribeServiceResponse.class);//设置已订阅redoService.subscriberRegistered(serviceName, groupName, clusters);return response.getServiceInfo();}

设置订阅信息已订阅

    public void subscriberRegistered(String serviceName, String groupName, String cluster) {String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), cluster);synchronized (subscribes) {SubscriberRedoData redoData = subscribes.get(key);// 标记订阅数据已订阅if (null != redoData) {redoData.setRegistered(true);}}}

Nacos订阅机制

Nacos的订阅机制,如果用一句话来描述就是:Nacos客户端通过一个定时任务,每6秒从注册中心获取实例列表,当发现实例发生变化时,发布变更事件,订阅者进行业务处理。该更新实例的更新实例,该更新本地缓存的更新本地缓存。
UpdateTask

 public class UpdateTask implements Runnable {public void run() {long delayTime = DEFAULT_DELAY;try {//校验订阅任务是否不对 如果不对就不处理 if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey(serviceKey)) {NAMING_LOGGER.info("update task is stopped, service:{}, clusters:{}", groupedServiceName, clusters);isCancel = true;return;}//从缓存中拿 Service信息ServiceInfo serviceObj = serviceInfoHolder.getServiceInfoMap().get(serviceKey);//如果拿不到则去服务端拉取if (serviceObj == null) {serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);              //然后再填充进缓存 serviceInfoHolder.processServiceInfo(serviceObj);//更新下事件lastRefTime = serviceObj.getLastRefTime();return;}// 过期服务(服务的最新更新时间小于等于缓存刷新时间),从注册中心重新查询if (serviceObj.getLastRefTime() <= lastRefTime) {//服务过期了重新查serviceObj = namingClientProxy.queryInstancesOfService(serviceName, groupName, clusters, 0, false);             //在缓存进去serviceInfoHolder.processServiceInfo(serviceObj);}// 刷新更新时间lastRefTime = serviceObj.getLastRefTime();if (CollectionUtils.isEmpty(serviceObj.getHosts())) {incFailCount();return;}// 下次更新缓存时间设置,默认为6秒// TODO multiple time can be configured.delayTime = serviceObj.getCacheMillis() * DEFAULT_UPDATE_CACHE_TIME_MULTIPLE;// 重置失败数量为0resetFailCount();} catch (Throwable e) {incFailCount();NAMING_LOGGER.warn("[NA] failed to update serviceName: {}", groupedServiceName, e);} finally {// 下次调度刷新时间,下次执行的时间与failCount有关// failCount=0,则下次调度时间为6秒,最长为1分钟// 即当无异常情况下缓存实例的刷新时间是6秒if (!isCancel) {executor.schedule(this, Math.min(delayTime << failCount, DEFAULT_DELAY * 60),TimeUnit.MILLISECONDS);}}}}

实例变更事件处理

监听事件的注册

在NacosNamingService的subscribe方法中,通过如下方式进行了监听事件的注册:

    @Overridepublic void subscribe(String serviceName, String groupName, List<String> clusters, EventListener listener)throws NacosException {if (null == listener) {return;}String clusterString = StringUtils.join(clusters, ",");changeNotifier.registerListener(groupName, serviceName, clusterString, listener);clientProxy.subscribe(serviceName, groupName, clusterString);}

这里的changeNotifier.registerListener便是进行具体的事件注册逻辑

   public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) {String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters);ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);//这里用到了双重检查锁机制if (eventListeners == null) {synchronized (lock) {eventListeners = listenerMap.get(key);if (eventListeners == null) {eventListeners = new ConcurrentHashSet<EventListener>();listenerMap.put(key, eventListeners);}}}eventListeners.add(listener);}

可以看出,事件的注册便是将EventListener存储在InstancesChangeNotifier的listenerMap属性当中了。

这里的数据结构为Map,key为服务实例信息的拼接,value为监听事件的集合。

监听服务变更事件

因为UpdateTask 中假如没有从缓存中拿到服务信息则会通过grpc协议从服务端拉取然后会执行serviceInfoHolder.processServiceInfo方法缓存服务信息,当实例发生变化的话这个方法最终会发送一个InstancesChangeEvent 事件 所以这里会监听InstancesChangeEvent 事件进行处理

InstancesChangeNotifier

public class InstancesChangeNotifier extends Subscriber<InstancesChangeEvent> {private final Map<String, ConcurrentHashSet<EventListener>> listenerMap = new ConcurrentHashMap<String, ConcurrentHashSet<EventListener>>();@Overridepublic void onEvent(InstancesChangeEvent event) {String key = ServiceInfo.getKey(NamingUtils.getGroupedName(event.getServiceName(), event.getGroupName()), event.getClusters());ConcurrentHashSet<EventListener> eventListeners = listenerMap.get(key);if (CollectionUtils.isEmpty(eventListeners)) {return;}for (final EventListener listener : eventListeners) {//[] final com.alibaba.nacos.api.naming.listener.Event namingEvent = transferToNamingEvent(event);final com.alibaba.nacos.api.naming.listener.Event namingEvent = new NamingEvent(instancesChangeEvent.getServiceName(), instancesChangeEvent.getGroupName(),instancesChangeEvent.getClusters(), instancesChangeEvent.getHosts());// 最终调度执行listener.onEvent(namingEvent),只在NacosWatch#start找到了有效的EventListener,见下文if (listener instanceof AbstractEventListener && ((AbstractEventListener) listener).getExecutor() != null) {((AbstractEventListener) listener).getExecutor().execute(() -> listener.onEvent(namingEvent));} else {listener.onEvent(namingEvent);}}}
}
}
public class NacosWatch implements ApplicationEventPublisherAware, SmartLifecycle, DisposableBean {private Map<String, EventListener> listenerMap = new ConcurrentHashMap(16);private final AtomicBoolean running = new AtomicBoolean(false);public void start() {if (this.running.compareAndSet(false, true)) {EventListener eventListener = (EventListener)this.listenerMap.computeIfAbsent(this.buildKey(), (event) -> {return new EventListener() {public void onEvent(Event event) {if (event instanceof NamingEvent) {List instances = ((NamingEvent)event).getInstances();//[] Optional instanceOptional = NacosWatch.this.selectCurrentInstance(instances);// 按IP和端口选择第一个instance作为当前的instanceOptional instanceOptional = instances.stream().filter((instance) -> {return this.properties.getIp().equals(instance.getIp()) && this.properties.getPort() == instance.getPort();}).findFirst()instanceOptional.ifPresent((currentInstance) -> {//[] NacosWatch.this.resetIfNeeded(currentInstance);// 重新设置properties的metadataif (!this.properties.getMetadata().equals(instance.getMetadata())) {this.properties.setMetadata(instance.getMetadata());}});}}};});}}

获取服务信息

NacosDiscoveryClient

	@Overridepublic List<String> getServices() {try {return serviceDiscovery.getServices();}catch (Exception e) {log.error("get service name from nacos server fail,", e);return Collections.emptyList();}}
	public List<String> getServices() throws NacosException {//获取分组String group = discoveryProperties.getGroup();//获取服务信息ListView<String> services = namingService().getServicesOfServer(1,Integer.MAX_VALUE, group);return services.getData();}
    @Overridepublic ListView<String> getServicesOfServer(int pageNo, int pageSize, String groupName) throws NacosException {return getServicesOfServer(pageNo, pageSize, groupName, null);}
    @Overridepublic ListView<String> getServicesOfServer(int pageNo, int pageSize, String groupName, AbstractSelector selector)throws NacosException {return clientProxy.getServiceList(pageNo, pageSize, groupName, selector);}

GRPC拉取信息

    @Overridepublic ListView<String> getServiceList(int pageNo, int pageSize, String groupName, AbstractSelector selector)throws NacosException {//构建请求ServiceListRequest request = new ServiceListRequest(namespaceId, groupName, pageNo, pageSize);if (selector != null) {if (SelectorType.valueOf(selector.getType()) == SelectorType.label) {request.setSelector(JacksonUtils.toJson(selector));}}//采用GRPC协议拉取信息ServiceListResponse response = requestToServer(request, ServiceListResponse.class);ListView<String> result = new ListView<String>();result.setCount(response.getCount());result.setData(response.getServiceNames());return result;}

服务端处理GRPC请求

接收获取服务的请求

ServiceListRequestHandler

    @Override@Secured(action = ActionTypes.READ)public ServiceListResponse handle(ServiceListRequest request, RequestMeta meta) throws NacosException {//根据命名空间获取这个命名空间下的所有服务信息  erviceManager.getInstance().getSingletons这个方法服务注册的时候里有Collection<Service> serviceSet = ServiceManager.getInstance().getSingletons(request.getNamespace());//构建返回信息ServiceListResponse result = ServiceListResponse.buildSuccessResponse(0, new LinkedList<>());//服务信息不等于空填充返回信息if (!serviceSet.isEmpty()) {Collection<String> serviceNameSet = selectServiceWithGroupName(serviceSet, request.getGroupName());// TODO select service by selectorList<String> serviceNameList = ServiceUtil.pageServiceName(request.getPageNo(), request.getPageSize(), serviceNameSet);result.setCount(serviceNameSet.size());result.setServiceNames(serviceNameList);}return result;}

订阅服务请求

SubscribeServiceRequestHandler

    @Override@Secured(action = ActionTypes.READ)public SubscribeServiceResponse handle(SubscribeServiceRequest request, RequestMeta meta) throws NacosException {//命名空间String namespaceId = request.getNamespace();//服务名称String serviceName = request.getServiceName();//分组名称String groupName = request.getGroupName();String app = request.getHeader("app", "unknown");String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);//构建服务信息Service service = Service.newService(namespaceId, groupName, serviceName, true);//组装订阅请求Subscriber subscriber = new Subscriber(meta.getClientIp(), meta.getClientVersion(), app, meta.getClientIp(),namespaceId, groupedServiceName, 0, request.getClusters());//获取健康的实例       ServiceInfo serviceInfo = ServiceUtil.selectInstancesWithHealthyProtection(serviceStorage.getData(service),               //服务元数据信息    metadataManager.getServiceMetadata(service).orElse(null), subscriber);//是否订阅      if (request.isSubscribe()) {clientOperationService.subscribeService(service, subscriber, meta.getConnectionId());} else {clientOperationService.unsubscribeService(service, subscriber, meta.getConnectionId());}//构建返回数据return new SubscribeServiceResponse(ResponseCode.SUCCESS.getCode(), "success", serviceInfo);}

发送订阅事件 后续事件监听可参考服务事件处理的那篇文章

    @Overridepublic void subscribeService(Service service, Subscriber subscriber, String clientId) {Service singleton = ServiceManager.getInstance().getSingletonIfExist(service).orElse(service);Client client = clientManager.getClient(clientId);if (!clientIsLegal(client, clientId)) {return;}client.addServiceSubscriber(singleton, subscriber);client.setLastUpdatedTime();NotifyCenter.publishEvent(new ClientOperationEvent.ClientSubscribeServiceEvent(singleton, clientId));}

发送取消订阅事件 后续事件监听可参考服务事件处理的那篇文章

    @Overridepublic void unsubscribeService(Service service, Subscriber subscriber, String clientId) {Service singleton = ServiceManager.getInstance().getSingletonIfExist(service).orElse(service);Client client = clientManager.getClient(clientId);if (!clientIsLegal(client, clientId)) {return;}client.removeServiceSubscriber(singleton);client.setLastUpdatedTime();NotifyCenter.publishEvent(new ClientOperationEvent.ClientUnsubscribeServiceEvent(singleton, clientId));}

服务查询请求

ServiceQueryRequestHandler

    @Override@Secured(action = ActionTypes.READ)public QueryServiceResponse handle(ServiceQueryRequest request, RequestMeta meta) throws NacosException {//获取命名空间String namespaceId = request.getNamespace();//分组明String groupName = request.getGroupName();//服务名String serviceName = request.getServiceName();//创建服务信息Service service = Service.newService(namespaceId, groupName, serviceName);//集群String cluster = null == request.getCluster() ? "" : request.getCluster();boolean healthyOnly = request.isHealthyOnly();//获取服务信息ServiceInfo result = serviceStorage.getData(service);//获取服务元数据信息ServiceMetadata serviceMetadata = metadataManager.getServiceMetadata(service).orElse(null);// 获取有保护机制的健康实例result = ServiceUtil.selectInstancesWithHealthyProtection(result, serviceMetadata, cluster, healthyOnly, true,meta.getClientIp());//构建返回信息return QueryServiceResponse.buildSuccessResponse(result);}
    public ServiceInfo getData(Service service) {return serviceDataIndexes.containsKey(service) ? serviceDataIndexes.get(service) : getPushData(service);}
    public Optional<ServiceMetadata> getServiceMetadata(Service service) {return Optional.ofNullable(serviceMetadataMap.get(service));}

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

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

相关文章

Linux下activemq的安装与安装成功确认

一、下载 apache-activemq-5.14.0-bin.tar.gz 二、安装 将压缩包拷入linux内&#xff0c;进行解压tar -zxvf apache-activemq-5.14.0-bin.tar.gz&#xff0c;与redis、nginx不同的是&#xff0c;active解压不需要安装就可以直接启动&#xff01; 启动命令&#xff1a;./bin…

1949-2021年全国31省公路里程数据

1949-2021年全国31省公路里程数据 1、指标&#xff1a;公路里程 2、范围&#xff1a;包括31省 1978-2021年期间无缺失 3、来源&#xff1a;各省NJ、产业NJ、各省统计GB 4、指标解释&#xff1a;公路里程指报告期末公路的实际长度。 统计范围&#xff1a;包括城间、城乡间、乡…

Rocketmq架构

NameServer&#xff1a;作为注册中心&#xff0c;提供路由注册、路由踢出、路由发现功能&#xff0c;舍弃强一致&#xff0c;保证高可用&#xff0c;集群中各个节点不会实时通讯&#xff0c;其中一个节点下线之后&#xff0c;会提供另外一个节点保证路由功能。 Rocket mq name…

java学习part32StringBuffer和StringBuilder

Java中的值传递和引用传递&#xff08;详解&#xff09; - 知乎 (zhihu.com) 146-常用类与基础API-StringBuffer与StringBuilder的源码分析、常用方法_哔哩哔哩_bilibili 1. 2.扩容机制 不够用&#xff1a;长度为 原长度*22&#xff1b;如果还不够&#xff0c;那么就扩容到目…

多文件夹图片预处理:清除空值、重置大小、分割训练集

→ 清理空值 防止出现cannot identify image file 参考Python数据清洗----删除读取失败图片__简单版_python用pil读取图片出错删除掉-CSDN博客 #%pip install -i https://pypi.tuna.tsinghua.edu.cn/simple opencv-python #可能需要重启jupyter import os import shutil imp…

【Unity动画】状态机添加参数控制动画切换(Animator Controller)

Unity - 手册&#xff1a;动画参数 在Unity中&#xff0c;动画状态的切换是通过Animator Controller中的过渡&#xff08;Transition&#xff09;来实现的。过渡是状态之间的连接&#xff0c;控制过渡一般都是靠调用代码参数 我们来实现一个案例&#xff1a; 创建动画状态机&a…

leetcode 142.环形链表2

我来更新 leetcode 题目了&#xff0c;接着上一次&#xff0c;这一次是上一道题目的提升&#xff08;有点数学题的感觉&#xff09; 142.环形链表2 题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表…

识KDJ指标,看懂超买超卖信号

一、认识KDJ 1、KDJ的含义 KDJ分析股票中短期趋势的一个常用指标&#xff0c;中文名称“随机指标”。它是一个综合考虑股票最高价、最低价和收盘价的技术指标&#xff0c;能够帮助我们根据历史价格预测出股票未来的价格走势。在实际应用的过程中&#xff0c;它的短期预测功能要…

JAVA毕业设计113—基于Java+Springboot+Vue的体育馆预约系统(源代码+数据库+12000字论文)

基于JavaSpringbootVue的体育馆预约系统(源代码数据库12000字论文)113 一、系统介绍 本项目前后端分离&#xff0c;本系统分为管理员、用户两种角色 用户角色包含以下功能&#xff1a; 注册、登录、场地(查看/预订/收藏/退订)、在线论坛、公告查看、我的预订管理、我的收藏…

TimeGPT:时间序列预测模型实例

时间序列预测领域正在经历一个非常激动人心的时期。在过去的三年里&#xff0c;我们见证了许多重要的贡献&#xff0c;如N-BEATS、N-HiTS、PatchTST和TimesNet等。同时&#xff0c;大型语言模型&#xff08;LLM&#xff09;近来在流行度方面取得了很大的成功&#xff0c;例如Ch…

PMP-01

考纲 需要看的书籍 学习计划

04-数据库操作对象Statement对象和PreparedStatement对象的区别,SQL注入的优缺点

Statement对象和查询结果集 Statement对象相关的方法 Connection接口中获取数据库操作对象Statement对象的方法 方法名功能Statement createStatement()创建Statement对象 Statement对象执行增删改查的SQL语句(不含占位符"?")的方法,JDBC中的SQL语句不需要提供分…

Verilog 入门(八)(验证)

文章目录 编写测试验证程序波形产生值序列重复模式 测试验证程序实例从文本文件中读取向量实例&#xff1a;时序检测器 测试验证程序用于测试和验证设计方法的正确性。Verilog 提供强有力的结构来说明测试验证程序。 编写测试验证程序 测试验证程序有三个主要目的&#xff1a;…

minigpt4安装的一系列问题

问题一 合并权重&#xff1a; python -m fastchat.model.apply_delta --base /data/qq/llama2-7b-chat-hf --target /data/qq/MiniGPT-4/vicuna_weights/ --delta /data/qq/vicuna-7b-delta-v0fastchat版本与模型版本 不兼容 解决方法&#xff1a; 第一种&#xff1a;用低版…

【算法】滑动窗口题单——5.多指针滑动窗口醒醒⭐

文章目录 930. 和相同的二元子数组解法1——前缀和 哈希表解法2——滑动窗口 ⭐ 1248. 统计「优美子数组」1712. 将数组分成三个子数组的方案数⭐⭐⭐2444. 统计定界子数组的数目解法——多指针滑动窗口代码2——简洁写法&#xff1a;一次遍历O(1) 空间&#x1f402;⭐ 992. K…

《Junit单元测试》

目录 SpringBoot2.2.0版本之前的单元测试模式 SpringBoot2.2.0版本之后的单元测试模式 SpringBoot2.4以上版本移除了默认对Vintage的依赖 SpringBoot2.2.0版本之前的单元测试模式 SpringBooot 2.2.0 版本开始引入Junit5作为单元测试默认库&#xff0c;之前的版本是使用Junit…

Centos图形化界面封装OpenStack Centos镜像

目录 背景 环境 宿主机环境安装 创建与安装Centos7.8虚机 虚机设置 安全相关 安装ACPI服务 安装cloud-init 安装cloud-utils-growpart 停⽌虚拟机 删除个性化信息 模板化与压缩 登录与验证 背景 今天早上在Centos官网下载的CentOS-7-aarch64-GenericCloud-2003.…

nodejs介绍

nodejs官网支持的各种库api https://nodejs.org/docs/latest-v21.x/api/http.html nodejs包括vp8引擎和内置的基本库如fs,path,http,querystring等&#xff0c;也可以用npm按转第三方库 npm是nodejs环境的包管理工具&#xff0c;可以为这个环境安装卸载各种包。 npm install pk…

总结react中css的使用

1、css in js css in js有很多库&#xff0c;这里介绍styled-components styled-components 下载【vscode可以安装vscode-styled-components 插件&#xff0c;有代码提示】 npm i styled-components 1、然后为某个组件新建style.js文件&#xff0c;然后写一些样式。 impo…

【算法套路】(数组中)等价转换

文章目录 例题——2488. 统计中位数为 K 的子数组⭐【套路】子数组统计问题常用技巧&#xff1a;等价转换 相似题目列表面试题 17.05. 字母与数字525. 连续数组1124. 表现良好的最长时间段解法1解法2——利用单调栈 例题——2488. 统计中位数为 K 的子数组⭐ https://leetcode…