为什么你需要Redis?

别被那些花里胡哨的理论吓到,换个角度看Redis就是一个跑在内存里的数据库,读写速度飞起,拿来当缓存再合适不过。你问我为什么要用它?举个例子:你家网站首页有个热门排行榜,每次用户访问都要去MySQL里查半天,并发一上来直接卡成PPT。这时候Redis往中间一插,把结果缓存起来,下次直接内存命中,响应时间从几百毫秒降到几微秒,用户体验瞬间起飞。

当然,Redis不光是缓存,还能做分布式锁、消息队列、计数器等等,但咱今天的目标就是——让你能上手就用,不翻车

前置准备

安装Redis

别怕,安装这事我踩过坑。Windows用户直接去 [Redis官网](https://redis.io/download) 下载Windows版本或者用WSL。Mac用户 brew install redis 搞定。Linux用户 apt install redis-serveryum install redis

装完后启动:

redis-server

如果看到那个经典的ASCII艺术字图,恭喜你,跑起来了。默认端口6379,别乱改。

客户端工具

推荐安装 redis-cliredis-desktop-manager(跨平台可视化工具)。当然你也可以用IDE插件。

基本操作:从Hello World开始

打开终端,进入redis-cli:

redis-cli

试试最基础的四件套:

SET name "Redis实战" GET name DEL name EXISTS name

输出分别会是:OK、"Redis实战"、1、0。完事。

但光会这个不够,缓存要设置过期时间,不然内存迟早炸。

SET cache_key "some_value" EX 60 # 60秒后自动删除 TTL cache_key # 查看剩余秒数,-1表示永不过期,-2表示已过期

关键点:别傻傻地不设过期时间,除非你的缓存永远不会失效。我见过生产环境里有人用SET不加任何参数,结果Redis内存涨到90%,然后OOM掉,运维背锅。

数据结构实战

Redis有五种基本数据结构,但最常用的是String和Hash。

String:最简单的缓存

适合存单个值的场景,比如用户Token、验证码。

SET token:123456 "abc123" EX 7200 GET token:123456

Hash:存对象

比如用户信息:

HSET user:1001 name "张三" age 28 city "北京" HGET user:1001 name HGETALL user:1001

Hash的好处是你不用序列化整个对象,修改单个字段时开销小。

List / Set / Sorted Set

这些在缓存里用得少,但排行榜得用Sorted Set:

ZADD ranking 100 "用户A" 200 "用户B" ZREVRANGE ranking 0 2 WITHSCORES # 前两名

实战:Java + Redis 缓存用户数据

好了实操来了。我们要写一个完整的Spring Boot应用,用Redis缓存用户查询结果。

项目依赖(Maven)

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> </dependencies>

application.yml 配置

spring: redis: host: localhost port: 6379 password: # 没设就空着 lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0

核心代码:UserService

创建一个User实体:

import java.io.Serializable; public class User implements Serializable { private Long id; private String name; private Integer age; // 无参构造、全参构造、getter/setter 略(IDE生成) // 注意:必须实现Serializable,否则Redis序列化会报错 }

Service层:

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 User getUserFromDB(Long id) { // 实际项目里这里是调用Mapper System.out.println("查询数据库,id=" + id); return new User(id, "张三", 28); } public User getUser(Long id) { String key = "user:" + id; // 先查缓存 User cached = (User) redisTemplate.opsForValue().get(key); if (cached != null) { System.out.println("命中缓存"); return cached; } // 缓存没命中,查数据库 User dbUser = getUserFromDB(id); // 存入缓存,过期时间5分钟 redisTemplate.opsForValue().set(key, dbUser, 5, TimeUnit.MINUTES); return dbUser; } public void updateUser(User user) { // 更新数据库(模拟) System.out.println("更新数据库,id=" + user.getId()); // 删除缓存,保持一致性 String key = "user:" + user.getId(); redisTemplate.delete(key); } }

配置Redis序列化

Spring Boot默认使用JDK序列化,存进Redis的是乱码,而且不可读。建议改成JSON:

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.GenericJackson2JsonRedisSerializer; 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); // key用字符串序列化 template.setKeySerializer(new StringRedisSerializer()); // value用JSON序列化,支持对象 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // Hash的key也设为字符串 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } }

控制器调用

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userService.getUser(id); } @PostMapping public String updateUser(@RequestBody User user) { userService.updateUser(user); return "ok"; } }

启动应用,访问 http://localhost:8080/user/1,第一次会查数据库,第二次直接走缓存,观察控制台输出你就能看到效果。

常见错误及解决方法

错误1:`java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class xxx.User`

场景:从Redis读取缓存时,反序列化回来的Object实际上是LinkedHashMap,无法强转为User。

原因:默认的Jackson序列化在存储时丢失了类型信息。

解决:在RedisConfig中使用 GenericJackson2JsonRedisSerializer 时,需要在RedisTemplate的ValueSerializer上启用类型信息。或者更简单的方式:不要在 opsForValue().get() 后强转,而是用JSON工具手动转换。但推荐在序列化器上加上 ObjectMapper 启用默认类型:

GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(); // 或者在ObjectMapper中设置 ObjectMapper om = new ObjectMapper(); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(om);

错误2:`Redis connection refused` 或 `connect: Cannot assign requested address`

场景:应用启动就报连不上Redis,或者高并发下偶尔连不上。

原因:最常见的是Redis服务没启动,或者端口被防火墙拦截。另一个是连接池耗尽,Linux默认临时端口不够。

解决

实战经验心得

缓存穿透、击穿、雪崩

这三个词面试常考,实战也经常遇到。

实战经验实例:有次我负责的活动页面,上线后Redis里所有的活动配置key都设了同一时间过期,结果半夜零点所有缓存集体失效,数据库被打挂了。从那以后我养成了给过期时间加随机抖动的习惯。

Big Key问题

千万别把一个List塞几万条数据当缓存,读写时Redis会单线程阻塞。经验之谈:我用Hash存用户购物车,有个用户商品加了2000件,结果每次获取购物车都耗时几百毫秒。后来拆成多个小key。

序列化坑

前面说了,Redis存储对象必须序列化。如果你的对象有嵌套对象或者循环引用,Jackson序列化会报错。建议:只缓存简单数据结构,或者用DTO降级。

最佳实践建议

总结(其实没有总结)

Redis入门不难,难的是面对复杂业务时做出合理的设计。很多新手一上来就想着用Redis解决所有性能问题,结果反而搞出了更复杂的缓存一致性问题。我的建议是:先跑通上面的Java例子,然后去你自己的项目里找一个简单的查询接口,加上缓存试试。遇到问题别慌,大概率是我上面列的那几个常见错误之一。 等你真正踩过一两个坑,Redis就算入门了。

好了,该说的都说完了。打开IDEA,开始写代码吧。

常见错误总结:缓存雪崩(大量key同时过期)、缓存穿透(查询不存在的数据)、缓存击穿(热点key突然失效)。这三个问题几乎涵盖80%的缓存故障,建议每个人都理解并掌握解决方案。