一、前言
最近,博主自己去学习了一下Redis的相关技术,自己看的是b站up主:【狂神说Java】的视频。个人觉得狂神讲的很好,对于redis入门或者进阶有很大帮助。这里博主就将自己所学的内容以博客的形式进行记录。在笔记中有很多博主自己的思考和代码的编写。由于Redis的内容是十分多的,所以博主会分成几个部分来讲解。这部分就主要讲redis的数据类型,对于以前的redis有 五大基本数据类型(String、Set、Zset、Hash、List)以及三大特殊类型(BitMap、Geospatial、Hyperloglog)。但是随着技术的发展,最新的redis又增加了Stream流、BitField位域两大数据类型。这里博主就将这十大数据类型整合在一起,做一个简单的基本指令介绍。希望对各位小伙伴有所帮助。
二、Redis技术
Redis技术,英文全称是Remote Dictionary Server(远程字典服务)。是一种NoSQL技术,这是一种基于内存的数据库,并且提供一定的持久化功能。首先在我们一般的系统中不会出现高并发的情况,所以只用MySQL这样的数据库是完全可以的。但是,一旦接入大规模的数据访问,如:一些节假日时,某些平台的商品抢购活动,会导致一瞬间有大量的访问数据的到来,如果这些访问直接访问我们的MySQL数据库,那就很有可能导致我们数据库瘫痪、甚至服务器宕机等严重失误。所以说不管是技术的了解还是真实业务的需要Redis这类缓存技术的学习都是必不可少的。
三、Jedis技术
对于redis的学习,其实有多种方式,现在市面上大多数都是采用在Linux上执行redis相关命令。但是博主个人觉得要真正学习这门技术,还得是要在redis的客户端执行这些指令,并能集成在我们自己的项目之中。于是Jedis就成了我的首选。它是官方推荐的用于连接Java的开发工具,是用Java操作Redis的中间键。这里博主就采用Jedis来实现我们十大数据类型的基本指令。
导入相关Jar包
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.3</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.7</version>
</dependency>
四、Redis相关数据类型介绍
1、Redis中关于key的基础命令
这里是一些基础指令,相关指令都有批注,这些指令在Linux操作系统上同样可执行。
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping());
System.out.println("-------- 基本的key的操作 ------");
System.out.println("清楚数据"+jedis.flushDB());
System.out.println("判断某个值是否存在"+jedis.exists("name"));
System.out.println("新增<'name','lfy'>键值对"+jedis.set("name","lfy"));
System.out.println("新增<'age','18'>键值对"+jedis.set("age","18"));
System.out.println("系统中的所有键:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("----------------");
System.out.println("删除键age:"+jedis.del("age"));
System.out.println("判断键age是否存在:"+jedis.exists("age"));
System.out.println("查看name键的属性的类型"+jedis.type("name"));
System.out.println("随机返回一个key的空间"+jedis.randomKey());
System.out.println("重命名key"+jedis.rename("name","rename"));
System.out.println("-----------");
System.out.println("取出修改了的name的名字"+jedis.get("name"));
System.out.println("取出修改了的name的名字"+jedis.get("rename"));
System.out.println("按照索引查询:"+jedis.select(0));
System.out.println("------------");
System.out.println("删除当前数据库中所有的key:"+jedis.flushDB());
System.out.println("返回当前数据库库中key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中的的key:"+jedis.flushAll());
}
}
2、String(字符串)类型
①基本指令代码
public class TestString {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("---------添加数据----------");
System.out.println(jedis.set("key1", "id1"));
System.out.println(jedis.set("key2", "id2"));
System.out.println(jedis.set("key3", "id3"));
User user = new User();
user.setName("小明");
user.setAge(18);
System.out.println("添加User对象:"+jedis.set("user", JSON.toJSONString(user)));
System.out.println("获取User对象:"+jedis.get("user"));
System.out.println(jedis.keys("*"));
System.out.println("删除key2"+jedis.del("key2"));
System.out.println("获取key2"+jedis.get("key2"));
System.out.println("修改key1的值"+jedis.set("key1","123"));
System.out.println("获得key1的值"+jedis.get("key1"));
System.out.println("在key3的值后面加值:"+jedis.append("key3","456"));
System.out.println("获得key3的值:"+jedis.get("key3"));
System.out.println("添加多个键值对:"+jedis.mset("key01","value01","key02","value02"));
System.out.println("获得多个键值对:"+jedis.mget("key01","key02"));
System.out.println("删除多个键值对:"+jedis.del("key1","key01"));
System.out.println("----------新增键值对覆盖原来的值----------");
jedis.flushDB();
//如果key1不存在就创建key1并,设置值为666,如果存在就覆盖原来的值
System.out.println(jedis.setnx("key1","666"));
System.out.println("获得key1的值:"+jedis.get("key1"));
System.out.println("-----------新增键值对并设置有效时间-----------");
//判断过期时间,如果60秒内不存在就过期
System.out.println(jedis.setex("key1",60,"888"));
System.out.println(jedis.get("key1"));
System.out.println("---------获取原来的值,更新为新的值----------");
//先get再set,获取完值后再设置
System.out.println(jedis.getSet("key2","9090asfgfhgfh"));
System.out.println(jedis.get("key2"));
System.out.println("获取key2的值的字串:"+jedis.getrange("key2",2,5));
}
}
②应用场景举例
a.数据缓存
我们可以用String类型将数据存入缓存,来提高读取速度,减少后端数据库的压力。
b.计数器
我们在一些文章里的浏览量和一些网站的粉丝数、点赞数、评论数、播放数等统计多单位的数量。
c、对象存储
我们可以用redis存储json对象(上面代码有),用于一些复杂的商品、用户信息的存储。
d、验证码
在String类型中可以设置多少秒内,数据过期的功能。这个和我们验证码的功能刚好可以结合起来,产生 了验证码我们先存在redis中,设置时间,并在规定时间中对用户填入的验证码进行校验。后面博主会做一期关于阿里云短信验证的作品,到时候可以将redis集合在其中,大家持续关注哦。
e、分布式锁
String类型中的setnx:不存在再设置。设置成功返回值为1,设置失败返回值为0。判断key是否存在和是否设置value是两个原子性操作,这个在分布式锁中常常应用。
3、List(列表)类型
①:基本指令代码
public class TestList {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("--------添加一个list--------");
jedis.lpush("collections","ArrayList","Vector","HashMap","WeakHashMap");
jedis.lpush("collections","HashSet");
jedis.lpush("collections","TreeSet");
jedis.lpush("collections","TreeMap");
//-1代表查询所有
System.out.println("获取collections中的内容:"+jedis.lrange("collections",0,-1));
jedis.rpush("right","1","2","3");
jedis.rpush("right","4");
System.out.println("获取right中的内容:"+jedis.lrange("right",0,-1));
//注意 lpush与rpush的区别分别表示从队列(将list看作一个队列)的左边进和右边进
//而lrange()表示从左边出。
System.out.println("collections区间0~3的内容:"+jedis.lrange("collections",0,3));
System.out.println("---------删除列表中指定的值--------");
//删除列表指定个数,第二个数为删除的个数(有重复时),后add进入的值先被珊,类似于出栈操作。
System.out.println("删除指定的元素:"+jedis.lrem("collections",2,"HashMap"));
System.out.println("获取collections中的内容:"+jedis.lrange("collections",0,-1));
System.out.println("删除区间0~3之外的个数:"+jedis.ltrim("collections",0,3));
System.out.println("获取collections中的内容:"+jedis.lrange("collections",0,-1));
System.out.println("collections列表左端出栈(lpop)"+jedis.lpop("collections"));
System.out.println("获取collections中的内容:"+jedis.lrange("collections",0,-1));
System.out.println("collections列表右端出栈(rpop)"+jedis.rpop("collections"));
System.out.println("---------collections的长度-----------");
System.out.println("collections的长度为:"+jedis.llen("collections"));
System.out.println("获取下标为2的元素:"+jedis.lindex("collections",2));
System.out.println("---------排序sort---------");
jedis.lpush("sortList","2","3","8","1","9");
System.out.println("sortList排序前:"+jedis.lrange("sortList",0,-1));
System.out.println(jedis.sort("sortList"));
System.out.println("sortList排序后:"+jedis.lrange("sortList",0,-1));
}
}
②:应用场景举例
a.消息队列
如果我们对List使用Lpush、Rpop就是队列的结构,使用Lpush、Lpop就是栈的结构。其实就是和RabbitMQ实现的功能差不多。等下后面讲Stream数据类型时,就会发现和Stream的工作原理和List是一样的,只是Redis官方特意出了一个Stream类型,专门用于消息的传输。
b.消息排队
比如我们在QQ、或者微信要显示别人给你发的动态的点赞顺序,依次显示,像这种有顺序的业务我们就可以通过List来实现。
4、Set(集合)类型
①:基本指令代码
public class TestSet {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("----------向集合中添加元素(不重复)----------");
System.out.println(jedis.sadd("eleSet","e1","e2","e3","e4","e5"));
System.out.println(jedis.sadd("eleSet","e6"));
System.out.println(jedis.sadd("eleSet","e6"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
//删除多个元素依次类推
System.out.println("删除一个元素e1"+jedis.srem("eleSet","e1"));
System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));
System.out.println("随机移除集合中的一个元素:"+jedis.spop("eleSet"));
System.out.println("随机移除集合中的一个元素:"+jedis.spop("eleSet"));
System.out.println("eleSet中包含的元素的个数:"+jedis.scard("eleSet"));
System.out.println("判断e3是否在eleSet中:"+jedis.sismember("eleSet","e3"));
System.out.println("-----------------------");
System.out.println(jedis.sadd("eleSet1","e1","e2","e3","e4","e5","e6","e7"));
System.out.println(jedis.sadd("eleSet2","e1","e2","e3","e4","e5"));
System.out.println("将elsSet1中删除e1并存入eleSet3中:"+jedis.smove("eleSet1","eleSet3","e1"));
System.out.println("将elsSet2中删除e2并存入eleSet3中:"+jedis.smove("eleSet1","eleSet3","e2"));
System.out.println("eleSet中的元素:"+jedis.smembers("eleSet3"));
System.out.println("-----------集合运算-----------");
System.out.println("elsSet1和eleSet2的交集:"+jedis.sinter("eleSet1","eleSet2"));
System.out.println("elsSet1和eleSet2的并集:"+jedis.sunion("eleSet1","eleSet2"));
System.out.println("elsSet1和eleSet2的差集:"+jedis.sdiff("eleSet1","eleSet2"));
//求交集并将交集保存到dstkey的集合
jedis.sinterstore("eleSet4","eleSet1","eleSet2");
System.out.println("eleSet4中的元素:"+jedis.smembers("eleSet4"));
}
}
②:应用场景举例
a.共同关注、共同爱好、推荐好友
采用set中的并集实现。
b.避免键名冲突
在设置键名时,应避免使用过于简单或易于冲突的名称。最好使用具有唯一标识符的键名,如前缀加随机数、时间戳等,以确保不会发生键名冲突的情况。
5、Zset(有序集合)类型
①:基本指令代码
public class TestZset {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
//注意Zset与Set只是多了一个计数位
//添加一个值
jedis.zadd("MySet",1,"一");
jedis.zadd("MySet",2,"二");
jedis.zadd("MySet",4,"三");
jedis.zadd("MySet",3,"五");
jedis.zadd("MySet",6,"四");
System.out.println("获取MySet中的值:"+jedis.zrange("MySet",0,-1));
//使用zadd(String key,Map<String,Double>)添加多个数据
HashMap<String, Double> map = new HashMap();
map.put("小明",123.00);
map.put("小刚",456.00);
map.put("小李",789.00);
jedis.zadd("Salary",map);
Set<String> sets = jedis.zrangeByScore("Salary", "-inf", "+inf");
Iterator<String> iterator = sets.iterator();
while (iterator.hasNext()){
System.out.println("排序小到大:"+iterator.next());
}
//查询456之内的人
Set<String> salary = jedis.zrangeByScore("Salary", "-inf", "457");
Iterator<String> iterator1 = salary.iterator();
while (iterator1.hasNext()){
System.out.println("查询工资在456之内的人:"+iterator1.next());
}
//从大到小排序
System.out.println(jedis.zrevrange("Salary", 0, -1));
//用zcount用户获取指定区间的成员数量
long mySet = jedis.zcount("MySet", 1, 5);
System.out.println(mySet);
}
}
②:应用场景举例
a.有关排序的业务
如存储班级成绩表、部门工资表、排行榜应用、取TOP N测试等。
b.代权重执行
如普通消息与重要消息分别带上权重执行。
6、Hash(哈希)类型
①:基本指令代码
public class TestHash {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
HashMap<String,String> map = new HashMap<>();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
//添加名称为hash(key)的hash元素
jedis.hmset("hash",map);
//向名称为hash中添加key为key5,value为value5的元素
jedis.hset("hash","key5","value5");
System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));
System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));
System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));
System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash","key1","key2"));
System.out.println("散列表中的键值对的个数:"+jedis.hlen("hash"));
System.out.println("判断hash中key1是否存在:"+jedis.hexists("hash","key1"));
System.out.println("判断hash中key3是否存在:"+jedis.hexists("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));
System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key2","key4"));
}
}
②:应用场景举例
a.存储对象
b.购物车
如购物车中商品的信息存储,数量的加减(incrby),商品的单选、多选、全选(hmget(),hgetall())等。
7、Geospatial(地理位置)类型
①:基本指令代码
public class TestGeospatial {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("添加单个城市的地理位置:"+jedis.geoadd("city",104.0489567,30.39456,"chengdu"));
HashMap<String, GeoCoordinate> map = new HashMap();
//添加多个城市地址位置
map.put("pengzhou",new GeoCoordinate(103.56789,30.58623));
map.put("qionglai",new GeoCoordinate(103.27389,30.24856));
map.put("guizhou",new GeoCoordinate(106.42763,26.34561));
map.put("kunming",new GeoCoordinate(102.42963,25.02963));
System.out.println("添加多个城市地址位置:"+jedis.geoadd("city",map));
System.out.println("获取单个地址地理位置:"+jedis.geopos("city","chengdu"));
System.out.println("获取多个地址地理位置:"+jedis.geopos("city","chengdu","guizhou"));
System.out.println("计算两个城市之间的距离:"+jedis.geodist("city","chengdu","guizhou"));
System.out.println("-------------------------------");
System.out.println("以成都为中心 查询直线距离500km的城市:"+jedis.georadius("city",104.04,30.39,500, GeoUnit.KM));
System.out.println("找出指定元素类的其他元素:"+jedis.georadiusByMember("city","pengzhou",200, GeoUnit.KM));
System.out.println("--------------------------------");
System.out.println("获取全部元素:"+jedis.zrange("city",0,-1));
System.out.println("删除指定元素:"+jedis.zrem("city","kunming"));
System.out.println("获取全部元素:"+jedis.zrange("city",0,-1));
jedis.flushDB();
}
}
②:应用场景举例
a.附近的人
现在很多APP的聊天软件都有“附近的人”、“找朋友”等功能,都可以通过geospatial来 实现。
b.打车定位
现在的打车软件的记录出租车坐标位置、查询附近的出租车等功能也都可以通过geospatial相关指令来实现。
8、Hyperloglog(基数统计)类型
①:基本指令代码
基数:不重复的元素
如:A{1,3,5,7,8,7},B{1,3,5,7},基数=5.
public class TestHyperloglog {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("创建第一组元素:"+jedis.pfadd("log_one","a","b","c"));
System.out.println("统计log_one的元素个数:"+jedis.pfcount("log_one"));
System.out.println("创建第二组元素 : "+jedis.pfadd("log_two", "c", "d", "e", "f"));
System.out.println("统计第二组元素数量 : "+jedis.pfcount("log_two"));
System.out.println("合并第一组和第二组数据 : "+jedis.pfmerge("log_three", "log_one", "log_two"));
System.out.println("合并后log_three的元素数量 : "+jedis.pfcount("log_three"));
}
}
②:应用场景举例
a.统计网页的UV(Unique Visitors 独立访客)
当一个人访问网站多次时,还是算作一个人。如果用set存储大量id,会比较麻烦,因为只是做一个统计,如果用Hyperloglog,占用的内存固定,2的64次方的不同元素,只需要12kb的内存,更有优势。(Hyperloglog有0.81%错误率,可以忽略)
9、BitMap(位图)类型
①:基本指令代码
BitMap是位存储方式(只有0和1两个状态)。
public class TestBitMap {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("举例: user为用户 1,2,3,4,5,6,7 代表周一到周末 false-未打卡 true-打卡");
System.out.println(" 周一 : "+jedis.setbit("user", 1, true));
System.out.println(" 周二 : "+jedis.setbit("user", 2, true));
System.out.println(" 周三 : "+jedis.setbit("user", 3, true));
System.out.println(" 周四 : "+jedis.setbit("user", 4, true));
System.out.println(" 周五 : "+jedis.setbit("user", 5, true));
System.out.println(" 周六 : "+jedis.setbit("user", 6, false));
System.out.println(" 周日 : "+jedis.setbit("user", 7, false));
System.out.println("查看某一天是否有打卡");
System.out.println(" 查看周三 : "+jedis.getbit("user", 3));
System.out.println(" 查看周六 : "+jedis.getbit("user", 6));
System.out.println("统计打卡的天数");
System.out.println(+jedis.bitcount("user"));
jedis.flushDB();
}
}
②:应用场景举例
a.快速筛选用户
b.统计用户信息
可以通过BitMap来统计活跃与不活跃、登录与不登陆等只有两个状态的情况。
c.上班打卡情况
10、Stream(流)类型
①:简单介绍
由于Stream这种数据类型是Redis在5.0中新增加的数据类型,所以先做一个介绍。Stream是Redis推出的一种专门用来处理消息队列场景的高级数据结构。(消息队列就是队列,有着先进先出的特点,可以让我们的代码有序执行,避免出现混乱的情况)其实上面讲的List数据类型就具备了支持MQ对应的处理模型。但还出一个Stream类型,除了Redis公司为了专门做一个消息队列来抢占其他市场上的消息中间键(kafka,RabbitMQ)的市场份额以为,还完善了之前List做消息队列时的一些不足之处。
②:Stream的优点
相比与List类型的消息队列,List类型只能处理点对点的模式,但是对于Redis的发布者模式(类似于我们关注的公众号向我们发送消息的模式)有一个缺点就是消息无法持久化,如果网络断开、Redis宕机时,数据就会丢失。而且没有ack机制来确保数据的可靠性等问题。而Stream都成功解决了这个问题,在后面的代码中会有演示。
③:Stream类型包含的功能和指令
这里分为生产者和消费者两部分来介绍。
a.生产者
对于生产者来说通过xadd()指令生产消息放入队列中,且每个消息都有一个消息ID,由毫秒时间戳和序列号组成。当消息生成后可以通过xrange()获取消息队列(可指定范围)或者xrevrange()反向获取,消息ID从大到小。除此之外还有,xdel()删除消息,xlen()获取Stream消息中的长度,xtrim()限制Stream的长度,如果已经超长会进行截取。对于xread()消息的读取分为阻塞读和非堵塞读两种情况获取消息列表。
b.消费者
在消费者中有xgroup()创建消费组、xreadgroup()、xack()、xpengding()等等指令,都会在代码中进行展示。
c.四个特殊符号
- +:最小和最大可能出现的id;
$:表示消费者的最新消息;
*:用于xadd()指令中,让系统自动生成消息ID;
>:用于xreadgroup()指令,表示迄今还没有发送给使用者的消息。
④:基本指令代码(该代码中有上述所有指令的相应批注和解释)
public class TestStream {
public static void main(String[] args) {
//redis-stream 就是redis版本的MQ,消息中间键
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("-----------添加消息到队列的末尾------------");
HashMap<String, String> map = new HashMap();
HashMap<String, String> map2 = new HashMap();
HashMap<String, String> map3 = new HashMap();
HashMap<String, String> map4 = new HashMap();
map.put("name","李四");
map.put("name2","张三");
map.put("name3","王五");
map2.put("id","1");
map2.put("id2","2");
map2.put("id3","3");
map3.put("age","18");
map3.put("age2","34");
map3.put("age3","12");
System.out.println(jedis.ping());
StreamEntryID myStream1 = jedis.xadd("MyStream", StreamEntryID.NEW_ENTRY, map);
StreamEntryID myStream2 = jedis.xadd("MyStream", StreamEntryID.NEW_ENTRY, map2);
StreamEntryID myStream3 = jedis.xadd("MyStream", StreamEntryID.NEW_ENTRY, map3);
System.out.println("生成第一条信息ID:"+myStream1);
System.out.println("生成第二条信息ID:"+myStream2);
System.out.println("生成第三条信息ID:"+myStream3);
//System.out.println("输出MyStream中的值:"+jedis.xrange("MyStream",));
List<StreamEntry> myStream = jedis.xrange("MyStream", myStream1, myStream3,3);
for (StreamEntry streamEntry : myStream) {
System.out.println(streamEntry.toString());
}
//反转
System.out.println("--------反转----------");
List<StreamEntry> myStream4 = jedis.xrevrange("MyStream", myStream3, myStream1, 3);
for (StreamEntry streamEntry : myStream4) {
System.out.println(streamEntry.toString());
}
System.out.println("删除一个myStream1:"+jedis.xdel("MyStream",myStream1));
map4.put("sex","男");
//再添加一条信息
StreamEntryID myStream5 = jedis.xadd("MyStream", StreamEntryID.NEW_ENTRY, map4);
// long len = jedis.xtrim("MyStream", 2, false);
// System.out.println("截取MyStream:"+len);
System.out.println("求MyStream的长度:"+jedis.xlen("MyStream"));
System.out.println("---------读消息--------");
//在读消息时,有多种读法,我这里是通过设定读取的条数和从那条消息以后开始读来读,通过查看xread()这个方法也可以知道有多种读法
HashMap<String, StreamEntryID>Entry = new HashMap<>();
Entry.put("MyStream",myStream2);
//在myStream2以后读取一条信息
List<Map.Entry<String, List<StreamEntry>>> xread = jedis.xread(XReadParams.xReadParams().count(1), Entry);
Map.Entry<String, List<StreamEntry>> stringListEntry = xread.get(0);
List<StreamEntry> value = stringListEntry.getValue();
for (StreamEntry streamEntry : value) {
System.out.println(streamEntry);
}
//消费者分组读的目的,让组内的多个消费者共同分担读取信息,每个消费者读取部分消息,从而实现读取负载在多个消费者间是均衡分布的。
System.out.println("--------消费者分组读取收据----------");
//创建groupA、groupB两个分组
String first = jedis.xgroupCreate("MyStream", "groupA", myStream1, true);
String second = jedis.xgroupCreate("MyStream", "groupB", myStream1, true);
HashMap<String, StreamEntryID> entry = new HashMap<>();
entry.put("MyStream",StreamEntryID.UNRECEIVED_ENTRY);
System.out.println("------------ 测试groupA ---------");
//同一个组中consumer1读取的消息,consumer2、consumer3....不可再读,不同组之间成员可再读
//如下面例子所示,现在MyStream中还有3条消息,consumer1读了2条,consumer2读了1条,则consumer3读到的数据为空
System.out.println("------groupA中consumer1读两条条数据---------");
List<Map.Entry<String, List<StreamEntry>>> entries = jedis.xreadGroup("groupA", "consumer1", XReadGroupParams.xReadGroupParams().count(2), entry);
Map.Entry<String, List<StreamEntry>> stringListEntry1 = entries.get(0);
List<StreamEntry> value1 = stringListEntry1.getValue();
for (StreamEntry streamEntry : value1) {
System.out.println(streamEntry);
}
System.out.println("------groupA中consumer2读第一条数据---------");
List<Map.Entry<String, List<StreamEntry>>> entries1 = jedis.xreadGroup("groupA", "consumer2", XReadGroupParams.xReadGroupParams().count(1), entry);
Map.Entry<String, List<StreamEntry>> stringListEntry2 = entries1.get(0);
List<StreamEntry> value2 = stringListEntry2.getValue();
for (StreamEntry streamEntry : value2) {
if(streamEntry==null){
System.out.println("值为空");
}else{
System.out.println(streamEntry);
}
}
System.out.println("------groupA中consumer3读第三条数据---------");
List<Map.Entry<String, List<StreamEntry>>> entries2 = jedis.xreadGroup("groupA", "consumer3", XReadGroupParams.xReadGroupParams(), entry);
if(entries2==null){
System.out.println("数组值为空");
}else{
System.out.println("值不为空");
}
System.out.println("查看每个消费组内所有的消费者的消息(已读、未确认的)");
StreamPendingSummary xpending = jedis.xpending("MyStream", "groupA");
System.out.println("总数:"+xpending.getTotal()+" MinID: "+xpending.getMinId()+" MaxID: "+xpending.getMaxId()+" 消息: "+xpending.getConsumerMessageCount());
System.out.println("-----ack命令已经读取,已确认-----");
System.out.println("查看consumer1读了那几条数据,以streamEntryID为标识");
List<StreamPendingEntry> xpending1 = jedis.xpending("MyStream", "groupA", myStream1, myStream5, 10, "consumer1");
for (StreamPendingEntry streamPendingEntry : xpending1) {
System.out.println(streamPendingEntry.toString());
}
System.out.println("将myStream2进行ack提交:"+jedis.xack("MyStream", "groupA", myStream2));
System.out.println("---------groupA中consumer1提交ack后的查看xpending中的数据------------");
StreamPendingSummary xpending2 = jedis.xpending("MyStream", "groupA");
//在groupA中consumer1提交ack后的查看xpending中的数据时,我们会发现消息总数会减一,因为当发出ack()命令后,相当于通知Stream消息已经处理完成,否则消息不会被清楚会备案。
System.out.println("总数:"+xpending2.getTotal()+" MinID: "+xpending2.getMinId()+" MaxID: "+xpending2.getMaxId()+" 消息: "+xpending2.getConsumerMessageCount());
System.out.println("---------XINFO打印Stream/Consumer/Group详细信息---------");
System.out.println("打印Consumer信息:");
List<StreamConsumersInfo> streamConsumersInfos = jedis.xinfoConsumers("MyStream", "groupA");
for (StreamConsumersInfo streamConsumersInfo : streamConsumersInfos) {
System.out.println(streamConsumersInfo.getConsumerInfo());
}
System.out.println("打印Stream信息:");
StreamInfo myStream6 = jedis.xinfoStream("MyStream");
System.out.println(myStream6.getStreamInfo());
System.out.println("打印Group信息:");
List<StreamGroupInfo> myStream7 = jedis.xinfoGroup("MyStream");
for (StreamGroupInfo streamGroupInfo : myStream7) {
System.out.println(streamGroupInfo.getGroupInfo());
}
}
}
⑤:应用场景举例
a.消息队列
可以用于减少响应时间,降低系统的耦合性,用于一些秒杀和促销活动。
11、BitField(位域)类型
BitField位域可以将Redis字符串看作一个由二进制组成的数组,并对这个数组中任意偏移量进行访问。主要作用位域修改(get,set指令)、溢出控制(wrap、sat、fall指令)。该数据类型了解即可。
①:基本指令代码
public class TestBitField {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
System.out.println("---------BitField中的Get指令----------");
jedis.set("fieldkey","hello");
//i表示有符号,u表示无符号
List<Long> bitfield = jedis.bitfield("fieldkey", "get", "i8", "0");
List<Long> bitfield2 = jedis.bitfield("fieldkey", "get", "i8", "8");
List<Long> bitfield3 = jedis.bitfield("fieldkey", "get", "i8", "16");
List<Long> bitfield4 = jedis.bitfield("fieldkey", "get", "i8", "24");
List<Long> bitfield5 = jedis.bitfield("fieldkey", "get", "i8", "32");
for (Long aLong : bitfield) {
System.out.println(aLong.byteValue());
}
for (Long aLong : bitfield2) {
System.out.println(aLong.byteValue());
}
for (Long aLong : bitfield3) {
System.out.println(aLong.byteValue());
}
for (Long aLong : bitfield4) {
System.out.println(aLong.byteValue());
}
for (Long aLong : bitfield5) {
System.out.println(aLong.byteValue());
}
System.out.println("---------BitField中的Set指令----------");
System.out.println("将hello中的e改成x:");
//从第九位开始,将接下来8个位用有字符号数120(字母x)替换
List<Long> bitfield6 = jedis.bitfield("fieldkey", "set", "i8", "8", "120");
System.out.println(jedis.get("fieldkey"));
//不停的加
System.out.println("---------BitField中的Incrby指令----------");
//从第三位开始,对接下来的4位无符号数+1
List<Long> bitfield7 = jedis.bitfield("fieldkey", "incrby", "u4", "2", "1");
for (Long aLong : bitfield7) {
System.out.println(aLong.byteValue());
}
jedis.bitfield("fieldkey", "incrby", "u4", "2", "1");
jedis.bitfield("fieldkey", "incrby", "u4", "2", "1");
jedis.bitfield("fieldkey", "incrby", "u4", "2", "1");
jedis.bitfield("fieldkey", "incrby", "u4", "2", "1");
List<Long> bitfield8 = jedis.bitfield("fieldkey", "incrby", "u4", "2", "1");
//不停的加,产出数值。这时默认情况下,incrby使用wrap参数
//wrap采用回绕方法处理有符号和无符号整数溢出情况
System.out.println("-------incrby使用wrap参数----");
for (Long aLong : bitfield8) {
System.out.println(aLong.byteValue());
}
}
}
②:应用场景举例
a.位域修改和溢出控制
五、总结
到这儿也就是结束了关于这十大数据类型的介绍。那也做个总结吧,其实从刚开始学Redis到写完这篇文章是花了很多时间的,并不是看一遍就能写的。大多数小伙伴也知道现在市面上关于Redis的讲解大都是在Linux系统上操作指令的,但是我们真实的运用肯定是用的Java代码。所以博主也是看了很多视频、官网资料才写完这部分关于Redis的数据类型的介绍。如果这篇文章对各位小伙伴有帮助,莫忘了点赞、收藏哦!