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 { @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:直接查询数据即可
- 判断该页对应的值是否有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);
}