redis没有及时更新问题
四:总结
一:背景介绍
业务中使用redis做缓存,来减少对数据库的操作,提升速度。
使用的过程是去redis内查询数据。如果有的话直接返回redis内的数据,如果没有的话去数据库查询数据,并将其存储到redis中,那么下次的查询就是在redis内查询的,省去了我们多次查询数据库的操作。
但是我们这个业务里,redis内存的数据可能与数据库的不一致。导致这种现象的原因是。在另外的业务中更改了数据库内的数据后,没有去修改redis内的数据,于是造成了redis数据与数据库数据不一致的问题。
下面我就用一个小例子,演示上述问题,以及解决方案
二:前期准备
此实例是一个普通的maven项目。使用前需要准备好mysql数据库,redis。具体安装的方式大家可以自行上网查询,这里不做介绍。
2.1 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>
2.1 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>
2.2 连接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); } } }
2.3 连接mysql工具类
连接mysql需要对应的配置文件,新建一个jdbc.properties文件将此文件放到resources目录下即可
2.3.1 配置文件
userName=root password=deng123~ url=jdbc:mysql://ip地址/数据库名?useSSL=false&useUnicode=true&characterEncoding=utf8 driverClass=com.mysql.cj.jdbc.Driver
2.3.2 连接mysql工具类
连接数据库的工具类
/** * @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(); } } }
2.3.3 项目结构
到这里我们真个环境就准备好了,下面我们就复现背景里提到的,使用缓存的情况
三:过程
3.1 使用redis缓存,缓存用户年龄
这里是一个普通的使用redis的场景,我使用redis缓存了用户的年龄。key值是用户的id,value值是用户的年龄。
3.1.2 业务对应流程图
3.1.3 使用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的数据没有过期,这是后就会造成一个数据不一致的问题。
3.2 执行代码,控制台打印
3.3 查看redis内数据3.4 查看数据库内数据
现在我们的数据是正确无误,缓存与数据库数据一致。可如果我们的业务更新这个age的话,就会产生不一致的问题
3.5 更新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); } }
3.6 更新完之后redis内数据3.7 更新完之后数据库内数据3.8 存在问题
这时候,问题就暴露出来了,缓存数据与数据库数据不一致。并且缓存的数据还未过期。导致过期前查询的数据都不准确。
所以在使用缓存的时候,如果数据库内数据改了,一定要及时的
清空缓存!!!!清空缓存!!!!清空缓存!!!!
四:总结
这个问题的出现的根本原因是。不同的开发人员没有做好沟通,这两个业务是不同的人员编写的,没有考虑的业务的相关性,导致了这种问题的出现!!
所以不仅仅要实现功能也要考虑业务相关性!!!