druid

Druid.io(以下簡稱Druid)是面向海量數據的、用于實時查詢與分析的OLAP存儲系統。Druid的四大關鍵特性總結如下:

  1. 亞秒級的OLAP查詢分析。Druid采用了列式存儲、倒排索引、位圖索引等關鍵技術,能夠在亞秒級別內完成海量數據的過濾、聚合以及多維分析等操作。

  2. 實時流數據分析。區別于傳統分析型數據庫采用的批量導入數據進行分析的方式,Druid提供了實時流數據分析,采用LSM(Long structure merge)-Tree結構使Druid擁有極高的實時寫入性能;同時實現了實時數據在亞秒級內的可視化。

  3. 豐富的數據分析功能。針對不同用戶群體,Druid提供了友好的可視化界面、類SQL查詢語言以及REST 查詢接口。

  4. 高可用性與高可拓展性。Druid采用分布式、SN(share-nothing)架構,管理類節點可配置HA,工作節點功能單一,不相互依賴,這些特性都使得Druid集群在管理、容錯、災備、擴容等方面變得十分簡單。

1 為什么會有Druid

大數據技術從最早的Hadoop項目開始已經有十多年的歷史了,而Druid是在2013年年底才開源的,雖然目前還不是Apache頂級項目,但是作為后起之秀,依然吸引了大量用戶的目光,社區也非?;钴S。那么,為什么會有Druid,而Druid又解決了傳統大數據處理框架下的哪些“痛點”問題,下面我們來一一解答。

大數據時代,如何從海量數據中提取有價值的信息,是一個亟待解決的難題。針對這個問題,IT巨頭們已經開發了大量的數據存儲與分析類產品,比如IBM Netezza、HP Vertica、EMC GreenPlum等,但是他們大多是昂貴的商業付費類產品,業內使用者寥寥。

而受益于近年來高漲的開源精神,業內出現了眾多優秀的開源項目,其中最有名的當屬Apache Hadoop生態圈。時至今日,Hadoop已經成為了大數據的“標準”解決方案,但是,人們在享受Hadoop便捷數據分析的同時,也必須要忍受Hadoop在設計上的許多“痛點”,下面就羅列三方面的問題:

  1. 何時能進行數據查詢?對于Hadoop使用的Map/Reduce批處理框架,數據何時能夠查詢沒有性能保證。

  2. 隨機IO問題。Map/Reduce批處理框架所處理的數據需要存儲在HDFS上,而HDFS是一個以集群硬盤作為存儲資源池的分布式文件系統,那么在海量數據的處理過程中,必然會引起大量的讀寫操作,此時隨機IO就成為了高并發場景下的性能瓶頸。

  3. 數據可視化問題。HDFS是一個優秀的分布式文件系統,但是對于數據分析以及數據的即席查詢,HDFS并不是最優的選擇。

傳統的大數據處理架構Hadoop更傾向于一種“后臺批處理的數據倉庫系統”,其作為海量歷史數據保存、冷數據分析,確實是一個優秀的通用解決方案,但是如何保證高并發環境下海量數據的查詢分析性能,以及如何實現海量實時數據的查詢分析與可視化,Hadoop確實顯得有些無能為力。

2 Druid直面痛點

Druid的母公司MetaMarket在2011年以前也是Hadoop的擁躉者,但是在高并發環境下,Hadoop并不能對數據可用性以及查詢性能給出產品級別的保證,使得MetaMarket必須去尋找新的解決方案,當嘗試使用了各種關系型數據庫以及NoSQL產品后,他們覺得這些已有的工具都不能解決他們的“痛點”,所以決定在2011年開始研發自己的“輪子”Druid,他們將Druid定義為“開源、分布式、面向列式存儲的實時分析數據存儲系統”,所要解決的“痛點”也是上文中反復提及的“在高并發環境下,保證海量數據查詢分析性能,同時又提供海量實時數據的查詢、分析與可視化功能”。

在介紹Druid架構之前,我們先結合有關OLAP的基本原理來理解Druid中的一些基本概念。

1 數據

Druid數據

以圖3.1為例,結合我們在第一章中介紹的OLAP基本概念,按列的類型上述數據可以分成以下三類:

  1. 時間序列(Timestamp),Druid既是內存數據庫,又是時間序列數據庫,Druid中所有查詢以及索引過程都和時間維度息息相關。Druid底層使用絕對毫秒數保存時間戳,默認使用ISO-8601格式展示時間(形如:yyyy-MM-ddThh:mm:sss.SSSZ,其中“Z”代表零時區,中國所在的東八區可表示為+08:00)。

  2. 維度列(Dimensions),Druid的維度概念和OLAP中一致,一條記錄中的字符類型(String)數據可看作是維度列,維度列被用于過濾篩選(filter)、分組(group)數據。如圖3.1中page、Username、Gender、City這四列。

  3. 度量列(Metrics),Druid的度量概念也與OLAP中一致,一條記錄中的數值(Numeric)類型數據可看作是度量列,度量列被用于聚合(aggregation)和計算(computation)操作。如圖3.1中的Characters Added、Characters Removed這兩列。

2 上卷

生產環境中,每天會有成百上千億的原始數據(raw data)進入到Druid中,Druid最小粒度支持毫秒級別的事件,但是在一般使用場景中,我們很少會關注如此細粒度的數據集,同時,對數據按一定規律進行聚合不僅可以節約存儲空間,亦可獲得更有價值的視圖。所以與其他OLAP類產品一樣,Druid也支持上卷(roll-up)操作。最常用的上卷操作是對時間維度進行聚合,比如對圖3.2中的數據按照小時粒度進行聚合可以得到圖3.3,圖3.3相對于圖3.2來說,顯得更加直觀,也更有助于分析人員掌握全局態勢。不過,上卷操作也會帶來信息量的丟失,因為上卷的粒度會變成最小數據可視化粒度,即毫秒級別的原始數據,如果按照分鐘粒度進行roll-up,那么入庫之后我們能夠查看數據的最小粒度即為分鐘級別。


上卷

3 分片

Druid是時間序列數據庫,也存在分片(Sharding)的概念。Druid對原始數據按照時間維度進行分片,每一個分片稱為段(Segment)。
Segment是Druid中最基本的數據存儲單元,采用列式(columnar)存儲某一個時間間隔(interval)內某一個數據源(dataSource)的部分數據所對應的所有維度值、度量值、時間維度以及索引。

Segment數據結構

時間維度(絕對毫秒數)和度量值在底層使用整數(Integer)或者浮點數(floating point)數組進行壓縮存儲,默認采用LZ4壓縮算法(可選LZF、uncompressed)。

維度列使用字典編碼、位圖索引以及相應壓縮算法,包含如下三種數據結構,以圖3.1中數據舉例:


segment

為什么使用這三種數據結構,它們有哪些優勢:

  1. 使用字典編碼可以減少字符串數據的存儲空間,同時表達更加簡便、緊湊;

  2. 位圖索引,結構類似于倒排索引,可以快速地進行按位邏輯操作;

  3. 位圖索引尺寸=列基數 *數據行數,對于高基數列,我們在第二章中也詳細介紹了很多位圖索引壓縮算法,Druid中實現了Concisebitmap compression以及Roaring bitmap compression,默認使用Concise。

Segment存儲結構
Segment邏輯名稱形如“datasource_intervalStart_intervalEnd_version_partitionNum”,:

dataSource:數據源;

intervalStart、intervalEnd:時間間隔的起止,使用ISO-8601格式;

version:版本號,默認v1,用于區分多次加載同一數據對應的Segment;

partitionNumber:分區編號,在每個時間間隔內,根據數據量的大小一個Segment內部可能會有多個分區,官方推薦通過控制時間間隔粒度或者partition的個數來保證每個partition的大小在300Mb-700Mb之間,從而獲得最優的加載與查詢性能。

這里寫圖片描述
這里寫圖片描述
這里寫圖片描述

4 集群節點

Druid集群包含多種節點類型,分別是Historical Node、Coordinator Node、Broker Node、Indexing Service Node(包括Overlord、MiddleManager和Peon)以及Realtime Node(包括Firehose和Plumber)。

Druid將整個集群切分成上述角色,有兩個目的:第一,劃分Historical Node和Realtime Node,是將歷史數據的加載與實時流數據處理切割開來,因為二者都需要占用大量內存與CPU;第二,劃分Coordinator Node和Broker Node,將查詢需求與數據如何在集群內分布的需求切割開來,確保用戶的查詢請求不會影響數據在集群內的分布情況,從而不會造成數據“冷熱不均”,局部過熱,影響查詢性能的問題。

圖3.5給出了Druid集群內部的實時/批量數據流以及查詢請求過程。我們可以看到,實時數據到達Realtime Node,經過Indexing Service,在時間窗口內的數據會停留在Realtime Node內存中,而時間窗口外的數據會組織成Segment存儲到Deep Storage中;批量數據經過Indexing Service也會被組織成Segment存儲到Deep Storage中,同時Segment的元信息都會被注冊到元信息庫中,Coordinator Nodes會定期(默認為1分鐘)去同步元信息庫,感知新生成的Segment,并通知在線的Historical Node去加載Segment,Zookeeper也會更新整個集群內部數據分布拓撲圖。


這里寫圖片描述

當用戶需要查詢信息時,會將請求提交給Broker Node,Broker Node會請求Zookeeper獲取集群內數據分布拓撲圖,從而知曉請求應該發給哪些Historical Node以及Realtime Node,匯總各節點的返回數據并將最終結果返回給用戶。
在(三)中,我們將逐一介紹各類節點。

1 Historical Node

Historical Node的職責單一,就是負責加載Druid中非實時窗口內且滿足加載規則的所有歷史數據的Segment。每一個Historical Node只與Zookeeper保持同步,不與其他類型節點或者其他Historical Node進行通信。

根據上節知曉,Coordinator Nodes會定期(默認為1分鐘)去同步元信息庫,感知新生成的Segment,將待加載的Segment信息保存在Zookeeper中在線的Historical Nodes的load queue目錄下,當Historical Node感知到需要加載新的Segment時,首先會去本地磁盤目錄下查找該Segment是否已下載,如果沒有,則會從Zookeeper中下載待加載Segment的元信息,此元信息包括Segment存儲在何處、如何解壓以及如何如理該Segment。Historical Node使用內存文件映射方式將index.zip中的XXXXX.smoosh文件加載到內存中,并在Zookeeper中本節點的served segments目錄下聲明該Segment已被加載,從而該Segment可以被查詢。對于重新上線的Historical Node,在完成啟動后,也會掃描本地存儲路徑,將所有掃描到的Segment加載如內存,使其能夠被查詢。

2 Broker Node

Broker Node是整個集群查詢的入口,作為查詢路由角色,Broker Node感知Zookeeper上保存的集群內所有已發布的Segment的元信息,即每個Segment保存在哪些存儲節點上,Broker Node為Zookeeper中每個dataSource創建一個timeline,timeline按照時間順序描述了每個Segment的存放位置。我們知道,每個查詢請求都會包含dataSource以及interval信息,Broker Node根據這兩項信息去查找timeline中所有滿足條件的Segment所對應的存儲節點,并將查詢請求發往對應的節點。

對于每個節點返回的數據,Broker Node默認使用LRU緩存策略;對于集群中存在多個Broker Node的情況,Druid使用memcached共享緩存。對于Historical Node返回的結果,Broker Node認為是“可信的”,會緩存下來,而Real-Time Node返回的實時窗口內的數據,Broker Node認為是可變的,“不可信的”,故不會緩存。所以對每個查詢請求,Broker Node都會先查詢本地緩存,如果不存在才會去查找timeline,再向相應節點發送查詢請求。

3 Coordinator Node

Coordinator Node主要負責Druid集群中Segment的管理與發布,包括加載新Segment、丟棄不符合規則的Segment、管理Segment副本以及Segment負載均衡等。如果集群中存在多個Coordinator Node,則通過選舉算法產生Leader,其他Follower作為備份。

Coordinator會定期(默認一分鐘)同步Zookeeper中整個集群的數據拓撲圖、元信息庫中所有有效的Segment信息以及規則庫,從而決定下一步應該做什么。對于有效且未分配的Segment,Coordinator Node首先按照Historical Node的容量進行倒序排序,即最少容量擁有最高優先級,新的Segment會優先分配到高優先級的Historical Node上。由3.3.4.1節可知,Coordinator Node不會直接與Historical Node打交道,而是在Zookeeper中Historical Node對應的load queue目錄下創建待加載Segment的臨時信息,等待Historical Node去加載該Segment。

Coordinator在每次啟動后都會對比Zookeeper中保存的當前數據拓撲圖以及元信息庫中保存的數據信息,所有在集群中已被加載的、卻在元信息庫中標記為失效或者不存在的Segment會被Coordinator Node記錄在remove list中,其中也包括我們在3.3.3節中所述的同一Segment對應的新舊version,舊version的Segments同樣也會被放入到remove list中,最終被邏輯丟棄。

對于離線的Historical Node,Coordinator Node會默認該Historical Node上所有的Segment已失效,從而通知集群內的其他Historical Node去加載該Segment。但是,在生產環境中,我們會遇到機器臨時下線,Historical Node在很短時間內恢復服務的情況,那么如此“簡單粗暴”的策略勢必會加重整個集群內的網絡負載。對于這種場景,Coordinator會為集群內所有已丟棄的Segment保存一個生存時間(lifetime),這個生存時間表示Coordinator Node在該Segment被標記為丟棄后,允許不被重新分配最長等待時間,如果該Historical Node在該時間內重新上線,則Segment會被重新置為有效,如果超過該時間則會按照加載規則重新分配到其他Historical Node上。

考慮一種最極端的情況,如果集群內所有的Coordinator Node都停止服務,整個集群對外依然有效,不過新Segment不會被加載,過期的Segment也不會被丟棄,即整個集群內的數據拓撲會一直保持不變,直到新的Coordinator Node服務上線。

4 Indexing Service

Indexing Service是負責“生產”Segment的高可用、分布式、Master/Slave架構服務。主要由三類組件構成:負責運行索引任務(indexing task)的Peon,負責控制Peon的MiddleManager,負責任務分發給MiddleManager的Overlord;三者的關系可以解釋為:Overlord是MiddleManager的Master,而MiddleManager又是Peon的Master。其中,Overlord和MiddleManager可以分布式部署,但是Peon和MiddleManager默認在同一臺機器上。圖3.5給出了Indexing Service的整體架構。

Overlord
Overlord負責接受任務、協調任務的分配、創建任務鎖以及收集、返回任務運行狀態給調用者。當集群中有多個Overlord時,則通過選舉算法產生Leader,其他Follower作為備份。

Overlord可以運行在local(默認)和remote兩種模式下,如果運行在local模式下,則Overlord也負責Peon的創建與運行工作,當運行在remote模式下時,Overlord和MiddleManager各司其職,根據圖3.6所示,Overlord接受實時/批量數據流產生的索引任務,將任務信息注冊到Zookeeper的/task目錄下所有在線的MiddleManager對應的目錄中,由MiddleManager去感知產生的新任務,同時每個索引任務的狀態又會由Peon定期同步到Zookeeper中/Status目錄,供Overlord感知當前所有索引任務的運行狀況。

Overlord對外提供可視化界面,通過訪問http://:/console.html,我們可以觀察到集群內目前正在運行的所有索引任務、可用的Peon以及近期Peon完成的所有成功或者失敗的索引任務。

MiddleManager
MiddleManager負責接收Overlord分配的索引任務,同時創建新的進程用于啟動Peon來執行索引任務,每一個MiddleManager可以運行多個Peon實例。

在運行MiddleManager實例的機器上,我們可以在${ java.io.tmpdir}目錄下觀察到以XXX_index_XXX開頭的目錄,每一個目錄都對應一個Peon實例;同時restore.json文件中保存著當前所有運行著的索引任務信息,一方面用于記錄任務狀態,另一方面如果MiddleManager崩潰,可以利用該文件重啟索引任務。

Peon
Peon是Indexing Service的最小工作單元,也是索引任務的具體執行者,所有當前正在運行的Peon任務都可以通過Overlord提供的web可視化界面進行訪問。

這里寫圖片描述

5 Real-Time Node

在流式處理領域,有兩種數據處理模式,一種為Stream Push,另一種為Stream Pull。

Stream Pull
如果Druid以Stream Pull方式自主地從外部數據源拉取數據從而生成Indexing Service Tasks,我們則需要建立Real-Time Node。Real-Time Node主要包含兩大“工廠”:一個是連接流式數據源、負責數據接入的Firehose(中文翻譯為水管,很形象地描述了該組件的職責);另一個是負責Segment發布與轉移的Plumber(中文翻譯為搬運工,同樣也十分形象地描述了該組件的職責)。在Druid源代碼中,這兩個組件都是抽象工廠方法,使用者可以根據自己的需求創建不同類型的Firehose或者Plumber。Firehose和Plumber給我的感覺,更類似于Kafka_0.9.0版本后發布的Kafka Connect框架,Firehose類似于Kafka Connect Source,定義了數據的入口,但并不關心接入數據源的類型;而Plumber類似于Kafka Connect Sink,定義了數據的出口,也不關心最終輸出到哪里。

Stream Push
如果采用Stream Push策略,我們需要建立一個“copy service”,負責從數據源中拉取數據并生成Indexing Service Tasks,從而將數據“推入”到Druid中,我們在druid_0.9.1版本之前一直使用的是這種模式,不過這種模式需要外部服務Tranquility,Tranquility組件可以連接多種流式數據源,比如Spark-Streaming、Storm以及Kafka等,所以也產生了Tranquility-Storm、Tranquility-Kafka等外部組件。Tranquility-Kafka的原理與使用將在3.4節中進行詳細介紹。

6 外部拓展

Druid集群依賴一些外部組件,與其說依賴,不如說正是由于Druid開放的架構,所以用戶可以根據自己的需求,使用不同的外部組件。

Deep Storage
Druid目前支持使用本地磁盤(單機模式)、NFS掛載磁盤、HDFS、Amazon S3等存儲方式保存Segments以及索引任務日志。

Zookeeper
Druid使用Zookeeper作為分布式集群內部的通信組件,各類節點通過Curator Framework將實例與服務注冊到Zookeeper上,同時將集群內需要共享的信息也存儲在Zookeeper目錄下,從而簡化集群內部自動連接管理、leader選舉、分布式鎖、path緩存以及分布式隊列等復雜邏輯。

Metadata Storage
Druid集群元信息使用MySQL 或者PostgreSQL存儲,單機版使用derby。在Druid_0.9.1.1版本中,元信息庫druid主要包含十張表,均以“druid_”開頭,如圖3.7所示。

這里寫圖片描述

這里寫圖片描述
這里寫圖片描述
這里寫圖片描述
這里寫圖片描述
這里寫圖片描述
這里寫圖片描述
這里寫圖片描述
這里寫圖片描述

7 加載數據

對于加載外部數據,Druid支持兩種模式:實時流(real-time ingestion)和批量導入(batch ingestion)。

Real-Time Ingestion
實時流過程可以采用Apache Storm、Apache Spark Streaming等流式處理框架產生數據,再經過pipeline工具,比如Apache Kafka、ActiveMQ、RabbitMQ等消息總線類組件,使用Stream Pull 或者Stream Push模式生成Indexing Service Tasks,最終存儲在Druid中。

Batch Ingestion
批量導入模式可以采用結構化信息作為數據源,比如JSON、Avro、Parquet格式的文本,Druid內部使用Map/Reduce批處理框架導入數據。

8 高可用性

Druid高可用性可以總結以下幾點:

Historical Node
如3.3.4.1節中所述,如果某個Historical Node離線時長超過一定閾值,Coordinator Node會將該節點上已加載的Segments重新分配到其他在線的Historical Nodes上,保證滿足加載規則的所有Segments不丟失且可查詢。

Coordinator Node
集群可配置多個Coordinator Node實例,工作模式為主從同步,采用選舉算法產生Leader,其他Follower作為備份。當Leader宕機時,其他Follower能夠迅速failover。
即使當所有Coordinator Node均失效,整個集群對外依然有效,不過新Segments不會被加載,過期的Segments也不會被丟棄,即整個集群內的數據拓撲會一直保持不變,直到新的Coordinator Node服務上線。

Broker Node
Broker Node與Coordinator Node在HA部署方面一致。

Indexing Service
Druid可以為同一個Segment配置多個Indexing Service Tasks副本保證數據完整性。

Real-Time
Real-Time過程的數據完整性主要由接入的實時流語義(semantics)決定。我們在0.9.1.1版本前使用Tranquility-Kafka組件接入實時數據,由于存在時間窗口,即在時間窗口內的數據會被提交給Firehose,時間窗口外的數據則會被丟棄;如果Tranquility-Kafka臨時下線,會導致Kafka中數據“過期”從而被丟棄,無法保證數據完整性,同時這種“copy service”的使用模式不僅占用大量CPU與內存,又不滿足原子操作,所以在0.9.1.1版本后,我們使用Druid的新特性Kafka Indexing Service,Druid內部使用Kafka高級Consumer API保證exactly-once semantics,盡最大可能保證數據完整性。不過我們在使用中,依然發現有數據丟失問題。

Metadata Storage
如果Metadata Storage失效,Coordinator則無法感知新Segment的生成,整個集群中數據拓撲亦不會改變,不過不會影響老數據的訪問。

Zookeeper
如果Zookeeper失效,整個集群的數據拓撲不會改變,由于Broker Node緩存的存在,所以在緩存中的數據依然可以被查詢。

9 數據分層

Druid訪問控制策略采用數據分層(tier),有以下兩種用途:

  1. 將不同的Historical Node劃分為不同的group,從而控制集群內不同權限(priority)用戶在查詢時訪問不同group。

  2. 通過劃分tier,讓Historical Node加載不同時間范圍的數據。例如tier_1加載2016年Q1數據,tier_2加載2016年Q2數據,tier_3加載2016年Q3數據等;那么根據用戶不同的查詢需求,將請求發往對應tier的Historical Node,不僅可以控制用戶訪問請求,同時也可以減少響應請求的Historical Node數量,從而加速查詢。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容