Spring Boot+SQL/JPA實(shí)戰(zhàn)悲觀鎖和樂(lè)觀鎖

【轉(zhuǎn)載請(qǐng)注明出處】:http://www.reibang.com/p/6287599cd0fd

業(yè)務(wù)還原

首先環(huán)境是:Spring Boot 2.1.0 + data-jpa + mysql + lombok

數(shù)據(jù)庫(kù)設(shè)計(jì)

對(duì)于一個(gè)有評(píng)論功能的博客系統(tǒng)來(lái)說(shuō)梧税,通常會(huì)有兩個(gè)表:1.文章表 2.評(píng)論表沦疾。其中文章表除了保存一些文章信息等,還有個(gè)字段保存評(píng)論數(shù)量第队。我們?cè)O(shè)計(jì)一個(gè)最精簡(jiǎn)的表結(jié)構(gòu)來(lái)還原該業(yè)務(wù)場(chǎng)景哮塞。

article 文章表

字段 類(lèi)型 備注
id INT 自增主鍵id
title VARCHAR 文章標(biāo)題
comment_count INT 文章的評(píng)論數(shù)量

comment 評(píng)論表

字段 類(lèi)型 備注
id INT 自增主鍵id
article_id INT 評(píng)論的文章id
content VARCHAR 評(píng)論內(nèi)容

當(dāng)一個(gè)用戶(hù)評(píng)論的時(shí)候,1. 根據(jù)文章id獲取到文章 2. 插入一條評(píng)論記錄 3. 該文章的評(píng)論數(shù)增加并保存

代碼實(shí)現(xiàn)

首先在maven中引入對(duì)應(yīng)的依賴(lài)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

然后編寫(xiě)對(duì)應(yīng)數(shù)據(jù)庫(kù)的實(shí)體類(lèi)

@Data
@Entity
public class Article {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private Long commentCount;
}
@Data
@Entity
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private Long articleId;

    private String content;
}

接著創(chuàng)建這兩個(gè)實(shí)體類(lèi)對(duì)應(yīng)的Repository凳谦,由于spring-jpa-data的CrudRepository已經(jīng)幫我們實(shí)現(xiàn)了最常見(jiàn)的CRUD操作忆畅,所以我們的Repository只需要繼承CrudRepository接口其他啥都不用做。

public interface ArticleRepository extends CrudRepository<Article, Long> {
}
public interface CommentRepository extends CrudRepository<Comment, Long> {
}

接著我們就簡(jiǎn)單的實(shí)現(xiàn)一下Controller接口和Service實(shí)現(xiàn)類(lèi)尸执。

@Slf4j
@RestController
public class CommentController {

    @Autowired
    private CommentService commentService;

    @PostMapping("comment")
    public String comment(Long articleId, String content) {
        try {
            commentService.postComment(articleId, content);
        } catch (Exception e) {
            log.error("{}", e);
            return "error: " + e.getMessage();
        }
        return "success";
    }
}
@Slf4j
@Service
public class CommentService {
    @Autowired
    private ArticleRepository articleRepository;

    @Autowired
    private CommentRepository commentRepository;

    public void postComment(Long articleId, String content) {
        Optional<Article> articleOptional = articleRepository.findById(articleId);
        if (!articleOptional.isPresent()) {
            throw new RuntimeException("沒(méi)有對(duì)應(yīng)的文章");
        }
        Article article = articleOptional.get();

        Comment comment = new Comment();
        comment.setArticleId(articleId);
        comment.setContent(content);
        commentRepository.save(comment);

        article.setCommentCount(article.getCommentCount() + 1);
        articleRepository.save(article);
    }
}

并發(fā)問(wèn)題分析

從剛才的代碼實(shí)現(xiàn)里可以看出這個(gè)簡(jiǎn)單的評(píng)論功能的流程家凯,當(dāng)用戶(hù)發(fā)起評(píng)論的請(qǐng)求時(shí),從數(shù)據(jù)庫(kù)找出對(duì)應(yīng)的文章的實(shí)體類(lèi)Article剔交,然后根據(jù)文章信息生成對(duì)應(yīng)的評(píng)論實(shí)體類(lèi)Comment肆饶,并且插入到數(shù)據(jù)庫(kù)中,接著增加該文章的評(píng)論數(shù)量岖常,再把修改后的文章更新到數(shù)據(jù)庫(kù)中,整個(gè)流程如下流程圖葫督。

image.png

在這個(gè)流程中有個(gè)問(wèn)題竭鞍,當(dāng)有多個(gè)用戶(hù)同時(shí)并發(fā)評(píng)論時(shí),他們同時(shí)進(jìn)入步驟1中拿到Article橄镜,然后插入對(duì)應(yīng)的Comment偎快,最后在步驟3中更新評(píng)論數(shù)量保存到數(shù)據(jù)庫(kù)。只是由于他們是同時(shí)在步驟1拿到的Article洽胶,所以他們的Article.commentCount的值相同晒夹,那么在步驟3中保存的Article.commentCount+1也相同裆馒,那么原來(lái)應(yīng)該+3的評(píng)論數(shù)量,只加了1丐怯。

我們用測(cè)試用例代碼試一下

@RunWith(SpringRunner.class)
@SpringBootTest(classes = LockAndTransactionApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CommentControllerTests {
    @Autowired
    private TestRestTemplate testRestTemplate;

    @Test
    public void concurrentComment() {
        String url = "http://localhost:9090/comment";
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            new Thread(() -> {
                MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
                params.add("articleId", "1");
                params.add("content", "測(cè)試內(nèi)容" + finalI);
                String result = testRestTemplate.postForObject(url, params, String.class);
            }).start();
        }

    }
}

這里我們開(kāi)了100個(gè)線(xiàn)程喷好,同時(shí)發(fā)送評(píng)論請(qǐng)求,對(duì)應(yīng)的文章id為1读跷。

在發(fā)送請(qǐng)求前梗搅,數(shù)據(jù)庫(kù)數(shù)據(jù)為

select * from article
image.png
select count(*) comment_count from comment
image.png

發(fā)送請(qǐng)求后,數(shù)據(jù)庫(kù)數(shù)據(jù)為

select * from article
image.png
select count(*) comment_count from comment
image.png

明顯的看到在article表里的comment_count的值不是100效览,這個(gè)值不一定是我圖里的14无切,但是必然是不大于100的,而comment表的數(shù)量肯定等于100丐枉。

這就展示了在文章開(kāi)頭里提到的并發(fā)問(wèn)題哆键,這種問(wèn)題其實(shí)十分的常見(jiàn),只要有類(lèi)似上面這樣評(píng)論功能的流程的系統(tǒng)瘦锹,都要小心避免出現(xiàn)這種問(wèn)題籍嘹。

下面就用實(shí)例展示展示如何通過(guò)悲觀鎖和樂(lè)觀鎖防止出現(xiàn)并發(fā)數(shù)據(jù)問(wèn)題,同時(shí)給出SQL方案和JPA自帶方案沼本,SQL方案可以通用“任何系統(tǒng)”噩峦,甚至不限語(yǔ)言,而JPA方案十分快捷抽兆,如果你恰好用的也是JPA识补,那就可以簡(jiǎn)單的使用上樂(lè)觀鎖或悲觀鎖。最后也會(huì)根據(jù)業(yè)務(wù)比較一下樂(lè)觀鎖和悲觀鎖的一些區(qū)別

悲觀鎖解決并發(fā)問(wèn)題

悲觀鎖顧名思義就是悲觀的認(rèn)為自己操作的數(shù)據(jù)都會(huì)被其他線(xiàn)程操作辫红,所以就必須自己獨(dú)占這個(gè)數(shù)據(jù)凭涂,可以理解為”獨(dú)占鎖“。在java中synchronizedReentrantLock等鎖就是悲觀鎖贴妻,數(shù)據(jù)庫(kù)中表鎖切油、行鎖、讀寫(xiě)鎖等也是悲觀鎖名惩。

利用SQL解決并發(fā)問(wèn)題

行鎖就是操作數(shù)據(jù)的時(shí)候把這一行數(shù)據(jù)鎖住澎胡,其他線(xiàn)程想要讀寫(xiě)必須等待,但同一個(gè)表的其他數(shù)據(jù)還是能被其他線(xiàn)程操作的娩鹉。只要在需要查詢(xún)的sql后面加上for update攻谁,就能鎖住查詢(xún)的行,特別要注意查詢(xún)條件必須要是索引列弯予,如果不是索引就會(huì)變成表鎖戚宦,把整個(gè)表都鎖住。

現(xiàn)在在原有的代碼的基礎(chǔ)上修改一下锈嫩,先在ArticleRepository增加一個(gè)手動(dòng)寫(xiě)sql查詢(xún)方法受楼。

public interface ArticleRepository extends CrudRepository<Article, Long> {
    @Query(value = "select * from article a where a.id = :id for update", nativeQuery = true)
    Optional<Article> findArticleForUpdate(Long id);
}

然后把CommentService中使用的查詢(xún)方法由原來(lái)的findById改為我們自定義的方法

public class CommentService {
    ...

    public void postComment(Long articleId, String content) {
        // Optional<Article> articleOptional = articleRepository.findById(articleId);
        Optional<Article> articleOptional = articleRepository.findArticleForUpdate(articleId);

        ...
    }
}

這樣我們查出來(lái)的Article垦搬,在我們沒(méi)有將其提交事務(wù)之前,其他線(xiàn)程是不能獲取修改的艳汽,保證了同時(shí)只有一個(gè)線(xiàn)程能操作對(duì)應(yīng)數(shù)據(jù)猴贰。

現(xiàn)在再用測(cè)試用例測(cè)一下,article.comment_count的值必定是100骚灸。

利用JPA自帶行鎖解決并發(fā)問(wèn)題

對(duì)于剛才提到的在sql后面增加for update糟趾,JPA有提供一個(gè)更優(yōu)雅的方式,就是@Lock注解甚牲,這個(gè)注解的參數(shù)可以傳入想要的鎖級(jí)別义郑。

現(xiàn)在在ArticleRepository中增加JPA的鎖方法,其中LockModeType.PESSIMISTIC_WRITE參數(shù)就是行鎖丈钙。

public interface ArticleRepository extends CrudRepository<Article, Long> {
    ...

    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("select a from Article a where a.id = :id")
    Optional<Article> findArticleWithPessimisticLock(Long id);
}

同樣的只要在CommentService里把查詢(xún)方法改為findArticleWithPessimisticLock()非驮,再測(cè)試用例測(cè)一下,肯定不會(huì)有并發(fā)問(wèn)題雏赦。而且這時(shí)看一下控制臺(tái)打印信息劫笙,發(fā)現(xiàn)實(shí)際上查詢(xún)的sql還是加了for update,只不過(guò)是JPA幫我們加了而已星岗。

image.png

樂(lè)觀鎖解決并發(fā)問(wèn)題

樂(lè)觀鎖顧名思義就是特別樂(lè)觀填大,認(rèn)為自己拿到的資源不會(huì)被其他線(xiàn)程操作所以不上鎖,只是在插入數(shù)據(jù)庫(kù)的時(shí)候再判斷一下數(shù)據(jù)有沒(méi)有被修改俏橘。所以悲觀鎖是限制其他線(xiàn)程允华,而樂(lè)觀鎖是限制自己,雖然他的名字有鎖寥掐,但是實(shí)際上不算上鎖靴寂,只是在最后操作的時(shí)候再判斷具體怎么操作。

樂(lè)觀鎖通常為版本號(hào)機(jī)制或者CAS算法

利用SQL實(shí)現(xiàn)版本號(hào)解決并發(fā)問(wèn)題

版本號(hào)機(jī)制就是在數(shù)據(jù)庫(kù)中加一個(gè)字段當(dāng)作版本號(hào)召耘,比如我們加個(gè)字段version百炬。那么這時(shí)候拿到Article的時(shí)候就會(huì)帶一個(gè)版本號(hào),比如拿到的版本是1污它,然后你對(duì)這個(gè)Article一通操作剖踊,操作完之后要插入到數(shù)據(jù)庫(kù)了。發(fā)現(xiàn)哎呀衫贬,怎么數(shù)據(jù)庫(kù)里的Article版本是2蜜宪,和我手里的版本不一樣啊,說(shuō)明我手里的Article不是最新的了祥山,那么就不能放到數(shù)據(jù)庫(kù)了。這樣就避免了并發(fā)時(shí)數(shù)據(jù)沖突的問(wèn)題掉伏。

所以我們現(xiàn)在給article表加一個(gè)字段version

article 文章表

字段 類(lèi)型 備注
version INT DEFAULT 0 版本號(hào)

然后對(duì)應(yīng)的實(shí)體類(lèi)也增加version字段

@Data
@Entity
public class Article {
    ...

    private Long version;
}

接著在ArticleRepository增加更新的方法缝呕,注意這里是更新方法澳窑,和悲觀鎖時(shí)增加查詢(xún)方法不同。

public interface ArticleRepository extends CrudRepository<Article, Long> {
    @Modifying
    @Query(value = "update article set comment_count = :commentCount, version = version + 1 where id = :id and version = :version", nativeQuery = true)
    int updateArticleWithVersion(Long id, Long commentCount, Long version);
}

可以看到update的where有一個(gè)判斷version的條件供常,并且會(huì)set version = version + 1摊聋。這就保證了只有當(dāng)數(shù)據(jù)庫(kù)里的版本號(hào)和要更新的實(shí)體類(lèi)的版本號(hào)相同的時(shí)候才會(huì)更新數(shù)據(jù)。

接著在CommentService里稍微修改一下代碼栈暇。

// CommentService
public void postComment(Long articleId, String content) {
    Optional<Article> articleOptional = articleRepository.findById(articleId);

    ...    

    int count = articleRepository.updateArticleWithVersion(article.getId(), article.getCommentCount() + 1, article.getVersion());
    if (count == 0) {
        throw new RuntimeException("服務(wù)器繁忙,更新數(shù)據(jù)失敗");
    }
    // articleRepository.save(article);
}

首先對(duì)于Article的查詢(xún)方法只需要普通的findById()方法就行不用上任何鎖麻裁。

然后更新Article的時(shí)候改用新加的updateArticleWithVersion()方法≡雌恚可以看到這個(gè)方法有個(gè)返回值煎源,這個(gè)返回值代表更新了的數(shù)據(jù)庫(kù)行數(shù),如果值為0的時(shí)候表示沒(méi)有符合條件可以更新的行香缺。

這之后就可以由我們自己決定怎么處理了手销,這里是直接回滾,spring就會(huì)幫我們回滾之前的數(shù)據(jù)操作图张,把這次的所有操作都取消以保證數(shù)據(jù)的一致性锋拖。

現(xiàn)在再用測(cè)試用例測(cè)一下

select * from article
image.png
select count(*) comment_count from comment
image.png

現(xiàn)在看到Article里的comment_count和Comment的數(shù)量都不是100了,但是這兩個(gè)的值必定是一樣的了祸轮。因?yàn)閯偛盼覀兲幚淼臅r(shí)候假如Article表的數(shù)據(jù)發(fā)生了沖突兽埃,那么就不會(huì)更新到數(shù)據(jù)庫(kù)里,這時(shí)拋出異常使其事務(wù)回滾适袜,這樣就能保證沒(méi)有更新Article的時(shí)候Comment也不會(huì)插入柄错,就解決了數(shù)據(jù)不統(tǒng)一的問(wèn)題。

這種直接回滾的處理方式用戶(hù)體驗(yàn)比較差痪蝇,通常來(lái)說(shuō)如果判斷Article更新條數(shù)為0時(shí)鄙陡,會(huì)嘗試重新從數(shù)據(jù)庫(kù)里查詢(xún)信息并重新修改,再次嘗試更新數(shù)據(jù)躏啰,如果不行就再查詢(xún)趁矾,直到能夠更新為止。當(dāng)然也不會(huì)是無(wú)線(xiàn)的循環(huán)這樣的操作给僵,會(huì)設(shè)置一個(gè)上線(xiàn)毫捣,比如循環(huán)3次查詢(xún)修改更新都不行,這時(shí)候才會(huì)拋出異常帝际。

利用JPA實(shí)現(xiàn)版本現(xiàn)解決并發(fā)問(wèn)題

JPA對(duì)悲觀鎖有實(shí)現(xiàn)方式蔓同,樂(lè)觀鎖自然也是有的,現(xiàn)在就用JPA自帶的方法實(shí)現(xiàn)樂(lè)觀鎖蹲诀。

首先在Article實(shí)體類(lèi)的version字段上加上@Version注解斑粱,我們進(jìn)注解看一下源碼的注釋?zhuān)梢钥吹接胁糠謱?xiě)到:

The following types are supported for version properties: int, Integer, short, Short, long, Long, java.sql.Timestamp.

注釋里面說(shuō)版本號(hào)的類(lèi)型支持int, short, long三種基本數(shù)據(jù)類(lèi)型和他們的包裝類(lèi)以及Timestamp,我們現(xiàn)在用的是Long類(lèi)型脯爪。

@Data
@Entity
public class Article {
    ...

    @Version
    private Long version;
}

接著只需要在CommentService里的評(píng)論流程修改回我們最開(kāi)頭的“會(huì)觸發(fā)并發(fā)問(wèn)題”的業(yè)務(wù)代碼就行了则北。說(shuō)明JPA的這種樂(lè)觀鎖實(shí)現(xiàn)方式是非侵入式的矿微。

// CommentService
public void postComment(Long articleId, String content) {
    Optional<Article> articleOptional = articleRepository.findById(articleId);
    ...

    article.setCommentCount(article.getCommentCount() + 1);
    articleRepository.save(article);
}

和前面同樣的,用測(cè)試用例測(cè)試一下能否防止并發(fā)問(wèn)題的出現(xiàn)尚揣。

select * from article
image.png
select count(*) comment_count from comment
image.png

同樣的Article里的comment_count和Comment的數(shù)量也不是100涌矢,但是這兩個(gè)數(shù)值肯定是一樣的】炱看一下IDEA的控制臺(tái)會(huì)發(fā)現(xiàn)系統(tǒng)拋出了ObjectOptimisticLockingFailureException的異常娜庇。

image.png

這和剛才我們自己實(shí)現(xiàn)樂(lè)觀鎖類(lèi)似,如果沒(méi)有成功更新數(shù)據(jù)則拋出異撤嚼海回滾保證數(shù)據(jù)的一致性名秀。如果想要實(shí)現(xiàn)重試流程可以捕獲ObjectOptimisticLockingFailureException這個(gè)異常,通常會(huì)利用AOP+自定義注解來(lái)實(shí)現(xiàn)一個(gè)全局通用的重試機(jī)制恭取,這里就是要根據(jù)具體的業(yè)務(wù)情況來(lái)拓展了泰偿,想要了解的可以自行搜索一下方案。

悲觀鎖和樂(lè)觀鎖比較

悲觀鎖適合寫(xiě)多讀少的場(chǎng)景蜈垮。因?yàn)樵谑褂玫臅r(shí)候該線(xiàn)程會(huì)獨(dú)占這個(gè)資源耗跛,在本文的例子來(lái)說(shuō)就是某個(gè)id的文章,如果有大量的評(píng)論操作的時(shí)候攒发,就適合用悲觀鎖调塌,否則用戶(hù)只是瀏覽文章而沒(méi)什么評(píng)論的話(huà),用悲觀鎖就會(huì)經(jīng)常加鎖惠猿,增加了加鎖解鎖的資源消耗羔砾。

樂(lè)觀鎖適合寫(xiě)少讀多的場(chǎng)景。由于樂(lè)觀鎖在發(fā)生沖突的時(shí)候會(huì)回滾或者重試偶妖,如果寫(xiě)的請(qǐng)求量很大的話(huà)姜凄,就經(jīng)常發(fā)生沖突,經(jīng)常的回滾和重試趾访,這樣對(duì)系統(tǒng)資源消耗也是非常大态秧。

所以悲觀鎖和樂(lè)觀鎖沒(méi)有絕對(duì)的好壞,必須結(jié)合具體的業(yè)務(wù)情況來(lái)決定使用哪一種方式扼鞋。另外在阿里巴巴開(kāi)發(fā)手冊(cè)里也有提到:

如果每次訪(fǎng)問(wèn)沖突概率小于 20%申鱼,推薦使用樂(lè)觀鎖,否則使用悲觀鎖云头。樂(lè)觀鎖的重試次
數(shù)不得小于 3 次捐友。

阿里巴巴建議以沖突概率20%這個(gè)數(shù)值作為分界線(xiàn)來(lái)決定使用樂(lè)觀鎖和悲觀鎖,雖然說(shuō)這個(gè)數(shù)值不是絕對(duì)的溃槐,但是作為阿里巴巴各個(gè)大佬總結(jié)出來(lái)的也是一個(gè)很好的參考匣砖。

【轉(zhuǎn)載請(qǐng)注明出處】:http://www.reibang.com/p/6287599cd0fd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子脆粥,更是在濱河造成了極大的恐慌砌溺,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件变隔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蟹倾,警方通過(guò)查閱死者的電腦和手機(jī)匣缘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鲜棠,“玉大人肌厨,你說(shuō)我怎么就攤上這事』砺剑” “怎么了柑爸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)盒音。 經(jīng)常有香客問(wèn)我表鳍,道長(zhǎng),這世上最難降的妖魔是什么祥诽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任譬圣,我火速辦了婚禮,結(jié)果婚禮上雄坪,老公的妹妹穿的比我還像新娘厘熟。我一直安慰自己,他們只是感情好维哈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布绳姨。 她就那樣靜靜地躺著,像睡著了一般阔挠。 火紅的嫁衣襯著肌膚如雪飘庄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天谒亦,我揣著相機(jī)與錄音竭宰,去河邊找鬼。 笑死份招,一個(gè)胖子當(dāng)著我的面吹牛切揭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锁摔,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼匾寝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了绝编?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涩盾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后励背,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體春霍,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年叶眉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了址儒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衅疙,死狀恐怖莲趣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饱溢,我是刑警寧澤喧伞,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站绩郎,受9級(jí)特大地震影響潘鲫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嗽上,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一次舌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兽愤,春花似錦彼念、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至洼畅,卻和暖如春吩案,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帝簇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工徘郭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丧肴。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓残揉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親芋浮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子抱环,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353