Nacos源码解读01——服务注册

Nacos 2.0 架构设计及新模型

参考 https://zhuanlan.zhihu.com/p/344572647

使用GRPC注册临时实例流程图请添加图片描述

SpringBoot自动注入

在这里插入图片描述
注入对应服务注册的Bean
在这里插入图片描述
在这里插入图片描述

监听Tomcat启动事件

NacosAutoServiceRegistration 继承了AbstractAutoServiceRegistration 而 AbstractAutoServiceRegistration实现了ApplicationListener接口当tomcat启动之后会发送WebServerInitializedEvent事件 AbstractAutoServiceRegistration监听了WebServerInitializedEvent事件进行后续的注册操作
在这里插入图片描述

开始注册服务

在这里插入图片描述
在这里插入图片描述
构建出来的 Instance信息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

临时实例使用GRPC协议注册

这里判断是不是临时节点 临时节点默认为true 会走NamingGrpcClientProxy的注册方法采用grpc协议,nacos2.x版本中临时节点默认都是此协议进行通信。持久化节点使用HTTP请求进行通信
在这里插入图片描述
在这里插入图片描述

缓存实例信息

在这里插入图片描述

开始注册服务信息

在这里插入图片描述

发送注册请求

在这里插入图片描述

将服务信息改为已注册

在这里插入图片描述

服务端接收注册请求

在这里插入图片描述
InstanceRequestHandler 继承自RequestHandler所以当RequestHandler执行handler方法的时候会执行InstanceRequestHandler 的handler方法进行逻辑处理
在这里插入图片描述
clientOperationService是一个临时实例还是持久化实例的代理类,去管理不同实例的注册行为
在这里插入图片描述

Client模型

Nacos2.x以后新增Client模型**。**一个客户端gRPC长连接对应一个Client,每个Client有自己唯一的id(clientId)。Client负责管理一个客户端的服务实例注册Publish和服务订阅Subscribe。我们可以看一下这个模型其实就是一个接口

public interface Client {// 客户端id/gRPC的connectionIdString getClientId();// 是否临时客户端boolean isEphemeral();// 客户端更新时间void setLastUpdatedTime();long getLastUpdatedTime();// 服务实例注册/注销/查询boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo);InstancePublishInfo removeServiceInstance(Service service);InstancePublishInfo getInstancePublishInfo(Service service);Collection<Service> getAllPublishedService();// 服务订阅/取消订阅/查询订阅boolean addServiceSubscriber(Service service, Subscriber subscriber);boolean removeServiceSubscriber(Service service);Subscriber getSubscriber(Service service);Collection<Service> getAllSubscribeService();// 生成同步给其他节点的client数据ClientSyncData generateSyncData();// 是否过期boolean isExpire(long currentTime);// 释放资源void release();
}

服务信息Service与Instance创建

    @Overridepublic void registerInstance(Service service, Instance instance, String clientId) {//缓存服务、缓存命名空间与服务的关系Service singleton = ServiceManager.getInstance().getSingleton(service);if (!singleton.isEphemeral()) {throw new NacosRuntimeException(NacosException.INVALID_PARAM,String.format("Current service %s is persistent service, can't register ephemeral instance.",singleton.getGroupedServiceName()));}//连接Id作为客户端Id,获取客户端Client client = clientManager.getClient(clientId);//检查客户端是否合法:客户端是否存在、客户端是否瞬时if (!clientIsLegal(client, clientId)) {return;}// 生成服务端存储的instance信息,并记录到ClientInstancePublishInfo instanceInfo = getPublishInfo(instance);//在客户端添加服务与实例的关系信息client.addServiceInstance(singleton, instanceInfo);client.setLastUpdatedTime();// 发布注册服务事件,源码解读见下文:服务变更事件处理NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));// 发布元数据事件,源码解读见下文:管理元数据源码NotifyCenter.publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));}

ServiceManager

Service的容器是ServiceManager,但是在com.alibaba.nacos.naming.core.v2包下,容器中Service都是单例。

public class ServiceManager {private static final ServiceManager INSTANCE = new ServiceManager();//单例Service,可以查看Service的equals和hasCode方法private final ConcurrentHashMap<Service, Service> singletonRepository;//namespace下的所有serviceprivate final ConcurrentHashMap<String, Set<Service>> namespaceSingletonMaps;.....
}

所以从这个位置可以看出,当调用这个注册方法的时候ServiceManager负责管理Service单例

//通过Map储存单例的Service
public Service getSingleton(Service service) {singletonRepository.putIfAbsent(service, service);Service result = singletonRepository.get(service);namespaceSingletonMaps.computeIfAbsent(result.getNamespace(), (namespace) -> new ConcurrentHashSet<>());namespaceSingletonMaps.get(result.getNamespace()).add(result);return result;
}

ClientManager

ClientManager这是一个接口这里我们要看它对应的一个实现类ConnectionBasedClientManager,这个实现类负责管理长连接clientId与Client模型的映射关系

// 根据clientId查询Client
public Client getClient(String clientId) {return clients.get(clientId);
}
@Override
public boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {if (null == publishers.put(service, instancePublishInfo)) {MetricsMonitor.incrementInstanceCount();}NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(this));Loggers.SRV_LOG.info("Client change for service {}, {}", service, getClientId());return true;
}

添加服务实例信息

    @Overridepublic boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {return super.addServiceInstance(service, parseToHealthCheckInstance(instancePublishInfo));}

Client实例AbstractClient负责存储当前客户端的服务注册表,即Service与Instance的关系。注意对于单个客户端来说,同一个服务只能注册一个实例。

    @Overridepublic boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {//缓存服务与实例映射关系到客户端的容器、一个客户端对应一个连接if (null == publishers.put(service, instancePublishInfo)) {MetricsMonitor.incrementInstanceCount();}NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(this));Loggers.SRV_LOG.info("Client change for service {}, {}", service, getClientId());return true;}

可以从下图中看到 我把同一个服务 开了两个不同端口的实例 他每一个客户端id 都不相同 所以Clinet里面publishers这个map是一个service 对应一个实例
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

持久化节点注册

在Nacos 2.0版本中如果是持久化实例会使用NamingHttpClientProxy进行HTTP请求进行实例的注册
在这里插入图片描述

    @Overridepublic void registerService(String serviceName, String groupName, Instance instance) throws NacosException {NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,instance);String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);if (instance.isEphemeral()) {BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);beatReactor.addBeatInfo(groupedServiceName, beatInfo);}//拼接请求参数final Map<String, String> params = new HashMap<String, String>(32);params.put(CommonParams.NAMESPACE_ID, namespaceId);params.put(CommonParams.SERVICE_NAME, groupedServiceName);params.put(CommonParams.GROUP_NAME, groupName);params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());params.put(IP_PARAM, instance.getIp());params.put(PORT_PARAM, String.valueOf(instance.getPort()));params.put(WEIGHT_PARAM, String.valueOf(instance.getWeight()));params.put(REGISTER_ENABLE_PARAM, String.valueOf(instance.isEnabled()));params.put(HEALTHY_PARAM, String.valueOf(instance.isHealthy()));params.put(EPHEMERAL_PARAM, String.valueOf(instance.isEphemeral()));params.put(META_PARAM, JacksonUtils.toJson(instance.getMetadata()));reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);}
    public String reqApi(String api, Map<String, String> params, String method) throws NacosException {return reqApi(api, params, Collections.EMPTY_MAP, method);}public String reqApi(String api, Map<String, String> params, Map<String, String> body, String method)throws NacosException {return reqApi(api, params, body, serverListManager.getServerList(), method);}

发送HTTP请求

    public String reqApi(String api, Map<String, String> params, Map<String, String> body, List<String> servers,String method) throws NacosException {params.put(CommonParams.NAMESPACE_ID, getNamespaceId());if (CollectionUtils.isEmpty(servers) && !serverListManager.isDomain()) {throw new NacosException(NacosException.INVALID_PARAM, "no server available");}NacosException exception = new NacosException();if (serverListManager.isDomain()) {String nacosDomain = serverListManager.getNacosDomain();for (int i = 0; i < maxRetry; i++) {try {return callServer(api, params, body, nacosDomain, method);} catch (NacosException e) {exception = e;if (NAMING_LOGGER.isDebugEnabled()) {NAMING_LOGGER.debug("request {} failed.", nacosDomain, e);}}}} else {Random random = new Random(System.currentTimeMillis());int index = random.nextInt(servers.size());for (int i = 0; i < servers.size(); i++) {String server = servers.get(index);try {return callServer(api, params, body, server, method);} catch (NacosException e) {exception = e;if (NAMING_LOGGER.isDebugEnabled()) {NAMING_LOGGER.debug("request {} failed.", server, e);}}index = (index + 1) % servers.size();}}NAMING_LOGGER.error("request: {} failed, servers: {}, code: {}, msg: {}", api, servers, exception.getErrCode(),exception.getErrMsg());throw new NacosException(exception.getErrCode(),"failed to req API:" + api + " after all servers(" + servers + ") tried: " + exception.getMessage());}

服务端接收HTTP请求进行注册

通过阅读官方文档可以知道,对于HTTP注册来说,服务端处理的API地址为:

http://ip:port/nacos/v1/ns/instance/register

InstanceController

    /*** Register new instance.** @param request http request* @return 'ok' if success* @throws Exception any error during register*/@CanDistro@PostMapping@Secured(action = ActionTypes.WRITE)public String register(HttpServletRequest request) throws Exception {// 获得当前请求中的命名空间信息,如果不存在则使用默认的命名空间final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);// 获得当前请求中的服务名称,如果不存在则使用默认的服务名称final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);// 检查服务名称是否合法NamingUtils.checkServiceNameFormat(serviceName);// 将当前信息构造为一个Instance实例对象final Instance instance = HttpRequestInstanceBuilder.newBuilder().setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();// 根据当前对GRPC的支持情况 调用符合条件的处理,支持GRPC特征则调用InstanceOperatorClientImplgetInstanceOperator().registerInstance(namespaceId, serviceName, instance);return "ok";}

判断是否支持GRPC 如果支持GRPC则会使用V2版本 否则使用V1

    private InstanceOperator getInstanceOperator() {return upgradeJudgement.isUseGrpcFeatures() ? instanceServiceV2 : instanceServiceV1;}

V1版本方案进行注册

先来看v1会调用 InstanceOperatorServiceImpl的registerInstance进行注册

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {//创建服务信息,不存在则进行创建,同时创建service、cluster的关系//同时初始化service的时候,创建服务端的心跳检测判断任务createEmptyService(namespaceId, serviceName, instance.isEphemeral());// 再次获得服务信息Service service = getService(namespaceId, serviceName);// 再次判断服务checkServiceIsNull(service, namespaceId, serviceName);// 添加实例信息addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}
创建服务信息
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)throws NacosException {//从缓存中获取服务信息Service service = getService(namespaceId, serviceName);if (service == null) {Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);//初始化serviceservice = new Service();service.setName(serviceName);service.setNamespaceId(namespaceId);service.setGroupName(NamingUtils.getGroupName(serviceName));// now validate the service. if failed, exception will be thrownservice.setLastModifiedMillis(System.currentTimeMillis());service.recalculateChecksum();if (cluster != null) {//关联服务和集群的关系cluster.setService(service);service.getClusterMap().put(cluster.getName(), cluster);}//校验服务名称等是否合规service.validate();//初始话服务信息,创建心跳检测任务putServiceAndInit(service);if (!local) {//是否是临时服务,一致性处理addOrReplaceService(service);}}

在putServiceAndInit方法中的核心逻辑就是将当前服务信息放置到缓存中,同时调用初始化方法开启服务端的心跳检测任务,用于判断当前服务下的实例信息的变化,如果有变化则同时客户端.

public void init() {// 开启当前服务的心跳检测任务HealthCheckReactor.scheduleCheck(clientBeatCheckTask);for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {entry.getValue().setService(this);entry.getValue().init();}
}
添加实例信息
public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)throws NacosException {//构建keyString key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);//从缓存中获得服务信息Service service = getService(namespaceId, serviceName);//为服务设置一把锁synchronized (service) {//这个方法里面就是最核心的对命名空间->服务->cluster->instance//基于这套数据结构和模型完成内存服务注册,就是在这里List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);Instances instances = new Instances();instances.setInstanceList(instanceList);// 真正你的Distro协议生效,主要是在这里,会去走distro的put逻辑// 会把你的服务实例数据页放在内存里,同时发起一个延迟异步任务的sync的数据复制任务// 延迟一段时间consistencyService.put(key, instances);}
}

对于addIpAddresses方法来说,核心的就是创建起相关的关联关系

public List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips)throws NacosException {Datum datum = consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));List<Instance> currentIPs = service.allIPs(ephemeral);Map<String, Instance> currentInstances = new HashMap<>(currentIPs.size());Set<String> currentInstanceIds = CollectionUtils.set();for (Instance instance : currentIPs) {currentInstances.put(instance.toIpAddr(), instance);currentInstanceIds.add(instance.getInstanceId());}Map<String, Instance> instanceMap;if (datum != null && null != datum.value) {instanceMap = setValid(((Instances) datum.value).getInstanceList(), currentInstances);} else {instanceMap = new HashMap<>(ips.length);}for (Instance instance : ips) {if (!service.getClusterMap().containsKey(instance.getClusterName())) {Cluster cluster = new Cluster(instance.getClusterName(), service);cluster.init();service.getClusterMap().put(instance.getClusterName(), cluster);Loggers.SRV_LOG.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",instance.getClusterName(), instance.toJson());}if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)) {instanceMap.remove(instance.getDatumKey());} else {Instance oldInstance = instanceMap.get(instance.getDatumKey());if (oldInstance != null) {instance.setInstanceId(oldInstance.getInstanceId());} else {instance.setInstanceId(instance.generateInstanceId(currentInstanceIds));}instanceMap.put(instance.getDatumKey(), instance);}}if (instanceMap.size() <= 0 && UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD.equals(action)) {throw new IllegalArgumentException("ip list can not be empty, service: " + service.getName() + ", ip list: " + JacksonUtils.toJson(instanceMap.values()));}return new ArrayList<>(instanceMap.values());
}

该方法结束以后,命名空间->服务->cluster->instance,这个存储结构的关系就确定了。

V2版本方案进行注册

InstanceOperatorClientImpl

    @Overridepublic void registerInstance(String namespaceId, String serviceName, Instance instance) {//是否是临时实例boolean ephemeral = instance.isEphemeral();//获取客户端idString clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);createIpPortClientIfAbsent(clientId);//获取服务信息Service service = getService(namespaceId, serviceName, ephemeral);//注册实例信息clientOperationService.registerInstance(service, instance, clientId);}

持久化实例会使用PersistentClientOperationServiceImpl进行服务的信息注册 使用JRaft协议,将数据写入raft集群

    @Overridepublic void registerInstance(Service service, Instance instance, String clientId) {Service singleton = ServiceManager.getInstance().getSingleton(service);if (singleton.isEphemeral()) {throw new NacosRuntimeException(NacosException.INVALID_PARAM,String.format("Current service %s is ephemeral service, can't register persistent instance.",singleton.getGroupedServiceName()));}final InstanceStoreRequest request = new InstanceStoreRequest();request.setService(service);request.setInstance(instance);request.setClientId(clientId);final WriteRequest writeRequest = WriteRequest.newBuilder().setGroup(group()).setData(ByteString.copyFrom(serializer.serialize(request))).setOperation(DataOperation.ADD.name()).build();try {protocol.write(writeRequest);} catch (Exception e) {throw new NacosRuntimeException(NacosException.SERVER_ERROR, e);}}

服务信息映射事件处理

在上面的流程中,可以看到调用通知中心派发了2个事件:

new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId)

new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId()

这里的目的是为了过滤目标服务得到最终Instance列表建立Service与Client的关系,建立Service与Client的关系就是为了加速查询。

ClientServiceIndexesManager类服务处理这个类的监听业务,ClientServiceIndexesManager维护了两个索引:

Service与发布clientId
Service与订阅clientId

private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();private void handleClientOperation(ClientOperationEvent event) {Service service = event.getService();String clientId = event.getClientId();if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {addPublisherIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {removePublisherIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {addSubscriberIndexes(service, clientId);} else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {removeSubscriberIndexes(service, clientId);}
}//建立Service与发布Client的关系
private void addPublisherIndexes(Service service, String clientId) {publisherIndexes.computeIfAbsent(service, (key) -> new ConcurrentHashSet<>());publisherIndexes.get(service).add(clientId);NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}

从ClientServiceIndexesManager类的源代码中可以看到,该类注册订阅了4个事件:

客户端注册服务事件、客户端取消注册服务事件、客户端订阅服务事件、客户端取消订阅服务事件

@Override
public List<Class<? extends Event>> subscribeTypes() {List<Class<? extends Event>> result = new LinkedList<>();result.add(ClientOperationEvent.ClientRegisterServiceEvent.class);result.add(ClientOperationEvent.ClientDeregisterServiceEvent.class);result.add(ClientOperationEvent.ClientSubscribeServiceEvent.class);result.add(ClientOperationEvent.ClientUnsubscribeServiceEvent.class);result.add(ClientEvent.ClientDisconnectEvent.class);return result;
}

这个索引关系建立以后,还会触发ServiceChangedEvent,代表服务注册表变更。对于注册表变更紧接着还要做两个事情:

1.通知订阅客户端

2.Nacos集群数据同步。

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

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

相关文章

第一百八十七回 DropdownButton组件

文章目录 1. 概念介绍2. 使用方法2.1 DropdownButton2.2 DropdownMenuItem 3. 示例代码4. 内容总结5. 经验分享 我们在 上一章回中介绍了"DropdownMenu组件"相关的内容&#xff0c;本章回中将介绍 DropdownButton组件.闲话休提&#xff0c;让我们一起Talk Flutter吧…

cyclictest 交叉编译与使用

目录 使用版本问题编译 numactl编译 cyclictest使用参考 cyclictest 主要是用于测试系统延时&#xff0c;进而判断系统的实时性 使用版本 rt-tests-2.6.tar.gz numactl v2.0.16 问题 编译时&#xff0c;需要先编译 numactl &#xff0c;不然会有以下报错&#xff1a; arm-…

C++11——initializer_list

initializer_list的简介 initializer_list是C11新出的一个类型&#xff0c;正如类型的简介所说&#xff0c;initializer_list一般用于作为构造函数的参数&#xff0c;来让我们更方便赋值 但是光看这些&#xff0c;我们还是不知道initializer_list到底是个什么类型&#xff0c;…

链表数组插入排序

InsertSort 插入排序算法&#xff0c;比如打扑克牌的算法时&#xff0c;按照从左到右&#xff0c;找到对应的位置插入排序 最重要的是位置移动 找到对应位置值 #include "iostream" #include "bits/stdc.h"using namespace std;void sort(vector<in…

el-table,列表合并,根据名称列名称相同的品名将其它列值相同的进行合并

el-table,列表合并,根据名称列名称相同的品名将其它列值相同的进行合并,并且不能跨品名合并 如图 用到el-table合并行的方法合并 tableSpanMethod({ row, column, rowIndex, columnIndex }) {if (column.property "materielName") {//合并商品名const _row this…

滑动平均窗口的定义,优点,缺点,以及目前的应用!!

文章目录 前言一、滑动平均窗口的优点二、滑动平均窗口的缺点三、滑动平均窗口的应用 前言 滑动平均窗口是一种数据处理方法&#xff0c;它以固定的窗口大小对数据进行移动&#xff0c;并在每个窗口内计算数据的平均值。这种方法主要用于平滑数据&#xff0c;减小数据波动的影…

【算法通关村】链表基础经典问题解析

【算法通关村】链表基础&经典问题解析 一.什么是链表 链表是一种通过指针将多个节点串联在一起的线性结构&#xff0c;每一个节点&#xff08;结点&#xff09;都由两部分组成&#xff0c;一个是数据域&#xff08;用来存储数据&#xff09;&#xff0c;一个是指针域&…

【Linux】TCP套接字编程

目录 前言 UDP服务器的完善 线程的封装 结构定义 接口实现 环形队列 结构定义 接口实现 加锁 信号量的申请与释放 入队与出队 整体组装 初始化与析构 信息接收线程 消息发送线程 TCP套接字 创建套接字 listen accept 收发操作 客户端的编写 进一步完善 …

C语言中的格式化输出符号:%d %c %p %x等

文章目录 概览%d%c%d和%c的区别%p%x %X输出浮点数参考 概览 C语言中的格式化输出符号有很多&#xff0c;以下是一些常见的&#xff1a; %d 或 %i&#xff1a;用于输出十进制整数。 %u&#xff1a;用于输出无符号十进制整数。 %f&#xff1a;用于输出浮点数。 %s&#xff1a;用…

酷开科技 | 酷开系统,让家庭娱乐方式焕然一新!

在这个快节奏的社会&#xff0c;家庭娱乐已成为我们日常生活中不可或缺的一部分&#xff0c;为了给家庭带来更多欢笑与感动&#xff0c;酷开科技发力研发出拥有丰富内容和技术的智能电视操作系统——酷开系统&#xff0c;它集合了电影、电视剧、综艺、游戏、音乐等海量内容&…

基于SpringBoot的企业客户管理系统的设计与实现

摘 要 本论文主要论述了如何使用JAVA语言开发一个企业客户管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述企业客户管理系统的当前背景以及系统开发的目…

Spinnaker 基于 docker registry 触发部署

docker registry 触发部署 Spinnaker可以通过Docker镜像的变化来触发部署&#xff0c;这种方法允许你在Docker镜像发生变化时自动启动新的部署流程。 示例原理如下图所示&#xff1a; 以下是如何在Spinnaker中实现基于Docker Registry触发部署的配置流程。最终实现的效果如下…

JavaScript 的初步学习下篇

函数 语法格式 创建函数/函数声明/函数定义 function 函数名(形参列表) {函数体return 返回值; }函数调用 函数名(实参列表) // 不考虑返回值 返回值 函数名(实参列表) // 考虑返回值 注: 函数定义并不会执行函数体内容, 必须要调用才会执行. 调用几次就会执行几次. js 中…

Windows11系统下MemoryCompression导致内存占用率过高

. # &#x1f4d1;前言 本文主要是win11系统下CPU占用率过高如何下降的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日…

如何在财税行业查找批量客户?

现在市场上代记账公司也不算少&#xff0c;做过这行的都知道&#xff0c;最初呢行业竞争不强&#xff0c;都是靠地推、老客户转介绍&#xff0c;或者长期以往的蹲守各个地区的工商注册服务中心&#xff0c;找那些才注册企业的老板或者创业者。但是&#xff0c;随着市场经济的发…

iceoryx(冰羚)-publisher注册RouDi进程

1、发UserApp启动&#xff0c;发布REG消息过程 2、初始化状态转换 3、pub->RouDi 4、sub->RouDi 5、IPC通信 IPC通信支持socket和管道&#xff0c;IpcInterfaceUser ->RouDi进程的socket&#xff0c; IpcInterfaceCreator接收RouDi进程的消息socket。 optional<…

结构体||联合体

1.结构体 1.1实际生活中一些东西往往有多个元素组成。如一名学生有身高、体重、名字、学号等。这时候就需要用到结构体。 结构体是一些值的结合&#xff0c;这些值被称为成员变量。结构体的每个成员可以是不同类型的变量&#xff0c;如&#xff1a;标量、数组、指针、甚至是其…

基于SSM的网上书城

简介 本系统主要分为前台和后台&#xff0c;前台网页主要是面向用户的&#xff0c;用户注册登录后网上书城可以进行下单购买图书&#xff0c;主要功能有图书基本信息的查询、用户登录和注册、图书搜索、添加购物车、购买、订单查询等功能&#xff0c;后台是管理员进入的&#x…

Python基础学习快速入门

文章目录 Number变量String字符串Def函数Class类List列表Tuple元组Dictionary字典Set集合值与引用类型if条件控制Loop循环 Number变量 python直接赋值&#xff0c;不需要定义变量类型。不需要**,逗号结尾符 使用print**直接进行输出 #赋值 a 1.0 print(a)a 7 print(a)p…

Selenium 学习(0.17)——软件测试之测试用例设计方法——白盒测试——逻辑覆盖法(条件覆盖和条件判定覆盖)

条件覆盖 设计测试用例&#xff0c;使每个判断中每个条件的可能取值至少满足一次。 条件判定覆盖 通过设计足够的测试用例&#xff0c;满足如下条件&#xff1a; 所有条件的可能至少执行一次的取值 所有判断的可能结果至少执行一次 条件判定覆盖同时满足判定覆…