Nacos 服务发现(订阅)源码分析(服务端)
前言:
前文我们分析了 Nacos 服务发现(订阅)的流程,从 Nacos Client 端的源码分析了服务发现的过程,服务发现最终还是要调用 Nacos Server 端来获取服务信息,缓存到客户端本地,并且会定时向 Nacos Server 端发送请求,获取服务信息,本篇我们从 Nacos Server 来分析一下服务订阅源码。
Nacos 系列文章传送门:
Nacos 初步认识和 Nacos 部署细节
Nacos 配置管理模型 – 命名空间(Namespace)、配置分组(Group)和配置集ID(Data ID)
Nacos 注册中心和配置中心【实战】
服务启动何时触发 Nacos 的注册流程?
Nacos Client 端服务注册流程源码分析
Nacos Server 端服务注册流程源码分析
Nacos 服务发现(订阅)源码分析(客户端)
InstanceController#list 方法源码解析
前文我们分析到服务的发现(订阅)最终会调用 Nacos Server 端的接口,而这个接口就在 InstanceController 中,根据接口路径我们找到了对应的方法也就是 InstanceController#list 方法,源码解析如下:
//com.alibaba.nacos.naming.controllers.InstanceController#list
@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public ObjectNode list(HttpServletRequest request) throws Exception {//获取 namespaceIdString namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);//获取 serviceNameString serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);//检查格式NamingUtils.checkServiceNameFormat(serviceName);//agent java、c、c++、go、nginx、dnsfString agent = WebUtils.getUserAgent(request);//获取集群信息String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);//获取 udp 端口int udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));//获取环境信息String env = WebUtils.optional(request, "env", StringUtils.EMPTY);boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));String app = WebUtils.optional(request, "app", StringUtils.EMPTY);String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));//获取服务列表return doSrvIpxt(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant,healthyOnly);
}
InstanceController#list 方法本身没有什么难懂逻辑,只是从 request 中获取一些属性后,继续调用了 InstanceController#doSrvIpxt 方法,我们接着往下看。
InstanceController#doSrvIpxt 方法源码解析
InstanceController#doSrvIpxt 方法的源码比较多,大概拆分一下重要步骤,做了如下事情。
- 根据 namespaceId, 和 serviceName 获取服务信息。
- 判断是有有客户端订阅了服务,如果由客户端订阅了服务,则加入到 UDP 推送列表中,也就是之前我们分析过的 Nacos Server 是如何通知 Nacos Client 服务下线。
- 阀值判断,通过各种判断规则得到服务列表(判断详情请看源码分析)。
- 封装结果集返回。
//com.alibaba.nacos.naming.controllers.InstanceController#doSrvIpxt
public ObjectNode doSrvIpxt(String namespaceId, String serviceName, String agent, String clusters, String clientIP,int udpPort, String env, boolean isCheck, String app, String tid, boolean healthyOnly) throws Exception {//创建客户端对象ClientInfo clientInfo = new ClientInfo(agent);//创建 ObjectNodeObjectNode result = JacksonUtils.createEmptyJsonNode();//根据 namespaceId serviceName 获取 serviceService service = serviceManager.getService(namespaceId, serviceName);//缓存时间 默认 10 秒long cacheMillis = switchDomain.getDefaultCacheMillis();// now try to enable the pushtry {// udp 端口大于0 且已经开启推送 只有客户端订阅了 udp 端口才会大于0if (udpPort > 0 && pushService.canEnablePush(agent)) {//添加当前客户端 IP、UDP端口到 PushService 中 会作为可推送的目标客户端添加给推送服务组件pushService.addClient(namespaceId, serviceName, clusters, agent, new InetSocketAddress(clientIP, udpPort),pushDataSource, tid, app);//根据服务名 获取缓存时间默认10 秒cacheMillis = switchDomain.getPushCacheMillis(serviceName);}} catch (Exception e) {Loggers.SRV_LOG.error("[NACOS-API] failed to added push client {}, {}:{}", clientInfo, clientIP, udpPort, e);cacheMillis = switchDomain.getDefaultCacheMillis();}//service 为空判断if (service == null) {if (Loggers.SRV_LOG.isDebugEnabled()) {Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);}//service 为空 构造空对象 返回result.put("name", serviceName);result.put("clusters", clusters);result.put("cacheMillis", cacheMillis);result.replace("hosts", JacksonUtils.createEmptyArrayNode());return result;}//检查服务是否禁用 默认是 启用的checkIfDisabled(service);//服务实例 ipsList<Instance> srvedIPs;//服务实例 ipssrvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));// filter ips using selector://若选择器不空 则根据选择算法选择可用的intance列表 默认情况下 选择器不做任何过滤if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) {srvedIPs = service.getSelector().select(clientIP, srvedIPs);}//serviceIps 为空判断if (CollectionUtils.isEmpty(srvedIPs)) {//为空if (Loggers.SRV_LOG.isDebugEnabled()) {Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);}//客户端类型判断if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {result.put("dom", serviceName);} else {result.put("dom", NamingUtils.getServiceName(serviceName));}//构造空对象返回result.put("name", serviceName);result.put("cacheMillis", cacheMillis);result.put("lastRefTime", System.currentTimeMillis());result.put("checksum", service.getChecksum());result.put("useSpecifiedURL", false);result.put("clusters", clusters);result.put("env", env);result.set("hosts", JacksonUtils.createEmptyArrayNode());result.set("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));return result;}//存储健康和不健康的实例//key为true的value中存放的是所有健康的instance//key为false的value存放的是所有不健康的instanceMap<Boolean, List<Instance>> ipMap = new HashMap<>(2);ipMap.put(Boolean.TRUE, new ArrayList<>());ipMap.put(Boolean.FALSE, new ArrayList<>());//服务实例遍历 区分健康和不健康的实例for (Instance ip : srvedIPs) {ipMap.get(ip.isHealthy()).add(ip);}//阀值判断 isCheck 客户端请求中如果没有传值 则默认是 fasleif (isCheck) {//false 标识没有达到保护阀值result.put("reachProtectThreshold", false);}//获取保护阀值double threshold = service.getProtectThreshold();//通过健康实例除以所有实例 来判断是否触发阀值if ((float) ipMap.get(Boolean.TRUE).size() / srvedIPs.size() <= threshold) {//进入这里标识已经出发了保护阀值Loggers.SRV_LOG.warn("protect threshold reached, return all ips, service: {}", serviceName);if (isCheck) {//启动服务保护机制result.put("reachProtectThreshold", true);}//将不健康的实例全部加入到健康的实例中//这样做的好处是可以保证服务不会那么快被打崩溃 即使有部分失败的 但是还是有可用的服务 //不健康的实例存在的目的就是分流 缓解健康服务的压力ipMap.get(Boolean.TRUE).addAll(ipMap.get(Boolean.FALSE));//清空不健康的实例ipMap.get(Boolean.FALSE).clear();}//阀值判断if (isCheck) {result.put("protectThreshold", service.getProtectThreshold());result.put("reachLocalSiteCallThreshold", false);return JacksonUtils.createEmptyJsonNode();}//能够走到这里 标识没有出发 阀值保护ArrayNode hosts = JacksonUtils.createEmptyArrayNode();//遍历实例for (Map.Entry<Boolean, List<Instance>> entry : ipMap.entrySet()) {List<Instance> ips = entry.getValue();//如果只需要健康的实例 那就跳过不健康的实例if (healthyOnly && !entry.getKey()) {continue;}//遍历服务实例for (Instance instance : ips) {// remove disabled instance://移除禁用的实例if (!instance.isEnabled()) {continue;}//创建空对ObjectNode ipObj = JacksonUtils.createEmptyJsonNode();//构建实例对象ipObj.put("ip", instance.getIp());ipObj.put("port", instance.getPort());// deprecated since nacos 1.0.0:ipObj.put("valid", entry.getKey());ipObj.put("healthy", entry.getKey());ipObj.put("marked", instance.isMarked());ipObj.put("instanceId", instance.getInstanceId());ipObj.set("metadata", JacksonUtils.transferToJsonNode(instance.getMetadata()));ipObj.put("enabled", instance.isEnabled());ipObj.put("weight", instance.getWeight());ipObj.put("clusterName", instance.getClusterName());if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {ipObj.put("serviceName", instance.getServiceName());} else {ipObj.put("serviceName", NamingUtils.getServiceName(instance.getServiceName()));}ipObj.put("ephemeral", instance.isEphemeral());hosts.add(ipObj);}}//设置服务实例列表result.replace("hosts", hosts);//客户端类型判断if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {result.put("dom", serviceName);} else {result.put("dom", NamingUtils.getServiceName(serviceName));}//返回结果result.put("name", serviceName);result.put("cacheMillis", cacheMillis);result.put("lastRefTime", System.currentTimeMillis());result.put("checksum", service.getChecksum());result.put("useSpecifiedURL", false);result.put("clusters", clusters);result.put("env", env);result.replace("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));return result;
}
PushService#addClient 方法源码解析
我们上面在分析 InstanceController#doSrvIpxt 方法时候,提到如果客户端的订阅了该服务,Nacos 服务端会进行通过 UDP 推送给客户端最新的服务信息,而这个操作就是由 PushService 类实现的,PushService#addClient 方法只是把服务相关信息加入到了推送列表中。
//com.alibaba.nacos.naming.push.PushService#addClient(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.net.InetSocketAddress, com.alibaba.nacos.naming.push.DataSource, java.lang.String, java.lang.String)
public void addClient(String namespaceId, String serviceName, String clusters, String agent,InetSocketAddress socketAddr, DataSource dataSource, String tenant, String app) {//构造 PushClient 对象PushClient client = new PushClient(namespaceId, serviceName, clusters, agent, socketAddr, dataSource, tenant,app);//加入到推送列表中addClient(client);
}//com.alibaba.nacos.naming.push.PushService#addClient(com.alibaba.nacos.naming.push.PushService.PushClient)
public void addClient(PushClient client) {// client is stored by key 'serviceName' because notify event is driven by serviceName change//获取 serviceKeyString serviceKey = UtilsAndCommons.assembleFullServiceName(client.getNamespaceId(), client.getServiceName());//从 客户端 map 中获取 当前 client 对象ConcurrentMap<String, PushClient> clients = clientMap.get(serviceKey);//为空 判断if (clients == null) {//为空 则加入到 客户端 map 中clientMap.putIfAbsent(serviceKey, new ConcurrentHashMap<>(1024));//加入后获取clients = clientMap.get(serviceKey);}//获取之前的 client 对象PushClient oldClient = clients.get(client.toString());if (oldClient != null) {//为空空 刷新 其实是修改 client 最后一次引用时间 可以理解为更新时间oldClient.refresh();} else {//为空 也就是还没有注册这个推送目标客户端 将 client 加入到 clientsPushClient res = clients.putIfAbsent(client.toString(), client);if (res != null) {Loggers.PUSH.warn("client: {} already associated with key {}", res.getAddrStr(), res.toString());}Loggers.PUSH.debug("client: {} added for serviceName: {}", client.getAddrStr(), client.getServiceName());}
}
PushService#onApplicationEvent 方法源码分析
上面我们分析到客户端获取服务信息的时候,服务端会判断是否有客户端订阅了该服务信息,如果有,则会出发推送给客户端,最终会把服务信息封装成一个 PushClient 加入到 clientMap 中,前文我们分析了客户端是如果感知服务下线的,其中也发现了一个 clientMap 的存储结构,而在 PushService#onApplicationEvent 方法会注册一个延时任务并将该 future 放入 futureMap,该延时任务会从 clientMap 获取指定namespaceId、 serviceName 的client 集合,遍历 client 集合,判断 client 是否是 zombie(僵尸) client,如果是的则移除该 client,否则创建 Receiver.AckEntry,然后通过 UDP 的方式推送给 client,执行完毕后会从 futureMap 移除该 future,至此回到了我们前一篇分析的地方,后续就是我们熟悉的流程,不在重复分析了。
//com.alibaba.nacos.naming.push.PushService#onApplicationEvent
public void onApplicationEvent(ServiceChangeEvent event) {//从事件对象中获取到 serviceService service = event.getService();//获取 servicenameString serviceName = service.getName();//获取名称空间idString namespaceId = service.getNamespaceId();//使用延时任务 延时1 秒 通过 UDP 的方式来发送Future future = GlobalExecutor.scheduleUdpSender(() -> {try {Loggers.PUSH.info(serviceName + " is changed, add it to push queue.");//从缓存map中获取当前服务的内层map 内层map中存放着当前服务的所有Nacos Client的//根据 namespaceId 和 serviceName 获取对应的 client 信息ConcurrentMap<String, PushClient> clients = clientMap.get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));//为空判断 如果为空 就没有必要推送了if (MapUtils.isEmpty(clients)) {return;}//创建缓存 mapMap<String, Object> cache = new HashMap<>(16);//获取当前时间的 纳秒long lastRefTime = System.nanoTime();//遍历所有的 client 信息for (PushClient client : clients.values()) {//是否是僵尸客户端if (client.zombie()) {Loggers.PUSH.debug("client is zombie: " + client.toString());//如果是的话 就移除僵尸客户端clients.remove(client.toString());Loggers.PUSH.debug("client is zombie: " + client.toString());continue;}//ACKReceiver.AckEntry ackEntry;Loggers.PUSH.debug("push serviceName: {} to client: {}", serviceName, client.toString());//获取推送 keyString key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());byte[] compressData = null;Map<String, Object> data = null;//switchDomain.getDefaultPushCacheMillis() 默认是 10秒 因此不会进入 ifif (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);compressData = (byte[]) (pair.getValue0());data = (Map<String, Object>) pair.getValue1();Loggers.PUSH.debug("[PUSH-CACHE] cache hit: {}:{}", serviceName, client.getAddrStr());}//封装 ackEntry 将客户端信息封装到 ackEntry if (compressData != null) {ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);} else {//这里初始化了需要推送的 客户端ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);if (ackEntry != null) {cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));}}Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",client.getServiceName(), client.getAddrStr(), client.getAgent(),(ackEntry == null ? null : ackEntry.key));//通过 udp 协议向 Nacos 客户端推送数据udpPush(ackEntry);}} catch (Exception e) {Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);} finally {//移除 futurefutureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));}}, 1000, TimeUnit.MILLISECONDS);//任务放入 futureMap 表示已经发送了 udp 到客户端的服务实例futureMap.put(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName), future);}
总结:服务发现(订阅)Nacos 服务端的代码还是比较简答的,而且也有一种一通百通的感觉,分析的过程中,又回到了我们前文分析的代码,一下子就知道是怎么回事了,也更加理解了 Nacos 的设计思想。
欢迎提出建议及对错误的地方指出纠正。
相关文章:
Nacos 服务发现(订阅)源码分析(服务端)
前言: 前文我们分析了 Nacos 服务发现(订阅)的流程,从 Nacos Client 端的源码分析了服务发现的过程,服务发现最终还是要调用 Nacos Server 端来获取服务信息,缓存到客户端本地,并且会定时向 Na…...
DICOM CT\MR片子免费在线查看工具;python pydicom包加载查看;mayavi 3d查看
DICOM CT\MR片子免费在线查看工具 参考: https://zhuanlan.zhihu.com/p/668804209 dicom格式: DICOM(Digital Imaging and Communications in Medicine)是医学数字成像和通信的标准。它定义了医学图像(如CT、MRI、X…...
VSCode远程连接Ubuntu/Linux
文章目录 前言SSH(Secure Shell)简介主要功能工作原理常见的 SSH 客户端和服务器 Ubuntu安装sshvscode远程插件安装远程插件开始远程连接 打开文件夹新建终端 总结 前言 在现代开发环境中,远程工作和跨平台开发变得越来越普遍。Visual Studi…...
【Nginx80端口被占用】80端口被System占用如何解决【已解决】
【Nginx80端口被占用】80端口被System占用如何解决【已解决】 01 问题背景 Nginx 版本 1.19及以上80端口被System占用,无法kill tcp6 0 0 :::111 :::* LISTEN 1/systemd tcp6 0 0 :::80 :::* LISTEN 1/systemd 执行以下代码无效&…...
云计算的发展历程与边缘计算
云计算的发展历程 初期发展(1960s-1990s) 概念萌芽:云计算的概念可以追溯到1960年代,当时约翰麦卡锡(John McCarthy)提出了“计算将来可能成为一种公共设施”的想法。这个概念类似于现代的云计算…...
199.二叉树的右视图(DFS)
给定一个二叉树的根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 3: 输入: [] 输出: [] 解题…...
机器学习基础入门(1)
最近也在努力的想要学习些机器学习的知识,目前正在了解各个概念及术语,下面就把学习到的概念都列出来。 人工智能 (AI) Artificial intelligence 人工智能生成内容(AIGC) 机器学习(ML) Machine Learning …...
mybatis的xml中,where标签不自动删除多余的and之类的问题
遇到了这个莫名其妙的问题,起初是很疑惑的,where标签好像失灵了一般不会自动删除掉 多余的and 看了眼sql语句,发现还是有and没被删除。 后来重新写了遍后发现又没事了。真的是神人。 然后就研究了好一会,发现!&#…...
RK3588 编译opencvopencv_contrib记录
RK3588 编译opencv&opencv_contrib记录 1. 下载文件1.1 opencv源码1.2 安装cmake 2.开始编译2.1 提示缺少boostdesc_bgm.i 等问题2.2 提示缺少某hpp头文件2.3 其它问题 3. 设置环境变量4. 测试5.参考 1. 下载文件 1.1 opencv源码 需要opencv和opencv-contrib的版本号保持…...
Eureka: 微服务架构中的服务发现与注册实践
Eureka介绍与使用教程 你好,我是悦创。 Eureka 是 Netflix 开发的一款服务发现(Service Discovery)工具,它主要用于云中基于微服务架构的应用程序。Eureka使服务实例能够动态地注册自己,而其他服务实例可以通过 Eure…...
8、添加第三方包
目录 1、安装Django Debug Toolbar Django的一个优势就是有丰富的第三方包生态系统。这些由社区开发的包,可以用来快速扩展应用程序的功能集 1、安装Django Debug Toolbar Django Debug Toolbar位于名列前三的第三方包之一 这是一个用于调试Debug Web应用程序的有…...
【算法】算法模板
算法模板 文章目录 算法模板简介数组字符串列表数学树图动态规划 简介 博主在LeetCode网站中学习算法的过程中使用到并总结的算法模板,在算法方面算是刚过初学者阶段,竞赛分数仅2000。 为了节省读者的宝贵时间,部分基础的算法与模板未列出。…...
特征工程方法总结
方法有以下这些 首先看数据有没有重复值、缺失值情况 离散:独热 连续变量:离散化(也成为分箱) 作用:1.消除异常值影响 2.引入非线性因素,提升模型表现能力 3.缺点是会损失一些信息 怎么分:…...
Unity | AssetBundle
1 定义 Unity中的一种特殊资源包格式,用于存储和分发游戏资源。这些资源可以包括模型、纹理、音频文件、预制体、场景等。 AssetBundle允许开发者在游戏运行时动态加载和卸载资源,从而实现灵活的资源管理。 2 使用场景 1、资源管理 有效管理游戏中的资…...
【虚幻引擎】C++网络通信TCP和HTTP实战开发全流程,以接入科大讯飞星火大模型和文心一言千帆大模型为案例讲解
本套课程介绍了使用我们的虚幻C去写开发我们的插件开发,如何使用我们的虚幻C 封装我们的TCP和HTTP,如何使用的我们虚幻C子系统,如何根据第三方文档去写接口请求,如何通过我们的加密算法去签名我们的URL,如何声明我们的…...
.NET单元测试使用AutoFixture按需填充的方法总结
AutoFixture是一个.NET库,旨在简化单元测试中的数据设置过程。通过自动生成测试数据,它帮助开发者减少测试代码的编写量,使得单元测试更加简洁、易读和易维护。AutoFixture可以用于任何.NET测试框架,如xUnit、NUnit或MSTest。 默…...
求职学习day5
安排明天hr面 投一下平安可能。 hr面准备,复习java核心技术,复习java项目。 正视自己,调整心态。 也是很早接触了javaguide但是没有持续学习,项目介绍 | JavaGuide,面试前复习一下感觉还是很有收获的。 还有一些…...
微服务常用的中间件有哪些?都有什么用途?
前言 最近整理一下我们的项目使用了哪些中间件,借此机会也来分享一下,在微服务架构中我们常用的那些中间件,都有什么作用,为什么要使用中间件。 消息中间件-RocketMQ 比如RocketMQ,RocketMQ 是一个开源的分布式消息…...
华为云认证
华为云认证 首页 云原生 DevOps工作级开发者认证:HCCDP – Cloud Native DevOps 对云上敏捷开发感兴趣的人员,培训DevOps的理论知识及在云端交付软件全生命周期的实操能力。 DevOps...
【Linux学习】常用基本指令
🔥个人主页: Forcible Bug Maker 🔥专栏:Linux学习 目录 🌈前言🔥XShell的一些使用查看Linux主机IP使用XShell登录主机XShell下的复制粘贴 🔥Linux下常用基本指令ls指令pwd指令cd指定touch指令…...
windows上安装Apache
安装前须知: 下载并安装,如未完成,请访问下载页面。安装Apache前需要安装Visual C Redistributable for Visual Studio 2015-2022 x64。 解压与配置: 将Apache24文件夹解压至C:\Apache24(这是配置中的ServerRoot&am…...
wps office 2019 Pro Plus 集成序列号Vba安装版教程
前言 wps office 2019专业增强版含无云版是一款非常方便的办公软件,我们在日常的工作中总会碰到需要使用WPS的时候,它能为我们提供更好的文档编写帮助我们更好的去阅读PDF等多种格式的文档,使用起来非常的快捷方便。使用某银行专业增强版制作…...
院内影像一体化平台PACS源码,C#语言的PACS/RIS系统,二级医院应用案例
全院级PACS系统源码,一体化应用系统整合,满足放射、超声、内窥镜中心、病理、检验等多个科室的工作流程和需求,为不同科室提供专业的解决方案,实现了全院乃至区域内信息互联互通、数据统一存储与管理等功能,做到以病人…...
基于java的设计模式学习
PS :以作者的亲身来看,这东西对于初学者来说有用但不多,这些东西,更像一种经验的总结,在平时开发当中一般是用不到的,因此站在这个角度上用处不大。 1.工厂模式 1.1 简单工厂模式 我们把new 对象逻辑封装…...
组合数学+费用背包+刷表,G2 - Playlist for Polycarp (hard version)
目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 G2 - Playlist for Polycarp (hard version) 二、解题报告 1、思路分析 一…...
阿尔泰科技利用485模块搭建自动灌溉系统实现远程控制
自动灌溉系统又叫土壤墒情监控系统,土壤墒情监控系统主要实现固定站无人值守情况下的土壤墒情数据的自动采集和无线传输,数据在监控中心自动接收入库;可以实现24小时连续在线监控并将监控数据通过有线、无线等传输方式实时传输到监控中心生成…...
Python正则表达式中的分组
表达式中的分组 它是可以通过" () “来进行分组,更专业的表达就是捕获组,每个完整的” () “可以分为一组,同时,” () “中还可以嵌套” () ",即组之间还可以存在更小的组 概念 1、当我们在一个正则表达式…...
openstack设置IP直接登录,不需要加dashboard后缀
openstack 实验环境,openstack-t版,centos2009 修改配置文件 [rootcontroller ~]# vim /WEBROOT /etc/openstack-dashboard/local_settings #将dashboard去掉 WEBROOT /dashboard/ #改为 WEBROOT /[rootcontroller ~]# vim /etc/httpd/conf.d/openst…...
PHP宠物店萌宠小程序系统源码
🐾萌宠生活新方式🐾 🏡【一键直达萌宠世界】 你是否也梦想着拥有一家随时能“云撸猫”、“云吸狗”的神奇小店?现在,“宠物店萌宠小程序”就是你的秘密花园!🌟只需轻轻一点,就能瞬…...
nginx负载均衡实例
实现效果 浏览器输入地址http://nginx服务器ip(:80)/edu/a.html,实现负债均衡效果,平均分配到 服务器ip:8080和 服务器ip:8081进程中。 准备工作 准备两个tomcat,一个监听在8080端口,一个监听在8081端口。也可以准备多个tomcat。…...
环保网站建设/北京网站建设公司报价
使用mybatis标签规避空where一、where标签案例1、原始sql2、简单if标签判断是否为空3、使用where标签后逻辑代码二、Select注解中当参数为空则不添加该参数的判断一、where标签案例 List<FilterUsersListQueryResDTO> filterUsers(Param("filterUsersQueryDTO"…...
古尔邦节网站建设/怎么发帖子做推广
题意:给一个无向无环图(n<1000),在尽量少的节点上放灯,使得所有边都被照亮,灯可以照亮相邻的边,在灯数最小的前提下,使得被两盏灯照亮的边最多,输出灯数以及被两盏灯照亮的边数,及…...
哪些网站做的好看/万网官网
一、无法动态更新数据的实例 1. 如下,数据库中创建了班级表和教师表,两张表的对应关系为“多对多” 1 from django.db import models2 3 4 class Classes(models.Model):5 title models.CharField(max_length32)6 7 8 class Teacher(models.Model):…...
芜湖网站建设/餐饮品牌全案策划
Matplotlib是用于数据可视化的最流行的Python包之一。 它是一个跨平台库,用于根据数组中的数据制作2D图。 它提供了一个面向对象的API,有助于使用Python GUI工具包(如PyQt,WxPythonotTkinter)在应用程序中嵌入绘图。 它…...
期货贵金属网站源码建设/如何介绍自己设计的网页
1、用预编译指令符可以避免在多文件工程中调用文件的时候可能出现的重复定义的现象。比如:Main.cpp#include “Animal.h”#include “Fish.h”……Animal.hclass Animal(){}Fish.h#include “Animal.h”class Fish():public Animal{}因此在调用Main.cpp的时候先运行…...
为推广网站做的宣传活动/搜索引擎优化seo应用
浮动的特点 1.脱离文档流 2.浮动元素会脱离文档流并向左/向右浮动,直到碰到父元素或另一个浮动元素 3.会导致父元素高度坍塌 早期为实现文字环绕效果 清除浮动 一个常用的clearfix清除浮动方法: .clearfix:before,//befor以解决现代浏览器上边距折叠的问…...