传统关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名.字段数据类型.字段约束等等信息,插入的数据必须遵守这些约束。
NoSql则对数据库格式没有严格约束,往往形式松散,自由。可以是键值型,也可以是文档型,甚至可以是图格式。
传统数据库的表与表之间往往存在关联,例如外键;
非关系型数据库不存在关联关系,要维护关系要么靠代码中的业务逻辑,要么靠数据之间的耦合。
{
id: 1,
name: "张三",
orders: [
{
id: 1,
item: {
id: 10, title: "荣耀6", price: 4999
}
},
{
id: 2,
item: {
id: 20, title: "小米11", price: 3999
}
}
]
}
传统关系型数据库会基于Sql语句做查询,语法有统一标准;
不同的非关系数据库查询语法差异极大,五花八门各种各样。
传统关系型数据库能满足事务ACID的原则;
非关系型数据库往往不支持事务,或者不能严格保证ACID的特性,只能实现基本的一致性。
除了上述四点以外,在存储方式.扩展性.查询性能上关系型与非关系型也都有着显著差异,总结如下:
SQL | NoSQL | |
---|---|---|
数据结构 | 结构化(Structured) | 非结构化 |
数据关联 | 关联的(Relational) | 无关联 |
查询方式 | SQL查询 | 非SQL |
事务特性 | ACID | BASE |
Redis诞生于2009年全称是Remote Dictionary Server,远程词典服务器,是一个基于内存的键值型NoSQL数据库。这里有两个关键字:
其中键值型,是指Redis中存储的数据都是以key.value对的形式存储,而value的形式多种多样,可以是字符串.数值.甚至json,而NoSql则是相对于传统关系型数据库而言,有很大差异的一种数据库。
对于存储的数据,没有类似Mysql那么严格的约束,比如唯一性,是否可以为null等等,所以我们把这种松散结构的数据库,称之为NoSQL数据库。
特征:
Redis的官方网站地址:https://redis.io/
大多数企业都是基于Linux服务器来部署项目,而且Redis官方也没有提供Windows版本的安装包。因此,此处是基于Linux系统来安装的,选择的Linux版本为CentOS 7.
Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖:
yum install -y gcc tcl
将Redis安装包上传到虚拟机的任意目录:例如,放到/usr/local/src 目录:
解压缩:
tar -xzf redis-6.2.6.tar.gz
解压后进入redis目录:
cd redis-6.2.6
运行编译命令:
make && make install
如果没有出错,应该就安装成功了。
默认的安装路径是在 /usr/local/bin
目录下:
该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:
redis的启动方式有很多种,例如:
安装完成后,在任意目录输入redis-server命令即可启动Redis:
redis-server
这种启动属于前台启动
,会阻塞整个会话窗口,窗口关闭或者按下CTRL + C
则Redis停止。不推荐使用。
如果要让Redis以后台
方式启动,则必须修改Redis配置文件,就在我们之前解压的redis安装包下(/usr/local/src/redis-6.2.6
),名字叫redis.conf;我们先将这个配置文件备份一份:
cp redis.conf redis.conf.bck
然后修改redis.conf文件中的一些配置:
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123321
Redis的其它常见配置:
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志.持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
启动Redis:
# 进入redis安装目录
cd /usr/local/src/redis-6.2.6
# 启动
redis-server redis.conf
停止服务:
# 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务,
# 因为之前配置了密码,因此需要通过 -u 来指定密码
redis-cli -u 123321 shutdown
我们也可以通过配置来实现开机自启。
首先,新建一个系统服务文件:
vi /etc/systemd/system/redis.service
内容如下:
[Unit]
Description=redis-server
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true
[Install]
WantedBy=multi-user.target
然后重载系统服务:
systemctl daemon-reload
现在,我们可以用下面这组命令来操作redis了:
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
执行下面的命令,可以让redis开机自启:
systemctl enable redis
安装完成Redis,我们就可以操作Redis,实现数据的CRUD了。这需要用到Redis客户端,包括:
Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:
redis-cli [options] [commonds]
其中常见的options有:
-h 127.0.0.1
:指定要连接的redis节点的IP地址,默认是127.0.0.1-p 6379
:指定要连接的redis节点的端口,默认是6379-a 123321
:指定redis的访问密码其中的commonds就是Redis的操作命令,例如:
ping
:与redis服务端做心跳测试,服务端正常会返回pong
不指定commond时,会进入redis-cli
的交互控制台:
GitHub上的大神编写了Redis的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager
不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包。
在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases
在下载的而文件中找到Redis的图形化桌面客户端;
解压缩后,运行安装程序即可安装;
安装完成后,在安装目录下找到rdm.exe文件;双击即可运行:
点击左上角的连接到Redis服务器
按钮,在弹出的窗口中填写Redis服务信息,点击确定后,在左侧菜单会出现一个链接,点击即可建立连接了。
Redis默认有16个仓库,编号从0至15. 通过配置文件可以设置仓库数量,但是不超过16,并且不能自定义仓库名称。
如果是基于redis-cli连接Redis服务,可以通过select命令来选择数据库:
# 选择 0号库
select 0
通用指令是部分数据类型的,都可以使用的指令,常见的有:
通过help [command] 可以查看一个命令的具体用法,例如:
# 查看keys命令的帮助信息:
127.0.0.1:6379> help keys
KEYS pattern
summary: Find all keys matching the given pattern
since: 1.0.0
group: generic
String类型,也就是字符串类型,是Redis中最简单的存储类型。
其value是字符串,不过根据字符串的格式不同,又可以分为3类:
String的常见命令有:
redisTemplate.opsForValue().set("BBB","你好");
redisTemplate.opsForValue().set("BBB","你好", Duration.ofMinutes(1));
redisTemplate.opsForValue().set("BBB","你好", 1, TimeUnit.MINUTES);
redisTemplate.opsForValue().append("AAA", "哈哈哈");
redisTemplate.opsForValue().set("BBB","您的",1);
redisTemplate.opsForValue().setBit("BBB", 0, true);
redisTemplate.opsForValue().getBit("BBB", 1);
redisTemplate.opsForValue().setIfAbsent("BBB", "好的");
redisTemplate.opsForValue().setIfAbsent("AAA", "好的", 1, TimeUnit.MINUTES);
redisTemplate.opsForValue().setIfAbsent("BBB", "好的", Duration.ofMinutes(1));
redisTemplate.opsForValue().setIfPresent("BBB", "好的");
redisTemplate.opsForValue().setIfPresent("BBB", "好的",1, TimeUnit.MINUTES);
redisTemplate.opsForValue().setIfPresent("BBB", "好的",Duration.ofMinutes(1));
redisTemplate.opsForValue().getAndSet("BBB", "心情");
redisTemplate.opsForValue().increment("AAA");
redisTemplate.opsForValue().increment("AAA",2);
redisTemplate.opsForValue().increment("AAA", 3.2);
redisTemplate.opsForValue().decrement("AAA");
redisTemplate.opsForValue().decrement("AAA",2);
redisTemplate.opsForValue().size("BBB");
String BBB = (String) redisTemplate.opsForValue().get("BBB");
System.out.println("BBB = " + BBB);
String BBB = redisTemplate.opsForValue().get("BBB",0,1);
System.out.println("BBB = " + BBB);
multiSet(Map extends K, ? extends V> var1):将map中的key分别作为不同的key存到Redis中
Map valueMap = new HashMap();
valueMap.put("valueMap1","aa");
valueMap.put("valueMap2","bb");
valueMap.put("valueMap3","cc");
valueMap.put("valueMap4","ee");
redisTemplate.opsForValue().multiSet(valueMap);
multiSetIfAbsent(Map extends K, ? extends V> var1):将map中的key分别作为不同的key存到Redis中
Map valueMap = new HashMap();
valueMap.put("valueMap1","aa");
valueMap.put("valueMap2","bb");
valueMap.put("valueMap3","cc");
valueMap.put("valueMap4","ee");
valueMap.put("valueMap5","ff");
redisTemplate.opsForValue().multiSetIfAbsent(valueMap);
List paraList = new ArrayList();
paraList.add("valueMap1");
paraList.add("valueMap2");
paraList.add("valueMap3");
List list = redisTemplate.opsForValue().multiGet(paraList);
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便。
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD。
Hash类型的常见命令
put(H var1, HK var2, HV var3):新增hashMap值
redisTemplate.opsForHash().put("hashValue","map1","value1");
redisTemplate.opsForHash().put("hashValue","map2","value2");
Object o = redisTemplate.opsForHash().get("hashValue", "map1");
System.out.println("o = " + o);
Map hashValue = redisTemplate.opsForHash().entries("hashValue");
System.out.println("hashValue = " + hashValue);
Set hashValue = redisTemplate.opsForHash().keys("hashValue");
System.out.println("hashValue = " + hashValue);
List hashValue = redisTemplate.opsForHash().values("hashValue");
System.out.println("hashValue = " + hashValue);
Boolean aBoolean = redisTemplate.opsForHash().hasKey("hashValue", "map1");
System.out.println("aBoolean = " + aBoolean);
Long hashValue = redisTemplate.opsForHash().size("hashValue");
System.out.println("hashValue = " + hashValue);
redisTemplate.opsForHash().putIfAbsent("hashValue", "map3", "value3");
Map newMap = new HashMap();
newMap.put("map4","map4");
newMap.put("map5","map5");
redisTemplate.opsForHash().putAll("hashValue",newMap);
List list = new ArrayList<>();
list.add("map1");
list.add("map2");
List hashValue = redisTemplate.opsForHash().multiGet("hashValue", list);
System.out.println("hashValue = " + hashValue);
Long aLong = redisTemplate.opsForHash().lengthOfValue("hashValue", "map1");
System.out.println("aLong = " + aLong);
Long increment = redisTemplate.opsForHash().increment("hashValue", "map7", 1);
System.out.println("increment = " + increment);
Double increment = redisTemplate.opsForHash().increment("hashValue", "map8", 1.2);
System.out.println("increment = " + increment);
Cursor<Map.Entry<Object,Object>> cursor = redisTemplate.opsForHash().scan("hashValue",ScanOptions.scanOptions().match("map1").build());
//Cursor> cursor = redisTemplate.opsForHash().scan("hashValue",ScanOptions.NONE);
while (cursor.hasNext()) {
Map.Entry<Object, Object> entry = cursor.next();
System.out.println("entry.getKey() = " + entry.getKey());
System.out.println("entry.getValue() = " + entry.getValue());
}
Long delete = redisTemplate.opsForHash().delete("hashValue", "map1", "map2");
System.out.println("delete = " + delete);
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征也与LinkedList类似:
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
List的常见命令有:
redisTemplate.opsForList().leftPush("list","a");
redisTemplate.opsForList().leftPush("list","a");
redisTemplate.opsForList().leftPush("list","b");
leftPush(K key, V v, V v1):从集合左边开始在v值后边插入新值v1
redisTemplate.opsForList().leftPush("list", "a", "e");
List<String> strings = Arrays.asList("j", "q", "k");
redisTemplate.opsForList().leftPushAll("list", strings);
redisTemplate.opsForList().leftPushAll("list", "j", "q", "k");
redisTemplate.opsForList().leftPushIfPresent("list", "j");
redisTemplate.opsForList().leftPop("list");
redisTemplate.opsForList().leftPop("list",1,TimeUnit.MINUTES);
redisTemplate.opsForList().rightPopAndLeftPush("list", "list2");
rightPopAndLeftPush(K k1, K k2, long timeout, TimeUnit unit):指定过期时间后,移除k1中最右的值,并将移除的值插入k2中最左侧(同上)
redisTemplate.opsForList().rightPopAndLeftPush("list", "list2",1,TimeUnit.MINUTES);
redisTemplate.opsForList().rightPush("rightList",'a');
redisTemplate.opsForList().rightPush("rightList",'b');
redisTemplate.opsForList().rightPush("rightList",'c');
redisTemplate.opsForList().rightPush("rightList", "b", "e");
redisTemplate.opsForList().rightPushAll("rightList", "e", "f","g");
List<String> strings = Arrays.asList("j", "q", "k");
redisTemplate.opsForList().rightPushAll("rightList", strings);
redisTemplate.opsForList().rightPushIfPresent("rightList", "a");
redisTemplate.opsForList().rightPop("rightList");
redisTemplate.opsForList().rightPop("rightList",1,TimeUnit.MINUTES);
String string1 = (String) redisTemplate.opsForList().index("rightList", 2);
System.out.println("string1 = " + string1);
Long size = redisTemplate.opsForList().size("rightList");
System.out.println("size = " + size);
redisTemplate.opsForList().set("rightList",2,"e");
trim(K key, long long1, long long2):截取下标long1和long2之间的值,包括long1和long2对应的值,并将其保留为key对应的新值
左侧坐标从0开始,右侧从-1开始
当long1超过坐标时(此时与long2无关),都会截取为空,key会被删除
当long1为负时(此时与long2无关),都会截取为空,key会被删除
当long1为正且在下标存在其中,long2为负数时,只要两个没有重叠,相当于去左去右,保留了中间的部分
当long1为正且在下标存在其中,long2为负数时,只要两个交叉重叠,截取为空
redisTemplate.opsForList().trim("rightList",1,3);
List<Object> list = redisTemplate.opsForList().range("rightList", 0, -1);//获取所有值
remove(K key, long count, Object value):从存储在键中的列表中删除等于值的元素的第一个计数事件。
redisTemplate.opsForList().remove("rightList", 0, "c");
Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:
Set类型的常见命令
redisTemplate.opsForSet().add("set", "aa", "bb", "cc");
redisTemplate.opsForSet().add("set", "ee");
Set set = redisTemplate.opsForSet().members("set");
System.out.println("set = " + set);
Long set = redisTemplate.opsForSet().size("set");
System.out.println("set = " + set);
Object set = redisTemplate.opsForSet().randomMember("set");
System.out.println("set = " + set);
List set = redisTemplate.opsForSet().randomMembers("set", 2);
System.out.println("set = " + set);
Boolean member = redisTemplate.opsForSet().isMember("set", "aa");
System.out.println("member = " + member);
Set set = redisTemplate.opsForSet().distinctRandomMembers("set", 3);
System.out.println("set = " + set);
Boolean move = redisTemplate.opsForSet().move("set", "aa", "set2");
System.out.println("move = " + move);
Object set = redisTemplate.opsForSet().pop("set");
System.out.println("set = " + set);
List set = redisTemplate.opsForSet().pop("set", 2);
System.out.println("set = " + set);
redisTemplate.opsForSet().remove("set", "cc","aa");
Cursor<Object> cursor = redisTemplate.opsForSet().scan("set", ScanOptions.scanOptions().match("ee").build());
while (cursor.hasNext()){
Object object = cursor.next();
System.out.println("object = " + object);
}
List list = new ArrayList();
list.add("set2");
Set set = redisTemplate.opsForSet().difference("set", list);
System.out.println("set = " + set);
Set difference = redisTemplate.opsForSet().difference("set", "set2");
System.out.println("difference = " + difference);
Long aLong = redisTemplate.opsForSet().differenceAndStore("set", "set2", "set3");
System.out.println("aLong = " + aLong);
differenceAndStore(K key, Collection otherKeys, K destKey):获取key与另外一些otherKeys集合之间的差值,并将结果存入指定的destKey中
List list = new ArrayList();
list.add("set2");
Long aLong = redisTemplate.opsForSet().differenceAndStore("set", list, "set3");
System.out.println("aLong = " + aLong);
Set intersect = redisTemplate.opsForSet().intersect("set", "set2");
System.out.println("intersect = " + intersect);
List list = new ArrayList();
list.add("set2");
list.add("set3");
Set set = redisTemplate.opsForSet().intersect("set", list);
System.out.println("set = " + set);
intersectAndStore(K key, K otherKey, K destKey):获取key集合与另一个otherKey集合之间的交集元素,并将其放入指定的destKey集合中
Long aLong = redisTemplate.opsForSet().intersectAndStore("set", "set2", "set4");
System.out.println("aLong = " + aLong);
intersectAndStore(K key, Collection otherKeys, K destKey):获取key集合与其他otherKeys集合之间的交集元素,并将其放入指定的destKey集合中
List list = new ArrayList();
list.add("set2");
Long aLong = redisTemplate.opsForSet().intersectAndStore("set", list, "set5");
System.out.println("aLong = " + aLong);
Set union = redisTemplate.opsForSet().union("set", "set2");
System.out.println("union = " + union);
List list = new ArrayList();
list.add("set2");
Set union = redisTemplate.opsForSet().union("set", list);
System.out.println("union = " + union);
unionAndStore(K key, K otherKey, K destKey):获取两个集合之间的合集,并放入指定key对应的新集合中
Long aLong = redisTemplate.opsForSet().unionAndStore("se3", "set2", "set4");
System.out.println("aLong = " + aLong);
unionAndStore(K key, Collection otherKeys, K destKey):获取多个集合之间的合集,并放入指定key对应的新集合中
List list = new ArrayList();
list.add("set2");
list.add("set3");
redisTemplate.opsForSet().unionAndStore("set", list, "set4");
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。
SortedSet具备下列特性:
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
SortedSet的常见命令有:
redisTemplate.opsForZSet().add("zSet", "aaa", 1);
redisTemplate.opsForZSet().add("zSet", "bbb", 2);
redisTemplate.opsForZSet().add("zSet", "ccc", 3);
ZSetOperations.TypedTuple<String> objectTypedTuple1 = new DefaultTypedTuple<>("eee",9.6);
ZSetOperations.TypedTuple<String> objectTypedTuple2 = new DefaultTypedTuple<>("fff",1.5);
ZSetOperations.TypedTuple<String> objectTypedTuple3 = new DefaultTypedTuple<>("ggg",7.4);
Set<ZSetOperations.TypedTuple<String>> typles = new HashSet<>();
typles.add(objectTypedTuple1);
typles.add(objectTypedTuple2);
typles.add(objectTypedTuple3);
redisTemplate.opsForZSet().add("zSet", typles);
Cursor<ZSetOperations.TypedTuple<Object>> cursor = redisTemplate.opsForZSet().scan("zSetValue", ScanOptions.NONE);
while (cursor.hasNext()){
ZSetOperations.TypedTuple<Object> typedTuple = cursor.next();
System.out.println("通过scan(K key, ScanOptions options)方法获取匹配元素:" + typedTuple.getValue() + "--->" + typedTuple.getScore());
}
double incrementScore = redisTemplate.opsForZSet().incrementScore("zSetValue","C",5);
System.out.print("通过incrementScore(K key, V value, double delta)方法修改变量中的元素的分值:" + incrementScore);
score = redisTemplate.opsForZSet().score("zSetValue","C");
System.out.print(",修改后获取元素的分值:" + score);
zSetValue = redisTemplate.opsForZSet().range("zSetValue",0,-1);
System.out.println(",修改后排序的元素:" + zSetValue);
Double score = redisTemplate.opsForZSet().score("zSet", "aaa");
System.out.println("score = " + score);
Long zSet = redisTemplate.opsForZSet().size("zSet");
System.out.println("zSet = " + zSet);
Long zSet = redisTemplate.opsForZSet().zCard("zSet");
System.out.println("zSet = " + zSet);
long count = redisTemplate.opsForZSet().count("zSetValue",1,5);
System.out.println("通过count(K key, double min, double max)方法获取区间值的个数:" + count);
Set zSet = redisTemplate.opsForZSet().range("zSet", 0, 2);
System.out.println("zSet = " + zSet);
Set zSetValue = redisTemplate.opsForZSet().range("zSetValue",0,-1);
System.out.println("通过range(K key, long start, long end)方法获取指定区间的元素:" + zSetValue);
RedisZSetCommands.Range range = new RedisZSetCommands.Range();
//range.gt("A");
range.lt("D");
zSetValue = redisTemplate.opsForZSet().rangeByLex("zSetValue", range);
System.out.println("通过rangeByLex(K key, RedisZSetCommands.Range range)方法获取满足非score的排序取值元素:" + zSetValue);
RedisZSetCommands.Limit limit = new RedisZSetCommands.Limit();
limit.count(2);
//起始下标为0
limit.offset(1);
zSetValue = redisTemplate.opsForZSet().rangeByLex("zSetValue", range,limit);
System.out.println("通过rangeByLex(K key, RedisZSetCommands.Range range, RedisZSetCommands.Limit limit)方法获取满足非score的排序取值元素:" + zSetValue);
Set zSet = redisTemplate.opsForZSet().rangeByScore("zSet", 1, 5);
System.out.println("zSet = " + zSet);
Set zSet = redisTemplate.opsForZSet().rangeByScore("zSet", 1, 5, 1, 2);
System.out.println("zSet = " + zSet);
Set<ZSetOperations.TypedTuple<Object>> typedTupleSet = redisTemplate.opsForZSet().rangeWithScores("typedTupleSet",1,3);
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTupleSet.iterator();
while (iterator.hasNext()){
ZSetOperations.TypedTuple<Object> typedTuple = iterator.next();
Object value = typedTuple.getValue();
double score = typedTuple.getScore();
System.out.println("通过rangeWithScores(K key, long start, long end)方法获取RedisZSetCommands.Tuples的区间值:" + value + "---->" + score );
}
Set<ZSetOperations.TypedTuple<Object>> typedTupleSet = redisTemplate.opsForZSet().rangeByScoreWithScores("typedTupleSet",5,8);
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTupleSet.iterator();
while (iterator.hasNext()){
ZSetOperations.TypedTuple<Object> typedTuple = iterator.next();
Object value = typedTuple.getValue();
double score = typedTuple.getScore();
System.out.println("通过rangeByScoreWithScores(K key, double min, double max)方法获取RedisZSetCommands.Tuples的区间值通过分值:" + value + "---->" + score );
}
Set<ZSetOperations.TypedTuple<Object>> typedTupleSet = redisTemplate.opsForZSet().rangeByScoreWithScores("typedTupleSet",5,8,1,1);
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTupleSet.iterator();
while (iterator.hasNext()){
ZSetOperations.TypedTuple<Object> typedTuple = iterator.next();
Object value = typedTuple.getValue();
double score = typedTuple.getScore();
System.out.println("通过rangeByScoreWithScores(K key, double min, double max, long offset, long count)方法获取RedisZSetCommands.Tuples的区间值从给定下标和给定长度获取最终值通过分值:" + value + "---->" + score );
}
Long rank = redisTemplate.opsForZSet().rank("zSet", "aaa");
System.out.println("rank = " + rank);
Long rank = redisTemplate.opsForZSet().reverseRank("zSet", "eee");
System.out.println("rank = " + rank);
Set zSet = redisTemplate.opsForZSet().reverseRange("zSet", 0, 3);
System.out.println("zSet = " + zSet);
Set zSetValue = redisTemplate.opsForZSet().reverseRangeByScore("zSetValue",1,5);
System.out.println("通过reverseRangeByScore(K key, double min, double max)方法倒序排列指定分值区间元素:" + zSetValue);
Set zSetValue = redisTemplate.opsForZSet().reverseRangeByScore("zSetValue",1,5,1,2);
System.out.println("通过reverseRangeByScore(K key, double min, double max, long offset, long count)方法倒序排列从给定下标和给定长度分值区间元素:" + zSetValue);
Set<ZSetOperations.TypedTuple<Object>> typedTupleSet = redisTemplate.opsForZSet().reverseRangeByScoreWithScores("zSetValue",1,5);
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTupleSet.iterator();
while (iterator.hasNext()){
ZSetOperations.TypedTuple<Object> typedTuple = iterator.next();
Object value = typedTuple.getValue();
double score1 = typedTuple.getScore();
System.out.println("通过reverseRangeByScoreWithScores(K key, double min, double max)方法倒序排序获取RedisZSetCommands.Tuples的区间值:" + value + "---->" + score1 );
}
Set<ZSetOperations.TypedTuple<Object>> typedTupleSet = redisTemplate.opsForZSet().reverseRangeByScoreWithScores("zSetValue",1,5,1,2);
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTupleSet.iterator();
while (iterator.hasNext()){
ZSetOperations.TypedTuple<Object> typedTuple = iterator.next();
Object value = typedTuple.getValue();
double score1 = typedTuple.getScore();
System.out.println("通过reverseRangeByScoreWithScores(K key, double min, double max, long offset, long count)方法倒序排序获取RedisZSetCommands.Tuples的从给定下标和给定长度区间值:" + value + "---->" + score1 );
}
Set<ZSetOperations.TypedTuple<Object>> typedTupleSet = redisTemplate.opsForZSet().reverseRangeWithScores("zSetValue",1,5);
Iterator<ZSetOperations.TypedTuple<Object>> iterator = typedTupleSet.iterator();
while (iterator.hasNext()){
ZSetOperations.TypedTuple<Object> typedTuple = iterator.next();
Object value = typedTuple.getValue();
double score1 = typedTuple.getScore();
System.out.println("通过reverseRangeWithScores(K key, long start, long end)方法索引倒序排列区间值:" + value + "----->" + score1);
}
redisTemplate.opsForZSet().intersectAndStore("zSetValue","typedTupleSet","intersectSet");
Set zSetValue = redisTemplate.opsForZSet().range("intersectSet",0,-1);
System.out.println("通过intersectAndStore(K key, K otherKey, K destKey)方法获取2个变量的交集存放到第3个变量里面:" + zSetValue);
List list = new ArrayList();
list.add("typedTupleSet");
redisTemplate.opsForZSet().intersectAndStore("zSetValue",list,"intersectListSet");
Set zSetValue = redisTemplate.opsForZSet().range("intersectListSet",0,-1);
System.out.println("通过intersectAndStore(K key, Collection otherKeys, K destKey)方法获取多个变量的交集存放到第3个变量里面:" + zSetValue);
redisTemplate.opsForZSet().unionAndStore("zSetValue","typedTupleSet","unionSet");
Set zSetValue = redisTemplate.opsForZSet().range("unionSet",0,-1);
System.out.println("通过unionAndStore(K key, K otherKey, K destKey)方法获取2个变量的交集存放到第3个变量里面:" + zSetValue);
List list = new ArrayList();
list.add("typedTupleSet");
redisTemplate.opsForZSet().unionAndStore("zSetValue",list,"unionListSet");
Set zSetValue = redisTemplate.opsForZSet().range("unionListSet",0,-1);
System.out.println("通过unionAndStore(K key, Collection otherKeys, K destKey)方法获取多个变量的交集存放到第3个变量里面:" + zSetValue);
long removeCount = redisTemplate.opsForZSet().remove("unionListSet","A","B");
Set zSetValue = redisTemplate.opsForZSet().range("unionListSet",0,-1);
System.out.print("通过remove(K key, Object... values)方法移除元素的个数:" + removeCount);
System.out.println(",移除后剩余的元素:" + zSetValue);
long removeCount = redisTemplate.opsForZSet().removeRange("unionListSet",3,5);
Set zSetValue = redisTemplate.opsForZSet().range("unionListSet",0,-1);
System.out.print("通过removeRange(K key, long start, long end)方法移除元素的个数:" + removeCount);
System.out.println(",移除后剩余的元素:" + zSetValue);
long removeCount = redisTemplate.opsForZSet().removeRangeByScore("unionListSet",3,5);
Set zSetValue = redisTemplate.opsForZSet().range("unionListSet",0,-1);
System.out.print("通过removeRangeByScore(K key, double min, double max)方法移除元素的个数:" + removeCount);
System.out.println(",移除后剩余的元素:" + zSetValue);
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可,例如:
/***
* 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中。
* @param key redis的key
* @param longitude 经度
* @param latitude 纬度
* @param name 该坐标的名称(标识)
* @return
*/
public Long geoAdd(String key, double longitude, double latitude, String name) {
Long addedNum = redisTemplate.opsForGeo().add(key, new Point(longitude, latitude), name);
return addedNum;
}
/***
* 从key里返回所有给定位置元素的位置(经度和纬度)。
* @param key redis的key
* @param nameList 坐标名称(标识)的集合
*/
public List<Point> geoGet(String key, List<String> nameList) {
List<Point> points = redisTemplate.opsForGeo().position(key, nameList);
return points;
}
/***
* 【获取两个坐标之间的距离】
* 根据redis中键名(key)中,名字为 name1 和 name2 两个坐标的距离
* @param key redis的key
* @param name1 坐标名称(标识)1
* @param name2 坐标名称(标识)2
* @return distance(单位米)
*/
public double geoGetDistance(String key, String name1, String name2) {
double distance = redisTemplate.opsForGeo()
.distance(key, name1, name2, RedisGeoCommands.DistanceUnit.METERS).getValue();
return distance;
}
/***
* 【获取指定范围内的坐标】
* 以给定的经纬度为中心画圆, 返回键(key)包含的位置元素当中,
* 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
* @param key redis的key
* @param longitude 经度
* @param latitude 纬度
* @param distance 距离(单位:米)
* @param count 如果 count > 0 则最多返回count个坐标, 否则返回所有
* @return
*/
public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoGetCoordinatesWithinRange(String key, double longitude, double latitude,Integer distance,Integer count) {
//以当前坐标为中心画圆,标识当前坐标覆盖的distance的范围, Point(经度, 纬度) Distance(距离量, 距离单位)
Circle circle = new Circle(new Point(longitude, latitude), new Distance(distance, RedisGeoCommands.DistanceUnit.METERS));
// 从redis获取的信息包含:距离中心坐标的距离、当前的坐标、并且升序排序,如果count > 0 则只取count个坐标,否则返回所有
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending();
if (count > 0) {
args.limit(count);
}
GeoResults<RedisGeoCommands.GeoLocation<Object>> radius = redisTemplate.opsForGeo().radius(key, circle, args);
return radius;
}
/***
* 【获取指定范围内的坐标】
* 以给定的键(key)中的坐标名字(标识)name为中心画圆, 返回键包含的位置元素当中,
* 与中心的距离不超过给定最大距离的所有位置元素,并给出所有位置元素与中心的平均距离。
* @param key redis的key
* @param name 坐标名称(标识)
* @param distance 距离
* @param count 如果 count > 0 则最多返回count个坐标, 否则返回所有
* @return
*/
public GeoResults<RedisGeoCommands.GeoLocation<Object>> geoGetCoordinatesWithinRange(String key,String name, Integer distance, Integer count) {
// 创建距离对象
Distance distances = new Distance(distance, RedisGeoCommands.DistanceUnit.METERS);
// 需要从redis获取的参数
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending();
if (count > 0) {
args.limit(count);
}
GeoResults<RedisGeoCommands.GeoLocation<Object>> radius = redisTemplate.opsForGeo()
.radius(key, name, distances, args);
return radius;
}
/***
* 返回一个或多个位置元素的 Geohash 表示
* @param key redis的key
* @param nameList 名称的集合
*/
public List<String> geoHash(String key, List<String> nameList) {
List<String> results = redisTemplate.opsForGeo().hash(key, nameList);
return results;
}
在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/
其中Java客户端也包含很多:
标记为❤的就是推荐使用的java客户端,包括:
Jedis的官网地址: https://github.com/redis/jedis
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.7.0version>
dependency>
<dependency>
<groupId>org.junit.jupitergroupId>
<artifactId>junit-jupiterartifactId>
<version>5.7.0version>
<scope>testscope>
dependency>
新建一个单元测试类,内容如下:
private Jedis jedis;
@BeforeEach
void setUp() {
// 1.建立连接
jedis = new Jedis("127.0.0.1",6379);
// 2.设置密码
jedis.auth("123321");
// 3.选择库
jedis.select(0);
}
@Test
void testString() {
// 存入数据
String result = jedis.set("name", "虎哥");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}
@Test
void testHash() {
// 插入hash数据
jedis.hset("user:1", "name", "Jack");
jedis.hset("user:1", "age", "21");
// 获取
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
Jedis使用的基本步骤:
Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式
public class JedisConnectionFacotry {
private static final JedisPool jedisPool;
static {
//配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(8);
poolConfig.setMaxIdle(8);
poolConfig.setMinIdle(0);
poolConfig.setMaxWaitMillis(1000);
//创建连接池对象
jedisPool = new JedisPool(poolConfig, "192.168.150.101",6379,1000,"123321");
}
public static Jedis getJedis(){
return jedisPool.getResource();
}
}
代码说明:
1) JedisConnectionFacotry:工厂设计模式是实际开发中非常常用的一种设计模式,我们可以使用工厂,去降低代的耦合,比如Spring中的Bean的创建,就用到了工厂设计模式
2)静态代码块:随着类的加载而加载,确保只能执行一次,我们在加载当前工厂类的时候,就可以执行static的操作完成对 连接池的初始化
3)最后提供返回连接池中连接的方法.
代码说明:
1.在我们完成了使用工厂设计模式来完成代码的编写之后,我们在获得连接时,就可以通过工厂来获得,而不用直接去new对象,降低耦合,并且使用的还是连接池对象。
2.当我们使用了连接池后,当我们关闭连接其实并不是关闭,而是将Jedis还回连接池的。
@BeforeEach
void setUp(){
//建立连接
/*jedis = new Jedis("127.0.0.1",6379);*/
jedis = JedisConnectionFacotry.getJedis();
//选择库
jedis.select(0);
}
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis
SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中。
API | 返回值类型 | 说明 |
---|---|---|
redisTemplate.opsForValue() | ValueOperations | 操作String类型数据 |
redisTemplate.opsForHash() | HashOperations | 操作Hash类型数据 |
redisTemplate.opsForList() | ListOperations | 操作List类型数据 |
redisTemplate.opsForSet() | SetOperations | 操作Set类型数据 |
redisTemplate.opsForZSet() | ZSetOperations | 操作SortedSet类型数据 |
redisTemplate | 通用的命令 |
SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
spring:
redis:
host: 192.168.150.101
port: 6379
password: 123321
lettuce:
pool:
max-active: 8 #最大连接
max-idle: 8 #最大空闲连接
min-idle: 0 #最小空闲连接
max-wait: 100ms #连接等待时间
因为有了SpringBoot的自动装配,我们可以拿来就用:
@SpringBootTest
class RedisStringTests {
@Autowired
private RedisTemplate redisTemplate;
}
@SpringBootTest
class RedisStringTests {
@Autowired
private RedisTemplate edisTemplate;
@Test
void testString() {
// 写入一条String数据
redisTemplate.opsForValue().set("name", "虎哥");
// 获取string数据
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}
}
SpringDataRedis的使用步骤:
RedisTemplate可以接收任意Object作为值写入Redis。
只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的
缺点:
我们可以自定义RedisTemplate的序列化方式,代码如下:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}
这里采用了JSON序列化来代替默认的JDK序列化方式。
整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。
Spring默认提供了一个StringRedisTemplate类,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:
@SpringBootTest
class RedisStringTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void testString() {
// 写入一条String数据
stringRedisTemplate.opsForValue().set("verify:phone:13600527634", "124143");
// 获取string数据
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("虎哥", 21);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:200", json);
// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}
}
方案一:
方案二: