SpringCloud微服务续约实现源码分析详解

目录
  • 一、前言
  • 二、客户端续约
    • 1、入口
      • 构造初始化
      • initScheduledTasks()调度执行心跳任务
    • 2、TimedSupervisorTask组件
      • 构造初始化
      • TimedSupervisorTask#run()任务逻辑
    • 3、心跳任务
      • HeartbeatThread私有内部类
      • 发送心跳
    • 4、发送心跳到注册中心
      • 构建请求数据发送心跳
  • 三、服务端处理客户端续约
    • 1、InstanceRegistry#renew()逻辑
    • 2、PeerAwareInstanceRegistryImpl#renew()逻辑
    • 3、AbstractInstanceRegistry#renew()逻辑

一、前言

微服务续约都有通用的设计,就是(微服务)客户端使用心跳机制向注册中心报告自己还活着(可以提供服务),它们的心跳机制略有不同。而Eureka Client客户端会每隔 30 秒发送一次心跳来续约,通过续约来告知 Eureka Server注册中心该 Eureka Client客户端正常运行,没有出现问题。那么心跳机制是什么呢、底层基于什么的?客户端发送心跳的代码在哪里?注册中心怎么处理的?

二、客户端续约

真正触发的还是SpringBoot的自动装配,这里不会过多赘述,下面直奔主题:

1、入口

构造初始化

    private final ScheduledExecutorService scheduler;
    private final ThreadPoolExecutor heartbeatExecutor;
    @Inject
    DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config,
                    AbstractDiscoveryClientOptionalArgs args,
                    Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) {
            ......此处省略n行代码.....
        try {
            // default size of 2 - 1 each for heartbeat and cacheRefresh心跳和缓存刷新的默认大小分别为2-1
            scheduler = Executors.newScheduledThreadPool(2,
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-%d")
                            .setDaemon(true)
                            .build());
            // 心跳执行者
            heartbeatExecutor = new ThreadPoolExecutor(
                    1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(),
                    new ThreadFactoryBuilder()
                            .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d")
                            .setDaemon(true)
                            .build()
            );  // use direct handoff
            ......此处省略n行代码.....
        // 最后,初始化调度任务(例如,集群解析器、 heartbeat、 instanceInfo replicator、 fetch
        initScheduledTasks();
            ......此处省略n行代码.....
}

主要逻辑:

scheduler和heartbeatExecutor都是DiscoveryClient的私有成员变量,并且是final的,故在构造方法中必须初始化。而DiscoveryClient的构造初始化前面也讲了,是在SpringBoot的自动装配过程调用的。构造方法中:

1)scheduler是交给jdk的Executors工具类创建的,核心线程数为2(心跳和缓存刷新需用到)。

2)直接调用ThreadPoolExecutor原生构造方法初始化,核心线程数为1,使用SynchronousQueue队列。

3)最后,初始化调度任务(例如,集群解析器、 心跳、 服务实例复制、 刷新),进入下面逻辑分析

initScheduledTasks()调度执行心跳任务

    private void initScheduledTasks() {
        if (clientConfig.shouldRegisterWithEureka()) {
            /*  LeaseInfo:
                public static final int DEFAULT_LEASE_RENEWAL_INTERVAL = 30;
                // Client settings
                private int renewalIntervalInSecs = DEFAULT_LEASE_RENEWAL_INTERVAL;
             */
            // 默认30
            int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
            int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
            logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);
            // Heartbeat timer心跳任务
            heartbeatTask = new TimedSupervisorTask(
                    "heartbeat",
                    scheduler,
                    heartbeatExecutor,
                    renewalIntervalInSecs,
                    TimeUnit.SECONDS,
                    expBackOffBound,
                    new HeartbeatThread()
            );
            // 默认的情况下会每隔30秒向注册中心 (eureka.instance.lease-renewal-interval-in-seconds)发送一次心跳来进行服务续约
            scheduler.schedule(
                    heartbeatTask,
                    renewalIntervalInSecs, TimeUnit.SECONDS);
            // InstanceInfo replicator实例信息复制任务
            instanceInfoReplicator = new InstanceInfoReplicator(
                    this,
                    instanceInfo,
                    clientConfig.getInstanceInfoReplicationIntervalSeconds(),
                    2); // burstSize
            // 状态变更监听者
            statusChangeListener = new ApplicationInfoManager.StatusChangeListener() {
                @Override
                public String getId() {
                    return "statusChangeListener";
                }
                @Override
                public void notify(StatusChangeEvent statusChangeEvent) {
                    // Saw local status change event StatusChangeEvent [timestamp=1668595102513, current=UP, previous=STARTING]
                    logger.info("Saw local status change event {}", statusChangeEvent);
                    instanceInfoReplicator.onDemandUpdate();
                }
            };
            // 初始化状态变更监听者
            if (clientConfig.shouldOnDemandUpdateStatusChange()) {
                applicationInfoManager.registerStatusChangeListener(statusChangeListener);
            }
            // 定时刷新服务实例信息和检查应用状态的变化,在服务实例信息发生改变的情况下向server重新发起注册
            instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds());
        } else {
            logger.info("Not registering with Eureka server per configuration");
        }
    }

主要逻辑:

  • 根据配置决定是否发送心跳,默认会发送。在LeaseInfo租约信息中维护发送心跳时间,默认间隔为30秒,可以在yml配置文件(eureka.instance.lease-renewal-interval-in-seconds)中更改默认值。
  • 初始化heartbeatTask,真正的心跳任务为HeartbeatThread类型(下面3分析)。
  • 调度执行心跳任务,默认的情况下会每隔30秒向注册中心 (eureka.instance.lease-renewal-interval-in-seconds)发送一次心跳来进行服务续约
  • 本方法下面的逻辑上一节已经分析

2、TimedSupervisorTask组件

可见TimedSupervisorTask是Runnable类型的任务,那么它的任务逻辑在run()方法。

构造初始化

    public TimedSupervisorTask(String name, ScheduledExecutorService scheduler, ThreadPoolExecutor executor,
                               int timeout, TimeUnit timeUnit, int expBackOffBound, Runnable task) {
        this.name = name;
        this.scheduler = scheduler;
        // heartbeatExecutor或cacheRefreshExecutor
        this.executor = executor;
        this.timeoutMillis = timeUnit.toMillis(timeout);
        // HeartbeatThread或CacheRefreshThread等类型任务
        this.task = task;
        this.delay = new AtomicLong(timeoutMillis);
        this.maxDelay = timeoutMillis * expBackOffBound;
        // Initialize the counters and register.
        successCounter = Monitors.newCounter("success");
        timeoutCounter = Monitors.newCounter("timeouts");
        rejectedCounter = Monitors.newCounter("rejectedExecutions");
        throwableCounter = Monitors.newCounter("throwables");
        threadPoolLevelGauge = new LongGauge(MonitorConfig.builder("threadPoolUsed").build());
        Monitors.registerObject(name, this);
    }

初始化自己的一些字段,在阿里Java编程规范中是强烈建议给线程起别名的,这样便于监控排查问题等。name线程名字,在心跳任务中为heartbeat;scheduler字段传递进来是为了周期性执行任务;executor用于提交任务,下面分析;task任务,为HeartbeatThread或CacheRefreshThread类型任务;delay,用于存储以及计算延迟时间;最大延迟时间不能超过maxDelay。

TimedSupervisorTask#run()任务逻辑

    @Override
    public void run() {
        // Future模式
        Future<?> future = null;
        try {
            // 提交任务待执行
            future = executor.submit(task);
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            // 阻塞直到完成或超时
            future.get(timeoutMillis, TimeUnit.MILLISECONDS);
            // 更新以便计算延迟时间
            delay.set(timeoutMillis);
            // 更新
            threadPoolLevelGauge.set((long) executor.getActiveCount());
            successCounter.increment();
        } catch (TimeoutException e) {
            // 任务主管超时了
            logger.warn("task supervisor timed out", e);
            timeoutCounter.increment();
            long currentDelay = delay.get();
            // 任务线程超时的时候,就把delay变量翻倍,但不会超过外部调用时设定的最大延时时间
            long newDelay = Math.min(maxDelay, currentDelay * 2);
            // CAS更新延迟时间,考虑到多线程,所以用了CAS
            delay.compareAndSet(currentDelay, newDelay);
        } catch (RejectedExecutionException e) {
            // 一旦线程池的阻塞队列中放满了待处理任务,触发了拒绝策略
            if (executor.isShutdown() || scheduler.isShutdown()) {
                // 线程池关闭,拒绝任务
                logger.warn("task supervisor shutting down, reject the task", e);
            } else {
                // 线程池拒绝任务
                logger.warn("task supervisor rejected the task", e);
            }
            rejectedCounter.increment();
        } catch (Throwable e) {
            if (executor.isShutdown() || scheduler.isShutdown()) {
                // 任务主管关闭,不能接受任务
                logger.warn("task supervisor shutting down, can't accept the task");
            } else {
                // 任务主管抛出了一个异常
                logger.warn("task supervisor threw an exception", e);
            }
            throwableCounter.increment();
        } finally {
            // 上面的异常catch了没有外跑,下面继续运行
            if (future != null) {
                // 中断
                future.cancel(true);
            }
            // 调度器没有关闭,延迟继续周期性执行任务。这样的周期性任务时间设置灵活
            if (!scheduler.isShutdown()) {
                scheduler.schedule(this, delay.get(), TimeUnit.MILLISECONDS);
            }
        }
    }

主要逻辑:

  • 提交任务待执行
  • 阻塞直到完成或超时
  • 更新以便计算延迟时间

异常处理:

  • 超时异常,任务线程超时的时候,就把delay变量翻倍,但不会超过外部调用时设定的最大延时时间。CAS更新延迟时间,考虑到多线程,所以用了CAS。
  • 拒绝执行异常,一旦线程池的阻塞队列中放满了待处理任务,触发了拒绝策略。
  • 其他异常

上面的异常catch了没有外跑,下面继续运行:

1)丢弃当前任务;

2)调度器没有关闭,延迟继续周期性执行任务。这样的周期性任务时间设置灵活

3、心跳任务

HeartbeatThread私有内部类

    private class HeartbeatThread implements Runnable {
        public void run() {
            if (renew()) {
                lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis();
            }
        }
    }

HeartbeatThread实现了Runnable接口,也就是上面说的task,它的逻辑封装了出去:

发送心跳

    boolean renew() {
        EurekaHttpResponse<InstanceInfo> httpResponse;
        try {
            // 发送心跳
            httpResponse = eurekaTransport.registrationClient
                    .sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null);
            // 打印日志,如:心跳状态
            logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode());
            // 响应状态没有找到重新注册
            if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) {
                REREGISTER_COUNTER.increment();
                logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName());
                long timestamp = instanceInfo.setIsDirtyWithTime();
                boolean success = register();
                if (success) {
                    instanceInfo.unsetIsDirty(timestamp);
                }
                return success;
            }
            return httpResponse.getStatusCode() == Status.OK.getStatusCode();
        } catch (Throwable e) {
            // 无法发送心跳!
            logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e);
            return false;
        }
    }

主要逻辑:

  • EurekaTransport是DiscoevryClient的内部类,封装了几个与注册中心通信的XXXclient。获取通信类发送心跳请求,传入appName、唯一ID以及instanceInfo。
  • 响应状态没有找到重新注册,毕竟当前客户端是运行中正常状态
  • 返回是否发送心跳成功

4、发送心跳到注册中心

构建请求数据发送心跳

    @Override
    public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
        // 拼接请求URL
        String urlPath = "apps/" + appName + '/' + id;
        ClientResponse response = null;
        try {
            // 构建请求资源
            WebResource webResource = jerseyClient.resource(serviceUrl)
                    .path(urlPath)
                    .queryParam("status", info.getStatus().toString())
                    .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
            if (overriddenStatus != null) {
                webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
            }
            Builder requestBuilder = webResource.getRequestBuilder();
            addExtraHeaders(requestBuilder);
            // 请求注册中心
            response = requestBuilder.put(ClientResponse.class);
            EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
            if (response.hasEntity() &&
                    !HTML.equals(response.getType().getSubtype())) { //don't try and deserialize random html errors from the server
                eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
            }
            // 返回构建结果
            return eurekaResponseBuilder.build();
        } finally {
            if (logger.isDebugEnabled()) {
                logger.debug("Jersey HTTP PUT {}{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
            }
            if (response != null) {
                // 关闭资源
                response.close();
            }
        }
    }

主要逻辑:

  • 拼接请求URL
  • 构建请求资源数据
  • 请求注册中心
  • 返回构建结果
  • 关闭资源

三、服务端处理客户端续约

InstanceRegistry父子关系图上一节也分析了,在使用super关键字时注意一下。

1、InstanceRegistry#renew()逻辑

	@Override
	public boolean renew(final String appName, final String serverId,
			boolean isReplication) {
		log("renew " + appName + " serverId " + serverId + ", isReplication {}"
				+ isReplication);
		List<Application> applications = getSortedApplications();
		for (Application input : applications) {
			if (input.getName().equals(appName)) {
				InstanceInfo instance = null;
				for (InstanceInfo info : input.getInstances()) {
					if (info.getId().equals(serverId)) {
						instance = info;
						break;
					}
				}
				// 发布服务实例处理心跳事件
				publishEvent(new EurekaInstanceRenewedEvent(this, appName, serverId,
						instance, isReplication));
				break;
			}
		}
		// 调用父类的处理心跳方法
		return super.renew(appName, serverId, isReplication);
	}

这里逻辑主要是委托父类处理心跳,具体逻辑见下面分析:

2、PeerAwareInstanceRegistryImpl#renew()逻辑

    public boolean renew(final String appName, final String id, final boolean isReplication) {
        if (super.renew(appName, id, isReplication)) {
            // 服务续约成功,将所有的Eureka操作复制到对等的Eureka节点,除了复制到此节点的流量。
            replicateToPeers(Action.Heartbeat, appName, id, null, null, isReplication);
            return true;
        }
        return false;
    }

PeerAwareInstanceRegistryImpl职责:处理将所有操作复制到 AbstractInstanceRegistry的peer Eureka节点,以保持所有操作同步。这里如果服务续约成功,将所有的Eureka操作复制到对等的Eureka节点,除了复制到此节点的流量。

3、AbstractInstanceRegistry#renew()逻辑

    public boolean renew(String appName, String id, boolean isReplication) {
        RENEW.increment(isReplication);
        // 根据appName从本地注册表服务实例信息
        Map<String, Lease<InstanceInfo>> gMap = registry.get(appName);
        Lease<InstanceInfo> leaseToRenew = null;
        if (gMap != null) {
            leaseToRenew = gMap.get(id);
        }
        if (leaseToRenew == null) {
            // 没有找到租约
            RENEW_NOT_FOUND.increment(isReplication);
            // 注册: 租约不存在,注册资源:
            logger.warn("DS: Registry: lease doesn't exist, registering resource: {} - {}", appName, id);
            return false;
        } else {
            // 获取服务实例信息
            InstanceInfo instanceInfo = leaseToRenew.getHolder();
            if (instanceInfo != null) {
                // touchASGCache(instanceInfo.getASGName());
                InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(
                        instanceInfo, leaseToRenew, isReplication);
                if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) {
                    logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}"
                            + "; re-register required", instanceInfo.getId());
                    RENEW_NOT_FOUND.increment(isReplication);
                    return false;
                }
                if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) {
                    // 实例状态{}与实例{}的重写实例状态{}不同。因此将状态设置为覆盖状态
                    logger.info(
                            "The instance status {} is different from overridden instance status {} for instance {}. "
                                    + "Hence setting the status to overridden status", instanceInfo.getStatus().name(),
                                    overriddenInstanceStatus.name(),
                                    instanceInfo.getId());
                    instanceInfo.setStatusWithoutDirty(overriddenInstanceStatus);
                }
            }
            renewsLastMin.increment();
            // 续约
            leaseToRenew.renew();
            return true;
        }
    }

主要逻辑:

  • 根据appName从本地注册表服务实例信息
  • 没有找到租约,返回false
  • 获取到的服务实例状态为UNKNOWN,返回false;续约,更新续约字段,下面分析

Lease#renew()逻辑

    public void renew() {
        lastUpdateTimestamp = System.currentTimeMillis() + duration;

    }

lastUpdateTimestamp是Lease租约的字段,维护租约时间,在服务剔除下线会根据该字段判断是否过期需要对服务剔除下线处理。下一篇我们就来探讨一下,敬请期待!!!

到此这篇关于SpringCloud微服务续约实现源码分析详解的文章就介绍到这了,更多相关SpringCloud微服务续约内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringCloud微服务熔断器使用详解

    目录 一.简介 二.作用 三.核心概念 3.1 熔断目的 3.2 降级目的 四.实例 4.1 基于Hystrix 4.1.1 熔断触发降级 4.1.2 超时触发降级 4.1.3 资源隔离触发降级 4.2 基于OpenFeign pom.xml 一.简介 当微服务中的某个子服务,发生异常服务器宕机,其他服务在进行时不能正常访问而一直占用资源导致正常的服务也发生资源不能释放而崩溃,这时为了不造成整个微服务群瘫痪,进行的保护机制 就叫做熔断,是一种降级策略 熔断的目的:保护微服务集群 二.作用 对第三

  • SpringCloud超详细讲解微服务网关Gateway

    目录 前言 微服务网关GateWay介绍 GateWay特性介绍 Gateway 中的相关术语 Gateway实战 1.创建项目gateway 2.创建启动类 3.新增配置文件 4.编程方式实现路由 5.启动验证 总结 前言 上一篇:微服务网关Zuul 上文中,我们介绍了微服务网关Zuul,Zuul 是 Netflix 公司开源的产品,被称为第一代网关,也是 Spring Cloud 前几个版本默认使用的一款提供动态路由微服务网关组件,但是随着 Netflix 公司一系列的停更事件,在最新的 S

  • SpringCloud微服务熔断器Hystrix使用详解

    目录 什么是Hystrix Hystrix实战 总结 什么是Hystrix 在日常生活用电中,如果我们的电路中正确地安置了保险丝,那么在电压异常升高时,保险丝就会熔断以便切断电流,从而起到保护电路安全运行的作用. 在货船中,为了防止漏水和火灾的扩散,一般会将货仓进行分割,避免了一个货仓出事导致整艘船沉没的悲剧,这就是舱壁保护机制. Hystrix提供的熔断器也类似,在调用某个服务提供者时,当一定时间内请求总数超过配置的阈值,且窗口期内错误率过高,那Hystrix就会对调用请求熔断,后续的请求直接

  • SpringCloud超详细讲解微服务网关Zuul基础

    目录 一.Zuul的简介 1.Zuul是怎么工作的 2.Zuul能干嘛 二.Zuul的使用 1.配置Pom.xml 2.配置Application.yml 3.撰写启动类 4.效果图 三.学会SpringCloud的感触 一.Zuul的简介 1.Zuul是怎么工作的 Zull包含了对请求的路由(用来跳转的)和过滤两个最主要功能: 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础.Zu

  • SpringCloud 分布式微服务架构操作步骤

    目录 前言 SpringCloud微服务 单体架构和微服务分布式架构 单体架构分析 微服务分布式架构分析 服务拆分和远程调用 服务拆分 案例需求准备 远程调用初步 Eureka注册中心 服务注册与负载均衡 服务注册 Ribbon负载均衡 指定负载均衡规则 Nocas 注册中心 环境配置启动服务注册 Nacos 分级存储模型与集群 负载均衡 namespace 环境隔离 统一配置管理与热更新 前言 这篇笔记文章我还是没有接上之前的java,因为我中间偷懒了,写不动了.打算先把这篇安排下,然后再把之

  • SpringCloud超详细讲解微服务网关Zuul

    目录 网关的作用 Spring Cloud 网关组件Zuul介绍 Zuul网关实战 1.创建服务 2.创建配置文件 3.创建Zuul过滤器 4.编写启动类 5.启动验证 总结 网关的作用 微服务架构中,服务实例的地址可能经常会发生变化,所以我们不能直接将服务的地址暴露出来.如果每一个微服务都直接暴露接口,会导致一系列的问题,比如调用过于复杂,涉及到账户.权限不能统一处理等.另外基于高内聚低耦合的设计准则来讲,我们也应该将内部系统和外部系统做切割. 因此,这时就需要有一个独立的组件来处理外部的请求

  • SpringCloud微服务开发基于RocketMQ实现分布式事务管理详解

    目录 消息队列实现分布式事务原理 RocketMQ的事务消息 代码实现 基础配置 发送半消息 执行本地事务与回查 Account-Service消费消息 测试 小结 消息队列实现分布式事务原理 首先让我们来看一下基于消息队列实现分布式事务的原理方案. 柔性事务 发送消息的服务有个OUTBOX数据表,在进行INSERT.UPDATE.DELETE 业务操作时也会给OUTBOX数据表INSERT一条消息记录,这样可以保证原子性,因为这是基于本地的ACID事务. OUTBOX表充当临时消息队列,然后我

  • SpringCloud微服务剔除下线功能实现原理分析

    目录 一.前言 二.微服务剔除下线源码解析 1.EurekaBootStrap#contextInitialized() 1.1.初始化注册中心上下文 1.2.openForTraffic()逻辑 1.3.postInit()执行任务 1.4.剔除任务 2.服务剔除下线 2.1.AbstractInstanceRegistry#evict()逻辑 2.1.判断是否过期 2.2.从本地列表异常下线处理 一.前言 上一篇SpringCloud微服务续约源码解析已经分析了心跳机制是什么.底层实现.客户

  • SpringCloud微服务续约实现源码分析详解

    目录 一.前言 二.客户端续约 1.入口 构造初始化 initScheduledTasks()调度执行心跳任务 2.TimedSupervisorTask组件 构造初始化 TimedSupervisorTask#run()任务逻辑 3.心跳任务 HeartbeatThread私有内部类 发送心跳 4.发送心跳到注册中心 构建请求数据发送心跳 三.服务端处理客户端续约 1.InstanceRegistry#renew()逻辑 2.PeerAwareInstanceRegistryImpl#rene

  • Python日志打印里logging.getLogger源码分析详解

    实践环境 WIN 10 Python 3.6.5 函数说明 logging.getLogger(name=None) getLogger函数位于logging/__init__.py脚本 源码分析 _loggerClass = Logger # ...略 root = RootLogger(WARNING) Logger.root = root Logger.manager = Manager(Logger.root) # ...略 def getLogger(name=None): "&quo

  • React commit源码分析详解

    目录 总览 commitBeforeMutationEffects commitMutationEffects 插入 dom 节点 获取父节点及插入位置 判断当前节点是否为单节点 在对应位置插入节点 更新 dom 节点 更新 HostComponent 更新 HostText 删除 dom 节点 unmountHostComponents commitNestedUnmounts commitUnmount commitLayoutEffects 执行生命周期 处理回调 总结 总览 commit

  • python django事务transaction源码分析详解

    python Django事务 网上关于django1.6的事务资料很多,但是1.8的却搜不到任何资料,自己要用的时候费了不少劲就是不行,现在记下要用的人少走弯路 version:Django 1.8 事务官方文档 事务中文文档里面介绍很多方法,不一一赘述,按照文档即可,下面只分析下atomic方法的源码 按照官方文档 transaction.atomic 有两种用法装饰器和上下文管理器 # atomic() 方法 # from django.db import transaction ####

  • Netty启动流程服务端channel初始化源码分析

    目录 服务端channel初始化 回顾上一小节initAndRegister()方法 init(Channel)方法 前文传送门 Netty分布式server启动流程 服务端channel初始化 回顾上一小节initAndRegister()方法 final ChannelFuture initAndRegister() { Channel channel = null; try { //创建channel channel = channelFactory.newChannel(); //初始化

  • Golang HTTP编程的源码解析详解

    目录 1.网络基础 2.Golang HTTP编程 2.1 代码示例 2.2 源码分析 3. 总结 1.网络基础 基本TCP客户-服务器程序Socket编程流程如如下图所示. TCP服务器绑定到特定端口并阻塞监听客户端端连接, TCP客户端则通过IP+端口向服务器发起请求,客户-服务器建立连接之后就能开始进行数据传输. Golang的TCP编程也是基于上述流程的. 2.Golang HTTP编程 2.1 代码示例 func timeHandler(w http.ResponseWriter, r

  • java TreeMap源码解析详解

    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树.相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高. 如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的value值都是小于根结点的value值的,所有右孩子的value值都是大于根结点的.这样做的好处在于:如果需要按照键值查找数据元素,只要比较当前结点的value值即可(小于当前结点value值的,往左走,否则往右走),这种方式,每次可以减少一半的操作,所以效率比较高

  • IOS身份证识别(OCR源码)详解及实例代码

    IOS身份证识别(OCR源码)详解 最近项目用到身份证识别,在github上搜了一堆demo,在Google上找了一堆代码,有能识别出证件照的,但是都是打包成.a的静态库,没有源码,我努力吃了几天书,有了一点研究成果,现在贴出来与大家分享,要是有更好的方法,希望大神指正,共同探讨解决方案.(以下代码本人亲测可用,正在进一步探索智能识别,如有兴趣,请加入) 这里用到了两个开源库:OpenCV.TesseractOCRiOS,两个语言包chi_sim.eng.身份证识别的流程主要有:灰度化,阀值二值

  • Vue之vue.$set()方法源码案例详解

    在使用vue开发项目的过程中,经常会遇到这样的问题:当vue的data里边声明或者已经赋值过的对象或者数组(数组里边的值是对象)时,向对象中添加新的属性,如果更新此属性的值,是不会更新视图的. 这是因为新加入的属性不是响应式的,因此不会触发视图的更新,通常使用静态方法Vue.set()或者实例方法this.$set()解决 ,使用方式: 对象:this.$set(target,key,  value) 数组:this.$set(target,index,  value) 但不管是静态方法Vue.

  • Java Spring @Lazy延迟注入源码案例详解

    前言 有时候我们会在属性注入的时候添加@Lazy注解实现延迟注入,今天咱们通过阅读源码来分析下原因 一.一个简单的小例子 代码如下: @Service public class NormalService1 { @Autowired @Lazy private MyService myService; public void doSomething() { myService.getName(); } } 作用是为了进行延迟加载,在NormalService1进行属性注入的时候,如果MyServ

随机推荐