sentinel 限流熔断基本应用

官方文档地址
https://github.com/alibaba/Sentinel/wiki/%E6%96%B0%E6%89%8B%E6%8C%87%E5%8D%97

引入 Sentinel 依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.0</version>
</dependency>

定义规则

    private static final String USER_KEY = "user";
    /**
     * 初始化限流
     */
    private static void initFlowRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule = new FlowRule();
        rule.setResource(USER_KEY);  //资源名称, 自定义
        // 降级模式 QPS或者并发数
        // QPS 每秒可以同时查询的数量
        // 并发线程数,是指同一时刻最大的并发数
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(1);  // QPS 为5
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

限流测试

public static void main(String[] args) {
        // 初始化限流
        initFlowRule();
        while (true){
            // 1.5.0 版本开始可以直接利用 try-with-resources 特性,自动 exit entry
            try (Entry entry = SphU.entry(USER_KEY)){
                // 被保护的逻辑
                System.out.println("hello world");
            } catch (BlockException e) {
                // 处理被流控的逻辑
                System.out.println("Handle rejected request.");
            }
        }
    }

Spring Cloud Alibaba Sentinel

引入依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
服务级限流简单使用
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT)
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
    返回值类型必须与原函数返回值类型一致;
    方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
    返回值类型必须与原函数返回值类型一致;
    方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
@Service
public class TestService {

    /**
     * value : 表示资源名称,自定义
     * @param name
     * @return
     */
    @SentinelResource(value = "sayHello")
    public String sayHello(String name) {
        return "Hello, " + name;
    }

    /**
     * 初始化限流
     */
    @PostConstruct
    private static void initFlowRule() {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule = new FlowRule();
        rule.setResource("sayHello");  //资源名称, 自定义
        // 降级模式 QPS或者并发数
        // QPS 每秒可以同时查询的数量
        // 并发线程数,是指同一时刻最大的并发数
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(1);  // QPS 为5
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}
@RestController
public class TestController {

    @Autowired
    private TestService service;

    @GetMapping(value = "/hello/{name}")
    public String apiHello(@PathVariable String name) {
        return service.sayHello(name);
    }
}

测试


image.png

刷新快一点就会触发熔断


image.png

触发降级函数

    /**
     * value : 表示资源名称,自定义
     * @param name
     * @return
     */
    @SentinelResource(value = "sayHello",blockHandler = "blockHandler")
    public String sayHello(String name) {
        return "Hello, " + name;
    }

    /**
     * blockHandler / blockHandlerClass:
     * blockHandler 对应处理 BlockException 的函数名称,可选项。
     * blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,
     * 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。
     * blockHandler 函数默认需要和原方法在同一个类中。
     * 若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,
     * 注意对应的函数必需为 static 函数,否则无法解析。
     * @param name
     * @param e
     * @return
     */
    public String blockHandler(String name, BlockException e){
        return "服务被限流了";
    }

测试


image.png

image.png

触发熔断

    /**
     * value : 表示资源名称,自定义
     * @param name
     * @return
     */
    @SentinelResource(value = "sayHello",blockHandler = "blockHandler",fallback = "fallback")
    public String sayHello(String name) {
        return "Hello, " + name;
    }

    /**
     * blockHandler / blockHandlerClass:
     * blockHandler 对应处理 BlockException 的函数名称,可选项。
     * blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,
     * 参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。
     * blockHandler 函数默认需要和原方法在同一个类中。
     * 若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,
     * 注意对应的函数必需为 static 函数,否则无法解析。
     * @param name
     * @param e
     * @return
     */
    public String blockHandler(String name, BlockException e){
        return "服务被限流了";
    }

    /**
     * fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。
     * fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。
     * fallback 函数签名和位置要求:
     *      1. 返回值类型必须与原函数返回值类型一致;
     *      2. 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
     *      3. fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,
     *         注意对应的函数必需为 static 函数,否则无法解析。
     * @param name
     * @return
     */
    public String fallback(String name){
        return "服务被降级了";
    }
注意: 如果 blockHandler 和 fallback 同时配置,只会触发 blockHandler

sentinel SPI 扩展

  • 上面我们使用的是 @PostConstruct 加载规则,下面我们是用 SPI 初始化规则
  • 实现 InitFunc 接口
public class FlowRuleInitFunc implements InitFunc {
    @Override
    public void init() throws Exception {
        List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule = new FlowRule();
        rule.setResource("sentinelSpi");  //资源名称, 自定义
        // 降级模式 QPS或者并发数
        // QPS 每秒可以同时查询的数量
        // 并发线程数,是指同一时刻最大的并发数
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(3);  // QPS 为5
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}
  • 在 resource 目录下创建目录 META-INF,在 META-INF 下载创建 services 目录,在 services 目录下创建问文件,文件名为 InitFunc 接口全路径,文件内容为实现 InitFunc 接口的 FlowRuleInitFunc 类的全路径,这样 Springboot 启动的时候就会去自动加载


    image.png
  • service 中加一个方法
    @SentinelResource(value = "sentinelSpi")
    public String sentinelSpi(){
        return "sentitnel spi ......";
    }
  • controller 中加一个方法
    @GetMapping(value = "/initSpi")
    public String sentinel(){
        return service.sentinelSpi();
    }
  • 测试


    image.png

QPS (TPS)

基于QPS/并发数的流量控制

  • 我们可以通过下面的命令查看实时统计信息:
curl http://localhost:8719/cnode?id=resourceName
  • 输出内容格式如下:
idx id     thread  pass  blocked   success  total Rt   1m-pass   1m-block   1m-all   exception
2   abc647    0     46      0         46      46   1     2763       0         2763     0
  • 其中
    thread: 代表当前处理该资源的并发数;
    pass: 代表一秒内到来到的请求;
    blocked: 代表一秒内被流量控制的请求数量;
    success: 代表一秒内成功处理完的请求;
    total: 代表到一秒内到来的请求以及被阻止的请求总和;
    RT: 代表一秒内该资源的平均响应时间;
    1m-pass: 则是一分钟内到来的请求;
    1m-block: 则是一分钟内被阻止的请求;
    1m-all: 则是一分钟内到来的请求和被阻止的请求的总和;
    exception: 则是一秒内业务本身异常的总和。

并发线程数控制

并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。

QPS流量控制

当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:直接拒绝、Warm Up、匀速排队。对应 FlowRule 中的 controlBehavior 字段。

直接拒绝

直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时

Warm Up
image.png

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮

  • warmupFlowRule() 通过设置下列的参数,构建了一个流控规则:
FlowRule rule = new FlowRule();
rule.setResource(resourceName);
rule.setCount(20);
rule.setGrade(RuleConstant.GRADE_QPS);
rule.setLimitApp("default");
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
rule.setWarmUpPeriodSec(10);
  • 其中,CONTROL_BEHAVIOR_WARM_UP 表示启用冷启动模式,warmUpPeriodSec 代表期待系统进入稳定状态的时间(即预热时长)。coldSystem() 则是通过调节 qpsInColdPeriod 的大小,让系统长时间的处于冷的状态。simulateTraffic() 则通过调节 threadCount 的大小,来模拟大流量的情况
  • 观察输出
Large amount of traffic is coming
88 send qps is: 2061
1528883307808,total:2061, pass:9, block:2053
87 send qps is: 3699
1528883308808,total:3699, pass:7, block:3692
86 send qps is: 3898
1528883309808,total:3898, pass:7, block:3893
85 send qps is: 3713
1528883310808,total:3713, pass:7, block:3708
84 send qps is: 3756
1528883311808,total:3756, pass:8, block:3749
83 send qps is: 3750
1528883312808,total:3750, pass:9, block:3741
82 send qps is: 3492
1528883313806,total:3492, pass:10, block:3482
81 send qps is: 3923
1528883314808,total:3923, pass:11, block:3913
80 send qps is: 3176
1528883315820,total:3176, pass:13, block:3163
79 send qps is: 3729
1528883316821,total:3729, pass:22, block:3708
78 send qps is: 3534
1528883317820,total:3534, pass:20, block:3514

可以看到第 10 秒的时候,系统开始稳定的接受 20 个请求

匀速排队
image.png

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法

  • paceFlowRule() 这个函数里面,通过设置下面几个参数让匀速器生效:
rule.setGrade(RuleConstant.GRADE_QPS);
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER); // 流控效果:匀速排队模式
rule.setCount(10);
rule.setMaxQueueingTimeMs(20 * 1000); // 最长排队等待时间:20s

注意,当匀速器生效的时候,规则的限流类型一定是 RuleConstant.GRADE_QPS,否则该规则将不生效。当 count 设为 10 的时候,则代表一秒匀速的通过 10 个请求,也就是每个请求平均间隔恒定为 1000 / 10 = 100 ms,每一个请求的最长等待时间(maxQueueingTimeMs)为 20 * 1000ms = 20s。

  • simulatePulseFlow() 这个函数,模拟了几乎同时到来的 100 个请求。
  • 我们来看一下运行结果
下面的输出以逗号分隔,第一栏位通过的时间,第二栏为等待的时间
....
1528872403887 pass, cost 9348
1528872403986 pass, cost 9469
1528872404087 pass, cost 9570
1528872404187 pass, cost 9642
1528872404287 pass, cost 9770
1528872404387 pass, cost 9848
1528872404487 pass, cost 9970
done
pass:100 block:0

我们可以看到,这 100 个请求,都以匀速 100 ms 的速度依次通过,并且没有阻塞

  • 和默认的行为对比
    我们通过 defaultFlowRule() 里,将该规则的流控效果设为 CONTROL_BEHAVIOR_DEFAULT(默认效果,快速失败),再发起同样的流量。这个时候检查结果,将会发现同样的流量,默认行为仅会通过 10 个

基于调用关系的流量控制

调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系。Sentinel 通过 NodeSelectorSlot 建立不同资源间的调用的关系,并且通过 ClusterBuilderSlot 记录每个资源的实时统计信息。

有了调用链路的统计信息,我们可以衍生出多种流量控制手段。

根据调用方限流

  • ContextUtil.enter(resourceName, origin) 方法中的 origin 参数标明了调用方身份。这些信息会在 ClusterBuilderSlot 中被统计。可通过以下命令来展示不同的调用方对同一个资源的调用数据:
curl http://localhost:8719/origin?id=nodeA
  • 调用数据示例:
id: nodeA
idx origin  threadNum passedQps blockedQps totalQps aRt   1m-passed 1m-blocked 1m-total 
1   caller1 0         0         0          0        0     0         0          0
2   caller2 0         0         0          0        0     0         0          0
  • 上面这个命令展示了资源名为 nodeA 的资源被两个不同的调用方调用的统计。

流控规则中的 limitApp 字段用于根据调用来源进行流量控制。该字段的值有以下三种选项,分别对应不同的场景:

  1. default:表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则触发限流。
  2. {some_origin_name}:表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如 NodeA 配置了一条针对调用者caller1的规则,那么当且仅当来自 caller1 对 NodeA 的请求才会触发流量控制。
  3. other:表示针对除 {some_origin_name} 以外的其余调用方的流量进行流量控制。例如,资源NodeA配置了一条针对调用者 caller1 的限流规则,同时又配置了一条调用者为 other 的规则,那么任意来自非 caller1 对 NodeA 的调用,都不能超过 other 这条规则定义的阈值。
    同一个资源名可以配置多条规则,规则的生效顺序为:{some_origin_name} > other > default

根据调用链路入口限流:链路限流

NodeSelectorSlot 中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:

                  machine-root
                    /       \
                   /         \
             Entrance1     Entrance2
                /             \
               /               \
      DefaultNode(nodeA)   DefaultNode(nodeA)
  • 上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 strategy 为 RuleConstant.STRATEGY_CHAIN,同时设置 refResource 为 Entrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经 Entrance2 到来的调用。
  • 调用链的入口(上下文)是通过 API 方法 ContextUtil.enter(contextName) 定义的,其中 contextName 即对应调用链路入口名称

具有关系的资源流量控制:关联流量控制

当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。

Sentinel 控制台

jar 包下载,最下面找到 sentinel-dashboard-1.8.0.jar

https://github.com/alibaba/Sentinel/releases/tag/v1.8.0

上传至服务器,运行
-Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080
sentinel.log 2>&1 & 用于执行 log 文件

nohup java \
-Dserver.port=8080 \
-Dcsp.sentinel.dashboard.server=localhost:8080 \ 
-Dproject.name=sentinel-dashboard \
-jar \
sentinel-dashboard-1.8.0.jar >> sentinel.log 2>&1 &
  • 启动完成,浏览器输入 http://ip:prot,默认用户名 sentinel 密码 sentinel
    image.png

SpringCloud 集成 Sentinel 控制台

application.yml

spring:
  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: localhost:8080

这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了一个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中

  • 狂刷几次请求, Sentinel 控制面板每十秒会更新一次


    image.png

动态规则,从远程服务器加载规则

非 springCloud 工程集成
  • 添加依赖
        <!--  使用 Nacos 配置规则 -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <version>1.8.0</version>
        </dependency>
  • 配置规则,创建 NacosDataSource 并将其注册至对应的 RuleManager 上即可
public class FlowRuleInitFunc implements InitFunc {

    private final String remoteAddress = "192.168.245.131:8848";
    private final String groupId = "SENTINEL_GROUP";
    private final String dataId = "springboot-sentinel";

    @Override
    public void init() throws Exception {
        /*List<FlowRule> rules = new ArrayList<FlowRule>();
        FlowRule rule = new FlowRule();
        rule.setResource("sentinelSpi");  //资源名称, 自定义
        // 降级模式 QPS或者并发数
        // QPS 每秒可以同时查询的数量
        // 并发线程数,是指同一时刻最大的并发数
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(3);  // QPS 为5
        rules.add(rule);
        FlowRuleManager.loadRules(rules);*/


        // remoteAddress 代表 Nacos 服务端的地址
        // groupId 和 dataId 对应 Nacos 中相应配置
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource <List<FlowRule>>(remoteAddress, groupId, dataId,
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
    }
}
  • 添加 nacos 对应配置


    image.png
  • 测试


    image.png
springCloud 工程,不需要上面的配置,springCloud 已经集成好了
  • 添加对应配置
spring:
  cloud:
    sentinel:
      transport:
        port: 8719
        dashboard: 192.168.245.131:8080
      eager: true # 配置 true 时,取消 Sentinel 控制台懒加载功能
      datasource:
        ds1:
          nacos:
            serverAddr: 192.168.245.131:8848
            dataId: springboot-sentinel
            groupId: SENTINEL-GROUP
            dataType: json
            ruleType: flow
            username: nacos
            password: nacos
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,963评论 6 542
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,348评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,083评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,706评论 1 317
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,442评论 6 412
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,802评论 1 328
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,795评论 3 446
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,983评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,542评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,287评论 3 358
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,486评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,030评论 5 363
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,710评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,116评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,412评论 1 294
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,224评论 3 398
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,462评论 2 378