通過Omegamon XE和Tivoli Monitoring改進WebSphere MQ 叢集工作負載平衡

CloudSpace發表於2010-08-17
Ian Vanstone, 顧問軟體工程師, IBM

簡介: 本文展示如何使用 WebSphere MQ Clustering、Omegamon XE for Messaging 和 Tivoli Monitoring,通過降低由應用程式停用導致的失敗和服務超時,來改進訊息傳遞系統中的服務可用性。

簡介

IBM® WebSphere® Clustering 提供了一組強大的訊息工作負載平衡特性。預設情況下,訊息是基於佇列管理器可用性實現負載平衡的,而不是基於訊息使用方應用程式的可用性。因此,可以將訊息傳送到訊息使用方應用程式不積極提供服務的叢集佇列。WebSphere MQ 觸發機制可用於避免訊息在佇列上保持未處理狀態,但有時該機制並不適用,例如當應用程式擁有特殊的連線池或啟動邏輯時。如果未啟用觸發機制且訊息在佇列上保持未處理狀態,那麼服務失敗或超時的風險將增加。

本文展示如何只將訊息傳送到正由訊息使用方應用程式積極提供服務的那些佇列。這個解決方案降低了由不適合使用觸發機制的 WebSphere MQ 應用程式導致的失敗和服務超時。這個解決方案使用:

  • WebSphere MQ Clustering 來平衡訊息工作負載;
  • Omegamon XE for Messaging 和 IBM Tivoli Monitoring 來監控 WebSphere MQ 佇列的狀態並自動化配置更改。

為何基於應用程式可用性實現工作負載平衡?

WebSphere MQ Clustering 並不基於訊息使用方應用程式的可用性來實現訊息負載平衡,比如通過一個含三個佇列管理器的叢集來實現(見圖 1)。連線到 QM1 的訊息生成方應用程式將訊息放置到佇列 CLUSQ1。預設情況下,叢集機制在 QM2 和 QM3 上的 CLUSQ1 的各個例項之間均等地平衡訊息負載,即使一個訊息使用方應用程式當時不對 QM3 上的 CLUSQ1 提供服務。


圖 1. 一個 WebSphere MQ 叢集示例
一個 WebSphere MQ 叢集示例

在多數環境中,如果訊息到達 QM3 上的 CLUSQ1,WebSphere MQ 觸發機制可用於啟動一個訊息使用方應用程式。在這個示例中,訊息使用方應用程式在一個由於計劃外停用而離線的應用程式伺服器中執行。由於特殊的啟動流程,觸發機制不能用於啟動該應用程式伺服器。在這種情況下,當該應用程式伺服器離線時,最好將所有訊息傳送到擁有一個活動應用程式伺服器和訊息使用方應用程式的 QM2 上的 CLUSQ1 中。通過活動訊息使用方應用程式傳送訊息到佇列的需求與以下幾種情景相關:

  • 一個服務級別協議(SLA)指定最大訊息處理時間;
  • 一個服務超時已在一個或多個服務元件中配置;
  • 某人發起這個服務且必須等待一個答覆,並且最終將放棄等待。

您可以基於應用程式可用性來構建工作負載平衡機制,這裡要用到 WebSphere MQ 的一些特性和其他一些產品 —— 本例中是 IBM Tivoli Monitoring 和 Omegamon XE for Messaging。

使用的產品

本小節介紹如何使用這三個產品來基於應用程式可用性實現訊息負載平衡。要參考關於其他解決方案的討論,請參見下面的 備用方案 小節。

WebSphere MQ Clustering

WebSphere MQ 叢集機制用於將多個佇列管理器合併為一些小組。如這個解決方案所示,叢集機制有兩個主要目的:

  1. 叢集機制提供一個訊息工作負載平衡系統,通常用於在多個佇列管理器之間分佈訊息傳遞工作負載,或者將訊息傳送到可用性最高的佇列管理器。本文中的解決方案增強了這些工作負載平衡特性。
  2. 叢集機制提供一個物件自動定義系統,支援建立在拓撲變更期間保持靈活性的 WebSphere MQ 訊息傳遞網路。其結果是一個更簡單的管理模型。例如,在叢集中新增或刪除佇列管理器比在通過常規(非叢集)通道互聯的大型 WebSphere MQ 網路中完成相同的操作更簡單。叢集機制在 SOA 訊息傳遞正規化中使用得越來越多,SOA 訊息傳遞正規化要求多個佇列管理器直接互聯,以提供一個靈活的企業服務匯流排(ESB)。這個解決方案使用叢集自動定義特性,以在多個叢集佇列管理器之間自動共享物件定義更改。

Omegamon XE for Messaging

Omegamon XE for Messaging 為 WebSphere MQ 和 WebSphere Message Broker 提供監控和配置特性。在這個解決方案中,它用於監控 WebSphere MQ 佇列的狀態。

IBM Tivoli Monitoring

Tivoli Monitoring 提供一些基礎設施和工具來監控、配置和自動化一些健康業務系統的執行。其他許多監控產品都可以連線到 Tivoli Monitoring,提供一個集中的企業級監控解決方案。這個解決方案聯合使用 Omegamon XE for Messaging 和 Tivoli Monitoring 來監控、配置和自動化對 WebSphere MQ 資產的更改。Tivoli Enterprise Portal 圖形使用者介面用於配置 Omegamon XE for Messaging 的特性並自動化操作。Tivoli Monitoring 向 Tivoli Enterprise Portal 提供替代管理介面。

解決方案架構

這個解決方案包括三個階段,以基於應用程式可用性實現工作負載平衡。

檢測應用程式的健康狀況

開放輸入計數佇列屬性(IPPROCS)用於表明訊息使用方應用程式的存在。IPPROCS 顯示允許處理佇列訊息的應用程式量。一個非零開放輸入計數表明至少有一個應用程式準備好處理佇列上的訊息。開放輸入計數為零表示沒有一個應用程式準備好處理佇列上的訊息。

影響叢集工作負載平衡演算法

放置佇列屬性(PUT)用於影響叢集工作負載平衡。在這個解決方案中 PUT 的功效在於它基於叢集機制的兩個強大特性。第一,叢集工作負載演算法不會將訊息傳送到禁用放置的佇列。第二,叢集機制在叢集佇列物件定義的 put 屬性改變時自動傳播這些定義。以上面的圖 1 為例,如果 QM2 上的 CLUSQ1 更改為禁用放置,則物件變更被自動傳播到 QM1。因此,由連線到 QM1 的應用程式放置的訊息將不會被髮送到 QM2,這樣,所有訊息都將被髮送到 QM3。如果 QM2 上的 CLUSQ1 稍後更改為允許放置,同樣,物件更改將自動傳播到 QM1,從而使訊息工作負載在 QM2 和 QM3 之間均衡分佈。

確保訊息得到及時處理

如果訊息排列在缺乏活動訊息使用方應用程式的叢集佇列上,最好重新分發訊息,以便將它們傳送到擁有活動訊息使用方應用程式的佇列。這個解決方案使用一個簡單的應用程式從沒有活動訊息使用方應用程式的叢集佇列提取訊息,並將它們放置到另一個叢集佇列中。叢集工作負載平衡演算法不允許將訊息放置到禁用放置的佇列(沒有活動訊息使用方應用程式的佇列)。通過這種方法,訊息被重新分發到在其中得到及時處理的可能性最大的佇列中。

解決方案元件

關鍵的解決方案元件列示如下並如圖 2 所示:

  • 三個 WebSphere MQ 叢集佇列管理器:
    • QM1 連線了一個訊息生成方應用程式,並將訊息放置到叢集佇列 DCQ1 中。
    • QM2 和 QM3 分別託管叢集佇列 DCQ1 的一個例項。這個解決方案確保訊息只被傳送到擁有活動訊息使用方應用程式的那些例項。
    • QM2 和 QM3 通過 Omegamon XE for Messaging 代理監控。
  • 兩個 shell 指令碼:
    • 放置啟用指令碼,用於對佇列啟用放置。
    • 放置禁用指令碼,用於對佇列禁用放置。此指令碼還執行 Q 應用程式(WebSphere MQ SupportPac MA01 提供)來重新分發訊息。這個 Q 應用程式簡單易用,且有利於測試。
  • Tivoli Monitoring 元件被連線到 Omegamon XE for Messaging 代理,並使用在一臺膝上型電腦上執行的 Web 瀏覽器上的 Tivoli Enterprise Portal 檢視。Tivoli Enterprise Portal 用於配置一些監控佇列的開放輸入計數並自動執行這些 shell 指令碼的情景,比如:
    • 如果開放輸入計數為零,則執行放置禁用指令碼。
    • 如果開放輸入計數不為零,則執行放置啟用指令碼。


    圖 2. 解決方案架構
    解決方案架構

配置解決方案

本文介紹的一般原則可以根據產品和平臺組合的不同而相應進行調整。這個特殊的解決方案使用以下 AIX V5.3 軟體部署和測試:

  • Tivoli Monitoring V6.2.1
  • Omegamon XE for Messaging V6.0.1.1
  • WebSphere MQ V6.0.2.5

這些配置說明做出以下假設:

  • Omegamon XE for Messaging 和 Tivoli Monitoring 是預先配置的,以便在 Tivoli Enterprise Portal 中監控佇列管理器。
  • 三個佇列管理器(QM1、QM2 和 QM3)連線到一個 WebSphere MQ 叢集中。
  • 叢集佇列 DCQ1 同時定義在 QM2 和 QM3 上,其 DEFBIND 佇列屬性設定為 NOTFIXED。例如:DEF QL(DCQ1) CLUSTER(C1) DEFBIND(NOTFIXED)

設定 shell 指令碼

  1. 將以下 shell 指令碼儲存在目錄 /opt/MQScripts 中的 Machine2 和 Machine3 上。
    • queueEnable.sh 對佇列啟用放置。
      #!/bin/sh
      #
      
      # Check that the required parameters were used
      if [ $# -ne 2 ]
      then
         echo "$0 : You must supply the queue and qmgr"
         exit 1
      fi
      
      # Put-enable the queue
      echo 'ALTER QL(' $1 ') PUT(ENABLED)' | runmqsc $2
      	

    • queueDisable.sh 對佇列禁用放置並重新分發佇列上的訊息。
      #!/bin/sh
      #
      
      # Check that the required parameters were used
      if [ $# -ne 2 ]
      then
        echo "$0 : You must supply the queue and qmgr"
        exit 1
      fi
      
      # Put-disable the queue 
      echo 'ALTER QL(' $1' ) PUT(DISABLED)' | runmqsc $2 
      
      # Redistribute any messages on the queue by 
      # using the Q application
      /opt/IBM/MA01/q -I$1 -m$2 -o$1 –p1
      	

      這個解決方案使用一些 shell 指令碼,以便在一個地方、以一致的方式配置 WebSphere MQ 並執行訊息重新分發應用程式。或者,您也可以使用 Tivoli Enterprise Portal 工作流和 Omegamon XE for Messaging 執行上述操作。

  2. 下載 WebSphere MQ SupportPac MA01 到 Machine2 和 Machine3 上,將 Q 應用程式儲存在 /opt/IBM/MA01 目錄中。

設定 Tivoli Enterprise Portal 情景

  1. 啟動一個 Web 瀏覽器,登入到 Tivoli Enterprise Portal。
  2. 單擊工具欄上的 Situations Editor 圖示(Situations Editor 圖示):

    圖 3
    工具欄

  3. 展開 Situation Editor 視窗中的 MQSERIES 區域:

    圖 4
    圖 4

  4. 右鍵單擊 MQSERIES 並選擇 Create New,開始建立與不含訊息處理應用程式的叢集佇列關聯的情景。
  5. 輸入新情景的名稱、說明和型別:

    圖 5
    Create New 視窗

  6. 在 Select Condition 視窗中,選擇 Queue Data 屬性組,然後選擇 Input OpensQueue Name 屬性專案。然後單擊 OK

    圖 6
    Select Condition 視窗

  7. 選擇 Situation Editor 視窗中的 Formula 選項卡。設定公式,以便在沒有應用程式擁有對輸入開放的 DCQ1 佇列時觸發該情景:
    1. 設定 Queue Name 值為:== 'DCQ1'
    2. 設定 Input Opens 值為:== 0
    3. 設定 Sampling interval 為:5 分鐘

    根據最大訊息處理時間來設定取樣間隔。



    圖 7
    Situation Editor 視窗 -- Formula 選項卡

  8. 選擇 Situation Editor 視窗中的 Distribution 選項卡。將適當的佇列管理器新增到指定列表。在這個示例中,DCQ1 佇列位於 QM2QM3 佇列管理器上。
  9. 選擇 Situation Editor 視窗中的 Action 選項卡:
    1. 設定 System Command 值為:
      /opt/MQScripts/queueDisable.sh
      &{Queue_Data.Queue_Name}
      &{Queue_Data.MQ_Manager_Name}
      	

    2. 選擇 Take action on each item 單選按鈕,以便為滿足公式要求的每個佇列觸發該情景。
    3. 選擇 Execute the Action at the Managed System (Agent) 單選按鈕,以便系統命令在正確的機器(託管指令碼的機器)上執行。
    4. 選擇 Don't take action twice in a row(一直等待,直到情景變為假然後再次為真)單選按鈕,以便指令碼只在應用程式狀態變化時執行。


    圖 8
    Situation Editor 視窗 -- Action 選項卡

  10. 單擊 Apply 儲存情景。
  11. 展開 Situation Editor 視窗中的 MQSERIES 區域。

    圖 9
    Situation Editor 視窗 -- MQSERIES 區域

  12. 右鍵單擊 MQSERIES 並選擇 Create New,開始建立與含訊息處理應用程式的叢集佇列關聯的情景。
  13. 輸入新情景姓名、說明和型別:

    圖 10
    Create New 視窗

  14. 在 Select condition 視窗中,選擇 Queue Data 屬性組,然後選擇 Input OpensQueue Name 屬性專案。然後單擊 OK

    圖 11
    Select condition 視窗

  15. 選擇 Situation Editor 中的 Formula 選項卡。設定公式,以便在任意應用程式擁有對輸入開放的 DCQ1 佇列時觸發該情景:
    1. 設定 Queue Name 值為:== ‘DCQ1’
    2. 設定 Input Opens 值為:> 0
    3. 設定 Sampling interval 值為:5 分鐘

    根據最大訊息處理時間來設定取樣間隔:



    圖 12
    Situation Editor 視窗 -- Formula 選項卡

  16. 選擇 Situations Editor 視窗中的 Distribution 選項卡。將適當的佇列管理器新增到指定列表。在這個示例中,DCQ1 佇列位於 QM2QM3 佇列管理器上。
  17. 選擇 Situations Editor 視窗中的 Action 選項卡。
    1. 設定 System Command 值為:
      /opt/MQScripts/queueEnable.sh
      &{Queue_Data.Queue_Name}
      &{Queue_Data.MQ_Manager_Name}
      	

    2. 選擇 Take action on each item 單選按鈕,以便為滿足公式要求的每個佇列觸發該情景。
    3. 設定 Execute the action at the Managed System (Agent) 單選按鈕,以便系統命令在正確的機器(佇列管理器託管指令碼的本地機器)上執行。
    4. 設定 Don't take action twice in a row(一直等待,直到情景變為假然後再次為真)單選按鈕,以便指令碼只在應用程式狀態變化時執行。


    圖 13
    Situation Editor 視窗 -- Action 選項卡

  18. 單擊 Apply 儲存情景。
  19. 右鍵單擊 MQQueueEnable 情景並單擊 Start Situation
  20. 右鍵單擊 MQQueueDisable 情景並單擊 Start Situation
  21. 單擊 OK 退出 Situation Editor。

配置現在完成,準備好進行測試。

測試解決方案

測試摘要:

  1. 將訊息使用方應用程式連線到每個叢集佇列 DCQ1 並檢查這兩個佇列是否都啟用了放置。
  2. 使用一個連線到 QM1 的訊息生成方應用程式將訊息放置到 DCQ1。
  3. 停止其中一個訊息使用方應用程式,並使用訊息生成方應用程式將更多訊息放置到 DCQ1 佇列中。這樣,訊息排列在沒有活動訊息使用方應用程式的 DCQ1 的例項上。
  4. 檢查一旦情景觸發後,沒有訊息使用方應用程式的 DCQ1 的例項是否將禁用放置,它的訊息是否將被重新分發到帶有一個活動訊息使用方應用程式的 DCQ1 的例項上。

這個測試應用程式就是來自 WebSphere MQ SupportPac MA01 的 Q 應用程式。要測試這個解決方案,請執行以下步驟:

  1. 下載 WebSphere MQ SupportPac MA01 到 Machine1,將 Q 應用程式儲存在 /opt/IBM/MA01 目錄中。
  2. 啟動訊息使用方應用程式,以便它們可以從 DCQ1 的例項獲取訊息:
    1. 在 Machine2 上執行以下命令:/opt/IBM/MA01/ q -IDCQ1 –mQM2 -w10000
    2. 在 Machine3 上執行以下命令:/opt/IBM/MA01/ q -IDCQ1 –mQM3 -w10000
  3. 在 QM1 上啟動一個訊息生成方應用程式以將訊息放置到 DCQ1:
    1. 在 Machine1 上執行以下命令:/opt/IBM/MA01/ q -oDCQ1 -mQM1
  4. 在 Tivoli Enterprise Portal 中等待情景的取樣間隔中指定的時長,然後檢查 QM1 上的叢集佇列的放置屬性。在 Machine1 上執行以下命令並檢查 DCQ1 的兩個例項的 PUT 屬性是否設定為 ENABLED:echo 'DIS QC(DCQ1) ALL' | runmqsc QM1
  5. 使用連線到 QM1 的訊息生成方應用程式將 100 條訊息放置到佇列 DCQ1。在 Machine1 上的 Q 應用程式會話中執行以下命令:#100
  6. 確保訊息到達 QM2 和 QM3 上。確保文字(比如,(4 bytes) #100)同時出現在 Machine2 和 Machine3 上的 Q 應用程式會話中。
  7. 按下 Ctrl + C 組合鍵終止 Machine2 上的訊息使用方應用程式:
    1. 使用連線到 QM1 的訊息生成方應用程式將另外 100 條訊息放置到佇列 DCQ1。在 Machine1 上的 Q 應用程式會話中執行以下命令:#100
    2. 立即檢查,看看訊息現在是否在 QM2 上的 DCQ1 中排隊。在 Machine2 上執行以下命令並確保 DCQ1 佇列擁有大於零的 CURDEPTH: echo 'DIS QL(DCQ1) CURDEPTH' | runmqsc QM2
  8. 在 Tivoli Enterprise Portal 中等待情景的取樣間隔中指定的時長(例如,5 分鐘),然後檢查 QM1 上的叢集佇列的放置屬性。在 Machine1 上執行以下命令並檢查 QM2 佇列的 PUT 屬性是否設定為 DISABLED,QM3 佇列的 PUT 屬性是否設定為 ENABLED: echo 'DIS QC(DCQ1) ALL' | runmqsc QM1
  9. 還要檢查 QM2 上的訊息是否已經被重新分發。在 Machine2 上執行以下命令並檢查 DCQ1 佇列的 CURDEPTH 是否等於零: echo 'DIS QL(DCQ1) CURDEPTH'| runmqsc QM2

測試到此完畢。

備用方案

這個解決方案包括三個階段,以基於應用程式可用性實現工作負載平衡:

  1. 檢測應用程式的健康狀況
  2. 影響叢集工作負載平衡演算法
  3. 確保訊息得到及時處理

本小節介紹如何使用備用方案實現每個階段。

檢測應用程式的健康狀況

可以使用開放輸入計數來大致估計哪些佇列擁有準備好處理訊息的應用程式,但這種方法並不總是可靠。如果一個佇列擁有一個非零開放輸入計數,一個應用程式可能沒有準備好處理訊息。可能的原因包括:

  • 應用程式擁有開放的佇列,但是並不健康或者沒有準備好處理訊息(例如,它已經耗盡工作執行緒)。
  • 應用程式沒有能力處理相關服務級別協議中定義的訊息速率。
  • 機器本身不健康(例如,CPU 利用率已經為 100%)。
  • 錯誤的應用程式連線到佇列。

使用開放輸入計數的備用方案:

  • 使用佇列的當前深度。如果佇列深度比較高,則假定應用程式不健康或不能夠處理訊息的速率。佇列深度事件能夠用於發起配置更改。
  • 監控實際的應用程式程式。如果程式正在執行,則假定應用程式是健康的。
  • 新增一個應用程式介面,以便它能夠被輪詢。如果對該介面的呼叫返回一個良好的返回碼,則假定應用程式是健康的。
  • 這些備用方案可以聯合使用,它們可以通過使用 Omegamon XE for Messaging 和 Tivoli Monitoring 的特性被全部或部分實現。

影響叢集工作負載平衡演算法

對佇列啟用和禁用放置是一種好方法,因為叢集機制會自動把更改傳播到放置屬性並在平衡工作負載時使用該屬性。叢集工作負載等級(CLWLRANK)是適合這個自動更改傳播模型的另一個佇列屬性。叢集工作負載等級屬性與放置屬性存在以下兩方面區別:

  • 它的粒度更細。叢集工作負載等級的值範圍從 0 到 9,而不包含啟用和禁用這兩個值。訊息被髮送到級別最高的一個或多個佇列,因此這個細粒度允許對佇列排序,以表明應用程式可用性的等級。
  • 它不會導致訊息放置在所有佇列都擁有同等低值的情況下失敗。如果一條訊息被放置到一個所有佇列例項都禁用放置的佇列中,則放置失敗,放置應用程式收到一個壞返回碼(MQRC_CLUSTER_PUT_INHIBITED)。如果一條訊息被放置到一個所有佇列例項都被同等評級的佇列中,則對該佇列的放置將成功完成,且訊息工作負載在所有佇列例項之間均衡分佈。

如果您使用叢集工作負載等級屬性,則應將叢集工作負載使用佇列(CLWLUSEQ)設定為 ANY,以便訊息工作負載被平衡到遠端佇列,即使存在一個本地工作佇列。

放置屬性和叢集工作負載等級屬性在行為上的區別提供了兩類服務:

  • 如果您希望(佇列例項不含活動應用程式的佇列)放置失敗,則可以使用放置屬性。
  • 如果您希望(佇列例項不含活動應用程式的佇列)放置完成且訊息被排隊,則可以使用叢集工作負載等級屬性。

還有其他備用方案,包括影響叢集工作負載演算法的叢集通道屬性(例如,叢集工作負載等級或叢集工作負載權重)。

確保訊息得到及時處理

在本文介紹的解決方案中,Q 應用程式用於將訊息重新分發到擁有活動應用程式的佇列。您必須確保訊息被正確地重新分發。在一個叢集佇列的所有例項都禁用放置的情況下,重新分發應用程式將失敗(由於 MQRC_CLUSTER_PUT_INHIBITED),導致訊息被回滾到佇列的原始本地例項上並在那裡等待,直到啟動一個本地應用程式來處理訊息。如果一個訊息使用者稍後在一個遠端佇列管理器上啟動,重新分發流程不會重新啟動,因此訊息將保持排隊狀態,即使叢集中的其他地方有一個健康的訊息使用方應用程式可用。可以重新編寫應用程式,以便在重新分發訊息之前將所有佇列例項的狀態納入考慮範圍,並在重新分發成功完成之前按照計劃的間隔執行。

如果使用叢集工作負載等級屬性,要確保將訊息重新分發到同等低階的佇列不會導致從佇列管理器到佇列管理器的訊息迴圈,這是重新分發迴圈的一部分。

訊息處理時間由服務級別協議規定。在為 Tivoli Enterprise Portal 中定義的情景設定取樣間隔時,應該考慮這些時間。WebSphere MQ 觸發機制通常是基於監控和取樣啟動應用程式的推薦備用方案,儘管如前所述,該特性並不適合所有環境。

儘管叢集自動定義和物件更改傳播功能非常有用,但您需要確保佇列管理器之間的通道(特別是來往於完整儲存庫佇列管理器的通道)保持健康。如果通道不健康,叢集物件更改訊息則不能流動,導致工作負載平衡方法選擇基於過時資料(比如,佇列的放置屬性)。您可以使用 Omegamon XE for Messaging 和 Tivoli Monitoring 這樣的產品來監控通道狀況並就通道問題向操作人員發出警報,從而避免這種情況發生。

結束語

本文展示如何使用 WebSphere MQ Clustering、Omegamon XE for Messaging 和 Tivoli Monitoring,通過降低由應用程式停用導致的失敗和服務超時,來改進訊息傳遞系統中的服務可用性。這些產品提供可靠的工具來管理和監控您的訊息傳遞基礎設施,從而改善可用性。這些產品還連結到範圍廣泛的其他 Tivoli 產品,支援一種集中、整體的訊息傳遞系統監控方法。

原文連結:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1005_vanstone/1005_vanstone.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14789789/viewspace-671156/,如需轉載,請註明出處,否則將追究法律責任。

相關文章