CopyOnWrite 思想在 Kafka 源码中的运用
CopyOnWrite 思想在 Kafka 源码中的运用
在 Kafka 的内核源码中,有这么一个场景,客户端在向 Kafka 写数据的时候,会把消息先写入客户端本地的内存缓冲,然后在内存缓冲里形成一个 Batch 之后再一次性发送到 Kafka 服务器上去,这样有助于提升吞吐量。
请看下图:
这个时候 Kafka 的内存缓冲用的是什么数据结构呢?
请看源码:
private final ConcurrentMap<TopicPartition, Deque<RecordBatch>> batches = new CopyOnWriteMap<TopicPartition, Deque<RecordBatch>>();
这个数据结构就是核心的用来存放写入内存缓冲中的消息的数据结构,要看懂这个数据结构需要对很多 Kafka 内核源码里的概念进行解释。Kafka 是自己实现了一个 CopyOnWriteMap,这个CopyOnWriteMap 采用的就是 CopyOnWrite 思想。
我们来看一下这个 CopyOnWriteMap 的源码实现:
// 典型的volatile修饰普通Map private volatile Map<K, V> map; @Override public synchronized V put(K k, V v) { // 更新的时候先创建副本,更新副本,然后对volatile变量赋值写回去 Map<K, V> copy = new HashMap<K, V>(this.map); V prev = copy.put(k, v); this.map = Collections.unmodifiableMap(copy); return prev; } @Override public V get(Object k) { // 读取的时候直接读volatile变量引用的map数据结构,无需锁 return map.get(k); }
Kafka 这个核心数据结构在这里之所以采用 CopyOnWriteMap 思想来实现,就是因为这个 Map 的 Key-Value 对,其实没那么频繁更新。
也就是 TopicPartition-Deque 这个 Key-Value 对,更新频率很低。但是它的 Get 操作却是高频的读取请求,因为会高频的读取出来一个 TopicPartition 对应的 Deque 数据结构,来对这个队列进行入队出队等操作,所以对于这个 Map 而言,高频的是其 Get 操作。这个时候,Kafka 就采用了 CopyOnWrite 思想来实现这个 Map,避免更新 Key-Value 的时候阻塞住高频的读操作,实现无锁的效果,优化线程并发的性能。
相信看完这个文章,对于 CopyOnWrite 思想以及适用场景,包括 JDK 中的实现,以及在 Kafka 源码中的运用,都有了一个切身的体会了。