背景介绍:
redis是一个key-value存储系统。它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
Redis缓存的基本数据类型有5种:String、hash、list\set、zset分别有各自的使用命令和对应的适用场景。
redis适用场景:
String的常用命令: get set mset mget incr
批量插入缓存
批量获取缓存
每执行一次加一命令
适用场景举例:统计文章阅读量
Hash: 哈希类型 是指键值本身又是一个 键值对结构。哈希 形如 value={ {field1,value1},...{fieldN,valueN} }
常用命令:
hset
hget
查看hash中有多少个key
查看hash中有多少个值
既查看hash value中的key又看值
删除key
购物车 :以用户id为key 商品id为field 商品数量为value
- 购物车操作
- 添加商品àhset cart:1001 10088 1 1001是用户id,cart是前缀
- 增加数量àhincrby cart:1001 10088 1
- 商品种类总数àhlen cart:1001
- 删除商品àhdel cart:1001 10088
- 获取购物车所有商品àhgetall cart:1001
Hash优点:外层的key便于归类管理,相比String更节省存储空间
Hash缺点:过期功能不能使用在field上,只能用在key上,不适用于集群
List: 一个key对应一个列表
Lpush命令:向左边添加, list1原本不存在,执行命令之后就存在了
lpush list 1 2 3 4 5 6
rpush 向右边添加list1 a b c d e f g
查看list元素:lrange 0表示起始元素从头开始 -1表示获取所有
Pop:获取list元素 lpop 从左边获取
List可以用于实现分布式的栈:先进后出,向左放,从左边拿LPUSH + LPOP
队列:LPUSH + RPOP
使用场景:消息流:微博,进入主页关注的人发布的消息展示出来,或者朋友圈,发得越晚越在上面。每个用户维护一个list,每个关注的公众号发布了消息就LPUSH进去一条。这样实现发的越晚在用户那里的排名越早。
Set 无顺序,不重复
Sadd添加命令自动过滤重复
删除命令:
交并差操作:
取set2特有的元素
取交集
取并集
使用场景:抽奖,从参与活动的5个人中随机抽取两个人
微信点赞:
SortedSet(zset):有顺序,不能重复 运算代价高:既要排序、又要去重
Zadd 每个元素都有分数,根据分数排序(升序)
降序排列
取出分数和值
使用场景:热搜
设置key的过期时间
问题出现过程:
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();
}
总结:
对数据进行增删改操作时候同步删除缓存,保证缓存一致性。
升华:
先把redis缓存的数据删掉,然后在修改数据库数据,即延迟几百毫秒,然后在把redis缓存的数据删掉,达到的效果是即时更新数据库时,有其他线程读取了mysql的旧数据更新到了redis种,旧数据也会被删除,保证数据一致。