主頁 > 後端開發 > Redis 異步客戶端選型及落地實踐

Redis 異步客戶端選型及落地實踐

2023-02-09 07:18:57 後端開發

作者:京東科技 王晨

Redis異步客戶端選型及落地實踐

可視化服務編排系統是能夠通過線上可視化拖拽、配置的方式完成對介面的編排,可在線完成服務的除錯、測驗,實作業務需求的交付,詳細內容可參考:https://mp.weixin.qq.com/s/5oN9JqWN7n-4Zv6B9K8kWQ,

為了支持更加廣泛的業務場景,可視化編排系統近期需要支持對快取的操作功能,為保證編排系統的性能,服務的執行程序采用了異步的方式,因此我們考慮使用Redis的異步客戶端來完成對快取的操作,

Redis客戶端

Jedis/Lettuce

Redis官方推薦的Redis客戶端有Jedis、Lettuce等等,其中Jedis 是老牌的 Redis 的 Java 實作客戶端,提供了比較全面的 Redis 命令的支持,在spring-boot 1.x 默認使用Jedis,

但是Jedis使用阻塞的 IO,且其方法呼叫都是同步的,程式流需要等到 sockets 處理完 IO 才能執行,不支持異步,在并發場景下,使用Jedis客戶端會耗費較多的資源,

此外,Jedis 客戶端實體不是執行緒安全的,要想保證執行緒安全,必須要使用連接池,每個執行緒需要時從連接池取出連接實體,完成操作后或者遇到例外歸還實體,當連接數隨著業務不斷上升時,對物理連接的消耗也會成為性能和穩定性的潛在風險點,因此在spring-boot 2.x中,redis客戶端默認改用了Lettuce,

我們可以看下 Spring Data Redis 幫助檔案給出的對比表格,里面詳細地記錄了兩個主流Redis客戶端之間的差異,

異步客戶端Lettuce

Spring Boot自2.0版本開始默認使用Lettuce作為Redis的客戶端,Lettuce客戶端基于Netty的NIO框架實作,對于大多數的Redis操作,只需要維持單一的連接即可高效支持業務端的并發請求 —— 這點與Jedis的連接池模式有很大不同,同時,Lettuce支持的特性更加全面,且其性能表現并不遜于,甚至優于Jedis,

Netty是由JBOSS提供的一個java開源框架,現為 Github上的獨立專案,Netty提供異步的、事件驅動的網路應用程式框架和工具,用以快速開發高性能、高可靠性的網路服務器和客戶端程式,

也就是說,Netty 是一個基于NIO的客戶、服務器端的編程框架,使用Netty 可以確保你快速和簡單的開發出一個網路應用,例如實作了某種協議的客戶、服務端應用,Netty相當于簡化和流線化了網路應用的編程開發程序,例如:基于TCP和UDP的socket服務開發,

上圖展示了Netty NIO的核心邏輯,NIO通常被理解為non-blocking I/O的縮寫,表示非阻塞I/O操作,圖中Channel表示一個連接通道,用于承載連接管理及讀寫操作;EventLoop則是事件處理的核心抽象,一個EventLoop可以服務于多個Channel,但它只會與單一執行緒系結,EventLoop中所有I/O事件和用戶任務的處理都在該執行緒上進行;其中除了選擇器Selector的事件監聽動作外,對連接通道的讀寫操作均以非阻塞的方式進行 —— 這是NIO與BIO(blocking I/O,即阻塞式I/O)的重要區別,也是NIO模式性能優異的原因,

Lettuce憑借單一連接就可以支持業務端的大部分并發需求,這依賴于以下幾個因素的共同作用:

1.Netty的單個EventLoop僅與單一執行緒系結,業務端的并發請求均會被放入EventLoop的任務佇列中,最終被該執行緒順序處理,同時,Lettuce自身也會維護一個佇列,當其通過EventLoop向Redis發送指令時,成功發送的指令會被放入該佇列;當收到服務端的回應時,Lettuce又會以FIFO的方式從佇列的頭部取出對應的指令,進行后續處理,

2.Redis服務端本身也是基于NIO模型,使用單一執行緒處理客戶端請求,雖然Redis能同時維持成百上千個客戶端連接,但是在某一時刻,某個客戶端連接的請求均是被順序處理及回應的,

3.Redis客戶端與服務端通過TCP協議連接,而TCP協議本身會保證資料傳輸的順序性,

如此,Lettuce在保證請求處理順序的基礎上,天然地使用了管道模式(pipelining)與Redis互動 —— 在多個業務執行緒并發請求的情況下,客戶端不必等待服務端對當前請求的回應,即可在同一個連接上發出下一個請求,這在加速了Redis請求處理的同時,也高效地利用了TCP連接的全雙工特性(full-duplex),而與之相對的,在沒有顯式指定使用管道模式的情況下,Jedis只能在處理完某個Redis連接上當前請求的回應后,才能繼續使用該連接發起下一個請求,

在并發場景下,業務系統短時間內可能會發出大量請求,在管道模式中,這些請求被統一發送至Redis服務端,待處理完成后統一回傳,能夠大大提升業務系統的運行效率,突破性能瓶頸,R2M采用了Redis Cluster模式,在通過Lettuce連接R2M之前,應該先對Redis Cluster模式有一定的了解,

Redis Cluster模式

在redis3.0之前,如果想搭建一個集群架構還是挺復雜的,就算是基于一些第三方的中間件搭建的集群總感覺有那么點差強人意,或者基于sentinel哨兵搭建的主從架構在高可用上表現又不是很好,尤其是當資料量越來越大,單純主從結構無法滿足對性能的需求時,矛盾便產生了,

隨著redis cluster的推出,這種海量資料+高并發+高可用的場景真正從根本上得到了有效的支持,

cluster 模式是redis官方提供的集群模式,使用了Sharding 技術,不僅實作了高可用、讀寫分離、也實作了真正的分布式存盤,

集群內部通信

在redis cluster集群內部通過gossip協議進行通信,集群元資料分散的存在于各個節點,通過gossip進行元資料的交換,

不同于zookeeper分布式協調中間件,采用集中式的集群元資料存盤,redis cluster采用分布式的元資料管理,優缺點還是比較明顯的,在redis中集中式的元資料管理類似sentinel主從架構模式,集中式有點在于元資料更新實效性更高,但容錯性不如分布式管理,gossip協議優點在于大大增強集群容錯性,

redis cluster集群中單節點一般配置兩個埠,一個埠如6379對外提供api,另一個一般是加1w,比如16379進行節點間的元資料交換即用于gossip協議通訊,

gossip協議包含多種訊息,如ping pong,meet,fail等,

1.meet:集群中節點通過向新加入節點發送meet訊息,將新節點加入集群中,

2.ping:節點間通過ping命令交換元資料,

3.pong:回應ping,

4.fail:某個節點主觀認為某個節點宕機,會向其他節點發送fail訊息,進行客觀宕機判定,

分片和尋址演算法

hash slot即hash槽,redis cluster采用的正式這種hash槽演算法實作的尋址,在redis cluster中固定的存在16384個hash slot,

如上圖所示,如果我們有三個節點,每個節點都是一主一從的主從結構,redis cluster初始化時會自動均分給每個節點16384個slot,當增加一個節點4,只需要將原來node1~node3節點部分slot上的資料遷移到節點4即可,在redis cluster中資料遷移并不會阻塞主行程,對性能影響是十分有限的,總結一句話就是hash slot演算法有效的減少了當節點發生變化導致的資料漂移帶來的性能開銷,

集群高可用和主備切換

主觀宕機和客觀宕機:

某個節點會周期性的向其他節點發送ping訊息,當在一定時間內未收到pong訊息會主觀認為該節點宕機,即主觀宕機,然后該節點向其他節點發送fail訊息,其他超過半數節點也確認該節點宕機,即客觀宕機,十分類似sentinel的sdown和odown,

客觀宕機確認后進入主備切換階段及從節點選舉,

節點選舉:

檢查每個 slave node 與 master node 斷開連接的時間,如果超過了 cluster-node-timeout * cluster-slave-validity-factor,那么就沒有資格切換成 master,

每個從節點,都根據自己對 master 復制資料的 offset,來設定一個選舉時間,offset 越大(復制資料越多)的從節點,選舉時間越靠前,優先進行選舉,

所有的 master node 開始 slave 選舉投票,給要進行選舉的 slave 進行投票,如果大部分 master node(N/2 + 1)都投票給了某個從節點,那么選舉通過,那個從節點可以切換成 master,

從節點執行主備切換,從節點切換為主節點,

Lettuce的使用

建立連接

使用Lettuce大致分為以下三步:

1.基于Redis連接資訊創建RedisClient

2.基于RedisClient創建StatefulRedisConnection

3.從Connection中獲取Command,基于Command執行Redis命令操作,

由于Lettuce客戶端提供了回應式、同步和異步三種命令,從Connection中獲取Command時可以指定命令型別進行獲取,

在本地創建Redis Cluster集群,設定主從關系如下:

7003(M) --> 7001(S)

7004(M) --> 7002(S)

7005(M) --> 7000(S)

List<RedisURI> servers = new ArrayList<>();
servers.add(RedisURI.create("127.0.0.1", 7000));
servers.add(RedisURI.create("127.0.0.1", 7001));
servers.add(RedisURI.create("127.0.0.1", 7002));
servers.add(RedisURI.create("127.0.0.1", 7003));
servers.add(RedisURI.create("127.0.0.1", 7004));
servers.add(RedisURI.create("127.0.0.1", 7005));
//創建客戶端
RedisClusterClient client = RedisClusterClient.create(servers);
//創建連接
StatefulRedisClusterConnection<String, String> connection = client.connect();
//獲取異步命令
RedisAdvancedClusterAsyncCommands<String, String> commands = connection.async();
//執行GET命令
RedisFuture<String> future = commands.get("test-lettuce-key");
try {
    String result = future.get();
    log.info("Get命令回傳:{}", result);
} catch (Exception e) {
    log.error("Get命令執行例外", e);
}

可以看到成功地獲取到了值,由日志可以看出該請求發送到了7004所在的節點上,順利拿到了對應的值并進行回傳,

作為一個需要長時間保持的客戶端,保持其與集群之間連接的穩定性是至關重要的,那么集群在運行程序中會發生哪些特殊情況呢?作為客戶端又應該如何應對呢?這就要引出智能客戶端(smart client)這個概念了,

智能客戶端

在Redis Cluster運行程序中,所有的資料不是永遠固定地保存在某一個節點上的,比如遇到cluster擴容、節點宕機、資料遷移等情況時,都會導致集群的拓撲結構發生變化,此時作為客戶端需要對這一類情況作出應對,來保證連接的穩定性以及服務的可用性,隨著以上問題的出現,smart client這個概念逐漸走到了人們的視野中,智能客戶端會在內部維護hash槽與節點的映射關系,大家耳熟能詳的Jedis和Lettuce都屬于smart client,客戶端在發送請求時,會先根據CRC16(key)%16384計算key對應的hash槽,通過映射關系,本地就可實作鍵到節點的查找,從而保證IO效率的最大化,

但如果出現故障轉移或者hash槽遷移時,這個映射關系是如何維護的呢?

客戶端重定向

MOVED

當Redis集群發生資料遷移時,當對應的hash槽已經遷移到變的節點時,服務端會回傳一個MOVED重定向錯誤,此時并告訴客戶端這個hash槽遷移后的節點IP和埠是多少;客戶端在接收到MOVED錯誤時,會更新本地的映射關系,并重新向新節點發送請求命令,

ASK

Redis集群支持在線遷移槽(slot)和資料來完成水平伸縮,當slot對應的資料從源節點到目標節點遷移程序中,客戶端需要做到智能識別,保證鍵命令可正常執行,例如當一個slot資料從源節點遷移到目標節點時,期間可能出現一部分資料在源節點,而另一部分在目標節點,如下圖所示

當出現上述情況時,客戶端鍵命令執行流程將發生變化,如下所示:

1)客戶端根據本地slots快取發送命令到源節點,如果存在鍵物件則直 接執行并回傳結果給客戶端

2)如果鍵物件不存在,則可能存在于目標節點,這時源節點會回復 ASK重定向例外,

3)客戶端從ASK重定向例外提取出目標節點資訊,發送asking命令到目標節點打開客戶端連接標識,再執行鍵命令,如果存在則執行,不存在則回傳不存在資訊,

在客戶端收到ASK錯誤時,不會更新本地的映射關系

節點宕機觸發主備切換

上文提到,如果redis集群在運行程序中,某個主節點由于某種原因宕機了,此時就會觸發集群的節點選舉機制,選舉其中一個從節點作為新的主節點,進入主備切換,在主備切換期間,新的節點沒有被選舉出來之前,打到該節點上的請求理論上是無法得到執行的,可能會產生超時錯誤,在主備切換完成之后,集群拓撲更新完成,此時客戶端應該向集群請求新的拓撲結構,并更新至本地的映射表中,以保證后續命令的正確執行,

有意思的是,Jedis在集群主備切換完成之后,是會主動拉取最新的拓撲結構并進行更新的,但是在使用Lettuce時,發現在集群主備切換完成之后,連接并沒有恢復,打到該節點上的命令依舊會執行失敗導致超時,必須要重啟業務程式才能恢復連接,

在使用Lettuce時,如果不進行設定,默認是不會觸發拓撲重繪的,因此在主備切換完成后,Lettuce依舊使用本地的映射表,將請求打到已經掛掉的節點上,就會導致持續的命令執行失敗的情況,

可以通過以下代碼來設定Lettuce的拓撲重繪策略,開啟基于事件的自適應拓撲重繪,其中包括了MOVED、 ASK、PERSISTENT_RECONNECTS等觸發器,當客戶端觸發這些事件,并且持續時間超過設定閾值后,觸發拓撲重繪,也可以通過enablePeriodicRefresh()設定定時重繪,不過建議這個時間不要太短,

// 設定基于事件的自適應重繪策略
ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
        //開啟自適應拓撲重繪
        .enableAllAdaptiveRefreshTriggers()
        //自適應拓撲重繪事件超時時間,超時后進行重繪
        .adaptiveRefreshTriggersTimeout(Duration.ofSeconds(30))
        .build();

redisClusterClient.setOptions(ClusterClientOptions.builder()
        .topologyRefreshOptions(topologyRefreshOptions)
        // redis命令超時時間
        .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(30)))
        .build());

進行以上設定并進行驗證,集群在主備切換完成后,客戶端在段時間內恢復了連接,能夠正常存取資料了,

總結

對于快取的操作,客戶端與集群之間連接的穩定性是保證資料不丟失的關鍵,Lettuce作為熱門的異步客戶端,對于集群中產生的一些突發狀況是具備處理能力的,只不過在使用的時候需要進行設定,本文目的在于將在開發快取操作功能時遇到的問題,以及將一些涉及到的底層知識做一下總結,也希望能給大家一些幫助,

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

標籤:其他

上一篇:ASP.Net Core 教程_編程入門自學教程_菜鳥教程-免費教程分享

下一篇:drf 認證組件、權限組件、頻率組件

標籤雲
其他(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