一:背景介绍
此案例是通过Reids查询该课程下所有的班级信息,如果从reids中没有查询到数据,那么就会从数据库中查询并把查询到的数据存入到redis中。
存在的问题:没有再更新课程下的班级数据时删除缓存,这样会导致如果更新了该课程下的班级数据,那么缓存中的数据和数据中的数据出现不一致的情况。
二:redis
1)redis数据类型
redis支持五种数据类型:string(字符串)、hash(哈希)、list(列表)、set(集合)
①String(字符串)
string是redis最基本的类型,可以理解成与Memcached一模一样的类型,一个key对应一个value。redis的string可以包含任何数据,比如jpg图片或者序列化的对象。
redis 127.0.0.1:6379> SET runoob "Hello" OK redis 127.0.0.1:6379> GET runoob "Hello"
以上实例我们使用了Redis的SET和GET命令,键为runoob,对应的值为"Hello"
②Hash(哈希)
Redis hash是一个键值(key=>value)对集合,是一个string类型的field和value的映射表,用于存储对象
DEL runoob用于删除前面测试用过的key,不然会报错:(error) WRONGTYPE Operation against a key holding the wrong kind of value
redis 127.0.0.1:6379> DEL runoob redis 127.0.0.1:6379> HMSET runoob field1 "Hello" field2 "World" "OK" redis 127.0.0.1:6379> HGET runoob field1 "Hello" redis 127.0.0.1:6379> HGET runoob field2 "World"
以上实例我们使用了Redis HMSET,HGET命令,HMSET设置了两个field=>value对,HGET获取对应field对应的value。
③List(列表)
Redis列表是简单的字符串列表,按照插入顺序排序
redis 127.0.0.1:6379> DEL runoob redis 127.0.0.1:6379> lpush runoob redis (integer) 1 redis 127.0.0.1:6379> lpush runoob mongodb (integer) 2 redis 127.0.0.1:6379> lpush runoob rabbitmq (integer) 3 redis 127.0.0.1:6379> lrange runoob 0 10 1) "rabbitmq" 2) "mongodb" 3) "redis" redis 127.0.0.1:6379>
④Set(集合)
Redis的Set是string类型的无序集合,集合是通过哈希表实现的
sadd命令
添加一个string元素到key对应的set集合中,成功返回1,如果元素已经在集合中返回0.
sadd key member
redis 127.0.0.1:6379> DEL runoob redis 127.0.0.1:6379> sadd runoob redis (integer) 1 redis 127.0.0.1:6379> sadd runoob mongodb (integer) 1 redis 127.0.0.1:6379> sadd runoob rabbitmq (integer) 1 redis 127.0.0.1:6379> sadd runoob rabbitmq (integer) 0 redis 127.0.0.1:6379> smembers runoob 1) "redis" 2) "rabbitmq" 3) "mongodb"
以上实例中rebbitmq添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略
2)缓存同步
①设置有效期
给缓存设置有效期,到期后自动删除。再次查询时更新。
优点: 简单,方便
缺点: 时效性差,缓存过期之前可能不一致
场景: 更新频率较低,时效性要求低的业务
②同步双写
在修改数据库的同时,直接修改缓存
优点: 时效性强,缓存与数据库强一致
缺点: 有代码侵入,耦合度高
场景: 对一致性,时效性要求较高的缓存数据
③异步通知
修改数据库时发送事件通知,相关服务监听到通知后修改缓存数据
优点: 低耦合,可以同时通知多个缓存服务
缺点: 时效性一般,可能存在中间不一致状态
场景: 时效性一般,有多个服务需要同步
3)key的过期时间
Redis过期时间设置命令有两种:
PEXPIRE:以毫秒为单位设置key的生存时间
EXPIPE:以秒为单位设置key的生存时间
具体设置方式
- EXPIRE key seconds //将key的生存时间设置为ttl秒
- PEXPIRE key milliseconds //将key的生成时间设置为ttl毫秒
- EXPIREAT key timestamp //将key的过期时间设置为timestamp所代表的的秒数的时间戳
- PEXPIREAT key milliseconds-timestamp //将key的过期时间设置为timestamp所代表的的毫秒数的时间戳
三:问题分析过程
Redis如果只是将数据存入缓存以提高效率并设置缓存时间,带来的问题是如果数据发生变化之后就得等key失效之后查询数据才会得到正确的数据。
public List<TbContent> getContentListByCid(long cid) { //查询缓存 try { //如果缓存中有直接响应结果 String json = jedisClient.hget(CONTENT_LIST, cid + ""); if (StringUtils.isNotBlank(json)) { //json转列表,TbContent.class是list中每个元素的类型 List<TbContent> list = JsonUtils.jsonToList(json, TbContent.class); return list; } } catch (Exception e) { e.printStackTrace(); } //如果缓存没有就查询数据库 TbContentExample example = new TbContentExample(); Criteria criteria = example.createCriteria(); //设置查询条件 criteria.andCategoryIdEqualTo(cid); //执行查询 List<TbContent> list = contentMapper.selectByExampleWithBLOBs(example); //把结果添加到缓存 try { jedisClient.hset(CONTENT_LIST, cid + "", JsonUtils.objectToJson(list)); jedisClient.expire(CONTENT_LIST, 3600); } catch (Exception e) { e.printStackTrace(); } return list; }
应该采取的做法是在对数据进行添加、修改和删除操作时删除数据,查询数据时发现缓存中已删除就从数据库中查询得到最新的数据,将最新的数据重新插入到缓存保证缓存中数据的准确性。
public E3Result addContent(TbContent content) { content.setCreated(new Date()); content.setUpdated(new Date()); contentMapper.insert(content); //缓存同步 jedisClient.hdel(CONTENT_LIST, content.getCategoryId().toString()); return E3Result.ok(); }
四:总结
1、如果开发人员要开发已有的代码,需要和写此代码的开发人员进行沟通,避免出现问题。
2、对于redis如何在项目中应用,要及时查阅,做总结。建议至少看三遍redis官网并画思维导图。
五:升华
不怕不知道,就怕不知道。