1. 接口
1 | package main |
若 T 是一个具体类型,并且想在 T 上实现 接口 I:
- 如果将接口的方法绑定到 T 上,那么可以将 T 或者 *T 类型的变量赋值给 I 类型的变量。
- 如果将接口的方法绑定到 T 上,那么只能将 T 类型的变量赋值给 I 类型的变量。
- 如果将接口的一部分方法绑定到 T 上,一部分绑定到 T 上,那么只能将 T 类型的变量赋值给 I 类型的变量。
1 | package main |
若 T 是一个具体类型,并且想在 T 上实现 接口 I:
QuorumPeerMain
类的 Main
函数较为简单,直接调用了 initializeAndRun
方法,参数就是 zkServer.sh
转入的参数,这里是 “start”。在 initializeAndRun
方法内部,首先启动的是定时清除镜像任务 DatadirCleanupManager
,默认设置为保留 3 份。由于 purgeInterval
这个参数默认设置为 0,所以不会启动镜像定时清除机制。
org.apache.zookeeper.server.DatadirCleanupManager#start
1 | public void start() { |
在 ZooKeeper 客户端与服务端成功完成连接创建后,就创建了一个会话。ZooKeeper 会话在整个运行期间的生命周期中,会在不同的会话状态之间进行切换,这些状态可以一般可以分为 CONNECTING
、CONNECTED
、RECONNECTING
、RECONNECTED
、CLOSE
等。
一旦客户端开始创建 ZooKeeper 对象,那么客户端状态就会变成 CONNECTING
,同时客户端开始从服务器地址列表中逐个选取 IP 地址来尝试进行网络连接,直到成功连接上服务器,然后将客户端状态变更为 CONNECTED
。
通常情况下,伴随着网络闪断或是其他原因,客户端与服务端之间的连接会出现断开情况,一旦碰到这种情况,ZooKeeper 客户端会自动进行重连操作,同时客户端的状态再次变为 CONNCTING
,直到重新连接上服务器后,客户端状态又会再次转变成 CONNECTED
。因此,通常情况下,在 ZooKeeper 运行期间,客户端的状态总是介于 CONNECTING
和 CONNECTED
两者之一。
另外,如果出现诸如会话超时、权限检查失败或是客户端主动退出程序等情况,那么客户端的状态就会直接变更为 CLOSE
。
ZooKeeper 客户端主要由以下几个核心组件组成:
ZooKeeper
实例:客户端的入口ClientWatchManager
:客户端Watcher管理器HostProvider
:客户端地址列表管理器ClientCnxn
:客户端核心线程使用方式:
telnet
1 | $ telnet localhost 2181 |
nc
1 | $ echo conf | nc localhost 2181 |
ZooKeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些指定事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。
ZooKeeper 的 Watcher 机制主要包括客户端线程、客户端 WatchManager 和 ZooKeeper 服务器三部分。在具体工作流程上,简单地讲,客户端在向 ZooKeeper 服务器注册 Watcher 的同时,会将 Watcher 对象存储在客户端的 WatchManager 中。当 ZooKeeper 服务器端触发 Watcher 事件后,会向客户端发送通知,客户端线程从 WatchManager 中取出对应的 Watcher 对象来执行回调逻辑。
使用 Jute 来对对象进行序列化和反序列化,大体可以分为 4 步:
Record
接口的 serialize
和 deserialize
方法。ByteOutputArchive
。serialize
方法,将对象序列化到指定 tag 中去。deserialize
方法,从指定的 tag 中反序列化出数据内容。Jute 定义了自己独特的序列化格式 Record
。
org.apache.jute.Record
1 | public interface Record { |
所有实体类通过实现 Record
接口的这两个方法,来定义自己将如何被序列化和反序列化。其中 archive
是底层真正的序列化器和反序列化器,并且每个 archive
中可以包含对多个对象的序列化和反序列化,因此两个接口方法中都标记了参数 tag
,用于向序列化器和反序列化器标识对象自己的标记。
OutputArchive
和 InputArchive
分别是 Jute 底层的序列化器和反序列化器接口定义。在最新版本的 Jute 中,分别有 BinaryOutputArchive
/BinaryInputArchive
、CsvoutputArchive
/CsvInputArchive
和 XmlOutputArchive
/XmlInputArchive
三种实现。无论哪种实现,都是基于 OutputStream
和 InputStream
进行操作。
基于 TCP/IP 协议,ZooKeeper 实现了自己的通信协议来完成客户端与服务端、服务端与服务端之间的网络通信。ZooKeeper 通信协议整体上的设计非常简单,对于请求,主要包含请求头和请求体,而对于响应,则主要包含响应头和响应体。
org.apache.zookeeper.proto.RequestHeader
1 | public class RequestHeader implements Record { |
xid 用于记录客户端请求发起的先后序号,用来确保单个客户端请求的响应顺序。type 代表请求的操作类型,所有操作类型都被定义在类 org.apache.zookeeper.ZooDefs.OpCode
中。根据协议规定,除非是会话创建请求,其他所有的客户端请求中都会带上请求头。
协议的请求体部分是指请求的主体内容部分,包含了请求的所有操作内容。不同的请求类型,其请求体部分的结构是不同的。
org.apache.zookeeper.proto.GetDataRequest
1 | public class GetDataRequest implements Record { |
org.apache.zookeeper.proto.ReplyHeader
1 | class ReplyHeader implements Record { |
xid 与请求头中的xid一致,zxid 表示 ZooKeeper 服务器上当前最新的事务 ID,err 则是一个错误码,当请求处理过程中出现异常情况时,会在这个错误码中标识出来,
协议的响应体部分包含了响应的所有返回数据,不同的响应类型,其响应体部分的结构是不同的。
org.apache.zookeeper.proto.GetDataResponse
1 | class GetDataResponse implements Record { |
Redis 服务器是典型的一对多服务器程序:一个服务器可以与多个客户端建立网络连接,每个客户端可以向服务器发送命令请求,而服务器则接收并处理客户端发送的命令请求,并向客户端返回命令回复。
Sentinel 是 Redis 的高可用性解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
Redis 的发布与订阅功能由 PUBLISH
、SUBSCRIBE
、PSUBSCRIBE
等命令组成。
通过执行 SUBSCRIBE
命令,客户端可以订阅一个或多个频道从而成为这些频道的订阅者(subscriber):每当有其它客户端向被订阅的频道发送消息(message)时,频道的所有订阅者都会收到这条消息。
除了订阅频道之外,客户端还可以通过执行 PSUBSCRIBE
命令订阅一个或多个模式,从而成为这些模式的订阅者:每当有其他客户端向某个频道发送消息时,消息不仅会被发送给这个频道的所有订阅者,它还会被发送给所有与这个频道相匹配的模式的订阅者。
AOF 持久化保存数据库状态的方法是将服务器执行的命令保存到文件中。被写入 AOF 文件的所有命令都是以 Redis 的命令请求协议格式保存的。
服务器在启动时,可以通过载入和执行 AOF 文件中保存的命令来还原服务器关闭之前的数据库状态,以下就是服务器载入 AOF 文件并还原数据库状态时打印的日志:
1 | [8321] 05 Sep 11:58:50.448 # Server started, Redisversion 2.9.11 |