标签 java 下的文章

消息队列二三事


最近在看kafka的代码,就免不了想看看消息队列的一些要点:服务质量(QOS)性能扩展性等等,下面一一探索这些概念,并谈谈在特定的消息队列如kafka或者mosquito中是如何具体实现这些概念的。

服务质量

服务语义

服务质量一般可以分为三个级别,下面说明它们不同语义。

At most once

至多一次,消息可能丢失,但绝不会重复传输。
生产者:完全依赖底层TCP/IP的传输可靠性,不做特殊处理,所谓“发送即忘”。kafka中设置acks=0
消费者:先保存消费进度,再处理消息。kafka中设置消费者自动提交偏移量并设置较短的提交时间间隔。


源码分析Kafka之Producer


Kafka是一款很棒的消息系统,可以看看我之前写的 后端好书阅读与推荐来了解一下它的整体设计。今天我们就来深入了解一下它的实现细节(我fork了一份代码),首先关注Producer这一方。

要使用kafka首先要实例化一个KafkaProducer,需要有brokerIP、序列化器必要Properties以及acks(0、1、n)、compression、retries、batch.size非必要Properties,通过这个简单的接口可以控制Producer大部分行为,实例化后就可以调用send方法发送消息了。

核心实现是这个方法:

public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
    // intercept the record, which can be potentially modified; this method does not throw exceptions
    ProducerRecord<K, V> interceptedRecord = this.interceptors.onSend(record);//①
    return doSend(interceptedRecord, callback);//②
}

通过不同的模式可以实现发送即忘(忽略返回结果)、同步发送(获取返回的future对象,回调函数置为null)、异步发送(设置回调函数)三种消息模式。


计算机科学中抽象的好处与问题—伪共享实例分析


David John Wheeler有一句名言“计算机科学中的任何问题都可以通过加上一层间接层来解决”一层不够就再加一层。后半句是我加的 (* ̄︶ ̄) ,虽然有点玩笑的意思,但是也的确能说明一些问题。计算机科学的确是靠着一层又一层的抽象与封装解决了巨量的问题。

我们来简单回顾一下:
开始的时候是程序员直接输入二进制指令来操纵硬件的,不仅性能低下还很耗费用户时间;
于是后来出现了操作系统,用文件、进程与线程、地址空间抽象了磁盘、CPU与内存,统一和简化了硬件访问方式;
机器语言对用户不友好,于是便出现了汇编语言、中级语言(如C)、高级语言(如Java)的包装,其最终执行还是要转化为机器语言;
裸高级语言大家还用得不爽,觉得开发效率低,于是又出现了各种框架(如Spring、Hibernate)
......

这样一层一层抽象包装下来,我们要想实现一个功能比如定时写文件等已经变成了很简单的事,只需要几行代码就搞定了。
但是抽象层数过多就会导致我们顶层的用户有时候会出现一些莫名其妙的问题,我们用一个实际的案例伪共享来说明一下


为什么要指令重排序?


我们知道java在运行的时候有两个地方可能用到重排序,一个是编译器编译的的时候,一个是处理器运行的时候。
那么我们就应该问问为啥要用指令重排序呢?

生活类比

我们从生活中举个例子,假设你有一箱红纸,现在要你剪成小红花贴在窗上。你有两种极端的选择:拿出来一个,把这个剪好,再贴上去......一个一个依次进行;另一种方式是先全部拿出来,然后全部剪好,最后全部贴上去。

那种效率更高?很明显是后者,因为前者你就需要不停地在箱子,剪刀和胶水之间切换,这个切换过程不仅浪费时间,还耗费精力。但是后者一直做一个工作也很无聊,还会导致半天了窗上一朵花都没有,会给你带来失落感,所以比较合适的做法就是拿出来一叠,把这一叠剪好,贴上去。这样既不无聊,也减少了切换次数,提高了工作效率。

再想想,如果有三个人,一个负责拿,一个负责剪,一个负责贴,就更快了。


实现高可用的两种方案与实战


我之前在一片文章 用Nginx+Redis实现session共享的均衡负载 中做了一个负载均衡的实验,其主要架构如下:

round.png

debian1作为调度服务器承担请求分发的任务,即用户访问的是debian1,然后debain1把请求按照一定的策略发送给应用服务器:debian2或者debain3,甚至更多的debain4、5、6......

状态数据可以放在外部的分布式缓存服务分布式数据库服务中,这样应用服务本身就是无状态的,所以机器增减都是很容易的,应用的高可用是有保证的(对于有状态的高可用不仅要注意机器增减与切换、还要注意备份冗余数据一致性等问题)。但是当时忽略了一个地方,那就是调度服务器debian1本身的高可用性没有考虑到,存在单点问题。

高可用的首要想法就是双机热备,故障时自动切换,所以我们要给debian1加一个备机debain1'。我现在按照自己的知识粗浅的把解决方案分为两类:客户端有感知的高可用对客户端透明的高可用,并分别挑选一个示例做一下实验。

注:下面实现高可用都用的是双机热备,为了方便,把调度服务器debian1简称为主机,把调度服务器debian1的备机debian1'简称为备机