mysql和redis的同步

mysql和redis的同步

1 为什么使用redis做缓存

  1. 数据库链接慢,磁盘IO慢

  2. mysql线程数不够

  3. mybatis缓存,缓存在JVM中

  4. redis缓存时基于内存的,速度快

若使用redis,如何保证redis和数据库同步问题

  • 配置redis

      redis:
        url: redis://iampw@192.168.198.129:6379
    

2 方案一:基于SpringCache注解

  • @cacheable用于查询数据时,先从缓存中查找数据,如果缓存中有数据,则直接返回缓存中的数据,不会再去查询数据库;如果缓存中没有数据,则去数据库中查询数据,并将查询结果存入缓存中。

  • @cacheput用于更新数据时,先更新数据库中的数据,然后再将更新后的数据存入缓存中,以便下次查询时可以直接从缓存中获取更新后的数据,提高查询效率。

 @Override
 @Cacheable(cacheNames = "find",key = "0")
public JSONResult find() {
     log.info("数据库启动了...");
     List<Parking> list =list();
     //将查询到的小区名称赋给车位表
     list.forEach(parking-> {
         Integer community_id = parking.getCommunity_id();
         String name = communityFegin.findNameById(community_id).getData().toString();
         System.out.println("----------"+name);
         parking.setCommunity_name(name);
     });
     return new JSONResult(true,null,200,list);
 }
 
 @Override
 @CachePut(cacheNames = "find" ,key = "0")
 public JSONResult delete(int id){
         removeById(id);
         return find();
 }

3 方案二: 利用"栈堆"思想管理缓存

将索引存放在一个地方(list,zset),具体对象存放在一个地方(string)

  • 具体思路

    判断缓存中是否存在数据

    • 如果不存在数据
      • 从数据库中查询数据,并分别将索引具体对象放到缓存中
    • 如果存在数据
      • 根据索引获取对应具体对象
        • 如果对应对象过期了,根据对应索引查询该条对象重新存放到redis中
        • 如果对应对象没过期,直接从redis中查询该条数据
    @Slf4j
    @Service
    public class ParkingServiceImpl extends ServiceImpl<ParkingMapper, Parking>
            implements ParkingService {
        @Resource
        private CommunityFegin communityFegin;
        @Resource
        private StringRedisTemplate stringRedisTemplate;
        //所有索引的前缀
        private static final String ALL_PARKING_KEY = "ALL_PARKING_KEY";
        //每个索引的前缀
        private static final String PREFIX_PARKING = "PREFIX_PARKING::";
    
        @Override
        public JSONResult find() {
            //索引存放到list中
             ListOperations<String, String> keysOps = stringRedisTemplate.opsForList();
            //具体对象存放到string中
             ValueOperations<String, String> valuesOps = stringRedisTemplate.opsForValue();
    
            //遍历索引的集合
            List<String> keys = keysOps.range(ALL_PARKING_KEY, 0, -1);
            //1.如果redis中没有数据
            if (keys == null || keys.size() == 0) {
                log.info("数据库启动了...");
                //1.1从数据库中获取数据
                List<Parking> list = list();
                //将查询到的小区名称赋给车位表
                list.forEach(parking -> {
                    //1.2 将索引存放到list中
                    keysOps.leftPush(ALL_PARKING_KEY, PREFIX_PARKING + parking.getId());
                    //1.3 将具体对象存放到string中,并设置过期时间
                    valuesOps.set(PREFIX_PARKING + parking.getId(), JSONArray.toJSONString(parking), 1, TimeUnit.DAYS);
                });
                return new JSONResult<>(true, null, 200, list);
            }
            //2.如果redis中有数据
            List<Parking> parkings = new ArrayList<>();
            keys.forEach(key -> {
                //2.1 根据索引获得索引对应的对象
                String value = valuesOps.get(key);
                //2.1.1 对应对象为空的话
                if (value == null) {
                    //单独查询该对象并放到redis中
                    //  获取key对应的id,并转为int型
                    int id = Integer.parseInt(key.replace(PREFIX_PARKING, ""));
                    //根据id查询车位
                    Parking parking = baseMapper.selectById(id);
                    //根据小区id查询小区
                    Integer community_id = parking.getCommunity_id();
                    String name = communityFegin.findNameById(community_id).getData().toString();
                    parking.setCommunity_name(name);
                    parkings.add(parking);
                    //存放到redis中
                    valuesOps.set(PREFIX_PARKING + parking.getId(), JSONArray.toJSONString(parking), 1, TimeUnit.DAYS);
                } else {//2.1.2对应对象不为空的话
                    //直接查询
                    Parking parking = JSONArray.parseObject(value, Parking.class);
                    parkings.add(parking);
                }
            });
            return new JSONResult<>(true, null, 200, parkings);
        }
    
        @Override
        public JSONResult delete(int id) {
            //索引存放到list中
            ListOperations<String, String> keysOps = stringRedisTemplate.opsForList();
            //具体对象存放到string中
            ValueOperations<String, String> valuesOps = stringRedisTemplate.opsForValue();
            //数据库如果有数据从数据库删除
            boolean flag = removeById(id);
            //从redis中删除
            if (flag) {
                // 更新redis
                keysOps.remove(ALL_PARKING_KEY, 1, PREFIX_PARKING + id);
                valuesOps.getAndDelete(PREFIX_PARKING + id);
            }
            return new JSONResult<>(true, null, 200, "删除成功!");
        }
    }
    

4 根据方案二进行升级-查询分页操作

添加以下代码:

PageHelper.startPage(page, 3);
  • 此时出现问题1

问题1:当分页查询时,redis会缓存首次查询的数据,此时出现问题:无论查询第几页都是最先开始缓存到redis中的那一页,该如何解决?

答: 此问题是因为判断缓存中数据时,缓存中的数据为所有页数据;

  • 解决:判断时,判断查询时的数据为目标页数据即可
    	//本页前面页的数据
        int begin =(page-1)*3;
        //本页数据
        int end =page*3;
        //遍历索引的集合
        List<String> keys = keysOps.range(ALL_PARKING_KEY, begin, end);
		if (keys == null || keys.size() == 0) 
  • 此时出现问题2

问题2: 如果当第一次查询的数据从第二页开始,redis会缓存第二页数据为第一页,此时该如何解决?

初始化一个跟记录总数一样大小的数组,设置对应的值都null,查询某一页时,看该页对应记录数redis的值是否null,如果为null,从数据库查询,并替换为数据对应Id…

  • 具体思路

    • 如果是第一次查询
      • 进行初始化数组操作,只在第一次查询时执行
      • 将初次查询的数据,填充id和具体对象到具体位置
    • 不是第一次查询
      • 判断该页对应的值是否有null
        • 如果有null:连接数据库填充数据
        • 如果没有null:直接查询数据即可

如果是第一次查询

 //判断是否为第一次查询
 if (keyList == null || keyList.size() == 0) {
     //分页查询
     PageHelper.startPage(page, 3);
     List<Parking> parkings = list();
     PageInfo<Parking> parkingPageInfo = new PageInfo<>(parkings);
     //总条数
     long total = parkingPageInfo.getTotal();
     //填充
     for (int i = 0; i < total; i++) {
         keysOps.leftPush(ALL_PARKING_KEY, "null");
     }
     //将初次查询的数据,填充进具体对象中
     int begin = (page - 1) * 3;
     for (Parking parking: parkings){
         //将查询到的小区名称赋给车位表
         Integer community_id = parking.getCommunity_id();
         String name = communityFegin.findNameById(community_id).getData().toString();
         parking.setCommunity_name(name);
         //改变对应id
         keysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());
         //填充value
         valuesOps.set(PREFIX_PARKING+parking.getId(),JSONArray.toJSONString(parking),1,TimeUnit.DAYS );
         begin++;
     }
     return new JSONResult<>(true, null, 200, parkings);
  

如果不是第一次查询

  • 该页有对应值为null
//不是第一次查询的话,定位到此次查询的位置
int begin = (page-1)*3;
List<String> keyList2 = keysOps.range(ALL_PARKING_KEY, begin, begin + 2);
//查询对应的数据是否为null
for (String s : keyList2) {
    if ("null".equals(s)) {
        //连接数据库填充数据
        log.info("数据库启动了...");
        //1.1从数据库中获取数据
        PageHelper.startPage(page, 3);
        List<Parking> parkings = list();
        for (Parking parking: parkings){
            //将查询到的小区名称赋给车位表
            Integer community_id = parking.getCommunity_id();
            String name = communityFegin.findNameById(community_id).getData().toString();
            parking.setCommunity_name(name);
            //改变对应id
            keysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());
            //填充value
           valuesOps.set(PREFIX_PARKING+parking.getId(),JSONArray.toJSONString(parking),1,TimeUnit.DAYS );
            begin++;
        
        return new JSONResult<>(true, null, 200, parkings);
    }
}
  • 该页所有值都不为null
List<Parking> parkings = new ArrayList<>();
keyList2.forEach(key -> {
    //2.1 根据索引获得索引对应的对象
    String value = valuesOps.get(key);
    //2.1.1 对应对象为空的话
    if (value == null) {
        //  单独查询该对象并放到redis中
        //  获取key对应的id,并转为int型
        int id = Integer.parseInt(key.replace(PREFIX_PARKING, ""));
        //根据id查询车位
        Parking parking = baseMapper.selectById(id);
        //根据小区id查询小区
        Integer community_id = parking.getCommunity_id();
        String name = communityFegin.findNameById(community_id).getData().toString();
        parking.setCommunity_name(name);
        parkings.add(parking);
        //存放到redis中
        valuesOps.set(PREFIX_PARKING + parking.getId(), JSONArray.toJSONString(parking), 1, TimeUnit.DAYS);
    } else {//2.1.2对应对象不为空的话
        //直接查询
        Parking parking = JSONArray.parseObject(value, Parking.class);
        parkings.add(parking);
    }
});
return new JSONResult<>(true, null, 200, parkings);

总代码

    public JSONResult find(int page) {
        //索引存放到list中
        ListOperations<String, String> keysOps = stringRedisTemplate.opsForList();
        //具体对象存放到string中
        ValueOperations<String, String> valuesOps = stringRedisTemplate.opsForValue();
        //遍历索引
        List<String> keyList = keysOps.range(ALL_PARKING_KEY, 0, -1);
        //判断是否为第一次查询
        if (keyList == null || keyList.size() == 0) {
            PageHelper.startPage(page, 3);
            List<Parking> parkings = list();
            PageInfo<Parking> parkingPageInfo = new PageInfo<>(parkings);
            //总条数
            long total = parkingPageInfo.getTotal();
            //填充
            for (int i = 0; i < total; i++) {
                keysOps.leftPush(ALL_PARKING_KEY, "null");
            }
            //将初次查询的数据,填充进具体对象中
            int begin = (page - 1) * 3;
            for (Parking parking: parkings){
                //将查询到的小区名称赋给车位表
                Integer community_id = parking.getCommunity_id();
                String name = communityFegin.findNameById(community_id).getData().toString();
                parking.setCommunity_name(name);
                //改变对应id
                keysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());
                //填充value
                valuesOps.set(PREFIX_PARKING+parking.getId(),JSONArray.toJSONString(parking),1,TimeUnit.DAYS );
                begin++;
            }
            return new JSONResult<>(true, null, 200, parkings);
        }
        //不是第一次查询的话,定位到此次查询的位置
        int begin = (page-1)*3;
        List<String> keyList2 = keysOps.range(ALL_PARKING_KEY, begin, begin + 2);
        //查询对应的数据是否为null
        for (String s : keyList2) {
            if ("null".equals(s)) {
                //连接数据库填充数据
                log.info("数据库启动了...");
                //1.1从数据库中获取数据
                PageHelper.startPage(page, 3);
                List<Parking> parkings = list();
                for (Parking parking: parkings){
                    //将查询到的小区名称赋给车位表
                    Integer community_id = parking.getCommunity_id();
                    String name = communityFegin.findNameById(community_id).getData().toString();
                    parking.setCommunity_name(name);
                    //改变对应id
                    keysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());
                    //填充value
                    valuesOps.set(PREFIX_PARKING+parking.getId(),JSONArray.toJSONString(parking),1,TimeUnit.DAYS );
                    begin++;
                }
                return new JSONResult<>(true, null, 200, parkings);
            }
        }
        List<Parking> parkings = new ArrayList<>();
        keyList2.forEach(key -> {
            //2.1 根据索引获得索引对应的对象
            String value = valuesOps.get(key);
            //2.1.1 对应对象为空的话
            if (value == null) {
                //单独查询该对象并放到redis中
                //  获取key对应的id,并转为int型
                int id = Integer.parseInt(key.replace(PREFIX_PARKING, ""));
                //根据id查询车位
                Parking parking = baseMapper.selectById(id);
                //根据小区id查询小区
                Integer community_id = parking.getCommunity_id();
                String name = communityFegin.findNameById(community_id).getData().toString();
                parking.setCommunity_name(name);
                parkings.add(parking);
                //存放到redis中
                valuesOps.set(PREFIX_PARKING + parking.getId(), JSONArray.toJSONString(parking), 1, TimeUnit.DAYS);
            } else {//2.1.2对应对象不为空的话
                //直接查询
                Parking parking = JSONArray.parseObject(value, Parking.class);
                parkings.add(parking);
            }
        });
        return new JSONResult<>(true, null, 200, parkings);
    }
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: Canal是一个开源的数据库增量订阅&消费组件,可以实现MySQLRedis同步。它可以通过解析MySQL的binlog日志,将数据变更事件转换成Java对象,然后通过消息队列的方式异步地传递给消费端,消费端可以根据自己的需求进行处理,比如将数据同步Redis中。Canal支持多种消息队列,包括Kafka、RocketMQ、RabbitMQ等,也可以自定义消息处理器。 ### 回答2: Canal是阿里巴巴开源的一个基于数据库增量日志解析的数据同步工具,通过解析MySQL的binlog日志实现MySQLRedis的数据同步。 Canal的工作原理如下: 首先,Canal会建立一个轻量级的MySQL实例来监听MySQL的binlog日志,然后解析这些日志文件生成一个逻辑日志,将这些逻辑日志发送给指定的消费端,例如Redis等。 其次,Canal将这些日志文件解析成数据格式,然后将这些格式化的数据推送到指定的消费端。在Redis端,我们可以通过Canal提供的接口来订阅这些数据并将其存储到Redis中。 最后,Canal还支持针对不同表的数据同步,可以通过配置不同的过滤规则来实现不同表的数据同步。 Canal的优点在于: 1. 数据同步实时性高:由于Canal是基于数据增量日志解析的,所以同步数据的实时性非常高,可以达到毫秒级别的数据同步。 2. 可扩展性强:Canal支持横向扩展和纵向扩展,可以随时根据系统负载情况进行扩展。 3. 可配置性强:Canal支持灵活的配置规则,可以根据不同的业务需求进行配置,满足不同的数据同步需求。 总体来说,Canal是一个非常实用的数据同步工具,可以帮助我们解决MySQLRedis之间的数据同步问题,同时还支持多种数据源的同步,具有很高的可扩展性和可配置性,是一个非常好用的开源工具。 ### 回答3: Canal是阿里巴巴中间件团队开发的一款基于数据库增量日志解析同步工具,支持MySQL、Oracle、SqlServer等多种关系型数据库。Canal通过订阅MySQL的binlog实现对MySQL数据的实时同步,同时配合redis实现数据的高速缓存,提高数据访问速度和可靠性。 首先,在Canal的配置文件中,需要设置对MySQL binlog的订阅,包括MySQL的地址、用户名、密码和需要订阅的数据库和表。Canal会根据这些订阅信息,实时监听MySQL数据库的变化,并将变化的数据转化为JSON格式的数据。 其次,配置Canal与redis的连接信息,包括redis的地址和端口号等信息。Canal通过redis的发布和订阅机制,将解析出的JSON格式的数据发布到redis缓存中。 最后,应用程序可根据自己的需求从redis缓存中获取数据,从而实现MySQLredis同步。相比于直接访问MySQL数据库,Canal和redis的方式可以大大提高数据的访问速度和可靠性,尤其是对于一些关键业务数据的访问,更加有利于保障数据的安全性和完整性。 总之,Canal和redis的组合非常适合需要实时同步MySQL数据并提高访问速度和可靠性的应用场景,具有很好的性能和稳定性,并且易于配置和集成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@活着笑

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

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

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

打赏作者

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

抵扣说明:

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

余额充值