分布式工具之redisson

分布式工具之redisson

目录

一.Redisson的引入

二.Redisson

三.Redisson解决可重入锁

四.Redisson解决重试、超时续约问题

五.Redisson解决主从一致问题

六.总结

1.Redisson的引入

我们先看看之前基于setnx实现的分布式锁存在的问题: 不可重入:同一个线程无法多次获取同一把锁 不可重试:获取锁只尝试一次就返回false,没有重试机制 超时释放:锁超时释放虽然避免死锁,但是业务执行耗时较长,也会导致释放存在安全隐患 主从一致:如果redis提供了主从集群,主从同步存在延迟,当主节点宕机,如果从节点同步主中的锁数据,则会出现锁失效

1、不可重入

简单的来说就是一旦setnx [key] [value]后,就不能再对这个key做任何操作了(除了删除)

假设我们在开发中有A和B两个业务,在业务A中,执行了setnx操作,然后在业务A中调用业务B。然后在业务B中也有setnx的操作(同一个KEY),此时,业务B就会阻塞在这里,等待业务A释放锁,但是,业务A肯定不会释放锁,因为业务A还没有执行完(调B)。故就会发生死锁。

2、不可重试

在我们之前业务逻辑中,尝试获取锁,如果获取不到就直接return了,没有“重来”的机会!也无法提供重试的机制!

3、超时释放

虽然我们可以通过加超时时间,可以解决当业务执行发生异常后出现死锁,但是,仍然会存在隐患!,我们这里是用TTL来控制它。业务执行时间都是未知数,TTL要咋样设置?如何处理执行业务阻塞?

3、主从一致

在主节点上获取到了锁,但是主节点突然宕机了,就会从从结点中选出一个节点,作为主节点。 但由于,因为之前的那个主节点宕机了。在新选举出来的这个主节点中是无法获取到之前的锁。 所以之前的那个锁相当于失效了!

2.Redisson

要解决上述问题并不是那么容易的,如果我们自己实现很有可能会出一些问题!所以最好的办法就是使用市面上的一些框架来解决!

1、什么Redisson?

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。

2、Redisson使用手册 ?

Redission使用手册https://www.bookstack.cn/read/redisson-wiki-zh/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D.md里面提到了Redisson可以实现大致如下的分布式锁

3、Redisson快速入门

<!-- redis-redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency> 

/**
 * 配置 Redisson
 */
@Configuration
public class RedisConfig 
    @Bean
    public RedissonClient redissonClient() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.56.20:6379").setPassword("123456");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

@Test
void testRedisson() throws Exception 
    RLock anyLock = redissonClient.getLock("anyLock");
    boolean isLock = anyLock.tryLock(1, 10, TimeUnit.SECONDS);
    if(isLock) 
        try 
            System.out.println("执行业务");
         finally 
            anyLock.unlock();

3.Redisson解决可重入锁 4.Redisson解决重试、超时续约问题 5.Redisson解决主从一致问题

三.Redisson解决可重入锁

这里可重入的实现和java的ReentrantLock类似! 获取锁的时候,先判断是否是同一个对象,是就val+1, 是否锁的时候就val - 1, 当小于0就将key删除!(redisson帮我们做好了) [图片上传失败...(image-97ec9-1683640000804)]

核心实现主要是lua脚本: 获取锁:

-- 判断锁是否存在
if (redis.call('exists', KEYS[1]) == 0) then
    -- 不存在,获取锁
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    -- 设置有效期
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
-- 锁已经存在,判断是否是自己?!
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    -- 自增+1
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    -- 重置有效期
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
return redis.call('pttl', KEYS[1]);

释放锁:

-- 判断当前锁是否还是被自己持有
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    -- 不是就就直接返回
    return nil;
    end;
-- 是自己,则重入次数 -1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
-- 判断重入次数是否已经为0
if (counter > 0) then
    -- 大于0,说明不能释放,重置有效期即可
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 0;
else
    -- 等于0,说明可以直接删除
    redis.call('del', KEYS[1]);
    -- 发消息
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1;
    end;
return nil;

四.Redisson解决重试、超时续约问题

实现重试、超时续约问题流程 [图片上传失败...(image-e0f263-1683640000804)]

可重试:不是简单while循环,利用信号量和发布订阅功能实现等待,唤醒,获取锁失败的重试机制;好处,不浪费cpu性能 超时续约: waitTime:是最大等待时间,如果使用 tryLock() 的时候,有传参数表明是可重试的锁;反之,不是!

leaseTime:超时释放时间,默认是-1,建议不要设定,Redisson看门狗机制可以就行锁续约!

锁重试核心代码:

@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException 
    // 转成毫秒,后面都是以毫秒为单位
    long time = unit.toMillis(waitTime);
    // 当前时间
    long current = System.currentTimeMillis();
    // 线程ID-线程标识
    long threadId = Thread.currentThread().getId();

    // 尝试获取锁 tryAcquire() !!!
    Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);

    // 如果上面尝试获取锁返回的是null,表示成功;如果返回的是时间则表示失败。
    if (ttl == null) 
        return true;

    // 剩余等待时间 = 最大等待时间 -(用现在时间 - 获取锁前的时间)
    time -= System.currentTimeMillis() - current;

    // 剩余等待时间 < 0 失败
    if (time <= 0) 
        acquireFailed(waitTime, unit, threadId);
        return false;

    // 再次获取当前时间
    current = System.currentTimeMillis();
    // 重试逻辑,但不是简单的直接重试!
    // subscribe是订阅的意思
    RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    // 如果在剩余等待时间内,收到了释放锁那边发过来的publish,则才会再次尝试获取锁
    if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) 
        if (!subscribeFuture.cancel(false)) 
            subscribeFuture.onComplete((res, e) -> 
                if (e == null)    
                    // 取消订阅
                    unsubscribe(subscribeFuture, threadId);

            );

        // 获取锁失败
        acquireFailed(waitTime, unit, threadId);
        return false;

    try 
        // 又重新计算了一下,上述的等待时间
        time -= System.currentTimeMillis() - current;
        if (time <= 0) 
            acquireFailed(waitTime, unit, threadId);
            return false;

        // 重试!
        while (true) 
            long currentTime = System.currentTimeMillis();
            ttl = tryAcquire(waitTime, leaseTime, unit, threadId);

            // 成功
            if (ttl == null) 
                return true;

            // 又获取锁失败,再次计算上面的耗时
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) 
                acquireFailed(waitTime, unit, threadId);
                return false;

            currentTime = System.currentTimeMillis();
            // 采用信号量的方式重试!
            if (ttl >= 0 && ttl < time) 
                subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
             else 
                subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);

            // 重新计算时间(充足就继续循环)
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) 
                acquireFailed(waitTime, unit, threadId);
                return false;

     finally 
        unsubscribe(subscribeFuture, threadId);

五.Redisson解决主从一致问题

  1. 问题描述 为了redis可靠,我们一般会使用Redis主从模式 使用了主从模式,一般会采用读写分离策略,主节点写,从节点读! [图片上传失败...(image-1c4f09-1683640000803)]

那么,当数据被写入主节点的时候,主节点时需要向从节点去同步数据的!

这个过程一定会有延时,一致性问题也就发生在这里!

假如,在主节点中获取到了锁,在主节点向从节点同步这个锁信息的时候,主节点宕机了!那么从节点就会从中挑选一个作为主节点!

可是,此时之前的锁信息就丢失了!也就发生了锁失效的问题!!! [图片上传失败...(image-402cb5-1683640000804)]# 分布式工具之redisson

目录

一.Redisson的引入

二.Redisson

三.Redisson解决可重入锁

四.Redisson解决重试、超时续约问题

五.Redisson解决主从一致问题

六.总结

1.Redisson的引入

我们先看看之前基于setnx实现的分布式锁存在的问题: 不可重入:同一个线程无法多次获取同一把锁 不可重试:获取锁只尝试一次就返回false,没有重试机制 超时释放:锁超时释放虽然避免死锁,但是业务执行耗时较长,也会导致释放存在安全隐患 主从一致:如果redis提供了主从集群,主从同步存在延迟,当主节点宕机,如果从节点同步主中的锁数据,则会出现锁失效

1、不可重入

简单的来说就是一旦setnx [key] [value]后,就不能再对这个key做任何操作了(除了删除)

假设我们在开发中有A和B两个业务,在业务A中,执行了setnx操作,然后在业务A中调用业务B。然后在业务B中也有setnx的操作(同一个KEY),此时,业务B就会阻塞在这里,等待业务A释放锁,但是,业务A肯定不会释放锁,因为业务A还没有执行完(调B)。故就会发生死锁。

2、不可重试

在我们之前业务逻辑中,尝试获取锁,如果获取不到就直接return了,没有“重来”的机会!也无法提供重试的机制!

3、超时释放

虽然我们可以通过加超时时间,可以解决当业务执行发生异常后出现死锁,但是,仍然会存在隐患!,我们这里是用TTL来控制它。业务执行时间都是未知数,TTL要咋样设置?如何处理执行业务阻塞?

3、主从一致

在主节点上获取到了锁,但是主节点突然宕机了,就会从从结点中选出一个节点,作为主节点。 但由于,因为之前的那个主节点宕机了。在新选举出来的这个主节点中是无法获取到之前的锁。 所以之前的那个锁相当于失效了!

2.Redisson

要解决上述问题并不是那么容易的,如果我们自己实现很有可能会出一些问题!所以最好的办法就是使用市面上的一些框架来解决!

1、什么Redisson?

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。

2、Redisson使用手册 ?

Redission使用手册https://www.bookstack.cn/read/redisson-wiki-zh/Redisson%E9%A1%B9%E7%9B%AE%E4%BB%8B%E7%BB%8D.md里面提到了Redisson可以实现大致如下的分布式锁

3、Redisson快速入门

<!-- redis-redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency> 

/**
 * 配置 Redisson
 */
@Configuration
public class RedisConfig 
    @Bean
    public RedissonClient redissonClient() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.56.20:6379").setPassword("123456");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

@Test
void testRedisson() throws Exception 
    RLock anyLock = redissonClient.getLock("anyLock");
    boolean isLock = anyLock.tryLock(1, 10, TimeUnit.SECONDS);
    if(isLock) 
        try 
            System.out.println("执行业务");
         finally 
            anyLock.unlock();

3.Redisson解决可重入锁 4.Redisson解决重试、超时续约问题 5.Redisson解决主从一致问题

三.Redisson解决可重入锁

这里可重入的实现和java的ReentrantLock类似! 获取锁的时候,先判断是否是同一个对象,是就val+1, 是否锁的时候就val - 1, 当小于0就将key删除!(redisson帮我们做好了) [图片上传失败...(image-1057e7-1683640012795)]

核心实现主要是lua脚本: 获取锁:

-- 判断锁是否存在
if (redis.call('exists', KEYS[1]) == 0) then
    -- 不存在,获取锁
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    -- 设置有效期
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
-- 锁已经存在,判断是否是自己?!
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
    -- 自增+1
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    -- 重置有效期
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return nil;
end;
return redis.call('pttl', KEYS[1]);

释放锁:

-- 判断当前锁是否还是被自己持有
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    -- 不是就就直接返回
    return nil;
    end;
-- 是自己,则重入次数 -1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
-- 判断重入次数是否已经为0
if (counter > 0) then
    -- 大于0,说明不能释放,重置有效期即可
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 0;
else
    -- 等于0,说明可以直接删除
    redis.call('del', KEYS[1]);
    -- 发消息
    redis.call('publish', KEYS[2], ARGV[1]);
    return 1;
    end;
return nil;

四.Redisson解决重试、超时续约问题

实现重试、超时续约问题流程 [图片上传失败...(image-1ed1f0-1683640012795)]

可重试:不是简单while循环,利用信号量和发布订阅功能实现等待,唤醒,获取锁失败的重试机制;好处,不浪费cpu性能 超时续约: waitTime:是最大等待时间,如果使用 tryLock() 的时候,有传参数表明是可重试的锁;反之,不是!

leaseTime:超时释放时间,默认是-1,建议不要设定,Redisson看门狗机制可以就行锁续约!

锁重试核心代码:

@Override
public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException 
    // 转成毫秒,后面都是以毫秒为单位
    long time = unit.toMillis(waitTime);
    // 当前时间
    long current = System.currentTimeMillis();
    // 线程ID-线程标识
    long threadId = Thread.currentThread().getId();

    // 尝试获取锁 tryAcquire() !!!
    Long ttl = tryAcquire(waitTime, leaseTime, unit, threadId);

    // 如果上面尝试获取锁返回的是null,表示成功;如果返回的是时间则表示失败。
    if (ttl == null) 
        return true;

    // 剩余等待时间 = 最大等待时间 -(用现在时间 - 获取锁前的时间)
    time -= System.currentTimeMillis() - current;

    // 剩余等待时间 < 0 失败
    if (time <= 0) 
        acquireFailed(waitTime, unit, threadId);
        return false;

    // 再次获取当前时间
    current = System.currentTimeMillis();
    // 重试逻辑,但不是简单的直接重试!
    // subscribe是订阅的意思
    RFuture<RedissonLockEntry> subscribeFuture = subscribe(threadId);
    // 如果在剩余等待时间内,收到了释放锁那边发过来的publish,则才会再次尝试获取锁
    if (!subscribeFuture.await(time, TimeUnit.MILLISECONDS)) 
        if (!subscribeFuture.cancel(false)) 
            subscribeFuture.onComplete((res, e) -> 
                if (e == null)    
                    // 取消订阅
                    unsubscribe(subscribeFuture, threadId);

            );

        // 获取锁失败
        acquireFailed(waitTime, unit, threadId);
        return false;

    try 
        // 又重新计算了一下,上述的等待时间
        time -= System.currentTimeMillis() - current;
        if (time <= 0) 
            acquireFailed(waitTime, unit, threadId);
            return false;

        // 重试!
        while (true) 
            long currentTime = System.currentTimeMillis();
            ttl = tryAcquire(waitTime, leaseTime, unit, threadId);

            // 成功
            if (ttl == null) 
                return true;

            // 又获取锁失败,再次计算上面的耗时
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) 
                acquireFailed(waitTime, unit, threadId);
                return false;

            currentTime = System.currentTimeMillis();
            // 采用信号量的方式重试!
            if (ttl >= 0 && ttl < time) 
                subscribeFuture.getNow().getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);
             else 
                subscribeFuture.getNow().getLatch().tryAcquire(time, TimeUnit.MILLISECONDS);

            // 重新计算时间(充足就继续循环)
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) 
                acquireFailed(waitTime, unit, threadId);
                return false;

     finally 
        unsubscribe(subscribeFuture, threadId);

五.Redisson解决主从一致问题

  1. 问题描述 为了redis可靠,我们一般会使用Redis主从模式 使用了主从模式,一般会采用读写分离策略,主节点写,从节点读! [图片上传失败...(image-f89dfb-1683640012794)]

那么,当数据被写入主节点的时候,主节点时需要向从节点去同步数据的!

这个过程一定会有延时,一致性问题也就发生在这里!

假如,在主节点中获取到了锁,在主节点向从节点同步这个锁信息的时候,主节点宕机了!那么从节点就会从中挑选一个作为主节点!

可是,此时之前的锁信息就丢失了!也就发生了锁失效的问题!!! [图片上传失败...(image-a37de6-1683640012795)]

  1. 解决方案 之前我们分析了,主从模式是导致锁失效的原因,所以Redisson中就直接将它们视为相同的角色! 此时,我们获取锁的方式就变了,获取锁的时候,我们需要依次向全部节点获取锁,只有都获取成功时才算成功!!! [图片上传失败...(image-e93ff4-1683640012793)]

    如果此时也发生了刚刚描述的问题,是不会出现锁失效的问题的! [图片上传失败...(image-ac6b02-1683640012792)]

    这套方案就是Redisson中的联锁——MultiLock

  2. 代码实现

@Configuration
public class RedisConfig 
    @Bean
    public RedissonClient redissonClient() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.89.128:6379").setPassword("888888");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

    @Bean
    public RedissonClient redissonClient2() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.89.128:6380");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

    @Bean
    public RedissonClient redissonClient3() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.89.128:6381");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

@SpringBootTest
@Slf4j
public class RedissonTest 

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedissonClient redissonClient2;

    @Resource
    private RedissonClient redissonClient3;

    private RLock lock;

    @BeforeEach
    void setUp() 
        RLock lock1 = redissonClient.getLock("order");
        RLock lock2 = redissonClient.getLock("order");
        RLock lock3 = redissonClient.getLock("order");

        // 创建联锁
        lock = redissonClient.getMultiLock(lock1, lock2, lock3);

    @Test
    void method1() throws InterruptedException 
        boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);
        if (!isLock) 
            log.error("获取锁失败 ... 1");
            return;

        try 
            log.info("获取锁成功 ... 1");
            method2();
            log.info("开始执行业务 ... 1");
         finally 
            log.warn("准备释放锁 ... 1");
            lock.unlock();

    @Test
    void method2() 
        boolean isLock = lock.tryLock();
        if (!isLock) 
            log.error("获取锁失败 ... 2");
            return;

        try 
            log.info("获取锁成功 ... 2");
            log.info("开始执行业务 ... 2");
         finally 
            log.warn("准备释放锁 ... 2");
            lock.unlock();

六.总结

  1. 解决方案 之前我们分析了,主从模式是导致锁失效的原因,所以Redisson中就直接将它们视为相同的角色! 此时,我们获取锁的方式就变了,获取锁的时候,我们需要依次向全部节点获取锁,只有都获取成功时才算成功!!! [图片上传失败...(image-a332e5-1683640000802)]

    如果此时也发生了刚刚描述的问题,是不会出现锁失效的问题的! [图片上传失败...(image-400e34-1683640000802)]

    这套方案就是Redisson中的联锁——MultiLock

  2. 代码实现

@Configuration
public class RedisConfig 
    @Bean
    public RedissonClient redissonClient() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.89.128:6379").setPassword("888888");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

    @Bean
    public RedissonClient redissonClient2() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.89.128:6380");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

    @Bean
    public RedissonClient redissonClient3() 

        // 配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.89.128:6381");

        // 创建 RedissonClient 对象
        return Redisson.create(config);

@SpringBootTest
@Slf4j
public class RedissonTest 

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedissonClient redissonClient2;

    @Resource
    private RedissonClient redissonClient3;

    private RLock lock;

    @BeforeEach
    void setUp() 
        RLock lock1 = redissonClient.getLock("order");
        RLock lock2 = redissonClient.getLock("order");
        RLock lock3 = redissonClient.getLock("order");

        // 创建联锁
        lock = redissonClient.getMultiLock(lock1, lock2, lock3);

    @Test
    void method1() throws InterruptedException 
        boolean isLock = lock.tryLock(1L, TimeUnit.SECONDS);
        if (!isLock) 
            log.error("获取锁失败 ... 1");
            return;

        try 
            log.info("获取锁成功 ... 1");
            method2();
            log.info("开始执行业务 ... 1");
         finally 
            log.warn("准备释放锁 ... 1");
            lock.unlock();

    @Test
    void method2() 
        boolean isLock = lock.tryLock();
        if (!isLock) 
            log.error("获取锁失败 ... 2");
            return;

        try 
            log.info("获取锁成功 ... 2");
            log.info("开始执行业务 ... 2");
         finally 
            log.warn("准备释放锁 ... 2");
            lock.unlock();

六.总结

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容