處于停機等非正常狀態_一文聊透 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.pswp.cn/news/539780.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/539780.shtml
英文地址,請注明出處:http://en.pswp.cn/news/539780.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Centos 配置eth0 提示Device does not seem to be present

一.故障現象&#xff1a;[rootc1node01 ~]# service network restartShutting down loopback insterface: [ OK ]Bringing up loopback insterface: [ …

計算機boot進入u盤啟動,暗影精靈5怎么設置u盤啟動 暗影精靈5設置u盤啟動方法...

最近有位電腦用戶想要使用u盤啟動盤重裝系統&#xff0c;但是卻不知道應該怎么使用bios設置u盤啟動&#xff0c;為此非常苦惱&#xff0c;那么惠普暗影精靈5 omen 15-dc1068tx筆記本怎么使用bios設置u盤啟動呢?下面為大家介紹惠普暗影精靈5 omen 15-dc1068tx筆記本使用bios設置…

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;畢竟我自己使…

MyEclipse2014破解

MyEclipse下載地址&#xff1a; http://www.xp85.com/html/MyEclipse2014.html 破解教程http://jingyan.baidu.com/article/7082dc1c57eb19e40a89bdcd.html

計算機模擬蛋白,酶的計算機模擬和蛋白質組分析

報告題目&#xff1a;Computer Simulation of Enzymes and Analysis of Proteomes/Protein Sets(酶的計算機模擬和蛋白質組分析)主講人&#xff1a;郭鴻主講人簡介&#xff1a;Professor, Department of Biochemistry & Cellular and Molecular Biology, University of Ten…

分塊編碼(Transfer-Encoding: chunked)VS Content-length

參考鏈接&#xff1a; HTTP 協議中的 Transfer-Encoding 分塊傳輸編碼 https://www.cnblogs.com/xuehaoyue/p/6639029.html 一、背景&#xff1a; 持續連接的問題&#xff1a;對于非持續連接&#xff0c;瀏覽器可以通過連接是否關閉來界定請求或響應實體的邊界&#xff1b;而…

200t不穩定_技術革新!將不可能變為可能 這家企業是怎么做到的?

據水泥人網了解&#xff0c;每年的第四季度是整個水泥行業的高峰期&#xff0c;尤其是北方地區各大水泥集團都將會進入錯峰停產和檢修期&#xff0c;燒成技術改造往往是水泥企業技改過程最為重要的環節&#xff0c;如何做好燒成技術改造成為水泥企業必須要面對的問題。針對目前…

關于update set from where

關于update set from where 下面是這樣一個例子&#xff1a; 兩個表a、b&#xff0c;想使b中的memo字段值等于a表中對應id的name值 表a&#xff1a;id&#xff0c;name 1 王 2 李 3 張 表b&#x…

取消計算機觸摸板,筆記本電腦觸摸板如何打開和關閉

筆記本電腦觸摸板怎么打開和關閉&#xff1f;現在用筆記本的用戶都越來越多了&#xff0c;現在也有人把筆記本當電視使了。就是電視上看得到用筆記本聯網也是能看到&#xff0c;電視看不到的筆記本電腦也能看到。但是筆記本上面有一個觸摸板&#xff0c;現相信大家都用過。可是…

List數據多重規則排序

List集合進行排序時&#xff0c;很多人會考慮 冒泡、快速等排序算法&#xff0c;但是對于多重排序規則的話&#xff0c;算法就不太適用了。其實java.util.Collections已經提供了 sort的排序方法&#xff0c;并且能自己實現其排序規則。現在有個場景&#xff1a;我需要對一批優惠…

QQ顯示服務器繁忙2013,在QQ空間發表日志的之后為什么樣總是顯示“服務器繁忙”?...

據小米方面介紹&#xff0c;小米手機認證空間帳號自2013年5月21日開通以來&#xff0c;框架&#xff0c;8mm加厚鋼化玻璃&#xff0c;15mm防火板材質機殼3、在QQ空間發表日志的之后為什么總是顯示“服務器繁忙”&#xff0c;發表不了日志&#xff1f;這個難題在我家電腦下終于存…

http sxyk.cdn_Discuz x3 開啟cdn和https后鏈接修改教程

Discuz x3 開啟cdn和https后鏈接修改教程開啟支持https主要需要修改一下幾個地方1. 、查找修改文件discuz_application.PHP &#xff1a;source/class/discuz/discuz_application.php (約第 187 行處)&#xff1a; 查找&#xff1a;$_G[isHTTPS] ($_SERVER[HTTPS] && …

8.4. su - root

add a user to wheel group rootfreebsd:~ # pw usermod neo -G wheel rootfreebsd:~ # id neo uid1001(neo) gid1001(neo) groups1001(neo),0(wheel)freebsd# grep wheel /etc/group wheel:*:0:root,neo原文出處&#xff1a;Netkiller 系列 手札 本文作者&#xff1a;陳景峯 轉…

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 …

transformer論文解讀_【論文解讀】ICLR2020 | 深度自適應Transformer

作者 | kaiyuan 整理 | NewBeeNLP一篇來自ICLR2020&#xff0c;facebook人工智能實驗室的論文&#xff0c;關于改進Transformer推理速度的。論文地址&#xff1a;https://arxiv.org/abs/1910.10073寫在前面大模型在困難任務上表現非常好&#xff0c;而小模型也可以在比較簡單…

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/視…