Kafka源码解析(八)- 高性能数据结构CopyOnWriteMap
上一篇介绍了将数据存入RecordAccumulator 这一过程


这段代码是在高并发的情况下运行的,既然如此,就会有线程安全的问题。我们看到作者设计这段代码的时候,并没有在整个方法上加锁,而是采用分段解锁的方式,将性能提升到最高
但是不知道大家有没有注意到获取或创建队列的时候,也就是这行代码 Deque
答案是没有!在这儿:

我们看到,进来之后第一件事儿就是从batches里面获取队列,那么batches究竟是怎么实现的呢?

在初始化的时候,new了一个CopyOnWriteMap,这个是kafka自己设计的数据结构。看一下这个数据结构里面是怎么做的
public class CopyOnWriteMap<K, V> implements ConcurrentMap<K, V> {
private volatile Map<K, V> map;
public CopyOnWriteMap() {
this.map = Collections.emptyMap();
}
public CopyOnWriteMap(Map<K, V> map) {
this.map = Collections.unmodifiableMap(map);
}
@Override
public boolean containsKey(Object k) {
return map.containsKey(k);
}
@Override
public boolean containsValue(Object v) {
return map.containsValue(v);
}
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
return map.entrySet();
}
@Override
public V get(Object k) {
return map.get(k);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public Set<K> keySet() {
return map.keySet();
}
@Override
public int size() {
return map.size();
}
@Override
public Collection<V> values() {
return map.values();
}
@Override
public synchronized void clear() {
this.map = Collections.emptyMap();
}
@Override
public synchronized V put(K k, V v) {
//新的内存空间
Map<K, V> copy = new HashMap<K, V>(this.map);
//插入数据
V prev = copy.put(k, v);
//合并赋值给map
this.map = Collections.unmodifiableMap(copy);
return prev;
}
@Override
public synchronized void putAll(Map<? extends K, ? extends V> entries) {
Map<K, V> copy = new HashMap<K, V>(this.map);
copy.putAll(entries);
this.map = Collections.unmodifiableMap(copy);
}
@Override
public synchronized V remove(Object key) {
Map<K, V> copy = new HashMap<K, V>(this.map);
V prev = copy.remove(key);
this.map = Collections.unmodifiableMap(copy);
return prev;
}
@Override
public synchronized V putIfAbsent(K k, V v) {
if (!containsKey(k))
return put(k, v);
else
return get(k);
}
@Override
public synchronized boolean remove(Object k, Object v) {
if (containsKey(k) && get(k).equals(v)) {
remove(k);
return true;
} else {
return false;
}
}
@Override
public synchronized boolean replace(K k, V original, V replacement) {
if (containsKey(k) && get(k).equals(original)) {
put(k, replacement);
return true;
} else {
return false;
}
}
@Override
public synchronized V replace(K k, V v) {
if (containsKey(k)) {
return put(k, v);
} else {
return null;
}
}
看这样一个数据结构最主要是看他的get 和 put 方法,我们可以看到,get方法就是从map里面去取数据,没有加锁。那么看set方法
@Override
public synchronized V put(K k, V v) {
//新的内存空间
Map<K, V> copy = new HashMap<K, V>(this.map);
//插入数据
V prev = copy.put(k, v);
//合并赋值给map
this.map = Collections.unmodifiableMap(copy);
return prev;
}
往map里面put数据的时候,首先开辟一块新的内存空间,然后将数据存到里面,最后将新开辟的空间和之前的map数据合并,最后赋值给map
这样做到了读写分离的效果,读和写互不影响,而且private volatile Map<K, V> map; 这个map是用volatile修饰的,保证了多线程的可见性。
因此,在获取或创建队列的这里块,也是线程安全的。
那么这个高性能的数据结构应用于什么场景呢? 答案是:读多写少的场景!
我们知道getOrCreateDeque 这一过程首先是要从batches里面获取队列,这一动作就是“读”,而每次进来都要get一下,也就是说每发送一条数据都要读一次
而put这一动作,仅在从batches里面没有获取到队列的时候,才会创建,并将队列这个对象put导batches里面,也就是触发“写”动作,因此是读多写少的场景。

浙公网安备 33010602011771号