Redis 五种基本数据类型的底层实现 Redis 对外暴露的是 String、Hash、List、Set、Sorted Set 五种类型,但内部每种类型往往有 两种(甚至多种)底层编码 ,根据数据量大小自动切换。核心设计哲学是: 小数据用紧凑、连续内存的结构省内存;数据量大了再切换成哈希表/跳表这类效率更高但更耗内存的结构 。 1. String —— SDS(Simple Dynamic String) Redis 没有直接用 C 语言原生字符串,而是自己实现了 SDS。 结构(以 sdshdr8 为例): len : 已使用长度 alloc : 总分配空间 flags : 类型标记 buf[] : 实际字节数组(以 \ 0 结尾,但不依赖它判断长度) 根据字符串长度不同,还细分为 sdshdr5/8/16/32/64 ,用最小的头部类型存放,进一步省内存。 为什么这样设计: - C 字符串要遍历到 \0 才能拿到长度,SDS 直接读 len 字段, O(1) 。 - C 字符串不是二进制安全的(遇到 \0 就截断),SDS 靠 len 判断边界,可以存图片、序列化数据等任意二进制内容。 - 拼接前会检查剩余空间,杜绝了缓冲区溢出。 - 空间预分配 :字符串变长时,若新长度 < 1MB,分配双倍空间;若 ≥ 1MB,每次只多分配 1MB,避免频繁 realloc 。 - 惰性空间释放 :字符串变短时不立即释放内存,留着以后用。 优缺点: 换来了安全性、O(1) 长度获取、减少内存重分配次数,代价是比原生 C 字符串多占一点元数据空间。 2. Hash —— listpack / hashtable 小对象 :用 listpack (Redis 7.0 之前叫 ziplist ),本质是一段连续内存,紧凑存放 key1 value1 key2 value2... ,没有指针开销。 大对象 :转为 hashtable (Redis 自己实现的 dict ),本质是数组 + 链地址法解决哈希冲突,支持 渐进式 rehash 。 触发转换的阈值(可配置): - hash-max-listpack-entries (默认 128) - hash-max-listpack-value (默认 64 字节) 任一超限...
使用 Kafka 作为消息队列时,有些坑是因为它设计初衷是"分布式日志系统"而非传统消息队列(如 RabbitMQ),如果照搬传统 MQ 的思维方式,很容易踩坑。以下是常见的几类问题: 1. 消息顺序问题 分区内有序,分区间无序 :Kafka 只保证同一分区内的消息顺序,如果你的业务需要严格顺序(比如同一用户的操作按顺序处理),必须保证相关消息落到同一分区(合理设计 key)。 重试导致乱序 :生产者开启重试(retries)且 max.in.flight.requests.per.connection > 1 时,重试的消息可能"插队",导致同一分区内乱序。如果对顺序敏感,建议设置为 1,或使用 enable.idempotence=true (幂等生产者会自动处理这个问题)。 2. 重复消费 / 消息丢失 at-least-once 是默认语义 :消费者处理完消息后才提交 offset,如果提交前挂掉,重启后会重复消费。业务逻辑必须做到 幂等 ,否则会出问题(比如重复扣款)。 自动提交的陷阱 : enable.auto.commit=true 时,offset 是按时间间隔自动提交的,可能在消息还没处理完就提交了(消息丢失),也可能处理完了还没提交就挂了(重复消费)。生产环境通常建议手动提交,并且在业务处理成功后再提交。 acks 设置不当 : acks=0 或 acks=1 在某些故障场景下会丢消息,需要金融级可靠性的场景建议 acks=all 配合 min.insync.replicas 。 3. 消费者组与 Rebalance 风暴 Rebalance 影响吞吐 :消费者数量变化、消费者处理超时(超过 max.poll.interval.ms )都会触发 rebalance,期间整个消费者组会短暂停止消费。如果单条消息处理耗时很长,容易频繁触发 rebalance。 消费者数量 > 分区数 :多余的消费者会一直空闲,分区数决定了消费并行度的上限,扩容消费者之前要先看分区数够不够。 4. 把 Kafka 当成传统 MQ 用 没有真正的"消息确认/拒绝/重新入队"机制 :不像 RabbitMQ 那样可以针对单条消息 ack/nack,Kafk...