git pull 发现 zmalloc.c更新了
写拆分,读累加
Avoid used_memory contention when update from multiple threads
commit log 里 The solution介绍,都不用看代码改了什么,作为资深javaer ,老八股之jdk8 LongAdder 就条件反射出来了,会心一笑
LongAdder 的基本思路就是分散热点,将 value 值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行 CAS 操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的 long 值,只要将各个槽中的变量值累加返回
相似之处
核心思想相同
-
Redis的实现:将全局的used_memory变量分解为多个线程本地计数器
-
Java LongAdder:将全局的long计数器分解为多个Cell数组
解决相同的问题
-
问题:多线程同时更新同一个计数器时的高竞争
-
解决方案:使用分片计数(sharded counting)来减少锁竞争
具体实现对比
Redis
// 每个线程有自己的内存计数器
typedef struct used_memory_entry {
redisAtomic long long used_memory; // 原子long long
char padding[CACHE_LINE_SIZE - sizeof(long long)]; // 缓存行填充
} used_memory_entry;
static used_memory_entry used_memory[MAX_THREADS]; // 16个线程的计数器数组
Java LongAdder
// 每个线程有自己的Cell
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
}
Cell[] cells; // 动态增长的Cell数组
线程分配策略
-
Redis:使用my_thread_index &= THREAD_MASK(位运算取模)
-
Java LongAdder:使用ThreadLocalRandom.getProbe()哈希分配
内存布局优化
-
Redis:使用CACHE_LINE_SIZE填充避免伪共享
-
Java LongAdder:使用@sun.misc.Contended注解避免伪共享
聚合计算
-
Redis:遍历所有活跃线程的计数器并求和
-
Java LongAdder:遍历所有Cell并求和
主要差异
-
动态性:LongAdder的Cell数组是动态增长的,而Redis固定16个线程
-
内存管理:Redis专门针对内存分配统计,LongAdder是通用计数器