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

news2025/1/23 4:48:59

SpringBoot自动注入

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

获取实例信息

NacosDiscoveryClient

	private NacosServiceDiscovery serviceDiscovery;

	public NacosDiscoveryClient(NacosServiceDiscovery nacosServiceDiscovery) {
		this.serviceDiscovery = nacosServiceDiscovery;
	}

	@Override
	public 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);
	}
    @Override
    public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy) throws NacosException {
        return selectInstances(serviceName, groupName, healthy, true);
    }
    @Override
    public List<Instance> selectInstances(String serviceName, String groupName, boolean healthy, boolean subscribe)
            throws NacosException {
        return selectInstances(serviceName, groupName, new ArrayList<String>(), healthy, subscribe);
    }
    @Override
    public 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请求拉取服务实例信息

    @Override
    public 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);
        //定时同步服务端serviceInfo
        serviceInfoUpdateService.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) {
       //拿服务当key
        String 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;
                // 重置失败数量为0
                resetFailCount();
            } 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方法中,通过如下方式进行了监听事件的注册:

    @Override
    public 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>>();
    @Override
    public 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作为当前的instance
                            Optional 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的metadata
                                if (!this.properties.getMetadata().equals(instance.getMetadata())) {
                                    this.properties.setMetadata(instance.getMetadata());
                                }
                            });
                        }
                    }
                };
            });
        }
    }

获取服务信息

NacosDiscoveryClient

	@Override
	public 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();
	}
    @Override
    public ListView<String> getServicesOfServer(int pageNo, int pageSize, String groupName) throws NacosException {
        return getServicesOfServer(pageNo, pageSize, groupName, null);
    }
    @Override
    public ListView<String> getServicesOfServer(int pageNo, int pageSize, String groupName, AbstractSelector selector)
            throws NacosException {
        return clientProxy.getServiceList(pageNo, pageSize, groupName, selector);
    }

GRPC拉取信息

    @Override
    public 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 selector
            List<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);
    }

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

    @Override
    public 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));
    }

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

    @Override
    public 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.coloradmin.cn/o/1279629.html

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

相关文章

ROS集成开发环境的搭建

目录 一、安装终端 二、安装VScode 1、安装 2、vscode 集成 ROS 插件 三、launch文件示例 实现 运行 launch 文件 一、安装终端 sudo apt install terminatorTerminator 常用快捷键 AltUp //移动到上面的终端 AltDown …

基于remix+metamask+ganache的智能合约部署调用

在我们部署合约时为了让它更接近真实区块链去中心化体验&#xff0c;我们需要调用小狐狸&#xff08;Metamask&#xff09;来进行真实交易&#xff0c;而metamask里没有内置虚拟测试币&#xff0c;我们需要进行调用Ganache来添加带有虚拟测试币的账号。以上就是三者的关系&…

量子光学的进步:光子学的“下一件小事”

量子光学是量子力学和光学交叉领域中发展迅速的一门学科&#xff0c;探索光的基本特性及其与物质在量子水平上的相互作用。通过利用光的独特特性&#xff0c;量子光学为通信、计算、密码学和传感等各个学科的变革性进步铺平了道路。 如今&#xff0c;量子光学领域的研究人员和工…

YITH Request a Quote Premium商城请求报价插件

点击访问原文 YITH Request a Quote Premium商城请求报价插件 - 易服客工作室 YITH Request a Quote Premium商城请求报价插件是一款非常强大的工具&#xff0c;专为希望为其尊贵客户提供定制定价选项的企业量身定制。这个特殊的插件使客户能够认真提交包含特定产品或服务的细…

Python---格式化输出与%百分号----涉及转义符 \ 反斜杠的使用

相关链接Python--格式化输出中的转义符号----\t 制表符&#xff08;空格的&#xff09;和\n&#xff08;换行的&#xff09;_唯元素的博客-CSDN博客 Python---字符串&#xff08;用单、双引号、 三单/双引号定义。反斜杠 \ 转义&#xff0c;单在双内/双在单内 &#xff09;-CS…

手写VUE后台管理系统7 - 整合Less样式

整合LESS 安装使用 Less&#xff08;Leaner Style Sheets&#xff09;&#xff0c;是一门向后兼容的 CSS 扩展语言。 Less 官网&#xff1a;https://less.bootcss.com/ 安装 yarn add less安装完成就可以直接使用了 使用 以文件形式定义全局样式 在 assets 目录下创建 less …

【Element-ui】InputNumber 计数器与Select 选择器

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、InputNumber 计数器1.1 基础用法&#xff1a;1.2 禁用状态1.3 步数1.4 严格步数1.5 精度1.6 尺寸1.7 按钮位置1.8 Events1.9 Methods 二、Select 选择器2.1…

超大规模集成电路设计----学习框架(一)

本文仅供学习&#xff0c;不作任何商业用途&#xff0c;严禁转载。绝大部分资料来自----数字集成电路——电路、系统与设计(第二版)及中国科学院段成华教授PPT 超大规模集成电路设计----学习框架&#xff08;一&#xff09; 这门课在学什么&#xff1f;这门课该怎么学&#xf…

Java LeetCode篇-深入了解关于栈的经典解法(栈实现:中缀表达式转后缀)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 中缀表达式转后缀说明 1.1 实现中缀表达式转后缀思路 2.0 逆波兰表达式求值 2.1 实现逆波兰表达式求值思路 3.0 有效的括号 3.1 实现有效的括号思路 4.0 栈的压…

魔法时代:人工智能如何重塑我们的日常生活

现在的我们正在目睹一个新时代的到来——人工智能(AI)的时代。这个时代由算法驱动&#xff0c;由数据精炼&#xff0c;由机器学习引领&#xff0c;正逐渐成为我们生活中不可或缺的一部分。人工智能像一位魔法师&#xff0c;以其独特的能力&#xff0c;从根本上改变了我们的生活…

力扣题:字符的统计-12.2

力扣题-12.2 [力扣刷题攻略] Re&#xff1a;从零开始的力扣刷题生活 力扣题1&#xff1a;423. 从英文中重建数字 解题思想&#xff1a;有的单词通过一个字母就可以确定&#xff0c;依次确定即可 class Solution(object):def originalDigits(self, s):""":typ…

Hdoop学习笔记(HDP)-Part.14 安装YARN+MR

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

可用的镜像 yum 源

目录 ftp.sjtu.edu.cn 镜像 yum 源centos 的镜像 yum 源 mirrors.sohu.comcentos 的镜像 yum 源 mirrors.163.comcentos 的镜像 yum 源 ftp.sjtu.edu.cn 镜像 yum 源 镜像 yum 源地址 &#xff1a; http://ftp.sjtu.edu.cn/centos/ centos 的镜像 yum 源 http://ftp.sjtu.edu…

git如何关联克隆远程仓库

一、添加远程仓库 之前我们仅仅是在本地创建了一个Git本地仓库&#xff0c;这里我们再在GitHub创建一个Git远程仓库&#xff0c;并且让这两个仓库进行远程同步&#xff0c;这样&#xff0c;GitHub上的仓库既可以作为备份&#xff0c;又可以让其他人通过该仓库来协作开发。 1.…

面试--各种场景问题总结

1.在开发过程中&#xff0c;你是如何保证机票系统的正常运行的&#xff1f; 用户、测试、监控和日志、安全措施、数据备份、系统设计、需求分析 2.在机票系统开发过程中&#xff0c;你最有成就的事情&#xff0c;为什么&#xff1f; 用户体验感、高可用和稳定性、客户满意度、系…

使用Visual Studio创建第一个C代码工程

文章目录 2019创建C工程创建C文件运行 上一节我们使用记事本编辑C代码&#xff0c;在命令行运行文件&#xff0c;这种方式只是作为对编译器的了解&#xff0c;实际的开发中一般使用集成开发环境比较多&#xff0c;因为 集成开发环境操作比较简单&#xff0c;通常可编辑&#x…

cc-product-waterfall仿天猫、淘宝购物车店铺商品列表组件

cc-product-waterfall仿天猫、淘宝购物车店铺商品列表组件 引言 在电商应用中&#xff0c;购物车体验的优化对于提升用户满意度和转化率至关重要。在本文中&#xff0c;我们将深入探讨如何使用cc-product-waterfall组件&#xff0c;结合uni-number-box和xg-widget&#xff0c;…

WebGL笔记:图形缩放的原理和实现

缩放 1 &#xff09;原理 缩放可以理解为对向量长度的改变&#xff0c;或者对向量坐标分量的同步缩放 如下图&#xff0c;比如让向量OA 收缩到点B的位置&#xff0c;也就是从OA变成OB&#xff0c;缩放了一半 2 &#xff09;公式 已知 点A的位置是(ax,ay,az)点A基于原点內缩了…

L1-009:N个数求和

目录 ⭐题目描述⭐ ⭐分析 ⭐程序代码 运行结果 ⭐文案分享⭐ ⭐题目描述⭐ 本题的要求很简单&#xff0c;就是求N个数字的和。麻烦的是&#xff0c;这些数字是以有理数分子/分母的形式给出的&#xff0c;你输出的和也必须是有理数的形式。 输入格式&#xff1a; 输入第一行给出…

GAN:PacGAN-生成对抗网络中两个样本的威力

论文&#xff1a;https://arxiv.org/pdf/1712.04086.pdf 代码&#xff1a;GitHub - fjxmlzn/PacGAN: [NeurIPS 2018] [JSAIT] PacGAN: The power of two samples in generative adversarial networks 发表&#xff1a;2016 一、摘要 1&#xff1a;GAN最重大的缺陷是&#xf…