Redis(Remote Dictionary Server)作为当前最流行的内存数据库之一,其核心设计理念围绕"速度"与"简单"展开。与传统关系型数据库不同,Redis采用键值对(Key-Value)存储结构,所有数据常驻内存,这使得其读写性能可以达到微妙级别。我在实际生产环境中测得Redis单节点QPS(每秒查询率)轻松突破10万,这是磁盘存储数据库难以企及的指标。
内存存储带来的性能优势显而易见,但也伴随着特殊挑战。当服务器断电时,内存数据会全部丢失。为此Redis提供了两种持久化方案:RDB快照和AOF日志。RDB相当于给内存数据拍照片,定期保存到磁盘;AOF则记录所有写操作命令。在我的运维经验中,通常会同时开启两种方式——用RDB做定期全量备份,用AOF保证操作日志不丢失,这样即使发生故障也能将损失控制在秒级。
重要提示:Redis虽然是单线程模型,但通过IO多路复用技术(Linux下默认使用epoll)可以高效处理数万个并发连接。这意味着在常规业务场景下,单实例Redis的性能瓶颈往往出现在网络带宽而非CPU处理能力上。
缓存穿透是指查询不存在的数据,导致请求直接穿透缓存层到达数据库。去年我们电商系统就遭遇过恶意攻击——攻击者用脚本批量查询不存在的商品ID,导致数据库负载激增。解决方案我们采用了双重防护:
java复制String value = redis.get(key);
if(value == null) {
Object dbValue = db.query(key);
if(dbValue == null) {
redis.setex(key, 30, "NULL"); // 设置空值缓存
} else {
redis.setex(key, 3600, dbValue); // 正常缓存
}
}
java复制@Bean
public BloomFilter<String> productBloomFilter() {
return BloomFilter.create(
Funnels.stringFunnel(Charset.defaultCharset()),
1000000, // 预期元素数量
0.01 // 误判率
);
}
当某个热点key突然失效时,大量并发请求会直接冲击数据库。我们曾遇到某明星商品在秒杀开始时缓存刚好过期,导致数据库连接池被撑爆的事故。现在采用的解决方案是:
python复制def get_data(key):
data = redis.get(key)
if data and data['expire'] > time.time():
return data['value']
else:
# 异步重建缓存
threading.Thread(target=rebuild_cache, args=(key,)).start()
return data['value'] if data else None
go复制func GetData(key string) string {
data := redis.Get(key)
if data == "" {
// 尝试获取锁
lockKey := "lock:" + key
if redis.SetNX(lockKey, 1, 10*time.Second) {
// 重建缓存
newData := db.Query(key)
redis.Set(key, newData, 1*time.Hour)
redis.Del(lockKey)
return newData
} else {
// 等待重试
time.Sleep(100 * time.Millisecond)
return GetData(key)
}
}
return data
}
当大量key同时失效或Redis集群宕机时,就会发生雪崩效应。我们通过多级防护来应对:
javascript复制function setCache(key, value) {
const baseTTL = 3600; // 1小时
const randomTTL = Math.floor(Math.random() * 600); // 0-10分钟
redis.setex(key, baseTTL + randomTTL, value);
}
code复制sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
RDB通过fork子进程来执行持久化,父进程继续处理请求。在AWS c5.xlarge实例上测试,保存10GB的RDB文件大约需要3-5秒。关键配置项(redis.conf):
code复制save 900 1 # 900秒内至少1个key变化
save 300 10 # 300秒内至少10个key变化
save 60 10000 # 60秒内至少10000个key变化
rdbcompression yes # 启用压缩
dbfilename dump.rdb
生产环境经验:RDB的save配置需要根据业务特点调整。对于写入量大的系统,应适当放宽条件避免频繁fork影响性能。
AOF提供了三种刷盘策略:
我们通过重写机制压缩AOF文件:
code复制auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
在AOF和RDB混合模式下(Redis 4.0+),重启恢复时会先加载RDB再重放AOF,大幅提高恢复速度:
code复制aof-use-rdb-preamble yes
全量同步过程:
监控同步状态命令:
bash复制redis-cli info replication
典型的三节点哨兵部署架构:
code复制 +------------+
| Sentinel 1 |
+------+-----+
|
+-------------+-------------------+
| | |
+-----+-----+ +---------+ +-----+-----+
| Master | | Sentinel2| | Slave |
| Redis 6379| +---------+ | Redis 6380|
+-----+-----+ +-----+-----+
| |
+------------+------------+
|
+-----+-----+
| Sentinel3 |
+-----------+
哨兵关键配置:
code复制sentinel monitor mycluster 127.0.0.1 6379 2
sentinel parallel-syncs mycluster 1
sentinel failover-timeout mycluster 180000
Redis Cluster数据分布原理:
集群创建命令示例:
bash复制redis-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
基于Redisson的分布式锁最佳实践:
java复制RLock lock = redisson.getLock("orderLock");
try {
// 尝试加锁,最多等待100秒,锁自动释放时间30秒
boolean res = lock.tryLock(100, 30, TimeUnit.SECONDS);
if (res) {
// 业务逻辑
}
} finally {
lock.unlock();
}
| 策略 | 描述 | 适用场景 |
|---|---|---|
| volatile-lru | 从已设置过期时间的key中淘汰最近最少使用的 | 缓存系统 |
| allkeys-lru | 从所有key中淘汰最近最少使用的 | 内存不足时 |
| volatile-ttl | 淘汰剩余存活时间最短的key | 时效性数据 |
| noeviction | 不淘汰,写操作返回错误 | 关键数据 |
配置方式:
code复制maxmemory-policy allkeys-lru
maxmemory 4gb
管道技术示例(减少网络往返时间):
python复制pipe = redis.pipeline()
for i in range(1000):
pipe.set(f'key_{i}', i)
pipe.execute()
Lua脚本实现原子操作:
lua复制-- 限流脚本
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then
return 0
else
redis.call("INCRBY", key, 1)
redis.call("EXPIRE", key, 60)
return 1
end
使用Hash类型存储对象:相比将每个字段存为单独key,Hash结构可以节省大量内存。测试显示存储100万个用户对象,String类型需要1.2GB,而Hash仅需600MB。
启用内存碎片整理:
code复制activedefrag yes
active-defrag-ignore-bytes 100mb
active-defrag-threshold-lower 10
bash复制redis-cli info memory
code复制# 操作系统配置
sysctl -w net.core.somaxconn=65535
sysctl -w vm.overcommit_memory=1
java复制JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(500);
config.setMaxIdle(100);
config.setMinIdle(50);
config.setMaxWaitMillis(2000);
在高写入场景下,RDB的fork操作可能导致服务短暂停顿。我们通过以下手段缓解:
repl-diskless-sync yes启用无盘复制bash复制redis-cli --latency
redis-cli --latency-history
code复制slowlog-log-slower-than 10000 # 超过10ms记录
slowlog-max-len 128 # 保存128条
案例1:内存溢出
症状:OOM command not allowed when used memory > 'maxmemory'
解决方案:
redis-cli --bigkeys案例2:主从同步失败
排查步骤:
info replicationreplicaof no one + replicaof host port配置示例:
yaml复制spring:
redis:
host: 127.0.0.1
port: 6379
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
缓存注解使用:
java复制@Cacheable(value = "users", key = "#userId")
public User getUser(String userId) {
// DB查询
}
@CacheEvict(value = "users", key = "#user.id")
public void updateUser(User user) {
// 更新逻辑
}
Spring Session配置:
java复制@EnableRedisHttpSession
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
基于Redis Stream的消息队列:
java复制// 生产者
Map<String, String> message = new HashMap<>();
message.put("event", "orderCreated");
redisTemplate.opsForStream().add("orderEvents", message);
// 消费者
StreamMessageListenerContainer.StreamMessageListenerContainerOptions options =
StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()
.pollTimeout(Duration.ofSeconds(1))
.build();
StreamMessageListenerContainer listenerContainer = StreamMessageListenerContainer
.create(connectionFactory, options);