1介绍
redis的学习与使用,以及经验总结
2学习
启动redis
redis-server.exe启动redis
3StringRedisTemplate和RedisTemplate、jedis
Jedis是Redis官方推荐的面向Java的操作Redis的客户端,而RedisTemplate,StringRedisTemplate是SpringDataRedis中对JedisApi的高度封装。SpringDataRedis相对于Jedis来说可以方便地更换Redis的Java客户端,比Jedis多了自动管理连接池的特性,方便与其他Spring框架进行搭配使用如:SpringCache
3.1StringRedisTemplate与RedisTemplate区别点
两者的关系是StringRedisTemplate继承RedisTemplate。
两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。
其实他们两者之间的区别主要在于他们使用的序列化类:
RedisTemplate使用的是JdkSerializationRedisSerializer 存入数据会将数据先序列化成字节数组然后在存入Redis数据库。
StringRedisTemplate使用的是StringRedisSerializer
使用时注意事项:
当你的redis数据库里面本来存的是字符串数据或者你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可。
但是如果你的数据是复杂的对象类型,而取出的时候又不想做任何的数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择。
RedisTemplate使用时常见问题:
redisTemplate 中存取数据都是字节数组。当redis中存入的数据是可读形式而非字节数组时,
使用redisTemplate取值的时候会无法获取导出数据,获得的值为null。
可以使用 StringRedisTemplate 试试。
3.2 RedisTemplate
3.2.1RedisTemplate使用介绍
所需要的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置文件
server:
port: 8083
spring:
redis:
database: 0
host: localhost
springboot中使用注解@Autowired 即可
//因为redistempalte默认装配了泛型为<Object,Object>的redistemplate,
//所以<String,String>也是可以注入的,因为string是object派生类 。
//如果需要要<String,Object>的redistemplate则需要自己装配
@Autowired
private RedisTemplate<String,String> redisTemplate;
注意如果你的RedisTemplate省略泛型,存入数据库的字符串前面就会有ASCII码的东西,原因上面说了RedisTemplate使用的是JdkSerializationRedisSerializer 存入数据会将数据先序列化成字节数组然后在存入Redis数据库。 所以RedisTemplate最好操作的数据是java对象。
@Test
public void test1(){
//给指定键设置过期时间
//redisTemplate.expire(userInfoInSessionKey,30,TimeUnit.MINUTES);//30分钟
redisTemplate.expire("lzj",12, TimeUnit.SECONDS);//12秒
//删除指定键
redisTemplate.delete("lzj");
//查找 指定的 键
redisTemplate.keys("*");
//判断是否存在键值
redisTemplate.hasKey("lzj");
//获取过期时间
redisTemplate.getExpire("lzj");
//获取指定格式的过期时间
redisTemplate.getExpire("lzj",TimeUnit.SECONDS);
//获取当前传入的key的值序列化为byte[]类型
redisTemplate.dump("lzj");
//修改指定键的名字 如果该键不存在则报错
redisTemplate.rename("lzj","lyx");
//旧值存在时,将旧值改为新值
redisTemplate.renameIfAbsent("lzj","lyx");
//获取指定键的类型
redisTemplate.type("lzj");
//将指定的键移动到指定的库中
redisTemplate.move("lzj",2);
//随机取一个key
redisTemplate.randomKey();
//将key持久化保存 就是把过期或者设置了过期时间的key变为永不过期
redisTemplate.persist("lzj");
}
RedisTemplate中定义了5种数据结构操作
redisTemplate.opsForValue(); //操作字符串
redisTemplate.opsForHash(); //操作hash
redisTemplate.opsForList(); //操作list
redisTemplate.opsForSet(); //操作set
redisTemplate.opsForZSet(); //操作有序set
3.2.2 RedisTemplate操作字符串(String数据结构)redisTemplate.opsForValue();
set() | multiSet() | get() | multGet() | append() | size() |
public class MyTest extends ParentTest{
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
public void test1(){
//存入数据
redisTemplate.opsForValue().set("name","lzj");
//获取数据
System.out.println(redisTemplate.opsForValue().get("name"));
//获取多个数据
System.out.println(redisTemplate.opsForValue().multiGet(Arrays.asList("lzj","lyx","3333")));
//存入数据并且设置过期时间
redisTemplate.opsForValue().set("num","123",10, TimeUnit.SECONDS);
//给指定键值 4 偏移量的位置开始替换内容
redisTemplate.opsForValue().set("name","lzj",2);
//设置键的字符串值并返回其旧值
System.out.println(redisTemplate.opsForValue().getAndSet("name","lyx"));
//给指定键 的值追加字符串
redisTemplate.opsForValue().append("test","Hello");
//存入数据 如果不存在则存入数据返回true 否则不覆盖数据返回false
System.out.println(redisTemplate.opsForValue().setIfAbsent("lzj","1234"));
//存入数据并设置过期时间 如果不存在则存入数据返回true 否则不覆盖数据返回false
System.out.println(redisTemplate.opsForValue().setIfAbsent("lzj","1234",200,TimeUnit.SECONDS));
//存入数据 如果存在键则覆盖数据 返回true 不存在则不作任何操作 返回false
System.out.println(redisTemplate.opsForValue().setIfPresent("lyx","1234"));
//存入数据并设置过期时间 如果存在键则覆盖数据 返回true 不存在则不作任何操作 返回false
System.out.println(redisTemplate.opsForValue().setIfPresent("lyx","1234",200,TimeUnit.SECONDS));
}
@Test
public void test2(){
Map<String,String> map = new HashMap<>();
map.put("1","123");
map.put("2","123");
//多个键值的插入
redisTemplate.opsForValue().multiSet(map);
//多个键值的插入 如果不存在则存入数据返回true 否则不覆盖数据返回false
redisTemplate.opsForValue().multiSetIfAbsent(map);
}
@Test
public void test3(){
//返回键的值的长度
System.out.println(redisTemplate.opsForValue().size("lzj"));
System.out.println(redisTemplate.opsForValue().multiGet(Arrays.asList("lzj","lyx","3333")));
//给指定键 加1如果值不是数字则抛出异常 不存在指定键创建一个初始为0的加1 增加成功则返回增加后的值
System.out.println(redisTemplate.opsForValue().increment("lzj"));
//给指定键 加指定整数如果值不是数字则抛出异常 不存在指定键创建一个初始为0的加指定整数 增加成功则返回增加后的值
System.out.println(redisTemplate.opsForValue().increment("1",1));
//给指定键 加指定浮点数如果值不是数字则抛出异常 不存在指定键创建一个初始为0的加指定浮点数 增加成功则返回增加后的值
System.out.println(redisTemplate.opsForValue().increment("1",1.2));
//给指定键 减1如果值不是数字则抛出异常 不存在指定键创建一个初始为0的减1 减少成功则返回增加后的值
System.out.println(redisTemplate.opsForValue().decrement("1"));
//给指定键 减指定整数如果值不是数字则抛出异常 不存在指定键创建一个初始为0的减指定整数 减少成功则返回增加后的值
System.out.println(redisTemplate.opsForValue().decrement("1",3));
}
}
3.2.3 RedisTemplate操作hash——Template.opsForHash();
put() | get() | keys() | values() | entries() | delete() | size()
public class MyTest3 extends ParentTest {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
public void test1(){
//向指定键推入一个元素(指定键不存在就创建一个空的推入)
redisTemplate.opsForHash().put("hash1","lzj","1234");
Map<String,String> map = new HashMap<>();
map.put("lxy","123445");
map.put("lhm","434564");
//向指定键推入多个元素(指定键不存在就创建一个空的推入)
redisTemplate.opsForHash().putAll("hash1",map);
//向指定键推入一个元素(仅当lzj不存在时才设置)
redisTemplate.opsForHash().putIfAbsent("hash1","lzj","1234");
//获取指定键里面单个元素key为lzj的值
System.out.println(redisTemplate.opsForHash().get("hash1","lzj"));
//获取指定键里面多个元素key为特定的值
redisTemplate.opsForHash().multiGet("hash1",Arrays.asList("lzj","num")).forEach(System.out::println);
//查看指定键内有没有元素的key是lzj的
System.out.println(redisTemplate.opsForHash().hasKey("hash1","lzj"));
//查看键所有元素的Key
redisTemplate.opsForHash().keys("hash1").forEach(System.out::println);
//查看键所有的元素
redisTemplate.opsForHash().entries("hash1").forEach((k,v) -> {System.out.println("k"+k+" _ "+"v"+v);});
//查看键所有元素的值
redisTemplate.opsForHash().values("hash1").forEach(System.out::println);;
//查看指定键的元素的key为lzj的值的长度
System.out.println(redisTemplate.opsForHash().lengthOfValue("hash1","lzj"));
//查看指定键有多少个元素
System.out.println(redisTemplate.opsForHash().size("hash1"));
//指定键的元素的Key为num的值加整数(如果key不存在创建一个初始为0加整数)
redisTemplate.opsForHash().increment("hash1","num",1);
//指定键的元素的Key为num的值加浮点数(如果key不存在创建一个初始为0加浮点数)
redisTemplate.opsForHash().increment("hash1","num",3.2);
//指定键 根据key值删除元素
redisTemplate.opsForHash().delete("hash","lzj");
//获取集合的游标。通过游标可以遍历整个集合。
Cursor<Map.Entry<Object, Object>> curosr = redisTemplate.opsForHash().scan("hash1", ScanOptions.NONE);
while(curosr.hasNext()){
Map.Entry<Object, Object> entry = curosr.next();
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
3.2.4RedisTemplate操作list——redisTemplate.opsForList();
leftPush() | leftPop() | … | index() | remove() | size()
public class MyTest2 extends ParentTest {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
public void test1(){
//存入List数据 做左边推入一个 如果键不存在 则创建一个空的并左推入
redisTemplate.opsForList().leftPush("list1","1");
//存入List数据 做左边推入多个 如果键不存在 则创建一个空的并左推入
redisTemplate.opsForList().leftPushAll("list1","88","999");
//存入List数据 做右边推入一个 如果键不存在 则创建一个空的并右推入
redisTemplate.opsForList().rightPush("list1","3");
//存入List数据 做右边推入多个 如果键不存在 则创建一个空的并右推入
redisTemplate.opsForList().leftPushAll("list1","77","6666");
//返回指定List数据下标的值
System.out.println(redisTemplate.opsForList().index("",2));
//移除2个指定List数据元素内容为1
redisTemplate.opsForList().remove("list1",2,"1");
//左边推出一个
System.out.println(redisTemplate.opsForList().leftPop("list1"));
//移除集合中左边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
System.out.println(redisTemplate.opsForList().leftPop("list1",2, TimeUnit.SECONDS));
//右边推出一个
System.out.println(redisTemplate.opsForList().rightPop("list1"));
//移除集合中右边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
System.out.println(redisTemplate.opsForList().rightPop("list1",2, TimeUnit.SECONDS));
//给指定键List数据下标为1的元素替换成2
redisTemplate.opsForList().set("list1",1,"2");
//查看指定键List数据元素个数
redisTemplate.opsForList().size("list1");
//获取指定健List数据 从开始到结束下标
redisTemplate.opsForList().range("list1",0,-1).forEach(System.out::println);
//移除列表的最后一个元素,并将该元素添加到另一个列表(如果这另一个List不存在就创建一个空的添加)并返回
redisTemplate.opsForList().rightPopAndLeftPush("list1","list2");
// 从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
redisTemplate.opsForList().rightPopAndLeftPush("list1","list2",80,TimeUnit.SECONDS);
}
@Test
public void test2(){
//指定键List数据右边推出一个元素
System.out.println(redisTemplate.opsForList().rightPop("list1"));
//指定键List数据右边推出一个元素,如果List元素 等待10秒 10秒内没有元素就不操作,有就推出
System.out.println(redisTemplate.opsForList().rightPop("list1",10, TimeUnit.SECONDS));
//指定键List数据左边推出一个元素,如果List元素 等待10秒 10秒内没有元素就不操作,有就推出
System.out.println(redisTemplate.opsForList().leftPop("list1"));
//指定键List数据左边推出一个元素
System.out.println(redisTemplate.opsForList().leftPop("list1",10, TimeUnit.SECONDS));
//给指定键List数据下标为1的元素替换成2
redisTemplate.opsForList().set("list1",1,"2");
//查看指定键List数据元素个数
redisTemplate.opsForList().size("list1");
//如果存在该键的List数据 则向左推入一个元素 不存在的话不操作
redisTemplate.opsForList().leftPushIfPresent("list1","1");
//如果存在该键的List数据 则向右推入一个元素 不存在的话不操作
redisTemplate.opsForList().rightPushIfPresent("list1","1");
}
}
3.2.5RedisTemplate操作set——redisTemplate.opsForSet();
add() | members() |isMember() | pop()| remove() | move() | size()
集合操作:差集difference() | 交集intersect() | 并集union()
public class MyTest4 extends ParentTest {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
public void test1(){
//向键为set1的添加元素1(若没有该键,创建一个新的,并加入元素)
redisTemplate.opsForSet().add("set1","1");
//查询指定键的包含所有元素
System.out.println(redisTemplate.opsForSet().members("set1"));
//查询指定键的包含元素个数
System.out.println(redisTemplate.opsForSet().size("set1"));
//查询指定键是否有该元素
System.out.println(redisTemplate.opsForSet().isMember("set1","1"));
//指定键随机推出一个元素 并返回
System.out.println(redisTemplate.opsForSet().pop("set1"));
//移除指定键里面的指定元素
redisTemplate.opsForSet().remove("set1","1","2");
//将指定键的指定元素移动到指定键中
redisTemplate.opsForSet().move("set1","1","set3");
//获取两个集合的差集
redisTemplate.opsForSet().difference("set1","set2").forEach(System.out::println);
//获取两个集合的差集,并存入一个集合中
redisTemplate.opsForSet().differenceAndStore("set1","set2","set3");
//求指定键与另外一个集合的差集
redisTemplate.opsForSet().difference("set1",Arrays.asList("1","2","3")).forEach(System.out::println);
//求指定键与另外一个集合的差集,并存入一个集合中
redisTemplate.opsForSet().differenceAndStore("set1",Arrays.asList("1","2","3"),"set3");
//获取两个集合的交集
redisTemplate.opsForSet().intersect("set1","set2").forEach(System.out::println);
//获取两个集合的交集,并存入一个集合中
redisTemplate.opsForSet().intersectAndStore("set1","set2","set3");
//求指定键与另外一个集合的交集
redisTemplate.opsForSet().intersect("set1",Arrays.asList("1","2","3"));
//求指定键与另外一个集合的交集,并存入一个集合中
redisTemplate.opsForSet().intersectAndStore("set1",Arrays.asList("1","2","3"),"set3");
//获取两个集合的并集
redisTemplate.opsForSet().union("set1","set2").forEach(System.out::println);
//获取两个集合的并集,并存入一个集合中
redisTemplate.opsForSet().unionAndStore("set1","set2","set3");
//求指定键与另外一个集合的并集
redisTemplate.opsForSet().union("set1",Arrays.asList("1","2","3")).forEach(System.out::println);
//求指定键与另外一个集合的并集,并存入一个集合中
redisTemplate.opsForSet().unionAndStore("set1",Arrays.asList("1","2","3"),"set3");
//随机获取集合中的一个元素
redisTemplate.opsForSet().randomMember("set1");
//随机返回集合中指定数量的元素。随机的元素可能重复
redisTemplate.opsForSet().randomMembers("set1",2);
//随机返回集合中指定数量的元素。随机的元素不会重复
redisTemplate.opsForSet().distinctRandomMembers("set1",2);
//获取集合的游标。通过游标可以遍历整个集合
Cursor<String> curosr = redisTemplate.opsForSet().scan("set1", ScanOptions.NONE);
while(curosr.hasNext()){
String item = curosr.next();
System.out.println(item);
}
}
}
3.2.6RedisTemplate操作有序set(zset)——redisTemplate.opsForZSet();
public class MyTest5 extends ParentTest {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
public void test1(){
//向指定键插入元素 和 分数
redisTemplate.opsForZSet().add("zset1","lzj",20.5);
//向指定键插入元素 和 分数
ZSetOperations.TypedTuple<String> objectTypedTuple1 = new DefaultTypedTuple<String>("zset-1",9.6);
ZSetOperations.TypedTuple<String> objectTypedTuple2 = new DefaultTypedTuple<String>("zset-2",9.9);
Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<ZSetOperations.TypedTuple<String>>();
tuples.add(objectTypedTuple1);
tuples.add(objectTypedTuple2);
redisTemplate.opsForZSet().add("zset1", tuples);
//获取指定键内指定元素的分数
redisTemplate.opsForZSet().score("zset1","zset-1");
//指定键的移除指定元素
redisTemplate.opsForZSet().remove("zset1","lzj","zset-1");
//通过分数返回有序集合指定区间内的成员个数
System.out.println(redisTemplate.opsForZSet().count("zset1",10,20));
//通过索引区间返回有序集合成指定区间内的成员,其中有序集成员按分数值递增(从小到大)排序
redisTemplate.opsForZSet().range("zset1",0,-1).forEach(System.out::println);
//返回有序集中指定成员的排名,其中有序集成员按分数值递增(从小到大)顺序排列
System.out.println(redisTemplate.opsForZSet().rank("zset1","zset-1"));
//返回有序集中指定成员的排名,其中有序集成员按分数值递增(从大到小)顺序排列
redisTemplate.opsForZSet().reverseRank("zset1","zset-1");
//通过分数返回有序集合指定区间内的成员,其中有序集成员按分数值递增(从小到大)顺序排列
redisTemplate.opsForZSet().rangeByScore("zset1",10,20).forEach(System.out::println);
//通过分数返回有序集合指定区间内的成员,其中有序集成员按分数值递增(从小到大)顺序排列 取下标1开始2个元素
redisTemplate.opsForZSet().rangeByScore("zset1",10,20,1,2).forEach(System.out::println);
//通过索引区间返回有序集合成指定区间内的成员,其中有序集成员按分数值递减(从大到小)顺序排列
redisTemplate.opsForZSet().reverseRange("zset1",0,-1).forEach(System.out::println);;
//指定键的分数在10到20之间的元素(从大到小排序)
redisTemplate.opsForZSet().reverseRangeByScore("zset1", 10, 20).forEach(System.out::println);
//指定键的分数在10到20之间的元素(从大到小排序) 取下标1开始2个元素
redisTemplate.opsForZSet().reverseRangeByScore("zset1", 10, 20,1,2).forEach(System.out::println);
//通过索引区间内的成员按分数值递增(从小到大)顺序排列 并且带有分数
Set<ZSetOperations.TypedTuple<String>> zset1 = redisTemplate.opsForZSet().rangeWithScores("zset1", 0, -1);
Iterator<ZSetOperations.TypedTuple<String>> iterator1 = zset1.iterator();
while (iterator1.hasNext())
{
ZSetOperations.TypedTuple<String> typedTuple = iterator1.next();
System.out.println("value:" + typedTuple.getValue() + "score:" + typedTuple.getScore());
}
//指定键的分数在10到20之间的元素(从小到大排序)并且带有分数
Set<ZSetOperations.TypedTuple<String>> zset2 = redisTemplate.opsForZSet().rangeByScoreWithScores("zset1", 10, 20);
Iterator<ZSetOperations.TypedTuple<String>> iterator2 = zset2.iterator();
while (iterator2.hasNext())
{
ZSetOperations.TypedTuple<String> typedTuple = iterator2.next();
System.out.println("value:" + typedTuple.getValue() + "score:" + typedTuple.getScore());
}
//通过索引区间返回有序集合成指定区间内的成员对象,其中有序集成员按分数值递减(从大到小)顺序排列
Set<ZSetOperations.TypedTuple<String>> zset3 = redisTemplate.opsForZSet().reverseRangeWithScores("zset1", 0, -1);
Iterator<ZSetOperations.TypedTuple<String>> iterator3 = zset3.iterator();
while (iterator3.hasNext())
{
ZSetOperations.TypedTuple<String> typedTuple = iterator3.next();
System.out.println("value:" + typedTuple.getValue() + "score:" + typedTuple.getScore());
}
//指定键的分数在10到20之间的元素(从小到大排序)并且带有分数
Set<ZSetOperations.TypedTuple<String>> zset4 = redisTemplate.opsForZSet().reverseRangeByScoreWithScores("zset1", 10,20);
Iterator<ZSetOperations.TypedTuple<String>> iterator4 = zset4.iterator();
while (iterator4.hasNext())
{
ZSetOperations.TypedTuple<String> typedTuple = iterator4.next();
System.out.println("value:" + typedTuple.getValue() + "score:" + typedTuple.getScore());
}
//遍历zset
Cursor<ZSetOperations.TypedTuple<String>> cursor5 = redisTemplate.opsForZSet().scan("zzset1", ScanOptions.NONE);
while (cursor5.hasNext()){
ZSetOperations.TypedTuple<String> item = cursor5.next();
System.out.println(item.getValue() + ":" + item.getScore());
}
}
}
3.3StringRedisTemplate的使用
3.3.1 StringRedisTemplate使用介绍
所需要的依赖和配置文件同RedisTemplate
springboot中使用注解@Autowired 即可
@Autowired
public StringRedisTemplate stringRedisTemplate;
StringRedisTemplate的api几乎和RedisTemplate相同 用法相同不多讲解,看完RedisTemplate相信你也有了对他们的区别,还有什么时候用什么有了进一步了解,我们通常会把这些方法封装起来 成一个工具类方便我们使用,下面我提供了一个工具类给大家
RedisTemplate封装的工具类(你也可以不要这个SpringUtils直接给RedisUtils标注上@Component,自动注入RedisTemplate使用,在要用到RedisUtils的地方注入RedisUtils即可)
4redis中的位图(bitmap)
日常开发工作中会有很多bool类型的值需要存取,比如记录某个玩家一年的活跃情况,每天对应的状态只有两种,活跃/不活跃。如果使用一个set来记录当天活跃的用户,当用户量非常大时会浪费非常多的空间。因此redis提供了位图(bitmap),让用户可以对每一位进行单独操作,设定某一位的值,位图并不是一个新的数据类型,它其实是使用了字符串类型。
4.1 redis bitmap使用介绍
bitmap 主要就三个操作命令,setbit,getbit以及 bitcount
所需依赖和配置文件同RedisTemplate
这个key就是代表redis的key-value中的key,它可以是任意的字符串,offset可以是用户ID
4.1.1 设置标记——setbit
设置指定key对应偏移量(某一位bit)上的bit值,value只能是1或0,如果key不存在就创建一个新的key并设定值,offset是偏移量,可以看做是比特位的下标,基本语法如下
setbit key offset 0/1
请注意这个offset 必须是数字,后面的value必须是0/1
offset 参数必须大于或等于 0 ,小于 2^32 (bit 映射被限制在 512 MB 之内)。
位数组在redis存储世界里,每一个字节也是8位,初始都是:
0 0 0 0 0 0 0 0
而位操作就是在对应的offset偏移量上设置0或者1,比如将第3位设置为1,即:
#对应redis操作即:
setbit key 3 1
>>>
0 0 0 0 1 0 0 0
在此基础上,如果要在偏移量为13的位置设置1,即:
setbit key 13 1
#对应redis中的存储为:0 0 1 0 | 0 0 0 0 | 0 0 0 0 | 1 0 0 0
对应的 SpringBoot 中,借助 RedisTemplate 可以比较容易的实现,通常有两种写法,都可以
@Autowired
private StringRedisTemplate redisTemplate;
/** * 设置标记位
* @param key
* @param offset
* @param tag
* @return */
public Boolean mark(String key, long offset, boolean tag) {
return redisTemplate.opsForValue().setBit(key, offset, tag);
}
public Boolean mark2(String key, long offset, boolean tag) {
return redisTemplate.execute(new RedisCallback() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {return connection.setBit(key.getBytes(), offset, tag);}
});
}
上面两种写法的核心区别,就是 key 的序列化问题,第一种写法使用默认的 jdk 字符串序列化,和后面的getBytes()会有一些区别。
4.1.2判断存在与否——getbit
获取指定key对应偏移量上的bit值
getbit key offset,如果返回 1,表示存在否则不存在
public Boolean container(String key, long offest) { return redisTemplate.opsForValue().getBit(key, offest);}
4.1.3计数——bitcount
bitcount key,统计和
public long bitCount(String key) {
return redisTemplate.execute(new RedisCallback() {
@Override
public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
return redisConnection.bitCount(key.getBytes());
}
});
}
4.1.4 位元操作——bitop operation destkey key [key …]
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种:
BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
/**
* 命令:BITOP * 复杂度:O(N) * 整个月全勤的员工数量,这里用2天代表整个月 * @param key1 第一天 * @param key2 第二天 */
public Long signedAllMonth(String key1, String key2){
String andMap = "signedAllMonth11";
redisTemplate.execute((RedisCallback) conn -> conn.bitOp(RedisStringCommands.BitOperation.AND, andMap.getBytes(),key1.getBytes(), key2.getBytes()));
return (Long) redisTemplate.execute((RedisCallback<Long>) conn -> conn.bitCount(andMap.getBytes()));}
4.2应用场景
用户签到
用户在线状态
统计活跃用户
各种状态值
自定义布隆过滤器
点赞功能
4.3 如何用redis存储统计1亿用户一年的登陆情况,并快速检索任意时间窗口内的活跃用户数量。
bitmap的优势是:非常均衡的特性,精准统计,可以得到每个统计对象的状态,秒出。缺点是:当你的统计对象数量十分十分巨大时,可能会占用到一点存储空间,但也可在接受范围内。也可以通过分片,或者压缩的额外手段去解决。
HyperLogLog的优势是:可以统计夸张到无法想象的数量,并且占用小的夸张的内存。 缺点是:建立在牺牲准确率的基础上,而且无法得到每个统计对象的状态。
5 实战
5.1使用redis中的位图(bitmap)统计在线人数
统计在线人数的方法有很多,像redis中比较常见的,HyperLogLog基数统计,位图,集合,有序集合都可以实现统计在线人数的方法
这里说一下比较常用的一个使用位图统计。
为什么使用这个呢?
因为使用集合或者有序集合都可以存储具体的在线用户名单,但是会消耗大量的内存。 而使用 HyperLogLog 虽然能够有效地减少统计在线用户所需的内存 ,但是它的准确性比较低,也就是说它没有办法准确的记录在线用户名单。
那么是否存在一种既能够获得在线用户名单, 又可以尽量减少内存消耗的方法存在呢? 这种方法的确存在 —— 使用 Redis 的位图就可以办到。
Redis的位图就是一个由二进制位组成的数组, 通过将数组中的每个二进制位与用户 ID 进行一一对应, 我们可以使用位图去记录每个用户是否在线。
**。。。因为使用的是redis,所以不需要这个mysql数据库,被误导了,注意!注意!注意!**还是因为redis不熟导致的
springboot+redis使用位图实现:
redis key可以设计成:一个字符串如"loginCount";offset 用user_id。
a) 当人员登录的时候使用(setBit)
//第一个参数是key,第二个参数是偏移量,第三个参数是设置二进制的值,true为1,false为0
public Boolean mark(String key, long offset, boolean tag) {
return redisTemplate.opsForValue().setBit(key.getBytes(), offset, tag);
}
除了使用这种方式也可以用
public Boolean mark2(String key, long offset, boolean tag) {
//两种方式都是一样的只是写法不同
(Boolean) redisTemplate.execute(
(RedisCallback<Boolean>) con -> con.setBit(key.getBytes(), offset, tag)
);
// return (Boolean) redisTemplate.execute(new RedisCallback() {
// @Override
// public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
// return connection.setBit(key.getBytes(), offset, tag);
// }
// });
}
第二种方式不会进行key的序列化,第一种方式会将key根据jdk的方式进行序列化
注意:在进行在线人数统计的时候,其中key.getBytes()的名字设定必须使用人员登陆时候登记在线操作的第二种方式,才可以获取到人数,使用第一种方式获取到的是0,因为key的序列化方式不同,导致第一种方式无法获取到准确的值
redisTemplate.opsForValue().getBit()方法是统计位图中指定的偏移量为否为1。也就是通过判断偏移量来判断用户是否在线
b) 查询用户在线与否(getBit)
public Boolean container(String key, long offest) {
return redisTemplate.opsForValue().getBit(key, offest);
}
当人员离线的时候,可进行
//设置tag为false 离线
redisTemplate.opsForValue().setBit(key.getBytes(), offest,false)
设置第三个参数的值false,表示用户离线了
c) 统计在线总人数
redisTemplate.execute(
(RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
5.2 统计活跃人数
redis key可以设计成:日期;offset 用user_id。
统计一周内登录的用户:bitop or result mon tue wed thu fri sat sun 再bitcount result
5.3使用 bitmap 实现用户上线次数统计
假设现在我们希望记录自己网站上的用户的上线频率,比如说,计算用户 A 上线了多少天,用户 B 上线了多少天,诸如此类,以此作为数据,从而决定让哪些用户参加 beta 测试等活动 —— 这个模式可以使用 SETBIT key offset value 和 BITCOUNT key [start] [end] 来实现。
比如说,每当用户在某一天上线的时候,我们就使用 SETBIT key offset value ,以用户名作为 key ,将那天所代表的网站的上线日作为 offset 参数,并将这个 offset 上的value设置为 1 。
举个例子,如果今天是网站上线的第 100 天,而用户 peter 在今天阅览过网站,那么执行命令 SETBIT peter 100 1 ;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1 ,以此类推。
当要计算 peter 总共以来的上线次数时,就使用 BITCOUNT key [start] [end] 命令:执行 BITCOUNT peter ,得出的结果就是 peter 上线的总天数。
6参考
StringRedisTemplate操作redis数据:https://www.cnblogs.com/slowcity/p/9002660.html
史上最全的RedisTemplate和StringRedisTemplate以及Jedis的入门使用讲解:https://blog.csdn.net/weixin_44012722/article/details/105665310(这个哥整理的很全了)
redis统计用户日活量_「SpringBoot系列」Redis位图使用姿势及应用场景:https://blog.csdn.net/weixin_29067143/article/details/112283459
使用redis中的位图(bitmap)统计在线人数:https://zhuanlan.zhihu.com/p/409386645
redis位图详解与应用:https://blog.csdn.net/weixin_39585051/article/details/104711566
基于Redis位图实现系统用户登录统计:https://www.cnblogs.com/bndong/p/7677781.html
redis位图法bitmap统计活跃用户:https://blog.csdn.net/angellee1988/article/details/104223088