• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            xiaoxiaoling

            C++博客 首頁 新隨筆 聯(lián)系 聚合 管理
              17 Posts :: 2 Stories :: 9 Comments :: 0 Trackbacks

            2019年1月29日 #

            將博客搬至CSDN
            posted @ 2019-01-29 10:39 clcl 閱讀(117) | 評(píng)論 (0)編輯 收藏

            2018年7月14日 #

            ET和LT:   
               LT一般用在單線程。
               ET和EPOLLONESHOT配合用在多線程共享一個(gè)epoll環(huán)境下,EPOLLONESHOT標(biāo)記觸發(fā)過的事件從epoll中移除,下次必須重新注冊(cè),用來防止多線程同時(shí)取到同一個(gè)socket的事件產(chǎn)生沖突。

            epoll_wait 第三個(gè)參數(shù) 取事件數(shù)量:
               單線程模型當(dāng)然盡可能一次多取一些效率高,多線程為了防止一個(gè)線程把所有事件取完其他線程饑餓,ACE實(shí)現(xiàn)是只取1個(gè)。

            錯(cuò)誤處理:
               EAGIN | EINTR | EWOULDBLOCK 重試。
               EPOLLERR | EPOLLHUP | EPOLLRDHUP 斷開連接。

            驚群:
               默認(rèn)系統(tǒng)都會(huì)有這問題,據(jù)說新系統(tǒng)有修復(fù)不過還是處理一下比較好,一般解決方案是同時(shí)只有一個(gè)線程等待accept,可以單獨(dú)線程accept,將連接在分給其他工作線程。nginx是多進(jìn)程模型,使用了基于共享內(nèi)存的互斥鎖,使得同時(shí)只有一個(gè)工作進(jìn)程的epoll含有accept的socket,通過這種方式實(shí)現(xiàn)連接數(shù)上的負(fù)載均衡(連接數(shù)少的工作進(jìn)程得到accept鎖的概率高)。

               為了避免大數(shù)據(jù)量io時(shí),et模式下只處理一個(gè)fd,其他fd被餓死的情況發(fā)生。linux建議可以在fd聯(lián)系到的結(jié)構(gòu)(一般都會(huì)自己封裝一個(gè)包含fd和讀寫緩沖的結(jié)構(gòu)體)中增加ready位,然后epoll_wait觸發(fā)事件之后僅將其置位為ready模式,然后在下邊輪詢r(jià)eady fd列表。


            epoll實(shí)現(xiàn):  
               epoll內(nèi)部用了一個(gè)紅黑樹記錄添加的socket,用了一個(gè)雙向鏈表接收內(nèi)核觸發(fā)的事件。

               注冊(cè)的事件掛載在紅黑樹中(紅黑樹的插入時(shí)間效率是logN,其中n為樹的高度)。

               掛載的事件會(huì)與設(shè)備(網(wǎng)卡)驅(qū)動(dòng)建立回調(diào)關(guān)系,也就是說,當(dāng)相應(yīng)的事件發(fā)生時(shí)會(huì)調(diào)用這個(gè)回調(diào)方法。這個(gè)回調(diào)方法在內(nèi)核中叫ep_poll_callback,它會(huì)將發(fā)生的事件添加到rdlist雙鏈表中。

               使用mmap映射內(nèi)存,減少內(nèi)核態(tài)和用戶態(tài)的不同內(nèi)存地址空間拷貝開銷。每次注冊(cè)新的事件到epoll中時(shí),會(huì)把fd拷貝進(jìn)內(nèi)核,通過內(nèi)核于用戶空間mmap同一塊內(nèi)存保證了只會(huì)拷貝一次。(返回的時(shí)候不需要拷貝,select要)

               執(zhí)行epoll_ctl時(shí),除了把socket放到紅黑樹上,還會(huì)給內(nèi)核中斷處理程序注冊(cè)一個(gè)回調(diào)函數(shù),告訴內(nèi)核,如果這個(gè)句柄的中斷到了,就把它放到準(zhǔn)備就緒list鏈表里。所以,當(dāng)一個(gè)socket上有數(shù)據(jù)到了,內(nèi)核在把網(wǎng)卡上的數(shù)據(jù)copy到內(nèi)核中后就把socket插入到準(zhǔn)備就緒鏈表里了。鏈表又是通過mmap映射的空間,所以在傳遞給用戶程序的時(shí)候不需要復(fù)制(這也是為什么比select效率高的原因,epoll_wait返回的只是就緒隊(duì)列,不需要輪詢 不需要復(fù)制完成的事件列表,select,poll實(shí)現(xiàn)需要自己不斷輪詢所有fd集合,直到設(shè)備就緒)。

               epoll_wait最后會(huì)檢查socket,如果是 LT,并且這些socket上確實(shí)有未處理的事件時(shí),又把該句柄放回到剛剛清空的準(zhǔn)備就緒鏈表了(LT比ET低效的原因)。

             

            可見,如果沒有大量的空閑,無效連接,epoll效率不比select高。

            測試數(shù)據(jù)(僅是剛接觸go的時(shí)候好奇做的參考意義的測試):  

               同樣的環(huán)境,echo服務(wù)器測并發(fā)io,單線程epoll qps:45000左右,每連接/協(xié)程 go: 50000多,多線程epoll(開6個(gè)epoll,每個(gè)epoll開8線程,一共48線程):qps 70000多。

             


            posted @ 2018-07-14 11:17 clcl 閱讀(419) | 評(píng)論 (0)編輯 收藏

            2018年7月13日 #

            概念
               tcp和udp,連接和無連接都是協(xié)議,是共享物理介質(zhì)的傳輸數(shù)據(jù)的應(yīng)用程序之間的約定。面向連接的協(xié)議維護(hù)了segment的狀態(tài)和次序。

            故障 :
                  默認(rèn)無keep alive:
            拔網(wǎng)線或路由器崩潰:發(fā)送端超時(shí)(重傳12次大約9分鐘)后放棄,接收端讀errorno ETIMEOUT,如果沒有讀則要等到下一次寫失敗sigpipe。如果中間路由器無法轉(zhuǎn)發(fā)則向源端發(fā)送 ICMP 目標(biāo)主機(jī)不可達(dá)。
            程序退出(包括崩潰): 程序退出和正常調(diào)用 close無法區(qū)分,都會(huì)返回FIN表示退出,如果一端退出,
                  另一端: 1.第一次寫合法(接收到fin后還是能繼續(xù)發(fā)送數(shù)據(jù))第二次寫的時(shí)候發(fā)現(xiàn)連接不存在,得到 RST RESET錯(cuò)誤 2.讀的時(shí)候得到 conn reset錯(cuò)誤,繼續(xù)寫則被SIGPIPE信號(hào)中止,程序退出。
            主機(jī)宕機(jī): 宕機(jī)后無法通過FIN通知對(duì)方,對(duì)方會(huì)繼續(xù)重傳直到timeout。如果超時(shí)前宕機(jī)的主機(jī)重啟了,此時(shí)收到重傳的主機(jī)沒有連接記錄,向源返回rst,發(fā)送端得到ECONNRESET錯(cuò)誤,如果發(fā)送端在讀得到 conn reset錯(cuò)誤,繼續(xù)寫則被SIGPIPE信號(hào)中止,程序退出。
            開啟keep alive情況下:
            如果程序崩潰返回fin , 如果主機(jī)可達(dá)但程序不存在(主機(jī)重啟),則響應(yīng)RSt,源端得到ECONNRESET 錯(cuò)誤。
            如果對(duì)方?jīng)]有對(duì)keep alive響應(yīng)ACK 或者RST,源端TIMEOUT(重試9次,每次間隔75秒,定時(shí)器2小時(shí)超時(shí)后的11.25分鐘)以程序自己做心跳還是必要的。

            細(xì)節(jié)
            tcp寫操作:
            用戶態(tài)拷貝到內(nèi)核態(tài)寫緩沖區(qū)后返回,只返回明顯錯(cuò)誤:socket無效或緩沖區(qū)無效。影響的因素有:發(fā)送窗口,擁塞窗口,寫緩沖區(qū)大小,Nagle。
            tcp是提高帶寬利用率的協(xié)議,每次發(fā)送傾向mss大小,同時(shí)不能大于對(duì)端指定的大小(發(fā)送窗口),tcp只考慮了網(wǎng)絡(luò)中路由器緩沖區(qū)耗盡情況(也是tcp的限制)所以有擁塞窗口和慢啟動(dòng):為了防止網(wǎng)絡(luò)擁塞(自律的協(xié)議)每次發(fā)送的不能大于對(duì)方指定的(發(fā)送窗口)和自己給自己限制的(擁塞窗口)。


                

            慢啟動(dòng):一開始指數(shù)級(jí)的增加擁塞窗口,到一個(gè)門閥值后變成線性的, 之后每次超時(shí)都把門閥值降低到原來一半(并且rto翻倍,TCP超時(shí)計(jì)算是RTOx2,這樣連續(xù)丟三次包就變成RTOx8了,十分恐怖),擁塞窗口設(shè)置為1重新開始慢啟動(dòng)(指數(shù)級(jí)增加)。一切都是為了讓路由器有時(shí)間處理積壓的緩沖。(所以不適用于頻繁斷開連接的移動(dòng)網(wǎng)絡(luò),這也是為什么以前的下載工具開多條tcp傳輸速度更快的原因)。

            Nagle算法:第一次(此時(shí)沒有等待ack確認(rèn),空閑連接)發(fā)送小包成功,第二次繼續(xù)發(fā)送 :哪怕發(fā)送窗口,擁塞窗口都很大,之前的包沒有ack確認(rèn)依舊不讓發(fā)直到收到之前的 ack確認(rèn)。

            shutdown和close的區(qū)別:

            close只是遞減引用計(jì)數(shù), shutdown的半關(guān)閉會(huì)影響所有的進(jìn)程。

            shutdown how=0 關(guān)閉讀,讀會(huì)返回eof
            shutdown how=1 關(guān)閉寫,任何寫會(huì)出錯(cuò),將緩沖區(qū)的發(fā)送完后會(huì)發(fā)送fin表示沒有數(shù)據(jù)了,
            收到對(duì)方發(fā)送的fin,recv會(huì)返回0。
            listen 的第二個(gè)參數(shù)制定的是全連接隊(duì)列大小(accept)

            time_wait和close_wait
            time_wait 出現(xiàn)在主動(dòng)close方,為了防止新連接收到舊鏈接的數(shù)據(jù)包, 數(shù)量高可以通過設(shè)置內(nèi)核參數(shù)縮短時(shí)間降低數(shù)值(2msl)。可以通過 SO_LINGER關(guān)閉。
            close_wait 出現(xiàn)在被動(dòng)close方,數(shù)量高一般因?yàn)樾阅芑蛘遙ug在收到fin后沒有調(diào)用close。

            SO_REUSEADDR
            用于程序崩潰后重啟,解決地址被占用問題(time_wait),另一個(gè)用途是開兩個(gè)服務(wù),第一個(gè)制定地址,第二個(gè)指定INADDR_ANY 通配地址,如果客戶端連接的是制定地址一樣會(huì)到第一個(gè)socket上。
                  對(duì)于綁定于同一地址端口組合上的UDP socket,kernel嘗試在它們之間平均分配收到的數(shù)據(jù)包;對(duì)于綁定于同一地址端口組合上的TCP監(jiān)聽socket,kernel嘗試在它們之間平均分配收到的連接請(qǐng)求(調(diào)用accept()方法所得到的請(qǐng)求)。

            Nagle和延遲ack的問題:
            發(fā)送端數(shù)據(jù)未發(fā)送完,被nagle禁止發(fā)送(必須等待接收端的ack,也就是沒有未確認(rèn)的包才允許發(fā)送),此時(shí)接收端ack被延遲發(fā)送(希望將ack和數(shù)據(jù)一起發(fā)送提高帶寬利用率)而發(fā)送方數(shù)據(jù)未發(fā)送完導(dǎo)致接收端無法回復(fù),雙方死鎖。
            已連接的UDP:
            tcp的connect是開始三次握手,將遠(yuǎn)程的ip端口綁定到socket,而bind是綁定本地的ip端口。
            udp沒有三次握手,connect純粹是本地行為,將遠(yuǎn)程的ip端口綁定到socket上。
            已連接的udp可以不用sendto,sendto會(huì)先將socket和目的地址連接,然后發(fā)送數(shù)據(jù)后再斷開,而已連接的已經(jīng)綁定地址可以用write,提高效率(資料顯示暫時(shí)連接斷開所用的時(shí)間是傳輸udp數(shù)據(jù)的三分之一,還是很可觀的)。異步錯(cuò)誤的接收,使用sendto發(fā)送完系統(tǒng)就沒有記錄了,應(yīng)用程序無法接收之后icmp返回的錯(cuò)誤。
            接收端已連接udp的好處: 可以標(biāo)識(shí)哪個(gè)socket是哪個(gè)用戶,另外還可以獨(dú)占連接,再另一個(gè)地方調(diào)用recvfrom指定相同地址會(huì)返回ECONNREFUSED,因?yàn)榇诉B接已經(jīng)被綁定。
            posted @ 2018-07-13 15:49 clcl 閱讀(264) | 評(píng)論 (0)編輯 收藏

            2018年5月9日 #

               工作上雖然和cdn沒關(guān)系但是技術(shù)方向還是很多相似之處的,例如:負(fù)載均衡,緩存,分布式,路由等。
               cdn簡單的說就是個(gè)復(fù)雜的大緩存,由于目標(biāo)用戶(包括源和端)廣泛直接導(dǎo)致了其復(fù)雜性,遍布廣節(jié)點(diǎn)多則需要分流負(fù)載甚至自組織,應(yīng)用繁雜則需要分流路由,提速則需要緩存,穩(wěn)定則需要監(jiān)控調(diào)度,為了透明則需要各種映射。
               由于接入面廣和網(wǎng)絡(luò)的復(fù)雜性,不可能讓客戶端直接面對(duì)源,于是就有了專門接入客戶端的邊緣服務(wù)器/組,這些邊緣服務(wù)器和后端的調(diào)度,監(jiān)控,源服務(wù)器通訊。既然是緩存就涉及到數(shù)據(jù)一致性的問題,最簡單的就是各接入端的邊緣服務(wù)器在需要的時(shí)候到后臺(tái)拉取,或者更智能的他們之間可以相互拉取,甚至后臺(tái)調(diào)度提前推送。
               邊緣接收到請(qǐng)求后首當(dāng)其沖的問題就是對(duì)請(qǐng)求的內(nèi)容 “去哪找”和“怎么去”,想要知道內(nèi)容的位置,一般緩存的實(shí)現(xiàn)不外乎就是統(tǒng)一到一個(gè)目錄服務(wù)器找,或者廣播所有自己知道的節(jié)點(diǎn)問一圈,更高效的方法是將請(qǐng)求url哈希后直接找到目的地址,當(dāng)然只要是緩存都會(huì)過期也就有TTL的概念。“怎么去”的方式多種多樣,由于互聯(lián)網(wǎng)web服務(wù)居多基于DNS的路由使用最廣泛,缺點(diǎn)是客戶端和中繼點(diǎn)會(huì)緩存,更新需要一定時(shí)間。HTTP重定向,URL改寫,也有直接在網(wǎng)絡(luò)設(shè)備路由器上做,直接在路由表中保持路徑。這些方法在cdn這個(gè)龐雜的系統(tǒng)中根據(jù)需要使用,例如邊緣服務(wù)器為了接收到客戶端的請(qǐng)求可以使用dns重定向,然后再用哈希或者url改寫轉(zhuǎn)發(fā)到后端的源。 
               現(xiàn)如今的網(wǎng)絡(luò)內(nèi)容有許多是動(dòng)態(tài)生成的,對(duì)這些無法提前緩存的內(nèi)容可以直接略過只存靜態(tài)內(nèi)容(分段緩存),組裝的時(shí)候發(fā)送回客戶端或者直接賦予邊緣服務(wù)器生成動(dòng)態(tài)內(nèi)容的能力(邊緣計(jì)算)當(dāng)然這對(duì)網(wǎng)頁的制作有規(guī)范。
               面向大眾的服務(wù)都有潮汐效應(yīng),在熱點(diǎn)時(shí)段的訪問量是平時(shí)的幾十上百倍(根據(jù)二八理論,80%的問題都是在20%的地方出現(xiàn)的,熱點(diǎn)訪問量也是大多訪問小部分?jǐn)?shù)據(jù),如果能提前將熱點(diǎn)緩存于各邊緣服務(wù)器最直接有效),如果有自適應(yīng)的動(dòng)態(tài)調(diào)整功能整個(gè)服務(wù)會(huì)健壯很多。邊緣服務(wù)器是離用戶最近的,可以將每個(gè)節(jié)點(diǎn)看成組,組長監(jiān)控負(fù)載自適應(yīng)的添加刪除組員(緩存服務(wù)器)以及更新dns,不允許組員跨組拉數(shù)據(jù)。當(dāng)然如果客戶端之間可以使用P2P相互取數(shù)據(jù)也是一個(gè)辦法。   
               當(dāng)前出現(xiàn)了基于流媒體的cdn,視頻內(nèi)容分發(fā)在后端以文件的形式傳輸(適合傳輸?shù)母袷礁咝В竭吘壏?wù)器再以流的形式和客戶端傳輸(不需要全部傳完即可開始播放)。同時(shí)也要綜合考慮時(shí)段需求,視頻編碼策略也很重要。
            posted @ 2018-05-09 23:57 clcl 閱讀(174) | 評(píng)論 (0)編輯 收藏

            2017年2月5日 #


            dpdk是通過許多不同的緯度來加速包處理的,其中主要包括:

             

            hugepage大頁內(nèi)存(進(jìn)程使用的是虛擬地址,一般頁表(4k)能映射的虛擬地址空間有限,使用大頁能減少換頁次數(shù)提高cache命中,通過mmap把大頁映射到用戶態(tài)的虛擬地址空間有用過mmap的都知道這是實(shí)現(xiàn)共享內(nèi)存的手段,所以dpdk還支持多進(jìn)程共享內(nèi)存)

             

            cache預(yù)取 (每次預(yù)讀當(dāng)前數(shù)據(jù)相鄰前后的數(shù)據(jù)),批量操作數(shù)據(jù),cache line對(duì)齊(通過浪費(fèi)一點(diǎn)內(nèi)存將要操作的數(shù)據(jù)對(duì)齊)

             

            接管了網(wǎng)卡用戶態(tài)驅(qū)動(dòng)使用輪詢而不是網(wǎng)卡中斷

             

            將網(wǎng)卡rx tx隊(duì)列映射到用戶態(tài)空間實(shí)現(xiàn)真正的零拷貝(傳統(tǒng)堆棧至少也得一次拷貝,因?yàn)殛?duì)列空間在內(nèi)核而內(nèi)核和用戶態(tài)使用不同的地址空間)(傳統(tǒng)堆棧為了支持通用性,例如ipx等其他網(wǎng)絡(luò),將包處理過程分了很多層次,層之間的接口標(biāo)準(zhǔn)統(tǒng)一數(shù)據(jù)結(jié)構(gòu)就需要轉(zhuǎn)換,無形中帶來了巨大的成本,如osi七層模型而實(shí)用的就是tcp/ip四層模型)

             

            線程綁定cpu

             

            支持NUMA,不同的core屬于不同的node,每個(gè)node有自己的mempool減少?zèng)_突

             

            無鎖環(huán)形隊(duì)列(沖突發(fā)生時(shí)也是一次cas的開銷)

             

            dpdk通過tools/dpdk-setup.sh的腳本,通過編譯、掛載內(nèi)核模塊, 綁定網(wǎng)卡(先把網(wǎng)卡ifconfig down),設(shè)置hugepage后就可以使用了。

             

             

            在內(nèi)核模塊igb加載時(shí),會(huì)注冊(cè)pci設(shè)備驅(qū)動(dòng)

            static struct pci_driver igbuio_pci_driver = {

            .name = "igb_uio",

            .id_table = NULL,

            .probe = igbuio_pci_probe,

            .remove = igbuio_pci_remove,

            };

             

            在綁定網(wǎng)卡時(shí),會(huì)調(diào)用igbuio_pci_probe,使用用戶態(tài)驅(qū)動(dòng)uio接管網(wǎng)卡(中斷處理、mmap映射設(shè)備內(nèi)存到用戶空間)

            系統(tǒng)啟動(dòng)時(shí),bios會(huì)將設(shè)備總線地址信息記錄在/sys/bus/pci/devices,dpdk程序啟動(dòng)時(shí)會(huì)去這里掃描pci設(shè)備,根據(jù)不同類型的NIC有對(duì)應(yīng)的初始化流程。在后面配置隊(duì)列的時(shí)候會(huì)把用戶態(tài)的隊(duì)列內(nèi)存地址通過硬件指令交給NIC,從而實(shí)現(xiàn)零拷貝。


             

            如果NIC收到包,會(huì)做標(biāo)記,輪詢的時(shí)候通過標(biāo)記取數(shù)據(jù)包

            while (nb_rx < nb_pkts) {

            /*

             * The order of operations here is important as the DD status

             * bit must not be read after any other descriptor fields.

             * rx_ring and rxdp are pointing to volatile data so the order

             * of accesses cannot be reordered by the compiler. If they were

             * not volatile, they could be reordered which could lead to

             * using invalid descriptor fields when read from rxd.

             */

            rxdp = &rx_ring[rx_id];

            staterr = rxdp->wb.upper.status_error;

            if (! (staterr & rte_cpu_to_le_32(E1000_RXD_STAT_DD)))

            break;

            rxd = *rxdp;

            發(fā)包的輪詢就是輪詢發(fā)包結(jié)束的硬件標(biāo)志位,硬件發(fā)包完成會(huì)寫回標(biāo)志位,驅(qū)動(dòng)發(fā)現(xiàn)后再釋放對(duì)應(yīng)的描述符和緩沖塊。

             

            KNI

            通過創(chuàng)建一個(gè)虛擬網(wǎng)卡,將收到的包丟給協(xié)議棧

             /* 發(fā)送skb到協(xié)議棧 */

                        /* Call netif interface */

                        netif_receive_skb(skb);

             

            POWER

            在負(fù)載小的時(shí)候沒有必要使用輪詢模式,這時(shí)可以打開網(wǎng)卡中斷 使用eventfd  epoll通知用戶層

             

            Ring

            無鎖環(huán)形隊(duì)列的核心就是操作頭尾索引,先將頭尾索引賦給臨時(shí)變量,再把尾索引往后跳n個(gè)位置,利用cas判斷頭如果還是在原來的位置就指向尾否則就重復(fù)這個(gè)過程,然后在操作中間跳過的n個(gè)元素就是安全的了,此時(shí)頭尾索引應(yīng)該指向同一個(gè)位置,如果不同應(yīng)該是有別的線程也在操作,重復(fù)等待即可。(這里有個(gè)細(xì)節(jié),索引是只加不減的,因?yàn)槭黔h(huán)形隊(duì)列索引又是unsigned 32bits,所以每次取數(shù)據(jù)前把索引模隊(duì)列長度-1, uint32_t mask;           /**< Mask (size-1) of ring. */即可)

             

            Windows下使用vmware虛擬機(jī)的時(shí)候出現(xiàn)EAL: Error reading from file descriptor,根據(jù)網(wǎng)上的說法打了patch還是不行,后來嘗試掛載內(nèi)核模塊的時(shí)候不加載vfio模塊就可以了

             

            posted @ 2017-02-05 14:08 clcl 閱讀(882) | 評(píng)論 (0)編輯 收藏

            2017年1月24日 #

            Redis是工作中很常用的,這里將比較普遍使用的結(jié)構(gòu)研究了下做個(gè)備忘。

             

            hash

            實(shí)現(xiàn)和dnspod的dataset半斤八兩,本質(zhì)上是個(gè)二維數(shù)組,通過將key哈希作為一維的下表,第二維的數(shù)組存相同哈希的元素,查找使用遍歷的方式,所以這里redis做了優(yōu)化,當(dāng)滿足條件的時(shí)候(數(shù)組數(shù)量太大)會(huì)進(jìn)行rehash,動(dòng)態(tài)擴(kuò)大桶的數(shù)量來減少最后一維遍歷的次數(shù).

            函數(shù)名稱

            作用

            復(fù)雜度

            dictCreate

            創(chuàng)建一個(gè)新字典

            O(1)

            dictResize

            重新規(guī)劃字典的大小

            O(1)

            dictExpand

            擴(kuò)展字典

            O(1)

            dictRehash

            對(duì)字典進(jìn)行N步漸進(jìn)式Rehash

            O(N)

            _dictRehashStep

            對(duì)字典進(jìn)行1步嘗試Rehash

            O(N)

            dictAdd

            添加一個(gè)元素

            O(1)

            dictReplace

            替換給定key的value值

            O(1)

            dictDelete

            刪除一個(gè)元素

            O(N)

            dictRelease

            釋放字典

            O(1)

            dictFind

            查找一個(gè)元素

            O(N)

            dictFetchValue

            通過key查找value

            O(N)

            dictGetRandomKey

            隨機(jī)返回字典中一個(gè)元素

            O(1)

             

            字典結(jié)構(gòu)

            typedef struct dict {

                // 類型特定函數(shù)

                dictType *type;

                // 私有數(shù)據(jù)

                void *privdata;

                // 哈希表

                dictht ht[2];

                // rehash 索引

                // 當(dāng) rehash 不在進(jìn)行時(shí),值為 -1

                int rehashidx; /* rehashing not in progress if rehashidx == -1 */

                // 目前正在運(yùn)行的安全迭代器的數(shù)量

                int iterators; /* number of iterators currently running */

            } dict;

            這里哈希表有兩個(gè),一般都用ht[0],當(dāng)需要rehash的時(shí)候會(huì)創(chuàng)建一個(gè)比ht[0]大的 2 的 N 次方的ht[1],然后漸進(jìn)式的將數(shù)據(jù)dictEntry移過去(除了定時(shí)的rehash,在每次操作哈希表時(shí)都會(huì)_dictRehashStep),完成后將ht[1]替換ht[0]

             

            zset

            zset本質(zhì)就是list,只不過每個(gè)元素都有若干個(gè)指向后繼span長的指針,這樣簡單的設(shè)計(jì)大大提高了效率,使得可以比擬平衡二叉樹,查找、刪除、插入等操作都可以在對(duì)數(shù)期望時(shí)間內(nèi)完成,對(duì)比平衡樹,跳躍表的實(shí)現(xiàn)要簡單直觀很多。

             

             

            /* ZSETs use a specialized version of Skiplists */

            /*

             * 跳躍表節(jié)點(diǎn)

             */

            typedef struct zskiplistNode {

                // 成員對(duì)象

                robj *obj;

                // 分值

                double score;

                // 后退指針

                struct zskiplistNode *backward;

                // 層

                struct zskiplistLevel {

                    // 前進(jìn)指針

                    struct zskiplistNode *forward;

                    // 跨度

                    unsigned int span;

                } level[];

            } zskiplistNode;

             

            /*

             * 跳躍表

             */

            typedef struct zskiplist {

                // 表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)

                struct zskiplistNode *header, *tail;

                // 表中節(jié)點(diǎn)的數(shù)量

                unsigned long length;

                // 表中層數(shù)最大的節(jié)點(diǎn)的層數(shù)

                int level;

            } zskiplist;

             

            /*

             * 有序集合

             */

            typedef struct zset {

             

                // 字典,鍵為成員,值為分值

                // 用于支持 O(1) 復(fù)雜度的按成員取分值操作

                dict *dict;

                // 跳躍表,按分值排序成員

                // 用于支持平均復(fù)雜度為 O(log N) 的按分值定位成員操作

                // 以及范圍操作

                zskiplist *zsl;

             

            } zset;

             

            雖然這種方式排序查找很快,但是修改的話就得多做些工作了

             

            /* Delete an element with matching score/object from the skiplist.

             *

             * 從跳躍表 zsl 中刪除包含給定節(jié)點(diǎn) score 并且?guī)в兄付▽?duì)象 obj 的節(jié)點(diǎn)。

             *

             * T_wrost = O(N^2), T_avg = O(N log N)

             */

            int zslDelete(zskiplist *zsl, double score, robj *obj)

             

            intset

            typedef struct intset {  

            uint32_t encoding; //所使用類型的長度,4\8\16  

            uint32_t length; //元素個(gè)數(shù)  

            int8_t contents[]; //保存元素的數(shù)組  

            } intset;  

             

            intset其實(shí)就是數(shù)組,有序、無重復(fù)地保存多個(gè)整數(shù)值,查找用的是二分查找 * T = O(log N),添加的話在找到對(duì)應(yīng)的數(shù)組中應(yīng)該存在的位子后使用memmove向后移出空位填補(bǔ)(當(dāng)然需要先realloc預(yù)分配空間),同理刪除也是用memmove向前移動(dòng)

             

            set

            當(dāng)使用整數(shù)時(shí),使用intset,否則使用哈希表

             

             

            其他的關(guān)于網(wǎng)絡(luò)事件處理,epoll,回調(diào),拆包都和正常使用差不多,關(guān)于錯(cuò)誤處理EINTR(系統(tǒng)調(diào)用期間發(fā)生中斷)和EAGAIN 繼續(xù)重試而如果是EPOLLHUP或EPOLLERR則讓io該讀讀該寫寫,有錯(cuò)處理就是了。

             

             

            posted @ 2017-01-24 18:13 clcl 閱讀(263) | 評(píng)論 (0)編輯 收藏

            2017年1月23日 #

             

             

            dns的遞歸解析過程還是挺繁瑣的,要知道一個(gè)域名可能有cname、ns 而請(qǐng)求的cname、ns可能還有cname、ns,如果按照線性的處理每個(gè)請(qǐng)求那邏輯就變成毛線團(tuán)了

            dnspod的處理還是挺巧妙的,通過一個(gè)公共的數(shù)據(jù)集dataset將所有域名對(duì)應(yīng)的a、cname、ns等類型的數(shù)據(jù)作為單獨(dú)的條目存入,當(dāng)有需要某個(gè)域名的信息時(shí)先去dataset找,找不到在加入qlist請(qǐng)求根,有專門的線程不間斷的將qlist輪詢dataset找(這里只要次數(shù)允許,沒得到想要的結(jié)果就輪詢所有qlist到dataset找雖然可以簡化邏輯分離的徹底但是會(huì)是個(gè)性能瓶頸,后面有方案)當(dāng)根返回以后只是簡單的將記錄(通常是一個(gè)域名的cname、ns或者a)存入dataset(而不是繼續(xù)流程,因?yàn)楦鶕?jù)這個(gè)返回是cname還是ns或者a處理不同邏輯復(fù)雜,而這樣處理對(duì)于用到相同域名的請(qǐng)求還有優(yōu)化作用),剩下的工作交給那邊不間斷輪詢的線程

             

            Dnspod主要由3個(gè)run(若干個(gè)線程)組成

             

            run_sentinel  監(jiān)聽53端口接收客戶端請(qǐng)求,將請(qǐng)求放到隊(duì)列中

            run_fetcher   從隊(duì)列中取出請(qǐng)求,根據(jù)qname取得最后一級(jí)cname,查看本地dataset 是否有記錄,如果有則返回,沒有則將該請(qǐng)求放入qlist中

             

            run_quizzer    

            1.不間斷的遍歷qlist,只要狀態(tài)為PROCESS_QUERYdataset中沒有的就向?qū)?yīng)的根發(fā)送請(qǐng)求。

            2.通過epoll等待根返回,解析返回的數(shù)據(jù)加入 dataset

            3.檢查記錄的ttl,在將記錄加入dataset時(shí)還會(huì)將這些記錄以紅黑樹的形式組織起來,取得ttl最早到期的,將其放入qlist中等待刷新,注意這里不是刪除,如果收不到不返回則該記錄一直存在

             

            關(guān)于dataset的實(shí)現(xiàn)

            dataset是使用哈希表實(shí)現(xiàn)的,本質(zhì)上是個(gè)二維數(shù)組,將域名哈希成一個(gè)值,模上數(shù)組的數(shù)量作為下標(biāo),找到對(duì)應(yīng)的數(shù)組接著遍歷查找,根據(jù)需要可以擴(kuò)大數(shù)組的數(shù)量提升性能。

             

            我們的優(yōu)化手段

            之前提到dnspod的qlist會(huì)不間斷輪詢,屬于主動(dòng)查詢,對(duì)性能有不小的影響,這里我們采取的做法是被動(dòng)(類似回調(diào)的方式),我們將請(qǐng)求的域名和類型分類,相同的放在一組,當(dāng)dataset找不到向根發(fā)出請(qǐng)求后我們并不每次主動(dòng)輪詢,而是在等到應(yīng)答后,觸發(fā)該域名和類型的請(qǐng)求組,讓他們根據(jù)自己的邏輯走下一步(一般是先找該域名的最后一級(jí)cname,根據(jù)這個(gè)cname查是否存在他的對(duì)應(yīng)請(qǐng)求類型的記錄,一般是a或者ns,如果沒有,則找這個(gè)cname的ns)

             

            以上可以看出dataset很重要,負(fù)載也不小,還經(jīng)常需要并發(fā)訪問,這里我們每次接收到根的回復(fù)后,除了將記錄的答案加進(jìn)dataset,還創(chuàng)建一個(gè)臨時(shí)的dataset,只存該次回復(fù)的信息,在后面的流程會(huì)優(yōu)先到這里去找,沒有的再找dataset。

            posted @ 2017-01-23 15:14 clcl 閱讀(210) | 評(píng)論 (0)編輯 收藏

            2017年1月22日 #

                 摘要: 最近在研究DPDK,這是sigcomm 2014的論文,紀(jì)錄在此備忘Ps:  文中關(guān)鍵詞的概念:segment : 對(duì)應(yīng)于tcp的PDU(協(xié)議傳輸單元),這里應(yīng)該指tcp層的包,如果一個(gè)包太大tcp負(fù)責(zé)將它拆分成多個(gè)segment(這個(gè)概念對(duì)理解后文有幫助)根據(jù)unix網(wǎng)絡(luò)編程卷1 第8頁注解2:packet是IP層傳遞給鏈路層并且由鏈路層打包好封裝在幀中的數(shù)據(jù)(不包括幀頭)而IP層的包...  閱讀全文
            posted @ 2017-01-22 18:01 clcl 閱讀(600) | 評(píng)論 (0)編輯 收藏

            2010年4月27日 #

            一直想用cegui,但是沒機(jī)會(huì)用,只是抽空看其代碼

            最近聽朋友說0.7 debug下幀數(shù)提高100多,挺驚訝的,重新到久違了的官網(wǎng)上下了0.7.1

            看了下渲染的實(shí)現(xiàn)(GL)

            首先,添加了GeometryBuffer玩意,使得每個(gè)window保存了屬于自己的頂點(diǎn)和紋理信息

            然后在RenderingSurface中有GeometryBuffer隊(duì)列,使得每個(gè)擁有AutoRenderingSurface屬性的window有屬于自己的隊(duì)列(默認(rèn)只有FrameWindow才有)

            而在drawself中執(zhí)行的則是先通過looknfeel,把需要渲染的信息丟到每個(gè)部件自己的GeometryBuffer里,然后把GeometryBuffer丟到RenderingSurface的隊(duì)列中(一般為

            FrameWindow的GeometryBuffer隊(duì)列,每個(gè)面板就有自己的渲染隊(duì)列了)

            要知道以往都是只有一個(gè)隊(duì)列的,要渲染啥直接往里塞。 。 。
            這樣一改就不必每個(gè)小部件有更改都要全部重新清空渲染了


            再往后就是把每個(gè)窗口隊(duì)列里的GeometryBuffer渲染到各自的RenderingSurface表面上,這里要注意的是并不是渲染到屏幕上而是表面上,cegui在這里使用了渲染到紋理,GL

            用的是fbo實(shí)現(xiàn)的。

            注意RenderingSurface只有兩個(gè)來源,一是通過設(shè)置AutoRenderingSurface屬性,另一個(gè)就是RenderingRoot了,RenderingRoot只有一個(gè),在render中,通過第一個(gè)來源的使

            用的是fbo的渲染,而第二個(gè)來源則直接渲染到屏幕了。

            所有的這些執(zhí)行完后就可以渲染到屏幕了,通過RenderingRoot執(zhí)行,注意這里的RenderingRoot中的RenderTarget和之前的不一樣,這里用的是OpenGLViewportTarget而不是

            OpenGLFBOTextureTarget。

            posted @ 2010-04-27 20:56 clcl 閱讀(1178) | 評(píng)論 (2)編輯 收藏

            2009年10月25日 #

                 摘要: 這個(gè)看代碼里面batch相關(guān)的。[Direct3D] 實(shí)現(xiàn)批次渲染、硬件 T&L 的渲染器和 D3DPipeline 在是否從 D3DRender 提供頂點(diǎn)緩存區(qū)操作給流水線時(shí)做了一些權(quán)衡,最后決定暫時(shí)使用 IDirect3DDevice9::DrawPrimitiveUP 來渲染,因?yàn)樗菀讜鴮懀议_銷是一次頂點(diǎn)拷貝,流水線也不用操心對(duì)緩存的使用。 (DrawPrimitive的...  閱讀全文
            posted @ 2009-10-25 17:15 clcl 閱讀(1157) | 評(píng)論 (0)編輯 收藏

            僅列出標(biāo)題  下一頁
            77777亚洲午夜久久多喷| 亚洲午夜久久久久久久久电影网| 久久久久国产| 午夜肉伦伦影院久久精品免费看国产一区二区三区 | 精品久久久久久久久午夜福利| 久久久久久久综合狠狠综合| 亚洲欧美日韩中文久久| 国产Av激情久久无码天堂| 久久精品中文无码资源站| 韩国无遮挡三级久久| 国产精品久久久久久久久软件| 久久亚洲国产午夜精品理论片| 亚洲中文久久精品无码| 日本欧美国产精品第一页久久| 色综合久久久久网| 伊人久久无码精品中文字幕| 高清免费久久午夜精品| 久久91精品国产91久久户| 性做久久久久久久| 久久久久久久99精品免费观看| 久久精品aⅴ无码中文字字幕重口| 亚洲一区精品伊人久久伊人 | yellow中文字幕久久网| 日韩AV无码久久一区二区| 久久精品视频一| 91久久精品国产免费直播| 久久精品国产一区| 久久青青草原综合伊人| 99精品国产在热久久无毒不卡 | 久久精品毛片免费观看| 性做久久久久久久| 久久综合精品国产一区二区三区| 久久精品免费大片国产大片| 日本久久久久久中文字幕| 亚洲成人精品久久| 国产综合久久久久| 久久国产欧美日韩精品| 亚洲国产精品无码久久| 久久超乳爆乳中文字幕| 99精品久久精品一区二区| 久久精品国产精品青草app|