前言
在前面的文章中为大家详细介绍了Hollow的数据模型以及深入分析了Hollow的内存布局。本文将详细介绍Hollow的生产消费模型。其中,Producer和Consumer概念是整个项目的核心,而HollowProducer和HollowConsumer分别代表Hollow的生产者和消费者。
希望大家通过本文可以充分的了解Hollow的生产消费模型。本文中会涉及很多Hollow中定义的名词,可以参考【Netflix Hollow系列】Hollow的一些术语。
生产者与消费者
在进程世界里,生产者就是生产数据的进程,消费者就是消费数据的进程。在多进程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。
为了解决这个问题于是引入了生产者和消费者的模式。本小节,首先将通过介绍操作系统和Kafka来介绍典型的生产消费模型。
操作系统的生产者消费者模型
首先我们来看下操作系统中的生产者消费者模型,生产者消费者问题是多个进程因共享一个缓存区而产生的相互依赖的问题。生产者进程需要向缓冲区中存放数据,消费者进程从共享缓存中取数据。当缓存区满的时候,生产者进程需要等待消费者进程取走数据,当缓存区空的时候,消费者进程需要等待生产者放入数据。
上述进程间的合作和依赖关系在整个计算机系统中非常普遍,比较典型的就是磁盘驱动程序和上层应用通过共享缓存区来交换数据。具体来讲,磁盘驱动程序是生产者进程,要将磁盘中读取的数据存放在共享缓存区中,上层应用程序是消费者进程,要从共享缓存区中取走数据并交给更上层的应用或用户来处理。
生产者和消费者之间如何通信呢?是的,信号量,操作系统通过信号量来解决互斥和同步的问题。
- 互斥:有节缓冲区是一个临界资源,对临界资源的访问需要设置一个信号量mutex。
- 同步:设置两个同步信号量empty和full,其初值分别为n、0,用于临界资源的具体数目,同时也可以表示等待条件。
kafka的生产者消费者模型
kafka流行的消息中间件,最初由LinkedIn开源,生产者生产(produce)各种信息,消费者消费(consume)(处理分析)这些信息,而在生产者与消费者之间,需要一个沟通两者的桥梁-消息系统。从一个微观层面来说,这种需求也可理解为不同的系统之间如何传递消息。
kafka实现了两种消息订阅模式:
点对点模式
基于拉取或者轮询的消息传送模型,这个模型的特点是发送到队列的消息被一个且只有一个消费者进行处理。生产者将消息放入消息队列后,由消费者主动地去拉取消息进行消费。 优点是消费者拉取消息的频率可以由自己控制。但是消息队列是否有消息需要消费,在消费者端无法感知,所以在消费者端需要额外的线程去监控。
发布订阅模式
发布订阅模式是一个基于消息推送的消息模型,这种模型可以有多种不同的订阅者。生产者将消息放入消息队列后,队列会将消息推送给订阅过该类消息的消费者。由于是消费者被动接收推送,所以无需感知消息队列是否有待消费的消息!
但是各个消费者由于机器性能不一样,所以处理消息的能力也会不一样,但消息队列却无法感知消费者消费的速度!所以推送的速度成了发布订阅模式的一个问题!假设三个消费者处理速度分别是3M/s、2M/s、1M/s,如果队列推送的速度为2M/s,则第三个消费者将无法承受!如果队列推送的速度为1M/s,则前两个消费者会出现资源的极大浪费!
生产者消费者模型实现方式
实现生产者消费者模型会有两种方式:
- wait—notify 方式实现生产者消费者模型,也就是上文中kafka中实现的点对点模式(注意这里需要加同步锁 synchronized)。
- 阻塞队列 方式实现生产者消费者模式,也就是上文中kafka实现的发布订阅模型。
以上两种方式也称之为 pull 和 push 方式,在不同的系统中可能有不同的名称或概念。
Hollow实现的生产者消费者
HollowProducer和HollowConsumer分别代表Hollow的生产者和消费者,可以直接使用HollowProducer和HollowConsumer完成生产和消费的任务。
其中HollowReadStateEngine和HollowWriteStateEngine分别是HollowProducer和HollowConsumer的底层数据处理类,如果觉得Hollow提供的Producer和Consumer不满足需求,可以基于HollowReadStateEngine和HollowWriteStateEngine实现自己的Producer和Consumer。
Producer与Consumer交互
通过上文介绍可以看出,Producer与Consumer之间的联系首先通过Announcer和AnnouncementWatcher相互关联的。但是具体的两者之间如何实现交互呢?
Producer通过Announcer发出通知,但是Hollow并没有限定如何通知,可以通过Producer主动推送消息到Consumer,也可以由Consumer通过不断的轮训获取通知。
Consumer借助于AnnouncementWatcher接收通知消息,Consumer接收消息的通知方式由Producer提供的消息发送方式决定,可以通过Pull或Push的方式完成。
如Hollow默认提供的
HollowFilesystemAnnouncementWatcher就是通过定义一个名为Watch的Runnable,不断的扫描Producer存放文件的文件夹判断是否有新的Blob产生。我们也可以自己实现如S3AnnouncementWatcher、MySQLAnnouncementWatcher等,实现HollowConsumer.AnnouncementWatcher的subscribeToUpdates方法,并定义自己的通知机制即可。
Push方式
使用Push方式Producer和Consumer之间通信,通常会有使用消息队列的方式完成。其中流行的消息中间件包括:
- RabbitMQ
- Kafka
- RocketMQ
- QMQ
Pull方式
使用Pull方式Producer和Consumer之间通信,在Consumer轮询Producer的特定接口。这里会涉及两部分中间件:
轮询方式
Consumer的轮询可以借助于定时器实现,比较典型的包括:
- JDK的ScheduledExecutorService和Timer
- 定时器中间件:quartz
Consumer使用定时器轮询Producer提供的RPC接口。
RPC
RPC的方式有很多,典型的直接使用HTTP、TCP访问,也可以使用中间件Dubbo等实现。
HollowProducer
HollowProducer的负责数据的生产工作,Hollow在HollowProducer中定义了Producer职责,除了核心的Publisher外,还定义了额外的附加机制,如下:
- 发布者(BlobPublisher)
- 通知(Announcer)
- Validator
- Listener
- 文件存储目录
- BLOB压缩器
- 按照BLOB版本查询器
- 自定义Executor
- 全量SNAPSHOT版本保留的份数
- 清理机制
- 监控收集器
Producer按照一定周期生成全量以及增量数据,在每个周期结束前会创建一个数据集(可以称之为BLOB),并将这个数据集合发送到某种存储介质上(可以是本地磁盘、S3、DB、Redis等),最后发布一个通知,告知Consumer数据已经Ready,可以来获取数据了。
可以使用如下方式初始化Producer,除Publisher外,其他都是可选项。
HollowProducer producer = HollowProducer
.withPublisher(publisher) /// required: a BlobPublisher
.withAnnouncer(announcer) /// optional: an Announcer
.withValidators(validators) /// optional: one or more Validator
.withListeners(listeners) /// optional: one or more HollowProducerListeners
.withBlobStagingDir(dir) /// optional: a java.io.File
.withBlobCompressor(compressor) /// optional: a BlobCompressor
.withBlobStager(stager) /// optional: a BlobStager
.withSnapshotPublishExecutor(e) /// optional: a java.util.concurrent.Executor
.withNumStatesBetweenSnapshots(n) /// optional: an int
.withTargetMaxTypeShardSize(size) /// optional: a long
.withBlobStorageCleaner(blobStorageCleaner) /// optional: a BlobStorageCleaner
.withMetricsCollector(hollowMetricsCollector) /// optional: a HollowMetricsCollector<HollowProducerMetrics>
.build();
接下来我们详细介绍HollowProducer的属性
Properties
首先让我们看下HollowProducer的构造方法。可以看出属性在上文中已经有所阐述,还请大家特别留意HollowWriteStateEngine,HollowWriteStateEngine是HollowProducer的底层核心类,负责数据的写入职责。本文中不再详细展开,后续会有专门的文章进行分析,也包括下文中HollowConsumer的底层类HollowReadStateEngine。
private AbstractHollowProducer(
HollowProducer.BlobStager blobStager,
HollowProducer.Publisher publisher,
HollowProducer.Announcer announcer,
List<? extends HollowProducerEventListener> eventListeners,
HollowProducer.VersionMinter versionMinter,
Executor snapshotPublishExecutor,
int numStatesBetweenSnapshots,
long targetMaxTypeShardSize,
boolean focusHoleFillInFewestShards,
HollowMetricsCollector<HollowProducerMetrics> metricsCollector,
HollowProducer.BlobStorageCleaner blobStorageCleaner,
SingleProducerEnforcer singleProducerEnforcer,
HollowObjectHashCodeFinder hashCodeFinder,
boolean doIntegrityCheck) {
this.publishe