主頁 > 後端開發 > 【轉】認識長輪詢:配置中心是如何實作推送的?

【轉】認識長輪詢:配置中心是如何實作推送的?

2021-12-23 06:16:09 後端開發

一 前言

傳統的靜態配置方式想要修改某個配置時,必須重新啟動一次應用,如果是資料庫連接串的變更,那可能還容易接受一些,但如果變更的是一些運行時實時感知的配置,如某個功能項的開關,重啟應用就顯得有點大動干戈了,配置中心正是為了解決此類問題應運而生的,特別是在微服務架構體系中,更傾向于使用配置中心來統一管理配置,

配置中心最核心的能力就是配置的動態推送,常見的配置中心如 Nacos、Apollo 等都實作了這樣的能力,在早期接觸配置中心時,我就很好奇,配置中心是如何做到服務端感知配置變化實時推送給客戶端的,在沒有研究過配置中心的實作原理之前,我一度認為配置中心是通過長連接來做到配置推送的,事實上,目前比較流行的兩款配置中心:Nacos 和 Apollo 恰恰都沒有使用長連接,而是使用的長輪詢,本文便是介紹一下長輪詢這種聽起來好像已經是上個世紀的技術,老戲新唱,看看能不能品出別樣的韻味,文中會有代碼示例,呈現一個簡易的配置監聽流程,

二 資料互動模式

眾所周知,資料互動有兩種模式:Push(推模式)和 Pull(拉模式),

推模式指的是客戶端與服務端建立好網路長連接,服務方有相關資料,直接通過長連接通道推送到客戶端,其優點是及時,一旦有資料變更,客戶端立馬能感知到;另外對客戶端來說邏輯簡單,不需要關心有無資料這些邏輯處理,缺點是不知道客戶端的資料消費能力,可能導致資料積壓在客戶端,來不及處理,

拉模式指的是客戶端主動向服務端發出請求,拉取相關資料,其優點是此程序由客戶端發起請求,故不存在推模式中資料積壓的問題,缺點是可能不夠及時,對客戶端來說需要考慮資料拉取相關邏輯,何時去拉,拉的頻率怎么控制等等,

三 長輪詢與輪詢

在開頭,重點介紹一下長輪詢(Long Polling)和輪詢(Polling)的區別,兩者都是拉模式的實作,

“輪詢”是指不管服務端資料有無更新,客戶端每隔定長時間請求拉取一次資料,可能有更新資料回傳,也可能什么都沒有,配置中心如果使用「輪詢」實作動態推送,會有以下問題:

  • 推送延遲,客戶端每隔 5s 拉取一次配置,若配置變更發生在第 6s,則配置推送的延遲會達到 4s,
  • 服務端壓力,配置一般不會發生變化,頻繁的輪詢會給服務端造成很大的壓力,
  • 推送延遲和服務端壓力無法中和,降低輪詢的間隔,延遲降低,壓力增加;增加輪詢的間隔,壓力降低,延遲增高,

“長輪詢”則不存在上述的問題,客戶端發起長輪詢,如果服務端的資料沒有發生變更,會 hold 住請求,直到服務端的資料發生變化,或者等待一定時間超時才會回傳,回傳后,客戶端又會立即再次發起下一次長輪詢,配置中心使用「長輪詢」如何解決「輪詢」遇到的問題也就顯而易見了:

  • 推送延遲,服務端資料發生變更后,長輪詢結束,立刻回傳回應給客戶端,
  • 服務端壓力,長輪詢的間隔期一般很長,例如 30s、60s,并且服務端 hold 住連接不會消耗太多服務端資源,

以 Nacos 為例的長輪詢流程如下:

可能有人會有疑問,為什么一次長輪詢需要等待一定時間超時,超時后又發起長輪詢,為什么不讓服務端一直 hold 住?主要有兩個層面的考慮,一是連接穩定性的考慮,長輪詢在傳輸層本質上還是走的 TCP 協議,如果服務端假死、fullgc 等例外問題,或者是重啟等常規操作,長輪詢沒有應用層的心跳機制,僅僅依靠 TCP 層的心跳保活很難確保可用性,所以一次長輪詢設定一定的超時時間也是在確保可用性,除此之外,在配置中心場景,還有一定的業務需求需要這么設計,在配置中心的使用程序中,用戶可能隨時新增配置監聽,而在此之前,長輪詢可能已經發出,新增的配置監聽無法包含在舊的長輪詢中,所以在配置中心的設計中,一般會在一次長輪詢結束后,將新增的配置監聽給捎帶上,而如果長輪詢沒有超時時間,只要配置一直不發生變化,回應就無法回傳,新增的配置也就沒法設定監聽了,

四 配置中心長輪詢設計

上文的圖中,介紹了長輪詢的流程,本節會詳解配置中心長輪詢的設計細節,

客戶端發起長輪詢

客戶端發起一個 HTTP 請求,請求資訊包含配置中心的地址,以及監聽的 dataId(本文出于簡化說明的考慮,認為 dataId 是定位配置的唯一鍵),若配置沒有發生變化,客戶端與服務端之間一直處于連接狀態,

服務端監聽資料變化

服務端會維護 dataId 和長輪詢的映射關系,如果配置發生變化,服務端會找到對應的連接,為回應寫入更新后的配置內容,如果超時內配置未發生變化,服務端找到對應的超時長輪詢連接,寫入 304 回應,

304 在 HTTP 回應碼中代表“未改變”,并不代表錯誤,比較契合長輪詢時,配置未發生變更的場景,

客戶端接收長輪詢回應

首先查看回應碼是 200 還是 304,以判斷配置是否變更,做出相應的回呼,之后再次發起下一次長輪詢,

服務端設定配置寫入的接入點

主要用配置控制臺和 client 發布配置,觸發配置變更,

這幾點便是配置中心實作長輪詢的核心步驟,也是指導下面章節代碼實作的關鍵,但在編碼之前,仍有一些其他的注意點需要實作闡明,

配置中心往往是為分布式的集群提供服務的,而每個機器上部署的應用,又會有多個 dataId 需要監聽,實體級別 * 配置數是一個不小的數字,配置中心服務端維護這些 dataId 的長輪詢連接顯然不能用執行緒一一對應,否則會導致服務端執行緒數爆炸式增長,一個 Tomcat 也就 200 個執行緒,長輪詢也不應該阻塞 Tomcat 的業務執行緒,所以需要配置中心在實作長輪詢時,往往采用異步回應的方式來實作,而比較方便實作異步 HTTP 的常見手段便是 Servlet3.0 提供的 AsyncContext 機制,

Servlet3.0 并不是一個特別新的規范,它跟 Java 6 是同一時期的產物,例如 SpringBoot 內嵌的 Tomcat 很早就支持了 Servlet3.0,你無需擔心 AsyncContext 機制不起作用,

SpringMVC 實作了 DeferredResult 和 Servlet3.0 提供的 AsyncContext 其實沒有多大區別,我并沒有深入研究過兩個實作背后的原始碼,但從使用層面上來看,AsyncContext 更加的靈活,例如其可以自定義回應碼,而 DeferredResult 在上層做了封裝,可以快速的幫助開發者實作一個異步回應,但沒法細粒度地控制回應,所以下文的示例中,我選擇了 AsyncContext,

五 配置中心長輪詢實作

1 客戶端實作

@Slf4j
public class ConfigClient {

    private CloseableHttpClient httpClient;
    private RequestConfig requestConfig;

    public ConfigClient() {
        // ① httpClient 客戶端超時時間要大于長輪詢約定的超時時間
        this.requestConfig = RequestConfig.custom().setSocketTimeout(40000).build();
        this.httpClient = HttpClientBuilder.create().setDefaultRequestConfig(this.requestConfig).build();
    }

    @SneakyThrows
    public void longPolling(String url, String dataId) {
        String endpoint = url + "?dataId=" + dataId;
        HttpGet request = new HttpGet(endpoint);
        CloseableHttpResponse response = httpClient.execute(request);
        switch (response.getStatusLine().getStatusCode()) {
            case 200: {
                BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
                StringBuilder result = new StringBuilder();
                String line;
                while ((line = rd.readLine()) != null) {
                    result.append(line);
                }
                response.close();
                String configInfo = result.toString();
                log.info("dataId: [{}] changed, receive configInfo: {}", dataId, configInfo);
                longPolling(url, dataId);
                break;
            }
            // ② 304 回應碼標記配置未變更
            case 304: {
                log.info("longPolling dataId: [{}] once finished, configInfo is unchanged, longPolling again", dataId);
                longPolling(url, dataId);
                break;
            }
            default: {
                throw new RuntimeException("unExcepted HTTP status code");
            }
        }

    }

    public static void main(String[] args) {
        // httpClient 會列印很多 debug 日志,關閉掉
        Logger logger = (Logger) LoggerFactory.getLogger("org.apache.http");
        logger.setLevel(Level.INFO);
        logger.setAdditive(false);

        ConfigClient configClient = new ConfigClient();
        // ③ 對 dataId: user 進行配置監聽
        configClient.longPolling("http://127.0.0.1:8080/listener", "user");
    }
}

主要有三個注意點:

  • RequestConfig.custom().setSocketTimeout(40000).build() :httpClient 客戶端超時時間要大于長輪詢約定的超時時間,很好理解,不然還沒等服務端回傳,客戶端會自行斷開 HTTP 連接,
  • response.getStatusLine().getStatusCode() == 304 :前文介紹過,約定使用 304 回應碼來標識配置未發生變更,客戶端繼續發起長輪詢,
  • configClient.longPolling("http://127.0.0.1:8080/listener", "user"):在示例中,我們處于簡單考慮,僅僅啟動一個客戶端,對單一的 dataId:user 進行監聽(注意,需要先啟動 server 端),

2 服務端實作

@RestController
@Slf4j
@SpringBootApplication
public class ConfigServer {

    @Data
    private static class AsyncTask {
        // 長輪詢請求的背景關系,包含請求和回應體
        private AsyncContext asyncContext;
        // 超時標記
        private boolean timeout;

        public AsyncTask(AsyncContext asyncContext, boolean timeout) {
            this.asyncContext = asyncContext;
            this.timeout = timeout;
        }
    }

    // guava 提供的多值 Map,一個 key 可以對應多個 value
    private Multimap<String, AsyncTask> dataIdContext = Multimaps.synchronizedSetMultimap(HashMultimap.create());

    private ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("longPolling-timeout-checker-%d").build();
    private ScheduledExecutorService timeoutChecker = new ScheduledThreadPoolExecutor(1, threadFactory);

    // 配置監聽接入點
    @RequestMapping("/listener")
    public void addListener(HttpServletRequest request, HttpServletResponse response) {

        String dataId = request.getParameter("dataId");

        // 開啟異步
        AsyncContext asyncContext = request.startAsync(request, response);
        AsyncTask asyncTask = new AsyncTask(asyncContext, true);

        // 維護 dataId 和異步請求背景關系的關聯
        dataIdContext.put(dataId, asyncTask);

        // 啟動定時器,30s 后寫入 304 回應
        timeoutChecker.schedule(() -> {
            if (asyncTask.isTimeout()) {
                dataIdContext.remove(dataId, asyncTask);
                response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                asyncContext.complete();
            }
        }, 30000, TimeUnit.MILLISECONDS);
    }

    // 配置發布接入點
    @RequestMapping("/publishConfig")
    @SneakyThrows
    public String publishConfig(String dataId, String configInfo) {
        log.info("publish configInfo dataId: [{}], configInfo: {}", dataId, configInfo);
        Collection<AsyncTask> asyncTasks = dataIdContext.removeAll(dataId);
        for (AsyncTask asyncTask : asyncTasks) {
            asyncTask.setTimeout(false);
            HttpServletResponse response = (HttpServletResponse) asyncTask.getAsyncContext().getResponse();
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().println(configInfo);
            asyncTask.getAsyncContext().complete();
        }
        return "success";
    }

    public static void main(String[] args) {
        SpringApplication.run(ConfigServer.class, args);
    }

}

對上述實作的一些說明:

  • @RequestMapping("/listener") ,配置監聽接入點,也是長輪詢的入口,在獲取 dataId 之后,使用 request.startAsync 將請求設定為異步,這樣在方法結束后,不會占用 Tomcat 的執行緒池,
  • dataIdContext.put(dataId, asyncTask) 會將 dataId 和異步請求背景關系給關聯起來,方便配置發布時,拿到對應的背景關系,注意這里使用了一個 guava 提供的資料結構 Multimap<String, AsyncTask> dataIdContext ,它是一個多值 Map,一個 key 可以對應多個 value,你也可以理解為 Map<String,List> ,但使用 Multimap 維護起來可以更方便地處理一些并發邏輯,至于為什么會有多值,很好理解,因為配置中心的 Server 端會接受來自多個客戶端對同一個 dataId 的監聽,
  • timeoutChecker.schedule() 啟動定時器,30s 后寫入 304 回應,再結合之前客戶端的邏輯,接收到 304 之后,會重新發起長輪詢,形成一個回圈,
  • @RequestMapping("/publishConfig") ,配置發布的入口,配置變更后,根據 dataId 一次拿出所有的長輪詢,為之寫入變更的回應,同時不要忘記取消定時任務,至此,完成了一個配置變更后推送的流程,

3 啟動配置監聽

先啟動 ConfigServer,再啟動 ConfigClient,客戶端列印長輪詢的日志如下:

發布一條配置:

curl -X GET "localhost:8080/publishConfig?dataId=user&configInfo=helloworld"

服務端列印日志如下:

客戶端接受配置推送:

六 實作細節思考

為什么需要定時器回傳 304?

上述的實作中,服務端采用了一個定時器,在配置未發生變更時,定時回傳 304,客戶端接收到 304 之后,重新發起長輪詢,在前文,已經解釋過了為什么需要超時后重新發起長輪詢,而不是由服務端一直 hold,直到配置變更再回傳,但可能有讀者還會有疑問,為什么不由客戶端控制超時,服務端去除掉定時器,這樣客戶端超時后重新發起下一次長輪詢,這樣的設計不是更簡單嗎?無論是 Nacos 還是 Apollo 都有這樣的定時器,而不是靠客戶端控制超時,這樣做主要有兩點考慮:

  • 和真正的客戶端超時區分開,
  • 僅僅使用例外(Exception)來表達例外流,而不應該用例外來表達正常的業務流,304 不是超時例外,而是長輪詢中配置未變更的一種正常流程,不應該使用超時例外來表達,

客戶端超時需要單獨配置,且需要比服務端長輪詢的超時要長,正如上述的 demo 中客戶端超時設定的是 40s,服務端判斷一次長輪詢超時是 30s,這兩個值在 Nacos 中默認是 30s 和 29.5s,在 Apollo 中默認是是 90s 和 60s,

長輪詢包含多組 dataId

在上述的 demo 中,一個 dataId 會發起一次長輪詢,在實際配置中心的設計中肯定不能這樣設計,一般的優化方式是,一批 dataId 組成一個組批量包含在一個長輪詢任務中,在 Nacos 中,按照 3000 個 dataId 為一組包裝成一個長輪詢任務,

七 長輪詢和長連接

講完實作細節,本文最核心的部分已經介紹完了,再回到最前面提到的資料互動模式上提到的推模型和拉模型,其實在寫這篇文章時,我曾經問過交流群中的小伙伴們“配置中心實作動態推送的原理”,他們中絕大多數人認為是長連接的推模型,然而事實上,主流的配置中心幾乎都是使用了本文介紹的長輪詢方案,這又是為什么呢?

我也翻閱了不少博客,顯然他們給出的理由并不能說服我,我嘗試著從自己的角度分析了一下這個既定的事實:

  • 長輪詢實作起來比較容易,完全依賴于 HTTP 便可以實作全部邏輯,而 HTTP 是最能夠被大眾接受的通信方式,
  • 長輪詢使用 HTTP,便于多語言客戶端的撰寫,大多數語言都有 HTTP 的客戶端,

那么長連接是不是真的就不適合用于配置中心場景呢?有人可能會認為維護一條長連接會消耗大量資源,而長輪詢可以提升系統的吞吐量,而在配置中心場景,這一假設并沒有實際的壓測資料能夠論證,

另外,翻閱了一下 Nacos 2.0 的 milestone,我發現了一個有意思的規劃,Nacos 的注冊中心(目前是短輪詢 + udp 推送)和配置中心(目前是長輪詢)都有計劃改造為長連接模式,

再回過頭來看,長輪詢實作已經將配置中心這個組件支撐的足夠好了,替換成長連接,一定需要找到合適的理由才行,

八 總結

本文介紹了長輪詢、輪詢、長連接這幾種資料互動模型的差異性,

分析了 Nacos 和 Apollo 等主流配置中心均是通過長輪詢的方式實作配置的實時推送的,實時感知建立在客戶端拉的基礎上,因為本質上還是通過 HTTP 進行的資料互動,之所以有“推”的感覺,是因為服務端 hold 住了客戶端的回應體,并且在配置變更后主動寫入了回傳 response 物件再進行回傳,



原文鏈接:認識長輪詢:配置中心是如何實作推送的?

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/390236.html

標籤:其他

上一篇:Python游戲開發,pygame模塊,Python實作2048小游戲

下一篇:如何從地圖類創建字典?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more