企業(yè)微信大規(guī)模組織架構(gòu)性能優(yōu)化實(shí)踐(企業(yè)微信大規(guī)模組織架構(gòu)性能優(yōu)化實(shí)踐報(bào)告)
作者:yecong,騰訊 WXG 客戶端開(kāi)發(fā)工程師
本文主要講述企業(yè)微信大規(guī)模組織架構(gòu)(后文簡(jiǎn)稱(chēng)為大架構(gòu))的性能優(yōu)化過(guò)程。分成兩部分講述,第一部分是短線迭代的優(yōu)化,主要是并發(fā)性能的優(yōu)化。第二部分是長(zhǎng)線迭代的優(yōu)化,主要是從業(yè)務(wù)模式上做了根本性優(yōu)化。
一、并發(fā)性能優(yōu)化
1.1 背景
當(dāng)私有化的組織架構(gòu)上升到100W的量級(jí)時(shí),出現(xiàn)了嚴(yán)重影響組織架構(gòu)使用的問(wèn)題:打開(kāi)二級(jí)部門(mén)時(shí),加載緩慢。如圖所示,loading可能持續(xù)一分鐘以上。
問(wèn)題:打開(kāi)二級(jí)部門(mén)加載緩慢
1.2 分析
我們分析一下加載二級(jí)部門(mén)的流程,下面是加載二級(jí)部門(mén)的流程圖。
- 如果從來(lái)沒(méi)加載過(guò)該部門(mén),需要從服務(wù)端拉取部門(mén)下的節(jié)點(diǎn)詳情。這里是因?yàn)橹拔覀円呀?jīng)做了優(yōu)化,首次登錄時(shí)只拉取了部門(mén)的節(jié)點(diǎn)ID,沒(méi)有拉取詳情。
- 如果加載過(guò)該部門(mén),就直接從DB讀取該部門(mén)的數(shù)據(jù),然后返回UI展示。
當(dāng)只有一條DB線程時(shí),組織架構(gòu)更新的任務(wù),可能會(huì)插入到加載二級(jí)部門(mén)的任務(wù)的前面。而在百萬(wàn)級(jí)別的組織架構(gòu)中,全量更新的DB任務(wù)有可能比較久,全量更新的插入或者更新節(jié)點(diǎn)可能比較多,導(dǎo)致本來(lái)很快可以完成的二級(jí)部門(mén)加載任務(wù),要排隊(duì)比較久才能執(zhí)行完。
下面是組織架構(gòu)全量更新的流程圖。
全量更新
在這里,讀寫(xiě)并發(fā)上出現(xiàn)了明顯的瓶頸。原因總結(jié)如下:
- 加載二級(jí)部門(mén)和全量更新共用一條DB線程
- 當(dāng)全量更新大量節(jié)點(diǎn)時(shí),全量更新的低優(yōu)先級(jí)任務(wù)卡住加載二級(jí)部門(mén)的高優(yōu)先級(jí)任務(wù)
1.3 方案
讀寫(xiě)分離為了提高組織架構(gòu)在大規(guī)模數(shù)據(jù)下的讀寫(xiě)并發(fā)性能,我們開(kāi)啟了wal模式,把讀寫(xiě)任務(wù)分別放在不同的線程中執(zhí)行。
針對(duì)加載二級(jí)部門(mén)的流程,可以在讀線程中讀取部門(mén)的詳情節(jié)點(diǎn),而組織架構(gòu)更新可以在寫(xiě)線程中單獨(dú)執(zhí)行。
由于加載二級(jí)部門(mén)的原流程是拉取數(shù)據(jù)、寫(xiě)入DB、再?gòu)腄B讀取數(shù)據(jù),而且WAL只支持一寫(xiě)多讀,因此我們調(diào)整了緩存策略,把保存節(jié)點(diǎn)詳情的寫(xiě)任務(wù)延遲到流程最后,優(yōu)先構(gòu)造了cache返回UI。這樣從DB中讀出數(shù)據(jù)的讀任務(wù),就不需要等待保存節(jié)點(diǎn)詳情的寫(xiě)任務(wù)。避免了保存節(jié)點(diǎn)的寫(xiě)任務(wù)再次被其他寫(xiě)任務(wù)阻塞,讀任務(wù)又被保存節(jié)點(diǎn)的寫(xiě)任務(wù)阻塞,退化成串行操作。
WAL機(jī)制的原理
調(diào)用方修改的數(shù)據(jù)并不直接寫(xiě)入到數(shù)據(jù)庫(kù)文件中,而是寫(xiě)入到另外一個(gè)稱(chēng)為WAL的文件中,然后在隨后的某個(gè)時(shí)間點(diǎn)被寫(xiě)回到數(shù)據(jù)庫(kù)文件中。在這個(gè)時(shí)間點(diǎn)的回寫(xiě)操作,會(huì)降低數(shù)據(jù)庫(kù)當(dāng)時(shí)的讀寫(xiě)性能。但是通過(guò)設(shè)置對(duì)WAL文件大小的限制,這種性能影響是可控的。實(shí)際上線后也沒(méi)有遇到由于checkpoint同步導(dǎo)致數(shù)據(jù)庫(kù)慢的反饋。
緩存策略
寫(xiě)策略的步驟:先更新緩存中的數(shù)據(jù),再更新數(shù)據(jù)庫(kù)中的數(shù)據(jù)。
讀策略的步驟:
如果讀取的數(shù)據(jù)命中了緩存,則直接返回?cái)?shù)據(jù);如果讀取的數(shù)據(jù)沒(méi)有命中緩存,則從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù),然后將數(shù)據(jù)寫(xiě)入到緩存,并且返回給UI。
方案總結(jié)
方案優(yōu)點(diǎn)缺點(diǎn)1. 開(kāi)啟WAL,拆分DB讀寫(xiě)線程 2. 緩存策略適配:先保證UI展示,再讓數(shù)據(jù)落地1. 讀寫(xiě)并發(fā)
2. 最大化利用緩存WAL文件同步回?cái)?shù)據(jù)庫(kù)文件的時(shí)候,會(huì)降低當(dāng)時(shí)的讀寫(xiě)性能。
1.4 效果
在優(yōu)化前,只有52%的用戶能在1s內(nèi)加載完二級(jí)部門(mén)。而上線之后,93%的用戶都能在1s內(nèi)打開(kāi)二級(jí)部門(mén)。耗時(shí)小于1s的用戶占比提升40%!
二、業(yè)務(wù)模式優(yōu)化
2.1 問(wèn)題
2.1.1 背景
當(dāng)業(yè)務(wù)進(jìn)一步發(fā)展時(shí),我們預(yù)估未來(lái)將要到達(dá)300W量級(jí)的組織架構(gòu)。于是我們就開(kāi)始提前規(guī)劃如何能在組織架構(gòu)數(shù)量一直增長(zhǎng)的情況下,還能讓組織架構(gòu)流暢好用。
2.1.2 問(wèn)題
- 選人控件閃退和ANR
- 組織架構(gòu)全量更新閃退
在300w的組織架構(gòu)環(huán)境中,舊的組織架構(gòu)加載方案,在全量更新、選人控件中均出現(xiàn)了占用內(nèi)存過(guò)大甚至閃退的問(wèn)題。而且舊方案的加載時(shí)間會(huì)隨著節(jié)點(diǎn)數(shù)量的增加,不可避免地成正比增長(zhǎng)。
2.1.3 分析
當(dāng)前方案的耗時(shí)、內(nèi)存占用與用戶組織架構(gòu)的大小成正比,單點(diǎn)優(yōu)化無(wú)法滿足組織架構(gòu)持續(xù)增長(zhǎng)的需求。具體來(lái)說(shuō),會(huì)造成下面的一些問(wèn)題:
- 選人控件會(huì)加載全量的組織架構(gòu)ID樹(shù),數(shù)量過(guò)多時(shí)容易發(fā)生閃退和ANR。
- 組織架構(gòu)全量更新占用內(nèi)存過(guò)大,造成閃退。
因此,我們需要一個(gè)新的業(yè)務(wù)模式,即便總的組織架構(gòu)規(guī)模一直上漲的情況下,也能維持較好的性能。
2.2 方案比較
比較容易想到的一個(gè)方案是web加載的模式,不保存本地?cái)?shù)據(jù),但是體驗(yàn)比較差,每層都會(huì)出loading。
聯(lián)系到我們的具體業(yè)務(wù),由于私有化對(duì)不同的部門(mén),劃分出了具有意義的獨(dú)立組織機(jī)構(gòu)–單位。單位是具有管理意義的部門(mén),不同單位可以獨(dú)立加載。而每個(gè)人,也擁有主單位和兼崗單位。所以可以按照單位加載的方式,從根本上解決目前組織架構(gòu)面臨的瓶頸。
按單位加載,可以簡(jiǎn)單理解為按部門(mén)加載。
方案缺點(diǎn)優(yōu)點(diǎn)Web加載模式:不保存本地?cái)?shù)據(jù)體驗(yàn)太差,每層都要出loading理論上可支持的數(shù)據(jù)量上限最大單位加載模式:按單位加載需要推廣到企業(yè)符合業(yè)務(wù)邏輯,可支持到500萬(wàn)量級(jí)
概念定義
- 單位:政府行政組織結(jié)構(gòu)中的職能部門(mén),組建架構(gòu)并承擔(dān)對(duì)應(yīng)責(zé)任
- 主單位:【我】所在的單位
- 其他單位:除了【我】所在的其他單位
- 骨架:通訊錄骨架包含了所有的單位節(jié)點(diǎn)
- 普通部門(mén):不屬于任何單位的部門(mén)節(jié)點(diǎn)
下圖是組織架構(gòu)樹(shù)的示意圖,藍(lán)色節(jié)點(diǎn)是優(yōu)先加載的本單位,灰色節(jié)點(diǎn)是其他單位,紅色節(jié)點(diǎn)是骨架。不同的單位獨(dú)立加載。
2.3 按單位加載
2.3.1 加載策略
接下來(lái)我們看看加載策略。
第一是對(duì)自己所在的主單位(藍(lán)色節(jié)點(diǎn)),每次喚醒時(shí)就會(huì)更新,跟舊組織架構(gòu)的邏輯類(lèi)似,但是會(huì)限制拉取節(jié)點(diǎn)的數(shù)量。
第二對(duì)于其他單位(灰色節(jié)點(diǎn)),點(diǎn)擊到該單位時(shí)才會(huì)拉取,2個(gè)小時(shí)后會(huì)淘汰刪除,避免數(shù)據(jù)表過(guò)大。
第三對(duì)于骨架(紅色節(jié)點(diǎn)),會(huì)全量加載節(jié)點(diǎn)ID,再拉取節(jié)點(diǎn)詳情。
拉取策略限制了能夠拉取的節(jié)點(diǎn)詳情數(shù)量,如果單位節(jié)點(diǎn)數(shù)量超過(guò)了限制,首先拉取全量ID,再按照優(yōu)先規(guī)則,拉取配置的節(jié)點(diǎn)詳請(qǐng)數(shù)量。
2.3.2 加載流程
加載的流程是先拉取自己的單位列表,然后拉取每個(gè)單位的全量通訊錄ID,再按照后臺(tái)策略,拉取所需的詳細(xì)節(jié)點(diǎn),最后拉取骨架。
- 如果點(diǎn)擊到主單位:
- 如果只有ID沒(méi)有節(jié)點(diǎn),會(huì)立刻拉取節(jié)點(diǎn)詳情返回界面。
- 如果ID和節(jié)點(diǎn)詳情都有,可以直接返回UI展示,然后延遲刷新節(jié)點(diǎn)。
- 如果是點(diǎn)擊到其他單位,可能出現(xiàn)ID和詳情都沒(méi)有的情況,需要拉取其他單位的節(jié)點(diǎn),界面loading等待。
- 如果是骨架,就一定有節(jié)點(diǎn)和詳情,只需要延遲刷新。
2.4 跨平臺(tái)設(shè)計(jì):分層設(shè)計(jì)
接下來(lái)我們看看如何分層。在500萬(wàn)量級(jí)的大規(guī)模組織架構(gòu)下,移動(dòng)端和pc端都出現(xiàn)了組織架構(gòu)卡頓、閃退的問(wèn)題,所以我們希望能夠開(kāi)發(fā)一套各端共用的邏輯,統(tǒng)一維護(hù)。
第一是要抽取公共的基礎(chǔ)庫(kù),包括boost庫(kù)、任務(wù)框架、線程管理框架等。
第二是設(shè)計(jì)公共的數(shù)據(jù)結(jié)構(gòu)。
第三,因?yàn)椴煌说木W(wǎng)絡(luò)庫(kù)差異比較大,這里不好完全共用,所以需要抽取網(wǎng)絡(luò)任務(wù)接口,由各端獨(dú)立實(shí)現(xiàn)。
具體到框架圖,我們從下往上看。底層是基礎(chǔ)庫(kù),接著是C 實(shí)現(xiàn)的跨平臺(tái)業(yè)務(wù)層,Service層是移動(dòng)端和pc端分開(kāi)實(shí)現(xiàn),主要是做接口調(diào)用和回調(diào)的簡(jiǎn)單封裝,上層則各端界面實(shí)現(xiàn)。上層界面為了兼容新舊兩套組織架構(gòu),也做了接口抽象,可以通過(guò)開(kāi)關(guān)自由切換。這樣優(yōu)點(diǎn)就是有統(tǒng)一的業(yè)務(wù)邏輯代碼、DB設(shè)計(jì)和線程管理。
- 關(guān)鍵點(diǎn)
- 抽取公共基礎(chǔ)庫(kù)
- 抽象公共的數(shù)據(jù)結(jié)構(gòu)
- 抽象網(wǎng)絡(luò)層和數(shù)據(jù)庫(kù)層接口
- 優(yōu)點(diǎn)
- 統(tǒng)一的業(yè)務(wù)邏輯代碼、DB設(shè)計(jì)、線程管理
2.5 跨平臺(tái)設(shè)計(jì):架構(gòu)設(shè)計(jì)
在具體實(shí)現(xiàn)之前,我們來(lái)看看架構(gòu)設(shè)計(jì)的一些概念。
2.5.1 架構(gòu)整潔之道
業(yè)務(wù)實(shí)體和用例
關(guān)鍵業(yè)務(wù)邏輯和關(guān)鍵業(yè)務(wù)數(shù)據(jù)是緊密相關(guān)的,所以它們很適合被放在同一個(gè)對(duì)象中處理。我們將這種對(duì)象稱(chēng)為“業(yè)務(wù)實(shí)體”。業(yè)務(wù)實(shí)體這個(gè)概念中應(yīng)該只有業(yè)務(wù)邏輯,沒(méi)有別的,與數(shù)據(jù)庫(kù)、用戶界面、第三方框架等內(nèi)容無(wú)關(guān)。
用例所描述的是某種特定應(yīng)用情景下的業(yè)務(wù)邏輯,可以理解為:輸入 業(yè)務(wù)實(shí)體 輸出 = 用例
軟件架構(gòu)
軟件的系統(tǒng)架構(gòu)應(yīng)該為該系統(tǒng)的用例提供支持。一個(gè)良好的架構(gòu)設(shè)計(jì)應(yīng)該圍繞著用例來(lái)展開(kāi),這樣的架構(gòu)設(shè)計(jì)可以在脫離框架、工具以及使用環(huán)境的情況下完整地描述用例。
整潔架構(gòu)
下圖的同心圓分別代表了軟件系統(tǒng)中的不同層次,越靠近中心,其所在的軟件層次就越高?;旧希鈱訄A代表的是機(jī)制,內(nèi)層圓代表的是策略。
這其中有一條貫穿整個(gè)架構(gòu)設(shè)計(jì)的規(guī)則,即依賴(lài)關(guān)系規(guī)則:
源碼中的依賴(lài)關(guān)系必須只指向同心圓的內(nèi)層,即由底層機(jī)制指向高層策略。依賴(lài)關(guān)系與數(shù)據(jù)流控制流脫鉤,而與組件所在層次掛鉤,始終從低層次指向高層次。
2.5.2 我們的架構(gòu)
我們的類(lèi)圖與架構(gòu)設(shè)計(jì)概念的對(duì)應(yīng)關(guān)系如下:
- 業(yè)務(wù)實(shí)體:ArchTask
- 用例:ArchProto
- 模型層,即最外層:各種第三方框架,如DbInterface(數(shù)據(jù)庫(kù)模塊)、ArchLogicHandler(網(wǎng)絡(luò)模塊)等。我們從一次具體的業(yè)務(wù)調(diào)用流程來(lái)看看這樣設(shè)計(jì)的意義。下面是從UI發(fā)起的一次架構(gòu)更新流程,大家可以主要關(guān)注控制流是怎么穿越各層的邊界:控制流從最外層的用戶界面開(kāi)始,穿過(guò)用例(Arch),最后調(diào)用最外層的組件:網(wǎng)絡(luò)模塊和數(shù)據(jù)庫(kù)模塊。但是我們?cè)创a中的依賴(lài)方向卻都是向內(nèi)指向用例的。
這里,我們采用的是依賴(lài)反轉(zhuǎn)原則(DIP)來(lái)解決這種相反性。我們可以通過(guò)調(diào)整代碼中的接口和繼承關(guān)系,利用源碼中的依賴(lài)關(guān)系,限制控制流只能在正確的地方跨域架構(gòu)邊界。
在上面的流程圖中,主要有兩個(gè)應(yīng)用依賴(lài)反轉(zhuǎn)原則的地方:
一、CalcPreLoadArchIDs是從SyncUnitArchTask(業(yè)務(wù)實(shí)體)調(diào)用調(diào)用到ArchProto(用例)。業(yè)務(wù)實(shí)體這樣的高層概念,是無(wú)須了解像用例這樣的底層概念的。反之,底層業(yè)務(wù)用例卻需要了解高層的業(yè)務(wù)實(shí)體。所以在SyncUnitArchTask中,其實(shí)是通過(guò)調(diào)用ArchProto的接口來(lái)調(diào)用CalcPreLoadArchIDs。SyncUnitArchTask中的調(diào)用代碼如下:
arch_service_context_->CalcPreLoadArchIDs(unit_id_, arch_service_context_->GetCurrentVid(), other_unit_click_partyid_, vecHashNode, all_tmp_ids, arch_ids, ptr_map_);
ArchProto會(huì)在Task初始化時(shí),把自己設(shè)置進(jìn)Task中,給各類(lèi)型的Task反向調(diào)用。
class ArchProto : public ArchServiceContext{...};
二、最外層的模型層一般是由工具、數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)框架等組成的??蚣芘c驅(qū)動(dòng)程序?qū)又邪怂械膶?shí)現(xiàn)細(xì)節(jié)。從系統(tǒng)架構(gòu)的角度看,工具通常是無(wú)關(guān)緊要的,因?yàn)檫@只是一個(gè)底層的實(shí)現(xiàn)細(xì)節(jié),一種達(dá)成目標(biāo)的手段。當(dāng)Task需要調(diào)用網(wǎng)絡(luò)模塊收發(fā)請(qǐng)求或者調(diào)用數(shù)據(jù)庫(kù)模塊獲取數(shù)據(jù)時(shí),為了避免內(nèi)層策略依賴(lài)外層機(jī)制,Task只會(huì)調(diào)用外層工具的接口層,而不會(huì)依賴(lài)實(shí)現(xiàn)細(xì)節(jié)。這樣的架構(gòu)設(shè)計(jì)給我們帶來(lái)的好處是,我們可以輕松替換框架,而不影響內(nèi)層策略。比如在桌面端,我們會(huì)有另外一套完全不同的網(wǎng)絡(luò)模塊實(shí)現(xiàn),只需要掛接不同的網(wǎng)絡(luò)實(shí)現(xiàn)子類(lèi),我們就可以在桌面端復(fù)用新的大架構(gòu)模塊。
良好的架構(gòu)設(shè)計(jì)應(yīng)該盡可能地允許用戶推遲和延后決定采用什么框架、數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)框架以及其他與環(huán)境相關(guān)的工具。總之,良好的架構(gòu)設(shè)計(jì)應(yīng)該只關(guān)注用例,并能將它們與其他的周邊因素隔離。
2.5.3 新舊組織架構(gòu)模塊的交互
大架構(gòu)跨平臺(tái)層,跟原來(lái)的組織架構(gòu)模塊是怎么交互的呢?原來(lái)的組織架構(gòu)的數(shù)據(jù)表主要分成三部分:部門(mén)表、人員信息表、部門(mén)人員關(guān)系表,而出現(xiàn)性能問(wèn)題的主要在于關(guān)系表上。所以數(shù)據(jù)設(shè)計(jì)上,人員信息保留在原組織架構(gòu)底層,部門(mén)人員關(guān)系表、部門(mén)表在大架構(gòu)底層。
- 表結(jié)構(gòu)設(shè)計(jì):
- 主要組成:人員信息表、部門(mén)表、部門(mén)人員關(guān)系表。
- 大架構(gòu)底層保存部門(mén)和部門(mén)人員關(guān)系表,人員信息保留在原組織架構(gòu)底層。
- 大架構(gòu)底層與原組織架構(gòu)底層的業(yè)務(wù)關(guān)聯(lián):
- 人員展示的部門(mén)鏈路如何獲取?—-從大架構(gòu)底層獲取,因?yàn)殛P(guān)系表存放在大架構(gòu)底層。
- 搜索如何做?—- 部門(mén)名字保存到原組織架構(gòu)底層,復(fù)用原組織架構(gòu)底層的索引建立邏輯。
2.6 雙DB切換
2.6.1 舊的讀寫(xiě)表切換方式
舊方案里組織架構(gòu)的全量更新流程
當(dāng)后臺(tái)告訴客戶端需要全量更新時(shí),客戶端會(huì)將所有節(jié)點(diǎn)標(biāo)為待刪除,然后同步后臺(tái)的節(jié)點(diǎn),清除待刪除標(biāo)記。同步完成后,將寫(xiě)表的數(shù)據(jù)同步到讀表,更新版本號(hào)。最后UI就可以從讀表中讀取到最新的數(shù)據(jù)。
而之前通過(guò)用戶日志案例分析,最長(zhǎng)的耗時(shí)主要是在將寫(xiě)表的數(shù)據(jù)拷貝到讀表上面。在這個(gè)過(guò)程中,大架構(gòu)下部分用戶的日志里有更新57w節(jié)點(diǎn)的數(shù)據(jù)用了2個(gè)半小時(shí)的情況,而且這個(gè)步驟是原子操作,如果不能夠一次完成,下次還得重新執(zhí)行。
原有流程里,讀表和寫(xiě)表是固定的,導(dǎo)致全量更新需要等讀表同步完數(shù)據(jù),界面才能讀到新數(shù)據(jù)。
- 分析:寫(xiě)表同步數(shù)據(jù)到讀表耗時(shí)很久,當(dāng)全量更新時(shí),如果有大量節(jié)點(diǎn)需要更新,會(huì)耗時(shí)很長(zhǎng)。
- 缺點(diǎn):寫(xiě)表和讀表固定,全量更新需要等數(shù)據(jù)同步完成,界面才能讀取到新數(shù)據(jù)。
2.6.2 新的雙DB切換方式
針對(duì)舊方案中讀寫(xiě)表同步過(guò)久的問(wèn)題,大架構(gòu)方案里我們換成了雙DB切換的模式。下面是我們的狀態(tài)機(jī)設(shè)計(jì)和業(yè)務(wù)代碼獲取表名的邏輯。
這樣修改之后,不需要等讀寫(xiě)表同步完,UI就可以讀取到最新數(shù)據(jù)。而同步的過(guò)程可以在后臺(tái)慢慢完成,并且不會(huì)受原子性操作的限制。業(yè)務(wù)代碼獲取讀表的邏輯,也收攏到了一個(gè)函數(shù)。
因?yàn)閱挝荒J较?,每個(gè)單位的節(jié)點(diǎn)數(shù)量都不會(huì)很多,而且大多數(shù)用戶只會(huì)加載日常有交流的幾個(gè)單位,所以讀寫(xiě)表同步這里,我們采用了把原表刪掉,全量拷貝的方式。
2.7 效果
對(duì)于耗時(shí),優(yōu)化前使用全量加載的方式使得耗時(shí)很長(zhǎng),而優(yōu)化后采用的“本單位 骨架”的預(yù)加載邏輯使得加載耗時(shí)大幅度減小。優(yōu)化后的內(nèi)存占用大小在各場(chǎng)景下均有減小,通訊錄頁(yè)面的流暢度也得到了一定的提升。
一、耗時(shí)
二、CPU占用率
三、內(nèi)存占用大小
四、卡頓
作者:yecong
來(lái)源:微信公眾號(hào):騰訊技術(shù)工程
出處:https://mp.weixin.qq.com/s/eK47AzCSSf8-W3wZdjrXXQ