SpringBoot連接Elasticsearch實(shí)戰(zhàn)總結(jié)

記一次線上的elasticsearch查詢(xún)采坑

第一次使用elasticsearch忍些,于是從網(wǎng)上找輪子復(fù)制粘貼。早好輪子測(cè)試完畢轰异,上線岖沛。可是幾天下來(lái)發(fā)現(xiàn)接口響應(yīng)時(shí)間一直都偏高(默認(rèn)的超時(shí)時(shí)間是500ms)搭独,所以就不停的對(duì)代碼優(yōu)化婴削,縮短時(shí)間。但是到最后代碼已經(jīng)不能再優(yōu)化了牙肝,響應(yīng)時(shí)間依然沒(méi)有明顯的下降趨勢(shì)唉俗,甚至在高峰期會(huì)嚴(yán)重超時(shí)。接下來(lái)會(huì)慢慢講解elasticsearch使用優(yōu)化配椭。

Spring Boot添加elasticsearch依賴(lài)

有很多種方案可以選擇虫溜,1)添加spring的data依賴(lài)。2)使用elasticsearch提供的client依賴(lài)股缸。3)使用jestClient依賴(lài)衡楞。前兩種并沒(méi)有什么區(qū)別,第三種是通過(guò)http請(qǐng)求訪問(wèn)elasticsearch的敦姻。

使用elasticsearch官方依賴(lài)

使用IDE初始化Springboot時(shí)勾選elasticsearch即可瘾境,或者你也可以直接添加如下依賴(lài):

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  <version>{elasticserch.version}</version>
        </dependency>

或者到maven網(wǎng)站查找對(duì)應(yīng)elasticsearch版本的依賴(lài):

<dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>{elasticserch.version}</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>{elasticserch.version}</version>
        </dependency>

需要注意的是歧杏,一定要使用與你的elasticsearch版本一直的依賴(lài),否則可能會(huì)出錯(cuò)寄雀。

elasticsearch的配置

@Configuration
public class ElasticsearchConfig {
    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchConfig.class);

    @Value("${elasticsearch.port}")
    private String port;
    @Value("${elasticsearch.cluster.name}")
    private String clusterName;
    @Value("${elasticsearch.pool}")
    private String poolSize;
    @Value("${elasticsearch.ip}")
    private String esHost;

    @Bean(name = "transportClient")
    public TransportClient transportClient() {
        logger.info("Elasticsearch初始化開(kāi)始得滤。。盒犹。懂更。。");
        TransportClient transportClient = null;
        try {
            // 配置信息
            Settings esSetting = Settings.builder()
                    //集群名字
                    .put("cluster.name", clusterName)
                    //增加嗅探機(jī)制急膀,找到ES集群
                    .put("client.transport.sniff", true)
//                    .put("client.transport.ignore_cluster_name", true)
                    //增加線程池個(gè)數(shù)沮协,暫時(shí)設(shè)為5
                    .put("thread_pool.search.size", Integer.parseInt(poolSize))
                    .build();
            //配置信息Settings自定義
            transportClient = new PreBuiltTransportClient(esSetting);
            TransportAddress transportAddress = new TransportAddress(InetAddress.getByName(esHost), Integer.valueOf(port));
            transportClient.addTransportAddresses(transportAddress);
            logger.info("連接elasticsearch");
        } catch (Exception e) {
            logger.error("elasticsearch TransportClient create error!!", e);
        }
        return transportClient;
    }
}

低版本的elasticsearch在配置setting自定義內(nèi)容時(shí)會(huì)不一樣。使用elasticsearch節(jié)點(diǎn)連接的端口是9300卓嫂。

簡(jiǎn)單的使用:

@Component
public class ElasticsearchUtils {
    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchUtils.class);

    @Resource(name = "transportClient")
    private TransportClient transportClient;

    private static TransportClient client;

    @PostConstruct
    public void init() {
        client = this.transportClient;
    }

    /**
     * @author xiaosen
     * @description 判斷索引是否存在
     * @date 2019/1/23
     * @param
     * @return
     */
    public static boolean isIndexExist(String index) {
        IndicesExistsResponse inExistsResponse = client.admin().indices().exists(new IndicesExistsRequest(index)).actionGet();
        if (inExistsResponse.isExists()) {
            logger.info("索引:{}存在", index);
        } else {
            logger.info("索引:{}不存在", index);
        }
        return inExistsResponse.isExists();
    }
  
   public static List<Map<String, Object>> searchListData(String index, String type, long startTime, long endTime, Integer size, String fields, String sortField, boolean matchPhrase, String highlightField, String matchStr) {

        SearchRequestBuilder searchRequestBuilder = client.prepareSearch(index);
        if (StringUtils.isNotEmpty(type)) {
            searchRequestBuilder.setTypes(type.split(","));
        }
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        if (startTime > 0 && endTime > 0) {
            boolQuery.must(QueryBuilders.rangeQuery("processTime")
                    .format("epoch_millis")
                    .from(startTime)
                    .to(endTime)
                    .includeLower(true)
                    .includeUpper(true));
        }

        //搜索的的字段
        if (StringUtils.isNotEmpty(matchStr)) {
            for (String s : matchStr.split(",")) {
                String[] ss = s.split("=");
                if (ss.length > 1) {
                    if (matchPhrase == Boolean.TRUE) {
                        boolQuery.must(QueryBuilders.matchPhraseQuery(s.split("=")[0], s.split("=")[1]));
                    } else {
                        boolQuery.must(QueryBuilders.matchQuery(s.split("=")[0], s.split("=")[1]));
                    }
                }

            }
        }

        // 高亮(xxx=111,aaa=222)
        if (StringUtils.isNotEmpty(highlightField)) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            // 設(shè)置高亮字段
            highlightBuilder.field(highlightField);
            searchRequestBuilder.highlighter(highlightBuilder);
        }


        searchRequestBuilder.setQuery(boolQuery);

        if (StringUtils.isNotEmpty(fields)) {
            searchRequestBuilder.setFetchSource(fields.split(","), null);
        }
        searchRequestBuilder.setFetchSource(true);

        if (StringUtils.isNotEmpty(sortField)) {
            searchRequestBuilder.addSort(sortField, SortOrder.DESC);
        }

        if (size != null && size > 0) {
            searchRequestBuilder.setSize(size);
        }
        SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();

        long totalHits = searchResponse.getHits().totalHits;
        long length = searchResponse.getHits().getHits().length;

        if (searchResponse.status().getStatus() == 200) {
            // 解析對(duì)象
            return setSearchResponse(searchResponse, highlightField);
        }

        return null;

    }

使用JestClient

添加maven依賴(lài)(這里的elasticsearch版本比較低慷暂,而且還沒(méi)有開(kāi)放9300端口,只能使用http請(qǐng)求)

 <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>io.searchbox</groupId>
            <artifactId>jest</artifactId>
            <version>6.3.1</version>
        </dependency>

io.searchbox是操作elasticsearch的依賴(lài)晨雳,使用其9200端口行瑞。

配置文件就比較簡(jiǎn)單了:

@Configuration
@RefreshScope
public class ElasticsearchConfigure {
    private static final Logger logger = LoggerFactory.getLogger(ElasticsearchConfigure.class);

    @Value("${elasticsearch.ip}")
    private String hostAndPort;

    @Bean(name = "elasticsearchClient")
    public JestClient getJestClient() throws Exception{
        JestClientFactory factory = new JestClientFactory();
        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (X509Certificate[] arg0, String arg1) -> true).build();
      // http配置
        factory.setHttpClientConfig(new HttpClientConfig.Builder("http://"+hostAndPort).connTimeout(2000)
                .readTimeout(2000).plainSocketFactory(PlainConnectionSocketFactory.getSocketFactory())
                .sslSocketFactory(new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE))
                .multiThreaded(true).maxTotalConnection(100).defaultMaxTotalConnectionPerRoute(4).build());
        return factory.getObject();
    }
}

創(chuàng)建一個(gè)JestClientFactory并配置httpClient。

簡(jiǎn)單的一個(gè)例子:

@Resource(name = "elasticsearchClient")
    private JestClient jestClient;

public static void main(String[] args){
  FilterBuilder filterBuilder = FilterBuilders.boolFilter()
                    .must(FilterBuilders.geoDistanceRangeFilter("location")
                            .point(lat, lon).from(Constants.MIN_RADIUS).to(Constants.MAX_RADIUS))
                    .should(FilterBuilders.termFilter("status", 200), FilterBuilders.termFilter("status", 201));
  FilteredQueryBuilder filteredQueryBuilder = new FilteredQueryBuilder(null, filterBuilder);
            // 按在線時(shí)間排序餐禁,先按時(shí)間再按距離排序
            FieldSortBuilder sortBuilderField = SortBuilders.fieldSort("time").order(SortOrder.DESC);
            // 按距離排序血久,為返回客戶(hù)端距離,返回的單位:米
            GeoDistanceSortBuilder sortBuilderDis = SortBuilders.geoDistanceSort("location").point(lat, lon).unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC).geoDistance(GeoDistance.SLOPPY_ARC);
   SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  searchSourceBuilder.query(filteredQueryBuilder).sort(sortBuilderField).sort(sortBuilderDis)
                    .from((queryNearbyDto.getCurrentPage()-1)*queryNearbyDto.getPageSize())
                    .size(queryNearbyDto.getPageSize()).fetchSource(Constants.QUERY_FIELD_TTID, null);
            String query = searchSourceBuilder.toString();
  result = search(jestClient, index, Constants.ES_NEARBY_TYPE, query);
  
}



private List<Map<String, Object>> search(JestClient jestClient, String indexName, String typeName, String query) throws Exception {
        Search search = new Search.Builder(query).setSearchType(SearchType.QUERY_THEN_FETCH)
                .addIndex(indexName)
                .addType(typeName)
                .build();
        SearchResult jr = jestClient.execute(search);
        if (!jr.isSucceeded()||jr.getResponseCode()!=200){
            return null;
        }
        Long total = jr.getTotal();
        List<SearchResult.Hit<Map, Void>> maps = jr.getHits(Map.class, false);
        List<Map<String, Object>> sourceList = maps.stream().map(source -> {
            source.source.put("sort", Double.valueOf(source.sort.get(1)));
            return (Map<String, Object>)source.source;
        }).collect(Collectors.toList());
        return sourceList;
    }

其中的變量query是查詢(xún)的elasticsearch的語(yǔ)句帮非,如果你知道elasticsearch的語(yǔ)法也可以直接寫(xiě)一個(gè)json代替氧吐。

距離排序

在jestClient中有一個(gè)按距離和時(shí)間排序的例子,是先按時(shí)間排序再按距離排序末盔,目的是返回距離筑舅。es是可以按多個(gè)字段排序的,靠前的為優(yōu)先匹配排序陨舱,最后的排序結(jié)果會(huì)在返回的sort數(shù)組中返回翠拣,數(shù)組中的位置即排序的匹配位置,我這里將返回的距離提取出來(lái)放到map中游盲。

5.2的elasticsearch的api的距離排序方法如下:

GeoDistanceSortBuilder sortBuilderDis = SortBuilders.geoDistanceSort("location", lat, lon).point(lat, lon).unit(DistanceUnit.METERS).order(SortOrder.ASC).geoDistance(GeoDistance.ARC);

這里如果不想讓elasticsearch計(jì)算距離也可以用他提供的方法自己計(jì)算心剥,前提知道二者的經(jīng)緯度,調(diào)用GeoDistance的calculate方法背桐,具體使用的精確度可以按照業(yè)務(wù)要求選擇优烧,不過(guò)我有做過(guò)測(cè)試,自己計(jì)算距離和elasticsearch計(jì)算的耗時(shí)幾乎相差不多链峭,如果是額外的計(jì)算距離可以不再查一遍elasticsearch減少io消耗畦娄。

分頁(yè)

對(duì)于elasticsearch不太熟悉的同學(xué),分頁(yè)也是一個(gè)坑。

淺分頁(yè)

elasticsearch的的淺分頁(yè)from&size熙卡,from是查詢(xún)的索引位置杖刷,size是每頁(yè)數(shù)量,優(yōu)點(diǎn)類(lèi)似于mysql的limit和start驳癌。

現(xiàn)在我們可以假設(shè)在一個(gè)有 5 個(gè)主分片的索引中搜索滑燃。 當(dāng)我們請(qǐng)求結(jié)果的第一頁(yè)(結(jié)果從 1 到 10 ),每一個(gè)分片產(chǎn)生前 10 的結(jié)果颓鲜,并且返回給 協(xié)調(diào)節(jié)點(diǎn) 表窘,協(xié)調(diào)節(jié)點(diǎn)對(duì) 50 個(gè)結(jié)果排序得到全部結(jié)果的前 10 個(gè)。現(xiàn)在假設(shè)我們請(qǐng)求第 1000 頁(yè)--結(jié)果從 10001 到 10010 甜滨。所有都以相同的方式工作除了每個(gè)分片不得不產(chǎn)生前10010個(gè)結(jié)果以外乐严。 然后協(xié)調(diào)節(jié)點(diǎn)對(duì)全部 50050 個(gè)結(jié)果排序最后丟棄掉這些結(jié)果中的 50040 個(gè)結(jié)果∫履Γ可以看到昂验,在分布式系統(tǒng)中,對(duì)結(jié)果排序的成本隨分頁(yè)的深度成指數(shù)上升艾扮。這就是 web 搜索引擎對(duì)任何查詢(xún)都不要返回超過(guò) 1000 個(gè)結(jié)果的原因既琴。你翻頁(yè)的時(shí)候,翻的越深泡嘴,每個(gè) Shard 返回的數(shù)據(jù)就越多呛梆,而且協(xié)調(diào)節(jié)點(diǎn)處理的時(shí)間越長(zhǎng),非晨恼铮坑爹。所以用 ES 做分頁(yè)的時(shí)候纹腌,你會(huì)發(fā)現(xiàn)越翻到后面霎终,就越是慢。我們之前也是遇到過(guò)這個(gè)問(wèn)題升薯,用 ES 作分頁(yè)莱褒,前幾頁(yè)就幾十毫秒,翻到 10 頁(yè)或者幾十頁(yè)的時(shí)候涎劈,基本上就要 5~10 秒才能查出來(lái)一頁(yè)數(shù)據(jù)了广凸。

使用from&size的最大查詢(xún)量是10000條數(shù)據(jù),這個(gè)值可以在elasticsearch中配置文件中設(shè)置蛛枚。

scroll 深分頁(yè)

為了解決上面的問(wèn)題谅海,elasticsearch提出了一個(gè)scroll滾動(dòng)的方式。scroll 類(lèi)似于sql中的cursor蹦浦,使用scroll扭吁,每次只能獲取一頁(yè)的內(nèi)容,然后會(huì)返回一個(gè)scroll_id。根據(jù)返回的這個(gè)scroll_id可以不斷地獲取下一頁(yè)的內(nèi)容侥袜,所以scroll并不適用于有跳頁(yè)的情景.

POST /twitter/_search?scroll=1m
{
    "size": 100,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
  1. scroll=1m表示設(shè)置scroll_id保留1分鐘可用蝌诡。
  2. 使用scroll必須要將from設(shè)置為0。
  3. size決定后面每次調(diào)用_search搜索返回的數(shù)量
POST /_search/scroll 
{
    "scroll" : "1m", 
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==" 
}

然后我們可以通過(guò)數(shù)據(jù)返回的_scroll_id讀取下一頁(yè)內(nèi)容枫吧,每次請(qǐng)求將會(huì)讀取下10條數(shù)據(jù)浦旱,直到數(shù)據(jù)讀取完畢或者scroll_id保留時(shí)間截止。請(qǐng)求的接口不再使用索引名了九杂,而是 _search/scroll颁湖,其中GET和POST方法都可以使用。

search_after

Scroll 被推薦用于深度查詢(xún)尼酿,但是contexts的代價(jià)是昂貴的爷狈,不推薦用于實(shí)時(shí)用戶(hù)請(qǐng)求,而更適用于后臺(tái)批處理任務(wù)裳擎,比如群發(fā)涎永。search_after 提供了一個(gè)實(shí)時(shí)的光標(biāo)來(lái)避免深度分頁(yè)的問(wèn)題,其思想是使用前一頁(yè)的結(jié)果來(lái)幫助檢索下一頁(yè)鹿响。search_after不能自由跳到一個(gè)隨機(jī)頁(yè)面羡微,只能按照 sort values 跳轉(zhuǎn)到下一頁(yè)。使用 search_after 參數(shù)的時(shí)候惶我,from參數(shù)必須被設(shè)置成 0 或 -1 (當(dāng)然你也可以不設(shè)置這個(gè)from參數(shù))

search_after 需要使用一個(gè)唯一值的字段作為排序字段妈倔,否則不能使用search_after方法
推薦使用_uid 作為唯一值的排序字段。

GET twitter/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"date": "asc"},
        {"tie_breaker_id": "asc"}      
    ]
}

在下一次查詢(xún)的時(shí)候講返回的最后的一條數(shù)據(jù)的sort的數(shù)組放放到search_after中绸贡。

GET twitter/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [1463538857, "654323"],
    "sort": [
        {"date": "asc"},
        {"tie_breaker_id": "asc"}
    ]
}

總結(jié)

  • 深度分頁(yè)不管是關(guān)系型數(shù)據(jù)庫(kù)還是Elasticsearch還是其他搜索引擎盯蝴,都會(huì)帶來(lái)巨大性能開(kāi)銷(xiāo),特別是在分布式情況下听怕。
  • 有些問(wèn)題可以考業(yè)務(wù)解決而不是靠技術(shù)解決捧挺,比如很多業(yè)務(wù)都對(duì)頁(yè)碼有限制,google 搜索尿瞭,往后翻到一定頁(yè)碼就不行了闽烙。
  • scroll 并不適合用來(lái)做實(shí)時(shí)搜索,而更適用于后臺(tái)批處理任務(wù)声搁,比如群發(fā)黑竞。
  • search_after不能自由跳到一個(gè)隨機(jī)頁(yè)面,只能按照 sort values 跳轉(zhuǎn)到下一頁(yè)疏旨。

排序與相關(guān)性

默認(rèn)情況下很魂,返回的結(jié)果是按照 相關(guān)性 進(jìn)行排序的——最相關(guān)的文檔排在最前。每個(gè)文檔都有相關(guān)性評(píng)分檐涝,用一個(gè)正浮點(diǎn)數(shù)字段 _score 來(lái)表示 莫换。 _score 的評(píng)分越高霞玄,相關(guān)性越高。

查詢(xún)語(yǔ)句會(huì)為每個(gè)文檔生成一個(gè) _score 字段拉岁。評(píng)分的計(jì)算方式取決于查詢(xún)類(lèi)型 不同的查詢(xún)語(yǔ)句用于不同的目的: fuzzy 查詢(xún)會(huì)計(jì)算與關(guān)鍵詞的拼寫(xiě)相似程度坷剧,terms 查詢(xún)會(huì)計(jì)算 找到的內(nèi)容與關(guān)鍵詞組成部分匹配的百分比,但是通常我們說(shuō)的 relevance 是我們用來(lái)計(jì)算全文本字段的值相對(duì)于全文本檢索詞相似程度的算法喊暖。

具體score算法可以到官網(wǎng)查詢(xún)惫企。

在代碼中設(shè)置:

// 設(shè)置是否按查詢(xún)匹配度排序
searchRequestBuilder.setExplain(true);

注意:

相關(guān)項(xiàng)排序消耗資源非常大,如果不是對(duì)文本精確度要求特別高的情況下陵叽,生產(chǎn)環(huán)境不建議按相關(guān)性排序狞尔。

參考:

https://www.elastic.co/guide/en/elasticsearch/reference/6.7/search-request-search-after.html

https://blog.csdn.net/yiyiholic/article/details/81661919

https://www.souyunku.com/2017/11/06/ElasticSearch-example/

https://www.cnblogs.com/yangzhenlong/p/9118089.html

https://www.elastic.co/guide/cn/elasticsearch/guide/current/relevance-intro.html


歡迎關(guān)注公眾號(hào):


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市巩掺,隨后出現(xiàn)的幾起案子偏序,更是在濱河造成了極大的恐慌,老刑警劉巖胖替,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件研儒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡独令,警方通過(guò)查閱死者的電腦和手機(jī)端朵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)燃箭,“玉大人冲呢,你說(shuō)我怎么就攤上這事≌欣辏” “怎么了敬拓?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)裙戏。 經(jīng)常有香客問(wèn)我乘凸,道長(zhǎng),這世上最難降的妖魔是什么挽懦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮木人,結(jié)果婚禮上信柿,老公的妹妹穿的比我還像新娘。我一直安慰自己醒第,他們只是感情好渔嚷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著稠曼,像睡著了一般形病。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天漠吻,我揣著相機(jī)與錄音量瓜,去河邊找鬼。 笑死途乃,一個(gè)胖子當(dāng)著我的面吹牛绍傲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耍共,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼烫饼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了试读?” 一聲冷哼從身側(cè)響起杠纵,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钩骇,沒(méi)想到半個(gè)月后比藻,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伊履,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年韩容,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片唐瀑。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡群凶,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哄辣,到底是詐尸還是另有隱情请梢,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布力穗,位于F島的核電站毅弧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏当窗。R本人自食惡果不足惜够坐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崖面。 院中可真熱鬧元咙,春花似錦、人聲如沸巫员。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)简识。三九已至赶掖,卻和暖如春感猛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煮落,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓拷泽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親袖瞻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子司致,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容