Redis Note(四)高可用保障

(四)redis高可用保障

A. 持久化

  • RDB模式持久化
    • 简述:该模式即为快照的概念,直接将redis内存的数据拷贝到本地磁盘,一般设置拷贝的执行间隔会较久,如一天一次或者一小时一次。
    • 实现机制:具体实现基于save & bgsave两种方式。
      • save:较不常用,易导致redis长时间处于阻塞状态。
      • bgsave:RDB模式下默认的持久化方式,会fork子进程进行持久化以保障redis的阻塞仅在fork动作的瞬间发生。
    • 触发事件
      • 周期性触发:redis.conf配置文件中会设置save m n,该配置默认使用bgsave命令,表示在m秒内发生n次修改时触发持久化。master & slave都会基于此配置周期性的进行bgsave,生成相应的rdb文件。
      • 主从建立:建立主从关系时slave会执行全量复制(sync),此时master自动触发bgsave生成rdb文件并发送给slave。
      • 重启:shutdown后重启,若没有开启aof时会自动执行bgsave。
      • debug:debug reload时,会导致save的操作。
    • 优点
      • 具体某个时间点的快照,且便于复制到其他redis server上进行数据恢复。
      • 恢复数据的速度远快于aof。
    • 缺点
      • 实时性较差,若对于灾后数据丢失的容忍性较低,则不建议使用。
      • rdb格式的版本较多,存在版本互相不兼容的潜在风险。
  • AOF模式持久化
    • 简述:该模式会独立创建日志以记录redis的所有历史命令,重启后会根据记录重新执行所有命令来恢复所有数据。
    • 触发事件:仅在redis重启时且redis.conf配置了appendonly yes & appendfilename xxx.aof会触发该模式。
    • 运行顺序
      1. redis命令会append到aof buffer然后再写入日志,在buffer写入日志文件时,建议配置everysec兼顾性能和数据安全;
      2. aof日志文件会越变越大,因此定期会对其重写实现压缩,亦可使用bgrewriteaof手动触发压缩;
      3. redis重启后使用aof日志文件开始恢复数据。
    • 优点:高时效性,保障数据的完整性。
    • 缺点:redis时cpu密集型服务,而aof会fork子进程来进行持久化将占用较大内存,当前以copy on write来进行父子进程间内存共享来缓解该问题。

B. 主从机制 & 同步问题

  • 简述
    • 为保障redis的高可用性生产中几乎不会使用单点redis,至少是最简单主从模式,多会采用哨兵模式或者集群模式。
    • master & slave间的数据同步,默认使用repl-disable-tcp-nodelay no,即开启tcp-nodelay来降低同步延迟,不会等待合并包以确保高时效性。若设置成repl-disable-tcp-nodelay yes则默认情况下会增加时延40ms,节约带宽消耗。

1. 数据同步

  • 概述:redis2.8及以上版本,使用psync完成主从数据同步,同步过程分为全量复制 & 部分复制两类。

  • psync:具体命令为于slave上使用psync runID offset

    • 若master响应fullresync,则触发全量复制。
    • 若master响应continue,则进行部分复制。
    • 注意,低版本下使用该命令,基于仅支持sync命令所以会响应error
  • 全量复制:一般仅在初次建立主从关系时会使用该模式,master通过bgsave将全量rdb数据同步至slave,网络开销较大。

  • 部分复制:多用于主从复制时网络闪断等造成的数据丢失场景。若slave再次成功连接master,情况允许下master会补发丢失数据给slave,相对全部重发而言降低了网络开销。为支撑该功能,psync的部分复制过程需要下面三个组件

    • 复制偏移量:slave每秒都会上报自己的offset给master,该数值可参考info replication中显示的offset内容。

    • 复制积压缓冲区:master有了对应slave时创建的对象,是保存在master上的固定长度的队列(queue, FIFO),默认大小为1mb,其核心功能保存最近已复制数据的功能,用于psync时丢失的数据补救。因为master响应set命令时,把该命令发送给slave的同时也会将之写入复制积压缓冲区。

    • 主节点运行ID(runID):redis启动后会动态分配一个40bits的十六进制string作为runID用于识别redis节点。

      • slave保存master的runID,便于slave识别自己从谁那儿复制。
      • runID可用info server进行查看确认。master重启后runID会变化,slave无法部分增量复制,只可全量复制。
      • 不可仅靠复制偏移量offset来进行部分复制,因为重启后master可能会加载其他的rdb文件导致数据变化
  • 退化问题:若复制积压缓冲区失效(slave请求的offset不在队列存储的offset内),runID即便未变化,也无法完成部分复制,只能进行全量复制。

  • 心跳:master & slave建立长连接后会发送心跳以维持,双方彼此都有心跳检测机制。

    • master:默认master每10s对slave进行ping命令,具体周期可通过repl-ping-slave-period的参数进行控制。

    • slave:在主线程中每隔1s发送replconf ack上报自己offset,告知master自己的复制偏移量。

    • 判定:master根据replconf命令判定slave的超时时间,可在info replicationlag字段确认,正常应该在1-2之间。默认配置repl-timeout为60s,若slave发送给master的时间超过60s,则判定slave下线。

  • 异步复制:master不仅负责数据读写,还需把写命令set 同步给slave。master发送写命令是异步的,自身处理完写命令并回复client,无需等待slave回复复制是否完成。

  • 过期数据同步:master存储了大量设置超时的数据时需注意同步问题,数据的删除设计两种策略。

    • 惰性删除:该策略下slave不会主动删除,master处理读取命令时检查key过期与否,若过期执行del删除并把该命令同步给slave让其也删除对应key。

    • 定时删除:master会定时采样一定量的key,若发现采样中有key过期则执行del,再同步给salve同步删除。若此时大量数据超时,master采样速度跟不上过期速度且master没读到过期的key,那么slave就收不到del命令,这会导致从slave上读取到过期数据。升级redis3.2后,读取slave时会再检查key过期与否再决定是否返回给client数据。

2. 简单主从模式

  • 实现方式:slave节点的redis.conf加入slaveof master-ip master-port,或者连接客户端使用slaveof xxx命令。

  • 复制过程:在slave上配置相关信息后启动时,或者使用slaveof xxx命令时已经激活复制的流程。

    1. slave会保存master的信息,可通过info replication查询保存的具体信息内容;
    2. slave定时(每秒)维护复制逻辑,依靠这个定时任务发现master的存在并与之建立连接;
    3. 建立主从连接时具体建立一个socket专门接收master发出的复制命令,打印日志connecting to master xxxx:6379
    4. 若建立连接失败,定时任务会持续尝试,可手动slaveof no one结束这个尝试的循环;
    5. 建立主从成功后,slave发送ping命令请求通信,以此检测主从间的socket是否可用,并确认master是否可接受处理命令;
    6. 若master有设置requirepass参数,则需要进行权限验证,确保slave的masterauth参数与之相同;
    7. 上方步骤全部完成后,开始同步数据。对于首次建立复制的场景master本身完成一次basave,将所有数据都传给slave。这一步操作是最耗时的;
    8. 后续master会持续将写命令发送给slave,保证主从一致性。
  • 配置文件redis.conf参考

    bind 0.0.0.0
    protected-mode yes
    port 6379
    tcp-backlog 511
    timeout 60
    tcp-keepalive 300
    daemonize yes
    supervised no
    pidfile /var/run/redis_6379.pid
    loglevel notice
    logfile "/apprun/redis/logs/redis.log"
    databases 16
    always-show-logo yes
    save 86400 1
    stop-writes-on-bgsave-error yes
    rdbcompression yes
    rdbchecksum yes
    dbfilename dump.rdb
    dir /apprun/data/redis
    masterauth Admin!123
    replica-serve-stale-data yes
    replica-read-only yes
    repl-diskless-sync no
    repl-diskless-sync-delay 5
    repl-disable-tcp-nodelay no
    repl-backlog-size 100mb
    replica-priority 100
    requirepass xx123
    maxmemory 5gb
    # 在redis达到maxmemory阈值时,采用lru算法将设置过期时间的key删除。使用该策略后,内存共享池机制失效。
    maxmemory-policy volatile-lru
    lazyfree-lazy-eviction no
    lazyfree-lazy-expire no
    lazyfree-lazy-server-del no
    replica-lazy-flush no
    appendonly no
    appendfilename "appendonly.aof"
    appendfsync everysec
    no-appendfsync-on-rewrite no
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    aof-load-truncated yes
    aof-use-rdb-preamble yes
    lua-time-limit 5000
    slowlog-log-slower-than 10000
    slowlog-max-len 128
    latency-monitor-threshold 0
    notify-keyspace-events ""
    hash-max-ziplist-entries 512
    hash-max-ziplist-value 64
    list-max-ziplist-size -2
    list-compress-depth 0
    set-max-intset-entries 512
    zset-max-ziplist-entries 128
    zset-max-ziplist-value 64
    hll-sparse-max-bytes 3000
    stream-node-max-bytes 4096
    stream-node-max-entries 100
    activerehashing yes
    client-output-buffer-limit normal 0 0 0
    client-output-buffer-limit replica 0 0 0
    client-output-buffer-limit pubsub 32mb 8mb 60
    # 内存回收策略:定时任务删除expired key,hz 10即每秒运行10次检测一次。
    hz 10
    dynamic-hz yes
    aof-rewrite-incremental-fsync yes
    rdb-save-incremental-fsync yes
    slave-serve-stale-data yes
    slave-read-only yes
    slaveof 10.xx.10.xx 6379
    

3. 哨兵模式sentinel

  • 概述:简单的主从模式,若master节点down后,slave切换master需要通过slaveof no one手动实现,且调用redis的模块需要修改ip地址并进行重启。基于此种场景,redis sentinel分布式架构应运而生。

    • 补充说明:简单的主从模式基于vip+keepalived的模式亦可实现自动切换主从且不用修改应用模块的配置文件,但不如哨兵模式简单直接。
  • 架构 & 工作流程

    • 包含若干sentinel节点和redis数据节点。
    • 若sentinel节点发现节点不可达时,会对该节点做下线标识
    • 若被标识的是master节点,sentinel会和其他sentinel节点投票协商。
    • 若大多数sentinel认为master不可达,则它们会选举一个sentinel节点完成自动故障转移工作,同时通知redis应用方。
  • 故障转移步骤细节

    1. master故障,两个slave节点和master失去连接(默认结构为1主2从的redis数据节点,3个sentinel节点),主从复制中断。
    2. sentinel节点监控发现master出现故障。
    3. 多个sentinel节点投票一致确认故障情况,选举出sentinel-3为leader负责故障转移。
    4. sentinel-3自动选择slave-1通过slaveof no one升级为master,并让slave-2更新自己的master对象,再通知客户端当前的master情况,最后让旧master恢复后也对应更新主从关系。
  • sentinel节点核心功能

    • 监控:定期检测redis数据节点、其余sentinel节点是否可达。
    • 通知:将故障转移结果通知给应用方。
    • 主节点故障转移:实现slave升级master,并维护主从关系。
    • 配置提供方:客户端初始化连接的是sentinel节点集合,从中获取master信息。
  • 实现方式

    1. sentinel需要投票完成选主,所以server数量为奇数(一般3即可,一主两从,三个sentinel监控);

    2. 完成/apprun/redis/etc/下redis.conf的配置内容(默认6379端口),注意配置slaveof xx;

    3. 进行/apprun/redis/conf/下sentinel.conf配置;

      # 3份sentinel配置文件,监控的都是当时作为master节点的redis ip+port,即所有sentinel的配置文件内容其实是一样的 & 投票需要的决定数目
      # 使用端口
      port 26379
      # 工作目录
      dir "/apprun/redis"
      daemonize yes
      # 日志所在路径
      logfile "/apprun/redis/logs/sentinel.log"
      sentinel deny-scripts-reconfig yes
      # sentinel monitor <master-name-custemized> <ip> <port> <quorum>,quorum用于判定主节点不可达时需要的票数。
      sentinel monitor mymaster 172.xx.xx.3x 6379 2
      # sentinel down-after-milliseconds <master-name-custemized> <times>,每个sentinel定期ping master,若5000ms未回复,即判定离线。
      sentinel down-after-milliseconds mymaster 5000
      sentinel failover-timeout mymaster 18000
      sentinel auth-pass mymaster xxpassxxxword
      
    4. 最后使用./redis-sentinel sentinel.conf启动,并检查logs文件确认启动成功与否。

  • 原理解释

    • 三个定时任务
      • 每10s,sentinel节点会向master和slave发送info reolication命令获取整体拓扑结构。
      • 每2s,sentinel节点会向redis数据节点_sentinel_:hello频道发送该sentinel对master的判定信息 & 自己节点信息。所有sentinel节点都订阅该频道,也可了解到其他sentinel对master的判断情况,作为客观下线 & leader选举的依据。
      • 每1s,sentinel节点向redis数据节点和其余sentinel发送ping作为心跳检测。
    • 主观下线
      • 上方定时任务,每秒一次ping,若被ping对象未能及时在sentinel down-after-milliseconds参数内响应,该sentinel即判定被ping对象下线,此为主观下线。
      • 主观下线时一家之言,存在误判可能性。
    • 客观下线:sentinel发送sentinel is-master-down-by-addr命令,让其他sentinel节点也判定master情况,超过quorum个数,此时的下线即为客观下线,可信度很高。
    • sentinel节点leader选举
      • 选举出leader后,才能对认定的客观下线master节点执行故障转移操作。
      • 选举leader基于Raft算法,大致逻辑为:
        1. 所有sentinel都可以成为leader,当它确认master主观下线时,向其他sentinel发送sentinel is-master-down-by-addr
        2. 若其他sentinel未同意过别人的该请求,就会同意本次请求(先到先得),否则就拒绝;
        3. 如果该sentinel发现自己的票数大于半数,那么它将成为leader。
    • 故障转移
      • 从slave中选择新的master,基于slave-priority参数;
      • slave-priority无法选出,根据复制偏移量最大的优先原则;
      • 若依然无法选举出结果,选择runid最小的slave节点,即谁先启动谁做master。
  • 总结:相对于简单的redis主从,哨兵模式可以只能地自动完成主从切换,基于jedis时不需要配置vip,使用sentinelPool即可。

4. 集群模式

a. 基础部分
  • 概述:redis cluster是分布式解决方案,于3.0版本推出,可解决单机内存、并发、流量的瓶颈问题。
  • 分区:基于hash分区(非一致性hash),具体为虚拟槽分区(0 - 16383个slots)。
    • 每个redis节点接收一部分的slots,若cluster共5个节点,每个节点分到3276个slots;
    • 计算公式:slot = CRC16(key) & 16383(取模)。
  • 潜在问题
    • 批量操作:对于mget mset之类的key批量操作仅在针对于同一个slot时可进行,事务操作同样受限,必须是指向同一个节点的key才可进行事务操作,pipeline相对不影响。
    • 负载不均:key是最小颗粒度,若key是hash或者list等big key,均指向同一个slot,会一定程度负载不均衡;
    • 结构问题:cluster仅有一个db 0,不支持16个db;复制结构只有一层,不存在树形拓扑结构。
  • masterauth & requirepass的区别
    • masterauth:slave节点数据同步时,需要用到这个密码来访问master节点;
    • requirepass:每个node自己登录时的密码,可以各不相同;
    • 注意:slave节点配置的masterauth,需要和master节点的requirepass一致,为防止混乱,建议全部都设成一样。理论上master上不用设置masterauth,但是主从身份可能会切换,所以一般都会配置上。
b. 集群新建
  • 节点握手:启动集群后的节点后它们彼此并不互相知晓。节点通过gossip协议彼此通信完成节点握手,具体步骤如下方。

    • 客户端发起命令cluster meet <ip> <port>,让其与对应的ip port对象握手通信。
    • 该对象收到meet消息,保存发起方的节点信息,并回复pong。
    • 两者通过定期ping pong命令保持心跳通信。
    • 注意,仅需在集群内任一节点执行cluster meet <ip> <port>即可,握手状态胡通过消息在集群中传播,其他节点会自动发现后续加入集群的新节点,并发起握手流程。最终,执行cluster nodes确认全部节点都彼此感知并组成集群。
  • 分配槽

    • 概述:redis集群将所有数据映射值16384个slots中,所以每个key都会掉落到某个slot中。
    • 初始分配命令:登陆不同节点各自分配工作,node1上进行cluster addslots {0..5461},node2上cluster addslots {5462..10922},node3上cluster addslots {10923.。16383}
    • 主从关系确立:剩余3个节点需要完成和前面3个节点的主从关系建立,使用命令cluster replicate <nodeId>
  • 总结:redis官方提供redis-trib.rb工具可帮助更快速搭建集群,使用命令redis-trib.rb create --replicas <node1ip:port> <node...>

c. 节点通信
  • 概述:分布式结构需要维护节点的metadata,一般采用方式有集中式P2P方式。redis cluster采用的是Gossip协议,是P2P方式,让节点彼此不断通信交换信息。
  • 过程说明
    1. 集群中的每个节点会单独开辟一个tcp通道用于节点间的互相通信,一般是16379(服务端口+1w)。
    2. 每个节点在固定周期内会通过特定规则选择几个节点发送ping消息。
    3. 接收到ping消息的节点会返回pong消息。
  • Gossip协议
    • 主要职责:作为集群中信息交换的载体。
    • 类别:ping,pong,meet以及fail,ping&pong是最频繁的消息用于保持心跳,meet是通知新节点加入,fail则是下线时。
d. 集群伸缩
  • 概述:在不影响提供服务前提下,完成redis集群的节点增加或下线,其主要是内容是slots在不同节点间的灵活移动。
  • 扩容集群步骤
    • 准备新节点:细节可参考初次启动集群节点的内容。
    • 加入集群:依然使用cluster meet <ip> <port>实现。加入集群后,新加入的节点都是master的角色,且没有分配槽
    • 注意点:建议避免使用cluster meet完成新节点加入集群,使用redis-trib.rb工具更稳定、适合生产环境,具体命令为redis-trib.rb add-node newhost:port existingHost:port
    • 槽 & 数据迁移:在上方操作完成后,开始进行slots的数据迁移,按slot逐个完成迁移工作,可通过pipeline提升网络利用率。实现槽迁移可用命令redis-trib.rb reshard host:port --from <源节点id,若有多个节点使用逗号分隔> --to<目标节点id,只可写一个> --slots <迁移槽总数量> --yes --timeout <migrate操作超时时间ms,默认6w> --pipeline <单次迁移key的数量,默认10>
  • 收缩集群:步骤类似扩容集群,但需先完成迁移工作后再下线,使用redis-trib.rb del-node ip:port nodeId完成忘记节点的操作,不建议于生产环境使用cluster forget进行忘记节点的操作。
e. 请求路由
  • 概述:为追求性能,客户端连接集群未采用代理方式,是直连节点的。
  • 请求重定向:redis集群收到任何key相关命令,首先计算key对应的slot,根据slot找到具体的节点。若不是本节点,回复MOVE重定向错误,通知客户端找到正确的节点。
  • smart客户端:该客户端维护slot --> node映射关系,本地即可实现key到节点的查找,MOVE协助smart更新映射关系。
  • ASK重定向:redis集群在线迁移slots和数据完成水平扩容,迁移过程中会存在一部分数据在源节点,一部分在目标节点。
    • 数据仍在源节点:基于slots缓存的映射关系,用户请求的key对象能被正常返回。
    • 数据已迁移至目标节点:用户请求的key根据计算会找到源节点,但实际key已经迁移到目标节点,返回ASK重定向异常。
f. 故障发现 & 恢复
  • 故障发现 & 恢复
    • 主观下线:类似哨兵模式sentinel下的概念,主观是仅单节点认定某节点下线,认为其超过timeout阈值未响应。
    • 客观下线
      • 概述:多节点共同判定某节点下线,一般是半数以上有槽节点投票结果。
      • 流程:通知故障节点下线并生效,让故障节点的从节点进行故障转移操作——升级成master。
    • 故障恢复
      • 资格确认:查看slave和故障master的最后断开时间,判断其是否有资格升级成master。
      • 选举:具备资格后,更新触发故障选举时间,待时间达到后开始选举,触发节点的配置纪元更新(该数值只增不减)。
g. 集群运维事项
  • 完整性:若slots未全部分配完毕,则此时集群不可用。即便仅一个slot未分配,也会导致集群不可用的状态。
  • 集群规模:基于带宽消耗考虑,带宽消耗主要位于gossip协议维系集群时和读写命令。官方建议集群规模在1000以内,避免耽搁集群体量过大。
  • 集群倾斜
    • 数据倾斜:节点和slots间分配不均、不同slots包含的key数量差异过大、集合对象中包含元素过多(bigkey) & 节点间内存配置不一致。
    • 请求倾斜:常出现于hotkey场景下,尽量避免hmget、hgetall这类高复杂度操作导致该类问题影响放大。一般可使用本地缓存降低hotkey调用。
  • 集群读写分离:集群一般不做读写分离,若压力过大可直接扩容主节点数量。默认情况下,集群中的slave不接受任何外来读写请求。
  • 故障转移
    • master变更:主节点需要更换时,可先手动将slave升级,然后再将原来master下线升级。
    • 强制故障转移:若master & slave同时故障、master & slave复制出现问题(slave不具备资格升级)、集群半数master故障时,需要强制故障转移。使用的命令有两个,cluster failover force & cluster failover takeover。后者尽可能避免使用,一般仅在大多数主节点故障时采用。
h. 分布式批量操作优化
  • 优化方法
    • 客户端n次get:n次网络+n次get命令。
    • 客户端1次pipeline get:1次网络+n次get命令。
    • 客户端1次mget:1次网络+1次get命令。
  • 对应场景
    • 串行命令:n个key均匀分布在多个节点,只可使用客户端n次get的方法。
    • 串行IO:基于集群中的Smart客户端,获得key对应的slot所在的节点,去往一个节点的命令一起发送,但是串行一个个发往不同节点上,实现了客户端1次pipeline get
    • 并行IO:思路同上,最后发送命令时优化成并行的模式。
    • hash_tag:利用hash_tag把多个key强制分配到一个redis节点,这样就可以使用mget实现客户端1次mget,这样最高效但是数据分发易不均匀。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容