案例01-redis数据设置过期时间不当导致读取脏数据的问题

一、背景介绍

写本篇文章的目的是解决因为更改数据库数据后没有及时同步redis中的数据造成数据不一致的问题,引以为戒,要有闭环思维。
在这里插入图片描述

二、前期准备

此实例是一个普通的maven项目。使用前需要准备好mysql数据,redis。

最终展示效果如下:
在这里插入图片描述

pom文件

    <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工具类


public class RedisUtils {
    public static Jedis jedis ;
    static {
        jedis = new Jedis("ip地址",6379,10000);
        //没有设置密码可以不填
        jedis.auth("000415");
    }

  
    public  static Object redisGet(String key){
        return RedisUtils.jedis.get(key);
    }


    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


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 
     * @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 
     * @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缓存用户的年龄。
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: 
     * @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,会产生数据不一致的问题。

举例如下:

    public static void main(String[] args) {
        String sql = "update user_info set age = 66 where id = 1";
        updateUserInfo(sql);
    }

	    /**
     * @description: 数据库修改用户数据
     * @author: 
     * @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中的数据和数据库中的数据
在这里插入图片描述
数据库中对应的数据
在这里插入图片描述
数据库中的数据已经变成66了,但是redis中的数据还是30,还没有过期,导致过期前查询redis中的数据不准确,所以代码中有使用缓存查询数据的地方,一定要关注如果数据库中的数据已经改变了,一定要及时地清空缓存!!!!

四、总结

不同开发人员修改一张表的时候要做好沟通,防止在同一个时间段内发生数据变更的问题,像现在这种redis没有过期的问题,还有在做测试的时候如果对公共性的文件进行修改的时候要互相告知,防止出现数据错乱的问题。

使用Redis作为缓存或数据存储解决方案时,有一些要注意的问题:

数据持久化和内存消耗:

Redis默认将所有数据存储在内存中,因此需要谨慎处理大量数据。如果数据量较大,可以考虑使用Redis的持久化功能(RDB快照或AOF日志)来保证数据的持久性,同时注意配置合理的内存限制。

键的设计:

好的键设计是提高Redis性能的关键。选择简洁且具有描述性的键名,并尽量避免使用过长的键名,以减少内存消耗。
注意避免键名冲突,特别是在使用Redis作为全局缓存时,防止不同模块使用相同的键名。
数据过期策略:

设置适当的数据过期时间,以确保缓存中的数据不会永久存在,避免数据过期导致脏数据问题。
对于不同类型的数据,可以根据其重要性和使用频率设置不同的过期时间。

高并发问题:

Redis天生是单线程的,这意味着在高并发情况下可能会出现性能瓶颈。确保系统能够处理高并发请求,可以使用Redis集群或哨兵模式来提高可用性和性能。
数据序列化:

在将数据存入Redis之前,必须对数据进行序列化(例如JSON或二进制序列化)。选择合适的序列化方式对性能和存储空间有重要影响。

异常处理:

在使用Redis时,要考虑到可能发生的异常情况,如连接错误、超时等。合理处理这些异常,以保障系统的稳定性和可用性。

监控和性能优化:

定期监控Redis的性能指标和资源占用情况,确保系统正常运行。使用Redis的性能分析工具来定位性能问题,并优化Redis的配置。

安全性:

Redis默认没有开启身份验证,所以务必设置合适的密码保护Redis服务器,避免未授权访问。

综上所述,使用Redis要注意合理设计键、数据过期策略、高并发处理、数据持久化、异常处理和安全性等问题,以确保系统稳定、性能良好并减少潜在的问题。同时,定期监控和优化Redis服务器是保持Redis运行顺畅的关键。

设置合理的过期时间是使用Redis时需要注意的重要问题。过期时间决定了缓存数据的有效期,当数据过期后,Redis会自动删除这些数据,避免数据过时或脏数据的问题。

注意过期时间的重要性在于:

脏数据问题:如果缓存的数据过期时间设置过长,那么可能会导致获取到过时的数据,造成脏数据的问题。特别是对于频繁更新的数据,过期时间应该设置得较短,以确保数据的及时更新。

缓存击穿:如果设置过短的过期时间,可能会导致缓存击穿的问题。当某个热点数据过期后,大量的请求会直接打到后端数据库,造成数据库负载过大。为避免缓存击穿,可以考虑在数据更新时,重新设置较短的过期时间,以防止瞬间大量请求。

内存消耗:缓存数据存储在Redis的内存中,设置过长的过期时间会导致内存占用较高。需要权衡数据的重要性和内存资源的使用,避免内存占用过大。

针对不同类型的数据,可以根据其重要性和更新频率来设置合理的过期时间。例如,对于不常变化的静态数据,可以设置较长的过期时间,而对于热点数据,可以设置较短的过期时间。同时,可以使用Redis的LRU(最近最少使用)策略来回收较旧的数据,以保持内存使用的合理性。

总的来说,设置合理的过期时间是使用Redis时需要注意的重要问题。根据业务需求和数据特性,权衡数据的有效性、缓存击穿、内存占用等因素,合理设置过期时间,以确保缓存数据的有效性和Redis服务器的高效运行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Circ.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值