Elasticsearch初探


原文: Elasticsearch初探
date: 2018-01-01 12:39:04


[TOC]

前言

最近這個好火, 簡單體驗下

一: 安裝和啟動

ElasticSearch下載

官方地址: https://www.elastic.co/cn/downloads/elasticsearch

github: https://github.com/elastic/elasticsearch

下載或clone后解壓

單實例節點啟動

# cd elasticsearch目錄下
bin/elasticsearch
bin/elasticsearch -d # 后臺啟動

默認端口9200, 啟動完成后訪問http://ip:9200 即可查看到節點信息

啟動中我遇到兩個錯誤:

錯誤一:

can not run elasticsearch as root  
# 不能以root用戶啟動
[root@01 bin]# groupadd xiefy
[root@01 bin]# useradd xiefy -g xiefy -p 123123
[root@01 bin]# chown -R xiefy:xiefy elasticsearch

錯誤二:

ERROR: [2] bootstrap checks failed
[1]: max file descriptors [65535] for elasticsearch process is too low, increase to at least [65536]
[2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

-- 錯誤[1]: max file descriptors過小
-- 錯誤[2]: max_map_count過小, max_map_count文件包含限制一個進程可以擁有的VMA(虛擬內存區域)的數量,系           統默認是65530,修改成262144
# 切換到root用戶
vi /etc/security/limits.conf

# 添加如下
* soft nofile 65536
* hard nofile 65536
# 切換到root用戶
vi /etc/sysctl.conf

# 添加如下
vm.max_map_count=655360
# 重新加載配置文件或重啟
sysctl -p # 從配置文件“/etc/sysctl.conf”加載內核參數設置

elasticsearch-head插件安裝

elasticsearch-head 是一個用于瀏覽Elastic Search集群并與之進行交互(操作和管理)的web界面

GitHub: https://github.com/mobz/elasticsearch-head

要使用elasticsearch-head, 需要具備nodejs環境:

nodejs安裝

下載源碼: https://nodejs.org/en/download/

安裝方式有多種, 我用源碼安裝方式(包含npm)

  1. 下載源碼:

    https://nodejs.org/dist/v8.9.4/node-v8.9.4.tar.gz
    
  2. 解壓源碼:

    tar xzvf node-v* && cd node-v*
    
  3. 安裝必要的編譯軟件

    sudo yum install gcc gcc-c++
    
  4. 編譯

    ./configure
    make
    
  5. 編譯&安裝

    sudo make install
    
  6. 查看版本

    node --version
    npm -version
    

下載或克隆elasticsearch-head后, 進入elasticsearch-head-master目錄:

  • npm install

    速度太慢可以使用淘寶鏡像: npm install -g cnpm --registry=https://registry.npm.taobao.org

  • npm run start

  • open http://localhost:9100/

這時可以訪問到頁面, 并沒有監聽到集群.

解決head插件和elasticsearch之間訪問跨域問題.

修改elasticsearch目錄下的elasticsearch.yml

# 加入以下內容
http.cors.enabled: true
http.cors.allow-origin: "*"

然后: http://localhost:9100/ 即可訪問到管理頁面.

分布式安裝啟動

elasticsearch的橫向擴展很容易: 這里建立一個主節點(node-master), 兩個隨從節點(node-1, node-2)

我提前拷貝了三個es:

[xiefy@01 elk]$ ll
total 33620
-rw-r--r-- 1 root  root  33485703 Aug 17 22:42 elasticsearch-5.5.2.tar.gz
drwxr-xr-x 7 root  root      4096 Jan  8 11:17 elasticsearch-head-master
drwxr-xr-x 9 xiefy xiefy     4096 Jan  8 10:15 elasticsearch-master
drwxr-xr-x 9 xiefy xiefy     4096 Jan  8 14:13 elasticsearch-node1
drwxr-xr-x 9 xiefy xiefy     4096 Jan  8 14:16 elasticsearch-node2
-rw-r--r-- 1 root  root    921421 Jan  8 11:14 master.zip

分別配置三個es目錄中的config/elasticsearch.yml

node-master:

cluster.name: xiefy_elastic 
node.name: node-master 
node.master: true 
network.host: 0.0.0.0 

# 除此之外, head插件需要連接到port: 9200的節點上, 還需要這個配置, 
# 用來允許 elasticsearch-head 運行時的跨域
http.cors.enabled: true
http.cors.allow-origin: "*"

node-1:

cluster.name: xiefy_elastic 
node.name: node-1
network.host: 0.0.0.0
http.port: 9201 
discovery.zen.ping.unicast.hosts: ["127.0.0.1"]

node-2: 參考node-1

相關配置解釋:

  • cluster.name: 集群名稱, 默認是elasticsearch

  • node.name: 節點名稱, 默認隨機分配

  • node.master: 是否是主節點, 默認情況下可不寫, 第一個起來的就是Master, 掛掉后會重新選舉Master

  • network.host: 默認情況下只允許本機通過localhost或127.0.0.1訪問, 為了測試方便,

    我需要遠程訪問所以配成了0.0.0.0

  • http.port: 默認為9200, 同一個服務器下啟動多個es節點, 默認端口號會從9200默認遞增1, 這里我手動指定了

  • discovery.zen.ping.unicast.hosts: ["host1", "host2"]

    Elasticsearch默認使用服務發現(Zen discovery)作為集群節點間發現和通信的機制, 當啟動新節點時,通過這個ip列表進行節點發現,組建集群.

分別啟動三個es實例和head插件:

訪問http://ip:9100:

es三個節點集群

二: 基礎概念

ElasticSearch與關系型數據庫的一些術語比較

關系型數據庫 Ela
Database Index
Table Type
Row Document
Column Field
Schema Mapping
Index Everything is indexed
SQL` Query DSL
select * from table... GET http://...
update tables set... PUT http://...

Server:

  • Node: 一個server實例
  • Cluster:多個node組成cluster
  • Shard:數據分片,一個index可能會有多個shards,不同shards可能在不同nodes
  • Replica:shard的備份,有一個primary shard,其余的叫做replica shards
  • Gateway:管理cluster狀態信息

Shards & Replicas

副本很重要, 主要有兩個原因:

  • 它在分片/節點發生故障時來保障高可用性, 因此, 副本分片永遠不會和主/原始分片分配在同一個節點中
  • 它允許擴展搜索量和吞吐量, 因為可以在所有副本上并行執行搜索

可以在創建索引時為索引定義分片和副本的數量。
創建索引后,可以隨時動態更改副本數,但不能再更改分片數。

每個Elasticsearch分片都是Lucene索引, 在一個Lucene上的最大文檔數量大約21億(Integer.MAX_VALUE-128 )

THE REST API:

elasticsearh提供了豐富的REST API來與集群交互, 這些API包括:

  • 檢查你的集群,節點和索引的健康情況,狀態和統計
  • 管理你的集群,節點,索引數據和metadata
  • 針對索引執行CURD(創建,讀取,更新,刪除)和搜索操作
  • 執行高級的搜索操作,例如分頁,排序,過濾,聚合,腳本等等。

通過_cat可以查看到很多資源:

curl -XGET 'localhost:9200/_cat'

=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/tasks
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates

偷偷看一眼集群健康狀態: curl -XGET 'localhost:9200/_cat/health?v'

節點的信息: curl -XGET 'localhost:9200/_cat/nodes?v&pretty'

而關于和集群索引和文檔的交互, 放在下一章.

三: 基礎用法

索引創建

  • 方式一: head插件可以直接新建/刪除/查詢索引(Index)

  • 方式二: 通過rest api

    # 新建
    curl -X PUT 'localhost:9200/book'
    
    # 刪除
    curl -X DELETE 'localhost:9200/book'
    
    # 查看當前節點下所有Index
    curl -X GET 'http://localhost:9200/_cat/indices?v'
    

這樣創建的Index是沒有結構的. 可以看到索引信息的mappings:{}

下面來定義一個有結構映射的Index

PUT http://ip:9200/people

{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 1
    },
    "mappings": {
        "man": {
            "properties": {
                "name": {"type": "text"},
                "country": {"type": "keyword"},
                "age": {"type": "integer"},
                "birthday": {
                    "type": "date",
                    "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
                }
            }
        },
        "women": {
        }
    }
}

里面設置了分片數, 備份數, 一個Index和兩個type的結構映射.

插入數據

PUT http://47.94.210.157:9200/people/man/1

指定ID為1

{
    "name": "伊布",
    "country": "瑞典",
    "age": 30,
    "birthday": "1988-12-12"
}

如果不指定ID, 會生成為隨機字符串, 此時需要改為POST方式 POST http://47.94.210.157:9200/people/man

神奇的一點, es不會限制你在創建一個文檔之前索引和類型必須存在, 不存在時, es會自動創建它.

更新數據

POST http://47.94.210.157:9200/people/man/1/_update -- 修改ID為1的文檔

{
    "doc": {
        "name": "梅西梅西很像很強"
    }
}

還可以通過腳本方式更新:

{ "script": "ctx._source.age += 10" }

這里是把年齡加10, ctx.source引用了當前文檔.

索引/替換文檔

PUT http://47.94.210.157:9200/football/man/1 -- 修改ID為1的文檔

{"name": "伊布asdasadasds3333333333333333sd"}

elasticsearch將會用一個新的文檔取代(即重建索引)舊的文檔

刪除數據

刪除文檔: DELETE http://47.94.210.157:9200/people/man/1

查看數據

  • 根據ID查詢

GET http://ip:9200/people/man/1?pretty=true

{
  "_index": "people",
  "_type": "man",
  "_id": "2",
  "_version": 1,
  "found": true,
  "_source": {
    "name": "伊布",
    "country": "瑞典",
    "age": 34,
    "birthday": "1972-12-12"
  }
}

found字段表示查到與否

?

  • 查詢全部

GET http://ip:9200/Index/Type/_search

或帶排序帶分頁的查詢:

POST http://47.94.210.157:9200/people/_search

ES 默認 從0開始(from), 一次返回10條(size), 并按照_score字段倒排, 也可以自己指定

# 帶排序帶分頁的查詢
{
    "query": { "match_all": {} },
    "sort": [{
        "birthday": {"order": "desc"}
        }
    ],
    "from": 0, 
    "size": 5
}
{
  "took": 5,
  "timed_out": false,
  "_shards": {
    "total": 3,
    "successful": 3,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 1,
    "hits": [
      {
        "_index": "people",
        "_type": "man",
        "_id": "2",
        "_score": 1,
        "_source": {
          "name": "伊布",
          "country": "瑞典",
          "age": 34,
          "birthday": "1972-12-12"
        }
      },
      ....
      ....
    ]
  }
}
# 返回結果解釋
- took: 耗時(單位毫秒)
- timed_out: 是否超時
- hits: 命中的記錄數組 
  - total: 返回的記錄數
  - max_score: 最高匹配度分數
  - hits: 記錄數組
    - _score: 匹配度
  • 關鍵字查詢
{
    "query": {
        "match": {"name": "梅西"}
    }
}

注意: 這里是模糊匹配查詢, 例如查詢的值是"西2", 那么會查詢所有記錄name有"西"和name有"2"的.

關于查詢多個關鍵字之間的邏輯運算:

如果這樣寫, 兩個關鍵字會被認為是 or的關系來查詢

{
    "query": {
        "match": {"name": "西 布"}
    }
}

如果是and關系來搜索, 需要布爾查詢

{
    "query": {
        "bool": {
            "must": [
                {"match": {"name": "西"}} ,
                {"match": {"name": "2"}} 
            ]
        }
    }
}

聚合查詢

POST http://47.94.210.157:9200/people/_search

  • 分組聚合
{
    "aggs": {
        "group_by_age": {
            "terms": {"field": "age"}
        }
    }
}

返回結果中, 除了有hits數組, 還有聚合查詢的結果:

在單個請求中就可以同時查詢數據和進行多次聚合運算是非常有意義的, 他可以降低網絡請求的次數

# 聚合查詢結果
"aggregations": {
    "group_by_age": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": 24,
          "doc_count": 2
        },
        {
          "key": 32,
          "doc_count": 1
        }
      ]
    }
}

支持多個聚合, 聚合結果也會返回多個:

{
    "aggs": {
        "group_by_age": {
            "terms": {"field": "age"}
        },
        "group_by_age": {
            "terms": {"field": "age"}
        }
    }
}
  • 其他功能函數
{
    "aggs": {
        "tongji_age": {
            "stats": {"field": "age"}
        }
    }
}

stats指定計算字段, 返回結果包括了總數, 最小值, 最大值, 平均值和求和

 "aggregations": {
    "tongji_age": {
      "count": 3,
      "min": 24,
      "max": 32,
      "avg": 26.666666666666668,
      "sum": 80
    }
  }

也可指定某種類型的計算

{
    "aggs": {
        "tongji_age": {
            "sum": {"field": "age"}
        }
    }
}

返回結果

"aggregations": {
    "tongji_age": {
      "value": 80
    }
 }

四: 高級查詢

分為子條件查詢復合條件查詢:

類型:

  • 全文本查詢: 針對文本類型數據
  • 字段級別查詢: 針對結構化數據, 如日期, 數字

文本查詢

  • 模糊匹配
{
    "query": {
        "match": {"name": "西2"}
    }
}
  • 短語匹配
{
    "query": {
        "match_phrase": {"name": "西2"}
    }
}
  • 多個字段匹配
{
    "query": {
        "multi_match": {
            "query": "瑞典",
            "fields": ["name", "country"]
        }
    }
}

語法查詢: 根據語法規則查詢:

  • 帶有布爾邏輯的查詢
{
    "query": {
        "query_string": {
            "query": "(西 AND 梅) OR 布"
        }
    }
}
  • query_string 查詢多個字段
{
    "query": {
        "query_string": {
            "query": "西梅 OR  瑞典",
            "fields": ["country", "name"]
        }
    }
}

結構化數據查詢

{
    "query": {"term": { "age": 24}}
}   
  • 帶范圍的查詢
{
    "query": {
        "range": {
            "birthday": {
                "gte": "1980-01-01",
                "lte": "now"
            }
        }
    }
}   

子條件查詢

Filter Context: 用來做數據過濾, 在查詢過程中, 只判斷該文檔是否滿足條件(y or not)

Filter和Query的區別?

Filter要結合bool使用, 查詢結果會放入緩存中, 速度較Query更快

{
    "query": {
        "bool": {
            "filter": {
                "term": { "age": 24 }
            }
        }
    }
}

復合查詢

  • 固定分數查詢
{
    "query": {
        "match": {"name": "梅西"}
    }
}

可以看到查詢的結果, 每條數據的_score不同, 代表了與查詢值的匹配程度的分數.

但查詢不一定都需要產生文檔得分,特別在過濾文檔集合的時候。為了避免不必要的文檔得分計算,Elasticsearch會檢查這種情況并自動的優化這種查詢。

例如在bool查詢中返回年齡范圍在20-50的文檔, 對我來說這個范圍內的文檔都是等價的, 即他們的相關度都是一樣的(filter子句查詢,不會改變得分).

{
    "query": {
        "constant_score": {
            "filter": {
                "match": {"name": "梅西"}
            },
            "boost": 2
        }
    }
}

可以看到查詢結果, 每條數據的_score都為2, 如果不指定boost則默認為1

  • 布爾查詢
{
    "query": {
        "bool": {
            "should": [
                { "match": {"name": "梅西"}},
                { "match": {"country": "阿"}}
            ]
        }
    }   
}

這里兩個match之間是或的邏輯關系. should 如果改為 must 代表與邏輯.

我們也可以把mustshouldmust_not同時組合到bool子句。此外,我們也可以組合bool 到任何一個bool子句中,實現復雜的多層bool子句嵌套邏輯。

再加一層Filter, 只有age=32的能返回

{
    "query": {
        "bool": {
            "must": [
                { "match": {"name": "梅西"} },
                { "match": {"country": "阿根廷"}}
            ],
            "filter": [{
                "term": {
                    "age": 32
                }
            }]
        }
    }   
}

country=阿根廷的不返回:

{
    "query": {
        "bool": {
            "must_not": {
                "term": {"country": "阿根廷"}
            }
        }
    }
}

五: 關于中文分詞

為什么需要中文分詞?

首先看一下默認的分詞規則.

# 英文
GET http://47.94.210.157:9200/_analyze?analyzer=standard&pretty=true&text=hello world, elasticsearch

{
  "tokens": [
    {
      "token": "hello",
      "start_offset": 0,
      "end_offset": 5,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "world",
      "start_offset": 6,
      "end_offset": 11,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "elasticsearch",
      "start_offset": 13,
      "end_offset": 26,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

可以看到, 英文的默認分詞是根據標點符號和空格默認來分的.

再看看中文的:

GET http://47.94.210.157:9200/_analyze?analyzer=standard&pretty=true&text=你好,啊

{
  "tokens": [
    {
      "token": "你",
      "start_offset": 0,
      "end_offset": 1,
      "type": "<IDEOGRAPHIC>",
      "position": 0
    },
    {
      "token": "好",
      "start_offset": 1,
      "end_offset": 2,
      "type": "<IDEOGRAPHIC>",
      "position": 1
    },
    {
      "token": "啊",
      "start_offset": 3,
      "end_offset": 4,
      "type": "<IDEOGRAPHIC>",
      "position": 2
    }
  ]
}

可以看到ES對中文的分詞并不智能, 是將漢字全部分開了, 所以引入中文分詞.

IK

IK: https://github.com/medcl/elasticsearch-analysis-ik

The IK Analysis plugin integrates Lucene IK analyzer into elasticsearch, support customized dictionary.

安裝

1.download or compile

  • optional 1 - download pre-build package from here: https://github.com/medcl/elasticsearch-analysis-ik/releases

    unzip plugin to folder your-es-root/plugins/

  • optional 2 - use elasticsearch-plugin to install ( version > v5.5.1 ):

    ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.2.1/elasticsearch-analysis-ik-6.2.1.zip

2.restart elasticsearch

兩種安裝方式, 任選其一, 注意版本就好

Github里有Quick Example 可以看下怎么使用

需要在建立索引時指定ik分詞器, 建立索引和搜索索引字段都需要指定, 例如:

"analyzer": "ik_max_word""search_analyzer": "ik_max_word"

IK提供兩種分詞規則:

  • ik_max_word: 會將文本做最細粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,中華人民,中華,華人,人民共和國,人民,人,民,共和國,共和,和,國國,國歌”,會窮盡各種可能的組合;
  • ik_smart: 會做最粗粒度的拆分,比如會將“中華人民共和國國歌”拆分為“中華人民共和國,國歌”。

除此之外, IK也支持擴展自定義詞典, 以及熱更新.

# Test
GET http://47.94.210.157:9200/_analyze?analyzer=ik_max_word&pretty=true&text=你好,啊

{
  "tokens": [
    {
      "token": "你好",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "啊",
      "start_offset": 3,
      "end_offset": 4,
      "type": "CN_CHAR",
      "position": 1
    }
  ]
}

六: Spring Boot 集成 Elastic Search

版本參考

Spring Boot Version (x) Spring Data Elasticsearch Version (y) Elasticsearch Version (z)
x <= 1.3.5 y <= 1.3.4 z <= 1.7.2*
x >= 1.4.x 2.0.0 <=y < 5.0.0** 2.0.0 <= z < 5.0.0**
服務器集群ES版本 5.5.2
Spring boot 1.5.9.RELEASE
Elastic Search 5.5.2
log4j-core 2.7

集成步驟

  1. 引入Maven依賴:

    <properties>
     <log4j-core.version>2.7</log4j-core.version>
     <elasticsearch-version>5.5.2</elasticsearch-version>
    </properties>
    
    <dependency>
     <groupId>org.elasticsearch.client</groupId>
     <artifactId>transport</artifactId>
     <version>${elasticsearch.version}</version>
    </dependency>
    
    <!--
    <dependency>
     <groupId>org.elasticsearch</groupId>
     <artifactId>elasticsearch</artifactId>
     <version>${elasticsearch-version}</version>
    </dependency>
    -->
    

    注意: transport中依賴了elasticsearch, 但默認是2.4.6版本, 需要指定下elasticsearch的版本5.5.2

  1. 也可以直接引入:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
    

    但是spring-boot-starter-data-elasticsearch只支持到2.4.x版本的es.

    如果使用5.x.x版本ES, 就用上面那種方式單獨引入ES依賴.

?

  1. 添加配置類

    @Configuration
    public class ElasticSearchConfig {
    
        /** 集群host */
        @Value("${spring.data.elasticsearch.cluster-nodes}")
        private String clusterNodes;
    
        /** 集群名稱 */
        @Value("${spring.data.elasticsearch.cluster-name}")
        private String clusterName;
    
        @Bean
        public TransportClient client() throws UnknownHostException{
    
            InetSocketTransportAddress node = new InetSocketTransportAddress(
                    InetAddress.getByName(clusterNodes), 9300
            );
    
            Settings settings = Settings.builder().put("cluster.name", clusterName).build();
    
            TransportClient client = new PreBuiltTransportClient(settings);
            client.addTransportAddress(node);
            return client;
        }
    }
    

    application.properties中配置:

    • spring.data.elasticsearch.cluster-nodes=xxx
    • spring.data.elasticsearch.cluster-name=xxx

測試用例

簡單的CRUL操作:

github: https://github.com/thank037/elasticsearch_demo.git

@Link com.thank.elasticsearch.TestElasticSearchCRUD.java

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

推薦閱讀更多精彩內容