Redis 基础¶
Redis 是一个基于内存的高性能键值存储数据库,常用于缓存、会话管理和消息队列。它的核心优势在于读写速度极快(单线程模型 + IO多路复用),数据存储在内存中,支持持久化到磁盘。
五大数据类型¶
Redis 提供 5 种基本数据结构,每种底层实现优化了空间和时间复杂度(例如 String 用 SDS 动态字符串,O(1) 获取长度)。
| 类型 | 描述 | 常见命令 | 示例场景 |
|---|---|---|---|
| String | 最简单类型,支持字符串/数字/二进制 | SET key value, GET key, INCR key | 缓存对象、计数器 |
| List | 双向链表,支持队列/栈 | LPUSH/RPUSH, LPOP/RPOP, LRANGE | 消息队列(如 RPUSH + LPOP) |
| Set | 无序集合,自动去重 | SADD key member, SMEMBERS key, SINTER | 标签系统、交集运算 |
| Hash | 键值对映射表,像 Map | HSET key field value, HGETALL key | 存储对象(如用户信息) |
| Zset | 有序集合,按分数排序 | ZADD key score member, ZRANGE | 排行榜、优先级队列 |
集成¶
Java 集成示例¶
在 Java 项目中使用 Jedis(轻量客户端)或 Spring Data Redis。Jedis 直连简单,但生产用连接池避免频繁创建连接。
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisExample {
private static final JedisPool pool = new JedisPool(new JedisPoolConfig(), "localhost", 6379);
public static void main(String[] args) {
try (Jedis jedis = pool.getResource()) {
// String 操作:缓存字符串
jedis.set("user:1:name", "Alice"); // SET 命令,O(1)
String name = jedis.get("user:1:name"); // GET,O(1)
System.out.println("Name: " + name); // 输出: Alice
// List 操作:简单队列
jedis.rpush("messages", "msg1", "msg2"); // 右推,队列尾部添加
String msg = jedis.lpop("messages"); // 左弹出,O(1),输出: msg1
System.out.println("Message: " + msg);
// Hash 操作:存储对象
jedis.hset("user:1", "age", "25"); // field-value,O(1)
String age = jedis.hget("user:1", "age");
System.out.println("Age: " + age); // 输出: 25
}
}
}
Spring Boot集成Redis¶
Spring Boot 通过 Spring Data Redis 轻松集成 Redis,支持 Lettuce 或 Jedis 客户端,默认使用 Lettuce(非阻塞)。这简化了连接管理、序列化和操作,生产中常用于缓存加速数据库查询。
集成步骤 1. 添加依赖:在 pom.xml 中引入 spring-boot-starter-data-redis(自动包含 Lettuce)。 2. 配置连接:在 application.yml 设置 Redis 地址、密码等。 3. 使用 RedisTemplate 操作数据,支持泛型键值。
为什么这样?Spring Boot 自动配置 LettuceConnectionFactory 和 RedisTemplate,避免手动管理连接池;默认序列化用 JDK,但建议自定义 JacksonSerializer 处理 JSON 对象。
配置文件示例
spring:
data:
redis:
host: localhost # Redis 主机
port: 6379 # 默认端口
password: # 可选密码
lettuce:
pool:
max-active: 8 # 连接池最大连接数,为什么?并发场景下复用连接提高性能
max-idle: 8
min-idle: 0
关键:连接池配置防止连接耗尽,Lettuce 支持读写分离(主从)。
Redis 配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 键序列化:字符串
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 值序列化:JSON(为什么?支持复杂对象,如 User POJO)
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
关键语句:Jackson2JsonRedisSerializer 确保对象自动 JSON 序列化,避免 JDK 默认序列化问题(如不可读字节)。
服务层使用示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void cacheUser(String key, Object user) {
redisTemplate.opsForValue().set(key, user, 60, TimeUnit.SECONDS); // 缓存 60s,为什么?防止脏数据,过期自动清理
}
public Object getUser(String key) {
return redisTemplate.opsForValue().get(key); // String 类型操作,O(1)
}
// Hash 示例:存储用户属性
public void hashUser(String hashKey, String field, Object value) {
redisTemplate.opsForHash().put(hashKey, field, value);
}
}
在 Controller 注入 UserService 调用。实际项目:结合 @Cacheable 注解简化,或用 Redisson 分布式锁。
问题¶
Redis 为什么这么快?¶
Redis 的高性能源于纯内存存储、单线程事件循环 + IO 多路复用、高效数据结构和简洁协议设计,这些机制让它单机轻松达到 10 万+ QPS。 + 纯内存操作 + Redis 将所有数据存于内存,读写速度达纳秒级,避免了磁盘 I/O(毫秒级)的瓶颈。传统数据库如 MySQL 需频繁访问磁盘,而 Redis 直接内存操作,相差数个数量级。 + 为什么快?内存访问无需寻道/旋转延迟,结合非阻塞 IO,命令执行时间极短(主要耗时在网络)。 + 单线程 + IO 多路复用 + Redis 主线程单线程执行命令,避免多线程上下文切换(时间开销 ~1μs/次)和锁竞争。使用 epoll 等 IO 多路复用(Reactor 模式),单线程监听多个 socket,只处理就绪事件(如 accept/read/write)。 + 为什么高效?文件事件处理器将 socket 事件分发给处理器,一个线程并发处理万级连接,无阻塞、无额外线程开销(对比多线程模型的 CPU 浪费)。 + 优化数据结构 + 支持 String/List/Set/Hash/ZSet 等,底层用 ziplist/quicklist/skiplist 等紧凑编码,动态选择节省空间并 O(1)/O(log N) 操作。 + 为什么设计如此?平衡性能与内存,如小集合用 ziplist 避免指针开销,大数据自动转 hashtable。 + 简洁 RESP 协议 + RESP(REdis Serialization Protocol)二进制安全、解析快,客户端/服务端序列化开销小,提升交互速度。
Redis 和 Memcached 的区别和共同点¶
Redis 和 Memcached 都是高性能内存缓存系统,用于加速数据访问,但 Redis 功能更丰富,Memcached 更专注简单键值存储。 共同点
- 内存存储:两者数据驻留在内存,读写速度纳秒级,远超磁盘数据库,适合缓存热点数据。
- 高性能:QPS 可达 10 万+,支持过期策略(TTL),用于会话、页面缓存等场景。
- 分布式支持:均可水平扩展,但需客户端分片(Memcached)或原生集群(Redis)。
为什么共同?内存操作避免 I/O 瓶颈,缓存命中率高时显著降低后端压力。
区别对比
| 维度 | Redis | Memcached |
|---|---|---|
| 数据类型 | 丰富(String/List/Set/Hash/ZSet 等)javaguide | 仅简单 K/V(字符串)javaguide |
| 持久化 | 支持 RDB/AOF,重启恢复数据javaguide | 无,重启丢失全部数据javaguide |
| 集群 | 原生支持(3.0+ 哈希槽)javaguide | 客户端分片(如一致性哈希)javaguide |
| 线程模型 | 单线程 + IO 多路复用(6.0+ 读写多线程)javaguide | 多线程,非阻塞 IOjavaguide |
| 额外特性 | 发布订阅、Lua 脚本、事务、Geo 等javaguide | 无,仅基础 Get/Set/CASjavaguide |
| 内存效率 | 复杂对象高效(如 Hash ziplist),支持淘汰策略 | 简单 K/V 更省内存,大对象优于 Rediscnblogs |
| 协议 | RESP(二进制安全)javaguide | 文本协议csdn |
为什么这些区别? Redis 设计为 NoSQL 数据库,支持复杂场景(如排行榜用 ZSet);Memcached 专注纯缓存,轻量但功能单一。Java 项目中,Spring Data Redis 集成更成熟,推荐 Redis 作为默认选择。
缓存读写策略详解