008.Redis

特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix

什么是 Redis

Redis 是用 C 语言开发的一个开源的高性能键值对(key-value)数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求

Redis 的特点

  • Redis 支持数据持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
  • Redis 不仅仅支持简单的 KV 类型的数据,同时还提供 listsetzsethash 等数据结构的存储
  • Redis 支持数据的备份,即 Master - Slave 模式的数据备份

Redis 的优势

  • 性能极高: Redis 读的速度是 110000 次/s, 写的速度是 81000 次/s
  • 丰富的数据类型: Redis 支持 Strings, Lists, Hashes, SetsOrdered Sets 数据类型操作
  • 原子性: Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTIEXEC 指令包起来
  • 其它特性:Redis 还支持 发布/订阅,Key 过期设置等

Redis 的应用场景

  • 缓存(数据查询、短连接、新闻内容、商品内容等等)
  • 分布式集群架构中的 Session 分离
  • 聊天室的在线好友列表
  • 任务队列(秒杀、抢购、12306 等等)
  • 应用排行榜
  • 网站访问统计
  • 数据过期处理(可以精确到毫秒)
  • 分布式协调(分布式锁)

Redis 高可用集群

HA(High Available,高可用性群集)机集群系统简称,是保证业务连续性的有效解决方案,一般有两个或两个以上的节点,且分为活动节点及备用节点。通常把正在执 行业务的称为活动节点,而作为活动节点的一个备份的则称为备用节点。当活动节点出现问题,导致正在运行的业务(任务)不能正常运行时,备用节点此时就会侦测到,并立即接续活动节点来执行业务。从而实现业务的不中断或短暂中断。

Redis 一般以主/从方式部署(这里讨论的应用从实例主要用于备份,主实例提供读写)该方式要实现 HA 主要有如下几种方案:

  • keepalived: 通过 keepalived 的虚拟 IP,提供主从的统一访问,在主出现问题时, 通过 keepalived 运行脚本将从提升为主,待主恢复后先同步后自动变为主,该方案的好处是主从切换后,应用程序不需要知道(因为访问的虚拟 IP 不变),坏处是引入 keepalived 增加部署复杂性,在有些情况下会导致数据丢失
  • zookeeper: 通过 zookeeper 来监控主从实例, 维护最新有效的 IP, 应用通过 zookeeper 取得 IP,对 Redis 进行访问,该方案需要编写大量的监控代码
  • sentinel: 通过 Sentinel 监控主从实例,自动进行故障恢复,该方案有个缺陷:因为主从实例地址( IP & PORT )是不同的,当故障发生进行主从切换后,应用程序无法知道新地址,故在 Jedis2.2.2 中新增了对 Sentinel 的支持,应用通过 redis.clients.jedis.JedisSentinelPool.getResource() 取得的 Jedis 实例会及时更新到新的主实例地址
20150620161606990.jpg

注意: sentinel 是解决 HA 问题的,cluster 是解决主从复制问题的,不重复,并且经常一起用

Redis Sentinel

Redis 集群可以在一组 redis 节点之间实现高可用性和 sharding。在集群中会有 1 个 master 和多个 slave 节点。当 master 节点失效时,应选举出一个 slave 节点作为新的 master。然而 Redis 本身 (包括它的很多客户端) 没有实现自动故障发现并进行主备切换的能力,需要外部的监控方案来实现自动故障恢复。

Redis Sentinel 是官方推荐的高可用性解决方案。它是 Redis 集群的监控管理工具,可以提供节点监控、通知、自动故障恢复和客户端配置发现服务。

18841d5327556bfa750148943011901d1eac3cd8.jpg

基于 Docker 安装 Redis 集群

2018 年 10 月 Redis 发布了稳定版本的 5.0 版本,推出了各种新特性,其中一点是放弃 Ruby 的集群方式,改为 使用 C 语言编写的 redis-cli 的方式,使集群的构建方式复杂度大大降低

  • 下载所需镜像
docker pull redis
docker pull zvelo/redis-trib
  • 创建配置文件

官方配置文件地址:http://download.redis.io/redis-stable/redis.conf ,我们部署 3 个节点,需要分别创建 3 个配置文件,路径如下

renode1:`cluster/node1/redis.conf`
renode2:`cluster/node2/redis.conf`
renode3:`cluster/node3/redis.conf`

配置文件内容如下 (端口号不要重复)

bind 0.0.0.0
# 关闭保护模式
protected-mode no
# 绑定自定义端口
port 6379
# 禁止 Redis 后台运行
# daemonize yes
pidfile /var/run/redis_6379.pid
# 开启集群
cluster-enabled yes
# 集群的配置,配置文件首次启动自动生成
cluster-config-file nodes_6379.conf
# 开启 AOF
appendonly yes
# 集群的 IP
cluster-announce-ip 192.168.x.x
# 集群的端口
cluster-announce-port 6379
# 集群的总线端口
cluster-announce-bus-port 16379
  • 创建资源配置

docker-compose.yml 配置文件如下

version: '3.1'
services:
  renode1:
    image: redis
    container_name: renode1
    restart: always
    ports:
      - 6379:6379
      - 16379:16379
    volumes:
      - ./cluster/node1:/usr/local/etc/redis
    command:
      redis-server /usr/local/etc/redis/redis.conf
  renode2:
    image: redis
    container_name: renode2
    restart: always
    ports:
      - 6380:6380
      - 16380:16380
    volumes:
      - ./cluster/node2:/usr/local/etc/redis
    command:
      redis-server /usr/local/etc/redis/redis.conf
  renode3:
    image: redis
    container_name: renode3
    restart: always
    ports:
      - 6381:6381
      - 16381:16381
    volumes:
      - ./cluster/node3:/usr/local/etc/redis
    command:
      redis-server /usr/local/etc/redis/redis.conf
docker-compose up -d
  • 创建集群
docker run --rm -it zvelo/redis-trib create 192.168.141.220:6379 192.168.141.220:6380 192.168.141.220:6381

# 输出如下
>>> Creating cluster
>>> Performing hash slots allocation on 3 nodes...
Using 3 masters:
192.168.141.220:6379
192.168.141.220:6380
192.168.141.220:6381
M: 9250c85592b7bb8a19636b90e4cf22590bd3334f 192.168.141.220:6379
   slots:0-5460 (5461 slots) master
M: 7373de7b44ee50a1e4f653bfba1bb808138e7e3a 192.168.141.220:6380
   slots:5461-10922 (5462 slots) master
M: ec7e6e4cdd142249e3aa83541044932655d75b66 192.168.141.220:6381
   slots:10923-16383 (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join..
>>> Performing Cluster Check (using node 192.168.141.220:6379)
M: 9250c85592b7bb8a19636b90e4cf22590bd3334f 192.168.141.220:6379
   slots:0-5460 (5461 slots) master
M: 7373de7b44ee50a1e4f653bfba1bb808138e7e3a 192.168.141.220:6380
   slots:5461-10922 (5462 slots) master
M: ec7e6e4cdd142249e3aa83541044932655d75b66 192.168.141.220:6381
   slots:10923-16383 (5461 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

验证 Redis 集群是否成功

  • 交互式进入容器
docker exec -it renode1 /bin/bash
  • 登录 Redis
redis-cli -p 6379
  • 查看集群信息
127.0.0.1:6379> cluster info

# 输出如下
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:3
cluster_my_epoch:1
cluster_stats_messages_ping_sent:860
cluster_stats_messages_pong_sent:870
cluster_stats_messages_sent:1730
cluster_stats_messages_ping_received:868
cluster_stats_messages_pong_received:860
cluster_stats_messages_meet_received:2
cluster_stats_messages_received:1730
  • 查看集群节点
127.0.0.1:6379> cluster nodes

# 输出如下
ec7e6e4cdd142249e3aa83541044932655d75b66 192.168.141.220:6381@16381 master - 0 1569665070102 3 connected 10923-16383
7373de7b44ee50a1e4f653bfba1bb808138e7e3a 192.168.141.220:6380@16380 master - 0 1569665069094 2 connected 5461-10922
9250c85592b7bb8a19636b90e4cf22590bd3334f 192.168.141.220:6379@16379 myself,master - 0 1569665069000 1 connected 0-5460
  • 查看集群插槽
127.0.0.1:6379> cluster slots

# 输出如下
1) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "192.168.141.220"
      2) (integer) 6381
      3) "ec7e6e4cdd142249e3aa83541044932655d75b66"
2) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "192.168.141.220"
      2) (integer) 6380
      3) "7373de7b44ee50a1e4f653bfba1bb808138e7e3a"
3) 1) (integer) 0
   2) (integer) 5460
   3) 1) "192.168.141.220"
      2) (integer) 6379
      3) "9250c85592b7bb8a19636b90e4cf22590bd3334f"

基于 Docker 安装 Redis Sentinel

  • 创建配置文件

我们部署 3 个 Sentinel 节点,需要分别创建 3 个配置文件,路径如下

resentinel1:`cluster/node1/sentinel.conf`
resentinel2:`cluster/node2/sentinel.conf`
resentinel3:`cluster/node3/sentinel.conf`

配置文件内容如下 (端口号不要重复)

bind 0.0.0.0
port 26379
dir /tmp
# 自定义集群名,其中 192.168.141.220 为 Redis Master 的 IP,6379 为 Redis Master 的端口,2 为最小投票数(因为有 3 台 Sentinel 所以可以设置成 2)
sentinel monitor rmaster 192.168.141.220 6379 2
sentinel down-after-milliseconds rmaster 30000
sentinel parallel-syncs rmaster 1
sentinel failover-timeout rmaster 180000
sentinel deny-scripts-reconfig yes
  • 创建资源配置

docker-compose.yml 配置文件如下

version: '3.1'
services:
  resentinel1:
    image: redis
    container_name: resentinel1
    ports:
      - 26379:26379
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./cluster/node1/sentinel.conf:/usr/local/etc/redis/sentinel.conf
  resentinel2:
    image: redis
    container_name: resentinel2
    ports:
      - 26380:26380
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./cluster/node2/sentinel.conf:/usr/local/etc/redis/sentinel.conf
  resentinel3:
    image: redis
    container_name: resentinel3
    ports:
      - 26381:26381
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    volumes:
      - ./cluster/node3/sentinel.conf:/usr/local/etc/redis/sentinel.conf

验证 Sentinel 集群是否成功

  • 交互式进入容器
docker exec -it resentinel1 /bin/bash
  • 登录 Redis Sentinel
redis-cli -p 26379
  • 查看集群 Master 信息
127.0.0.1:26379> sentinel master rmaster

# 输出如下
 1) "name"
 2) "rmaster"
 3) "ip"
 4) "192.168.141.220"
 5) "port"
 6) "6379"
 7) "runid"
 8) "30055483aeb9d75f35c0046aaec03440731e3e88"
 9) "flags"
10) "master"
11) "link-pending-commands"
12) "0"
13) "link-refcount"
14) "1"
15) "last-ping-sent"
16) "0"
17) "last-ok-ping-reply"
18) "413"
19) "last-ping-reply"
20) "413"
21) "down-after-milliseconds"
22) "30000"
23) "info-refresh"
24) "2878"
25) "role-reported"
26) "master"
27) "role-reported-time"
28) "143537"
29) "config-epoch"
30) "0"
31) "num-slaves"
32) "0"
33) "num-other-sentinels"
34) "2"
35) "quorum"
36) "2"
37) "failover-timeout"
38) "180000"
39) "parallel-syncs"
40) "1"

Redis 数据类型

Redis 支持 5 种数据类型

  • string(字符串)
  • hash(哈希)
  • list(列表)
  • set(集合)
  • zset(sorted set:有序集合)

字符串

  • string 是 Redis 最基本的数据类型。一个 key 对应一个 value
  • string 是二进制安全的。也就是说 Redis 的 string 可以包含任何数据。比如图片或者序列化的对象
  • string 类型的值最大能存储 512MB
  • string 类似于 Java 中的 Map,一个 key 对应一个 value
15593451-e46db8d61d8c41aa.jpg

哈希

  • Redis hash 是一个键值对(key - value)集合
  • Redis hash 是一个 string 类型的 keyvalue 的映射表,hash 特别适合用于存储对象
  • 可以将 Redis hash 看成一个 KV 的集合。也可以将其想成一个 hash 对应着多个 string
15593451-43d6c27ce64dffe1.jpg

列表

Redis 列表是简单的字符串列表,按照插入顺序排序。我们可以往列表的左边或者右边添加元素, list 就是一个简单的字符串集合,和 Java 中的 list 相差不大,区别就是这里的 list 存放的是字符串 (list 内的元素是可重复的)

15593451-948582ab93a858ce.jpg

集合

Redis 的 set 是字符串类型的无序集合。集合是通过哈希表实现的,因此添加、删除、查找的复杂度都是 o(1),Redis 的 set 是一个 key 对应着多个字符串类型的 value,也是一个字符串类型的集合,但是和 Redis 的 list 不同的是 set 中的字符串集合元素不能重复,但是 list 可以

有序集合

Redis zset 和 set 一样都是字符串类型元素的集合,并且集合内的元素不能重复;不同的是,zset 每个元素都会关联一个 double 类型的分数。redis 通过分数来为集合中的成员进行从小到大的排序

15593451-cf97abbc9867a7fd.jpg

zset 的元素是唯一的,但是分数(score)却可以重复

Spring Boot 整合 Redis

添加依赖

  • 主要依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-pool2</artifactId>
</dependency>
  • 因为要与数据库结合使用,以 provider-admin-service 项目为例,一并提供完整依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.funtl</groupId>
        <artifactId>provider</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <artifactId>provider-admin-service</artifactId>
    <packaging>jar</packaging>

    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Spring Boot Data -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- Spring Cloud -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <!-- Database -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.tomcat</groupId>
                    <artifactId>tomcat-jdbc</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
        </dependency>

        <!-- Commons -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.funtl.hello.spring.cloud.provider.ProviderAdminApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

添加配置

  • 主要配置
spring:
  redis:
    cluster:
      nodes: 192.168.141.206:6379,192.168.141.206:6380,192.168.141.206:6381
    lettuce:
      # 连接池配置
      pool:
        # 连接池中的最小空闲连接,默认 0
        min-idle: 0
        # 连接池中的最大空闲连接,默认 8
        max-idle: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制),默认 -1ms
        max-wait: -1ms
        # 连接池最大连接数(使用负值表示没有限制),默认 8
        max-active: 8
  • 完整配置
spring:
  main:
    allow-bean-definition-overriding: true
  application:
    name: provider-admin
  zipkin:
    base-url: http://192.168.141.204:9411
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.141.206:8066/myshop?useUnicode=true&characterEncoding=utf-8&serverTimezone=Hongkong&useSSL=false
    username: root
    password: 123456
    hikari:
      minimum-idle: 5
      idle-timeout: 600000
      maximum-pool-size: 10
      auto-commit: true
      pool-name: MyHikariCP
      max-lifetime: 1800000
      connection-timeout: 30000
      connection-test-query: SELECT 1
  redis:
    cluster:
      nodes: 192.168.141.206:6379,192.168.141.206:6380,192.168.141.206:6381
    lettuce:
      pool:
        min-idle: 0
        max-idle: 8
        max-wait: -1ms
        max-active: 8

server:
  port: 11000

eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8761/eureka/

mybatis:
  type-aliases-package: com.funtl.hello.spring.cloud.provider.domain
  mapper-locations: classpath:mapper/*.xml

客户端工具类

注意: 我们使用 Spring 官方推荐的 Lettuce 客户端工具不再采用 Jedis,原因是

  • 效率优于 Jedis
  • 目前 Jedis 只支持单机
  • Jedis setNx 和设置过期时间是不同步的,在某些极端的情况下会发生死锁,导致程序崩溃(如果没有设置 value, 线程 1 可能会释放线程 2 的锁)

注意: 由于代码量较大会导致《简书》页面无法正常显示,请移步 LettuceUtils.java

  • 通过 Spring 容器注入即可使用
package com.funtl.hello.spring.cloud.provider.cache;

import com.funtl.hello.spring.cloud.commons.cache.LettuceUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
public class LettuceUtilsTests {

    @Resource
    private LettuceUtils lettuceUtils;

    @Test
    public void testSet() {
        lettuceUtils.set("name", "李小红");
    }

    @Test
    public void testGet() {
        System.out.println(lettuceUtils.get("name"));
    }

    @Test
    public void testDelete() {
        lettuceUtils.delete("name");
    }
}

特别说明: 本人平时混迹于 B 站,不咋回复这里的评论,有问题可以到 B 站视频评论区留言找我
视频地址: https://space.bilibili.com/31137138/favlist?fid=326428938
课件说明: 本次提供的课件是 Spring Cloud Netflix 版微服务架构指南,如果有兴趣想要学习 Spring Cloud Alibaba 版,可以前往 http://www.qfdmy.com 查看相关课程资源
案例代码: https://github.com/topsale/hello-spring-cloud-netflix

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

推荐阅读更多精彩内容