Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
string(字符串)
字符串应该是最常用的类型了。不做过多的说明。
ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
// 简单的存储
opsForValue.set("key", "value");
// 保存key,同时设置有效期为60s; 最后一个是时间单位
opsForValue.set("key_time", "value", 60, TimeUnit.SECONDS);
// 累加,默认累加1
opsForValue.increment("order_id_default");
// 累加,加指定的值
opsForValue.increment("order_id", 3);
opsForValue.increment("order_id_double", 0.1);
// 累减,默认累减1
opsForValue.decrement("order_id_default");
// 累减,减指定的值
opsForValue.decrement("order_id", 2);
// 批量设置key-value
Map<String, String> member = new HashMap<>();
member.put("name", "张三");
member.put("age", "10");
opsForValue.multiSet(member);
List<String> list = opsForValue.multiGet(Lists.newArrayList("name", "age"));
System.out.println(list);
// 统计存储的字符串的长度;中文字符在UTF-8编码下一个中文字符是3
System.out.println(opsForValue.size("name"));
// 截取一部分,由于中文字符的问题,得到的结果可能会有乱码
System.out.println(opsForValue.get("name", 0, 3));
// Nx命令:不存在则设置,否则不操作返回false;并发条件下redis将保证最终只有一个可以操作成功
opsForValue.setIfAbsent("lock_flag", "1");
// 追加实现
opsForValue.setIfAbsent("2020-02-24", "");
opsForValue.append("2020-02-24", "追加字符串");
System.out.println(opsForValue.get("2020-02-24"));
hash(哈希)
hash其实就和Java中的hashmap类似(String, Map<K,V>)
;比如我们要统计IP的访问次数或者网站页面访问量;那么我们可以使用hash中的累加方法实现;其存储结构会类型如下:
redis中缓存的key名称: Map<K,V>
举个具体的例子就是:
redis_cache_key:
"127.0.0.1":1
"127.0.0.2":3
使用hash来实现IP访问统计
HashOperations<String, String, Long> opsForHash = redisTemplate.opsForHash();
// 累加
opsForHash.increment("ip_review_count", "127.0.0.1", 1);
opsForHash.increment("ip_review_count", "127.0.0.1", 1);
opsForHash.increment("ip_review_count", "127.0.0.2", 1);
// 获取值
System.out.println(opsForHash.get("ip_review_count", "127.0.0.1"));
// 获取所有的值,数据量的时候不要直接用这种方式获取值,否则将耗费大量的服务内存
Map<String, Long> reviewCount = opsForHash.entries("ip_review_count");
System.out.println(reviewCount);
使用hash来实现对象的存储
虽然使用string也可以实现,但是如果我们想要实现只更新对象的部分字段的时候就需要将整个进行更新;如果使用hash来存储的话就可以实现局部更新
// 使用hash来存储对象
HashOperations<String, String, Object> opsForHash2 = redisTemplate.opsForHash();
List<Student> students = new ArrayList<>();
Student stu1 = Student.create();
students.add(stu1);
Student stu2 = Student.create();
stu2.setId(2L);
stu2.setName("张三");
students.add(stu2);
for(Student stu:students){
opsForHash2.putAll("sut"+stu.getId(), JSON.parseObject(JSON.toJSONString(stu)) );
}
System.out.println(opsForHash2.entries("sut1"));
// 局部更新:只更新sut2的名字为李四
opsForHash2.put("sut2", "name", "李四");
System.out.println(opsForHash2.entries("sut2"));
set(无序不重复集合)
无序集合Set;注意set的结构是 Set
利用其自动去重的功能实现一篇文章的标签存储;用户朋友圈点赞;投票统计、抽奖活动、商品搜索构建反向索引;类似社交中朋友关注
SetOperations<String, String> opsForSet = stringRedisTemplate.opsForSet();
opsForSet.add("session", "token");
opsForSet.add("session", "session");
opsForSet.add("token", "token");
opsForSet.add("token", "auth_token");
// 查找2个集合中相同的元素;返回第一个key中相同的元素
Set<String> intersect = opsForSet.intersect("session", "token");
System.out.println("相同的元素:"+intersect);
// 查找2个集合中不相同的元素;返回第一个key中不相同的元素
Set<String> difference = opsForSet.difference("token", "session");
System.out.println("不相同的元素:"+difference);
// 随机取一个
String session = opsForSet.randomMember("session");
System.out.println("随机元素:"+session);
// 将key为session的集合中的值session移到到token集合中去
opsForSet.move("session", "session", "token");
System.out.println("session的元素:"+opsForSet.members("session"));
// 插入100w条数据
// int i=0;
// while (i<1000){
// String[] sets = new String[1000];
// for(int j=0; j<1000; j++){
// sets[j] = UUID.randomUUID().toString();
// }
// opsForSet.add("set_max_size", sets);
// i++;
// }
// 判断集合中是否存在 指定的值
System.out.println("判断集合中是否存在 指定的值"+opsForSet.isMember("set_max_size", "39a036dc-6e0d-4b65-9b2e-06b09cbc4768"));
// 和队列的pop操作一样
String val = opsForSet.pop("session");
System.out.println("pop的值(从头部推出一个值):"+val);
// 移除指定的值
opsForSet.remove("token", "auth_token");
list(列表)
基于list的左右推入和弹出的机制可以实现一个先进先出的队列,比如秒杀活动就可以采用此种方式。
ListOperations<String, String> operations = stringRedisTemplate.opsForList();
// 在队列的左边添加一个
operations.leftPush("goods_id:1", "购买0个");
operations.leftPush("goods_id:1", "购买1个");
operations.leftPush("goods_id:1", "购买2个");
operations.leftPush("goods_id:1", "购买3个");
// 从右边取一个值; 因此可以基于左右推入和弹出的机制实现一个先进先出的队列
String booking = operations.rightPop("goods_id:1");
System.out.println("取出的值:"+booking);
Long size = operations.size("goods_id:1");
System.out.println("元素个数:"+size);
List<String> range = operations.range("goods_id:1", 0, size);
System.out.println("原始的数据:"+range);
// 分页查询;后面的参数表示开始下标和结束下标;包含起始和结束的下标
range = operations.range("goods_id:1", 1, 3);
System.out.println("分页查询:"+range);
// 在某个元素的左边插入一个新的元素
operations.leftPush("goods_id:1", "购买1个", "新购4个");
range = operations.range("goods_id:1", 0, operations.size("goods_id:1"));
System.out.println("插入后的数据:"+range);
// 更新某个下标的值
operations.set("goods_id:1", 1, "新购4个");
range = operations.range("goods_id:1", 0, operations.size("goods_id:1"));
System.out.println("更新后的数据:"+range);
// 裁剪list,即lTrim命令;只保留设置的区间内的元素
operations.trim("goods_id:1", 1, 2);
range = operations.range("goods_id:1", 0, operations.size("goods_id:1"));
System.out.println("裁剪后的数据:"+range);
// 移除指定的的个数;lRem命苦
operations.remove("goods_id:1", 2, "新购4个");
range = operations.range("goods_id:1", 0, operations.size("goods_id:1"));
System.out.println("移除后的数据:"+range);
// 设置等待超时时间,如果没有将阻塞等待,在超过指定时间还没有的话就抛出异常
String val = operations.leftPop("goods_id:1", 60, TimeUnit.SECONDS);
System.out.println(val);
sort set(有序集合)
这个和set的功能几乎是一样的,区别的多了一个分数用于排序。我们需要实现排行榜、推荐(买了某件商品后给用户推荐商品、文章推荐)、输入框智能补全等
@Test
public void sortSetDemo(){
ZSetOperations<String, String> opsForZSet = redisTemplate.opsForZSet();
// 添加数据,会根据分数从小到大排序
opsForZSet.add("music_top", "tom", 100);
opsForZSet.add("music_top", "jerry", 10);
opsForZSet.add("music_top", "mary", 10);
opsForZSet.add("music_top", "nike", 0);
opsForZSet.add("music_top", "coco", 10);
// 统计分数在0~10之间的数量
Long count = opsForZSet.count("music_top", 0, 10);
System.out.println(count);
// 获取指定位置的数据,下标从0开始
Set<String> music = opsForZSet.range("music_top", 1, 2);
System.out.println("指定位置正序:"+JSON.toJSONString(music));
music = opsForZSet.reverseRange("music_top", 1, 2);
System.out.println("指定位置到序:"+JSON.toJSONString(music));
// 返回指定分数区间的值
music = opsForZSet.rangeByScore("music_top", 8, 20);
System.out.println("指定分数区间正序:"+JSON.toJSONString(music));
music = opsForZSet.reverseRangeByScore("music_top", 8, 20);
System.out.println("指定分数区间到序:"+JSON.toJSONString(music));
// 获取指定值字典序之间的值;注意集合中的分数必须一样该命令才会生效;否则无法返回正确的结果
RedisZSetCommands.Range range = new RedisZSetCommands.Range();
range.gte("mary");
range.lte("tom");
music = opsForZSet.rangeByLex("music_top", range);
System.out.println("指定值字典序之间的值:"+JSON.toJSONString(music));
opsForZSet.add("music_top_lex", "tom", 10);
opsForZSet.add("music_top_lex", "jerry", 10);
opsForZSet.add("music_top_lex", "mary", 10);
opsForZSet.add("music_top_lex", "nike", 10);
music = opsForZSet.rangeByLex("music_top_lex", range);
System.out.println("指定值字典序之间的值:"+JSON.toJSONString(music));
}
HyperLogLog
hyperLogLog 的使用场景:比如要统计的用户日活(周活、月活)UV等这种计数信息、获取是进行重复过滤(如评论区刷屏的评论内容),我们如何使用set的话,那么最终在redis中会存储大量的值(而这个几乎对我们没有什么大的用处,还要耗费大量内存);而使用hyperLogLog则可以避免这个问题,1个hyperLogLog占用内存大约为12kb; 需要注意的是这是个近似的值。
@Test
public void hyperLogLogDemo(){
HyperLogLogOperations<String, String> opsForHyperLogLog = redisTemplate.opsForHyperLogLog();
String key = "hyperLogLog_uv:"+ LocalDate.now().toString();
for(int i=0; i<1921; i++){
opsForHyperLogLog.add(key, String.valueOf(i));
if(i>1000){
// 添加函数:如果添加的值存在则返回0,否则返回1
opsForHyperLogLog.add(key+1, String.valueOf(i+500));
}
}
// 当前数量
System.out.println("total_uv: "+opsForHyperLogLog.size(key, key+1));
System.out.println("uv: "+opsForHyperLogLog.size(key));
System.out.println("uv_1: "+opsForHyperLogLog.size(key+1));
// 将后面的key合并到第一key里面
System.out.println("共计: "+opsForHyperLogLog.union(key, key + 1));
System.out.println("uv: "+opsForHyperLogLog.size(key));
System.out.println("uv_1: "+opsForHyperLogLog.size(key+1));
// 删除key
opsForHyperLogLog.delete(key+1);
System.out.println("total_uv: "+opsForHyperLogLog.size(key, key+1));
}
Bitmap 实现操作日志统计功能
Bitmap使用的是二进制存储的,当我们需要记录是否执行过某个操作时,比如用户行为记录,就可以采用bitmap。
redisTemplate.opsForValue().setBit("opera_log:"+opera, uid, true);
Boolean res = redisTemplate.opsForValue().getBit("opera_log:"+opera, uid);
GeoHash 实现坐标距离的运算
@Test
public void geoHashDemo(){
GeoOperations<String, String> opsForGeo = this.redisTemplate.opsForGeo();
// 北京位于东经115.7°—117.4°,北纬39.4°—41.6°,中心位于北纬39°54′20″,东经116°25′29″
Point point = new Point(116.25, 39.54);
opsForGeo.add("city", point, "北京");
// 成都, 东经104.06, 北纬30.67
point = new Point(104.06, 30.67);
opsForGeo.add("city", point, "成都");
// 东经 139.75,北纬 35.68
point = new Point(139.75, 35.68 );
opsForGeo.add("city", point, "东京");
// 计算2地之间的距离
Distance distance = opsForGeo.distance("city", "北京", "成都", RedisGeoCommands.DistanceUnit.KILOMETERS);
System.out.println("成都到北京的距离:"+distance.getValue()+"km");
// 查询指定距离2000km的城市
GeoResults<RedisGeoCommands.GeoLocation<String>> radius = opsForGeo.radius("city", "成都"
, new Distance(2000, RedisGeoCommands.DistanceUnit.KILOMETERS));
Iterator<GeoResult<RedisGeoCommands.GeoLocation<String>>> iterator = radius.iterator();
while (iterator.hasNext()){
GeoResult<RedisGeoCommands.GeoLocation<String>> next = iterator.next();
System.out.println(next.getContent().getName());
}
}