28个案例问题分析---01---redis没有及时更新问题--Redis

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 28个案例问题分析---01---redis没有及时更新问题--Redis

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 存在问题

这时候,问题就暴露出来了,缓存数据与数据库数据不一致。并且缓存的数据还未过期。导致过期前查询的数据都不准确。

所以在使用缓存的时候,如果数据库内数据改了,一定要及时的

清空缓存!!!!清空缓存!!!!清空缓存!!!!


四:总结

这个问题的出现的根本原因是。不同的开发人员没有做好沟通,这两个业务是不同的人员编写的,没有考虑的业务的相关性,导致了这种问题的出现!!

所以不仅仅要实现功能也要考虑业务相关性!!!


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
18天前
|
缓存 NoSQL Java
Redis7的10大应用场景和案例解析
你在项目中使用 Redis 实现了什么应用场景,欢迎一起跟 V 哥讨论。同时也做个小调查,朋多少兄弟是需要了解 Redis 核心源码的,人多的话,下一篇 V 哥写 Redis7的源码分析,人少的话就算了,感谢。
|
18天前
|
存储 监控 NoSQL
【Redis技术专区】「优化案例」谈谈使用Redis慢查询日志以及Redis慢查询分析指南
【Redis技术专区】「优化案例」谈谈使用Redis慢查询日志以及Redis慢查询分析指南
58 0
|
18天前
|
缓存 NoSQL 前端开发
【Redis技术专区】「实战案例」谈谈使用Redis缓存时高效的批量删除的几种方案
【Redis技术专区】「实战案例」谈谈使用Redis缓存时高效的批量删除的几种方案
52 0
|
18天前
|
NoSQL Java 数据库
优惠券秒杀案例 - CAS、Redis+Lua脚本解决高并发并行
优惠券秒杀案例 - CAS、Redis+Lua脚本解决高并发并行
117 0
|
18天前
|
存储 NoSQL 关系型数据库
Redis系列-8.Redis案例实战之Bitmap、Hyperloglog、GEO(下)
Redis系列-8.Redis案例实战之Bitmap、Hyperloglog、GEO
36 0
|
18天前
|
存储 NoSQL 算法
Redis系列-8.Redis案例实战之Bitmap、Hyperloglog、GEO(上)
Redis系列-8.Redis案例实战之Bitmap、Hyperloglog、GEO
55 0
|
11月前
|
存储 缓存 NoSQL
案例01-修改数据redis没有同步更新
修改数据redis没有同步更新
|
7月前
|
消息中间件 缓存 NoSQL
阿里云国际站代理商:Redis实现分布式配置管理的方法与应用案例
@luotuoemo飞机@TG阿里云国际站代理商:Redis实现分布式配置管理的方法与应用案例,为了实现高可用和负载均衡,可以将Redis部署成哨兵集群或集群模式。哨兵负责监控主从节点的状态,发现故障时自动进行故障转移。集群模式可以提高系统的可扩展性,通过添加更多的从节点来分摊负载压力。
|
8月前
24Redis - 事务测试案例
24Redis - 事务测试案例
36 0
|
10月前
|
NoSQL Redis 开发工具
redis搭建群集案例
redis搭建群集案例,共六个redis数据库