当前位置: 首页 > news >正文

Nacos服务注册总流程(源码分析)

文章目录

    • 服务注册
      • NacosClient找看源码入口
      • NacosClient服务注册源码
      • NacosServer处理服务注册

服务注册

服务注册 在线流程图

在这里插入图片描述



NacosClient找看源码入口

我们启动一个微服务,引入nacos客户端的依赖

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

并配置文件中指定nacosServer的地址,并运行该微服务,那么服务是如何注册进nacosServer中去的嘞?

server:port: 9002
spring:application:name: stock-servicecloud:nacos:discovery:# 指定nacos server的地址server-addr: localhost:8848# 指定集群名称cluster-name: SZmetadata:version: v3

我们应该怎么找源码的入口嘞?

我们知道SpringCloud是基于SpringBoot的,我们这里引入了这个nacos客户端nacos-discovery的依赖,我们就直接去找这个jar包下的spring.factories文件

在这里插入图片描述

这个文件中记录了很多的自动配置类,如果不知道应该看哪一个的话,那么就优先看和我们导入maven依赖名字相近的类

在这里插入图片描述


进入到这个NacosDiscoveryAutoConfiguration自动配置类后会发现这里会往容器中注入三个bean,看bean的名字好像都是和nacos注册相关的,按照经验来说,带有AutoXXXX这类方法一般都比较重要,再加上第三个方法还用到了上面两个bean,所以核心方法很大概率就是这个第三个方法。

在这里插入图片描述


进入到该类的构造方法之后,我们一般可以查看这个类的继承与实现结构,这样可以让我们更充分的了解该类

在这里插入图片描述


从上图中可以发现,NacosAutoServiceRegistration这个类还实现了ApplicationListener接口,这个接口我们都知道是Spring发布事件相关的接口

// ApplicationListener接口
package org.springframework.context;import java.util.EventListener;@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E var1);
}

我们再找这个方法的具体实现,会发现NacosAutoServiceRegistration这个类它没有重写该方法,那么就要在它的父类中找该方法的实现

public void onApplicationEvent(WebServerInitializedEvent event) {this.bind(event);
}// 方法调用,进入到bind(...)方法public void bind(WebServerInitializedEvent event) {ApplicationContext context = event.getApplicationContext();// 有if、并且有返回、并且后面还有业务代码的一般情况下这都是一个分支代码,可以先跳过if (context instanceof ConfigurableWebServerApplicationContext) {if ("management".equals(((ConfigurableWebServerApplicationContext) context).getServerNamespace())) {return;}}// 一般情况下,start()  init()     begin()这种方法名命名的方法一般都是比较重要的代码this.port.compareAndSet(0, event.getWebServer().getPort());this.start();
}

根据看源码的经验来说:

  • 有if,并且有返回值的,并且后面还有业务代码的一般情况下这都是一个分支代码,可以先跳过
  • 一般情况下,start() init() begin()这中方法名命名的方法一般都是比较重要的代码

接下来再进入到start()方法中

public void start() {// if + return + 后面有业务逻辑,大概率是一个分支代码 可以先跳过。这里实际上也只是记录了一个日志if (!isEnabled()) {if (logger.isDebugEnabled()) {logger.debug("Discovery Lifecycle disabled. Not starting");}return;}// only initialize if nonSecurePort is greater than 0 and it isn't already running// because of containerPortInitializer belowif (!this.running.get()) {this.context.publishEvent(new InstancePreRegisteredEvent(this, getRegistration()));// 核心方法,可以发现在调用这个方法之前和调用方法之后都发布了一个事件// 正好我们要找的也就是注册相关的方法// 因为我们就是从NacosDiscoveryAutoConfiguration自动配置类的NacosAutoServiceRegistration这个和注册相关的bean进来的register();if (shouldRegisterManagement()) {registerManagement();}this.context.publishEvent(new InstanceRegisteredEvent<>(this, getConfiguration()));this.running.compareAndSet(false, true);}}

接下来在点进register()方法,跳转几次之后就会进入到NamingService这个关键接口的registerInstance(...)方法中去




NacosClient服务注册源码

关键接口NamingService,它的registerInstance(...)方法就是服务注册。程序刚开始会进入到该接口的实现类NacosNamingServiceregisterInstance(String serviceName, String groupName, Instance instance)方法中

// 该方法 往NacosServer中注册一个微服务实例
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {NamingUtils.checkInstanceIsLegal(instance);String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);if (instance.isEphemeral()) {BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);beatReactor.addBeatInfo(groupedServiceName, beatInfo);}// 调用该方法进行微服务注册,instance对象中保存着微服务的各种信息,比如ip、端口、访问权重、健康状态、是否上线等等serverProxy.registerService(groupedServiceName, groupName, instance);
}

instance对象保存的内容如下图所示

在这里插入图片描述



public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,instance);// 把instance对象中的数据取出来,封装成一个HashMapfinal Map<String, String> params = new HashMap<String, String>(16);params.put(CommonParams.NAMESPACE_ID, namespaceId);params.put(CommonParams.SERVICE_NAME, serviceName);params.put(CommonParams.GROUP_NAME, groupName);params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());params.put("ip", instance.getIp());params.put("port", String.valueOf(instance.getPort()));params.put("weight", String.valueOf(instance.getWeight()));params.put("enable", String.valueOf(instance.isEnabled()));params.put("healthy", String.valueOf(instance.isHealthy()));params.put("ephemeral", String.valueOf(instance.isEphemeral()));params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));// 发送一个post的http请求发送给NacosServer,进行服务注册reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);}

在这里插入图片描述


再通过官方文档 / 接口详细文档就能发现这就是服务注册的接口

在这里插入图片描述



NacosServer处理服务注册

NacosClient向NacosServer发送了一个服务注册的post请求,url为/v1/ns/instance

NacosServer这个项目有很多很多的子工程,就拿下图所示,我应该如何去找嘞?

在这里插入图片描述


其实我们就可以双击shift键,直接搜索controller,一个一个文件夹找,或者是直接加上关键字,就比如我这里加上了instance这个关键字就找到了真正处理这个请求的controller,类名为InstanceController

在这里插入图片描述


还有一种方法就是,我们在NacosClient这边进行服务注册时会频繁的使用到NamingService,那我就在NacosServer端找有没有和Naming相关的子工程,最终发现还真就找到了

在这里插入图片描述



接下来是源码,首先直接查看该controller的post接口

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, 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);// 通过解析request请求参数,封装成instance对象final Instance instance = parseInstance(request);// 调用Service层方法进行服务实例注册serviceManager.registerInstance(namespaceId, serviceName, instance);return "ok";
}

接下来进入到service层的registerInstance(...)方法逻辑

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {// 这里会去创建一个service,如果连该service对应的命名空间都不存在的话就会利用DCL双重锁检测机制去创建一个map<serviceName,service>// 再去添加service,添加命名空间对应的map<serviceName,service>中。命名空间对应的这个map是存在注册表serviceMap中的// 这里创建的只是一个空服务,它里面还没有instance服务实例// 在创建service过程中还会进行初始化,会添加一个延迟定时任务进行该服务下的所有实例的健康检查、修改服务健康状态、删除过时服务createEmptyService(namespaceId, serviceName, instance.isEphemeral());// 这里再获取,就不应该为null了,如果为null也就该抛异常了// getService()会先根据命名空间取出一个Map,再从这个Map中根据serviceName取出service。service中包含instance微服务实例Service service = getService(namespaceId, serviceName);if (service == null) {throw new NacosException(NacosException.INVALID_PARAM,"service not found, namespace: " + namespaceId + ", service: " + serviceName);}// 进行服务实例instance添加操作addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}



createEmptyService(...)方法中,首先会去判断当前service是否存在,如果不存在则创建一个空的新的service,并且把这个service添加进namespaceId对应的Map中,如果在这个过程中,namespaceId命名空间不存在则使用DCL双重锁检测去创建一个map集合添加进注册表serviceMap中。接下来在对刚刚创建的service进行init()初始化操作,蛀牙就是开启一个延时的定时任务,定期对该服务中的实例进行健康检查,并修改不健康的状态或移除超时实例

public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {createServiceIfAbsent(namespaceId, serviceName, local, null);
}public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)throws NacosException {// 先从服务注册表serviceMap中获取Service service = getService(namespaceId, serviceName);// 如果不存在就创建if (service == null) {Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);service = 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();// 这里采用了DCL双重锁检测机制进行的添加操作putServiceAndInit(service);if (!local) {addOrReplaceService(service);}}
}private void putServiceAndInit(Service service) throws NacosException {// 将service添加进对应的NamespaceId中putService(service);// 服务进行初始化,主要就是添加延时定时任务,对实例进行健康检查service.init();consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
}public void putService(Service service) {// DCL双重检测机制if (!serviceMap.containsKey(service.getNamespaceId())) {synchronized (putServiceLock) {if (!serviceMap.containsKey(service.getNamespaceId())) {serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());}}}serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
}public void init() {// NacosClient客户端的心跳检测任务// 这里就会开启一个线程去执行任务,clientBeatCheckTask属性就是一个task,run()方法中会进行实例的健康检查,后面会详细介绍HealthCheckReactor.scheduleCheck(clientBeatCheckTask);for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {entry.getValue().setService(this);entry.getValue().init();}
}



接下来就进入到了addInstance(...)添加实例的方法中了,这个方法中主要做的事就是将当前service的所有实例通过一个key存入一个Map中,然后保存在一个dataStore这个Map中,同时再把这个key和相关的操作类型存入一个阻塞队列中,至此NacosClient发送的请求就处理完成了。之后会有单独一个线程异步处理进行真正的服务注册。

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)throws NacosException {// 根据ephemeral的值来决定生成上面key,默认情况下NacosClient传递过来的都是true,一般微服务的实例都是临时实例,不是持久化实例// 如果是持久化实例就没有下面的ephemeral这个字符串拼接// key = 一些字符串常量 + “ephemeral” + namespaceId + “##” + serviceNameString key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);Service service = getService(namespaceId, serviceName);synchronized (service) {// 添加微服务的实例,并返回当前服务所有的实例List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);Instances instances = new Instances();instances.setInstanceList(instanceList);// key主要就是命名空间+服务名,通过这个key添加进consistencyService中consistencyService.put(key, instances);}
}// 上面的Instances实现了Record接口,这里其实也就是一个封装
public void put(String key, Record value) throws NacosException {//  mapConsistencyService(key)这里会根据是否是临时实例进而去调用不同实现类的put()方法,这里以临时实例举例mapConsistencyService(key).put(key, value);
}public void put(String key, Record value) throws NacosException {// 将key和所有服务实例封装的Record对象封装成一个datum对象,并保存到一个map集合中。// 同时还有一个key和DataOperation枚举操作类型添加进阻塞队列的操作。// 后续肯定有一个线程从这个注释队列中取出数据,然后根据key把datum对象取出来onPut(key, value);distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,globalConfig.getTaskDispatchPeriod() / 2);
}public void onPut(String key, Record value) {if (KeyBuilder.matchEphemeralInstanceListKey(key)) {Datum<Instances> datum = new Datum<>();datum.value = (Instances) value;datum.key = key;datum.timestamp.incrementAndGet();// 这里把key和datum存入了一个dataMap这个集合中// 这个datum对象中保存着我当前服务所有的服务实例instancesdataStore.put(key, datum);}if (!listeners.containsKey(key)) {return;}// 将key和后面这个枚举参数封装成一个Pair对象,并添加进一个阻塞队列中notifier.addTask(key, DataOperation.CHANGE);
}public void addTask(String datumKey, DataOperation action) {if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {return;}if (action == DataOperation.CHANGE) {services.put(datumKey, StringUtils.EMPTY);}// 把key和dataOperation操作枚举添加进一个阻塞队列中,真正进行服务注册的是在下面的run()方法中tasks.offer(Pair.with(datumKey, action));
}// 另一个线程异步进行处理,从阻塞队列中获取数据,真正进行服务注册操作
@Override
public void run() {Loggers.DISTRO.info("distro notifier started");// 该线程会一直死循环,从阻塞队列中取数据for (; ; ) {try {Pair<String, DataOperation> pair = tasks.take();handle(pair);} catch (Throwable e) {Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);}}
}



接下来就是详细查看这个handle(pair)方法,查看另一个线程是如何进行服务注册的?

这里会从先判断操作类型,然后从handle()方法调用到onChange(...)方法;而在onChange(...)方法中首先会对服务实例的权重进行处理,再调用到updateIPs()方法;在updateIPs()方法中会对service中的cluster做一些处理,最终就会调用到updateIps(...)方法中,在这个方法中会通过CopyOnWrite思想真正更改保存instance实例的集合

// handle()方法调用到onChange(...)方法
private void handle(Pair<String, DataOperation> pair) {try {String datumKey = pair.getValue0();DataOperation action = pair.getValue1();services.remove(datumKey);int count = 0;if (!listeners.containsKey(datumKey)) {return;}for (RecordListener listener : listeners.get(datumKey)) {count++;try {if (action == DataOperation.CHANGE) {// dataStore.get(datumKey)取出来的对象是Datum类型的对象,它其中保存在key+所有服务实例instances// dataStore.get(datumKey).value 就是这个datumKey对应的所有服务实例instanceslistener.onChange(datumKey, dataStore.get(datumKey).value);continue;}if (action == DataOperation.DELETE) {listener.onDelete(datumKey);continue;}} catch (Throwable e) {...}}...} catch (Throwable e) {...}
}// 对服务实例的权重进行处理,再调用到updateIPs()方法,
public void onChange(String key, Instances value) throws Exception {Loggers.SRV_LOG.info("[NACOS-RAFT] datum is changed, key: {}, value: {}", key, value);// 对instance服务实例的特殊权重做处理for (Instance instance : value.getInstanceList()) {if (instance == null) {// Reject this abnormal instance list:throw new RuntimeException("got null instance " + key);}if (instance.getWeight() > 10000.0D) {instance.setWeight(10000.0D);}if (instance.getWeight() < 0.01D && instance.getWeight() > 0.0D) {instance.setWeight(0.01D);}}updateIPs(value.getInstanceList(), KeyBuilder.matchEphemeralInstanceListKey(key));recalculateChecksum();
}// 会对service中的cluster做一些处理,最终就会调用到updateIps(...)方法中
public void updateIPs(Collection<Instance> instances, boolean ephemeral) {Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size());// 遍历所有集群for (String clusterName : clusterMap.keySet()) {ipMap.put(clusterName, new ArrayList<>());}for (Instance instance : instances) {try {if (instance == null) {Loggers.SRV_LOG.error("[NACOS-DOM] received malformed ip: null");continue;}// 实例instance的默认集群名字if (StringUtils.isEmpty(instance.getClusterName())) {instance.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME);}if (!clusterMap.containsKey(instance.getClusterName())) {Loggers.SRV_LOG.warn("cluster: {} not found, ip: {}, will create new cluster with default configuration.",instance.getClusterName(), instance.toJson());Cluster cluster = new Cluster(instance.getClusterName(), this);cluster.init();getClusterMap().put(instance.getClusterName(), cluster);}List<Instance> clusterIPs = ipMap.get(instance.getClusterName());// 一个新的集群名处理if (clusterIPs == null) {clusterIPs = new LinkedList<>();ipMap.put(instance.getClusterName(), clusterIPs);}// 实例添加进对应的集群集合中clusterIPs.add(instance);} catch (Exception e) {Loggers.SRV_LOG.error("[NACOS-DOM] failed to process ip: " + instance, e);}}for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {//make every ip mineList<Instance> entryIPs = entry.getValue();// 微服务实例真正的注册进服务注册表的方法clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);}setLastModifiedMillis(System.currentTimeMillis());// 服务实例改变之后,会发布一个ServiceChangeEvent事件getPushService().serviceChanged(this);StringBuilder stringBuilder = new StringBuilder();for (Instance instance : allIPs()) {stringBuilder.append(instance.toIpAddr()).append("_").append(instance.isHealthy()).append(",");}Loggers.EVT_LOG.info("[IP-UPDATED] namespace: {}, service: {}, ips: {}", getNamespaceId(), getName(),stringBuilder.toString());}
/*** Update instance list.* 下面的方法中有CopyOnWrite的实现思想,真正存储实例的集合是ephemeralInstances,* 但是这里面基本上都是在围绕oldIpMap这个复制出来的副本集合进行相应的操作,最后拿最新的集合复制给ephemeralInstances** @param ips       instance list* @param ephemeral whether these instances are ephemeral*/
public void updateIps(List<Instance> ips, boolean ephemeral) {Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;// 先保存一份现有的实例列表HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size());for (Instance ip : toUpdateInstances) {// 各个服务实例的ip和端口不一样,所以这里生成的key也不一样oldIpMap.put(ip.getDatumKey(), ip);}// 接下来的很多操作就是拿老的实例集合和新的实例集合做一些数据比对,如果是新产生的实例那么就进行添加操作,如果是已经存在了的实例则是修改操作// 最后将最终的结果赋值给ephemeralInstances或persistentInstancesList<Instance> updatedIPs = updatedIps(ips, oldIpMap.values());if (updatedIPs.size() > 0) {for (Instance ip : updatedIPs) {Instance oldIP = oldIpMap.get(ip.getDatumKey());// do not update the ip validation status of updated ips// because the checker has the most precise result// Only when ip is not marked, don't we update the health status of IP:if (!ip.isMarked()) {ip.setHealthy(oldIP.isHealthy());}if (ip.isHealthy() != oldIP.isHealthy()) {// ip validation status updatedLoggers.EVT_LOG.info("{} {SYNC} IP-{} {}:{}@{}", getService().getName(),(ip.isHealthy() ? "ENABLED" : "DISABLED"), ip.getIp(), ip.getPort(), getName());}if (ip.getWeight() != oldIP.getWeight()) {// ip validation status updatedLoggers.EVT_LOG.info("{} {SYNC} {IP-UPDATED} {}->{}", getService().getName(), oldIP.toString(),ip.toString());}}}List<Instance> newIPs = subtract(ips, oldIpMap.values());if (newIPs.size() > 0) {Loggers.EVT_LOG.info("{} {SYNC} {IP-NEW} cluster: {}, new ips size: {}, content: {}", getService().getName(),getName(), newIPs.size(), newIPs.toString());for (Instance ip : newIPs) {HealthCheckStatus.reset(ip);}}List<Instance> deadIPs = subtract(oldIpMap.values(), ips);if (deadIPs.size() > 0) {Loggers.EVT_LOG.info("{} {SYNC} {IP-DEAD} cluster: {}, dead ips size: {}, content: {}", getService().getName(),getName(), deadIPs.size(), deadIPs.toString());for (Instance ip : deadIPs) {HealthCheckStatus.remv(ip);}}toUpdateInstances = new HashSet<>(ips);// 最终,微服务的实例会保存在下面这个集合中if (ephemeral) {ephemeralInstances = toUpdateInstances;} else {persistentInstances = toUpdateInstances;}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Elasticsearch:结合稀疏、密集和地理字段
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • Java中的锁都有什么
  • WPF中逻辑树和视觉树
  • SQL 游标
  • CentOS7 安装 git 命令
  • 使用kali Linux启动盘轻松破解Windows电脑密码
  • 博斯克化学试剂与元宇宙的融合探索
  • Linux实战记录
  • Github 2024-07-07php开源项目日报 Top9
  • Vue项目使用mockjs模拟后端接口
  • 笔记:mysql双主,keepalived 配置
  • godis源码分析——Redis协议解析器
  • JVM内存泄露的ThreadLocal详解
  • 树莓派_Pytorch学习笔记20:初步认识深度学习框架
  • bearychat的java client
  • GraphQL学习过程应该是这样的
  • Hibernate【inverse和cascade属性】知识要点
  • Swift 中的尾递归和蹦床
  • Terraform入门 - 1. 安装Terraform
  • 看完九篇字体系列的文章,你还觉得我是在说字体?
  • 聊聊hikari连接池的leakDetectionThreshold
  • 扑朔迷离的属性和特性【彻底弄清】
  • 前端之Sass/Scss实战笔记
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 时间复杂度与空间复杂度分析
  • 在Docker Swarm上部署Apache Storm:第1部分
  • k8s使用glusterfs实现动态持久化存储
  • ​学习笔记——动态路由——IS-IS中间系统到中间系统(报文/TLV)​
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (二)原生js案例之数码时钟计时
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • *算法训练(leetcode)第四十五天 | 101. 孤岛的总面积、102. 沉没孤岛、103. 水流问题、104. 建造最大岛屿
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET CORE 2.0发布后没有 VIEWS视图页面文件
  • .NET Micro Framework 4.2 beta 源码探析
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .NET 表达式计算:Expression Evaluator
  • .NETCORE 开发登录接口MFA谷歌多因子身份验证
  • .xml 下拉列表_RecyclerView嵌套recyclerview实现二级下拉列表,包含自定义IOS对话框...
  • /var/spool/postfix/maildrop 下有大量文件
  • @Resource和@Autowired的区别
  • @SpringBootConfiguration重复加载报错
  • [ MSF使用实例 ] 利用永恒之蓝(MS17-010)漏洞导致windows靶机蓝屏并获取靶机权限
  • [ 攻防演练演示篇 ] 利用通达OA 文件上传漏洞上传webshell获取主机权限
  • [20170705]diff比较执行结果的内容.txt
  • [Algorithm][综合训练][体育课测验(二)][合唱队形][宵暗的妖怪]详细讲解
  • [BSGS算法]纯水斐波那契数列
  • [C#]使用DlibDotNet人脸检测人脸68特征点识别人脸5特征点识别人脸对齐人脸比对FaceMesh
  • [CF]Codeforces Round #551 (Div. 2)
  • [ios-必看] IOS调试技巧:当程序崩溃的时候怎么办 iphone IOS
  • [leetcode] 103. 二叉树的锯齿形层次遍历
  • [LeetCode] 93. Restore IP Addresses 复原IP地址