Hadoop面試整理


1、Hadoop1.x和2.x之間的區別:Hadoop 1.0主要由兩個分支組成:MapReduce和HDFS,在高可用、擴展性等方面存在問題

(1)HDFS存在的問題

  1)NameNode單點故障,難以應用於在線場景。

  2)NameNode壓力過大,且內存受限,影響擴展性。

(2)MapReduce存在的問題

  1)JobTracker存在單點故障問題;JobTracker不僅管理資源,而且還監控每個作業的運行狀態,壓力很大。

  2)難以支持除MapReduce之外的計算框架,比如Spark、Strom等。

(3)Hadoop2.x主要解決單點故障和內存受限以及支持多種計算框架。  

  1)解決HDFS1.x中單點故障:通過主備NameNode使用HA解決單點故障問題。如果主NameNode發生故障,則切換到備NameNode之上。主NameNode對外提供服務,備NameNode同步主NameNode元數據,以待切換。

  2)解決HDFS1.x內存受限問題:使用聯邦HDFS機制,內存水平擴展,支持多NameNode。每個NameNode分管一部分目錄,所有NameNode共享所有DataNode存儲資源。

  3)Hadoop 2.0中新引入的資源管理系統YARN,它的引入使得Hadoop不再局限於MapReduce一類計算,而是支持多樣化的計算框架。它由兩類服務組成,分別是ResourceManager和NodeManager。

2、HDFS(Hadoop Distributed File System)分布式文件系統:

(1)分布式與集群之間的關系

  1)分布式:分布式是指多個系統協同合作完成一個特定任務的系統;把一個大的問題拆分為多個小的問題,並分別解決,最終協同合作。分布式的主要工作是分解任務,將職能拆解。

  2)集群:主要的使用場景是為了分擔請求的壓力,也就是在幾個服務器上部署相同的應用程序,來分擔客戶端請求。集群主要是簡單加機器解決問題,對於問題本身不做任何分解;

  3)區別:

   a、將一套系統拆分成不同子系統部署在不同服務器上叫分布式,然后部署多個相同的子系統在不同的服務器上叫集群,部署在不同服務器上的同一個子系統應做負載均衡。 

   b、分布式:一個業務拆分為多個子業務,部署在多個服務器上 

   c、集群:同一個業務,部署在多個服務器上。

  4)分布式的三大要素

   a、強一致性C:即在分布式系統中的同一數據多副本情形下,對於數據的更新操作體現出的效果與只有單份數據是一樣的。

   b、可用性A:客戶端在任何時刻對大規模數據系統的讀/寫操作都應該保證在限定延時內完成;

   c、分區容忍性(P):系統如果不能在時限內達成數據一致性,就意味着發生了分區的情況,必須就當前操作在C和A之間做出選擇。

  5)為什么分布式環境下CAP三者不可兼得呢?

   對於分布式環境下,P是必須要有的,所以該問題可以轉化為:如果P已經得到,那么C和A是否可以兼得?可以分為兩種情況來進行推演:

   a、如果在這個分布式系統中數據沒有副本,那么系統必然滿足強一致性條件,因為只有獨本數據,不會出現數據不一致的問題,此時C和P都具備。但是如果某些服務器宕機,那必然會導致某些數據是不能訪問的,那A就不符合了。

   b、如果在這個分布式系統中數據是有副本的,那么如果某些服務器宕機時,系統還是可以提供服務的,即符合A。但是很難保證數據的一致性,因為宕機的時候,可能有些數據還沒有拷貝到副本中,那么副本中提供的數據就不准確了。

   根據具體業務來側重於C或者A,對於一致性要求比較高的業務,那么對訪問延遲時間要求就會低點;對於訪問延時有要求的業務,那么對於數據一致性要求就會低點。

(2)HDFS特點:HDFS具有高容錯性,並提供了高吞吐量的數據訪問,非常適合大規模數據集上的應用。

  1)高容錯性,保存多個副本,且提供容錯機制。副本丟失或宕機自動恢復。默認存3份。

  2)流式數據訪問,一次寫入,多次讀取,高吞吐量,所以可以同時處理大量數據。

  3)可以存儲大量文件,如PB級。

  4)運行在廉價的機器上,通過副本提高可靠性,提供了容錯和恢復機制

(3)HDFS中的相關組件:

  1)namedode與secondNamenode

  管理HDFS的命名空間。維護着文件系統樹及整棵樹內的所有文件和目錄。這些信息以兩個文件形式永久保存在本地磁盤上:命名空間鏡像文件和編輯日志文件。同時也記錄着每個文件的文件名,以及文件划分成塊所在的數據節點信息,但它並不會永久保存塊的位置信息,因為這些信息會在系統啟動時根據數據節點信息重建。

  2)輔助namenode:輔助Namenode的角色被備用namenode所包含。

  SecondaryNameNode的重要作用是定期通過編輯日志文件合並命名空間鏡像,以防止編輯日志文件過大。SecondaryNameNode一般要在另一台機器上運行,因為它需要占用大量的CPU時間與namenode相同容量的內存才可以進行合並操作。它會保存合並后的命名空間鏡像的副本,並在namenode發生故障時啟用。

  3)namenode的發生故障的解決方案

   方案一:在非namenode節點上備份那些組成文件系統元數據持久狀態的文件,然后利用備份namenode執行這些文件(冷啟動)。

   方案二:利用檢查點進行故障恢復即利用輔助namenode,輔助namenode不能充當namenode,但是備用namenode可以充當輔助namenode。

   備用namenode如何代替主namenode:基於Zookeeper,Zookeeper Failover Controller(ZKFC)會監控NameNode的健康狀態,NameNode會向Zookeeper注冊,當主NameNode掛掉后,ZKFC為NameNode競爭鎖,獲得ZKFC鎖的NameNode變為active。

  4)輔助namenode為namenode創建檢查點機制

   a、secondNamenode請求主namenode停止使用正在進行中的edit文件,這樣新的編輯操作記錄到一個新文件中edit.new

   b、secondNamenode從主nomanode獲取最近的fsimage和edit文件.

   c、SecondNamenode將fsimage文件載入內存,逐一執行edit文件中的操作.創建新的fsimage即fsimage.ckpt。

   d、輔助namenode將新的fsimage文件發送回namenode,NameNode將fsimage.ckpt與edits.new文件分別重命名為fsimage與edits,然后更新fsimage。

  5)datanode

  數據塊在datanode上以文件形式存儲在磁盤上,包括兩個文件,一個是數據本身,一個是數據塊元數據即數據塊的長度,塊數據的校驗和,以及時間戳。心跳是每3秒一次,心跳返回結果帶有namenode給該datanode的命令如復制塊數據到另一台機器,或刪除某個數據塊。如果超過10分鍾沒有收到某個datanode的心跳,則認為該節點不可用。

  6)數據塊:HDFS默認有塊的概念,磁盤塊大小一般為512字節,而HDFS2.0的塊大小默認為128MB,HDFS上的文件被划分為塊大小的多個分塊,作為獨立的存儲單元。與面向單一磁盤的文件系統不同的是,HDFS中小於一個塊大小的文件不會占據整個塊的空間。如一個1MB的文件存儲在一個128MB塊中時,文件只是使用1MB的磁盤空間。

   a、HDFS中塊的大小為什么比磁盤塊大:目的是為了最小化尋址開銷,如果塊足夠大,從磁盤傳輸的時間會明顯大於定位這個塊開始位置所需的時間,因此傳輸一個由多個快組成的大文件的時間取決於磁盤傳輸速率。

   b、為什么塊不能設置過大:MR中的map認為一般處理一個塊中數據,如果任務數較少,作業運行速度將會比較慢。

   c、使用塊作為HDFS的最小存儲單元的好處:

    1.一個文件的大小可以大於網絡中任意磁盤的容量,文件的所有塊並不需要存儲在同一個磁盤中。

    2.利用塊而不是整個文件作為存儲單元可以簡化存儲管理(塊大小固定,單個磁盤能存儲多少個塊就相對容易),消除對元數據的顧慮(塊只存儲真正的數據,而文件的元數據不會與塊一同存儲,其他系統可以單獨管理這些元數據)

    3.塊適合數據備份進而提供容錯能力和提高可用性,默認塊的備份為3個,確保數據不會丟失,發現一個塊不可用,系統會從其他地方讀取另外一個副本。

  4)DataNode發生故障的處理方案步驟:

   a、排查datanode機器的硬件、網絡等環境,確認是否是datanode本身節點問題;

   b、關閉處於dead狀態節點的datanode、nodemanager進程即hadoop-daemon.sh stop datanode,yarn-daemon.sh stop nodemanager

   c、重啟dead狀態節點的datanode、nodemanager進程即hadoop-daemon.sh start datanode,yarn-daemon.sh start nodemanager。

  5)hdfs上傳寫數據的過程:

   a、client跟NameNode通信請求上傳文件,NameNode檢查目標文件是否存在以及client是否有新建此文件的權限.

   b、若通過檢查,NameNode就會為創建一條記錄,記錄該新建文件的信息.若未通過檢查就會拋出異常.

   c、然后NameNode向客戶端返回可以新建文件的信息以及相應的一組datanode,Client就近原則請求3台中的一台DataNode 1上傳數據(本質上是一個RPC調用,建立pipeline)DataNode 1收到請求會繼續調用DataNode 2,然后DataNode 2調用DataNode 3,將整個pipeline(管線)建立完成,然后逐級返回客戶端。

   d、Client將文件分成一個個數據包寫入到數據隊列中,往DN1上傳第一個塊,以數據包為單位.DN1收到一個數據包就會傳給DN2,DN2傳給DN3。

   c、當DN  3寫完最后一個pocket時,就會返回確認消息給DN2,DN2返回確認消息給DN1,然后返回給客戶端,最后由客戶端返回給namenode.

  6)hdfs讀取數據的過程:

   a、client與NameNode通信查詢元數據,找到文件塊所在的DataNode,namenode將相應的元數據信息返回給client.

   b、client根據元數據信息挑選一台DataNode(網絡拓撲上的就近原則,如果都一樣,則隨機挑選一台DataNode),請求建立socket流,以packet為單位進行發送.

   c、DataNode開始發送數據,客戶端以packet為單位接收,先在本地緩存,然后寫入目標文件。

3、MapReduce

(1)mapreduce的過程

  1)split過程:在map task執行時,它的輸入數據來源於HDFS的block。在MapReduce中,map task只讀取split。Split與block的對應關系可能是多對一,默認是一對一。輸入分片存儲的並不是數據本身,而是一個分片長度和一個記錄數據的位置的數組。inputFormat一行一行的讀取文件,按行分割形成<key,value>對。其中key為偏移量,value為每行數據內容。

  2)partition過程:在經過mapper的運行后,輸出結果是一個key/value對,MapReduce提供Partitioner接口,它根據key決定當前的這對輸出數據最終應該交由哪個reduce task處理。默認是對key hash后再對reduce task數量取模。之后key/value對以及Partition的結果將會被序列化寫入map中環形內存緩沖區中,緩沖區的作用是批量收集map結果,減少磁盤IO的影響。

  3)溢寫過程:map中的環形內存緩沖區其實是字節數組,是有大小限制的,默認是100MB。當達到環形緩沖區的閾值即80%時,map的輸出結果依然會寫入到剩余20%的緩沖區中同時會啟動溢寫線程,對環形緩沖區中80%的數據按照被序列化的后key+partitionsID進行排序。如果有combiner函數就將有相同key的value加起來,減少溢寫到磁盤的數據量,使輸出結果更加緊湊。

  4)merger溢寫文件:如果map的輸出結果非常大,則會產生多個溢出文件。此時會將多個溢寫文件合並成一個文件,若有設置Combiner,則也會使用Combiner來合並相同的key。 合並后的文件存放在本地磁盤中,而不是HDFS上。

  5)copy過程:簡單地拉取數據。Reduce進程啟動一些數據copy線程(Fetcher),通過HTTP方式請求map task的輸出文件。因為map task早已結束,這些文件就歸TaskTracker管理在本地磁盤中。 

  6)Merge階段:合並不同map端copy來的數據。不斷地merge后,最后會生成一個“最終文件”,此文件可能存在於磁盤上,也可能存在內存中。直接作為Reducer的輸入。默認情況下,此文件是存放於磁盤中的。

(3)map任務個數的計算

  1)默認map個數:如果不進行任何設置,一個塊就對應一個map任務:num = fileSize / blockSize;

  2)設置map個數:通過參數mapred.map.tasks來可以map個數,但此設置個數只有在大於(1)中num的時候,才會生效,goalNum = mapred.map.tasks;。

  3)設置處理文件大小: 可以通過mapred.min.split.size 設置每個task處理的文件大小,但是此設置大小只有在大於blockSize的時候才會生效。

    splitSize = max(mapred.min.split.size, block_size);

    splitNum = total_size / split_size;

  4)總結map個數的設置:

   a、如果想增加map個數,則設置mapred.map.tasks 為一個較大的值。

   b、如果想減小map個數,則設置mapred.min.split.size 為一個較大的值。

   c、如果輸入中有很多小文件,依然想減少map個數,則需要將小文件merger為大文件,然后使用准則2。

(4)mapreduce中使用到的排序

  MapTask和Reduce Task都會對數據按照key排序,MapReduce中使用了兩種排序算法:快速排序和歸並排序。在Map和Reduce Task的緩沖區使用的是快速排序,而對磁盤上的IFile文件合並使用的是歸並排序。

(5)combiner函數解析

  1)為什么需要combiner函數:運行combiner函數在map端進行局部聚合操作,使map的輸出結果更加緊湊,因此減少到寫到磁盤上的數據和傳遞給reduce的數據

  2)conmbiner函數何時被使用:先是分區partition,然后根據hash(key)+partition排序,最后使用Combiner進行局部聚合;merger溢寫文件時也會使用combiner函數合並相同的Key所對應的value。

  3)combiner為何不適用於求平均數:在求取平均數時,因為添加的Combiner組件是與reduce組件具有相同的邏輯,會提前求一次平均值后傳給reduce類,導致求取的平均值錯誤。例如,求0、20、10、25和15的平均數,直接使用Reduce求平均數Average(0,20,10,25,15),得到的結果是14, 如果先使用Combiner分別對不同Mapper結果求平均數,Average(0,20,10)=10,Average(25,15)=20,再使用Reducer求平均數Average(10,20),得到的結果為15,很明顯求平均數並不適合使用Combiner。

(6)mapreduce的優化

  1)數據的輸入:合並小文件,在執行mr任務前將小文件進行合並,大量的小文件會產生大量的map任務。

  2)map階段調優

   a、設置參數增大觸發spill的內存上限,減少spill次數,從而減少磁盤IO。

   b、先進行combine處理,使數據更加緊湊,減少磁盤IO

   c、減少合並(merge)次數:增大merge的文件數目,減少merge的次數,從而縮短mr處理時間。

  3)reduce階段

   a、合理設置map和reduce數:太少,會導致task等待,延長處理時間;太多,會導致 map、reduce任務間競爭資源,造成處理超時等錯誤。

   b、合理設置reduce端的buffer:默認情況下,數據達到一個閾值的時候,buffer中的數據就會寫入磁盤,然后reduce會從磁盤中獲得所有的數據。buffer和reduce是沒有直接關聯的,中間多個一個寫磁盤->讀磁盤的過程,既然如此可以通過參數來配置,使得buffer中的一部分數據可以直接輸送到reduce,從而減少IO開銷。mapred.job.reduce.input.buffer.percent,默認為0.0。當值大於0的時候,會保留指定比例的內存讀buffer中的數據直接拿給reduce使用。

  4)數據傾斜

   a、自定義分區

   b、Combine:使用Combine可以大量地減小數據傾斜。

   c、采用Map Join,盡量避免Reduce Join

4、YARN

(1)yarn的介紹:

  MR和Spark作為YARN的應用運行在集群計算層和集群的存儲層上的,Yarn整體上屬於master/slave模型,主要依賴於三個組件來實現功能,分別是ResourceManager,ApplicationMaster,NodeManager。

  1)ResourceManager:管理集群上的資源,包括兩部分調度器Scheduler,應用管理ApplicationManager。

   a、Scheduler負責各個運行中的應用的資源分配,受到資源容量,不負責應用程序的監控和狀態追蹤,而是基於應用程序的資源需求執行其調度功能,使用了叫做資源容器container的概念,container封裝了某個節點上的多維度資源,如內存、CPU、磁盤、網絡等,當AM向RM申請資源時,RM為AM返回的資源便是用Container表示的。YARN會為每個任務分配一個Container,且該任務只能使用該Container中描述的資源。

   b、ApplicationManager主要負責接收application的提交請求,為應用分配第一個Container來運行ApplicationMaster,還有就是負責監控ApplicationMaster,在遇到失敗時重啟ApplicationMaster運行的Container。

  2)NodeManager:主要負責與ResourceManager通信,負責啟動和管理應用程序的container,監控container的資源使用情況(cpu和內存等),跟蹤節點的監控狀態,管理日志等。並報告給RM。

  3)ApplicationMaster:每個應用程序都有自己的ApplicationMaster,ApplicationMaster負責與scheduler協商合適的container,跟蹤應用程序的狀態。

  通過將資源管理和應用程序兩部分分剝離開,分別由ResouceManager和ApplicationMaster負責,其中,ResouceManager專管資源管理和調度,而ApplicationMaster則負責與具體應用程序相關的任務切分、任務調度和容錯等,每個應用程序對應一個ApplicationMaster。

(2) YARN的運行機制

  1)客戶端提交application到ResourceManager並請求一個ApplicationMaster實例。

  2)ResourceManager找到一個可以運行Container的NodeManager,在這個Container中啟動ApplicationMaster,並建立ApplicationMaster的RPC端口和用於跟蹤的URL,用來監控application的狀態。

  3)AM向RM進行注冊后,ApplicationMaster向ResourceManager請求資源,ResourceManager根據資源調度策略盡可能最優的為AM分配container資源。

  4)Container被成功分配后AM向NodeManager發送信息啟動Container,並在啟動的Container中運行任務,並把任務運行的進度狀態信息發送給AM。

  5)當所有任務完成之后,AM將所用到的Container歸還給系統,並且注銷。此時分布式應用執行結束。

(3)yarn的資源調度算法

  1)FIFO調度器(FIFO Scheduler):將應用放置在第一個隊列中,然后按照提交的順序(先進先出)運行應用。首先為隊列中第一個應用的請求分配資源,第一個應用的請求被滿足后再一次為隊列中下一個應用服務。優點是簡單易懂不需要任何配置,但是不適合共享集群,當使用FIFO調度器時,小作業一直被阻塞,直至大作業完成。

  2)容量調度器(Capacity Scheduler):yarn中默認的調度器,通過為每個組織分配專門的隊列,然后再為每個隊列分配一定的集群資源,這樣整個集群就可以通過設置多個隊列的方式給多個組織提供服務了。除此之外,隊列內部又可以垂直划分,這樣一個組織內部的多個成員就可以共享這個隊列資源了,在一個隊列內部,資源的調度是采用的是先進先出(FIFO)策略。

  3)公平調度器(Fair Scheduler):公平調度器不需要預留一定量的資源,因為調度器會在所有運行的作業之間動態平衡資源。第一個(大)作業啟動時,它是唯一運行的作業,因而獲得集群中所有的資源。當第二個(小)作業啟動時,它被分配到集群的一半資源,這樣每個作業都能公平共享資源。

(4)YARN的資源隔離方案

  1)對於CPU而言,它是一種“彈性”資源,使用量大小不會直接影響到應用程序的存亡,因此CPU的資源隔離方案采用了Linux Kernel提供的輕量級資源隔離技術ControlGroup;yarn使用cgroup的兩種方式來控制cpu資源分配,分別是嚴格按核數隔離資源和按比例隔離資源.

  ControlGroup:先對計算機的某個資源設置了一些限制規則,如只能使用 CPU 的20%。若我們想一些進程去遵守這個使用 CPU 資源的限制的話,就將它加入到這個規則所綁定的進程組中,之后,相應的限制就會對其生效。

  2)內存的隔離:對於內存而言,它是一種“限制性”資源,使用量大小直接決定着應用程序的存亡,Cgroup會嚴格限制應用程序的內存使用上限,一旦使用量超過預先定義的上限值,就會將該應用程序“殺死”,因此無法使用Cgroup進行內存資源隔離,而是選擇了線程監控的方式。

5、Hadoop的高可用性。

(1)HDFS和MapReduce以及YARN單點故障解決方案:

  手動模式:通過命令實現主備之間的切換,可以用在HDFS升級等場合。

  自動模式:基於Zookeeper。Zookeeper Failover Controller(ZKFC)會監控NameNode的健康狀態,並向Zookeeper注冊NameNode,當主NameNode掛掉后,ZKFC為備NameNode競爭鎖,獲得鎖的NameNode變為active。

(2)Hadoop中高可用的組件如下:

  1)SharedStorage共享存儲系統:active master將信息寫入共享存儲系統,而standby master則讀取該信息以保持與active master的同步,從而減少切換時間。

  2)ZKFailoverController:zkfc會周期性的向它監控的namenode(每個namenode各有一個zkfc)發生健康探測命令,來鑒定namenode是否處於正常工作狀態,如果機器宕機,心跳失敗,那么zkfc就會標記它處於不健康的狀態;主要由兩個核心組件構成:ActiveStandbyElector和HealthMonitor

   a、HealthMonitor負責監控各個活動master的狀態,以根據它們狀態進行狀態切換。如主namenode磁盤空間不足。

   c、ActiveStandbyElector負責與zookeeper集群交互,通過嘗試獲取全局鎖,以判斷所管理的master進入active還是standby狀態;

  4)Zookeeper集群:核心功能通過維護一把全局鎖控制整個集群有且僅有一個active master。

(3)常用的共享存儲系統:zookeeper被YARN HA采用;HDFS被MapReduce HA采用;zookeeper,被HDFS HA采用。

(4)ZKFailoverController如何申請鎖:

  若每個namenode是健康的,則每個NN所對應的zkfc會在zookeeper中保持一個打開的會話;對於active狀態的namenode,其zkfc還會在zookeeper中占有一個短暫類型的znode,當主namenode掛掉時,其znode將會被刪除,其ZKFailoverController跟主NameNode無法通信,此時主NN所對應的FailoverController就會把主NN宕機的信息匯報給Zookeeper,其它的ZKFailoverController便從ZK中得到了這條信息,然后它們給各自監控的NameNode發送切換指令。當其中一個備用namenode率先得到這把鎖,即可升級為主namenode,同時標記狀態為active,當宕機的namenode,重新啟動,他會再次注冊zookeeper,若發現已經有znode了,就自動變為standby狀態,如此往復循環,保證高可靠性。

(5)主備namenode如何保持同步:兩個NameNode為了數據同步,會通過一組稱作JournalNodes的獨立進程進行相互通信。當active狀態的NameNode的命名空間有任何修改時,會告知大部分的JournalNodes進程。standby狀態的NameNode有能力讀取JNs中的變更信息,並且一直監控edit log的變化,把變化應用於自己的命名空間。standby可以確保在集群出錯時,命名空間狀態已經完全同步了。

(6)實現高可用可能會出現的問題:

  1)腦裂:腦裂是指在主備切換時,由於切換不徹底或其他原因,導致客戶端和Slave誤以為出現兩個active master,最終使得整個集群處於混亂狀態。

  2)解決腦裂問題,通常采用隔離(Fencing)機制,隔離要求:

   a、共享存儲fencing:確保只有一個Master往共享存儲中寫數據。

   b、客戶端fencing:確保只有一個Master可以響應客戶端的請求。

   c、Slave fencing:確保只有一個Master可以向Slave下發命令。

  3)Hadoop公共庫中有哪幾種隔離機制:

   a、sshfence:sshfence是指通過ssh登陸目標Master節點上,使用命令fuser將進程殺死(通過tcp端口號定位進程pid,該方法比jps命令更准確)

   b、shellfence:shellfence是指執行一個用戶事先定義的shell命令(腳本)完成隔離

6、總結知識點

(1)hadoop集群不用重啟集群使配置生效及配置查詢

  1)使用命令:hadoop dfsadmin -refreshNodes

  2)使用reconfigurable機制:提供一個ReconfigurationServlet工具,便於從web端變更配置。使用時將該servelt加入到相應節點的httpserver中,並在context中加入conf.servlet.reconfigurable.$P的參數,值為對應的Reconfigurable實現(一般為節點自身實現),其中$P表示的是ReconfigurationServlet在httpServer中對應的path。

   a、hadoop中Reconfigurable接口定義了可變配置的基本操作

   b、ReconfigurableBase,該基類有兩個抽象方法,所有想要實現動態配置的節點,都需要實現這兩個方法。

   c、ReconfigurationServlet 用於配置動態管理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM