九、redis集群


title: redis集群
date: 2021-01-14 20:12:14
categories: redis
tags:
- redis cluster
description: 集群模式是redis的分布式解决方案


redis集群数据分区

一个集群必须要解决数据分布的问题,常用的方法有哈希分区、顺序分区。哈希分区的特点是离散度好数据,而顺序分区则可以提供顺序访问。

redis采用虚拟槽分区的方式来将数据分区,一共16384个槽,所有的键根据哈希函数slog=CRC16(key)&16383,映射到0~16383整数槽内。
每一个节点负责维护一部分槽,以及槽所对应的键值数据

Redis虚拟槽分区的特点:

  • 解耦数据与节点之间的关系
  • 节点自身维护槽的映射关系,不需要客户端或者代理服务维护槽分区元数据
  • 支持节点、槽、键之间的映射查询,用于数据路由、在线伸缩等场景

redis集群功能缺陷:

  1. key批量操作支持有限,只能支持具有相同slot值的key执行批量操作
  2. 同理事物也同样只支持同一节点上的事物操作
  3. key作为数据分区的最小粒度,因此不能将一个大的键值对象如hash、list等拆分到不同的节点分布
  4. 不支持多数据库,只有一个db0
  5. 复制结构只支持一层、从节点只能复制主节点,不支持树状分布

集群搭建

  1. 准备好6个节点:

    redis-6379.conf
    redis-6380.conf
    redis-6381.conf
    redis-6382.conf
    redis-6383.conf
    redis-6384.conf
    $> cat redis-6379.conf
    port 6379
    cluster-enabled yes
    cluster-node-timeout 15000
    # 如果没有配置,则会在启动后生成一份
    cluster-config-file "nodes-6379.conf"
    logfile "/redis/logs/redis-6379.log"
    dbfilename "dump-6379.rdb"
    
  2. 启动集群
    redis-server conf/redis-6379.conf
    目前每个节点都只能识别自己的节点新,因为现在彼此不知道对方的存在

    127.0.0.1:6379> CLUSTER NODES
    6e39c9aba1bb22c51e1fb6d37aeda4174a782eac :6379@16379 myself,master - 0 0 0 connected
    

手动建立集群

  1. 节点握手
    节点握手是让当前节点感知到另一个节点,由客户端发起命令,cluster meet {ip} {port}


    节点握手

节点握手后集群还不能正常工作,因为还没有分配槽,这个时候写命令会发现失败:

```sh
127.0.0.1:6379> set hello redis
(error) CLUSTERDOWN Hash slot not served
# 查看集群信息,发现state是fail状态,分配的槽slot为0
127.0.0.1:6379> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:0
cluster_current_epoch:5
cluster_my_epoch:2
cluster_stats_messages_ping_sent:490
cluster_stats_messages_pong_sent:210
cluster_stats_messages_meet_sent:7
cluster_stats_messages_sent:707
cluster_stats_messages_ping_received:210
cluster_stats_messages_pong_received:198
cluster_stats_messages_received:408

```
  1. 分配槽
    之前说过,redis集群把所有的数据映射到16384个槽中,而这些槽如何分配,是要通过配置来决定。只有这些槽都被分配好了
    redis集群才能响应命令,通过cluster addslots命令为节点分槽。(正常做法应该使用create命令)

    ./redis-cli 9 -p 6381 cluster addslots {0..5461}
    ./redis-cli 9 -p 6381 cluster addslots {5462..10922}
    ./redis-cli 9 -p 6381 cluster addslots {10923..16383}
    

此时集群已经可用了,为了集群的高可用,我们再为每一个节点分配一个从节点。

  1. 配置集群从节点 cluster replicate {id}
    CLUSTER REPLICATE 6e39c9aba1bb22c51e1fb6d37aeda4174a782eac注意后面跟的是clusterid
    以上步骤依照Redis协议手动建立了一个集群,便于理解集群建立的过程,但比较繁琐且容易出错,因此,官方自带工具方便我们快速搭建集群,之前我们使用
    redis-trib.rb来创建,在Redis5.0中创建集群已经使用“redis-cli”来实现,所以redis-trib.rb的方式已经被抛弃

利用cluster create快速搭建集群

和上面的步骤一样先配置六个不同的配置文件,再启动6个进程,不过节点握手和分配槽这个步骤不一样,我们是有redis-cli来简化操作

redis-cli --cluster subcommand [args] [opt]
./redis-cli --cluster create --cluster-replicas 1 127.0.0.1:6380 127.0.0.1:6379 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384

最后输出结果

Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:6383 to 127.0.0.1:6380
Adding replica 127.0.0.1:6384 to 127.0.0.1:6379
Adding replica 127.0.0.1:6382 to 127.0.0.1:6381
# 优化
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 6e2da8e45d804b47fe599d121682e6a394c527ef 127.0.0.1:6380
   slots:[0-5460] (5461 slots) master
M: 4aa937577181f82ce88b1c909a519ca03963ab21 127.0.0.1:6379
   slots:[5461-10922] (5462 slots) master
M: 07b085e9e9750e5d0c319f3c099d70acda84f6fb 127.0.0.1:6381
   slots:[10923-16383] (5461 slots) master
S: 0c1c1ce2bf3c890396da31a1b536adcd32e2766a 127.0.0.1:6382
   replicates 07b085e9e9750e5d0c319f3c099d70acda84f6fb
S: 36b71d82eaccae49f0aff395fa3cae03c1adcc0f 127.0.0.1:6383
   replicates 6e2da8e45d804b47fe599d121682e6a394c527ef
S: 4885dcbd061e28d969eb8d2ae893f256c9c79401 127.0.0.1:6384
   replicates 4aa937577181f82ce88b1c909a519ca03963ab21
# 确认部署
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
# 其他校验信息
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered

可以看到:

  1. redis-cli命令会对节点分配进行优化,比如主从使用不同的ip等
  2. 部署方案需要人工再确认一遍
  3. 会确保所有槽都被分配成功

集群内通信

redis集群采用p2p的Gossip协议,类似留言,通过彼此不断地通信交换信息,最后所有节点直到集群完整的信息。


集群通信
  1. 集群中每个节点都会单独开辟一个TCP通道,用于节点之间彼此通信,通信端口号是在基础上加上10000.
  2. 每个节点在固定周期内通过特定规则选择几个节点发送ping消息
  3. 接收到ping消息的节点用pong消息作为响应

特定规则是什么?

由于内部需要频繁的进行节点信息交换,ping/pong消息会携带当前节点和部分其他节点的状态数据,Redis集群内
节点每秒执行10次,因此节点每次选择通信的节点列表非常重要。太多会影响带宽,太少又不能做到及时交换信息。因此redis采用过程如下:
[图片上传失败...(image-eae61e-1611843829725)]

消息的数据量

  • 消息头:固定占用myslots[CLUSTER_SLOTS/8],2kb
  • 消息体:默认是1/10个其他节点的信息,最少3个,醉倒total-2个,所以消息的大小跟节点数有关。

集群伸缩

集群伸缩就是利用槽在节点间的移动,也就是数据在各个节点的移动。

扩容

  1. 准备节点

    > cp redis-6384.conf redis-6385.conf
    > sed -i 's/6384/6385/' redis-6385.conf
    > ./redis/src/redis-server config/redis-6385.conf
    
  2. 加入集群

    > ./redis/src/redis-cli --cluster add-node localhost:6385 localhost:6384
    >>> Adding node 127.0.0.1:6385 to cluster 127.0.0.1:6384
    >>> Performing Cluster Check (using node 127.0.0.1:6384)
    S: 4885dcbd061e28d969eb8d2ae893f256c9c79401 127.0.0.1:6384
       slots: (0 slots) slave
       replicates 4aa937577181f82ce88b1c909a519ca03963ab21
    M: ce5e60a517c8714d52b82d9e24b71d2f5a1868c4 127.0.0.1:6385
       slots: (0 slots) master
    S: 0c1c1ce2bf3c890396da31a1b536adcd32e2766a 127.0.0.1:6382
       slots: (0 slots) slave
       replicates 07b085e9e9750e5d0c319f3c099d70acda84f6fb
    M: 07b085e9e9750e5d0c319f3c099d70acda84f6fb 127.0.0.1:6381
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
    S: 36b71d82eaccae49f0aff395fa3cae03c1adcc0f 127.0.0.1:6383
       slots: (0 slots) slave
       replicates 6e2da8e45d804b47fe599d121682e6a394c527ef
    M: 6e2da8e45d804b47fe599d121682e6a394c527ef 127.0.0.1:6380
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    M: 4aa937577181f82ce88b1c909a519ca03963ab21 127.0.0.1:6379
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    # meet 节点
    >>> Send CLUSTER MEET to node 127.0.0.1:6385 to make it join the cluster.
    [OK] New node added correctly
    
    # 检查node情况,发现已经加入
    > cluster nodes
    4aa937577181f82ce88b1c909a519ca03963ab21 127.0.0.1:6379@16379 myself,master - 0 1611108761000 2 connected 5461-10922
    36b71d82eaccae49f0aff395fa3cae03c1adcc0f 127.0.0.1:6383@16383 slave 6e2da8e45d804b47fe599d121682e6a394c527ef 0 1611108764049 5 connected
    07b085e9e9750e5d0c319f3c099d70acda84f6fb 127.0.0.1:6381@16381 master - 0 1611108763000 3 connected 10923-16383
    ce5e60a517c8714d52b82d9e24b71d2f5a1868c4 127.0.0.1:6385@16385 master - 0 1611108763046 0 connected
    0c1c1ce2bf3c890396da31a1b536adcd32e2766a 127.0.0.1:6382@16382 slave 07b085e9e9750e5d0c319f3c099d70acda84f6fb 0 1611108762000 4 connected
    6e2da8e45d804b47fe599d121682e6a394c527ef 127.0.0.1:6380@16380 master - 0 1611108765050 1 connected 0-5460
    4885dcbd061e28d969eb8d2ae893f256c9c79401 127.0.0.1:6384@16384 slave 4aa937577181f82ce88b1c909a519ca03963ab21 0 1611108763000 6 connected
    
  3. 迁移槽
    继续使用redis-cli cluster 命令,使用reshard参数迁移槽

    > ./redis/src/redis-cli --cluster reshard 127.0.0.1:6379
    
    # 进行一系列校验后提示输入要迁移的槽的个数,输入4096
    How many slots do you want to move (from 1 to 16384) 4069
    
    # 接着会提示输入目标节点,也就是新加入的节点id
    what is the receiveing node ID ? ce5e60a517c8714d52b82d9e24b71d2f5a1868c4
    
    # 然后会提示输入源节点的ID,也就是说可以只转移部分节点的槽  
    Please enter all the source node IDs.
    Type 'all' to use all the nodes as source nodes for the hash slots.
    Type 'done' once you entered all the source nodes IDs.
    Source node #1: 24b9eea3f3797a68e33100dccfdbfc57693f7a4b
    Source node #2: 07b085e9e9750e5d0c319f3c099d70acda84f6fb
    Source node #3: 6e2da8e45d804b47fe599d121682e6a394c527ef
    # redis会打印每个槽的迁移计划,会再确认一次,确认后会打印迁移过程,最后自动退出
    

    从redis的输出就可以看出,redis的迁移过程是一个一个槽迁移的,槽迁移过程如下:

    槽迁移过程

  4. 检查槽的均衡性

    > ./redis/src/redis-cli --cluster rebalance 127.0.0.1:6379
    >>> Performing Cluster Check (using node 127.0.0.1:6379)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    *** No rebalancing needed! All nodes are within the 2.00% threshold.
    

    说明迁移之后主节点负责的槽数量差异在2%以内,相对均匀,无需调整

  5. 添加从节点
    准备一个6386节点,步骤与前面一样,然后在该节点上执行 cluster replicate {masterId},注意集群模式下没有salveof命令

集群收缩

原理和方法和扩容是一样的,都是重新分片reshard,只不过之前是把已有节点的数据迁移一部分到新节点。而收缩则是把要下线节点的数据迁移到其他节点。
执行方法与扩容一样,都是用reshard。 注意del-node是删除空节点的命令,如果有槽是不能删除的。

下线后,为了让其他节点忘记下线节点,也就是避免继续和下线节点交换信息,这个时候可以用del-node 命令。

请求路由

在集群模式下,Redis接受任何键相关的命令都要先计算键对应的槽(每个集群节点在交换信息时都会发送槽信息,因此每个节点都知道所有槽和节点的对应关系),再根据槽找出相应的节点,如果节点是自身,则处理命令,如果不是则恢复MOVED重定向错误。通知客户端请求正确的节点

127.0.0.1:6386> set key:test:1 value-1
(error) MOVED 5191 127.0.0.1:6380

使用redis-cli工具时,可以加入-c参数支持自动重定向,也就是不用手动切换节点。原理是redis-cli客户端再收到MOVED信息时,再次发起请求。
redis节点对于不属于它的键命令,只会回复重定向响应,并不负责转发。重定向的过程由客户端实现

./redis/src/redis-cli -p 6386 -c
127.0.0.1:6386> set key:test:1 value-1
-> Redirected to slot [5191] located at 127.0.0.1:6380
OK

如果键内容包含{}大括号字符,则计算槽的有效部分是括号内的内容,其他内容不会被计算,如果不包含则所有内容都会加入计算,利用这一点我们可以优化批量数据,比如哈希类型的,我们如果使用hget的时候,如果键列表不再同一个槽中会报错,这个时候可以用hash_tag来使该哈希表中所有的键都在同一个槽中。

100.101.71.99:6380> CLUSTER KEYSLOT key:test:111
(integer) 10050
100.101.71.99:6380> CLUSTER KEYSLOT key:{test}:111
(integer) 6918
100.101.71.99:6380> CLUSTER KEYSLOT key:{test}:112
(integer) 6918
100.101.71.99:6380> CLUSTER KEYSLOT key:{test}:22
(integer) 6918
  • ASK重定向
    与MOVED重定向不一样,ASK重定向,是发生在槽迁移过程中,一部分迁移完成,一部分没有迁移成功,客户端根据本地slots缓存发送命令到源节点,但其实已经发送到了目标节点,因此会回复(error)ASK {slot}{targetIP}:{targetIP}

Smart客户端

显然每次通过重定向来完成操作,会造成很大的IO浪费。smart客户端则通过内部维护槽和节点的关系,本地就可以实现键和节点的查找。而MOVED重定向则只是协助客户端维护槽和节点之间的关系。

  • jedis客户端
Jedis客户端命令执行过程

故障转移

与哨兵模式一样,Redis集群模式也要解决分布式通用的部分失败的问题。

  1. 故障发现
    Redis集群内通过ping/pong消息实现节点通信,消息不止携带节点槽信息,还携带有其他状态,比如主从节点状态、节点故障灯。当集群内某个节点出现问题时,就会通过消息传播
    与哨兵模式类似,存在主观下线和客观下线两种状态

    • 主观下线:每个节点都会定期向其他节点发送ping消息,如果节点在cluster-node-timetout时间内没有收到pong消息,则该发送节点,就会认为对端存在故障。把接受节点标记为主观下线状态(pfail)。

      typedef struct clusterState {
      clusterNode *myself; /* 自身节点 /
      dict *nodes;/* 当前集群内所有节点的字典集合,key为节点ID,value为对应节点ClusterNode结构 */
      ...
      } clusterState;字典nodes属性中的clusterNode结构保存了节点的状态,关键属性如下:
      typedef struct clusterNode {
      int flags; /* 当前节点状态,如:主从角色,是否下线等 */
      mstime_t ping_sent; /* 最后一次与该节点发送ping消息的时间 */
      mstime_t pong_received; /* 最后一次接收到该节点pong消息的时间 */
      ...
      } clusterNode;
      
    • 客观下线
      当某个节点判断另一个节点主观下线后,相应的节点状态会跟随消息在集群内传播。当半数以上持有槽的主节点都标记某个节点是主观下线时,触发客观下线流程

故障恢复

当故障节点变为客观下线后,如果下线节点是持有槽的主节点,则需要选择一个从节点来替换它,从而保证高可用。下线主节点的所有从节点承担故障恢复的义务。当从节点通过内部
定时任务发现主节点客观下线时,就会触发故障恢复流程:

  1. 资格检查
    判断当前节点是否有资格替换主节点,判断依据是,从节点与主节点断线时间是否超过cluster-node-time*cluster-slave-validity-factor。如果超过则不具备3

  2. 准备选举时间
    当从节点具备故障转移资格后,更新触发故障选举的时间,只有到达该时间后才能执行后续流程。这里之所以要采用延时触发机制,主要是多个从节点有不同的优先级,也就是根据他们的优先级来触发故障选举,优先级更高的更容易成为主节点,而优先级的判断就是通过offset(复制的偏移)。

  3. 发起选举
    更新配置的epoch,每个主节点自身维护一个epoch表示当前主节点的版本,所有主节点的epoch都不相等,从节点会复制主节点的epoch。整个集群有一个最大的epoch,也就是集群epoch。epoch会随着消息的传递传播下去,
    如果epoch相等,那么会根据nodeid大小来更新,nodeid更大的会使当前集群epoch递增,并且赋值给自己的epoch。每当新节点加入、槽节点映射冲突、从节点投票选举冲突时都会更新epoch。
    更新后会在集群内广播选举消息(FAILOVER_AUTH_REQUEST),并记录已发送过消息的状态。保证该节点在一个epoch只能发起一次选举。

  4. 选举投票
    只有持有槽的主节点才会处理故障选举消息,也就是上面说的REQUET,在选举过程中每个节点在同一个epoch中只有一张选票,当收到第一个从节点消息是,就会回复ACK消息投出自己的选票,之后不会再响应选举请求。
    最终收到投票数大于一般的从节点会成为主节点。

  5. 替换主节点
    当从节点收到足够多的选票后,触发替换主节点操作:

    1. 取消复制,成为主节点
    2. 执行clusterDelSlot操作,撤销故障节点负责的槽,并执行clusterAddSlot把这些槽委派给自己
    3. 向集群广播自己的pong消息。通知其他节点更新信息。
  6. 故障转移时间

    • 主观下线识别时间=cluster-node-timeout
    • 传播时间<=cluster-node-timeout/2
    • 从节点转移时间<=1000毫秒
  7. 模拟故障转移:

    # 主节点日志
    128026:M 28 Jan 2021 19:56:59.247 * Clear FAIL state for node 4aa937577181f82ce88b1c909a519ca03963ab21: is reachable again and nobody is serving its slots after some time.
    128026:M 28 Jan 2021 19:56:59.247 # Cluster state changed: ok
    128026:M 28 Jan 2021 19:58:38.299 * Marking node 4aa937577181f82ce88b1c909a519ca03963ab21 as failing (quorum reached).
    128026:M 28 Jan 2021 19:58:38.299 # Cluster state changed: fail
    128026:M 28 Jan 2021 19:58:38.809 # Failover auth granted to 4885dcbd061e28d969eb8d2ae893f256c9c79401 for epoch 9
    128026:M 28 Jan 2021 19:58:38.848 # Cluster state changed: ok
    128026:M 28 Jan 2021 19:58:45.127 * Clear FAIL state for node 4aa937577181f82ce88b1c909a519ca03963ab21: master without slots is reachable again.
    
    # 从节点日志
    128093:S 28 Jan 2021 19:58:38.805 # Starting a failover election for epoch 9.
    128093:S 28 Jan 2021 19:58:38.812 # Failover election won: I'm the new master.
    128093:S 28 Jan 2021 19:58:38.812 # configEpoch set to 9 after successful failover
    128093:M 28 Jan 2021 19:58:38.812 # Setting secondary replication ID to 2609636bd51ab7c2e436ee5653a8bf8a7445eefa, valid up to offset: 113. New replication ID is 3231e43113314a13c4878d95263a718da8fb305d
    128093:M 28 Jan 2021 19:58:38.812 * Discarding previously cached master state.
    128093:M 28 Jan 2021 19:58:38.812 # Cluster state changed: ok
    128093:M 28 Jan 2021 19:58:45.128 * Clear FAIL state for node 4aa937577181f82ce88b1c909a519ca03963ab21: master without slots is reachable again.
    

集群运维

  1. 集群完整性
    默认情况下当集群16384个槽任何一个没有指派到节点上,则执行任何明林过度会报错。这是对集群的一种保护,但当主节点下线时,从故障发现到完成转移这个过程,整个集群时不可用状态的,很多业务场景无法容忍这种情况
    因此可以将参数cluster-require-full-coverage配置为no,当主节点故障时,只影响它负责槽的相关命令执行,不会影响其他节点。

  2. 带宽消耗
    官方建议规模在1000以内,不然会造成大量网络带宽,2适当提交cluster-node-timeout降低消息发送频率、3尽量均匀的部署节点。

  3. Pub/Sub广播问题
    尽量使用sentinel模式使用广播,集群模式会造成消息在所有节点广播一遍,加重带宽负担。

  4. 集群倾斜

    • 节点和槽分配不均,可以使用rebalance命令,进行平衡
    • 不同槽对应键数量差异过大,一般使用CRC16哈希算法会相对均匀,但如果大量使用hash_tag时,就会产生多个键映射到同一个槽的情况。
    • 集合对象包含大量元素,可以通过redis-cli bigkeys命令识别
    • 内存配置不一致
  5. 手动故障转移
    Redis提供了手动故障转移功能,比如需要切换设备,重新调整部署方案的时候,可能会用到(直接kill会导致一定时间的不可用)。使用cluster failover命令

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

推荐阅读更多精彩内容