mysql和redis的同步

news/2024/12/22 21:16:56/

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 {@Resourceprivate CommunityFegin communityFegin;@Resourceprivate StringRedisTemplate stringRedisTemplate;//所有索引的前缀private static final String ALL_PARKING_KEY = "ALL_PARKING_KEY";//每个索引的前缀private static final String PREFIX_PARKING = "PREFIX_PARKING::";@Overridepublic 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);}@Overridepublic 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) {// 更新rediskeysOps.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);//改变对应idkeysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());//填充valuevaluesOps.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);//改变对应idkeysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());//填充valuevaluesOps.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);//改变对应idkeysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());//填充valuevaluesOps.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);//查询对应的数据是否为nullfor (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);//改变对应idkeysOps.set(ALL_PARKING_KEY,begin,PREFIX_PARKING+parking.getId());//填充valuevaluesOps.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);}

http://www.ppmy.cn/news/80721.html

相关文章

代码随想录算法训练营第五十一天 | 买卖股票3

309.最佳买卖股票时机含冷冻期 文档讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;动态规划来决定最佳时机&#xff0c;这次有冷冻期&#xff01;| LeetCode&#xff1a;309.买卖股票的最佳时机含冷冻期_哔哩哔哩_bilibili 状态&#xff1a;dp定义看的…

小马识途:如何做好短视频内容运营

随着移动互联网普及&#xff0c;抖音和快手小红书这样的短视频平台已经成为这个时代最流行的内容承载形式。 短视频运营成为当下网络推广的一项重要任务&#xff0c;如何优化短视频呢&#xff1f;小马识途营销顾问就自身经历分享几点建议&#xff1a; 1、灵活的选题机制 内容选…

JavaScript实现循环读入整数进行累加,直到累加的和大于1000为止的代码

以下为实现循环读入整数进行累加&#xff0c;直到累加的和大于1000为止的程序代码和运行截图 目录 前言 一、循环读入整数进行累加&#xff0c;直到累加的和大于1000为止 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0…

易观千帆 | 2023年4月证券APP月活跃用户规模盘点

易观&#xff1a;2023年4月证券服务应用活跃人数13924.88万人&#xff0c;相较上月&#xff0c;环比下降1.46%&#xff0c;同比增长3.64%&#xff1b;2023年4月自营类证券服务应用Top10 活跃人数6144.02万人&#xff0c;环比下降0.01%&#xff1b;2023年4月第三方证券服务应用T…

「远程开发」VSCode使用SSH远程linux服务器 - 公网远程连接(1)

文章目录 前言视频教程1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 转…

Kuberntes云原生实战08 Kubesphere 通过OIDC实现gitlab联合登录(全网唯一可用)

大家好,我是飘渺。 今天咱们继续更新Kubernetes云原生实战系列,本节文章将会打通公司用户体系,使用Gitlab中的用户完成登录认证。 为什么需要集成登录认证 KubeSphere 多租户是实际生产使用中非常需要的一个功能,该功能满足不同用户登录 KubeSphere 平台的需求。比如开发…

Java修饰符

4 修饰符(static关键字) 4.1 权限修饰符 4.2 状态修饰符 final(最终态)static(静态)4.2.1 final的特点 final 关键字是最终的意思,可以修饰成员变量,成员方法,类final修饰的特点: 1.修饰方法:表示该方法是最终方法,不能被重写2.修饰变量:表示该变量是常量,不能被…

使用宝塔在Linux面板搭建网站,并实现公网远程访问「内网穿透」

文章目录 前言1. 环境安装2. 安装cpolar内网穿透3. 内网穿透4. 固定http地址5. 配置二级子域名6. 创建一个测试页面 转载自远程内网穿透的文章&#xff1a;Linux使用宝塔面板搭建网站&#xff0c;并内网穿透实现公网访问 前言 宝塔面板作为简单好用的服务器运维管理面板&#…