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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 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
目录
相关文章
|
4月前
|
NoSQL Linux Redis
linux安装单机版redis详细步骤,及python连接redis案例
这篇文章提供了在Linux系统中安装单机版Redis的详细步骤,并展示了如何配置Redis为systemctl启动,以及使用Python连接Redis进行数据操作的案例。
99 2
|
3月前
|
消息中间件 NoSQL Kafka
大数据-116 - Flink DataStream Sink 原理、概念、常见Sink类型 配置与使用 附带案例1:消费Kafka写到Redis
大数据-116 - Flink DataStream Sink 原理、概念、常见Sink类型 配置与使用 附带案例1:消费Kafka写到Redis
211 0
|
5月前
|
缓存 NoSQL 算法
【Azure Redis 缓存】Redis导出数据文件变小 / 在新的Redis复原后数据大小压缩近一倍问题分析
【Azure Redis 缓存】Redis导出数据文件变小 / 在新的Redis复原后数据大小压缩近一倍问题分析
|
7月前
|
JSON NoSQL Redis
|
7月前
|
NoSQL Redis
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
Redis系列学习文章分享---第五篇(Redis实战篇--优惠券秒杀,全局唯一id 添加优惠券 实现秒杀下单 库存超卖问题分析 乐观锁解决超卖 实现一人一单功能 集群下的线程并发安全问题)
147 0
|
8月前
|
缓存 NoSQL Java
Redis7的10大应用场景和案例解析
你在项目中使用 Redis 实现了什么应用场景,欢迎一起跟 V 哥讨论。同时也做个小调查,朋多少兄弟是需要了解 Redis 核心源码的,人多的话,下一篇 V 哥写 Redis7的源码分析,人少的话就算了,感谢。
147 0
|
8月前
|
存储 监控 NoSQL
【Redis技术专区】「优化案例」谈谈使用Redis慢查询日志以及Redis慢查询分析指南
【Redis技术专区】「优化案例」谈谈使用Redis慢查询日志以及Redis慢查询分析指南
182 0
|
8月前
|
缓存 NoSQL 前端开发
【Redis技术专区】「实战案例」谈谈使用Redis缓存时高效的批量删除的几种方案
【Redis技术专区】「实战案例」谈谈使用Redis缓存时高效的批量删除的几种方案
154 0
|
8月前
|
NoSQL Java 数据库
优惠券秒杀案例 - CAS、Redis+Lua脚本解决高并发并行
优惠券秒杀案例 - CAS、Redis+Lua脚本解决高并发并行
335 0
|
8月前
|
存储 NoSQL 关系型数据库
Redis系列-8.Redis案例实战之Bitmap、Hyperloglog、GEO(下)
Redis系列-8.Redis案例实战之Bitmap、Hyperloglog、GEO
90 0