Sentinel

一、前言:和Hystrix作对比

Hystrix:

  • 1、需要我们自己手工搭建监控平台
  • 2、没有一套web界面,不可以给我们进行更加细粒度化的配置流控、速率控制、服务熔断、服务降级

Sentinel:

  • 1、单独一个组件,可以独立出来
  • 2、直接界面化的细粒度统一配置

二、Sentinel(哨兵)是什么

官网:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。主要就是服务雪崩、服务降级、服务熔断、服务限流

主要特性

三、Sentinel的下载安装运行

Sentinel分为两部分:

核心库(java客户端):不依赖任何框架/库,能够运行于所有java运行环境,同时对Dubbo/Spring Cloud等框架也有较好的支持。
控制台(Dashboard):基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

下载Sentinel的jar包,然后直接java -jar 运行即可 ,Sentinel的用户和密码默认都是Sentinel

四、Sentinel的初始化监控

1、建立一个module被Sentinel保护
2、pom

//sentinel的依赖
<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
//后面做持久化会用到
<dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
//添加openfeign的依赖,后面会用到
<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

3、yml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848

    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719+1依次扫描
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

4、主启动类

@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
    public static void main(String[] args) {
        SpringApplication.run(MainApp8401.class,args);
    }
}

5、业务类

@RestController
public class FlowLimitController {


    @GetMapping("/testA")
    public String testA() {
        return "testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "testB";
    }
}

6、测试

此时访问一下controller中指定的路径,Sentinel就将对应用进行保护了,因为Sentinel采取的是懒加载,访问localhost:8080也就可以看到应用。

界面展示

五、Sentinel的流量控制

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
资源名:唯一名称,默认请求路径

阈值类型/单机阈值:
①QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
②线程数:当调用该api的线程数达到阈值的时候进行限流

流控模式:
①直接:api达到限流条件时,直接限流
②关联:当关联的资源达到阈值时,就限流自己。
③链路:只记录指定链路上的流量,如果达到阈值,就进行限流

流控效果
①快速失败:直接失败,抛异常
②Warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
③排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须为QPS,否则无效

1、QPS直接快速失败报错

表示一秒钟内点击一次,如果超过就会直接页面报错

出错页面

2、QPS和线程作对比
如下图所示,在这里都以设置为1作比较,QPS表示的是每秒的请求数量,如果一旦一秒钟超过了1个,就相当于是有一扇门直接给挡住了,然后直接报错。线程就像是,进去了这扇门,不管有多少数据都可以进来,但是只能一个个的依次的来处理,就像是银行的业务人员,一旦两个想同时访问,不好意思,不行。
QPS和线程数对比

3、QPS关联模式快速报错(因为微服务中两个服务之间是有关联的,比如说支付接口挂了,那你下单接口也歇一歇)当与A关联的B达到阈值时,A就限流自己,B惹事A遭殃

@GetMapping("/testA")
    public String testA() {
        return "testA";
    }

    @GetMapping("/testB")
    public String testB() {
        return "testB";
    }
关联配置
用postman进行访问测试

结果访问testA报错

4、QPS warm up流控效果
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo

通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:

image

如下图所示,我们最后想要设置的值是单机阈值是10,但是不想让他一下子到10,而是想让他慢慢的增长,一开始的单机阈值设置为10(单机阈值)/3(冷加载因子)=3.3,5秒(预热时长)之后达到理想单机阈值10。
公式:默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。

这个好牛逼

5、QPS匀速排队

匀速排队

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 流量控制 - 匀速器模式,具体的例子可以参见 PaceFlowDemo

该方式的作用如下图所示:

image

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

QPS排队等待

如上配置表示不管有多少的请求放进来,一秒钟只会处理一个请求,超时时间设置为了20s,表示如果这个请求在20s之后还没有被处理就会报错。

六、Sentinel的服务降级

1、简介

Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速的失败,当资源被降级时,在接下来的降级时间窗口之内,对该资源的调用都自动熔断,而Hystrix则会有一个半开状态,这是不一样的。
RT、异常比例、异常数

image.png

2、RT

平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

RT降级规则

如上图所示,设置RT降级规则,RT设置为200毫秒,时间窗口设置为1s,也就是说,如果访问/testB资源进入的N个请求,访问响应的时间都超过200ms了,那么在这个时间窗口期1s内,就会发生服务熔断。

3、异常比例

异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

异常比例降级规则

如上图所示,如果一秒钟内有四个请求访问/testB,50%以上的请求,比如说3个请求访问发生错误,就会在1s的时间窗口内发生服务熔断。

4、异常数

异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow(时间窗口) 小于 60s,则结束熔断状态后仍可能再进入熔断状态。所以时间窗口一定要大于等于一分钟时间

异常数降级规则

如上图所示,如果在一分钟内,异常数超过10个,就会发生服务降级。注意时间窗口一定要大于60s。

七、热点参数限流

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

1、在服务类新加配置

@GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
    public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                             @RequestParam(value = "p2", required = false) String p2) {
        return "-----testHotKey";

    }

    public String deal_testHotKey(String p1, String p2, BlockedException e) {
        return "------deal_testHotKey failed";

    }

2、浏览器访问

可以访问
也可以访问
对应图

此时如果访问这个路径http://localhost:8401/testHotKey?p1=a,因为上图进行了热点限流,参数索引是0,也就是p1,单机阈值为1,也就是1s内访问超过1次就会触发热点限流,而这里就是触发我们写的兜底方法。如果没有兜底方法的话就会是一个很不友好的error界面。而且只要有p1就会进行热点限流
http://localhost:8401/testHotKey?p1=a会进行热点限流
http://localhost:8401/testHotKey?p1=a&p2=b会进行热点限流
http://localhost:8401/testHotKey?p2=b不会进行热点限流

3、参数例外项(VIP)

image.png

如上图所示,进行了参数例外项的配置,其实意思就是当索引0下标对应的值也就是p1,当他是5时,阈值QPS可以达到200在进行热点限流,而不再是1。相当于是给p1=5开了一个vip。参数类型必须是八大基本类型和String。

4、系统保护规则

image.png

系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量。

Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

八、@SentinelResource注解

@GetMapping(value = "/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handlerException")
    public String CommonResult() {
        return "success";
    }

    public String handlerException(BlockException e) {
        return "failed" + e;
    }

如上图所示,如果每一个方法都要配置一个blockHandler,那么会造成业务和代码高耦合,且造成代码冗余,所以,把处理方法提取出来放到一个类里面,并且可以在这个类里面配置多个方法。


image.png
@RestController
public class RateLimitController {

    @GetMapping(value = "/extract")
    @SentinelResource(value = "extract", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "Handler1")
    public String HandlerExtract() {
        return "我成功了";
    }
}
public class CustomerBlockHandler {

    public static String Handler1(BlockException e) {
        return "handler1由我来处理";
    }

    public static String Handler2(BlockException e) {
        return "handler2由我来处理";
    }
}
image.png

测试效果

如上图所示,这样就可以避免了代码的冗余和业务的耦合。并且能够实现全局的统一处理方法,
@SentinelResource注解其他注意:
注意不可以用private注解
注意Sentinel的三个核心API:
①:sphU定义资源
②:Tracer定义统计
③:contextUtil定义上下文

九、服务熔断功能

一、环境预说

客户端8402和服务端9003、9004

9003和9004只有端口号的差异,举例9003配置:
1、module
2、pom

 <dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

3、yml

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'

4、主启动

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain9003.class, args);
    }
}

5、业务类

@RestController
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    public static HashMap<Long, Payment> hashMap = new HashMap<>();

    static {
        hashMap.put(1L, new Payment(1L, "dfadfhasdfgjadsf"));
        hashMap.put(2L, new Payment(2L, "nnnnnnnn"));
        hashMap.put(3L, new Payment(3L, "mmmmmmm"));
    }

    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = hashMap.get(id);
        CommonResult<Payment> result = new CommonResult<>(200, "serverPort:" + serverPort, payment);
        return result;

    }
}

8402客户端环境:
1、module
2、pom

<dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

3、yml

server:
  port: 8402

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  #配置Nacos地址
    sentinel:
      transport:
      #配置Sentinel dashboard地址
      dashboard: localhost:8080
      #默认8719端口,假如被占用会自动从8719+1依次扫描
      port: 8719


#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

4、主启动

@SpringBootApplication
@EnableDiscoveryClient
public class OrderMain {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain.class, args);
    }
}

5、配置类

@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}

6、业务类

@RestController
@Slf4j
public class CircleBreakerController {

    public static final String SERVICE_URL = "http://nacos-payment-provider";
    @Resource
    private RestTemplate restTemplate;

    @GetMapping(value = "/consumer/fallback/{id}")
    public CommonResult<Payment> fallBack(@PathVariable("id") Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数异常");
        } else if (result.getData() == null) {
            throw new NullPointerException("没有对应的记录");
        }
        return result;
    }
}
9004

9003

如上所示,客户端在访问时可以实现轮询

二、然后由业务类来进行服务熔断的学习

运用fallback管理程序运行时异常,如下所示添加一个fallback方法,就不再是难看的errorpage页面,而是我们自定义的页面。

@GetMapping(value = "/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handFallback")
    public CommonResult<Payment> fallBack(@PathVariable("id") Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数异常");
        } else if (result.getData() == null) {
            throw new NullPointerException("没有对应的记录");
        }
        return result;
    }

    public CommonResult<Payment> handFallback(@PathVariable("id") Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444, "兜底异常" + e.getMessage(), payment);
    }
image.png
image.png

三、fallback只会管java产生的异常,blockHandler只会管Sentinel控制台的配置违规

如下这样,既配置blockhandler又配置fallback就可以既处理java异常又处理Sentinel配置违规,如果两个规则都违规时,则会进入blockHandler的处理

@GetMapping(value = "/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback",fallback = "handFallback")
    @SentinelResource(value = "fallback",fallback = "handFallback",blockHandler = "blockHandler")
    public CommonResult<Payment> fallBack(@PathVariable("id") Long id) {
        CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数异常");
        } else if (result.getData() == null) {
            throw new NullPointerException("没有对应的记录");
        }
        return result;
    }

    public CommonResult<Payment> handFallback(@PathVariable("id") Long id, Throwable e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(444, "兜底异常" + e.getMessage(), payment);
    }
    
    public CommonResult<Payment> blockHandler(@PathVariable("id") Long id, BlockException e) {
        Payment payment = new Payment(id, "null");
        return new CommonResult<>(445, "blockhandler异常" + e.getMessage(), payment);
    }

exceptionsToIgnore表示忽略这个异常,保证这个程序先走通,如下所示,这里是个数组,可以加多个异常VIP

四、Sentinel和OpenFeign

1、修改pom

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2、修改yml

#添加激活Sentinel对Feign支持
feign:
  sentinel:
    enabled: true

3、主启动类添加注解@EnableFeignClients

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderMain8402 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain8402.class, args);
    }
}

4、业务类
首先是一个专门调用服务端方法的接口,详细看之前OpenFeign的学习,FeignClient的value对应着服务类的实例名,fallback表示处理降级的类

@FeignClient(value = "nacos-payment-provider", fallback = PaymentServiceImpl.class)
public interface PaymentService {
    @GetMapping(value = "/paymentSQL/{id}")
    CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
@Component
public class PaymentServiceImpl implements PaymentService {
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(44444, "服务降级返回", new Payment(id, "err"));
    }
}

然后在controller就可以直接调用了,而不再需要RestTemplate了

@Resource
    private PaymentService paymentService;

    @GetMapping(value = "consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        return paymentService.paymentSQL(id);
    }

5、测试


正常调用测试

将服务端停掉

服务降级测试

十、Sentinel的持久化规则

痛点:每次重启服务器,Sentinel的配置就都没有了,导致了每次都要重新配置
解决方法:将限流规则配置到nacos上面,只要刷新8401某个rest地址,Sentinel的流控规则就能看到,只要Nacos不删除配置,就一直有效。
详细步骤:8401示例
1、pom添加依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2、修改yml(yml一定要格式正确,因为格式不正确,我的Sentinel持久化一直没生效)

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719+1依次扫描
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data_type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'

在yml中配置的dataId即对应着在如下Nacos中配置的Data ID。

3、在nacos中配置Sentinel流控规则

配置规则:
resource:资源名,即限流规则的作用对象;
limitApp:流控针对的调用来源,若为 default 则不区分调用来源;
grade:限流阈值类型: 0表示线程数,1表示QPS;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流量控制效果:0快速失败、1Warm Up、2排队等待;
clusterMode:是否集群。

配置示例

Sentinel流控规则生成

这时Sentinel中就会一直有我们配置的限流规则了,但是目前来看Sentinel的持久化配置比较复杂,应该是个半成品,期待阿里后期继续完善。

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

推荐阅读更多精彩内容