您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

一文聊透 Dubbo 优雅停机

时间:2019-10-14 10:39:09  来源:  作者:

1 前言

一年之前,我(老徐)曾经写过一篇《研究优雅停机时的一点思考》,主要介绍了 kill -9,kill -15 两个 linux 指令的含义,并且针对性的聊到了 Spring Boot 应用如何正确的优雅停机,算是本文的前置文章,如果你对上述概念不甚了解,建议先去浏览一遍,再回头来看这篇文章。这篇文章将会以 Dubbo 为例,既聊架构设计,也聊源码,聊聊服务治理框架要真正实现优雅停机,需要注意哪些细节。

本文的写作思路是从 Dubbo 2.5.x 开始,围绕优雅停机这个优化点,一直追溯到最新的 2.7.x。先对 Dubbo 版本做一个简单的科普:2.7.x 和 2.6.x 是目前官方推荐使用的版本,其中 2.7.x 是捐献给 Apache 的版本,具备了很多新的特性,目前最新的 release 版本是 2.7.4,处于生产基本可用的状态;2.6.x 处于维护态,主要以 bugfix 为主,但经过了很多公司线上环境的验证,所以求稳的话,可以使用 2.6.x 分支最新的版本。至于 2.5.x,社区已经放弃了维护,并且 2.5.x 存在一定数量的 bug,本文介绍的 Dubbo 优雅停机特性便体现了这一点。

优雅停机一直是一个非常严谨的话题,但由于其仅仅存在于重启、下线这样的部署阶段,导致很多人忽视了它的重要性,但没有它,你永远不能得到一个完整的应用生命周期,永远会对系统的健壮性持怀疑态度。

同时,优雅停机又是一个庞大的话题

  • 操作系统层面,提供了 kill -9 (SIGKILL)和 kill -15(SIGTERM) 两种停机策略
  • 语言层面,JAVA 应用有 JVM shutdown hook 这样的概念
  • 框架层面,Spring Boot 提供了 actuator 的下线 endpoint,提供了 ContextClosedEvent 事件
  • 容器层面,Docker :当执行 docker stop 命令时,容器内的进程会收到 SIGTERM 信号,那么 Docker Daemon 会在 10s 后,发出 SIGKILL 信号;K8S 在管理容器生命周期阶段中提供了 prestop 钩子方法
  • 应用架构层面,不同架构存在不同的部署方案。单体式应用中,一般依靠 Nginx 这样的负载均衡组件进行手动切流,逐步部署集群;微服务架构中,各个节点之间有复杂的调用关系,上述这种方案就显得不可靠了,需要有自动化的机制。

为避免该话题过度发散,本文的重点将会集中在框架和应用架构层面,探讨以 Dubbo 为代表的微服务架构在优雅停机上的最佳实践。Dubbo 的优雅下线主要依赖于注册中心组件,由其通知消费者摘除下线的节点,如下图所示:

一文聊透 Dubbo 优雅停机

 

上述的操作旨在让服务消费者避开已经下线的机器,但这样就算实现了优雅停机了吗?似乎还漏掉了一步,在应用停机时,可能还存在执行到了一半的任务,试想这样一个场景:一个 Dubbo 请求刚到达提供者,服务端正在处理请求,收到停机指令后,提供者直接停机,留给消费者的只会是一个没有处理完毕的超时请求。

 

结合上述的案例,我们总结出 Dubbo 优雅停机需要满足两点基本诉求:

  1. 服务消费者不应该请求到已经下线的服务提供者
  2. 在途请求需要处理完毕,不能被停机指令中断

优雅停机的意义:应用的重启、停机等操作,不影响业务的连续性。

3 优雅停机初始方案 — 2.5.x

为了让读者对 Dubbo 的优雅停机有一个最基础的理解,我们首先研究下 Dubbo 2.5.x 的版本,这个版本实现优雅停机的方案相对简单,容易理解。

3.1 入口类:AbstractConfig

public abstract class AbstractConfig implements Serializable {
 static {
 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
 public void run() {
 ProtocolConfig.destroyAll();
 }
 }, "DubboShutdownHook"));
 }
}

在 AbstractConfig 的静态块中,Dubbo 注册了一个 shutdown hook,用于执行 Dubbo 预设的一些停机逻辑,继续跟进 ProtocolConfig.destroyAll() 。

3.2 ProtocolConfig

public static void destroyAll() {
 if (!destroyed.compareAndSet(false, true)) {
 return;
 }
 AbstractRegistryFactory.destroyAll(); // ①注册中心注销
 // Wait for registry notification
 try {
 Thread.sleep(ConfigUtils.getServerShutdownTimeout()); // ② sleep 等待
 } catch (InterruptedException e) {
 logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
 }
 ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
 for (String protocolName : loader.getLoadedExtensions()) {
 try {
 Protocol protocol = loader.getLoadedExtension(protocolName);
 if (protocol != null) {
 protocol.destroy(); // ③协议/流程注销
 }
 } catch (Throwable t) {
 logger.warn(t.getMessage(), t);
 }
 }
}

Dubbo 中的 Protocol 这个词不太能望文生义,它一般被翻译为”协议”,但我更习惯将它理解为“流程”,从 Protocol 接口的三个方法反而更加容易理解。

public interface Protocol {
 <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
 <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
 void destroy();
}

它定义了暴露、订阅、注销这三个生命周期方法,所以不难理解为什么 Dubbo 会把 shutdown hook 触发后的注销方法定义在 ProtocolConfig 中了。

回到 ProtocolConfig 的源码中,我把 ProtocolConfig 中执行的优雅停机逻辑分成了三部分,其中第 1,2 部分和注册中心(Registry)相关,第 3 部分和协议/流程(Protocol)相关,分成下面的 3.3 和 3.4 两部分来介绍。

3.3 注册中心注销逻辑

public abstract class AbstractRegistryFactory implements RegistryFactory {
 public static void destroyAll() {
 LOCK.lock();
 try {
 for (Registry registry : getRegistries()) {
 try {
 registry.destroy();
 } catch (Throwable e) {
 LOGGER.error(e.getMessage(), e);
 }
 }
 REGISTRIES.clear();
 } finally {
 // Release the lock
 LOCK.unlock();
 }
 }
}

这段代码对应了 3.2 小节 ProtocolConfig 源码的第 1 部分,代表了注册中心的注销逻辑,更深一层的源码不需要 debug 进去了,大致的逻辑就是删除掉注册中心中本节点对应的服务提供者地址。

// Wait for registry notification
try {
 Thread.sleep(ConfigUtils.getServerShutdownTimeout());
} catch (InterruptedException e) {
 logger.warn("Interrupted unexpectedly when waiting for registry notification during shutdown process!");
}

这段代码对应了 3.2 小节 ProtocolConfig 源码的第 2 部分,ConfigUtils.getServerShutdownTimeout() 默认值是 10s,为什么需要在 shutdown hook 中等待 10s 呢?在注释中可以发现这段代码的端倪,原来是为了给服务消费者一点时间,确保等到注册中心的通知。10s 显然是一个经验值,这里也不妨和大家探讨一下,如何稳妥地设置这个值呢?

  • 设置的过短。由于注册中心通知消费者取消订阅某个地址是异步通知过去的,可能消费者还没收到通知,提供者这边就停机了,这就违背了我们的诉求 1:服务消费者不应该请求到已经下线的服务提供者
  • 设置的过长。这会导致发布时间变长,带来不必要的等待。

两个情况对比下,起码可以得出一个实践经验:如果拿捏不准等待时间,尽量设置一个宽松的一点的等待时间。

这个值主要取决三点因素:

  • 集群规模的大小。如果只有几个服务,每个服务只有几个实例,那么再弱鸡的注册中心也能很快的下发通知。
  • 注册中心的选型。以 Naocs 和 Zookeeper 为例,同等规模服务实例下 Nacos 在推送地址方面的能力远超 Zookeeper。
  • 网络状况。服务提供者和服务消费者与注册中心的交互逻辑走的 TCP 通信,网络状况也会影响到推送时间。

所以需要根据实际部署场景测量出最合适的值。

3.4 协议/流程注销逻辑

ExtensionLoader<Protocol> loader = ExtensionLoader.getExtensionLoader(Protocol.class);
for (String protocolName : loader.getLoadedExtensions()) {
 try {
 Protocol protocol = loader.getLoadedExtension(protocolName);
 if (protocol != null) {
 protocol.destroy();
 }
 } catch (Throwable t) {
 logger.warn(t.getMessage(), t);
 }
}

这段代码对应了 3.2 小节 ProtocolConfig 源码的第 3 部分,在运行时,loader.getLoadedExtension(protocolName) 这段代码会加载到两个协议 :DubboProtocol 和 Injvm 。后者 Injvm 实在没啥好讲的,主要来分析一下 DubboProtocol 的逻辑。

DubboProtocol 实现了我们前面提到的 Protocol 接口,它的 destory 方法是我们重点要看的。

public class DubboProtocol extends AbstractProtocol {
 public void destroy() {
 for (String key : new ArrayList<String>(serverMap.keySet())) {
 ExchangeServer server = serverMap.remove(key);
 if (server != null) {
 server.close(ConfigUtils.getServerShutdownTimeout());
 }
 }
 for (String key : new ArrayList<String>(referenceClientMap.keySet())) {
 ExchangeClient client = referenceClientMap.remove(key);
 if (client != null) {
 client.close(ConfigUtils.getServerShutdownTimeout());
 }
 }
 for (String key : new ArrayList<String>(ghostClientMap.keySet())) {
 ExchangeClient client = ghostClientMap.remove(key);
 if (client != null) {
 client.close(ConfigUtils.getServerShutdownTimeout());
 }
 }
 stubServiceMethodsMap.clear();
 super.destroy();
 }
}

主要分成了两部分注销逻辑:server 和 client,注意这里是先注销了服务提供者后,再注销了服务消费者,这样做是有意为之。在 RPC 调用中,经常是一个远程调用触发一个远程调用,所以在关闭一个节点时,应该先切断上游的流量,所以这里是先注销了服务提供者,这样从一定程度上,降低了后面服务消费者被调用到的可能性(当然,服务消费者也有可能被单独调用到)。由于 server 和 client 的流程类似,所以我只选取了 server 部分来分析具体的注销逻辑。

public void close(final int timeout) {
 startClose();
 if (timeout > 0) {
 final long max = (long) timeout;
 final long start = System.currentTimeMillis();
 if (getUrl().getParameter(Constants.CHANNEL_SEND_READONLYEVENT_KEY, true)) {
 // 如果注册中心有延迟,会立即受到readonly事件,下次不会再调用这台机器,当前已经调用的会处理完
 sendChannelReadOnlyEvent();
 }
 while (HeaderExchangeServer.this.isRunning() // ①
 && System.currentTimeMillis() - start < max) {
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 logger.warn(e.getMessage(), e);
 }
 }
 }
 doClose(); // ②
 server.close(timeout); // ③
 }
 private boolean isRunning() {
 Collection<Channel> channels = getChannels();
 for (Channel channel : channels) {
 if (DefaultFuture.hasFuture(channel)) {
 return true;
 }
 }
 return false;
 }
private void doClose() {
 if (!closed.compareAndSet(false, true)) {
 return;
 }
 stopHeartbeatTimer();
 try {
 scheduled.shutdown();
 } catch (Throwable t) {
 logger.warn(t.getMessage(), t);
 }
 }

化繁为简,这里只挑选上面代码中打标的两个地方进行分析

  1. 判断服务端是否还在处理请求,在超时时间内一直等待到所有任务处理完毕
  2. 关闭心跳检测
  3. 关闭 NettyServer

特别需要关注第一点,正符合我们在一开始提出的优雅停机的诉求 2:“在途请求需要处理完毕,不能被停机指令中断”

3.5 优雅停机初始方案总结

上述介绍的几个类构成了 Dubbo 2.5.x 的优雅停机方案,简单做一下总结,Dubbo 的优雅停机逻辑时序如下:

Registry 注销
等待 -Ddubbo.service.shutdown.wait 秒,等待消费方收到下线通知
Protocol 注销
 DubboProtocol 注销
 NettyServer 注销
 等待处理中的请求完毕
 停止发送心跳
 关闭 Netty 相关资源
 NettyClient 注销
 停止发送心跳
 等待处理中的请求完毕
 关闭 Netty 相关资源

Dubbo 2.5.3 优雅停机的缺陷

如果你正在使用的 Dubbo 版本 <= 2.5.3,一些并发问题和代码缺陷会导致你的应用不能很好的实现优雅停机功能,请尽快升级。

详情可以参考该 pull request 的变更:https://github.com/apache/dubbo/pull/568

 

4 Spring 容器下 Dubbo 的优雅停机

上述的方案在不使用 Spring 时的确是无懈可击的,但由于现在大多数开发者选择使用 Spring 构建 Dubbo 应用,上述的方案会存在一些缺陷。

由于 Spring 框架本身也依赖于 shutdown hook 执行优雅停机,并且与 Dubbo 的优雅停机会并发执行,而 Dubbo 的一些 Bean 受 Spring 托管,当 Spring 容器优先关闭时,会导致 Dubbo 的优雅停机流程无法获取相关的 Bean,从而优雅停机失效。

Dubbo 开发者们迅速意识到了 shutdown hook 并发执行的问题,开始了一系列的补救措施。

4.1 增加 ShutdownHookListener

Spring 如此受欢迎的原因之一便是它的扩展点非常丰富,例如它提供了 ApplicationListener 接口,开发者可以实现这个接口监听到 Spring 容器的关闭事件,为解决 shutdown hook 并发执行的问题,在 Dubbo 2.6.3 中新增了 ShutdownHookListener 类,用作 Spring 容器下的关闭 Dubbo 应用的钩子。

private static class ShutdownHookListener implements ApplicationListener {
 @Override
 public void onApplicationEvent(ApplicationEvent event) {
 if (event instanceof ContextClosedEvent) {
 // we call it anyway since dubbo shutdown hook make sure its destroyAll() is re-entrant.
 // pls. note we should not remove dubbo shutdown hook when spring framework is present, this is because
 // its shutdown hook may not be installed.
 DubboShutdownHook shutdownHook = DubboShutdownHook.getDubboShutdownHook();
 shutdownHook.destroyAll();
 }
 }
}

当服务提供者 ServiceBean 和服务消费者 ReferenceBean 被初始化时,会触发该钩子被创建。

再来看看 AbstractConfig 中的代码,依旧保留了 JVM 的 shutdown hook

public abstract class AbstractConfig implements Serializable {
 static {
 Runtime.getRuntime().addShutdownHook(DubboShutdownHook.getDubboShutdownHook());
 }
}

也就是说,在 Spring 环境下会注册两个钩子,在 Non-Spring 环境下只会有一个钩子,但看到 2.6.x 的实现大家是否意识到了两个问题呢?

  1. 两个钩子并发执行不会报错吗?
  2. 为什么在 Spring 下不取消 JVM 的钩子,只保留 Spring 的钩子不就可以工作了吗?

先解释第一个问题,这个按照我的理解,这段代码的 Commiter 可能认为只需要有一个 Spring 的钩子能正常注销就完事了,不需要考虑另外一个报不报错,因为都是独立的线程,不会有很大的影响。

再解释第二个问题,其实这个疑问的答案就藏在上面 ShutdownHookListener 代码的注释中,这段注释的意思是说:在 Spring 框架下不能直接移除原先的 JVM 钩子,因为 Spring 框架可能没有注册 ContextClosed 事件。啥意思呢?这里涉及到 Spring 框架生命周期的一个细节,我打算单独介绍一下。

4.2 Spring 的容器关闭事件详解

在 Spring 中,我们可以使用至少三种方式来注册容器关闭时一些收尾工作:

  1. 使用 DisposableBean 接口
public class TestDisposableBean implements DisposableBean {
 @Override
 public void destroy() throws Exception {
 System.out.println("== invoke DisposableBean ==");
 }
}
  1. 使用 @PreDestroy 注解
public class TestPreDestroy {
 @PreDestroy
 public void preDestroy(){
 System.out.println("== invoke preDestroy ==");
 }
}
  1. 使用 ApplicationListener 监听 ContextClosedEvent
applicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
 @Override
 public void onApplicationEvent(ApplicationEvent applicationEvent) {
 if (applicationEvent instanceof ContextClosedEvent) {
 System.out.println("== receive context closed event ==");
 }
 }
});

但需要注意的是,在使用 SpringBoot 内嵌 Tomcat 容器时,容器关闭钩子是自动被注册,但使用纯粹的 Spring 框架或者外部 Tomcat 容器,需要显式的调用 context.registerShutdownHook(); 接口进行注册

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/beans.xml");
context.start();
context.registerShutdownHook();
context.addApplicationListener(new ApplicationListener<ApplicationEvent>() {
 @Override
 public void onApplicationEvent(ApplicationEvent applicationEvent) {
 if (applicationEvent instanceof ContextClosedEvent) {
 System.out.println("== receive context closed event ==");
 }
 }
});

否则,上述三种回收方法都无法工作。我们来看看 registerShutdownHook() 都干了啥

public abstract class AbstractApplicationContext extends DefaultResourceLoader
 implements ConfigurableApplicationContext, DisposableBean{
 @Override
 public void registerShutdownHook() {
 if (this.shutdownHook == null) {
 // No shutdown hook registered yet.
 this.shutdownHook = new Thread() {
 @Override
 public void run() {
 synchronized (startupShutdownMonitor) {
 doClose();
 }
 }
 };
 Runtime.getRuntime().addShutdownHook(this.shutdownHook); // 重点!
 }
 }
}

其实也就是显式注册了一个属于 Spring 的钩子。这也解释上了 4.1 小节中,为什么有那段注释了,注册了事件不一定管用,还得保证 Spring 容器注册了它自己的钩子。

4.3 Dubbo 优雅停机中级方案总结

第 4 节主要介绍了 Dubbo 开发者们在 Spring 环境下解决 Dubbo 优雅停机并发执行 shutdown hook 时的缺陷问题,但其实还不完善,因为在 Spring 环境下,如果没有显式注册 Spring 的 shutdown, 还是会存在缺陷的,准确的说,Dubbo 2.6.x 版本可以很好的在 Non-Spring、Spring Boot、Spring + ContextClosedEvent 环境下很好的工作。

5 Dubbo 2.7 最终方案

public class SpringExtensionFactory implements ExtensionFactory {
 public static void addApplicationContext(ApplicationContext context) {
 CONTEXTS.add(context);
 if (context instanceof ConfigurableApplicationContext) {
 ((ConfigurableApplicationContext) context).registerShutdownHook();
 DubboShutdownHook.getDubboShutdownHook().unregister();
 }
 BeanFactoryUtils.addApplicationListener(context, SHUTDOWN_HOOK_LISTENER);
 }
}

这段代码寥寥数行,却是经过了深思熟虑之后的产物,期间迭代了 3 个大版本,真是不容易。这段代码很好地解决了第 4 节提出的两个问题

  1. 担心两个钩子并发执行有问题?那就在可以注册 Spring 钩子的时候取消掉 JVM 的钩子。
  2. 担心当前 Spring 容器没有注册 Spring 钩子?那就显示调用 registerShutdownHook 进行注册。

其他细节方面的优化和 bugfix 我就不进行详细介绍了,可以见得实现一个优雅停机需要考虑的点非常之多。

6 总结

优雅停机看似是一个不难的技术点,但在一个通用框架中,使用者的业务场景类型非常多,这会大大加剧整个代码实现的复杂度。

摸清楚整个 Dubbo 优雅停机演化的过程,也着实花费了我一番功夫,有很多实现需要 checkout 到非常古老的分支,同时翻阅了很多 issue、pull request 的讨论,最终才形成了这篇文章,虽然研究的过程是困难的,但获取到真相是让人喜悦的。

在开源产品的研发过程中,服务到每一个类型的用户真的是非常难的一件事,能做的是满足大部分用户。例如 2.6.x 在大多数环境下其实已经没问题了,在 2.7.x 中则是得到了更加的完善,但是我相信,在使用 Dubbo 的部分用户中,可能还是会存在优雅停机的问题,只不过还没有被发现。

商业化的思考:和开源产品一样,商业化产品的研发也同样是一个逐渐迭代的过程,需要数代开发者一起维护一份代码,使用者发现问题,开发者修复问题,这样的正反馈可以形成一个正反馈,促使产品更加优秀。

喜欢的点个关注,一起学习探讨新技术。



Tags:Dubbo   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
第一部分介绍生产上出现Dubbo服务拥堵的情况,以及Dubbo官方对于单个长连接的使用建议。 第二部分介绍Dubbo在特定配置下的通信过程,辅以代码。 第三部分介绍整个调用过程中与性能相关的一些参数。 第四部分通过调整...【详细内容】
2021-06-18  Tags: Dubbo  点击:(123)  评论:(0)  加入收藏
1. Dubbo简介及线程池策略Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现...【详细内容】
2021-05-18  Tags: Dubbo  点击:(202)  评论:(0)  加入收藏
Dubbo里除了Service和Config层为API,其它各层均为SPI。相比于Java中的SPI仅仅通过接口类名获取所有实现,Dubbo的实现可以通过接口类名和key值来获取一个具体的实现。通过SPI...【详细内容】
2020-09-07  Tags: Dubbo  点击:(62)  评论:(0)  加入收藏
Dubbo(来自于阿里巴巴)Dubbo是一个分布式服务框架,致力于提供高性能和透明化的PRC远程调用服务调用方案。 Dubbo的的特点 通过spring配置的方式即可完成服务化,对于应用无入侵。...【详细内容】
2020-08-10  Tags: Dubbo  点击:(41)  评论:(0)  加入收藏
概述Dubbo支持同一服务向多个注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是也支持自定义扩...【详细内容】
2020-07-18  Tags: Dubbo  点击:(54)  评论:(0)  加入收藏
导读:在分布式系统中,远程调用是最基础也是最重要的基石。历史上,曾经先后出现过 CORBA、RMI、EJB、WebService 等技术和规范,在服务化以及微服务日趋流行的今天,更多的被广泛使...【详细内容】
2020-07-13  Tags: Dubbo  点击:(65)  评论:(0)  加入收藏
概述Dubbo的配置项非常多,但所有配置项分为三大类,服务发现、服务治理、性能调优。服务发现类的配置项,表示该配置项用于服务的注册与发现,目的是让消费方找到提供方;服务治理类...【详细内容】
2020-07-13  Tags: Dubbo  点击:(95)  评论:(0)  加入收藏
前言这周收到外部合作同事推送的一篇文章,【漏洞通告】Apache Dubbo Provider默认反序列化远程代码执行漏洞(CVE-2020-1948)通告。按照文章披露的漏洞影响范围,可以说是当前所有...【详细内容】
2020-07-09  Tags: Dubbo  点击:(118)  评论:(0)  加入收藏
0x01 漏洞背景2020年06月23日, 360CERT监测发现 Apache Dubbo 官方 发布了 Apache Dubbo 远程代码执行 的风险通告,该漏洞编号为 CVE-2020-1948,漏洞等级:高危。Apache Dubbo...【详细内容】
2020-06-24  Tags: Dubbo  点击:(47)  评论:(0)  加入收藏
不知道你是否在工作中有遇到过类似情况: dubbo接口调试复杂,需要通过telnet命令或者通过consumer调用来触发。 telnet语句参数格式复杂,每次编写都要小心谨慎,一旦出错又需重来...【详细内容】
2020-06-18  Tags: Dubbo  点击:(59)  评论:(0)  加入收藏
▌简易百科推荐
本文分为三个等级自顶向下地分析了glibc中内存分配与回收的过程。本文不过度关注细节,因此只是分别从arena层次、bin层次、chunk层次进行图解,而不涉及有关指针的具体操作。前...【详细内容】
2021-12-28  linux技术栈    Tags:glibc   点击:(3)  评论:(0)  加入收藏
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(2)  评论:(0)  加入收藏
程序是如何被执行的&emsp;&emsp;程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(10)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(20)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(25)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(25)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条