1. 程式人生 > >深入理解Lustre檔案系統-第10篇 LNET:Lustre網路

深入理解Lustre檔案系統-第10篇 LNET:Lustre網路

初始化和拆除

intLNetInit(void)和intLNetFini(void)是用來建立和拆除LNET連線的API。

面向記憶體的通訊語義

如下的API已經由註釋註解了:

int LNetGet(

      lnet_nid_t self,

      lnet_handle_md_t md_in,        /* local MD to hold requested data */

      lnet_process_id_t target_in,     /* target process id */

      unsigned int portal_in,            /* target portal index */

      __u64 match_bits_in,               /* match bits used on targetprocess */

      unsigned int offset_in);           /* offset into remote MD */

這個函式初始化了遠端讀操作。注意offset_in只有在目標記憶體描述符中設定了LNET_MD_MANAGE_REMOTE時才被使用。對PUT操作,也是如此。

int LNetPut(

      lnet_nid_t self,

      lnet_handle_md_t md_in,        /* local MD holding data to be sent */

      lnet_ack_req_t ack_req_in,      /* flag to request Ack event */

      lnet_process_id_t target_in,     /* target process id */

      unsigned int portal_in,             /* target portal index */

      __u64 match_bits_in,               /* match bits for target process*/

      unsigned int offset_in,             /* offset into remote MD */

      __u64 hdr_data_in);                /* 64-bit user data */

這個函式非同步地傳送資料。self設定了使用的源NID和輸出的NI(網路介面,network interface)。如果給定了LNET_NID_ANY,LNet將根據目的NID和路由表自行選擇源NID和NI。注意,acknowledgments只有在它們被初始化程序中的程序請求,並且本地MD有事件佇列,且遠端MD允許時,才被髮出。

匹配項管理

int

LNetMEAttach(unsignedint portal,

      lnet_process_id_t match_id,

      __u64 match_bits, __u64 ignore_bits,

      lnet_unlink_t unlink, lnet_ins_pos_t pos,

      lnet_handle_me_t *handle)

這個函式建立了一個新的ME。第一個引數表明ME需要和哪個本地portal聯絡,而下一個引數表明了允許訪問這個ME的運程程序id(或遠端peer)。接下來的是匹配位和忽略位。每個portal RPC都有一個獨有的事務id,所以portal RPC使用這個事務id來作為回覆的匹配位。事務id將被髮送給遠端peer,而遠端peer將使用這個事務id來作為它的回覆緩衝的匹配位。最後一個引數是ME指標,如果呼叫成功,它返回一個控制代碼。

int LNetMDAttach(

      lnet_handle_me_t meh, /* ME to beassociated with */

      lnet_md_t umd, /* user-visible part of theMD */

      lnet_unlink_t unlink, /* if MD is unlinkedwhen it is not active */

      lnet_handle_md_t *handle)

這個函式用來建立MD,並將它連到一個ME上。如果ME已經與一個MD相關聯,則出錯返回。umd是從LNET客戶端(現在為止,是Portal RPC或者LNET自測)來的,它指定了將建立的MD物件的引數,函式將以lnet_handl_md_t *handle引數返回一個該物件的控制代碼。

int LNetMDBind(

      lnet_md_t umd,

      lnet_unlink_t unlink,

      lnet_handle_md_t *handle)

這個函式建立一個獨立的記憶體描述符,即一個未與ME相關聯的MD。

LNET是無連線的、非同步的和不可靠的。然而,大部分LND是可靠的、面向連線的,所以在同它們的peer交流之前,它們需要建立一個連線。LNET訊息的有效載荷限制是1MB,且最大的段數量不超過256。另外,LNET既不分段也不組合段。LNET假定了上層絕不會傳給LNET大於1MB的有效載荷。這個限制的理由有幾個——例如,提前設定的限制使得緩衝管理變簡單,一些低層驅動的零散緩衝數是有限制的,例如是256個。另外,如果緩衝室以頁的方式釋出,諸如Portal RPC等高層可以更容易地對資料進行分段。這個限制的缺點是一旦有一個支援多於1MB的MTU的網路技術出現,LNET可能不能充分地使用其頻寬。

LND可以有多個例項——例如,在你有多個IB介面的時候。每個介面都由一個網路介面型別表示,該型別由lnet_ni_t定義。在該結構體中定義的其中一個欄位是lnd_t——一個對該特定LND型別可用的方法表。

LND API是在LNET和它的基礎網路驅動之間的介面。正如之前提到的,有兩組LND API。第一組是LNET期望LND實現的。例如:LNET期望呼叫LND方法來發送和傳輸訊息。

  • lnd_startup()、lnd_shutup():這些函式對每個介面呼叫,只要當LNET想要開啟或者刪除這些介面。
  • lnd_notify():這是可選的。
  • lnd_accept():這是可選的。
  • lnd_send()、lnd_recv()、lnd_eager_recv():傳送正在送出的訊息和接收正在來到的訊息。
  • lnd_ctl():這個呼叫是可選的。它將使用者空間的ioctl命令傳送到LND。LNET通過一個特殊的裝置檔案,支援許多ioctl;一些是由LNET直接處理(例如,增加路由),另外一些必須傳遞到LND處理。

另外一組API是提供給LND用的LNET函式:

  • lnet_register_lnd()和lnet_unregister_lnd():每個LND驅動都呼叫這個函式來註冊一個新的LND型別。
  • lnet_notify():一個peer掛了,呼叫這個函式告知LNET。
  • lnet_parse():對每個接收到的新訊息,LND呼叫這個函式來讓LNET知道接收到了一個訊息,這樣LNET可以分析並調查它。
  • lnet_finalize():這個函式在訊息進入和傳出時都被LND呼叫。當傳送訊息時,它被LND呼叫,用來允許LNET建立一個事件,以告知訊息已被髮送了。對訊息到達,這個呼叫表明已經完全接收了正在到來的訊息的有效載荷。

對於LNET原始碼組織的簡單評註:

lnet/ulnds /*LND in user space, */

lnet/klnds /*LND in kernel space */

lnet/lnet /*LNET layer */

顯然,在核心LND和使用者空間LND的原始碼並沒有多少相同。因此,只有Portal網路和TCP網路有使用者空間LND。在核心空間,ptllnd、o2iblnd和socklnd可能是需要知道的最重要的幾個。

當LNET初始化後,呼叫lnet_startup_lndnis()。

1. 呼叫lnet_parse_networks()來分析使用者提供的模組引數。之後,LNET得到一個需要啟動的網路介面列表。

2. Iterate(?)上面得到的每個介面。首先它嘗試定位由lnd_t表示的lnd例項。當找到時,呼叫lnd_startup()。我們將看看每個步驟的細節。

3. 在迴圈中,首先通過網路型別查詢驅動。

lnd =lnet_find_lnd_by_type(lnd_type);

如果沒有找到驅動,可能是驅動還沒有載入,所以將嘗試載入模組,然後重試定位驅動:

rc =request_module(libcfs_lnd2modname(lnd_type));

在驅動載入後,在它初始化過程中,它向LNET註冊,使得驅動能在稍後被定位到。

4. 在定位了網路介面的驅動之後,我們可以將驅動繫結到該介面,然後呼叫驅動的開啟方法:

ni->ni_lnd =lnd;

rc =(lnd->lnd_startup)(ni);

現在,我們開始解釋一個特定的LND模組,套接字LND。特別地,我們看看ksocknal_startup()。

1. 如果函式第一次被呼叫,那麼它將呼叫ksocknal_base_startup()來做一些單次初始化,例如建立所有介面共用的資料結構。

2. 找到使用這個網路的乙太網介面。它可能是使用者指定的,也可能是找到的系統第一個非迴圈介面。一旦找到,它將為這個介面初始化資料結構。nid按如下方法建立:

ni->ni_nid =LNET_MKNID(LNET_NIDNET(ni->ni->nid),

net->ksnn_interfaces[0].ksni_ipaddr);

在LNET建立後,使用者可以通過這個介面傳送或接受資料。

我們從LNetPut開始一直下到LND層,描述傳送過程的一般流程。

1. 首先,分配LNET訊息描述符msg,由struct lnet_msg_t描述。這個訊息描述符將傳遞給LND。特別地,裡面定義了一個lnet_hdr_t msg_hdr(訊息頭),它將最終成為傳輸線上的訊息的一部分。

msg =lnet_msg_alloc();

2. LNET MD控制代碼被從輸入引數轉換到實際MD結構,裡面儲存了有效負載。

md =lnet_handle2md(&mdh);

3. 將MD與訊息相關聯:

lnet_commit_md(md,msg);

4. 填入訊息細節。如果存在的話,這些細節可能是訊息的型別(PUT或者GET)、匹配位、portal索引、偏移量和使用者提供的資料。

lnet_prep_send(msg,LNET_MSG_PUT, target, 0, md->md_length);

msg->msg_hdr.msg.put.match_bits= cpu_to_le64(match_bits);

msg->msg_hdr.msg.put.ptl_index= cpu_to_le32(portal);

...

5. 如下所示,填入事件資訊:

msg->msg_ev.type= LNET_EVENT_SEND;

msg->msg_ev.initiator.nid= LNET_NID_ANY;

msg->msg.ev.initiator.pid= the_lnet.ln_pid;

...

6. 最後如下所示,呼叫lnet傳送函式(不是LND傳送)。

rc =lnet_send(self, msg);

7. 這個傳送函式需要定位使用的介面。如果目標是本地的,則解析到直連線口。如果目的地是遠端的,則解析到路由表中的一個路由。這個搜尋的結果是lp,由structlnet_peer_t定義,定義了peer的最佳選擇。這個peer要麼是路由的介面,要麼是最終目的地的介面,如下所示。

msg->msg_txpeer= lp;

8. 然後,呼叫lnet_post_send_locked()來檢查credit(?)。假設你只允許向peer傳送x個並行訊息。如果你超過了這個credit閾值,這個訊息將排隊,直到了更多的credit。

9. 如果通過了credit檢查,那麼:

if (rc==0)

lnet_ni_send(src_ni, msg);

這個傳送函式呼叫LND傳送,進行下一步傳送:

rc =(ni->ni_lnd->lnd_send)(ni, priv, msg);

在以後的某一時間點,在LND傳送訊息結束後,將呼叫lnet_finalize()來告知LNET層,訊息已經發送完畢。然而,讓我們繼續深入傳送訊息的過程。讓我們假設這是一個IP網路;那麼傳送的就是套接字LND,更確切地說,將呼叫ksocknal_send()。

10. 記住,套接字LND是基於連線的,所以當你想要傳送某些東西的時候,首先你需要定位peer,然後你需要檢查是否建立了連線。如果有,你只需將tx加入到連線佇列中。

if (peer!=NULL){

      if(ksocknal_find_connectable_route_locked(peer) == NULL) {

             conn =ksocknal_find_conn_locked(tx->tx_lnetmsg->msg_len, peer);

             if (conn != NULL) {

                    ksocknal_queue_tx_locked(tx,conn);

                    ...

}

所以,最終,排隊的訊息將通過核心套接字API,來在套接字連線上傳送。

11. 如果還沒有連線,那麼我們先將訊息排隊到peer,這樣當建立了新連線後,我們可以從peer的佇列中將訊息移至連線的佇列裡,將它們發生出去。

在傳輸線上傳輸的訊息的簡要佈局如下:


從一個套接字LND中傳送訊息有兩種方式(API):如果訊息較小,正常傳送將會把訊息送入一個核心套接字緩衝中(每個套接字都有一個傳送緩衝)。這不是一個零複製(zero-copy)傳輸。另外,你可以先不復制(零複製),而將緩衝直接傳送到網路中去。然而,零複製也有它的開銷,所以Lustre只使用這種流程傳送大訊息。

在接受端,假設我們使用套接字LND,ksocknal_process_receive()是接收的入口函式。這裡給出了一般步驟。

1. LND開始接受新訊息。開始時,它只接受截止到LNET訊息頭的部分,因為它現在還不知道將有效負載放在哪個地方,只有LNET層知道目標MD的資訊。

由於這個原因,LND將LNET頭傳給lnet_parse()。這個函式中,LNET層將檢視頭,並且確定portal、偏移量、匹配位、源NID、源PID等。LNET可能拒絕訊息(例如畸形訊息),也可能接受它。

2. 如果定位了合適的MD,LNET(從lnet_parse())呼叫另外一個LND API,lnd_recv()。現在套接字核心緩衝中的有效載荷被複制到目的MD。在套接字LND情況下,這是一個核心中記憶體到記憶體的複製。

3. 如果LNET在以後某個時間點要呼叫lnd_recv(),則會立即呼叫lnd_eager_recv()。

4. 在LNET呼叫lnd_recv()後,LND開始接收鎖有效載荷(要麼是通過記憶體到記憶體的複製,要麼是通過RDMA),而LND應當在有限的時間內呼叫lnet_finalize()。此時,如果RDMA可用,LND可以使用它來傳輸資料。

同時注意TCP可能會進行分段,但是當lnd_recv()結束時,在對訊息組段(de-fragmenting)之後,它傳輸了整個訊息。

我們提到,在套接字LND中有一個記憶體到記憶體的複製。對於任一支援RDMA的網路,例如o2ib LND,它可以使用RMDA將資料之間傳輸到目的MD,從而避免了記憶體到記憶體的拷貝。更完整的配合過程如下:

1. LNET PUT將訊息傳送到o2ib LND。現在o2ib LND又兩部分資訊:一個LNET資訊頭,裡面儲存了諸如源NID、目的NID和一個指向實際有效載荷的MD。

2. 與套接字LND不同,o2ib LND只通過網路向peer傳送(使用OFED API)了LNET頭。訊息包含了一個o2ib LND頭,其中表明這是一個o2ib PUT請求。

3. 在接收端,o2ib LND仍然呼叫lnet_parse(),因為它有LNET頭資訊,由此它確定了MD。然後呼叫lnd_recv()來接收資料。o2ib的lnd_recv()將首先向OFED註冊MD緩衝。在註冊記憶體後,它取得了一個OFED記憶體ID,它與註冊好的記憶體等價。

4. 現在o2ib向發起人發回另外一個訊息(PUT ACK),其中攜帶了遠端記憶體ID。發起人向它的保有有效載荷的本地MD註冊了這個訊息,並且取得了一個本地記憶體ID。最後,它呼叫RDMAwrite,將控制傳給OFED作進一步處理。

一般概念

Lustre路由有兩個基本特徵。第一個是Lustre網路內的所有節點可以直接相互通訊,而不需要路由層的參與。第二個是,Lustre路由是靜態路由,它的網路拓撲是靜態配置的,在系統初始化時就已分析好了。在執行時,可以更新配置,系統會對之響應,然而,根據我們的瞭解,這種動態更新和基於距離向量或者連結狀態的路由非常不同。

對於Lustre網路的一個粗陋定義是:一組可以相互之間直接通訊而不需路由參與的節點群。每個Lustre網路都有一個唯一的型別和號碼,例如tcp0、ib0、ib1等等,每個端節點(end node)都有一個NID(網路識別符號,NetworkIdentifier)。Figure 9.6畫出了一個路由層的棧的例項。這個定義有一些artifact(?):(1) 它與IP子網和IP路由毫無關係。如果你有兩個網路和一個兩者之間的IP路由器,它仍然可以看作是一個Lustre網路。(2) 如果你有一個TCP介面的端節點,和另一個有IB介面的端節點,那麼你在兩者之間需要有一個LNET路由,這將被看做兩個Lustre網路。(3) Lustre網路中的地址必須是唯一的。

上述定義的另外一個暗示是,端節點的路由表將把LNET路由而不是IP路由作為下一跳。為了確定一個Lustre網路,你通常會使用networks和ip2nets這兩個指令中的一個:

# router

options lnetnetworks = tcp0(eth0), tcp1(eth1)

# client

options lnetnetworks = tcp0

# singleuniversal modprobe.conf

options lnetip2nets="tcp0(eth0,eth1) 192.168.0.[2,4]; tcp0 192.168.0.*; \

      elan0 132.6.[1-3].[2-8/2]"


這個題目強調了我們在當前的LNET(production)原始碼的不足之處。它和對LNET的一般性討論時有關的,而我們相信和更多的讀者分享我們的觀察是有益的。這裡的問題是,路由很難可靠地確定一個介面是否掛掉了。當前,LNET甚至都不會嘗試去確定這個事。所以對於一個有兩個介面的路由,比如說tcp0和ib0,如果ib0介面掛了,而與tcp0連線的介面仍然在傳送資料,這將導致一個間歇性的通訊失敗。

解決這個問題的一種思想是LNET路由可以嘗試監測傳輸問題,然後暫時性地關閉所有介面。路由可能有能力更加智慧地處理這個問題:如果它瞭解整個拓撲和客戶端使用的輸入路徑的資訊,那麼它可以只關閉一些介面。在那之前,相比於因間歇性失敗而遭受超時,關閉所有的介面似乎要好些。

在初始化時,LNET路由預分配了一個確定數量的路由緩衝。端節點的LNET層不需要這樣做(除非在初始化它的路由表時)。分配和管理路由緩衝,是端節點和路由的執行邏輯之間的主要區別,如果不是唯一的區別。

由於路由緩衝是受限資源,為了防止單一端節點淹沒路由,每個節點都給定了一個限額。緩衝只有在轉寄(forwarded)之後,才能夠重新使用。對每個端節點的限額也被稱為緩衝credit。它只對路由有意義,和“傳輸中的RPC”credit不同,後者稱為peer到peer credit。一個RPC可以牽涉到多個LNET訊息(最多十個),這些可能是一個請求LNET訊息,一個回覆LNET訊息,而在塊傳輸的情況下,可能有四個LNET訊息發往一個方向,而四個LNET訊息發往相反的方向。這也暗示了塊傳輸LNET訊息是塊RPC事務的一部分,但是它們不被算作一個RPC。所以它們(這四個塊傳輸訊息)不被算作是正在傳輸中的RPC。

有三種路由緩衝:1MB(一個LNET訊息可以攜帶的最大有效載荷量),為小訊息準備的4KB,和為諸如ACK等極小訊息準備的有效載荷為零的緩衝。

從一個端節點發來的請求遵循先來先服務的原則。如果從一個特定端節點的請求超過了它的限額,那麼它的下一個請求將不會被處理,直到路由緩衝被釋放,這就是LNET層進行流控制的本質方法(流控是點到點的)。點到點的流控制是由更上層來實現的,例如,通過正在傳輸的RPC數(RPCs inflight)。在下層LND呼叫了lnet_finalize()之後,緩衝就可以釋放了。呼叫lnet_finalize(),標誌著LND將運送看作結束了,但並不意味這訊息已經放在傳輸線上了。根據訊息大小,LND和核心有處理它的不同邏輯。對於LNET的唯一要求是,一旦lnet_finalize()呼叫了,LNET就可以自由地回收利用緩衝了。

邏輯上,LNET只有一個佇列,從所有埠到達的訊息都入佇列,並按序做進一步處理。每個介面都有它自己的介面佇列,然而這不是LNET關心的事,因為這是中斷驅動的。所以,每個到達訊息都按其到達的順序接受處理是有保證的。

這個特性是最近開發出來的(Lustre bug #15332)for Jaguar/Spider deployment at ORNL。其驅動是LNET不向路由指定負載。所以如果你有多個連線同一個目的的路由,LNET將會執行一個roundrobin演算法來分配負載,對端節點和路由都是這樣的。細粒度路由加入的是,簡單地,對不同的路由,指定由系統管理員預先配置的負載。其目的是讓更好的路由負責更多的流量。這裡的更好是由site(?)系統管理員定義的。

dest network 1:

      w1 (router 1, router 3)

      w2 (router 4, router 5)

      w3 (router 2)

例如,你可以指定不同的負載級別,然後指定路一個級別,以表示你的偏好。如果w1<w2,那麼w1是兩者之中偏好的負載級別。在給定的負載級別裡,路由器是相等的。

對我們這個情況裡,更明確的是,這個機制提供了一個可能性:客戶端可以選取一個離他更近的路由器作為它偏好的路由。

本文章歡迎轉載,請保留原始部落格連結http://blog.csdn.net/fsdev/article

相關推薦

深入理解Lustre檔案系統-10 LNETLustre網路

初始化和拆除 intLNetInit(void)和intLNetFini(void)是用來建立和拆除LNET連線的API。 面向記憶體的通訊語義 如下的API已經由註釋註解了: int LNetGet(       lnet_nid_t self,       lnet_handle_md_t md_in,

深入理解Lustre檔案系統-3 LNETLustre網路

    LNET是Lustre Networking的縮寫,是Lustre的網路子系統,負責提供訊息傳遞API。LNET源自於Sandia Portals,但又與之存在著差異。 3.1      結構     LNET由兩部分組成: LNET層。它以通訊API的方式,向被稱

深入理解Lustre檔案系統-7 MDC和Lustre元資料

7.1   MDC概論 MDC模組是處在Lustre Lite之下的一層。它定義了一些元資料相關的函式, Lustre Lite可以呼叫這些函式來向MDS傳輸元資料請求。這些函式在lustre/mdc中實現,我們將在6.3節討論它們。 Lustre Lite在mdc_op_

深入理解Lustre檔案系統-12 Lustre磁碟檔案系統ldiskfs

ldiskfs(有些時候被錯誤地稱為Linux ext4檔案系統)是對Linux ext3檔案系統的打了很多補丁的一個版本,由Sun Microsystems公司開發和維護。ldiskfs是Linux ext3和ext4檔案系統的超集。現在它只被Lustre檔案系統用在伺服

深入理解Lustre檔案系統-2 Portal RPC

    遠端程序呼叫(Remote Procedure Call,RPC)是構建分散式系統時所使用的一種常見元件。它使得客戶端可以像進行本地呼叫一樣進行遠端的過程呼叫,即客戶端可以忽略訊息傳遞的細節,而專注於過程呼叫的效果。     Portal RPC是Lustre 的R

深入理解Lustre檔案系統-1 跟蹤除錯系統

    一直以來,Linus Torvalds對核心偵錯程式都秉持著抵觸態度,並且擺出了我是bastard我怕誰的姿態。他保持了一貫風格,言辭尖銳卻直指本質。相信這是經驗之談。在除錯核心時,最關鍵的問題是如何獲取出錯相關的資訊,準確定位出錯位置。獲取資訊有很多方法,其中核心

深入理解Lustre檔案系統-9 Portal RPC

Portal RPC為如下內容提供了基礎機制: 通過輸入口傳送請求,接受請求通過輸出口接收和處理請求,傳送請求執行塊資料傳輸錯誤恢復 我們將首先探討Portal RPC的介面,而不深入到實現細節中。我們將用LDLM的傳送機製作為例子。對這個例項,LDLM向客戶端傳送一個

深入理解Lustre檔案系統-3 lustre lite

在file結構體中定義的另外一個欄位是f_dentry,它指向一個儲存在dentry cache(即所謂dcache)中的dentry物件(struct dentry)。實質上,VFS在檔案和資料夾將被首次訪問的時候就會建立一個dentry物件。如果這是一個不存在的檔案/資料夾,那麼將會建立一個無效的de

深入理解閉包系列——閉包的10種形式

前面的話   根據閉包的定義,我們知道,無論通過何種手段,只要將內部函式傳遞到所在的詞法作用域以外,它都會持有對原始作用域的引用,無論在何處執行這個函式都會使用閉包。接下來,本文將詳細介紹閉包的10種形式 返回值   最常用的一種形式是函式作為返回值被返回 var F = function()

深入理解閉包系列——常見的一個迴圈和閉包的錯誤詳解

前面的話   關於常見的一個迴圈和閉包的錯誤,很多資料對此都有文字解釋,但還是難以理解。本文將以執行環境圖示的方式來對此進行更直觀的解釋,以及對此類需求進行推衍,得到更合適的解決辦法 犯錯 function foo(){ var arr = []; for(var i = 0

深入理解定時器系列——定時器應用(時鐘、倒計時、秒錶和鬧鐘)

前面的話   本文屬於定時器的應用部分,分別用於實現與時間相關的四個應用,包括時鐘、倒計時、秒錶和鬧鐘。與時間相關需要用到時間和日期物件Date,詳細情況移步至此 時鐘   最簡單的時鐘製作辦法是通過正則表示式的exec()方法,將時間物件的字串中的時間部分截取出來,使用定時器重新整理即可 &

深入理解DOM節點型別——註釋節點和文件型別節點

前面的話   把註釋節點和文件型別節點放在一起是因為IE8-瀏覽器的一個bug。IE8-瀏覽器將標籤名為"!"的元素視作註釋節點,所以文件宣告也被視作註釋節點。本文將詳細介紹這兩部分的內容 註釋節點 【特徵】   註釋在DOM中是通過Comment型別來表示,註釋節點的三個node屬性——node

深入理解DOM節點型別——元素節點Element

前面的話   元素節點Element非常常用,是DOM文件樹的主要節點;元素節點是HTML標籤元素的DOM化結果。元素節點主要提供了對元素標籤名、子節點及特性的訪問,本文將詳細介紹元素節點的主要內容 特徵   元素節點的三個node屬性——nodeType、nodeName、nodeValue分別是

深入理解javascript物件系列——神祕的屬性描述符

前面的話   對於作業系統中的檔案,我們可以駕輕就熟將其設定為只讀、隱藏、系統檔案或普通檔案。於物件來說,屬性描述符提供類似的功能,用來描述物件的值、是否可配置、是否可修改以及是否可列舉。本文就來介紹物件中神祕的屬性描述符 描述符型別   物件屬性描述符的型別分為兩種:資料屬性和訪問器屬性 資料屬

深入理解Java虛擬機器 | 虛擬機器位元組碼執行引擎

執行引擎是Java虛擬機器最核心的組成部分之一。“虛擬機器”是一個相對於“物理機”的概念,這兩種機器都有程式碼執行能力,其區別是物理機的執行引擎是直接建立在處理器、硬體、指令集和作業系統層面上的,而虛擬機器的執行引擎則是由自己實現的,因此可以自行制定指令集與執行引擎的結構體系

【任務排程系統Azkaban原理介紹

寫在前面 Azkaban官網:https://azkaban.github.io/ 1. azkaban簡單介紹 Azkaban是由Linkedin公司推出的一個批量工作流任務排程器,主要用於在一個工作流內以一個特定的順序執行一組工作和流程。Azkaban使用job配置檔案建

Flask學習【10自定義Form元件 自定義Form元件

自定義Form元件 一、wtforms原始碼流程 1、例項化流程分析 View Code 2、驗證流程分析

mysql學習【10數據庫之索引與慢查詢優化

就會 長度 oldboy pty ODB myisam 做了 一次 復制代碼 mysql之索引原理與慢查詢優化 一、介紹 1.什麽是索引? 一般的應用系統,讀寫比例在10:1左右,而且插入操作和一般的更新操作很少出現性能問題,在生產環境中,我們遇到最

讀書筆記 ---- 《深入理解Java虛擬機器》---- 10晚期(執行期)優化

上一篇:早期(編譯期)優化:https://blog.csdn.net/pcwl1206/article/details/84635959 目  錄: 1、HotSpot虛擬機器內的即時編譯器 1.1  直譯器與編譯器  1.2  編譯物件與觸

深入理解Android 卷III》五章 深入理解Android輸入系統

《深入理解Android 卷III》即將釋出,作者是張大偉。此書填補了深入理解Android Framework卷中的一個主要空白,即Android Framework中和UI相關的部分。在一個特別講究顏值的時代,本書分析了Android 4.2中WindowManagerS