分布式鎖系列(2) 基于Redis & lua無(wú)鎖化

1.概述

1.1 背景

分布式鎖在很多場(chǎng)景中是非常有用的原語(yǔ)委煤, 不同的進(jìn)程必須以獨(dú)占資源的方式實(shí)現(xiàn)資源共享就是一個(gè)典型的例子坑匠。

由于外圍的實(shí)現(xiàn)存在著各種各樣的問(wèn)題, Redis 作者提出了一種 RedLock算法來(lái)約定分布式鎖需要注意的事項(xiàng)肠缨。

當(dāng)前java版的實(shí)現(xiàn)是 Redisson 框架烦衣。

1.2 Redis分布式鎖的基本原則

>> 安全屬性(Safety property): 獨(dú)享(相互排斥)渐排。
在任意一個(gè)時(shí)刻史隆,只有一個(gè)客戶端持有鎖绵估。
>> 活性A(Liveness property A): 無(wú)死鎖爆捞。
即便持有鎖的客戶端崩潰(crashed)或者網(wǎng)絡(luò)被分裂(gets partitioned)奉瘤,鎖仍然可以被獲取。
>> 活性B(Liveness property B): 容錯(cuò)煮甥。 
只要大部分Redis節(jié)點(diǎn)都活著盗温,客戶端就可以獲取和釋放鎖.

1.3 單點(diǎn)問(wèn)題 & Master-Slave問(wèn)題

#基本實(shí)現(xiàn)
#加鎖
實(shí)現(xiàn)Redis分布式鎖的最簡(jiǎn)單的方法就是在Redis中創(chuàng)建一個(gè)key,
這個(gè)key有一個(gè)失效時(shí)間(TTL)成肘,以保證鎖最終會(huì)被自動(dòng)釋放掉(這個(gè)對(duì)應(yīng)特性2)卖局。
#解鎖
當(dāng)客戶端釋放資源(解鎖)的時(shí)候,會(huì)刪除掉這個(gè)key双霍。

#單點(diǎn)問(wèn)題 & Master-Slave問(wèn)題
從表面上看砚偶,似乎效果還不錯(cuò),但是這里有一個(gè)問(wèn)題:
這個(gè)架構(gòu)中存在一個(gè)嚴(yán)重的單點(diǎn)失敗問(wèn)題洒闸。如果Redis掛了怎么辦染坯?
你可能會(huì)說(shuō),可以通過(guò)增加一個(gè)slave節(jié)點(diǎn)解決這個(gè)問(wèn)題丘逸。
但這通常是行不通的单鹿。這樣做,我們不能實(shí)現(xiàn)資源的獨(dú)享,因?yàn)镽edis的主從同步通常是異步的深纲。

#Master-Slave問(wèn)題
在這種場(chǎng)景(主從結(jié)構(gòu))中存在明顯的競(jìng)態(tài):
>> 客戶端A從master獲取到鎖
>> 在master將鎖同步到slave之前仲锄,master宕掉了。
>> slave節(jié)點(diǎn)被晉級(jí)為master節(jié)點(diǎn)
>> 客戶端B取得了同一個(gè)資源被客戶端A已經(jīng)獲取到的另外一個(gè)鎖囤萤。安全失效昼窗!

1.4 Redis單機(jī)版(version > 2.6)的正確實(shí)現(xiàn)方法

1.4.1 加鎖

SET resource_name my_random_value NX PX 30000

這是一個(gè)原子命令(redis客戶端已支持)。
需注意key對(duì)應(yīng)的value是“my_random_value”(一個(gè)隨機(jī)值)涛舍,這個(gè)值在所有的客戶端必須是唯一的澄惊。

1.4.2 解鎖

value的值必須是隨機(jī)數(shù)主要是為了更安全的釋放鎖,釋放鎖的時(shí)候使用腳本告訴Redis:
只有key存在并且存儲(chǔ)的值和我指定的值一樣才能告訴我刪除成功。

#為保證兩個(gè)操作的原子性, 這里需要使用 lua 腳本實(shí)現(xiàn)掸驱。
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

#解鎖時(shí), 校驗(yàn) value 是否一致的原因
假設(shè)客戶端A取得資源鎖肛搬,但是緊接著被一個(gè)其他操作阻塞了,當(dāng)客戶端A運(yùn)行完畢其他操作后要釋放鎖時(shí)毕贼,
原來(lái)的鎖早已超時(shí)并且被Redis自動(dòng)釋放温赔,并且在這期間資源鎖又被客戶端B再次獲取到。
如果僅使用DEL命令將key刪除鬼癣,那么這種情況就會(huì)把客戶端B的鎖給刪除掉陶贼。
使用Lua腳本就不會(huì)存在這種情況,因?yàn)槟_本僅會(huì)刪除value等于客戶端A的value的key(value相當(dāng)于客戶端的一個(gè)簽名)待秃。

1.5 Redis 官網(wǎng)關(guān)于鎖的探討

1.5.1 加鎖

1.5.1.1 Redlock算法

假設(shè)有5個(gè)Redis master(防止單點(diǎn)故障)拜秧。這些節(jié)點(diǎn)完全互相獨(dú)立,不存在主從復(fù)制或者其他集群協(xié)調(diào)機(jī)制章郁。
在每個(gè)實(shí)例上使用與在Redis單實(shí)例下獲取和釋放鎖獲取和釋放鎖的方法枉氮。

#為了取到鎖,客戶端應(yīng)該執(zhí)行以下操作:
>> 1.獲取當(dāng)前Unix時(shí)間暖庄,以毫秒為單位聊替。
>> 2.依次嘗試從N個(gè)實(shí)例,使用相同的key和隨機(jī)值獲取鎖培廓。
當(dāng)向Redis設(shè)置鎖時(shí),客戶端應(yīng)該設(shè)置一個(gè)網(wǎng)絡(luò)連接和響應(yīng)超時(shí)時(shí)間惹悄,這個(gè)超時(shí)時(shí)間應(yīng)該小于鎖的失效時(shí)間。
例如你的鎖自動(dòng)失效時(shí)間為10秒医舆,則超時(shí)時(shí)間應(yīng)該在5-50毫秒之間俘侠。
這樣可以避免服務(wù)器端Redis已經(jīng)掛掉的情況下,客戶端還在死死地等待響應(yīng)結(jié)果蔬将。
如果服務(wù)器端沒(méi)有在規(guī)定時(shí)間內(nèi)響應(yīng)爷速,客戶端應(yīng)該盡快嘗試另外一個(gè)Redis實(shí)例。
>> 3.客戶端使用當(dāng)前時(shí)間減去開始獲取鎖時(shí)間(步驟1記錄的時(shí)間)就得到獲取鎖使用的時(shí)間霞怀。
當(dāng)且僅當(dāng)從大多數(shù)(這里是3個(gè)節(jié)點(diǎn))的Redis節(jié)點(diǎn)都取到鎖惫东,
并且使用的時(shí)間小于鎖失效時(shí)間時(shí),鎖才算獲取成功毙石。
>> 4.如果取到了鎖廉沮,key的真正有效時(shí)間等于有效時(shí)間減去獲取鎖所使用的時(shí)間(步驟3計(jì)算的結(jié)果)。
>> 5.如果因?yàn)槟承┰蛐炀兀@取鎖失敗(沒(méi)有在至少N/2+1個(gè)Redis實(shí)例取到鎖或者取鎖時(shí)間已經(jīng)超過(guò)了有效時(shí)間), 
客戶端應(yīng)該在所有的Redis實(shí)例上進(jìn)行解鎖(即便某些Redis實(shí)例根本就沒(méi)有加鎖成功)滞时。

1.5.1.2 系統(tǒng)時(shí)鐘的影響 & 自動(dòng)續(xù)約機(jī)制

#算法基于這樣一個(gè)假設(shè)
雖然多個(gè)進(jìn)程之間沒(méi)有時(shí)鐘同步,但每個(gè)進(jìn)程都以相同的時(shí)鐘頻率前進(jìn)滤灯,
時(shí)間差相對(duì)于失效時(shí)間來(lái)說(shuō)幾乎可以忽略不計(jì)坪稽。
每個(gè)計(jì)算機(jī)都有一個(gè)本地時(shí)鐘曼玩,我們可以容忍多個(gè)計(jì)算機(jī)之間有較小的時(shí)鐘漂移。

#注意點(diǎn) (時(shí)鐘漂移 & 閏秒現(xiàn)象 ---> 正確配置NTP):
只有在鎖的有效時(shí)間(在步驟3計(jì)算的結(jié)果)范圍內(nèi)客戶端能夠做完它的工作窒百,
鎖的安全性才能得到保證(鎖的實(shí)際有效時(shí)間通常要比設(shè)置的短黍判,因?yàn)橛?jì)算機(jī)之間有時(shí)鐘漂移的現(xiàn)象)。

#自動(dòng)續(xù)約(Redisson使用了watchdog機(jī)制來(lái)實(shí)現(xiàn))
>> 在工作進(jìn)行的過(guò)程中篙梢,當(dāng)發(fā)現(xiàn)鎖剩下的有效時(shí)間很短時(shí)顷帖,
可以再次向redis的所有實(shí)例發(fā)送一個(gè)Lua腳本,讓key的有效時(shí)間延長(zhǎng)一點(diǎn)(前提還是key存在并且value是之前設(shè)置的value)渤滞。
>> 客戶端擴(kuò)展TTL時(shí)必須像首次取得鎖一樣在大多數(shù)實(shí)例上擴(kuò)展成功才算再次取到鎖贬墩,
并且是在有效時(shí)間內(nèi)再次取到鎖(算法和獲取鎖是非常相似的)。
>> 這樣做從技術(shù)上將并不會(huì)改變算法的正確性蔼水,所以擴(kuò)展鎖的過(guò)程中
仍然需要達(dá)到獲取到N/2+1個(gè)實(shí)例這個(gè)要求震糖,否則活性特性之一就會(huì)失效。

1.5.1.3 失敗重試(注意腦裂現(xiàn)象)

當(dāng)客戶端無(wú)法取到鎖時(shí)趴腋,應(yīng)該在一個(gè)隨機(jī)延遲后重試,
防止多個(gè)客戶端在同時(shí)搶奪同一資源的鎖(這樣會(huì)導(dǎo)致腦裂,沒(méi)有人會(huì)取到鎖)论咏。
同樣优炬,客戶端取得大部分Redis實(shí)例鎖所花費(fèi)的時(shí)間越短,腦裂出現(xiàn)的概率就會(huì)越低(必要的重試)厅贪,
所以蠢护,理想情況一下,客戶端應(yīng)該同時(shí)(并發(fā)地)向所有Redis發(fā)送SET命令养涮。

需要強(qiáng)調(diào)葵硕,當(dāng)客戶端從大多數(shù)Redis實(shí)例獲取鎖失敗時(shí),應(yīng)該盡快地釋放(部分)已經(jīng)成功取到的鎖贯吓,
這樣其他的客戶端就不必非得等到鎖過(guò)完“有效時(shí)間”才能取到懈凹。
然而,如果已經(jīng)存在網(wǎng)絡(luò)分裂悄谐,客戶端已經(jīng)無(wú)法和Redis實(shí)例通信介评,
此時(shí)就只能等待key的自動(dòng)釋放了,等于被懲罰了爬舰。

1.5.2 釋放鎖

#這個(gè)釋放鎖指的是已當(dāng)前獲取到鎖的客戶端向所有實(shí)例發(fā)送解鎖命令
釋放鎖比較簡(jiǎn)單们陆,向所有的Redis實(shí)例發(fā)送釋放鎖命令即可,不用關(guān)心之前有沒(méi)有從Redis實(shí)例成功獲取到鎖.

1.5.3 一些問(wèn)題

#redis沒(méi)設(shè)置 slave 節(jié)點(diǎn)
假設(shè)我們的redis沒(méi)用使用備份情屹。一個(gè)客戶端獲取到了3個(gè)實(shí)例的鎖坪仇。
此時(shí),其中一個(gè)已經(jīng)被客戶端取到鎖的redis實(shí)例被重啟垃你,
在這個(gè)時(shí)間點(diǎn)椅文,就可能出現(xiàn)3個(gè)節(jié)點(diǎn)沒(méi)有設(shè)置鎖喂很,此時(shí)如果有另外一個(gè)客戶端來(lái)設(shè)置鎖,
鎖就可能被再次獲取到雾袱,這樣鎖的互相排斥的特性就被破壞掉了恤筛。

#如果我們啟用了AOF持久化,情況會(huì)好很多芹橡。
我們可用使用SHUTDOWN命令關(guān)閉然后再次重啟毒坛。
因?yàn)镽edis到期是語(yǔ)義上實(shí)現(xiàn)的,所以當(dāng)服務(wù)器關(guān)閉時(shí)林说,實(shí)際上還是經(jīng)過(guò)了時(shí)間煎殷,
所有(保持鎖)需要的條件都沒(méi)有受到影響. 沒(méi)有受到影響的前提是redis優(yōu)雅的關(guān)閉。
停電了怎么辦腿箩?
如果redis是每秒執(zhí)行一次fsync豪直,那么很有可能在redis重啟之后,key已經(jīng)丟棄珠移。
理論上弓乙,如果我們想在Redis重啟地任何情況下都保證鎖的安全,我們必須開啟fsync=always的配置钧惧。
這反過(guò)來(lái)將完全破壞與傳統(tǒng)上用于以安全的方式實(shí)現(xiàn)分布式鎖的同一級(jí)別的CP系統(tǒng)的性能.

然而情況總比一開始想象的好一些暇韧。
當(dāng)一個(gè)redis節(jié)點(diǎn)重啟后,只要它不參與到任意當(dāng)前活動(dòng)的鎖浓瞪,
沒(méi)有被當(dāng)做“當(dāng)前存活”節(jié)點(diǎn)被客戶端重新獲取到,算法的安全性仍然是有保障的懈玻。

為了達(dá)到這種效果,我們只需要將新的redis實(shí)例乾颁,在一個(gè)TTL時(shí)間內(nèi)涂乌,
對(duì)客戶端不可用即可,在這個(gè)時(shí)間內(nèi)英岭,所有客戶端鎖將被失效或者自動(dòng)釋放.

使用"延遲重啟"可以在不采用持久化策略的情況下達(dá)到同樣的安全湾盒,
然而這樣做有時(shí)會(huì)讓系統(tǒng)轉(zhuǎn)化為徹底不可用。
比如大部分的redis實(shí)例都崩潰了巴席,系統(tǒng)在TTL時(shí)間內(nèi)任何鎖都將無(wú)法加鎖成功历涝。

Martin Kleppmann 與 antirez 關(guān)于 RedLock 算法的互懟
http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html
http://antirez.com/news/101

1.6 關(guān)于redis分布式鎖的結(jié)論

#使用建議
1.分布式鎖的redis采用單機(jī)部署,分布式鎖專用
2.根據(jù)RedLock算法思想漾唉,意思是不能只在一個(gè)redis實(shí)例上創(chuàng)建鎖荧库,應(yīng)該是在多個(gè)redis實(shí)例上創(chuàng)建鎖,**n / 2 + 1**赵刑,
必須在大多數(shù)redis節(jié)點(diǎn)上都成功創(chuàng)建鎖分衫,才能算這個(gè)整體的RedLock加鎖成功,避免說(shuō)僅僅在一個(gè)redis實(shí)例上加鎖而帶來(lái)的問(wèn)題般此。
要求: 搭建幾臺(tái)獨(dú)立的redis機(jī)器, 互相之間不通信, 不構(gòu)成主從/哨兵/集群關(guān)系.
3.如果對(duì)鎖比較關(guān)注蚪战,一致性要求比較高牵现,可以使用ZK實(shí)現(xiàn)的分布式鎖

#其他方案
1.如果考慮各種網(wǎng)絡(luò)、宕機(jī)等原因邀桑,很多問(wèn)題需要考慮瞎疼,問(wèn)題會(huì)變的復(fù)雜,
其實(shí)分布式鎖的應(yīng)用場(chǎng)景不多壁畸,很多情況可以繞開分布式鎖贼急,使用其他方式解決,比如 隊(duì)列捏萍,異步太抓,響應(yīng)式
2.分布式鎖的場(chǎng)景,更多的應(yīng)用是一個(gè)操作不能同時(shí)多處進(jìn)行令杈,不能短時(shí)間內(nèi)重復(fù)執(zhí)行走敌,需要冪等操作等場(chǎng)景,
比如:防止快速的重復(fù)提交逗噩,mq與定時(shí)任務(wù)雙線更改狀態(tài)掉丽,防止消息重復(fù)消費(fèi) 等等。
這些情況一般使用setNx即可解決异雁。
3.減庫(kù)存其實(shí)也用不到分布式鎖, 可用redis+lua實(shí)現(xiàn)机打。

2.分布式鎖的開源實(shí)現(xiàn)框架-Redisson

2.1 概述

redisson 是 redis 官方的分布式鎖組件。

#Redisson的一些特點(diǎn)
1.redisson所有指令都通過(guò)lua腳本執(zhí)行片迅,redis支持lua腳本原子性執(zhí)行
2.redisson設(shè)置一個(gè)key的默認(rèn)過(guò)期時(shí)間為30s,如果某個(gè)客戶端持有一個(gè)鎖超過(guò)了30s怎么辦?
>> redisson中有一個(gè)watchdog的概念皆辽,翻譯過(guò)來(lái)就是看門狗柑蛇,
它會(huì)在你獲取鎖之后,每隔10秒幫你把key的超時(shí)時(shí)間設(shè)為30s
>> 這樣的話驱闷,就算一直持有鎖也不會(huì)出現(xiàn)key過(guò)期了耻台,其他線程獲取到鎖的問(wèn)題了。
3.redisson的“看門狗”邏輯保證了沒(méi)有死鎖發(fā)生空另。
如果機(jī)器宕機(jī)了盆耽,看門狗也就沒(méi)了。
此時(shí)就不會(huì)延長(zhǎng)key的過(guò)期時(shí)間扼菠,到了30s之后就會(huì)自動(dòng)過(guò)期了摄杂,其他線程可以獲取到鎖。

#ps
lua 腳本的執(zhí)行是原子性的循榆,再加上 Redis 執(zhí)行命令是單線程的析恢,
所以在 lua 腳本執(zhí)行完之前,其他的命令都得等著秧饮。
Redisson中的watchdog.png

https://www.cnblogs.com/thisiswhy/p/12596069.html (這里有Redisson 實(shí)現(xiàn)分布式鎖的分析, 挺好的, 本文不再分析)

2.2 基于lua腳本的無(wú)鎖化 or 基于 Redisson 的分布式鎖控制并發(fā)

package com.zy.redis5.single;

import org.assertj.core.util.Lists;
import org.junit.Test;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RScript;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 此處 demo 以 扣減庫(kù)存為例, 給出了兩種分布式解決方案
 * 方案1:
 *  先將商品及庫(kù)存數(shù)全量加載到 redis 中, 然后借助 lua 腳本實(shí)現(xiàn)原子性的扣減庫(kù)存, 注意這里的原子性是從 redis 中扣減庫(kù)存
 * 方案2:
 *  借助 redisson 的分布式鎖框架, 獲取全局資源操作權(quán)限, 然后操作 DB 庫(kù)存, 由于首先于 DB 的 qps, 所以并發(fā)效果并不會(huì)很好
 *  Redis當(dāng)做分布式鎖服務(wù)器時(shí)映挂,可使用獲取鎖和釋放鎖的響應(yīng)時(shí)間泽篮,每秒鐘可用執(zhí)行多少次 acquire / release 操作作為性能指標(biāo)。
 *  
 * 說(shuō)明:
 *  可以自行寫一個(gè) controller, 啟動(dòng)一個(gè)項(xiàng)目, 借助 jmeter 等工具, 驗(yàn)證下并發(fā)情況
 */
public class RedisSingleAtomicLuaOrDistributedLock {

    private static RedissonClient client;
    private static Codec codec;
    private static final String KEY = "apple";
    private static final String LOCK_KEY = "lockKey";
    private static List<Object> keyList = Lists.newArrayList();
    private int count = 20;

    static {
        Config config = new Config();
        config.useSingleServer()
                .setDatabase(10)
                .setAddress("redis://192.168.0.156:6379");

        client = Redisson.create(config);
        // FIXME 這里定義了 StringCodec 類型的編解碼器, 是因?yàn)槠淠J(rèn)的編解碼器是: MarshallingCodec
        // FIXME 而當(dāng)使用 lua 腳本時(shí), 要調(diào)用 lua 的 tonumber 函數(shù) 將庫(kù)存(string類型) 轉(zhuǎn)為 number 類型時(shí),
        // FIXME 如果用默認(rèn)的編解碼器, 將會(huì)得到 nil 的結(jié)果, 會(huì)出錯(cuò).
        // FIXME 故這里使用了 StringCodec 來(lái)解決, 也可以用 IntegerCodec 或 LongCodec.
        codec = StringCodec.INSTANCE;
        keyList.add(KEY);
    }

    /**************************** 方案1: 將數(shù)據(jù)全量加載至 redis 中, 在 redis 中扣減庫(kù)存, 借助 lua 腳本控制并發(fā) *******************************/
    @Test
    public void step01() {
        String luaScript = "return redis.call('set',KEYS[1],ARGV[1]);";
        Object result = client.getScript(codec).eval(RScript.Mode.READ_WRITE, luaScript, RScript.ReturnType.VALUE, keyList, 999);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>..");
        System.out.println(result);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>..");
    }

    @Test
    public void step02() {
        String luaScript = "return redis.call('get', KEYS[1]);";
        Object result = client.getScript(codec).eval(RScript.Mode.READ_ONLY, luaScript, RScript.ReturnType.VALUE, keyList);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>..");
        System.out.println(result);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>..");
    }

    @Test
    public void step03() {
        String luaScript =
                "if (redis.call('exists', KEYS[1]) == 0) then " +
                        "return 0; " +
                        "end;" +
                        "local count = redis.call('get', KEYS[1]); " +
                        "local decrementCount = ARGV[1]; " +
                        "local a = tonumber(count); " +
                        "local b = tonumber(decrementCount); " +
                        "if (a < b) then " +
                        "return 0; " +
                        "end; " +
                        "redis.call('set', KEYS[1], (a - b)); " +
                        "return 1; ";
        Object result = client.getScript(codec).eval(RScript.Mode.READ_ONLY, luaScript, RScript.ReturnType.VALUE, keyList, 3);

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>..");
        System.out.println(result);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>..");
    }

    /**************************** 方案2: 借助 redis 分布式鎖 腳本控制并發(fā) *******************************/
    @Test
    public void fn04() throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();
        int tobeDecreasedCount = 3;

        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                RLock lock = client.getLock(LOCK_KEY);
                boolean b = lock.tryLock();
                if (b) {
                    try {
                        int count = getCount();
                        if (count > tobeDecreasedCount) {
                            decreaseCount(count, tobeDecreasedCount);
                        }
                    } finally {
                        lock.unlock();
                    }
                }
            });
        }

        TimeUnit.SECONDS.sleep(10L);
        System.out.println("剩余庫(kù)存量是: " + getCount());
    }

    private int getCount() {
        return count;
    }

    private void decreaseCount(int count, int no) {
        this.count = count - no;
    }
}

參考資料
http://redis.cn/topics/distlock.html
https://redis.io/topics/distlock

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柑船,一起剝皮案震驚了整個(gè)濱河市帽撑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鞍时,老刑警劉巖亏拉,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異寸癌,居然都是意外死亡专筷,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蒸苇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)磷蛹,“玉大人,你說(shuō)我怎么就攤上這事溪烤∥犊龋” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵檬嘀,是天一觀的道長(zhǎng)槽驶。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鸳兽,這世上最難降的妖魔是什么掂铐? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮揍异,結(jié)果婚禮上全陨,老公的妹妹穿的比我還像新娘。我一直安慰自己衷掷,他們只是感情好辱姨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著戚嗅,像睡著了一般雨涛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上懦胞,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天替久,我揣著相機(jī)與錄音,去河邊找鬼医瘫。 笑死侣肄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的醇份。 我是一名探鬼主播稼锅,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼吼具,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了矩距?” 一聲冷哼從身側(cè)響起拗盒,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锥债,沒(méi)想到半個(gè)月后陡蝇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哮肚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年登夫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片允趟。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡恼策,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出潮剪,到底是詐尸還是另有隱情涣楷,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布抗碰,位于F島的核電站狮斗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弧蝇。R本人自食惡果不足惜碳褒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望看疗。 院中可真熱鬧骤视,春花似錦、人聲如沸鹃觉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)盗扇。三九已至,卻和暖如春沉填,著一層夾襖步出監(jiān)牢的瞬間疗隶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工翼闹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斑鼻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓猎荠,卻偏偏與公主長(zhǎng)得像坚弱,于是被迫代替她去往敵國(guó)和親蜀备。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355