【Nacos源码系列】Nacos心跳机制原理

2023-10-31

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

心跳机制是什么

心跳机制是一种用于监测和管理微服务可用性的机制,它用来维护注册中心和服务提供者之间的连接状态,并及时更新服务实例的状态信息。

心跳机制包括两个主要组件:心跳发送方(客户端)和心跳接收方(服务端)。

在微服务架构中,心跳机制是一种用于监测和管理微服务可用性的机制。由于微服务架构通常由多个相互独立的微服务组成,每个微服务都有自己的生命周期和状态,因此需要一种方法来实时检测和通知微服务的健康状况。

微服务的心跳机制包括两个主要组件:心跳发送方和心跳接收方。

  1. 心跳发送方(Heartbeat Sender):每个微服务都会定期发送称为心跳消息的请求到一个中央位置(例如注册中心或负载均衡器)。这个心跳消息包含有关该微服务的健康信息,如服务是否正常运行、负载情况、资源消耗等。心跳消息的频率可以根据需求进行配置,通常是以固定的时间间隔发送。

  2. 心跳接收方(Heartbeat Receiver):中央位置上的组件(如注册中心或负载均衡器)负责接收并处理微服务发送的心跳消息。它会记录每个微服务的心跳,并根据心跳消息的到达情况和内容来判断微服务的可用性。如果心跳消息超过一定时间没有到达,或者心跳消息中报告了错误状态,中央位置可以采取相应的措施,如将该微服务标记为不可用、重新分配负载或发送警报通知等。

通过心跳机制,微服务架构可以实时监测微服务的健康状态,从而实现故障检测和自动恢复。当某个微服务出现故障或不可用时,其他微服务可以感知到并做出相应的处理,以确保整个系统的稳定性和可用性。此外,心跳机制还可以协助进行负载均衡、容量规划和资源管理等任务,提高整体系统的效率和性能。

Nacos心跳机制

当一个服务注册到Nacos注册中心时,它会向Nacos发送一个心跳包,告诉Nacos它仍然处于活动状态。服务提供者定期发送心跳包,以保证其状态信息是最新的。如果一个服务提供者在指定的时间段内没有发送心跳包,Nacos就会将该服务提供者的状态设置为不可用,并将其从可用服务列表中移除。

对于超过15s没有发送客户端心跳的实例会将它的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)。

本文将从客户端和服务端两个角度介绍Nacos心跳机制的原理。

客户端心跳

之前介绍Nacos注册原理的时候说过,在NacosNamingService#registerInstance()方法注册服务实例时会开启一个心跳定时任务。

private BeatReactor beatReactor;

@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    // 临时节点
    if (instance.isEphemeral()) {
        //封装心跳信息
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        //加入定时任务
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

//Instance对象封装BeatInfo
public BeatInfo buildBeatInfo(String groupedServiceName, Instance instance) {
    BeatInfo beatInfo = new BeatInfo();
    beatInfo.setServiceName(groupedServiceName);
    beatInfo.setIp(instance.getIp());
    beatInfo.setPort(instance.getPort());
    beatInfo.setCluster(instance.getClusterName());
    beatInfo.setWeight(instance.getWeight());
    beatInfo.setMetadata(instance.getMetadata());
    beatInfo.setScheduled(false);
    beatInfo.setPeriod(instance.getInstanceHeartBeatInterval());
    return beatInfo;
}

Nacos会对临时节点创建一个定时任务并进行心跳检查。
下面重点来看一下 BeatReactor#addBeatInfo方法:

//存放客户端心跳信息
public final Map<String, BeatInfo> dom2Beat = new ConcurrentHashMap<String, BeatInfo>();

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
    NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
    // 心跳key,形如:  DEFAULT_GROUP@@provider#192.168.71.70#9093
    String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());

    BeatInfo existBeat = null;
    //fix #1733
    //如果之前有该服务的的心跳信息,把之前的停止并移除
    if ((existBeat = dom2Beat.remove(key)) != null) {
        existBeat.setStopped(true);
    }
    //缓存客户端心跳信息到本地
    dom2Beat.put(key, beatInfo);
    //心跳定时任务 默认5s一次
    executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
    MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

BeatReactor#addBeatInfo方法最后会生成一个 BeatTask 定时心跳任务,该任务默认5s执行一次。

BeatTask 类是 BeatReactor类的内部类,它实现了 Runnable 接口,主要作用是定时向服务端发送心跳。

private final ScheduledExecutorService executorService;

private final NamingProxy serverProxy;

class BeatTask implements Runnable {

    BeatInfo beatInfo;

    public BeatTask(BeatInfo beatInfo) {
        this.beatInfo = beatInfo;
    }

    @Override
    public void run() {
        if (beatInfo.isStopped()) {
            return;
        }
        long nextTime = beatInfo.getPeriod();
        try {
            //请求服务端,发送心跳
            JsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);
            //心跳时间间隔
            long interval = result.get("clientBeatInterval").asLong();

            //lightBeatEnabled是否启用轻量级心跳,轻量级心跳不会把客户端心跳信息发送给服务端,可以更快地检测到实例故障并进行处理,客户端第一次传false,之后从服务端返回true
            boolean lightBeatEnabled = false;
            if (result.has(CommonParams.LIGHT_BEAT_ENABLED)) {
                lightBeatEnabled = result.get(CommonParams.LIGHT_BEAT_ENABLED).asBoolean();
            }
            BeatReactor.this.lightBeatEnabled = lightBeatEnabled;
            if (interval > 0) {
                //将下次心跳延迟发送时间设置为从服务端返回的clientBeatInterval值
                nextTime = interval;
            }
            int code = NamingResponseCode.OK;
            if (result.has(CommonParams.CODE)) {
                code = result.get(CommonParams.CODE).asInt();
            }
            //请求资源没有找到,404
            if (code == NamingResponseCode.RESOURCE_NOT_FOUND) {
                //创建一个实例
                Instance instance = new Instance();
                instance.setPort(beatInfo.getPort());
                instance.setIp(beatInfo.getIp());
                instance.setWeight(beatInfo.getWeight());
                instance.setMetadata(beatInfo.getMetadata());
                instance.setClusterName(beatInfo.getCluster());
                instance.setServiceName(beatInfo.getServiceName());
                instance.setInstanceId(instance.getInstanceId());
                instance.setEphemeral(true);
                try {
                    //注册实例
                    serverProxy.registerService(beatInfo.getServiceName(),
                        NamingUtils.getGroupName(beatInfo.getServiceName()), instance);
                } catch (Exception ignore) {
                }
            }
        } catch (NacosException ex) {
            NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: {}, code: {}, msg: {}",
                JacksonUtils.toJson(beatInfo), ex.getErrCode(), ex.getErrMsg());

        }
        //使用当前beatInfo定时执行心跳任务
        executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
    }
}

BeatTask 实现了 Runnable 接口,所以主要看run()方法的实现就行了。

BeatTask#run()方法,首先判断该定时任务的停止标记stopped为true,为true就不再执行该定时任务。

接着就是调用NamingProxy#sendBeat向服务端发起心跳请求,如果客户端没有在服务端的本地缓存中找到,会返回404,之后客户端就要向服务端发起注册请求。

最后再将封装了当前心跳信息的 BeatInfo对象重新放到一个 新的 BeatTask 心跳任务中去。 ScheduledExecutorService#schedule()方法是延迟一定时长执行指定任务,而且只执行一次,所以这里要在run()方法最后重新启动一个定时任务,以保证心跳的正常执行。

BeatReactor 类中有个 lightBeatEnabled属性,它用于判断是否启用轻量级心跳,轻量级心跳不会把客户端心跳信息发送给服务端,可以更快地检测到实例故障并进行处理,客户端第一次传false,之后从服务端返回true。

lightBeatEnabled 轻量级心跳

下面看一下NamingProxy#sendBeat方法:

public JsonNode sendBeat(BeatInfo beatInfo, boolean lightBeatEnabled) throws NacosException {

    if (NAMING_LOGGER.isDebugEnabled()) {
        NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());
    }
    Map<String, String> params = new HashMap<String, String>(8);
    Map<String, String> bodyMap = new HashMap<String, String>(2);
    //如果不是轻量级心跳,把心跳信息发送给服务端
    if (!lightBeatEnabled) {
        bodyMap.put("beat", JacksonUtils.toJson(beatInfo));
    }
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
    params.put(CommonParams.CLUSTER_NAME, beatInfo.getCluster());
    params.put("ip", beatInfo.getIp());
    params.put("port", String.valueOf(beatInfo.getPort()));
    String result = reqApi(UtilAndComs.nacosUrlBase + "/instance/beat", params, bodyMap, HttpMethod.PUT);
    return JacksonUtils.toObj(result);
}

心跳请求信息

客户端发送心跳相对简单一些,至此就结束了。总结一下客户端的心跳机制,针对临时节点:客户端在注册时,对于临时节点会添加一个BeatTask 定时心跳任务,默认每5s执行一次。定时心跳任务的主要作用就是定时向服务端发送 http 请求,请求路径为 /nacos/v1/ns/instance/beat。从服务端获得响应之后如果不满足服务端要求需要重新进行注册,最后默认延迟5s再次执行 BeatTask 定时心跳任务。

服务端接收心跳

服务端接收到客户端心跳会有哪些操作呢?

在服务端接收心跳的方法是com.alibaba.nacos.naming.controllers.InstanceController#beat()方法。下面看下beat方法的源码:

@Autowired
private SwitchDomain switchDomain;

@Autowired
private ServiceManager serviceManager;


@CanDistro
@PutMapping("/beat")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public ObjectNode beat(HttpServletRequest request) throws Exception {

    ObjectNode result = JacksonUtils.createEmptyJsonNode();
    //客户端心跳间隔时间,默认5s一次
    result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, switchDomain.getClientBeatInterval());

    String beat = WebUtils.optional(request, "beat", StringUtils.EMPTY);
    RsInfo clientBeat = null;
    //轻量级心跳时,没有beat参数信息
    if (StringUtils.isNotBlank(beat)) {
        clientBeat = JacksonUtils.toObj(beat, RsInfo.class);
    }
    String clusterName = WebUtils
        .optional(request, CommonParams.CLUSTER_NAME, UtilsAndCommons.DEFAULT_CLUSTER_NAME);
    String ip = WebUtils.optional(request, "ip", StringUtils.EMPTY);
    int port = Integer.parseInt(WebUtils.optional(request, "port", "0"));
    if (clientBeat != null) {
        if (StringUtils.isNotBlank(clientBeat.getCluster())) {
            clusterName = clientBeat.getCluster();
        } else {
            // fix #2533
            clientBeat.setCluster(clusterName);
        }
        ip = clientBeat.getIp();
        port = clientBeat.getPort();
    }
    String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    checkServiceNameFormat(serviceName);
    Loggers.SRV_LOG.debug("[CLIENT-BEAT] full arguments: beat: {}, serviceName: {}", clientBeat, serviceName);
    // 从缓存中获取实例
    Instance instance = serviceManager.getInstance(namespaceId, serviceName, clusterName, ip, port);

    //本地缓存中没有服务实例
    if (instance == null) {
        if (clientBeat == null) {
            //返回404,让客户端重新注册
            result.put(CommonParams.CODE, NamingResponseCode.RESOURCE_NOT_FOUND);
            return result;
        }

        Loggers.SRV_LOG.warn("[CLIENT-BEAT] The instance has been removed for health mechanism, "
            + "perform data compensation operations, beat: {}, serviceName: {}", clientBeat, serviceName);

        instance = new Instance();
        instance.setPort(clientBeat.getPort());
        instance.setIp(clientBeat.getIp());
        instance.setWeight(clientBeat.getWeight());
        instance.setMetadata(clientBeat.getMetadata());
        instance.setClusterName(clusterName);
        instance.setServiceName(serviceName);
        instance.setInstanceId(instance.getInstanceId());
        instance.setEphemeral(clientBeat.isEphemeral());
        //注册一个服务实例
        serviceManager.registerInstance(namespaceId, serviceName, instance);
    }
    //从ServiceManager#serviceMap缓存中获取service
    Service service = serviceManager.getService(namespaceId, serviceName);

    if (service == null) {
        throw new NacosException(NacosException.SERVER_ERROR,
            "service not found: " + serviceName + "@" + namespaceId);
    }
    //轻量级心跳创建一个服务端指标信息RsInfo对象
    if (clientBeat == null) {
        clientBeat = new RsInfo();
        clientBeat.setIp(ip);
        clientBeat.setPort(port);
        clientBeat.setCluster(clusterName);
    }
    //处理客户端心跳
    service.processClientBeat(clientBeat);

    result.put(CommonParams.CODE, NamingResponseCode.OK);
    if (instance.containsMetadata(PreservedMetadataKeys.HEART_BEAT_INTERVAL)) {
        result.put(SwitchEntry.CLIENT_BEAT_INTERVAL, instance.getInstanceHeartBeatInterval());
    }
    result.put(SwitchEntry.LIGHT_BEAT_ENABLED, switchDomain.isLightBeatEnabled());
    return result;
}

通过beat方法可以看到服务端接收到客户端心跳会有以下操作:

  1. 从客户端请求参数中获取beat参数信息,并转成RsInfo对象
  2. 根据客户端信息,调用ServiceManager#getInstance()从注册中心的服务缓存中查询实例
  3. 如果实例信息不存在,并且实例符合条件的则会对实例进行注册
  4. ServiceManager#serviceMap缓存中获取服务对象service,调用Service#processClientBeat处理客户端心跳信息
  5. 响应客户端请求

接下来主要看服务端如何获取客户端实例的方法ServiceManager#getInstance()和服务端是怎么处理客户端心跳的源码。

先来看下获取客户端实例的方法ServiceManager#getInstance()的源码:

public Instance getInstance(String namespaceId, String serviceName, String cluster, String ip, int port) {
    //从本地缓存获取服务信息
    Service service = getService(namespaceId, serviceName);
    if (service == null) {
        return null;
    }

    List<String> clusters = new ArrayList<>();
    clusters.add(cluster);

    //获取服务所有实例信息
    List<Instance> ips = service.allIPs(clusters);
    if (ips == null || ips.isEmpty()) {
        return null;
    }

    for (Instance instance : ips) {
        //选择客户端对应实例信息
        if (instance.getIp().equals(ip) && instance.getPort() == port) {
            return instance;
        }
    }

    return null;
}

ServiceManager#getInstance() 先从服务端的serviceMap缓存中获取服务,然后获取服务的实例列表,最后遍历实例列表找到ip和端口号相同的实例并返回。

再来看下服务端是怎么处理客户端心跳的Service#processClientBeat源码:

public void processClientBeat(final RsInfo rsInfo) {
    ClientBeatProcessor clientBeatProcessor = new ClientBeatProcessor();
    clientBeatProcessor.setService(this);
    clientBeatProcessor.setRsInfo(rsInfo);
    //处理客户端心跳
    HealthCheckReactor.scheduleNow(clientBeatProcessor);
}

processClientBeat方法会创建一个客户端心跳处理程序类 ClientBeatProcessor对象,然后执行该处理程序。

ClientBeatProcessor 是客户端心跳处理程序类,它的主要任务是处理客户端的心跳,它实现了 Runnable 接口,主要看run()方法的实现即可。

public class ClientBeatProcessor implements Runnable {
    
    public static final long CLIENT_BEAT_TIMEOUT = TimeUnit.SECONDS.toMillis(15);
    
    private RsInfo rsInfo;
    
    private Service service;
    
    @JsonIgnore
    public PushService getPushService() {
        return ApplicationUtils.getBean(PushService.class);
    }
    
    //省略get/set方法
    
    @Override
    public void run() {
        Service service = this.service;
        if (Loggers.EVT_LOG.isDebugEnabled()) {
            Loggers.EVT_LOG.debug("[CLIENT-BEAT] processing beat: {}", rsInfo.toString());
        }
        
        String ip = rsInfo.getIp();
        String clusterName = rsInfo.getCluster();
        int port = rsInfo.getPort();
        Cluster cluster = service.getClusterMap().get(clusterName);
        //获取集群中所有临时节点信息
        List<Instance> instances = cluster.allIPs(true);
        
        for (Instance instance : instances) {
            //ip和端口号相同
            if (instance.getIp().equals(ip) && instance.getPort() == port) {
                if (Loggers.EVT_LOG.isDebugEnabled()) {
                    Loggers.EVT_LOG.debug("[CLIENT-BEAT] refresh beat: {}", rsInfo.toString());
                }
                //设置实例最新的心跳时间
                instance.setLastBeat(System.currentTimeMillis());
                if (!instance.isMarked()) {
                    //如果是不健康的实例
                    if (!instance.isHealthy()) {
                        //设置实例为健康状态
                        instance.setHealthy(true);
                        Loggers.EVT_LOG
                                .info("service: {} {POS} {IP-ENABLED} valid: {}:{}@{}, region: {}, msg: client beat ok",
                                        cluster.getService().getName(), ip, port, cluster.getName(),
                                        UtilsAndCommons.LOCALHOST_SITE);
                        //推送服务推送最新服务实例信息到客户端,以便客户端能够及时感知服务实例发生的变化,并做出相应的调整,从而保证服务的高可用性和稳定性。
                        getPushService().serviceChanged(service);
                    }
                }
            }
        }
    }
}

通过run()方法源码可以看到,服务端在接收到客户端心跳后处理心跳的步骤如下:

  1. 获取客户端所在集群中所有临时节点实例列表
  2. 循环临时节点实例列表找到对应发送心跳的客户端实例,设置该实例最新心跳时间为当前时间
  3. 如果客户端实例为非健康实例,设置该实例属性healthy设置为true,即标记为健康实例。
  4. 如果服务实例健康状态更新,就会调用`PushService#serviceChanged()方法推送服务实例最新信息到客户端以便客户端能够及时感知服务实例发生的变化,并做出相应的调整,从而保证服务的高可用性和稳定性。

总结

最后对Nacos心跳机制做一个简单的总结:

在进行服务实例注册时,会为临时节点生成一个默认5s的BeatTask心跳定时任务,BeatTask定时任务会调用NamingProxy#sendBeat向服务端发起心跳请求。
在服务的端接收到请求后会先从本地缓存中查询实例信息,获取到实例信息之后就会调用Service#processClientBeat对客户端心跳进行处理,处理的逻辑在ClientBeatProcessor#run()方法中。在处理中会从服务集群实例列表中找到对应的客户端,并将当前时间设置为最新的心跳时间。如果服务实例之前是不健康的,则将其属性healthy设置为true,即标记为健康实例,最后会调用`PushService#serviceChanged()方法推送服务实例最新信息到客户端以便客户端能够及时感知服务实例发生的变化,并做出相应的调整,从而保证服务的高可用性和稳定性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Nacos源码系列】Nacos心跳机制原理 的相关文章

  • Nacos介绍与安装启动

    什么是 Nacos 概览 欢迎来到 Nacos 的世界 Nacos 致力于帮助您发现 配置和管理微服务 Nacos 提供了一组简单易用的特性集 帮助您快速实现动态服务发现 服务配置 服务元数据及流量管理 Nacos 帮助您更敏捷和容易地构建
  • 基于Spring Cloud Alibaba 分布式微服务高并发数据平台化(中台)思想+多租户saas企业开发架构技术选型和设计方案

    基于Spring Cloud Alibaba 分布式微服务高并发数据平台化 中台 思想 多租户saas设计的企业开发架构 支持源码二次开发 支持其他业务系统集成 集中式应用权限管理 支持拓展其他任意子项目 架构源码可以加我WX haiwab
  • Nacos介绍以及使用

    目录 一 概述 1 1 Nacos是什么 能干嘛 1 2 去哪下载 1 3 各个注册中心比较 二 Nacos作为服务注册中心 2 1 基于Nacos的服务提供者 2 2 基于Nacos的服务消费者 三 Nacos作为服务配置中心 3 1 N
  • SpringCloud集成Nacos并使用

    摘要 今天分享下 SpringCloud 集成 Nacos 并使用 的一些基本知识 欢迎关注 欢迎阅读 总结系列 SpringCloudAlibaba 实战搭建 项目父模块 pom xml 配置
  • 在Docker上用3个Nacos1.3容器+一个MySQL5和8容器+一个Nginx容器进行集群的具体操作(Nacos集群版)

    在Docker上用Nacos1 3容器连接MySQL5 6和8 0 18容器进入持久化的具体操作 Nacos单机版 这里直接在linux中启动三个Nacos容器 文章目录 3个Nacos1 3容器 一个MySQL5 6容器 一个Nginx容
  • 基于Spring Cloud Alibaba搭建nacos

    关系 Spring Boot是框架 将各个组件集合在一起 方便快速开发web应用 Spring Cloud基于Spring Boot 限定了一组特定的组件 从而可以方便地进行微服务工程的开发 Spring Cloud Alibaba在Spr
  • win安装nacos

    nacos安装注意事项 nacos安装包下载之后 默认是集群模式 需要改成单例执行 路径 nacos bin startup cmd 2 nacos运行需要使用jdk jdk必须使用64位 3 本地新增nacos数据库 执行sql脚本 na
  • 浏览器强制刷新页面

    Chrome官方推荐使用如下快捷键 就可以不使用页面缓存进行刷新 Windows和Linux操作系统 Shift F5 或 Ctrl Shift R Mac OS Cmd Shft R 如果上面方法没有效果 可以按下F12 Windows
  • nacos注册中心面试总结

    1注册中心演变及其设计思想 2 Nacos注册中心架构 3 核心功能 服务注册 Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务 提供自身的元数据 比如ip地址 端口等信息 Nacos Serve
  • nacos--基础--1.3--理论--架构

    nacos 基础 1 3 理论 架构 1 基本架构及概念 1 1 服务 Service 是指一个或一组软件功能 例如特定信息的检索或一组操作的执行 其目的是不同的客户端可以为不同的目的重用 例如通过跨进程的网络调用 Nacos 支持主流的服
  • nacos-一致性协议(1)

    Nacos支持CP AP模式 即Nacos可以根据配置识别为CP模式或AP模式 默认是AP模式 如果注册Nacos的client节点注册时ephemeral true 那么Nacos集群对这个client节点的效果就是AP 采用distro
  • Docker Portainer 安装与报错处理

    安装docker 管理器 Portainer 最近在看spring cloud alibaba的时候 觉得docker是肯定要用的 然后找了个管理的docker的东东 比较方便的查询docker的情况 直接看操作吧 root localho
  • zookeeper到nacos的迁移实践

    本文已收录 https github com lkxiaolou lkxiaolou 欢迎star 技术选型 公司的RPC框架是dubbo 配合使用的服务发现组件一直是zookeeper 长久以来也没什么大问题 至于为什么要考虑换掉zook
  • 【SpringCloud】IDEA如何创建一个SpringCloud项目

    提示 本文包括父工程创建和环境配置 3个子Module为一个简单的订单微服务工程 文章目录 新建一个Maven项目 项目处理 导入依赖 关于SpringBoot SpringCloud SpringCloudAlibaba版本选择 Rest
  • nacos动态更新配置RefreshScope注解后取值为null

    首先排除版本问题 如果在controller类上面直接加 RefreshScope注解 会导致name值为null count值也为null Slf4j RestController RequestMapping api RefreshSc
  • 【微服务】Nacos:发现、配置和管理微服务

    Nacos 发现 配置和管理微服务 Nacos 在阿里巴巴起源于 2008 2008 2008 年五彩石项目 完成微服务拆分和业务中台建设 成长于十年双十一的洪峰考验 沉淀了简单易用 稳定可靠 性能卓越的核心竞争力 随着云计算兴起 2018
  • nacos2.2.1集成达梦数据库

    nacos2 2 1集成达梦数据库 1 下载源码 https github com alibaba nacos 2 新增达梦驱动依赖 父pom xml
  • Nacos启动: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorExceptionTable nacos_config.config_inf

    把nacos 1 1 4 server下载到本地之后 然后直接在bin目录下启动startup cmd报错 本地Mysql版本 5 6 44 nacos server版本 1 1 4 我找了很多解决办法 更多的说的是nacos自带的mysq
  • Nacos安装配置详细流程

    快速导航 1 Nacos安装 1 1概述 1 2 下载 1 3 安装 1 4 启动和关闭 1 5 测试 2 Nacos持久化 2 1为什么持久化 2 2配置Nacos持久化 2 2 1 切换数据库 2 2 2 初始化数据库 2 2 3 测试
  • Eureka迁移到Nacos之服务名称大小问题解决

    我们应用往Eureka中注册使用的名称以及应用内部通过Feign调用 使用的服务名称都是小写 如user service 但是注册到Eureka中后 应用的名称全部都是以大写的形式存储及展现 由于Eureka客户端对大小写的支持都是一样的

随机推荐

  • 如何自学web安全(详细路径)

    直入主题 不要嫌墨迹 看完对你有帮助 web基础知识 web安全 意为web的安全 web即万维网 是由超文本和HTTP构造 就是我们常说的网站那么要学习web安全 必要先要了解web的知识 不然何谈渗透 网站是由程序 空间 域名三大部分组
  • Leetcode437.路径总和 III——利用前缀和的回溯算法

    文章目录 引入 前缀和题解 之前在 5337 每个元音包含偶数次的最长子字符串 状态压缩DP一文中介绍过状态压缩DP 里面有个思想其实也是利用了前缀和 和本文很相似 引入 本题 如下 437 路径总和 III 给定一个二叉树 它的每个结点都
  • logging 日志表的设计

    Netkiller MySQL 手札 MySQL MariaDB Mr Neo Chan 陈景峰 BG7NYT 中国广东省深圳市龙华新区民治街道溪山美地518131 86 13113668890 86 755 29812080
  • 解决PicGo上传图片失败错误信息和上传图片失败包404错误以及Typora怎么一键导入本地图片到PicGo

    前言 解决PicGo上传图片失败错误信息和上传图片失败包404错误以及Typora怎么一键导入本地图片到PicGo 个人主页 尘觉主页 个人简介 大家好 我是尘觉 希望我的文章可以帮助到大家 您的满意是我的动力 在csdn获奖荣誉 csdn
  • Selenium WebDriver基础操作教程

    最近几个月在研究Selenium WebDriver 简单总结一下我的入坑记 一 在Java 环境中的安装 1 选取合适的浏览器 在配置Selenium的WebDriver前首先先选定测试的浏览器 IE Chrome Firefox等主流浏
  • Python代码:批量将BMP图片格式转换为JPG图片格式

    文章与视频资源多平台更新 微信公众号 知乎 B站 头条 AI研习图书馆 深度学习 大数据 IT编程知识与资源分享 欢迎关注 共同进步 1 介绍 Python代码 批量将BMP图片格式转换为JPG图片格式 即BMP转JPG格式 2 Pytho
  • vite 项目初始化(ts)

    为什么要写这篇文章 vite 已经出来这么久了 文档也看了一遍了 都写了啥 该自己动手了 在不动手就废了 再不学就要被淘汰了 不想变成废材 vite 初始化 为什么要写这篇文章 一 安装 vite 二 搭建你的 vite 项目 1 vite
  • Windows下Pentaho Report Designer之运行配置

    01 前言 偶然搜了一下报表工具 看到Pentaho的Report Designer在列 自不必说 折腾一番 试试好用否 然下完之后却怎么也运行不起来 而Java环境是有的 无奈 联想到之前刚要用Data Integration时也碰到过类
  • R语言绘制好看的饼图、空心饼图

    饼图或者说扇形图在统计分析结果展示中非常常用 R语言自带做饼图的函数pie 做起来非常简单 只需提供比例 样品名称即可 构建数据 ratio lt c 24 2 21 9 7 6 5 2 4 3 3 2 2 6 2 6 1 8 1 8 24
  • Spring之面向切面编程(AOP)

    1 面向切面编程 2 AOP核心概念 3 AOP的实现 4 Spring 对AOP支持 4 1 支持 Aspect 4 2 声明一个切面 4 3 声明一个切入点 4 4 声明增强 5 用AOP实现日志拦截 5 1 一般的实现 5 2 仅拦截
  • 跟着英雄刷算法-计数法

    博主是一个刚刚学校C语言和算法的小白 算法这一块完全是空白 偶然了解到了英雄大佬和万人社区 以后博主有时间会分享跟着英雄大佬学习到的东西和平时练习 引例 1 IQ测试 iq表示统计智商的数据列表 然后统计智商大于k的人数 int func
  • 使用selenium+chrome 爬取数据时出现的版本不兼容导致网页打不开或者无法运行

    代码如下 from selenium import webdriver import time chromeOptions webdriver ChromeOptions browser webdriver Chrome options c
  • python skimage图像处理

    基于python脚本语言开发的数字图片处理包 比如PIL Pillow opencv scikit image等 PIL和Pillow只提供最基础的数字图像处理 功能有限 opencv实际上是一个c 库 只是提供了python接口 更新速度
  • 用Delphi开发微信支付和支付宝支付生成签名并提交刷卡支付请求验证源码

    注意 以下来自Delphi盒子用户 wangpingdejiejie linuxping 感谢wangpingdejiejie linuxping 对全球Delphi社区做出的贡献 不管是微信支付还是支付宝支付 3个最棘手的问题是 1 如何
  • Java算法基础----数据结构基本概念

    数据和数据元素概念 数据 是人们利用文字 数字符号以及其他规定的符号对现实世界及其活动所做的抽象描述 表示一个事物的一组数据称为一个数据元素 构成数据元素的数据称为该数据元素的数据项 数据的逻辑结构 数据元素之间的相互联系方式称为数据逻辑结
  • Installation failed with message INSTALL_FAILED_USER_RESTRICTED错误

    星期一在as上运行代码 真机测试app时候就出现了INSTALL FAILED USER RESTRICTED的安装错误 上周就能连接手机运行测试的啊 然后重新建立个项目 还是如此 详细报错为 It is possible that thi
  • 显著区域和非显著区域特征提取Matlab实现

    显著区域和非显著区域特征提取Matlab实现 在图像处理和计算机视觉中 显著区域和非显著区域的提取是一项非常重要的任务 它可以帮助我们更好地理解图像并提供更有效的信息 在本文中 我们将介绍如何使用Matlab实现显著区域和非显著区域的提取
  • SQL Server 数据库增删改查语句

    在使用SQL Server的时候 需要在新建查询中简单的处理数据库 下面介绍一下SQL基础的增删改查语句 介绍下面用到的变量 Table 数据库表名 Column 字段 Content 内容 1 SQL查询语句 查询全表 select fr
  • 【Adobe】Premiere怎么把视频旋转

    文章目录 1 打开Premiere 新建项目 将视频导入媒体 将视频拖入时间轴中 点击轨道上的视频条 2 依次点击 效果 效果控件 运动 旋转 输入需要旋转的角度数 3 点击上方菜单栏中的 序列 序列设置 选项 4 将 帧大小 和 水平 中
  • 【Nacos源码系列】Nacos心跳机制原理

    文章目录 心跳机制是什么 Nacos心跳机制 客户端心跳 服务端接收心跳 总结 前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住分享一下给大家 点击跳转到网站 心跳机制是什么 心跳机制是一种用于监测和管理微服务可用性的机制