mysql和redis的同步
1 为什么使用redis做缓存
-
数据库链接慢,磁盘IO慢
-
mysql线程数不够
-
mybatis缓存,缓存在JVM中
-
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:直接查询数据即可
- 判断该页对应的值是否有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);}