Redis --- 基础篇

一、初识Redis

1.1、认识NoSQL

  • NoSql 可以翻译做Not Only Sql(不仅仅是SQL),或者是No Sql(非Sql的)数据库。是相对于传统关系型数据库而言,有很大差异的一种特殊的数据库,因此也称之为非关系型数据库

1.1.1、结构化与非结构化

  • 传统关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名.字段数据类型.字段约束等等信息,插入的数据必须遵守这些约束。

  • NoSql则对数据库格式没有严格约束,往往形式松散,自由。可以是键值型,也可以是文档型,甚至可以是图格式。

1.1.2、关联和非关联

  • 传统数据库的表与表之间往往存在关联,例如外键;

  • 非关系型数据库不存在关联关系,要维护关系要么靠代码中的业务逻辑,要么靠数据之间的耦合。

{
  id: 1,
  name: "张三",
  orders: [
    {
       id: 1,
       item: {
	 id: 10, title: "荣耀6", price: 4999
       }
    },
    {
       id: 2,
       item: {
	 id: 20, title: "小米11", price: 3999
       }
    }
  ]
}

1.1.3、查询方式

  • 传统关系型数据库会基于Sql语句做查询,语法有统一标准;

  • 不同的非关系数据库查询语法差异极大,五花八门各种各样。

1.1.4、事务

  • 传统关系型数据库能满足事务ACID的原则;

  • 非关系型数据库往往不支持事务,或者不能严格保证ACID的特性,只能实现基本的一致性。

1.1.5、总结

除了上述四点以外,在存储方式.扩展性.查询性能上关系型与非关系型也都有着显著差异,总结如下:

  • 存储方式
    • 关系型数据库基于磁盘进行存储,会有大量的磁盘IO,对性能有一定影响
    • 非关系型数据库,他们的操作更多的是依赖于内存来操作,内存的读写速度会非常快,性能自然会好一些
  • 扩展性
    • 关系型数据库集群模式一般是主从,主从数据一致,起到数据备份的作用,称为垂直扩展。
    • 非关系型数据库可以将数据拆分,存储在不同机器上,可以保存海量数据,解决内存大小有限的问题。称为水平扩展。
    • 关系型数据库因为表之间存在关联关系,如果做水平扩展会给数据查询带来很多麻烦
SQL NoSQL
数据结构 结构化(Structured) 非结构化
数据关联 关联的(Relational) 无关联
查询方式 SQL查询 非SQL
事务特性 ACID BASE

1.2、认识Redis

Redis诞生于2009年全称是Remote Dictionary Server,远程词典服务器,是一个基于内存的键值型NoSQL数据库。这里有两个关键字:

  • 键值型
  • NoSql

其中键值型,是指Redis中存储的数据都是以key.value对的形式存储,而value的形式多种多样,可以是字符串.数值.甚至json,而NoSql则是相对于传统关系型数据库而言,有很大差异的一种数据库。

对于存储的数据,没有类似Mysql那么严格的约束,比如唯一性,是否可以为null等等,所以我们把这种松散结构的数据库,称之为NoSQL数据库。

特征:

  • 键值(key-value)型,value支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(基于内存、IO多路复用、良好的编码)。
  • 支持数据持久化
  • 支持主从集群、分片集群
  • 支持多语言客户端

Redis的官方网站地址:https://redis.io/

1.3、安装Redis

1.3.1、Redis安装说明

大多数企业都是基于Linux服务器来部署项目,而且Redis官方也没有提供Windows版本的安装包。因此,此处是基于Linux系统来安装的,选择的Linux版本为CentOS 7.

1.3.2、安装Redis依赖库

Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖:

yum install -y gcc tcl

1.3.3、上传安装包并解压

将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-cli:是redis提供的命令行客户端
  • redis-server:是redis的服务端启动脚本
  • redis-sentinel:是redis的哨兵启动脚本

1.3.4、启动

redis的启动方式有很多种,例如:

  • 默认启动
  • 指定配置启动
  • 开机自启
1.3.4.1、默认启动

安装完成后,在任意目录输入redis-server命令即可启动Redis:

redis-server

这种启动属于前台启动,会阻塞整个会话窗口,窗口关闭或者按下CTRL + C则Redis停止。不推荐使用。

1.3.4.2、指定配置启动

如果要让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
1.3.4.3、开机自启

我们也可以通过配置来实现开机自启。

首先,新建一个系统服务文件:

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

1.3.5、Redis桌面客户端

安装完成Redis,我们就可以操作Redis,实现数据的CRUD了。这需要用到Redis客户端,包括:

  • 命令行客户端
  • 图形化桌面客户端
  • 编程客户端
1.3.5.1、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的交互控制台:

1.3.5.2、图形化桌面客户端

GitHub上的大神编写了Redis的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager

不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包。

在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases

1.3.5.3、安装

在下载的而文件中找到Redis的图形化桌面客户端;
解压缩后,运行安装程序即可安装;
安装完成后,在安装目录下找到rdm.exe文件;双击即可运行:

1.3.5.4、建立连接

点击左上角的连接到Redis服务器按钮,在弹出的窗口中填写Redis服务信息,点击确定后,在左侧菜单会出现一个链接,点击即可建立连接了。
Redis默认有16个仓库,编号从0至15. 通过配置文件可以设置仓库数量,但是不超过16,并且不能自定义仓库名称。

如果是基于redis-cli连接Redis服务,可以通过select命令来选择数据库:

# 选择 0号库
select 0

二、Redis常见命令

2.1、Redis数据结构介绍

  • Redis是一个key-value的数据库,key一般是String类型,不过value的类型多种多样;为了方便我们学习,将操作不同数据类型的命令也做了分组,在官网( https://redis.io/commands )可以查看到不同的命令。当然我们也可以通过Help命令来帮助我们去查看命令。

2.2、Redis通用命令

通用指令是部分数据类型的,都可以使用的指令,常见的有:

  • KEYS:查看符合模板的所有key
  • DEL:删除一个指定的key
  • EXISTS:判断key是否存在
  • EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除
  • TTL:查看一个KEY的剩余有效期

通过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

2.2.1、String类型

String类型,也就是字符串类型,是Redis中最简单的存储类型。

其value是字符串,不过根据字符串的格式不同,又可以分为3类:

  • string:普通字符串
  • int:整数类型,可以做自增.自减操作
  • float:浮点类型,可以做自增.自减操作

String的常见命令有:

  • set(K var1, V var2):新增一个字符串类型的值
    • var1是key
    • var2是值
    • key存在就覆盖,不存在新增
redisTemplate.opsForValue().set("BBB","你好");
  • set(K key, V value, Duration timeout):新增一个字符串类型的值,同时设置过期时间
    • var1是key
    • var2是值
    • key存在就覆盖,不存在新增
redisTemplate.opsForValue().set("BBB","你好", Duration.ofMinutes(1));
  • set(K var1, V var2, long var3, TimeUnit var5):新增一个字符串类型的值,同时设置过期时间
    • var1是key
    • var2是值
    • key存在就覆盖,不存在新增
redisTemplate.opsForValue().set("BBB","你好", 1, TimeUnit.MINUTES);
  • append(K var1, String var2) ;给对应的key追加value,key不存在直接新增
redisTemplate.opsForValue().append("AAA", "哈哈哈");
  • set(K key1, V v1, long v2):将key的值从下标1往后替换为新的value,key不存在相当于新增
redisTemplate.opsForValue().set("BBB","您的",1);
  • setBit(K var1, long var2, boolean var4):key键对应的值value对应的ascii码,在offset的位置(从左向右数)变为value
redisTemplate.opsForValue().setBit("BBB", 0, true);
  • getBit(K var1, long var2):判断指定的位置ASCII码的bit位是否为1
redisTemplate.opsForValue().getBit("BBB", 1);
  • setIfAbsent(K var1, V var2):如果key不存在则新增,key存在不做任何操作
redisTemplate.opsForValue().setIfAbsent("BBB", "好的");
  • setIfAbsent(K var1, V var2, long var3, TimeUnit var5):如果key不存在则新增,同时设置过期时间,key存在不做任何操作。
redisTemplate.opsForValue().setIfAbsent("AAA", "好的", 1, TimeUnit.MINUTES);
  • setIfAbsent(K key, V value, Duration timeout):如果key不存在则新增,同时设置过期时间,key存在不做任何操作。
redisTemplate.opsForValue().setIfAbsent("BBB", "好的", Duration.ofMinutes(1));
  • setIfPresent(K var1, V var2):如果key存在则修改,key不存在不做任何操作
redisTemplate.opsForValue().setIfPresent("BBB", "好的");
  • setIfPresent(K var1, V var2, long var3, TimeUnit var5):如果key存在则修改,同时设置过期时间,key不存在不做任何操作。
redisTemplate.opsForValue().setIfPresent("BBB", "好的",1, TimeUnit.MINUTES);
  • setIfPresent(K key, V value, Duration timeout):如果key存在则修改,同时设置过期时间,key不存在不做任何操作
redisTemplate.opsForValue().setIfPresent("BBB", "好的",Duration.ofMinutes(1));
  • getAndSet(K var1, V var2):获取key对应的值,如果key存在则修改,不存在则新增
redisTemplate.opsForValue().getAndSet("BBB", "心情");
  • increment(K var1):以增量的方式(默认增量为1)将long值存储在变量中(value为其他类型时报错),返回最新值
redisTemplate.opsForValue().increment("AAA");
  • increment(K var1, long var2):以指定增量的方式将Long值存储在变量中,返回最新值
redisTemplate.opsForValue().increment("AAA",2);
  • increment(K var1, double var2):以指定增量的方式将Double值存储在变量中,返回最新值
redisTemplate.opsForValue().increment("AAA", 3.2);
  • decrement(K var1):以递减的方式(默认为1)将long值存储在变量中(value为其他类型时报错,Double也不行,只能为Long),返回最新值
redisTemplate.opsForValue().decrement("AAA");
  • decrement(K var1, long var2):以指定递减量递减的方式将long值存储在变量中(value为其他类型时报错,Double也不行,只能为Long),返回最新值
redisTemplate.opsForValue().decrement("AAA",2);
  • size(K var1):获取指定key对应值的长度
redisTemplate.opsForValue().size("BBB");
  • get(Object var1):获取指定的key对应的值
String BBB = (String) redisTemplate.opsForValue().get("BBB");
System.out.println("BBB = " + BBB);
  • get(K var1, long var2, long var4):获取key指定下标之间对应的值
String BBB = redisTemplate.opsForValue().get("BBB",0,1);
System.out.println("BBB = " + BBB);
  • multiSet(Map var1):将map中的key分别作为不同的key存到Redis中

    • 若某个key已经存在则替换为新值,其他不存在的则新增
    • map中5个key,3个存在Redis中,2个没有,结果就是3个值被修改,2个新增
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 var1):将map中的key分别作为不同的key存到Redis中

    • 若某个key已经存在不做修改,不存在的则新增(map中的key在Redis中都不存在时才新增)
    • map中5个key,3个存在Redis中,2个没有,结果就是不会新增不会修改,若map中5个key,5个都不存在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);
  • multiGet(Collection var1):根据集合中的key取出对应的value值
List paraList = new ArrayList();
paraList.add("valueMap1");
paraList.add("valueMap2");
paraList.add("valueMap3");
List list = redisTemplate.opsForValue().multiGet(paraList);

2.2.2、Hash类型

Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。

String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便。

Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD。

Hash类型的常见命令

  • put(H var1, HK var2, HV var3):新增hashMap值

    • var1 为Redis的key
    • var2 为key对应的map值的key
    • var3 为key对应的map值的值
    • var2相同替换var3
redisTemplate.opsForHash().put("hashValue","map1","value1");
redisTemplate.opsForHash().put("hashValue","map2","value2");
  • get(H var1, Object var2):获取key对应的map中,key为var2的map的对应的值
Object o = redisTemplate.opsForHash().get("hashValue", "map1");
System.out.println("o = " + o);
  • entries(H key):获取key对应的所有map键值对
Map hashValue = redisTemplate.opsForHash().entries("hashValue");
System.out.println("hashValue = " + hashValue);
  • keys(H key):获取key对应的map中所有的键
Set hashValue = redisTemplate.opsForHash().keys("hashValue");
System.out.println("hashValue = " + hashValue);
  • values(H key):获取key对应的map中所有的值
List hashValue = redisTemplate.opsForHash().values("hashValue");
System.out.println("hashValue = " + hashValue);
  • hasKey(H key, Object var2):判断key对应的map中是否有指定的键
Boolean aBoolean = redisTemplate.opsForHash().hasKey("hashValue", "map1");
System.out.println("aBoolean = " + aBoolean);
  • size(H key):获取key对应的map的长度
Long hashValue = redisTemplate.opsForHash().size("hashValue");
System.out.println("hashValue = " + hashValue);
  • putIfAbsent(H key, HK var2, HV var3):如何key对应的map不存在,则新增到map中,存在则不新增也不覆盖
redisTemplate.opsForHash().putIfAbsent("hashValue", "map3", "value3");
  • putAll(H key, Map map):直接以map集合的方式添加key对应的值
    • map中key已经存在,覆盖替换
    • map中key不存在,新增
Map newMap = new HashMap();
newMap.put("map4","map4");
newMap.put("map5","map5");
redisTemplate.opsForHash().putAll("hashValue",newMap);
  • multiGet(H key, Collection var2):以集合的方式获取这些键对应的map
List list = new ArrayList<>();
list.add("map1");
list.add("map2");
List hashValue = redisTemplate.opsForHash().multiGet("hashValue", list);
System.out.println("hashValue = " + hashValue);
  • lengthOfValue(H key, HK var2):获取指定key对应的map集合中,指定键对应的值的长度
Long aLong = redisTemplate.opsForHash().lengthOfValue("hashValue", "map1");
System.out.println("aLong = " + aLong);
  • increment(H key, HK var2, long long1):使key对应的map中,键var2对应的值以long1自增
Long increment = redisTemplate.opsForHash().increment("hashValue", "map7", 1);
System.out.println("increment = " + increment);
  • increment(H key, HK var2, double d1):使key对应的map中,键var2对应的值以double类型d1自增
Double increment = redisTemplate.opsForHash().increment("hashValue", "map8", 1.2);
System.out.println("increment = " + increment);
  • scan(H var1, ScanOptions var2):匹配获取键值对
    • ScanOptions.NONE为获取全部键对
    • ScanOptions.scanOptions().match(“map1”).build(),匹配获取键位map1的键值对,不能模糊匹配
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());
}
  • delete(H key, Object… var2):删除key对应的map中的键值对
Long delete = redisTemplate.opsForHash().delete("hashValue", "map1", "map2");
System.out.println("delete = " + delete);

2.2.3、List类型

Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。

特征也与LinkedList类似:

  • 有序
  • 元素可以重复
  • 插入和删除快
  • 查询速度一般

常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。

List的常见命令有:

  • leftPush(K key, V value):从集合左边插入值
    • 值重复无影响
    • 先进先出
redisTemplate.opsForList().leftPush("list","a");
redisTemplate.opsForList().leftPush("list","a");
redisTemplate.opsForList().leftPush("list","b");
  • leftPush(K key, V v, V v1):从集合左边开始在v值后边插入新值v1

    • 在集合中查找v值
    • 有多个v值时,从左边开始查到第一个v值即可(也就是最后插入的v值,靠左的v值),然后在v值左边插入新值v1
    • 不存在v时,不插入新值v1
redisTemplate.opsForList().leftPush("list", "a", "e");
  • leftPushAll(K key, Collection values):从左边批量插入新值
List<String> strings = Arrays.asList("j", "q", "k");
redisTemplate.opsForList().leftPushAll("list", strings);
  • leftPushAll(K key, V… values):从左边批量插入新值(同上)
redisTemplate.opsForList().leftPushAll("list", "j", "q", "k");
  • leftPushIfPresent(K key, V value):如果key存在,从左边插入新值
    • key不存在,不新增
redisTemplate.opsForList().leftPushIfPresent("list", "j");	
  • leftPop(K key):默认移除key中最左的一个值
redisTemplate.opsForList().leftPop("list");
  • leftPop(K key, long timeout, TimeUnit unit):指定过期时间后删除key中最左的一个值
redisTemplate.opsForList().leftPop("list",1,TimeUnit.MINUTES);
  • rightPopAndLeftPush(K k1, K k2):移除k1中最右的值,并将移除的值插入k2中最左侧
    • k1和k2不是同一个key时,k1右侧移除,k2左侧插入,k2不存在时则新增一个然后在插入
    • k1和k2是同一个key时,相当于把最右侧的值移到了最左侧
redisTemplate.opsForList().rightPopAndLeftPush("list", "list2");
  • rightPopAndLeftPush(K k1, K k2, long timeout, TimeUnit unit):指定过期时间后,移除k1中最右的值,并将移除的值插入k2中最左侧(同上)

    • k1和k2不是同一个key时,k1右侧移除,k2左侧插入,k2不存在时则新增一个然后在插入
    • k1和k2是同一个key时,相当于把最右侧的值移到了最左侧
redisTemplate.opsForList().rightPopAndLeftPush("list", "list2",1,TimeUnit.MINUTES);
  • rightPush(K key, V value):从右侧插入新值
redisTemplate.opsForList().rightPush("rightList",'a');
redisTemplate.opsForList().rightPush("rightList",'b');
redisTemplate.opsForList().rightPush("rightList",'c');
  • rightPush(K key, V v1, V v2):从右查找v1,并在v1右侧插入新值v2
    • v1有多个,只找到最靠右的一个即可
    • v1没有,不插入
redisTemplate.opsForList().rightPush("rightList", "b", "e");
  • rightPushAll(K key, V… values):从右侧批量插入
redisTemplate.opsForList().rightPushAll("rightList", "e", "f","g");
  • rightPushAll(K key, Collection values):从右侧批量插入
List<String> strings = Arrays.asList("j", "q", "k");
redisTemplate.opsForList().rightPushAll("rightList", strings);
  • rightPushIfPresent(K key, V value):如果key存在,在右侧新插入value,否则不插入
redisTemplate.opsForList().rightPushIfPresent("rightList", "a");
  • rightPop(K key):默认从最右侧移除一个值
redisTemplate.opsForList().rightPop("rightList");
  • rightPop(K key, long timeout, TimeUnit unit):指定过期时间后,从最右侧移除一个值
redisTemplate.opsForList().rightPop("rightList",1,TimeUnit.MINUTES);
  • index(K key, final long index):获取指定位置的值(index从左往右,从0开始)
    • 超过下标,不报错,返回null
String string1 = (String) redisTemplate.opsForList().index("rightList", 2);
System.out.println("string1 = " + string1);
  • size(K key):获取对应key的集合长度
Long size = redisTemplate.opsForList().size("rightList");
System.out.println("size = " + size);
  • set(K key, final long index, V value):在指定坐标位置插入(替换)新值
    • index不存在,报错(ERR index out of range)
    • key不存在,报错(ERR no such key)
    • 从左侧插入
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);
  • range(K key, long start, long end):获取指定下标间的值
List<Object> list =  redisTemplate.opsForList().range("rightList", 0, -1);//获取所有值
  • remove(K key, long count, Object value):从存储在键中的列表中删除等于值的元素的第一个计数事件。

    • count> 0:删除等于从左到右移动的值的第一个元素;
    • count< 0:删除等于从右到左移动的值的第一个元素;
    • count = 0:删除等于value的所有元素。
redisTemplate.opsForList().remove("rightList", 0, "c");

2.2.4、Set类型

Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:

  • 无序
  • 元素不可重复
  • 查找快
  • 支持交集.并集.差集等功能

Set类型的常见命令

  • add(K key, V… var2):向key中批量添加值
redisTemplate.opsForSet().add("set", "aa", "bb", "cc");
redisTemplate.opsForSet().add("set", "ee");
  • members(K key): 获取key中的值
Set set = redisTemplate.opsForSet().members("set");
System.out.println("set = " + set);
  • size(K key):获取key对应集合的长度
Long set = redisTemplate.opsForSet().size("set");
System.out.println("set = " + set);
  • randomMember(K key):随机获取key对应的集合中的元素
Object set = redisTemplate.opsForSet().randomMember("set");
System.out.println("set = " + set);
  • randomMembers(K key, long long1):随机获取key对应集合中指定个数的元素,set本身是不会有重复的值,随机获取可能获取到一样的,所以返回的list集合中可能会有重复的值
List set = redisTemplate.opsForSet().randomMembers("set", 2);
System.out.println("set = " + set);
  • isMember(K key, Object o1):判断key对应的集合中是否包含元素o1
Boolean member = redisTemplate.opsForSet().isMember("set", "aa");
System.out.println("member = " + member);
  • distinctRandomMembers(K key, long long1):随机获取key对应集合中指定个数的元素,并且去重
Set set = redisTemplate.opsForSet().distinctRandomMembers("set", 3);
System.out.println("set = " + set);
  • move(K key1, V v1, K key2):将key1对应集合中的值v1,转移到key2集合中,key2不存在直接新增,v1不存在,转移失败返回false
Boolean move = redisTemplate.opsForSet().move("set", "aa", "set2");
System.out.println("move = " + move);
  • pop(K key):随机弹出key对应集合中的一个元素
Object set = redisTemplate.opsForSet().pop("set");
System.out.println("set = " + set);
  • pop(K key, long count):随机移除key对应集合中的count个元素
List set = redisTemplate.opsForSet().pop("set", 2);
System.out.println("set = " + set);
  • remove(K key, Object… values):批量移除key对应集合中指定的元素
redisTemplate.opsForSet().remove("set", "cc","aa");
  • scan(K key, ScanOptions options):匹配获取键值对,ScanOptions.NONE为获取全部键值对,ScanOptions.scanOptions().match(“C”).build()匹配获取键位map1的键值对,不能模糊匹配。
Cursor<Object> cursor = redisTemplate.opsForSet().scan("set", ScanOptions.scanOptions().match("ee").build());
while (cursor.hasNext()){
    Object object = cursor.next();
    System.out.println("object = " + object);
}
  • difference(K key, Collection otherKeys):获取key与其他集合之间的差值,key中有,otherKeys中没有的
List list = new ArrayList();
list.add("set2");
Set set = redisTemplate.opsForSet().difference("set", list);
System.out.println("set = " + set);
  • difference(K key, K otherKey):获取key与另一个otherKey集合之间的差值,key中有,otherKeys中没有的
Set difference = redisTemplate.opsForSet().difference("set", "set2");
System.out.println("difference = " + difference);
  • differenceAndStore(K key, K otherKey, K destKey):获取key与另一个otherKey所对应的集合之间的差值,并将结果存入指定的destKey中
    • destKey存在,它原本集合中的所有值会被清空并且替换为获取的差值
    • destKey不存在,直接新增
    • key与otherKey没有获取到差值,destKey如果存在,会被删除
Long aLong = redisTemplate.opsForSet().differenceAndStore("set", "set2", "set3");
System.out.println("aLong = " + aLong);
  • differenceAndStore(K key, Collection otherKeys, K destKey):获取key与另外一些otherKeys集合之间的差值,并将结果存入指定的destKey中

    • destKey存在,它原本集合中的所有值会被清空并且替换为获取的差值
    • destKey不存在,直接新增
    • key与otherKeys没有获取到差值,destKey如果存在,会被删除
List list = new ArrayList();
list.add("set2");
Long aLong = redisTemplate.opsForSet().differenceAndStore("set", list, "set3");
System.out.println("aLong = " + aLong);
  • intersect(K key, K otherKey):获取两个集合中的交集元素
Set intersect = redisTemplate.opsForSet().intersect("set", "set2");
System.out.println("intersect = " + intersect);
  • intersect(K key, Collection otherKeys):获取多个key对应集合之间的交集
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集合中

    • 没有获取到交集元素时,注意:若指定集合destKey存在,它将会被删除
Long aLong = redisTemplate.opsForSet().intersectAndStore("set", "set2", "set4");
System.out.println("aLong = " + aLong);
  • intersectAndStore(K key, Collection otherKeys, K destKey):获取key集合与其他otherKeys集合之间的交集元素,并将其放入指定的destKey集合中

    • 没有获取到交集元素时,注意:若指定集合destKey存在,它将会被删除
List list = new ArrayList();
list.add("set2");
Long aLong = redisTemplate.opsForSet().intersectAndStore("set", list, "set5");
System.out.println("aLong = " + aLong);
  • union(K key, K otherKey):获取两个集合的合集,并且去重
Set union = redisTemplate.opsForSet().union("set", "set2");
System.out.println("union = " + union);
  • union(K key, Collection otherKeys):获取多个集合的合集,去重
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对应的新集合中

    • 若指定key对应的集合存在,会被覆盖掉
Long aLong = redisTemplate.opsForSet().unionAndStore("se3", "set2", "set4");
System.out.println("aLong = " + aLong);
  • unionAndStore(K key, Collection otherKeys, K destKey):获取多个集合之间的合集,并放入指定key对应的新集合中

    • 若指定key对应的集合存在,会被覆盖掉
List list = new ArrayList();
list.add("set2");
list.add("set3");
redisTemplate.opsForSet().unionAndStore("set", list, "set4");

2.2.5、SortedSet类型

Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。

SortedSet具备下列特性:

  • 可排序
  • 元素不重复
  • 查询速度快

因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。

SortedSet的常见命令有:

  • add(K key, V value, double score):向指定key中添加元素,按照score值由小到大进行排列,集合中对应元素已存在,会被覆盖,包括score
redisTemplate.opsForZSet().add("zSet", "aaa", 1);
redisTemplate.opsForZSet().add("zSet", "bbb", 2);
redisTemplate.opsForZSet().add("zSet", "ccc", 3);
  • add(K key, Set tuples):向指定key中添加元素,按照score值由小到大进行排列,集合中对应元素已存在,会被覆盖,包括score
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);
  • scan(K key, ScanOptions options) :匹配获取键值对,ScanOptions.NONE为获取全部键值对;ScanOptions.scanOptions().match(“C”).build()匹配获取键位map1的键值对,不能模糊匹配。
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());  
}  
  • incrementScore(K key, V v1, double delta):增加key对应的集合中元素v1的score值,并返回增加后的值,v1不存在,直接新增一个元素
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);  
  • score(K key, Object o) : 获取key对应集合中o元素的score值
Double score = redisTemplate.opsForZSet().score("zSet", "aaa");
System.out.println("score = " + score);
  • size(K key):获取集合的大小,地层调用的还是 zCard(K key)
Long zSet = redisTemplate.opsForZSet().size("zSet");
System.out.println("zSet = " + zSet);
  • zCard(K key):获取集合大小
Long zSet = redisTemplate.opsForZSet().zCard("zSet");
System.out.println("zSet = " + zSet);
  • count(K key, double min, double max):获取指定score区间里的元素个数,包括min、max
long count = redisTemplate.opsForZSet().count("zSetValue",1,5);  
System.out.println("通过count(K key, double min, double max)方法获取区间值的个数:" + count);  
  • range(K key, long start, long end):获取指定下标之间的值,(0,-1)就是获取全部
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);  
  • rangeByLex(K key, RedisZSetCommands.Range range):用于获取满足非score的排序取值。这个排序只有在有相同分数的情况下才能使用,如果有不同的分数则返回值不确定。
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);  
  • rangeByLex(K key, RedisZSetCommands.Range range, RedisZSetCommands.Limit limit) :用于获取满足非score的设置下标开始的长度排序取值。
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);  
  • rangeByScore(K key, double min, double max):获取指定score区间的值
Set zSet = redisTemplate.opsForZSet().rangeByScore("zSet", 1, 5);
System.out.println("zSet = " + zSet);
  • rangeByScore(K key, double min, double max, long offset, long count):获取指定score区间的值,然后从给定下标和给定长度获取最终值
Set zSet = redisTemplate.opsForZSet().rangeByScore("zSet", 1, 5, 1, 2);
System.out.println("zSet = " + zSet);
  • rangeWithScores(K key, long start, long end): 获取RedisZSetCommands.Tuples的区间值。
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 );
}
  • rangeByScoreWithScores(K key, double min, double max):获取RedisZSetCommands.Tuples的区间值通过分值。
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 );  
}  
  • rangeByScoreWithScores(K key, double min, double max, long offset, long count): 获取RedisZSetCommands.Tuples的区间值从给定下标和给定长度获取最终值通过分值。
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 );  
}  
  • rank(K key, Object o):获取指定元素在集合中的索引,索引从0开始
Long rank = redisTemplate.opsForZSet().rank("zSet", "aaa");
System.out.println("rank = " + rank);
  • reverseRank(K key, Object o):获取倒序排列的索引值,索引从0开始
Long rank = redisTemplate.opsForZSet().reverseRank("zSet", "eee");
System.out.println("rank = " + rank);
  • reverseRange(K key, long start, long end):逆序获取对应下标的元素
Set zSet = redisTemplate.opsForZSet().reverseRange("zSet", 0, 3);
System.out.println("zSet = " + zSet);
  • reverseRangeByScore(K key, double min, double max):倒序排列指定分值区间元素。
Set zSetValue = redisTemplate.opsForZSet().reverseRangeByScore("zSetValue",1,5);  
System.out.println("通过reverseRangeByScore(K key, double min, double max)方法倒序排列指定分值区间元素:" + zSetValue);  
  • reverseRangeByScore(K key, double min, double max, long offset, long count):倒序排列从给定下标和给定长度分值区间元素。
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);  
  • reverseRangeByScoreWithScores(K key, double min, double max):倒序排序获取RedisZSetCommands.Tuples的分值区间值。
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 );  
}  
  • reverseRangeByScoreWithScores(K key, double min, double max, long offset, long count): 倒序排序获取RedisZSetCommands.Tuples的从给定下标和给定长度分值区间值。
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 );  
}  
  • reverseRangeWithScores(K key, long start, long end):索引倒序排列区间值。
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);  
}  
  • intersectAndStore(K key, K otherKey, K destKey):获取2个变量的交集存放到第3个变量里面。
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);  
  • intersectAndStore(K key, Collection otherKeys, K destKey): 获取多个变量的交集存放到第3个变量里面。
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);  
  • unionAndStore(K key, K otherKey, K destKey):获取2个变量的合集存放到第3个变量里面。
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);  
  • unionAndStore(K key, Collection otherKeys, K destKey):获取多个变量的合集存放到第3个变量里面。
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);  
  • remove(K key, Object… values):移除集合中指定的值
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);  
  • removeRange(K key, long start, long end):根据索引值移除区间元素。
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);  
  • removeRangeByScore(K key, double min, double max):根据分值移除区间元素。
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即可,例如:

  • 升序获取sorted set 中的指定元素的排名:ZRANK key member
  • 降序获取sorted set 中的指定元素的排名:ZREVRANK key memeber

2.2.6、Geo类型

/***
 * 将指定的地理空间位置(纬度、经度、名称)添加到指定的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的Java客户端

在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/

Redis --- 基础篇_第1张图片

其中Java客户端也包含很多:

Redis --- 基础篇_第2张图片

标记为❤的就是推荐使用的java客户端,包括:

  • Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。
  • Redisson:是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map.Queue等,而且支持跨进程的同步机制:Lock.Semaphore等待,比较适合用来实现特殊的功能需求。

3.1、Jedis客户端

Jedis的官网地址: https://github.com/redis/jedis

3.1.1、Jedis快速入门

3.1.1.1、引入依赖

<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>
3.1.1.2、建立连接

新建一个单元测试类,内容如下:

private Jedis jedis;

@BeforeEach
void setUp() {
    // 1.建立连接
    jedis = new Jedis("127.0.0.1",6379);
    // 2.设置密码
    jedis.auth("123321");
    // 3.选择库
    jedis.select(0);
}
3.1.1.3、测试
@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);
}
3.1.1.4、释放资源
@AfterEach
void tearDown() {
    if (jedis != null) {
        jedis.close();
    }
}
3.1.1.5、总结

Jedis使用的基本步骤:

  • 引入依赖
  • 创建Jedis对象,建立连接
  • 使用Jedis,方法名与Redis命令一致
  • 释放资源

3.1.2、Jedis连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式

3.1.2.1、创建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)最后提供返回连接池中连接的方法.

3.1.2.2、改造原始代码

代码说明:

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();
        }
    }

3.2、SpringDataRedis客户端

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)
  • 提供了RedisTemplate统一API来操作Redis
  • 支持Redis的发布订阅模型
  • 支持Redis哨兵和Redis集群
  • 支持基于Lettuce的响应式编程
  • 支持基于JDK.JSON.字符串.Spring对象的数据序列化及反序列化
  • 支持基于Redis的JDKCollection实现

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 通用的命令

3.2.1、快速入门

SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:

3.2.1.1、引入依赖

<dependency>
   <groupId>org.springframework.bootgroupId>
   <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

<dependency>
   <groupId>org.apache.commonsgroupId>
   <artifactId>commons-pool2artifactId>
dependency>
      
3.2.1.2、配置文件
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 #连接等待时间
3.2.1.3、注入RedisTemplate

因为有了SpringBoot的自动装配,我们可以拿来就用:

@SpringBootTest
class RedisStringTests {

    @Autowired
    private RedisTemplate redisTemplate;
}
3.2.1.4、测试代码
@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);
    }
}
3.2.1.5、总结

SpringDataRedis的使用步骤:

  • 引入spring-boot-starter-data-redis依赖
  • 在application.yml配置Redis信息
  • 注入RedisTemplate

3.2.2、自定义序列化

RedisTemplate可以接收任意Object作为值写入Redis。

只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的
Redis --- 基础篇_第3张图片

缺点:

  • 可读性差
  • 内存占用较大

我们可以自定义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名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。

3.2.3、StringRedisTemplate

  • 尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题。
  • 为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入json结果中,存入Redis,会带来额外的内存开销。
  • 为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。

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);
    }

}

3.2.4、RedisTemplate的两种序列化实践方案

方案一:

  • 自定义RedisTemplate
  • 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer

方案二:

  • 使用StringRedisTemplate
  • 写入Redis时,手动把对象序列化为JSON
  • 读取Redis时,手动把读取到的JSON反序列化为对象

你可能感兴趣的:(Redis,redis,java)