【51CTO.com快譯】數(shù)據(jù)是數(shù)據(jù)平臺(tái)最重要的資源,企業(yè)需要對(duì)如何將數(shù)據(jù)攝取到新的數(shù)據(jù)平臺(tái)中進(jìn)行設(shè)計(jì)和規(guī)劃。
本文將討論變更數(shù)據(jù)捕獲(CDC)解決方案,如何基于Debezium等開(kāi)源工具設(shè)計(jì)標(biāo)準(zhǔn)的復(fù)制解決方案,以及CDC可以幫助企業(yè)遷移到新的數(shù)據(jù)平臺(tái)的原因。
什么是變更數(shù)據(jù)捕獲(CDC)
變更數(shù)據(jù)捕獲(CDC)是一個(gè)軟件過(guò)程,它捕獲在源數(shù)據(jù)庫(kù)中所做的變更(DDL和DML)以同步另一個(gè)數(shù)據(jù)存儲(chǔ)庫(kù),例如數(shù)據(jù)庫(kù)、內(nèi)存緩存、數(shù)據(jù)倉(cāng)庫(kù)或數(shù)據(jù)湖。CDC用于本文沒(méi)有討論的其他互補(bǔ)的用例,例如:
- CQRS模式:其中一種實(shí)現(xiàn)涉及具有單獨(dú)的寫(xiě)入(命令)和讀取(查詢)數(shù)據(jù)庫(kù)和數(shù)據(jù)模型。寫(xiě)入層支持插入、更新和刪除操作,讀取層支持查詢數(shù)據(jù)操作。CDC允許將命令操作從寫(xiě)入數(shù)據(jù)庫(kù)復(fù)制到讀取數(shù)據(jù)庫(kù)。
- 分析微服務(wù):提供變更事件流以跟蹤變更發(fā)生的時(shí)間和內(nèi)容,并分析行為模式。
CDC解決方案由三個(gè)主要組件組成:
- 源連接器:它從數(shù)據(jù)庫(kù)中捕獲變更并生成包含這些變更詳細(xì)信息的事件。
- 通道:它是源連接器將這些事件與變更保持在一起的數(shù)據(jù)存儲(chǔ)庫(kù)。
- 接收器連接器:從通道讀取事件并處理應(yīng)用特定邏輯以將數(shù)據(jù)整合到目標(biāo)系統(tǒng)或其他目的(例如分析警報(bào)過(guò)程)。
實(shí)現(xiàn)CDC有多種方法,例如基于日志、基于觸發(fā)器或基于SQL腳本。本文將關(guān)注基于日志的方法,因?yàn)樗且环N更有效的方法,以下將描述這種方法的優(yōu)點(diǎn)。
源連接器發(fā)布的事件包含同步遠(yuǎn)程數(shù)據(jù)存儲(chǔ)庫(kù)所需的所有信息。它由以下部分組成:
- 元數(shù)據(jù):提供諸如表名、操作類型(插入、刪除等)、事務(wù)標(biāo)識(shí)符、源連接器進(jìn)行或捕獲變更時(shí)的時(shí)間戳等信息。
- 前值:變更前的數(shù)據(jù)值。
- 后值:變更后的數(shù)據(jù)值。
- JSON
- {
- "table":"stock"
- "operation": "update",
- "ts_ms" : "1627817475",
- "transaction_id": 2,
- "before" : {
- "id" : "0001",
- "item" : "T-Shirt",
- "quantity" : "10"
- },
- "after" : {
- "id" : "0001",
- "item" : "T-Shirt",
- "quantity" : "5"
- }
- }
并非所有連接器都具有相同的行為。有一些連接器(例如官方的MongoDB連接器)不提供“前值”。
在數(shù)據(jù)復(fù)制的情況下,這些事件由接收器連接器使用,并合并到目標(biāo)數(shù)據(jù)庫(kù)中。企業(yè)必須按照事件生成的順序使用事件,以確保流程的彈性。
如果事件沒(méi)有按順序進(jìn)行,就不能保證復(fù)制過(guò)程的彈性。以下是可能發(fā)生的一些場(chǎng)景的示例:
在復(fù)制以外的場(chǎng)景中,基于事件驅(qū)動(dòng)模式以及想要對(duì)特定事件做出反應(yīng)的情況下,按順序使用事件并不重要。
基于日志的CDC優(yōu)勢(shì)
與其他CDC方法或ETL復(fù)制過(guò)程相比,基于日志的CDC具有以下一些優(yōu)勢(shì):
- 性能:通過(guò)讀取該文件,從事務(wù)日志文件中檢索所有變更。與ETL等其他方法相比,這種操作對(duì)數(shù)據(jù)庫(kù)性能的影響較小。ETL方法是基于SQL查詢,需要持續(xù)優(yōu)化(索引、分區(qū)等),因此將消耗大量計(jì)算資源。
- 解耦數(shù)據(jù)提取:它提供解耦數(shù)據(jù)提取計(jì)算層,與其余工作負(fù)載隔離。這個(gè)解決方案允許僅在CDC解決方案上進(jìn)行垂直和水平擴(kuò)展。觸發(fā)器CDC方法使用數(shù)據(jù)庫(kù)計(jì)算層,此復(fù)制過(guò)程可能影響數(shù)據(jù)庫(kù)的性能。
- 接近實(shí)時(shí):低計(jì)算影響能夠提供接近實(shí)時(shí)的事件變更,而不會(huì)對(duì)源數(shù)據(jù)庫(kù)造成風(fēng)險(xiǎn)。檢測(cè)有序文件中的變更比對(duì)表進(jìn)行查詢輪詢過(guò)程更容易、更快。
- 捕獲所有變更:事務(wù)日志按確切順序提供所有數(shù)據(jù)變更,其中包括刪除操作。ETL過(guò)程忽略了ETL執(zhí)行之間發(fā)生的中間數(shù)據(jù)變更??梢允褂闷渌椒?ETL、基于CDC觸發(fā)器、CDC SQL)識(shí)別刪除操作需要?jiǎng)?chuàng)建表來(lái)注冊(cè)此操作,以及確保數(shù)據(jù)彈性的特定邏輯。
- 不影響數(shù)據(jù)模型和應(yīng)用程序:這不需要變更數(shù)據(jù)模型或源應(yīng)用程序。ETL和其他CDC解決方案需要?jiǎng)?chuàng)建觸發(fā)器和表或向表中添加時(shí)間戳。
需要考慮一些重要的細(xì)節(jié):
- 無(wú)日志事務(wù)操作:所有操作都不會(huì)在事務(wù)日志上注冊(cè)。數(shù)據(jù)倉(cāng)庫(kù)中通常使用目錄級(jí)別的操作,例如目標(biāo)表和臨時(shí)表之間的分區(qū)移動(dòng)。這種類型的操作取決于每個(gè)數(shù)據(jù)庫(kù)版本以及團(tuán)隊(duì)的工作方式。
- 商業(yè)工具:每個(gè)數(shù)據(jù)庫(kù)供應(yīng)商都提供特定于CDC的工具,通常帶有附加許可證。在復(fù)雜的多供應(yīng)商環(huán)境中,企業(yè)使用不同的CDC工具來(lái)復(fù)制數(shù)據(jù)會(huì)增加運(yùn)營(yíng)成本。
- 開(kāi)源工具:它們是一個(gè)不錯(cuò)的選擇。通常需要更多時(shí)間來(lái)更新數(shù)據(jù)庫(kù)供應(yīng)商發(fā)布的新功能。有時(shí),對(duì)故障排除或錯(cuò)誤解決的支持更為復(fù)雜。
- 反模式:在某些情況下,必須將特定源數(shù)據(jù)庫(kù)復(fù)制到多個(gè)目標(biāo)數(shù)據(jù)庫(kù)。有時(shí),團(tuán)隊(duì)會(huì)配置多個(gè)CDC復(fù)制,所有這些復(fù)制都從同一個(gè)事務(wù)日志中讀取。這是一個(gè)危險(xiǎn)的反模式。低影響并不意味著沒(méi)有影響,CDC會(huì)增加I/O操作,因此從同一文件中讀取多個(gè)CDC會(huì)增加大量I/O操作,并產(chǎn)生I/O的性能問(wèn)題。而使用中心輻射模式是一種更好的方法。
中心輻射型CDC模式(Data Hub)
中心輻射式架構(gòu)是最常見(jiàn)的數(shù)據(jù)集成架構(gòu)模式之一。這種架構(gòu)允許一次從數(shù)據(jù)庫(kù)中捕獲變更并多次交付它們。這種模式與Apache Kafka和其他流媒體平臺(tái)使用的發(fā)布和訂閱模式非常相似,并具備一些好處,例如:
(1)可重用性:更改事件從源數(shù)據(jù)庫(kù)讀取一次,并由接收器連接器多次使用。
(2)減少集成次數(shù):與源數(shù)據(jù)庫(kù)只有一次集成。
(3)標(biāo)準(zhǔn)接口:為所有消費(fèi)者提供相同的接口。在這種情況下,接收器連接器復(fù)制共享同一接口的目標(biāo)數(shù)據(jù)庫(kù)中的數(shù)據(jù)。
根據(jù)通道的特性,它將允許提供一些Data Hub的功能。數(shù)據(jù)保留是Data Hub的一項(xiàng)基本功能。如果無(wú)法存儲(chǔ)所有歷史數(shù)據(jù)甚至每個(gè)文檔或行的最后狀態(tài),用戶將不得不采用其他工具和流程來(lái)補(bǔ)充解決方案。
CDC的常見(jiàn)場(chǎng)景
CDC是一個(gè)很好的解決方案,并且有四種常見(jiàn)的場(chǎng)景:
- OLAP數(shù)據(jù)庫(kù)遷移:在企業(yè)將所有或部分工作負(fù)載從當(dāng)前數(shù)據(jù)倉(cāng)庫(kù)遷移到新的OLAP解決方案的情況下,CDC允許將相同的數(shù)據(jù)復(fù)制到另一個(gè)系統(tǒng)并使遷移變得更容易。如今,許多企業(yè)正在將工作負(fù)載從內(nèi)部部署數(shù)據(jù)庫(kù)遷移到數(shù)據(jù)云解決方案。
- 將信息從OLTP數(shù)據(jù)庫(kù)復(fù)制到OLAP數(shù)據(jù)庫(kù):將數(shù)據(jù)從運(yùn)營(yíng)數(shù)據(jù)庫(kù)復(fù)制到數(shù)據(jù)倉(cāng)庫(kù)或數(shù)據(jù)湖。
- 數(shù)據(jù)庫(kù)即服務(wù):為分析沙箱或提供數(shù)據(jù)庫(kù)的副本。
- 從單體到微服務(wù)的遷移:應(yīng)用扼殺者模式將單體應(yīng)用程序逐步遷移到微服務(wù)。在第一階段復(fù)制兩個(gè)應(yīng)用程序共存所需的一些數(shù)據(jù)集。
企業(yè)CDC解決方案
下圖描述了CDC進(jìn)程的行為方式以及組成它的組件?;诖颂岢鲆韵陆鉀Q方案架構(gòu):
- Debezium作為源連接器:這一部分將負(fù)責(zé)從源數(shù)據(jù)庫(kù)引擎讀取變更并將其發(fā)送到通道。它將作為連接器部署在Kafka Connect集群中。
- Kafka作為通道:它提供中間存儲(chǔ)以及用于事件生產(chǎn)/消費(fèi)的廣泛API和可部署在Kafka Connect或其他平臺(tái)上的大型生態(tài)系統(tǒng)連接器。
- Kafka Sink JDBC(Confluent提供)與Event flattering SMT(Debezium提供)作為Sink連接器:這個(gè)連接器允許用戶使用一些配置參數(shù)在目標(biāo)數(shù)據(jù)庫(kù)上執(zhí)行復(fù)制。作為一個(gè)通用解決方案,這是一個(gè)不錯(cuò)的選擇。在其他情況下,例如Snowflake或其他云服務(wù),JDBC連接器的成本效益和性能比供應(yīng)商本身提供的其他策略更差。評(píng)估切換到供應(yīng)商本身提供的連接器而不是使用通用JDBC的成本收益是很重要的。
- Kafka Connect as Connector Platform:它提供了一個(gè)框架,可以基于簡(jiǎn)單的配置將連接器部署為插件,并與Kafka完全集成。這是一個(gè)非常好的選擇,因?yàn)樗试S企業(yè)標(biāo)準(zhǔn)化接收器/源連接器管理,例如Debezium復(fù)制操作和JDBC接收器連接器。
1.Debezium
Debezium是一個(gè)開(kāi)源解決方案,提供了非常有趣的功能來(lái)捕獲數(shù)據(jù)庫(kù)中的變化。Debezium架構(gòu)提供了一些優(yōu)勢(shì),例如:
與特定的數(shù)據(jù)庫(kù)供應(yīng)商解決方案相比,事件標(biāo)準(zhǔn)化是使用Debezium等產(chǎn)品的重要優(yōu)勢(shì)之一。通常情況下,每個(gè)供應(yīng)商解決方案都有不同的事件規(guī)范,因?yàn)檫@些解決方案主要設(shè)計(jì)用于復(fù)制來(lái)自同一供應(yīng)商的數(shù)據(jù)庫(kù)。在多個(gè)數(shù)據(jù)庫(kù)產(chǎn)品之間進(jìn)行復(fù)制處理的場(chǎng)景中,具有多個(gè)事件規(guī)范會(huì)增加解決方案在操作、可維護(hù)性和編碼方面的復(fù)雜性。Debezium提供了一個(gè)通用、清晰且簡(jiǎn)單的事件規(guī)范,可以促進(jìn)與其他第三方產(chǎn)品(例如Kafka Connect接收器連接器)的集成。
以下看一個(gè)事件示例(為了便于閱讀而進(jìn)行了調(diào)整):
- JSON
- {
- "after": {
- "field_id": 1,
- "field_1": "Value 1"
- },
- "before": null,
- "op": "c",
- "source": {
- "connector": "mysql",
- "db": "inventory",
- "name": "mysqldb",
- "snapshot": "false",
- "table": "product",
- "ts_ms": 1627489969029,
- "version": "1.6.1.Final",
- (... other source vendor fields ...)
- },
- "transaction": null,
- "ts_ms": 1627489969200
- }
- after:包含表格列及其值的文檔。其值可以為null,例如在刪除操作中。
- before:包含表格列及其值的文檔。其值可以為null,例如在創(chuàng)建(插入)操作中。
- op:在數(shù)據(jù)庫(kù)中運(yùn)行的操作,如更新、插入或刪除。
- source:事件的元數(shù)據(jù)。該文檔具有公共信息,但它有幾個(gè)字段,具體取決于源數(shù)據(jù)庫(kù)(Oracle、SqlServer、MySQL或PostgreSQL)。
- t source.ts_ms:表示在數(shù)據(jù)庫(kù)中進(jìn)行更改的時(shí)間。
- ts_ms:Debezium處理該事件時(shí)的時(shí)間戳,與source.ts_ms不同。通過(guò)比較這些值,可以確定源數(shù)據(jù)庫(kù)更新和Debezium之間的延遲。
Debezium與Kafka生態(tài)系統(tǒng)完全集成。源連接器使用Kafka API發(fā)布更改事件,但也可以部署為Kafka連接器??梢允褂肦EST API將其部署在Kafka Connet集群中,以簡(jiǎn)化新CDC源連接器的部署和管理。
- JSON
- {
- "name": "debezium-postgres-inventory-connector",
- "config": {
- "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
- "tasks.max": "1",
- "database.hostname": "postgres",
- "database.port": "5432",
- "database.user": "postgres",
- "database.password": "postgres",
- "database.dbname": "postgres",
- "database.server.name": "postgresdb",
- "schema.include": "inventory",
- "table.include.list": "inventory.product"
- }
- }
在這個(gè)示例中,在PostgreSQL數(shù)據(jù)庫(kù)中部署了一個(gè)新的Debezium源連接器,并啟用了對(duì)庫(kù)存模式上產(chǎn)品表的變更捕獲。連接器讀取更改并將事件推送到Kafka主題“postgres.inventory.product”。
盡管每個(gè)Debezium數(shù)據(jù)庫(kù)連接器都有特定的配置、屬性和選項(xiàng),但也有通用的連接屬性。作為一個(gè)常見(jiàn)的選擇,可以在第一次配置數(shù)據(jù)庫(kù)快照到Kafka或禁用它。這些通用配置屬性加入Kafka連接器API,提供了一個(gè)標(biāo)準(zhǔn)的管理源連接器層,可以簡(jiǎn)化解決方案的操作。
需要考慮的事項(xiàng):
雖然有多種Debezium連接器,但并非所有連接器都提供相同的功能:
- MongoDB
- MySQL
- PostgreSQL
- Oracle
- Etc
在做出決定之前,對(duì)每一項(xiàng)進(jìn)行審查非常重要,因?yàn)樵谀承┣闆r下,使用供應(yīng)商連接器可能會(huì)更好,例如:
- Debezium MongoDB Source Connector:目前無(wú)法發(fā)送文檔的當(dāng)前狀態(tài),只能發(fā)送冪等格式的操作。
- Debezium SQL Server Source Connector:它不是基于日志的連接器,而是基于觸發(fā)器的連接器,它需要安裝觸發(fā)器過(guò)程并創(chuàng)建一個(gè)階段表。
2.Kafka
Kafka是提供通道功能的一個(gè)很好的選擇,因?yàn)樗峁┝藥讉€(gè)重要的功能,例如:
- 可擴(kuò)展的事件流平臺(tái):高度可配置以提供高可用性、低延遲、高性能、多次交付和持久性保證。
- 發(fā)布/訂閱模式:它促進(jìn)了一次發(fā)布和多次消費(fèi)的機(jī)制,提供了良好的系統(tǒng),每個(gè)用戶可以或按照希望提供的速度工作。
- 大型生態(tài)系統(tǒng):如今已被數(shù)千家公司使用。有許多用于數(shù)據(jù)管道、流分析和數(shù)據(jù)集成的開(kāi)源和商業(yè)工具。
- 無(wú)限存儲(chǔ)和保留:提供具有無(wú)限存儲(chǔ)和保留的集中平臺(tái)。Confluent最近提供的一些功能讓用戶能夠擁有更好的成本效益存儲(chǔ)層,將存儲(chǔ)和計(jì)算資源解耦。
Debezium CDC事件發(fā)布在Kafka主題中。一個(gè)Kafka事件由三部分組成:
- 鍵:用于確定將附加消息的分區(qū)。具有相同事件鍵的事件被寫(xiě)入同一個(gè)分區(qū)。Kafka保證分區(qū)的事件將被任何消費(fèi)者以與寫(xiě)入它們完全相同的順序讀取。
- 值:它包含事件本身。
- 標(biāo)頭:它是與Kafka記錄關(guān)聯(lián)的元數(shù)據(jù),并提供有關(guān)鍵/值對(duì)的額外信息。
作為一個(gè)鍵,Debezium包含了表的鍵域。這允許用戶按照變更事件在數(shù)據(jù)庫(kù)中發(fā)生的順序處理變更事件。
(1)主題策略
活動(dòng)發(fā)布有兩種策略:
- 每個(gè)表有一個(gè)主題。
- 每個(gè)數(shù)據(jù)庫(kù)有一個(gè)主題或一對(duì)數(shù)據(jù)庫(kù)和模式有一個(gè)主題。
最佳策略取決于環(huán)境的特征,兩種解決方案各有利弊。“每個(gè)表有一個(gè)主題”策略的主要問(wèn)題是所需的主題和分區(qū)的數(shù)量。Kafka對(duì)每個(gè)集群有一個(gè)分區(qū)限制,所以當(dāng)用戶的很多數(shù)據(jù)庫(kù)有成百上千的表時(shí),不建議使用這種策略。
(2)表現(xiàn)
這個(gè)解決方案中有兩個(gè)級(jí)別的并行性:
- 基于目標(biāo)數(shù)據(jù)庫(kù)的數(shù)量。
- 特定目標(biāo)數(shù)據(jù)庫(kù)的吞吐量。
Kafka提供了發(fā)布/訂閱模式,這允許用戶部署多個(gè)接收器連接器來(lái)處理事件,并將信息從主題并行復(fù)制到多個(gè)目標(biāo)數(shù)據(jù)庫(kù)。為了增加每個(gè)接收器連接器的吞吐量,需要組合兩個(gè)組件:
- 主題分區(qū)的數(shù)量。
- Kafka消費(fèi)者組中的消費(fèi)者數(shù)量。每個(gè)接收器連接器都與一個(gè)特定且獨(dú)特的消費(fèi)者群體相關(guān)聯(lián)。在Kafka連接器的情況下,消費(fèi)者統(tǒng)一體就像一個(gè)線程或任務(wù)。
資源組的成員劃分分區(qū),以便分區(qū)僅由組的消費(fèi)者使用,并且該消費(fèi)者將按順序讀取鍵的事件?;诖?,可以使用Kafka Connect來(lái)處理影響每個(gè)鍵的事件以將狀態(tài)復(fù)制到另一個(gè)目標(biāo)數(shù)據(jù)庫(kù)中,例如一個(gè)簡(jiǎn)單配置的數(shù)據(jù)倉(cāng)庫(kù),例如:
- JSON
- {
- "name": "jdbc-sink",
- "config": {
- "connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector",
- "tasks.max": "1",
- "topics": "postgres.inventory.product",
- "connection.url": "jdbc:dwhdriver://connection",
- "transforms": "unwrap",
- "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
- "transforms.unwrap.drop.tombstones": "false",
- "auto.create": "true",
- "insert.mode": "upsert",
- "delete.enabled": "true",
- "pk.fields": "id",
- "pk.mode": "record_key"
- }
- }
一個(gè)連接器可以讀取多個(gè)主題,并且可以在作為用戶組工作的任務(wù)中進(jìn)行擴(kuò)展。使用此配置中定義的屬性,可以執(zhí)行源的副本,或者可能僅將事件作為歷史演變附加以執(zhí)行某些分析過(guò)程。
(3)數(shù)據(jù)保留
Kafka數(shù)據(jù)保留在主題級(jí)別進(jìn)行管理,并且有不同的策略:
- 時(shí)間保留:超過(guò)時(shí)間時(shí),Kafka代理會(huì)定期刪除舊事件。
- 大小保留:當(dāng)超過(guò)主題大小時(shí),Kafka代理會(huì)定期刪除舊事件。
- 無(wú)限制。
作為一個(gè)有趣的新功能,Confluent提供了分層存儲(chǔ):可以將熱數(shù)據(jù)發(fā)送到經(jīng)濟(jì)高效的對(duì)象存儲(chǔ),并且僅在需要更多計(jì)算資源時(shí)進(jìn)行擴(kuò)展。在某些情況下,數(shù)據(jù)可能需要無(wú)限長(zhǎng)的存儲(chǔ)時(shí)間。
按時(shí)間或大小保留并不是Kafka定義清理策略的唯一能力。用戶可以定義一個(gè)緊湊策略,其中Kafka代理定期刪除事件,只保留每個(gè)鍵的最后一個(gè)事件,并在最后一個(gè)事件為null作為s值時(shí)刪除該鍵。
壓縮策略是CDC解決方案的一個(gè)非常有趣的功能。它允許用戶保留行或文檔的最后一個(gè)事件。這意味著用戶擁有最后的合并值,但丟失了變更的歷史記錄。
壓縮清理策略是一項(xiàng)代價(jià)昂貴的操作,但它允許用戶清理舊事件,保持?jǐn)?shù)據(jù)庫(kù)的最后狀態(tài),其優(yōu)點(diǎn)是,如果一年后需要新的使用者,則不需要處理這一年發(fā)生的事件。
結(jié)論
在有大量數(shù)據(jù)和技術(shù)多樣的復(fù)雜環(huán)境中,為新的數(shù)據(jù)平臺(tái)提供數(shù)據(jù)是一個(gè)巨大的挑戰(zhàn)。但真正的挑戰(zhàn)是在提供這些數(shù)據(jù)的同時(shí)確保企業(yè)做出有價(jià)值決策所需的質(zhì)量。
準(zhǔn)確性、一致性、唯一性、及時(shí)性是衡量數(shù)據(jù)質(zhì)量的一些指標(biāo)。CDC代替了其他解決方案,使用戶能夠以相對(duì)簡(jiǎn)單的方式標(biāo)準(zhǔn)化數(shù)據(jù)攝取并確保數(shù)據(jù)質(zhì)量。而標(biāo)準(zhǔn)化和自動(dòng)化是提高任何流程質(zhì)量的關(guān)鍵。
原文標(biāo)題:Data Platform: Building an Enterprise CDC Solution,作者:Miguel Garcia,Dario Cazas Pernas
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】