在 Spring Boot 中使用 Redis

Redis 本身的一些概念

Redis 支持的數(shù)據(jù)結(jié)構(gòu)

  • String 字符串
  • Hash 字典
  • List 列表
  • Set 集合
  • Sorted Set 有序集合

String 和 Hash 的對比

String 實際是就是一個 Key - Value 的映射;

Hash 就是一個 Key - (Key - Value) 的兩層映射崎岂。

# redis-cli
# Redis 中命令不區(qū)分大小寫。這里命令使用小寫沸伏,僅在特別的地方用大寫。
# 參數(shù)使用“大寫+下劃線”的方式援岩。

# String
set KEY VALUE
get KEY

# Hash
hset HASH_NAME KEY VALUE
hget HASH_NAME KEY
hMset HASH_NAME KEY0 VALUE0 KEY1 VALUE1 ...
hMget HASH_NAME KEY0 KEY1 ...

STACK OVERFLOW 上一個對 String 和 Hash 的討論

對于一個對象是把本身的數(shù)據(jù)序列化后用 String 存儲逗概,還是使用 Hash 來分別存儲對象的各個屬性:

  • 如果在大多數(shù)時候要訪問對象的大部分?jǐn)?shù)據(jù):使用 String
  • 如果在大多數(shù)時候只要訪問對象的小部分?jǐn)?shù)據(jù):使用 Hash
  • 如果對象里面還有對象這種結(jié)構(gòu)復(fù)雜的蔬胯,最好用 String朋截。否則最外層用 Hash蛹稍,里面又將對象序列化,兩者混用可能導(dǎo)致混亂部服。

Spring Boot 添加 Redis 的配置

以 gradle 為例唆姐。

  • 修改 build.gradle
compile("org.springframework.boot:spring-boot-starter-data-redis")
  • 修改 application.yml
spring:
    # redis
    redis:
        host: 127.0.0.1
        # 數(shù)據(jù)庫索引(默認(rèn)為0)
        database: 0
        port: 6379
        password: PASSWORD
        # 連接池中的最大空閑連接
        pool.max-idle: 8
        # 連接池中的最小空閑連接
        pool.min-idle: 0
        # 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
        pool.max-active: 8
        # 連接池最大阻塞等待時間(使用負(fù)值表示沒有限制)
        pool.max-wait: -1
        # 連接超時時間(毫秒)
        timeout: 0
  • 添加 RedisConfig
package zz.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * RedisConfig
 *
 * @author zz
 * @date 2018/5/7
 */
@Configuration
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return new KeyGenerator() {
            private static final String SEPARATE = ":";

            @Override
            public Object generate(Object target, Method method, Object... params) {
                log.debug("+++++generate");
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(SEPARATE).append(method);
                for (Object obj : params) {
                    sb.append(SEPARATE).append(obj);
                }
                return sb.toString();
            }
        };
    }

    /**
     * http://www.reibang.com/p/9255b2484818
     *
     * TODO: 對 Spring @CacheXXX 注解進(jìn)行擴(kuò)展:注解失效時間 + 主動刷新緩存
     */
    @Bean
    public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
        log.debug("++++cacheManager");
        RedisCacheManager redisCacheManager =new RedisCacheManager(redisTemplate);
        redisCacheManager.setTransactionAware(true);
        redisCacheManager.setLoadRemoteCachesOnStartup(true);

        // 最終在 Redis 中的 key = @Cacheable 注解中 'cacheNames' + 'key'
        redisCacheManager.setUsePrefix(true);

        // 所有 key 的默認(rèn)過期時間,不設(shè)置則永不過期
        // redisCacheManager.setDefaultExpiration(6000L);

        // 對某些 key 單獨設(shè)置過期時間
        // 這里的 key 是 @Cacheable 注解中的 'cacheNames'
        Map<String, Long> expires = new HashMap<>(10);
        // expires.put("feedCategoryDto", 5000L);
        // expires.put("feedDto", 5000L);
        redisCacheManager.setExpires(expires);

        return redisCacheManager;
    }


    // value serializer

    private Jackson2JsonRedisSerializer getJackson2JsonRedisSerializer() {
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        return jackson2JsonRedisSerializer;
    }

    private GenericJackson2JsonRedisSerializer getGenericJackson2JsonRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    /**
     *
     * Once configured, the template is thread-safe and can be reused across multiple instances.
     * -- https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        log.debug("++++redisTemplate");
        StringRedisTemplate template = new StringRedisTemplate(factory);

        // key serializer
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


        RedisSerializer valueRedisSerializer;
        // -- 1 Jackson2JsonRedisSerializer
        // valueRedisSerializer = getJackson2JsonRedisSerializer();

        // -- 2 GenericJackson2JsonRedisSerializer
        valueRedisSerializer = getGenericJackson2JsonRedisSerializer();

        // set serializer
        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(valueRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(valueRedisSerializer);

        template.afterPropertiesSet();
        return template;
    }
}


RedisConfig 中定義了三個函數(shù)廓八,主要作用如下:

  • wiselyKeyGenerator:定義了一個生成 Redis 的 key 的方法奉芦。如下文使用了 @Cacheable 注解的地方,可以指定 key 的生成方法使用我們這個函數(shù)剧蹂。
  • cacheManager:定義了對 Redis 的一些基本設(shè)置声功。
  • redisTemplate:對我們要使用的 RedisTemplate 做一些設(shè)置。主要是確定序列化方法宠叼。

RedisTemplate 設(shè)置序列化器

Spring Redis 雖然提供了對 list先巴、set、hash 等數(shù)據(jù)類型的支持冒冬,但是沒有提供對 POJO 對象的支持伸蚯,底層都是把對象序列化后再以字節(jié)的方式存儲的。

因此简烤,Spring Data Redis 提供了若干個 Serializer剂邮,主要包括:
  • JdkSerializationRedisSerializer: 默認(rèn)的序列化器。序列化速度快横侦,生成的字節(jié)長度較大抗斤。
  • OxmSerializer: 生成 XML 格式的字節(jié)。
  • StringSerializer: 只能對 String 類型進(jìn)行序列化丈咐。
  • JacksonJsonRedisSerializer:以 JSON 格式進(jìn)行序列化。
  • Jackson2JsonRedisSerializer:JacksonJsonRedisSerializer 的升級版龙宏。
  • GenericJackson2JsonRedisSerializer:Jackson2JsonRedisSerializer 的泛型版棵逊。
RedisTemplate 中需要聲明 4 種 serializer(默認(rèn)使用的是 JdkSerializationRedisSerializer):
  • keySerializer :對于普通 K-V 操作時,key 采取的序列化策略
  • valueSerializer:value 采取的序列化策略
  • hashKeySerializer: 在 hash 數(shù)據(jù)結(jié)構(gòu)中银酗,hash-key 的序列化策略
  • hashValueSerializer:hash-value 的序列化策略

無論如何辆影,建議 key/hashKey 采用 StringRedisSerializer。

by Spring-data-redis: serializer實例

我們設(shè)置了 serializer 后黍特,讀寫 Redis 要使用同一種 serizlizer蛙讥,否則會讀不出之前用不同 serializer 寫入的數(shù)據(jù)。

也就是設(shè)置 valueSerializer 為GenericJackson2JsonRedisSerializer灭衷,然后寫入了數(shù)據(jù)次慢。
后面要讀數(shù)據(jù)的時候,如果將 valueSerializer 又設(shè)置成了 Jackson2JsonRedisSerializer,那么讀取數(shù)據(jù)時就會報錯迫像。

通常情況下劈愚,我們只需要在 RedisConfig 中統(tǒng)一設(shè)置好 4 個 serializer 即可。

Jackson2JsonRedisSerializer 與 GenericJackson2JsonRedisSerializer 的對比

  • 兩者都是將對象的數(shù)據(jù)序列化成 JSON 格式的字符串闻妓。
  • Jackson2JsonRedisSerializer 需要自己指定 ObjectMaper 或某個特定的類型菌羽。
  • GenericJackson2JsonRedisSerializer 是 Jackson2JsonRedisSerializer 的一個特例,默認(rèn)支持所有類型由缆。
  • 兩者序列化時注祖,都會將原始對象的類名和包名寫入 JSON 字符串中。以便反序列化時均唉,確認(rèn)要將 JSON 轉(zhuǎn)成何種格式是晨。
可用如下方式來獲得通用的 Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = 
    new Jackson2JsonRedisSerializer(Object.class);

ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

jackson2JsonRedisSerializer.setObjectMapper(om);

Jackson2JsonRedisSerializer 與 GenericJackson2JsonRedisSerializer 生成 JSON 的對比

# Jackson2JsonRedisSerializer 序列化的效果
127.0.0.1:6379> get 123
"[\"zz.domain.User\",{\"id\":123,\"name\":\"name\"}]"
127.0.0.1:6379> get userList
"[\"java.util.LinkedList\",[[\"zz.domain.User\",{\"id\":233,\"name\":\"new\"}],[\"zz.domain.User\",{\"id\":233,\"name\":\"new\"}]]]"
# GenericJackson2JsonRedisSerializer 序列化的效果
127.0.0.1:6379> get 123
"{\"@class\":\"zz.domain.User\",\"id\":123,\"name\":\"name\"}"
127.0.0.1:6379> get userList
"[\"java.util.LinkedList\",[{\"@class\":\"zz.domain.User\",\"id\":233,\"name\":\"new\"},{\"@class\":\"zz.domain.User\",\"id\":233,\"name\":\"new\"}]]"

如何使用

使用注解來緩存函數(shù)的結(jié)果

在要緩存的方法上使用注解 @Cacheable@CachePut浸卦、@CacheEvict 分別用于緩存返回數(shù)據(jù)署鸡、更新緩存數(shù)據(jù)、刪除緩存數(shù)據(jù)限嫌。

package zz.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import zz.domain.User;

/**
 * UserService
 *
 * @author zz
 * @date 2018/5/7
 */
@Service
@Slf4j
public class UserService {
    public final String DEFAULT_NAME = "def";

    @Cacheable(cacheNames = "user", key = "'id_'+#userId")
    public User get(int userId) {
        // get from db
        log.debug("[++] get userId=" + userId);

        User user = new User();
        user.setId(userId);
        user.setName(DEFAULT_NAME);
        log.debug("[++] create default user=" + user);
        return user;
    }

    @CachePut(cacheNames = "user", key = "'id_'+#user.getId()")
    public User update(User user) {
        // save to db
        log.debug("[++] update user=" + user);
        return user;
    }

    @CacheEvict(cacheNames = "user", key = "'id_'+#userId")
    public void delete(int userId) {
        // delete from db
        log.debug("[++] delete userId=" + userId);
    }

    @CachePut(cacheNames = "user", key = "'id_'+#userId")
    public User updateName(int userId, String name) {
        // update to db
        log.debug("[++] updateName userId=" + userId + ", name=" + name);

        User user = get(userId);
        user.setName(name);
        return user;
    }
    
    public void innerCall(int userId) {
        log.debug("[++] innerCall");
        get(userId);
    }
}

  • 對函數(shù)的緩存是通過代理來實現(xiàn)的 :
    類內(nèi)部的某個函數(shù)對其他函數(shù)(即便被調(diào)用函數(shù)有 @CacheXXX 注解)的調(diào)用是不會走代理的靴庆,也就沒有緩存。(比如 innerCall 調(diào)用 get 時不會使用緩存) 怒医。
  • 注解可以放到 Service炉抒、Dao 或 Controller 層。
  • @CacheXXX 會緩存函數(shù)的返回值稚叹。比如 increaseComment 會緩存更新后的 FeedCount焰薄。
  • 當(dāng)緩存中有數(shù)據(jù)時,@Cacheable 注解的函數(shù)不會執(zhí)行扒袖,直接返回緩存中的數(shù)據(jù)塞茅。
  • @CachePut@CacheEvit 注解的函數(shù)季率,無論如何都會執(zhí)行野瘦。

自定義緩存

如果要更細(xì)粒度地控制 Redis,可以使用 RedisTemplate飒泻、StringRedisTemplate

StringRedisTemplate 是 RedisTemplate 的一個特例:key 和 value 都是 String 類型鞭光。

  • RedisTemplate 默認(rèn)使用 JDK 對 key 和 value 進(jìn)行序列化,轉(zhuǎn)成字節(jié)存入 Redis泞遗。
  • StringRedisTemplate 的 key惰许、value 本身就是 String,使用 StringRedisSerializer 將 String 轉(zhuǎn)成字節(jié)存入 Redis史辙。

當(dāng)我們將 RedisTemplate 的 keySerializer 和 valueSerializer 都設(shè)置成了 StringRedisSerializer汹买,則 RedisTemplate 和 StringRedisTemplate 的效果是相同的佩伤,就像下面的樣例所示。

RedisTemplate 對 Redis 中各個數(shù)據(jù)結(jié)構(gòu)的操作

  • redisTemplate.opsForValue();//操作字符串
  • redisTemplate.opsForHash();//操作hash
  • redisTemplate.opsForList();//操作list
  • redisTemplate.opsForSet();//操作set
  • redisTemplate.opsForZSet();//操作有序set
package zz;

import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.test.context.junit4.SpringRunner;
import zz.domain.User;
import zz.service.UserService;

import java.util.LinkedList;
import java.util.List;

/**
 * zz.TestRedis
 *
 * @author zz
 * @date 2018/5/7
 */
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class TestRedis {

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    UserService userService;

    @Test
    public void testSerializer() {
        // 1.
        // 這里的 opsForValue().get() 的參數(shù)必須轉(zhuǎn)成 String 類型卦睹。
        // 除非在 RedisConfig 中 將 keySerializer 設(shè)置成 GenericJackson2JsonRedisSerializer 等能將其他類型轉(zhuǎn)換成 String 的畦戒。

        // 2.
        // 如果切換了 RedisConfig 中的 ValueSerializer,要先用 redis-cli 將其中的舊數(shù)據(jù)刪除结序。
        // 不同 Serializer 格式之間的轉(zhuǎn)換可能存在問題障斋。

        final int ID = 123;
        User oldUser;
        oldUser = (User) redisTemplate.opsForValue().get(String.valueOf(ID));
        log.debug("oldUser=" + oldUser);

        User user = new User();
        user.setId(ID);
        user.setName("name");
        log.debug("user=" + user);

        redisTemplate.opsForValue().set(String.valueOf(user.getId()), user);

        User newUser;
        newUser = (User) redisTemplate.opsForValue().get(String.valueOf(ID));
        log.debug("newUser=" + newUser);

        Assert.assertEquals(user.getId(), newUser.getId());
        Assert.assertEquals(user.getName(), newUser.getName());


        List<User> userList = new LinkedList<>();
        userList.add(user);
        user.setId(233);
        user.setName("new");
        userList.add(user);

        redisTemplate.opsForValue().set("userList", userList);
        List<User> newUserList;
        newUserList = (List<User>) redisTemplate.opsForValue().get("userList");

        Assert.assertEquals(userList, newUserList);
    }

    @Test
    public void testSerizlizer2() {
        // 保存用于恢復(fù),以免影響其他部分
        RedisSerializer oldKeySerializer = redisTemplate.getKeySerializer();
        RedisSerializer oldValueSerializer = redisTemplate.getValueSerializer();

        RedisSerializer redisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setValueSerializer(redisSerializer);

        final String KEY = "key";
        String VALUE = "value";

        redisTemplate.opsForValue().set(KEY, VALUE);
        Assert.assertEquals(VALUE, redisTemplate.opsForValue().get(KEY));
        Assert.assertEquals(VALUE, stringRedisTemplate.opsForValue().get(KEY));


        VALUE = "Val2";
        stringRedisTemplate.opsForValue().set(KEY, VALUE);
        Assert.assertEquals(VALUE, stringRedisTemplate.opsForValue().get(KEY));
        Assert.assertEquals(VALUE, redisTemplate.opsForValue().get(KEY));


        // 恢復(fù)原本設(shè)置
        redisTemplate.setKeySerializer(oldKeySerializer);
        redisTemplate.setValueSerializer(oldValueSerializer);
    }


    @Test
    public void testCache() {
        final int USER_ID = 1;

        User user = userService.get(USER_ID);
        log.debug("user=" + user);
        Assert.assertEquals(userService.DEFAULT_NAME, user.getName());

        // 這次會直接返回 cache
        user = userService.get(USER_ID);
        log.debug("user=" + user);

        // 獲得修改過的 cache
        final String ANOTHER_NAME = "another user";
        user.setName(ANOTHER_NAME);
        userService.update(user);
        user = userService.get(USER_ID);
        log.debug("user=" + user);
        Assert.assertEquals(ANOTHER_NAME, user.getName());

        // 直接調(diào)用 get 會走緩存徐鹤,通過 innerCall 來調(diào)用 get 不會走緩存
        log.debug("------ before");
        userService.get(USER_ID);
        log.debug("------ middle");
        userService.innerCall(USER_ID);
        log.debug("------ after");

        // 另一種修改的方式
        final String NEW_NAME = "updated";
        userService.updateName(USER_ID, NEW_NAME);
        user = userService.get(USER_ID);
        log.debug("user=" + user);
        Assert.assertEquals(NEW_NAME, user.getName());


        // 刪除后垃环,cache 中的數(shù)據(jù)會被刪除,name 會變成初始值
        userService.delete(USER_ID);
        user = userService.get(USER_ID);
        log.debug("user=" + user);
        Assert.assertEquals(userService.DEFAULT_NAME, user.getName());

        // 即使 cache 中沒有該數(shù)據(jù)返敬,也會執(zhí)行 delete 中的邏輯
        userService.delete(USER_ID);
        userService.delete(USER_ID);

    }

}


附錄

本文中的完整代碼見 spring-boot-redis遂庄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市劲赠,隨后出現(xiàn)的幾起案子涛目,更是在濱河造成了極大的恐慌,老刑警劉巖凛澎,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件霹肝,死亡現(xiàn)場離奇詭異,居然都是意外死亡塑煎,警方通過查閱死者的電腦和手機(jī)沫换,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來最铁,“玉大人讯赏,你說我怎么就攤上這事±湮荆” “怎么了漱挎?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長雀哨。 經(jīng)常有香客問我识樱,道長,這世上最難降的妖魔是什么震束? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮当犯,結(jié)果婚禮上垢村,老公的妹妹穿的比我還像新娘。我一直安慰自己嚎卫,他們只是感情好嘉栓,可當(dāng)我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布宏榕。 她就那樣靜靜地躺著,像睡著了一般侵佃。 火紅的嫁衣襯著肌膚如雪麻昼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天馋辈,我揣著相機(jī)與錄音抚芦,去河邊找鬼。 笑死迈螟,一個胖子當(dāng)著我的面吹牛叉抡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播答毫,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼褥民,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了洗搂?” 一聲冷哼從身側(cè)響起消返,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耘拇,沒想到半個月后撵颊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡驼鞭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年秦驯,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挣棕。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡译隘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出洛心,到底是詐尸還是另有隱情固耘,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布词身,位于F島的核電站厅目,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏法严。R本人自食惡果不足惜损敷,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望深啤。 院中可真熱鬧拗馒,春花似錦、人聲如沸溯街。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挥等,卻和暖如春友绝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肝劲。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工迁客, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涡相。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓哲泊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親催蝗。 傳聞我的和親對象是個殘疾皇子切威,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,587評論 2 350

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