redis没有及时更新问题
- 一:背景介绍
- 二:前期准备
- pom依赖
- 连接Redis工具类
- 连接mysql工具类
- 三:过程
- 使用redis缓存,缓存用户年龄
- 业务对应流程图
- 使用redis缓存用户年龄对应代码
- 四:总结
一:背景介绍
业务中使用redis做缓存,来减少对数据库的操作,提升速度。使用的过程是
去redis内查询数据。如果有的话直接返回redis内的数据,如果没有的话去数据库查询数据,并将其存储到redis中,那么下次的查询就是在redis内查询的,省去了我们多次查询数据库的操作。
但是我们这个业务里,redis内存的数据可能与数据库的不一致。导致这种现象的原因是。在另外的业务中更改了数据库内的数据后,没有去修改redis内的数据,于是造成了redis数据与数据库数据不一致的问题。下面我就用一个小例子,演示上述问题,以及解决方案
二:前期准备
此实例是一个普通的maven项目。使用前需要准备好mysql数据,redis。具体方式大家可以上网查询。
pom依赖
项目连接mysql和redis,需要在maven内引入以下两个依赖.
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>
连接Redis工具类
编写一个连接Redis的工具类,这里直接使用的Jedis进行的连接。
/**
* @BelongsProject: redis
* @BelongsPackage: org.example.utils
* @Author:hlzs1
* @Description: redis的工具类
* @CreateTime: 2023-03-03 20:42
* @Version: 1.0
*/
public class RedisUtils {
public static Jedis jedis ;
static {
jedis = new Jedis("ip地址",6379,10000);
//没有设置密码可以不填
jedis.auth("000415");
}
/**
* @description: 在redis取出数据
* @author: haolizhuo
* @date: 2023/3/4
* @param: [key]
* @return: java.lang.Object
**/
public static Object redisGet(String key){
return RedisUtils.jedis.get(key);
}
/**
* @description:向redis存储数据
* @author: haolizhuo
* @date: 2023/3/4
* @param: [key, value, seconds]
* @return: void
**/
public static void redisSet(String key, String value, @Nullable Integer seconds){
RedisUtils.jedis.set(key,value);
if(seconds !=null){
RedisUtils.jedis.expire(key,seconds);
}
}
}
连接mysql工具类
连接mysql需要对应的配置文件,新建一个jdbc.properties文件将此文件放到resources目录下即可
userName=root
password=deng123~
url=jdbc:mysql://ip地址/数据库名?useSSL=false&useUnicode=true&characterEncoding=utf8
driverClass=com.mysql.cj.jdbc.Driver
连接数据库的工具类
/**
* @author : [haolizhuo]
* @version : [v1.0]
* @className : JDBCUtils
* @description : [描述说明该类的功能]
* @createTime : [2022/11/30 11:24]
* @updateUser : [haolizhuo]
* @updateTime : [2022/11/30 11:24]
* @updateRemark : [描述说明本次修改内容]
*/
public class JDBCUtils {
private static String url;
private static String userName;
private static String password;
private static String driverClass;
private static Connection connection;
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(inputStream);
driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
userName = properties.getProperty("userName");
password = properties.getProperty("password");
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* @version V1.0
* Title: getConnection
* @author haolizhuo
* @description 获取数据库连接
* @createTime 2022/11/30 11:36
* @param []
* @return java.sql.Connection
*/
public static Connection getConnection() {
try {
connection = DriverManager.getConnection(url, userName, password);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
/*
* @version V1.0
* Title: close
* @author haolizhuo
* @description 释放资源
* @createTime 2022/11/30 11:39
* @param [connection, statement, resultSet]
* @return void
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
项目结构
到这里我们真个环境就准备好了,下面我们就复现背景里提到的,使用缓存的情况
三:过程
使用redis缓存,缓存用户年龄
这里是一个普通的使用redis的场景,我使用redis缓存了用户的年龄。key值是用户的id,value值是用户的年龄。
业务对应流程图
使用redis缓存用户年龄对应代码
public static void main(String[] args) {
//使用redis缓存,缓存某个id的用户的年龄,这里以1做示例
String userId = "1";
Object redisUserInfo = RedisUtils.redisGet(userId);
//如果缓存查到信息,直接返回,并且结束
if(null != redisUserInfo){
System.out.println("redis获取到了用户"+userId+"的年龄,年龄为:" + redisUserInfo);
return;
}
//缓存里没有就去数据库进行查询
String sql ="select * from user_info where id = " + userId;
Map<String, Object> userInfo = getUserInfo(sql);
//如果查询到了信息,更新到缓存内
if(userInfo != null){
System.out.println("mysql获取到了用户"+userId+"的年龄,年龄为:" + userInfo.get("age"));
RedisUtils.redisSet(userInfo.get("id").toString(),userInfo.get("age").toString(),60);
}
}
/**
* @description: 数据库查询用户数据
* @author: haolizhuo
* @date: 2023/3/4
* @param: [sql]
* @return: java.util.Map<java.lang.String,java.lang.Object>
**/
private static Map<String, Object> getUserInfo(String sql){
Connection connection;
Statement statement;
ResultSet resultSet;
Map<String , Object> map = new HashMap<>(16);
try {
//创建数据库连接
connection = JDBCUtils.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
//将值取出
while (resultSet.next()){
map.put("id",resultSet.getInt("id"));
map.put("name",resultSet.getString("name"));
map.put("age",resultSet.getInt("age"));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return map;
}
这部分代码是没有问题,通过redis做了对应缓存。如果缓存过期了之后,就去数据库内查询,并且更新到缓存中。但是问题是,假设我们有别的业务更新了我们缓存的age数据,同时redis的数据没有过期,这是后就会造成一个数据不一致的问题。
执行代码,控制台打印
查看redis内数据
查看数据库内数据
现在我们的数据是正确无误,缓存与数据库数据一致。可如果我们的业务更新这个age的话,就会产生不一致的问题
public static void main(String[] args) {
String sql = "update user_info set age = 66 where id = 1";
updateUserInfo(sql);
}
/**
* @description: 数据库修改用户数据
* @author: haolizhuo
* @date: 2023/3/4
* @param: [sql]
* @return: void
**/
private static void updateUserInfo(String sql){
Connection connection;
Statement statement;
int resultSet;
try {
//创建数据库连接
connection = JDBCUtils.getConnection();
statement = connection.createStatement();
resultSet = statement.executeUpdate(sql);
System.out.println(resultSet);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
更新完之后redis内数据
更新完之后数据库内数据
这时候,问题就暴露出来了,缓存数据与数据库数据不一致。并且缓存的数据还未过期。导致过期前查询的数据都不准确。
所以在使用缓存的时候,如果数据库内数据改了,一定要及时的
清空缓存!!!!清空缓存!!!!清空缓存!!!!
四:总结
这个问题的出现的根本原因是。不同的开发人员没有做好沟通,这两个业务是不同的人员编写的,没有考虑的业务的相关性,导致了这种问题的出现!!
所以不仅仅要实现功能也要考虑业务相关性!!!