处于停机等非正常状态_一文聊透 Dubbo 优雅停机

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 优雅停机特性便体现了这一点。

2 优雅停机的意义

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

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

  • 操作系统层面,提供了 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 的优雅下线主要依赖于注册中心组件,由其通知消费者摘除下线的节点,如下图所示:

ed183acf66f9c54ab24033753ef98f51.png

上述的操作旨在让服务消费者避开已经下线的机器,但这样就算实现了优雅停机了吗?似乎还漏掉了一步,在应用停机时,可能还存在执行到了一半的任务,试想这样一个场景:一个 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) 这段代码会加载到两个协议 :DubboProtocolInjvm 。后者 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 ==");    }}

使用 @PreDestroy 注解

public class TestPreDestroy {    @PreDestroy    public void preDestroy(){        System.out.println("== invoke preDestroy ==");    }}

使用 ApplicationListener 监听 ContextClosedEvent

applicationContext.addApplicationListener(new ApplicationListener() {  @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 的部分用户中,可能还是会存在优雅停机的问题,只不过还没有被发现。

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

相关 pull request:

修复 2.5.3 bug 的 pr:https://github.com/apache/dubbo/pull/568 作者:@qinliujie

2.6.x Spring Shutdown Hook Enhancement: https://github.com/apache/dubbo/pull/1763 , https://github.com/apache/dubbo/pull/1820 作者:@ralf0131

2.7.x Spring Shutdown Hook Enhancement: https://github.com/apache/dubbo/pull/3008/ 作者:@beiwei30

4fb0b5671752fcbbd260d3fa16061937.png

「技术分享」某种程度上,是让作者和读者,不那么孤独的东西。欢迎关注我的微信公众号:「Kirito的技术分享」

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/539780.shtml

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

相关文章

WinDbg 命令三部曲:(一)WinDbg 命令手册

《WinDbg 命令三部曲&#xff1a;&#xff08;一&#xff09;WinDbg 命令手册》《WinDbg 命令三部曲&#xff1a;&#xff08;二&#xff09;WinDbg SOS 扩展命令手册》《WinDbg 命令三部曲&#xff1a;&#xff08;三&#xff09;WinDbg SOSEX 扩展命令手册》导航目录 内置帮助…

华为手机的分类有何区别_“鸿蒙”系统能不能玩安卓游戏?如果能,它跟安卓系统有何区别?...

“鸿蒙”系统能不能玩安卓游戏&#xff1f;如果能&#xff0c;它跟安卓系统有何区别&#xff1f;笔者其实挺好奇一件事情&#xff0c;按理来说&#xff0c;华为即将推出“鸿蒙”系统&#xff0c;作为一款真正的国产系统&#xff0c;笔者肯定是要支持的&#xff0c;毕竟我自己使…

200t不稳定_技术革新!将不可能变为可能 这家企业是怎么做到的?

据水泥人网了解&#xff0c;每年的第四季度是整个水泥行业的高峰期&#xff0c;尤其是北方地区各大水泥集团都将会进入错峰停产和检修期&#xff0c;烧成技术改造往往是水泥企业技改过程最为重要的环节&#xff0c;如何做好烧成技术改造成为水泥企业必须要面对的问题。针对目前…

取消计算机触摸板,笔记本电脑触摸板如何打开和关闭

笔记本电脑触摸板怎么打开和关闭&#xff1f;现在用笔记本的用户都越来越多了&#xff0c;现在也有人把笔记本当电视使了。就是电视上看得到用笔记本联网也是能看到&#xff0c;电视看不到的笔记本电脑也能看到。但是笔记本上面有一个触摸板&#xff0c;现相信大家都用过。可是…

QQ显示服务器繁忙2013,在QQ空间发表日志的之后为什么样总是显示“服务器繁忙”?...

据小米方面介绍&#xff0c;小米手机认证空间帐号自2013年5月21日开通以来&#xff0c;框架&#xff0c;8mm加厚钢化玻璃&#xff0c;15mm防火板材质机壳3、在QQ空间发表日志的之后为什么总是显示“服务器繁忙”&#xff0c;发表不了日志&#xff1f;这个难题在我家电脑下终于存…

oracle更改编码

背景&#xff1a;win764bit英文操作系统&#xff08;支持中文&#xff09;   oracle11G默认安装   从ZHS16GBK字符集导入数据库表现&#xff1a;plsql显示为乱码解决&#xff1a;1、查看并更改数据库的编码为ZHS16GBK $sqlplus system/oracleSQL> select * from v$nls…

she is so css什么意思,输入she is so什么意思 微信she is so什么梗

最近很多人都在微信玩she is so的小游戏&#xff0c;会出现很多不同的形容词很有趣&#xff0c;适合好友之间一起玩。而不少人也不明白输入she is so是什么意思&#xff1f;该怎么玩呢&#xff1f;下文具体介绍。微信输入she is so是什么意思在微信聊天对话框中输入she /he is …

vs2017下开发C++MFC动态库实现

2019独角兽企业重金招聘Python工程师标准>>> 今天无意间浏览了一些关于vs2017新功能的介绍&#xff0c;特别是微软发部了Visual Studio Installer&#xff0c;这个集成安装工具简约的操作风格&#xff0c;丰富vs开发内容&#xff0c;真正打通了开发的“最后一公里”…

hadoop为什么出现

在很多领域里面&#xff0c;在现在这个时代下面&#xff0c;很多公司产生的数据太多了&#xff0c;数据量太大了。用原来的技术去做&#xff0c;有种捉襟见肘的感觉&#xff0c;要么在性能上面&#xff0c;要么在速度上面遇到了瓶颈&#xff0c;这个时候需要新的技术来解决&…

微信视频开发jquery mobile

功能 微信企业号里开发一个微视频功能&#xff0c;用于播放视频。技术 J2EE&#xff0c;前端ui是jquerymobile&#xff0c;HTML5&#xff0c;CSS3&#xff0c;开源视频插件&#xff1a;mediaelement-and-player.min.js 插件官网&#xff1a;http://www.mediaelementjs.com/视…

eclipse中tomcat服务器locations不能修改,解决eclipse中Tomcat服务器的server location选项不能修改的问题...

解决eclipse中Tomcat服务器的server location选项不能修改的问题问题描述编辑tomcat服务器时&#xff0c;server locations无法编辑&#xff0c;如下图&#xff1a;解决方法在Eclipse菜单栏中选择window — show view — server 可以看到服务的面板&#xff0c;服务面板中可看到…

当create table as select 遇上大数据

统计24小时的红包感知专题&#xff0c;有1.5亿行以上的数据&#xff0c;Nokia给出的方法是先按小时执行算法&#xff0c;再汇总各个小时的执行结果。 算法中包含了大量的 sum(case when)计算。 专题里有5个小节&#xff0c;执行计划的时候&#xff0c;需要跑5次where条件不同…

mybatis源码分析(方法调用过程)

十一月月底&#xff0c;宿舍楼失火啦&#xff0c;搞得20多天没有网&#xff0c;目测直到放假也不会来了。。。 正题 嗯~&#xff0c;其实阅读源码不是为了应付面试&#xff0c;更重要的让你知道&#xff0c;大师是怎样去写代码的&#xff0c;同样是用Java&#xff0c;为啥Clint…

提取多个字段_动态合并多个工作表,数据再多也不怕

小伙伴们好啊&#xff0c;今天老祝和大家分享一个动态合并多个工作表的技巧。很多时候&#xff0c;咱们的数据是按照部门或是月份等项目&#xff0c;分别存放在不同工作表中的&#xff0c;要对这些数据进行分析汇总的时候&#xff0c;需要先将不同工作表中的数据合并到一起才可…

2017-2018-1 20155229 《信息安全系统设计基础》第十四周学习总结

2017-2018-1 20155229 《信息安全系统设计基础》第十四周学习总结 对“第三章 程序机器级表示”的深入学习 我选择这章的理由是第一次学的时候还是不太理解&#xff0c;老师也有说这章建议在认真学习&#xff0c;所以本周的学习任务是认真再次学习这一章c语言、汇编代码以及机器…

输入法画面_搜狗输入法:用AI技术谱写诗意生活

十九世纪著名的思想家斯宾塞曾说&#xff1a;科学本身就富有诗意。这里应该包含两种意思&#xff0c;字面上&#xff0c;科学是饱含文字之美的&#xff0c;比如原理和规律的推演&#xff0c;仅通过文字符号的简单排列&#xff0c;便有了生机。但深层次上科学又不止于文字&#…

hadoop伪分布式(单机版)安装,Linux

一、下载 1、hadoop官网下载&#xff1a;https://archive.apache.org/dist/hadoop/common/ 进入stable文件夹里下载&#xff0c;这是稳定版本。 stable/ 本文的版本是 hadoop-2.7.2.tar.gz 2、jdk下载&#xff0c;JDK7及以上&#xff0c;本文用jdk8-64位 二、版本区别 2.…

城轨的两类时钟系统均同步于_基于两台SDS3000示波器同步产生“8通道”示波器...

在很多应用场合需要4通道以上的示波器&#xff0c;但是市面上极大部分示波器最多只有四通道&#xff0c;而且没有外部输入的同步时钟接口。 有什么快捷的方法获得更多通道功能的示波器&#xff1f; 最简便的方法是:将两台示波器的辅助输入信号作为触发源&#xff0c;同时连接到…

Linux设置ssh免密码登录

一、SSH来源 对于需要远程管理其它机器&#xff0c;一般使用远程桌面或者telnet。linux一般只能是telnet。但是telnet的缺点是通信不加密&#xff0c;存在不安全因素&#xff0c;只适合内网访问。 为解决这个问题&#xff0c;推出了通信加密通信协议&#xff0c;即SSH&#x…

解析json数据_Retrofit同时解析JSON和XML数据格式

前言Android开发中&#xff0c;我们会经常遇到前端需要解析两种数据格式(json和xml),比如自己服务器返回的是json格式的数据&#xff0c;我们做微信登录的时候&#xff0c;微信返回的格式又是xml格式的。我们可以通过自己编写Retrofit的ConverterFactory来做到可以同时解析两种…