在Java中使用redisTemplate操作缓存

背景

在最近的项目中,有一个需求是对一个很大的数据库进行查询,数据量大概在几千万条。但同时对查询速度的要求也比较高。

这个数据库之前在没有使用Presto的情况下,使用的是Hive,使用Hive进行一个简单的查询,速度可能在几分钟。当然几分钟也并不完全是跑SQL的时间,这里面包含发请求,查询数据并且返回数据的时间的总和。但是即使这样,这样的速度明显不能满足交互式的查询需求。

我们的下一个解决方案就是Presto,在使用了Presto之后,查询速度降到了秒级。但是对于一个前端查询界面的交互式查询来说,十几秒仍然是一个不能接受的时间。

虽然Presto相比Hive已经快了很多(FaceBook官方宣称的是10倍),但是对分页的支持不是很友好。我在使用的时候是自己在后端实现的分页。

在这种情况下应用缓存实属无奈之举。讲道理,优化应从底层开始,自底而上。上层优化的方式和效率感觉都很有局限。

为什么要使用缓存

前端查询中,单次查询的匹配数据量有可能会达到上百甚至上千条,在前端中肯定是需要分页展示的。就算每次查询10条数据,整个查询也要耗时6-8s的时间。想象一下,每翻一页等10s的场景。

所以,此时使用redis缓存。减少请求数据库的次数。将匹配的数据一并存入数据库。这样只有在第一次查询时耗费长一点,一旦查询完成,用户点击下一页就是毫秒级别的操作了。

使用redisTemplate

Spring封装了一个比较强大的模板,也就是redisTemplate,方便在开发的时候操作Redis缓存。在Redis中可以存储String、List、Set、Hash、Zset。下面将针对List和Hash分别介绍。

List

Redis中的List为简单的字符串列表,常见的有下面几种操作。

hasKey

判断一个键是否存在,只需要调用hasKey就可以了。假设这个Key是test,具体用法如下。

if(redisTemplate.hasKey("test")) { System.out.println("存在");}else{ System.out.println("不存在");}

range

该函数用于从redis缓存中获取指定区间的数据。具体用法如下。

if(redisTemplate.hasKey("test")) {// 该键的值为 [4, 3, 2, 1]System.out.println(redisTemplate.opsForList().range("test",0,0));// [4]System.out.println(redisTemplate.opsForList().range("test",0,1));// [4, 3]System.out.println(redisTemplate.opsForList().range("test",0,2));// [4, 3, 2]System.out.println(redisTemplate.opsForList().range("test",0,3));// [4, 3, 2, 1]System.out.println(redisTemplate.opsForList().range("test",0,4));// [4, 3, 2, 1]System.out.println(redisTemplate.opsForList().range("test",0,5));// [4, 3, 2, 1]System.out.println(redisTemplate.opsForList().range("test",0,-1));// [4, 3, 2, 1] 如果结束位是-1, 则表示取所有的值}

delete

删除某个键。

List test =newArrayList<>();test.add("1");test.add("2");test.add("3");test.add("4");redisTemplate.opsForList().rightPushAll("test", test);System.out.println(redisTemplate.opsForList().range("test",0,-1));// [1, 2, 3, 4]redisTemplate.delete("test");System.out.println(redisTemplate.opsForList().range("test",0,-1));//``` []size获取该键的集合长度。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

redisTemplate.opsForList().rightPushAll("test", test);

System.out.println(redisTemplate.opsForList().size("test")); // 4

leftPush我们把存放这个值的地方想象成如图所示的容器。container![](https://s1.51cto.com/images/blog/201901/19/5f945fd5a935742db1c9d8ff680641d9.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)并且取数据总是从左边取,但是存数据可以从左也可以从右。左就是leftPush,右就是rightPush。leftPush如下图所示。left-push![](https://s1.51cto.com/images/blog/201901/19/ac804daac7fb92de7e7a797829ccb9bc.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)用法如下。

for (int i = 0; i < 4; i++) {

Integer value = i + 1;

redisTemplate.opsForList().leftPush("test", value.toString());

System.out.println(redisTemplate.opsForList().range("test", 0, -1));

}

控制台输出的结果如下。

[1]

[2, 1]

[3, 2, 1]

[4, 3, 2, 1]

leftPushAll基本和leftPush一样,只不过是一次性的将List入栈。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

redisTemplate.opsForList().leftPushAll("test", test);

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1]

当然你也可以这样

redisTemplate.opsForList().leftPushAll("test", "1", "2", "3", "4");

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [4, 3, 2, 1]

leftPushIfPresent跟leftPush是同样的操作,唯一的不同是,当且仅当key存在时,才会更新key的值。如果key不存在则不会对数据进行任何操作。

redisTemplate.delete("test");

redisTemplate.opsForList().leftPushIfPresent("test", "1");

redisTemplate.opsForList().leftPushIfPresent("test", "2");

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []

leftPop该函数用于移除上面我们抽象的容器中的最左边的一个元素。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

redisTemplate.opsForList().rightPushAll("test", test);

redisTemplate.opsForList().leftPop("test"); // [2, 3, 4]

redisTemplate.opsForList().leftPop("test"); // [3, 4]

redisTemplate.opsForList().leftPop("test"); // [4]

redisTemplate.opsForList().leftPop("test"); // []

redisTemplate.opsForList().leftPop("test"); // []

值得注意的是,当返回为空后,在redis中这个key也不复存在了。如果此时再调用leftPushIfPresent,是无法再添加数据的。有代码有真相。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

redisTemplate.opsForList().rightPushAll("test", test);

redisTemplate.opsForList().leftPop("test"); // [2, 3, 4]

redisTemplate.opsForList().leftPop("test"); // [3, 4]

redisTemplate.opsForList().leftPop("test"); // [4]

redisTemplate.opsForList().leftPop("test"); // []

redisTemplate.opsForList().leftPop("test"); // []

redisTemplate.opsForList().leftPushIfPresent("test", "1"); // []

redisTemplate.opsForList().leftPushIfPresent("test", "1"); // []

rightPushrightPush如下图所示。right-push![](https://s1.51cto.com/images/blog/201901/19/5e1ebfcb613fd49e1f1345e36f3bebe1.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)用法如下。

for (int i = 0; i < 4; i++) {

Integer value = i + 1;

redisTemplate.opsForList().leftPush("test", value.toString());

System.out.println(redisTemplate.opsForList().range("test", 0, -1));

}

控制台输出的结果如下。

[1]

[1, 2]

[1, 2, 3]

[1, 2, 3, 4]

rightPushAll同rightPush,一次性将List存入。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

redisTemplate.opsForList().rightPushAll("test", test);

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]

当然你也可以这样。

redisTemplate.opsForList().rightPushAll("test", "1", "2", "3", "4");

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3, 4]

rightPushIfPresent跟rightPush是同样的操作,唯一的不同是,当且仅当key存在时,才会更新key的值。如果key不存在则不会对数据进行任何操作。

redisTemplate.delete("test");

redisTemplate.opsForList().rightPushIfPresent("test", "1");

redisTemplate.opsForList().rightPushIfPresent("test", "2");

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // []

rightPop该函数用于移除上面我们抽象的容器中的最右边的一个元素。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

redisTemplate.opsForList().rightPushAll("test", test);

redisTemplate.opsForList().rightPop("test"); // [1, 2, 3]

redisTemplate.opsForList().rightPop("test"); // [1, 2]

redisTemplate.opsForList().rightPop("test"); // [1]

redisTemplate.opsForList().rightPop("test"); // []

redisTemplate.opsForList().rightPop("test"); // []

与leftPop一样,返回空之后,再调用rightPushIfPresent,是无法再添加数据的。index获取list中指定位置的元素。

if (redisTemplate.hasKey("test")) {

// 该键的值为 [1, 2, 3, 4]

System.out.println(redisTemplate.opsForList().index("test", -1)); // 4

System.out.println(redisTemplate.opsForList().index("test", 0)); // 1

System.out.println(redisTemplate.opsForList().index("test", 1)); // 2

System.out.println(redisTemplate.opsForList().index("test", 2)); // 3

System.out.println(redisTemplate.opsForList().index("test", 3)); // 4

System.out.println(redisTemplate.opsForList().index("test", 4)); // null

System.out.println(redisTemplate.opsForList().index("test", 5)); // null

}

值得注意的有两点。一个是如果下标是-1的话,则会返回List最后一个元素,另一个如果数组下标越界,则会返回null。trim用于截取指定区间的元素,可能你会理解成与range是一样的作用。看了下面的代码之后应该就会立刻理解。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4]

redisTemplate.opsForList().trim("test", 0, 2); // [1, 2, 3]

其实作用完全不一样。range是获取指定区间内的数据,而trim是留下指定区间的数据,删除不在区间的所有数据。trim是void,不会返回任何数据。remove用于移除键中指定的元素。接受3个参数,分别是缓存的键名,计数事件,要移除的值。计数事件可以传入的有三个值,分别是-1、0、1。-1代表从存储容器的最右边开始,删除一个与要移除的值匹配的数据;0代表删除所有与传入值匹配的数据;1代表从存储容器的最左边开始,删除一个与要移除的值匹配的数据。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

test.add("4");

test.add("3");

test.add("2");

test.add("1");

redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4, 4, 3, 2, 1]

// 当计数事件是-1、传入值是1时

redisTemplate.opsForList().remove("test", -1, "1"); // [1, 2, 3, 4, 4, 3, 2]

// 当计数事件是1,传入值是1时

redisTemplate.opsForList().remove("test", 1, "1"); // [2, 3, 4, 4, 3, 2]

// 当计数事件是0,传入值是4时

redisTemplate.opsForList().remove("test", 0, "4"); // [2, 3, 3, 2]

rightPopAndLeftPush该函数用于操作两个键之间的数据,接受两个参数,分别是源key、目标key。该函数会将源key进行rightPop,再将返回的值,作为输入参数,在目标key上进行leftPush。具体代码如下。

List test = new ArrayList<>();

test.add("1");

test.add("2");

test.add("3");

test.add("4");

List test2 = new ArrayList<>();

test2.add("1");

test2.add("2");

test2.add("3");

redisTemplate.opsForList().rightPushAll("test", test); // [1, 2, 3, 4]

redisTemplate.opsForList().rightPushAll("test2", test2); // [1, 2, 3]

redisTemplate.opsForList().rightPopAndLeftPush("test", "test2");

System.out.println(redisTemplate.opsForList().range("test", 0, -1)); // [1, 2, 3]

System.out.println(redisTemplate.opsForList().range("test2", 0, -1)); // [4, 1, 2, 3]

Hash存储类型为hash其实很好理解。在上述的List中,一个redis的Key可以理解为一个List,而在Hash中,一个redis的Key可以理解为一个HashMap。put用于写入数据。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString()); // [1, 2, 3, 4]

redisTemplate.opsForHash().put("test", "isAdmin", true); // true

putAll用于一次性向一个Hash键中添加多个key。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

List list2 = new ArrayList<>();

list2.add("5");

list2.add("6");

list2.add("7");

list2.add("8");

Map valueMap = new HashMap<>();

valueMap.put("map1", list.toString());

valueMap.put("map2", list2.toString());

redisTemplate.opsForHash().putAll("test", valueMap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}

putIfAbsent用于向一个Hash键中写入数据。当key在Hash键中已经存在时,则不会写入任何数据,只有在Hash键中不存在这个key时,才会写入数据。同时,如果连这个Hash键都不存在,redisTemplate会新建一个Hash键,再写入key。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().putIfAbsent("test", "map", list.toString());

System.out.println(redisTemplate.opsForHash().entries("test")); // {map=[1, 2, 3, 4]}

get用于获取数据。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());

redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().get("test", "map")); // [1, 2, 3, 4]

System.out.println(redisTemplate.opsForHash().get("test", "isAdmin")); // true

Boolean bool = (Boolean) redisTemplate.opsForHash().get("test", "isAdmin");

System.out.println(bool); // true

String str = redisTemplate.opsForHash().get("test", "map").toString();

List array = JSONArray.parseArray(str, String.class);

System.out.println(array.size()); // 4

值得注意的是,使用get函数获取的数据都是Object类型。所以需要使用类型与上述例子中的布尔类型的话,则需要强制转换一次。List类型则可以使用fastjson这种工具来进行转换。转换的例子已列举在上述代码中。delete用于删除一个Hash键中的key。可以理解为删除一个map中的某个key。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

List list2 = new ArrayList<>();

list2.add("5");

list2.add("6");

list2.add("7");

list2.add("8");

Map valueMap = new HashMap<>();

valueMap.put("map1", list.toString());

valueMap.put("map2", list2.toString());

redisTemplate.opsForHash().putAll("test", valueMap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}

redisTemplate.opsForHash().delete("test", "map1"); // {map2=[5, 6, 7, 8]}

values用于获取一个Hash类型的键的所有值。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());

redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().values("test")); // [[1, 2, 3, 4], true]

entries用于以Map的格式获取一个Hash键的所有值。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());

redisTemplate.opsForHash().put("test", "isAdmin", true);

Map map = redisTemplate.opsForHash().entries("test");

System.out.println(map.get("map")); // [1, 2, 3, 4]

System.out.println(map.get("map") instanceof String); // true

System.out.println(redisTemplate.opsForHash().entries("test")); // {a=[1, 2, 3, 4], isAdmin=true}

hasKey用于获取一个Hash键中是否含有某个键。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());

redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().hasKey("test", "map")); // true

System.out.println(redisTemplate.opsForHash().hasKey("test", "b")); // false

System.out.println(redisTemplate.opsForHash().hasKey("test", "isAdmin")); // true

keys用于获取一个Hash键中所有的键。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());

redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().keys("test")); // [a, isAdmin]

size用于获取一个Hash键中包含的键的数量。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

redisTemplate.opsForHash().put("test", "map", list.toString());

redisTemplate.opsForHash().put("test", "isAdmin", true);

System.out.println(redisTemplate.opsForHash().size("test")); // 2

increment用于让一个Hash键中的某个key,根据传入的值进行累加。传入的数值只能是double或者long,不接受浮点型

redisTemplate.opsForHash().increment("test", "a", 3);

redisTemplate.opsForHash().increment("test", "a", -3);

redisTemplate.opsForHash().increment("test", "a", 1);

redisTemplate.opsForHash().increment("test", "a", 0);

System.out.println(redisTemplate.opsForHash().entries("test")); // {a=1}

multiGet用于批量的获取一个Hash键中多个key的值。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

List list2 = new ArrayList<>();

list2.add("5");

list2.add("6");

list2.add("7");

list2.add("8");

redisTemplate.opsForHash().put("test", "map1", list.toString()); // [1, 2, 3, 4]

redisTemplate.opsForHash().put("test", "map2", list2.toString()); // [5, 6, 7, 8]

List keys = new ArrayList<>();

keys.add("map1");

keys.add("map2");

System.out.println(redisTemplate.opsForHash().multiGet("test", keys)); // [[1, 2, 3, 4], [5, 6, 7, 8]]

System.out.println(redisTemplate.opsForHash().multiGet("test", keys) instanceof List); // true

scan获取所以匹配条件的Hash键中key的值。我查过一些资料,大部分写的是无法模糊匹配,我自己尝试了一下,其实是可以的。如下,使用scan模糊匹配hash键的key中,带SCAN的key。

List list = new ArrayList<>();

list.add("1");

list.add("2");

list.add("3");

list.add("4");

List list2 = new ArrayList<>();

list2.add("5");

list2.add("6");

list2.add("7");

list2.add("8");

List list3 = new ArrayList<>();

list3.add("9");

list3.add("10");

list3.add("11");

list3.add("12");

Map valueMap = new HashMap<>();

valueMap.put("map1", list.toString());

valueMap.put("SCAN_map2", list2.toString());

valueMap.put("map3", list3.toString());

redisTemplate.opsForHash().putAll("test", valueMap); // {SCAN_map2=[5, 6, 7, 8], map3=[9, 10, 11, 12], map1=[1, 2, 3, 4]}

Cursor> cursor = redisTemplate.opsForHash().scan("test", ScanOptions.scanOptions().match("SCAN").build());

if (cursor.hasNext()) {

while (cursor.hasNext()) {

Map.Entry entry = cursor.next();

System.out.println(entry.getValue()); // [5, 6, 7, 8]

}

}

引入redisTemplate

如果大家看懂了怎么用,就可以将redisTemplate引入项目中了。

引入pom依赖

org.springframework.boot

spring-boot-starter-data-redis

2.0.5.RELEASE

新建配置文件

然后需要新建一个RedisConfig配置文件。

package com.detectivehlh;

import com.fasterxml.jackson.annotation.JsonAutoDetect;

import com.fasterxml.jackson.annotation.PropertyAccessor;

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

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.Jackson2JsonRedisSerializer;

/**

RedisConfig

@author Lunhao Hu

@date 2019-01-17 15:12br/>**/

@Configuration

public class RedisConfig {

br/>@Bean

public RedisTemplate redisTemplate(RedisConnectionFactory factory) {

ObjectMapper om = new ObjectMapper();

om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

//redis序列化

Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

jackson2JsonRedisSerializer.setObjectMapper(om);

StringRedisTemplatetemplate=newStringRedisTemplate(factory);template.setValueSerializer(jackson2JsonRedisSerializer);template.setHashKeySerializer(jackson2JsonRedisSerializer);template.setHashValueSerializer(jackson2JsonRedisSerializer);template.setValueSerializer(jackson2JsonRedisSerializer);template.afterPropertiesSet();returntemplate;

}

}

注入

将redisTemplate注入到需要使用的地方。

@Autowired

private RedisTemplate redisTemplate;

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

推荐阅读更多精彩内容