RPC框架及选型

1. RPC 概述

http://dubbo.apache.org/zh-cn/docs/user/references/protocol/dubbo.html (以dubbo为例)

1.1 什么是RPC

RPC(Remote Procedure Call Protocol)远程过程调用协议:
它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
简言之,RPC使得程序能够像访问本地系统资源一样,去访问远端系统资源。
比较关键的一些方面包括:
通讯协议、序列化、资源(接口)描述、服务框架、性能、语言支持等。

#简单的说
RPC就是从一台机器(客户端)上通过参数传递的方式调用 --> 
另一台机器(服务器)上的一个函数或方法(可以统称为服务) --> 
并得到返回的结果。

RPC 调用的分类

#从调用过程来看,可以分为同步通信RPC和异步通信RPC
>> 同步调用:客户端等待调用执行完成并获取到执行结果。
>> 异步调用:客户端调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。
若客户端不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
异步和同步的区分在于是否等待服务端执行完成并返回结果。

#从是否跨平台可分为
单语言 RPC,如 RMI, Remoting;
跨平台 RPC,如 google protobuffer, restful json,http XML。

#从通信协议层面可以分为
基于 HTTP 协议的 RPC;
基于二进制协议的 RPC;
基于 TCP 协议的 RPC。

1.2 RPC协议

在一个典型的RPC的使用场景中,
包含了服务发现、负载、容错、序列化和网络传输等组件,
其中RPC协议指明了程序如何进行序列化和网络传输,
也就是说一个RPC协议的实现等于一个非透明的RPC调用。

简单来说,分布式框架的核心是RPC框架,RPC框架的核心是RPC协议。
RPC协议在RPC调用中的位置.png

RPC协议的基本组成

>> IP:服务提供者的地址
>> 端口:协议指定开放端口
>> 运行服务
(1)netty
(2)mima
(3)rmi
(4)servlet容器(Jetty、Tomcat、Jboss)
>> 协议报文编解码
>> 序列化方式
(1)Hessian2Serialization
(2)DubboSerialization
(3)JavaSerialization
(4)JsonSerialization
RPC协议的基本组成.png

RPC消息协议

#消息边界

RPC 需要在一条 TCP 链接上进行屡次消息传递。
在连续的两条消息之间必需有明确的分割规则,以便接收端可以将消息分割开来,
这里的接收端可以是 RPC 服务器接收请求,也可以是 RPC 用户端接收响应。

基于 TCP 链接之上的单条消息假如过大,就会被网络协议栈拆分为多个数据包进行传送。
假如消息过小,网络协议栈可能会将多个消息组合成一个数据包进行发送。
对于接收端来说它看到的只是一串串的字节数组,假如没有明确的消息边界规则,
接收端是无从知道这一串字节数组到底是包含多条消息还是只是某条消息的一部分。

#比较常使用的两种分割方式是特殊分割符法和长度前缀法。
>> 特殊分割符法
消息发送端在每条消息的末尾追加一个特殊的分割符,并且保证消息中间的数据不能包含特殊分割符。
比方最为常见的分割符是 \r\n 。当接收端遍历字节数组时发现了 ,
就立就可以断定之前的字节数组是一条完整的消息,可以传递到上层逻辑继续进行解决。
HTTP 和 Redis 协议就大量用了 分割符。
此种消息一般要求消息体的内容是"文本消息"。
>> 长度前缀法
消息发送端在每条消息的开头添加一个 4 字节长度的整数值,标记消息体的长度。
这样消息接受者首先读取到长度信息,而后再读取相应长度的字节数组即可以将一个完整的消息分离出来。
此种消息比较常使用于"二进制消息"。

#比较
#特殊分割符法
优点: 消息的可读性比较强,可以直接看到消息的文本内容
缺点: 是不适合传递二进制消息,由于二进制的字节数组里面很容易就冒出连续的两个字节内容正好就是分割符的 ascii 值。
使用: 假如需要传递的话,一般是对二进制进行 base64 编码转变成普通文本消息再进行传送。

#长度前缀法
优点和缺点同特殊分割符法正好是相反的。
缺点: 由于适使用于二进制协议,所以可读性很差。
优点: 对传递的内容本身没有特殊限制,文本和内容皆可以传输,不需要进行特殊解决。
HTTP 协议的 Content-Length 头信息使用来标记消息体的长度,这个也可以看成是长度前缀法的一种应使用。

#应用
HTTP 协议是一种基于特殊分割符和长度前缀法的混合型协议。
比方 HTTP 的消息头采使用的是纯文本外加 分割符,
而消息体则是通过消息头中的 Content-Type 的值来决定长度。
HTTP 协议尽管被称之为文本传输协议,但是也可以在消息体中传输二进制数据数据的,
例如音视频图像,所以 HTTP 协议被称之为「超文本」传输协议。
消息边界--特殊分割符法.png
消息边界--长度前缀法.png
#消息的结构

每条消息都有它包含的语义结构信息,有些消息协议的结构信息是显式的,还有些是隐式的。

#显式结构的消息协议
比方 json 消息,它的结构即可以直接通过它的内容表现出来,所以它是一种显式结构的消息协议。
json 这种直观的消息协议的可读性非常棒,但是它的缺点也很显著,有太多的冗余信息。
比方每个字符串都用双引号来界定边界,key/value 之间必需有冒号分割,对象之间必需用大括号分割等等。
这些还只是冗余的小头,最大的冗余还在于连续的多条 json 消息即便结构完全一样,
仅仅只是 value 的值不一样,也需要发送同样的 key 字符串信息。

>> 对显示结构消息协议的改进之处
消息的结构在同一条消息通道上是可以复使用的,
比方在建立链接的开始 RPC 用户端和服务器之间先交流协商一下消息的结构,
后续发送消息时只要要发送一系列消息的 value 值,
接收端会自动将 value 值和相应位置的 key 关联起来,形成一个完成的结构消息。
在 Hadoop 系统中广泛用的 avro 消息协议就是通过这种方式实现的,
在 RPC 链接建立之处就开始交流消息的结构,后续消息的传递即可以节省很多流量。

#隐式结构的消息协议
消息的隐式结构一般是指那些结构信息由代码来商定的消息协议,
在 RPC 交互的消息数据中只是纯粹的二进制数据,由代码来确定相应位置的二进制是属于哪个字段。

假如纯粹看消息内容是无法知道节点消息内容中的哪些字节的含义,
它的消息结构是通过代码的结构顺序来确定的。
这种隐式的消息的优点就在于节省传输流量,它完全不需要传输结构信息。
#消息压缩

假如消息的内容太大,就要考虑对消息进行压缩解决,这可以减轻网络带宽压力。
但是这同时也会加重 CPU 的负担,由于压缩算法是 CPU 计算密集型操作,会导致操作系统的负载加重。
所以,最终能否进行消息压缩,肯定要根据业务情况加以权衡。

假如确定压缩,那么在选择压缩算法包时,
务必筛选那些底层使用 C 语言实现的算法库,由于 Python 的字节码执行起来太慢了。
比较流行的消息压缩算法有 Google 的 snappy 算法,
它的运行性能非常好,压缩比例尽管不是最优的,但是离最优的差距已经不是很大。
阿里的 SOFA RPC 就用了 snappy 作为协议层压缩算法。
消息的显式结构.png
消息的隐式结构.png
#流量的极致优化

开源的流行 RPC 消息协议往往对消息流量优化到了极致,
它们通过这种方式来打动使用户,吸引使用户来用它们。
比方对于一个整形数字,一般用 4 个字节来表示一个整数值。

但是经过研究发现,消息传递中大部分用的整数值都是很小的非负整数,假如一律用 4 个字节来表示一个整数会很白费。
所以就发明了一个类型叫变长整数varint。
数值非常小时,只要要用一个字节来存储,数值略微大一点可以用 2 个字节,
再大一点就是 3 个字节,它还可以超过 4 个字节使用来表达长整形数字。

其原理也很简单,就是保留每个字节的最高位的 bit 来标识能否后面还有字节,
1 表示还有字节需要继续读,0 表示到读到当前字节就结束。

#那假如是负数该怎样办呢?
-1 的 16 进制数是 0xFFFFFFFF,假如要按照这个编码那岂不是要 6 个字节才能存的下。
-1 也是非常常见的整数啊。

于是 zigzag 编码来了,专门使用来处理负数问题。
zigzag 编码将整数范围逐个映射到自然数范围,而后再进行 varint 编码。
zigzag 将负数编码成正奇数,正数编码成偶数。
解码的时候遇到偶数直接除 2 就是原值,遇到奇数就加 1 除 2 再取负就是原值。
流量的极致优化.png

https://blog.csdn.net/snow____man/article/details/84072901

1.3 RPC的实现基础

1、需要有非常高效的网络通信,比如一般选择Netty作为网络通信框架;
2、需要有比较高效的序列化框架,比如谷歌的Protobuf序列化框架;
3、可靠的寻址方式(主要是提供服务的发现),比如可以使用Zookeeper来注册服务等等;
4、如果是带会话(状态)的RPC调用,还需要有会话和状态保持的功能;

关键技术

1、动态代理
生成Client Stub(客户端存根)和Server Stub(服务端存根)的时候需要用到Java动态代理技术,
可以使用JDK提供的原生的动态代理机制,也可以使用开源的:CGLib代理,Javassist字节码生成技术。

2、序列化和反序列化
在网络中,所有的数据都将会被转化为字节进行传送,
所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。
>> 序列化:把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
>> 反序列化:把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。
目前比较高效的开源序列化框架:如Kryo、FastJson和Protobuf等。

3、NIO通信
出于并发性能的考虑,传统的阻塞式 IO 显然不太合适,因此我们需要异步的 IO,即 NIO。
Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持。
可以选择Netty或者MINA来解决NIO数据传输的问题。

4、服务注册中心
可选:Redis、Zookeeper、Consul 、Etcd。

1.4 RPC具体调用过程

1、服务消费者(client客户端)通过调用本地服务的方式调用需要消费的服务;
2、客户端存根(client stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体;
3、客户端存根(client stub)找到远程的服务地址,并且将消息通过网络发送给服务端;
4、服务端存根(server stub)收到消息后进行解码(反序列化操作);
5、服务端存根(server stub)根据解码结果调用本地的服务进行相关处理;
6、本地服务执行具体业务逻辑并将处理结果返回给服务端存根(server stub);
7、服务端存根(server stub)将返回结果重新打包成消息(序列化)并通过网络发送至消费方;
8、客户端存根(client stub)接收到消息,并进行解码(反序列化);
9、服务消费方得到最终结果;

而RPC框架的实现目标则是将上面的各步完好地封装起来,
也就是把调用、编码/解码的过程给封装起来,
让用户感觉上像调用本地服务一样的调用远程服务。

#更进一步的解释为:
>> RPC 服务方通过 RpcServer 去导出(export)远程接口方法,
客户方通过RpcClient 去引入(import)远程接口方法。 
>> 客户方像调用本地方法一样去调用远程接口方法,
RPC 框架提供接口的代理实现,实际的调用将委托给代理 RpcProxy 。
>> 代理封装调用信息并将调用转交给 RpcInvoker 去实际执行。 
>> 在客户端的 RpcInvoker 通过连接器 RpcConnector 去维持与服务端的通道 RpcChannel, 
并使用 RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。
>> RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,同样使用 RpcProtocol 执行协议解码(decode)。 
>> 解码后的调用信息传递给 RpcProcessor 去控制处理调用过程
>> 最后再委托调用给 RpcInvoker 去实际执行并返回调用结果。

#通过上述分析可知,这里面包括以下核心组件:
1、RpcServer
负责导出(export)远程接口
2、RpcClient
负责导入(import)远程接口的代理实现
3、RpcProxy
远程接口的代理实现
4、RpcInvoker
客户方实现:负责编码调用信息和发送调用请求到服务方并等待调用结果返回
服务方实现:负责调用服务端接口的具体实现并返回调用结果
5、RpcProtocol
负责协议编/解码
6、RpcConnector
负责维持客户方和服务方的连接通道和发送数据到服务方
7、RpcAcceptor
负责接收客户方请求并返回请求结果
8、RpcProcessor
负责在服务方控制调用过程,包括管理调用线程池、超时时间等
9、RpcChannel
数据传输通道
RPC的实现原理架构图1.png
RPC的实现原理架构图2.png
RPC结构.png

举例说明

比如说,A服务器想调用B服务器上的一个方法:User getUserByName(String userName)

#1、建立通信
首先要解决通讯的问题:
即A机器想要调用B机器,首先得建立起通信连接。
主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。
通常这个连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),
也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,
可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。

#2、服务寻址
要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,
如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么。
通常情况下我们需要提供B机器(主机名或IP地址)以及特定的端口,
然后指定调用的方法或者函数的名称以及入参出参等信息,这样才能完成服务的一个调用。可
靠的寻址方式(主要是提供服务的发现)是RPC的实现基石,比如可以采用Redis或者Zookeeper来注册服务等等。
#2.1、从服务提供者的角度看:
当服务提供者启动的时候,需要将自己提供的服务注册到指定的注册中心,
以便服务消费者能够通过服务注册中心进行查找;
当服务提供者由于各种原因致使提供的服务停止时,需要向注册中心注销停止的服务;
服务的提供者需要定期向服务注册中心发送心跳检测,
服务注册中心如果一段时间未收到来自服务提供者的心跳后,
认为该服务提供者已经停止服务,则将该服务从注册中心上去掉。
#2.2、从调用者的角度看:
服务的调用者启动的时候根据自己订阅的服务向服务注册中心查找服务提供者的地址等信息;
当服务调用者消费的服务上线或者下线的时候,注册中心会告知该服务的调用者;
服务调用者下线的时候,则取消订阅。

#3、网络传输
#3.1、序列化
当A机器上的应用发起一个RPC调用时,
调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,
所以我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。
然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。
#3.2、反序列化
当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作(序列化的逆操作),
即将二进制信息恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)
进行本地调用(一般是通过生成代理Proxy去调用,通常会有JDK动态代理、CGLIB动态代理、
Javassist生成字节码技术等),之后得到调用的返回值。

#4、服务调用
B机器进行本地调用(通过代理Proxy和反射调用)之后得到了返回值,
此时还需要再把返回值发送回A机器,同样也需要经过序列化操作,
然后再经过网络传输将二进制数据发送回A机器,
而当A机器接收到这些返回值之后,则再次进行反序列化操作,恢复为内存中的表达方式,
最后再交给A机器上的应用进行相关处理(一般是业务逻辑处理操作)。

通常,经过以上四个步骤之后,一次完整的RPC调用算是完成了,
另外可能因为网络抖动等原因需要重试等。

2.主流RPC框架有哪些

1、RMI
利用java.rmi包实现,基于Java远程方法协议(Java Remote Method Protocol) 和java的原生序列化。

2、Hessian
是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。 
基于HTTP协议,采用二进制编解码。

3、protobuf-rpc-pro
是一个Java类库,提供了基于 Google 的 Protocol Buffers 协议的远程方法调用的框架。
基于 Netty 底层的 NIO 技术。
支持 TCP 重用/ keep-alive、SSL加密、RPC 调用取消操作、嵌入式日志等功能。

4、Apache Thrift
是一种可伸缩的跨语言服务的软件框架。
它拥有功能强大的代码生成引擎,无缝地支持C + +,C#,Java,Python和PHP和Ruby。
Thrift允许你定义一个描述文件,描述数据类型和服务接口。
依据该文件,编译器方便地生成RPC客户端和服务器通信代码。
最初由facebook开发用做系统内个语言之间的RPC通信,
2007年由facebook贡献到apache基金 ,现在是apache下的opensource之一 。
支持多种语言之间的RPC方式的通信, 底层通讯基于SOCKET。

5、Avro
出自Hadoop之父Doug Cutting, 
在Thrift已经相当流行的情况下推出Avro的目标不仅是提供一套类似Thrift的通讯中间件,
更是要建立一个新的,标准性的云计算的数据交换和存储的Protocol。
支持HTTP,TCP两种协议。

6、Dubbo
Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,
使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。

7、Motan
新浪微博内部RPC框架。

2.1 跟语言平台绑定的开源 RPC 框架

>> Dubbo:
国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。
>> Motan:
微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。
>> Tars:
腾讯内部使用的 RPC 框架,于 2017 年对外开源,仅支持 C++ 语言。
>> Spring Cloud:
国外 Pivotal 公司 2014 年对外开源的 RPC 框架,仅支持 Java 语言。

2.1.1 Dubbo

https://www.jianshu.com/p/49a675abcad0 (参考该文)

2.1.2 Motan

https://github.com/weibocom/motan/wiki (参考官方wiki)

Motan 是国内另外一个比较有名的开源的 RPC 框架,同样也只支持 Java 语言实现。

Motan 与 Dubbo 的架构类似,都需要在 Client 端(服务消费者)和 Server 端(服务提供者)引入 SDK,
其中 Motan 框架主要包含下面几个功能模块。
>> register:
用来和注册中心交互,包括注册服务、订阅服务、服务变更通知、服务心跳发送等功能。
Server 端会在系统初始化时通过 register 模块注册服务,
Client 端会在系统初始化时通过 register 模块订阅到具体提供服务的 Server 列表,
当 Server 列表发生变更时也由 register 模块通知 Client。
>> protocol:
用来进行 RPC 服务的描述和 RPC 服务的配置管理,
这一层还可以添加不同功能的 filter 用来完成统计、并发限制等功能。
>> serialize:
将 RPC 请求中的参数、结果等对象进行序列化与反序列化,
即进行对象与字节流的互相转换,默认使用对 Java 更友好的 Hessian 2 进行序列化。
>> transport:
用来进行远程通信,默认使用 Netty NIO 的 TCP 长链接方式。
>> cluster:
Client 端使用的模块,cluster 是一组可用的 Server 在逻辑上的封装,
包含若干可以提供 RPC 服务的 Server,
实际请求时会根据不同的高可用与负载均衡策略选择一个可用的 Server 发起远程调用。
Motan架构图.png

2.1.3 Spring Cloud

Spring Cloud 是为了解决微服务架构中服务治理而提供的一系列功能的开发框架,
它是完全基于 Spring Boot 进行开发的,Spring Cloud 利用 Spring Boot 特性整合了开源行业中优秀的组件,
整体对外提供了一套在微服务架构中服务治理的解决方案。
只支持 Java 语言平台。
Spring-Cloud架构图.png

2.2 跨语言平台的开源 RPC 框架

>> gRPC:
Google 于 2015 年对外开源的跨语言 RPC 框架,支持常用的 
C++、Java、Python、Go、Ruby、PHP、Android Java、Objective-C 等多种语言。
>> Thrift:
最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,
成为 Apache 开源项目之一,支持常用的 C++、Java、PHP、Python、Ruby、Erlang 等多种语言。

2.2.1 gRPC

gRPC,它的原理是通过 IDL(Interface Definition Language)文件定义服务接口的参数和返回值类型,
然后通过代码生成程序生成服务端和客户端的具体实现代码,这样在 gRPC 里,
客户端应用可以像调用本地对象一样调用另一台服务器上对应的方法。

#它的主要特性包括三个方面
>> 通信协议采用了 HTTP/2,因为 HTTP/2 提供了连接复用、双向流、服务器推送、
请求优先级、首部压缩等机制,所以在通信过程中可以节省带宽、降低 TCP 连接次数、
节省 CPU,尤其对于移动端应用来说,可以帮助延长电池寿命。
>> IDL 使用了ProtoBuf,ProtoBuf 是由 Google 开发的一种数据序列化协议,
它的压缩和传输效率极高,语法也简单,所以被广泛应用在数据存储和通信协议上。
>> 多语言支持,能够基于多种语言自动生成对应语言的客户端和服务端的代码。
gRPC.png

2.2.2 Thrift

Thrift 是一种轻量级的跨语言 RPC 通信方案,支持多达 25 种编程语言。
为了支持多种语言,跟 gRPC 一样,Thrift 也有一套自己的接口定义语言 IDL,
可以通过代码生成器,生成各种编程语言的 Client 端和 Server 端的 SDK 代码,
这样就保证了不同语言之间可以相互通信。

#Thrift RPC 框架的特性。
>> 支持多种序列化格式:如 Binary、Compact、JSON、Multiplexed 等。
>> 支持多种通信方式:如 Socket、Framed、File、Memory、zlib 等。
>> 服务端支持多种处理方式:如 Simple 、Thread Pool、Non-Blocking 等。

https://github.com/apache/thrift/raw/master/doc/images/thrift-layers.png

2.n 总结

从长远来看,支持多语言是 RPC 框架未来的发展趋势。
正是基于此判断,各个 RPC 框架都提供了 Sidecar 组件来支持多语言平台之间的 RPC 调用。
>> Dubbo 在重启了维护,并且宣称要引入 Sidecar 组件来构建Dubbo Mesh提供多语言支持。
>> Motan 也在去年对外开源了其内部的 Sidecar 组件:
Motan-go,目前支持 PHP、Java 语言之间的相互调用。
>> Spring Cloud 也提供了 Sidecar 组件 spring-cloud-netflix-sideca,
可以让其他语言也可以使用 Spring Cloud 的组件。

所以未来语言不会成为使用上面这几种 RPC 框架的约束,
而 gRPC 和 Thrift 虽然支持跨语言的 RPC 调用,但是因为它们只提供了最基本的 RPC 框架功能,
缺乏一系列配套的服务化组件和服务治理功能的支撑,所以使用它们作为跨语言调用的 RPC 框架,
就需要自己考虑注册中心、熔断、限流、监控、分布式追踪等功能的实现。
不过好在大多数功能都有开源实现,可以直接采用。

参考资源
https://www.jianshu.com/p/bbaf6af8c01f (RPC解析)
https://www.jianshu.com/p/f2b8e0b11f73 (RPC框架选型参考)
https://www.cnblogs.com/wangkc/p/10864937.html (RPC与HTTP比较)
https://www.songma.com/news/txtlist_i3460v.html (RPC协议构成)
https://blog.csdn.net/nyyjs/article/details/77850523 (RPC调用中的反射技术)
http://www.360doc.com/content/18/0531/07/36490684_758407718.shtml (RPC调用中的反射技术)
https://www.jianshu.com/p/f4f789846fde (RPC调用过程详解)

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

推荐阅读更多精彩内容

  • 1.概述 七层模型五层模型四层模型应用层表示层应用层应用层会话层传输层传输层传输层网络层网络层网络层数据链路层数据...
    suxin1932阅读 3,005评论 1 3
  • 走在黑黑的小巷里 没有狗吠车喧 一切仿佛被时间定格 还记得曾经的你 也是定格在这默默的世界 远方的你是否睡了 我多...
    晨曦城光阅读 157评论 0 1
  • 与茶有缘阅读 91评论 0 2
  • 因热爱而学习 小女儿告诉我,她帮大女儿办了一件大事。原来一个月前,大女儿从海淘网上买一双鞋子。今天快递员打电话,因...
    漂亮的花阅读 266评论 0 5