1、Redis是什么?
非关系型数据库,以键值对(key-value)形式存储数据,也是一种NoSQL数据库
NoSQL可用于超大规模数据存储

2、有了Map为什么还需要Redis?
- Map是Java提供的,是存在于虚拟机中的,所以是有大小限制的,不能存储大量数据
- Map不能进行横向扩展和纵向扩展,但是Redis可以,并且Redis支持集群模式
- Redis的读写速度很快
- Redis支持多种持久化
- Redis支持多种数据类型(字符串、哈希、列表、集合、有序集合…)
- Redis支持多种数据淘汰策略(LRU算法、LFU 算法、随机、ttl、noeviction)
3、Redis能干嘛?
- 可以存储用户的登录信息和认证信息
- 系统的数据缓存(高速缓存)
- 秒杀系统
- 可以存储一些容忍丢失的数据
- 接口防刷和限流
4、RESP协议
REdis Serialization Protocol,简称RESP
RESP是基于TCP传输的应用层协议,底层采用TCP传输
在RESP协议中,数据的类型取决于第一个字符:
- +:单行字符串
- -:表示错误的类型
- *::表示整数
- $:表示多行字符
- *:表示数组
在RESP协议中,构成协议的每一部分必须使用\r\n作为结束符
示例:
set key value
*3\r\n 表示这个命令由三部分组成
$3\r\n 表示第一部分长度为3
set\r\n 表示第一部分的内容
$3\r\n 表示第二部分长度为3
key\r\n 表示第二部分的内容
$5\r\n 表示第三部分长度为35
value\r\n 表示第三部分的内容
5、通用命令
del key //删除一个键
exists key //是否存在一个键
expire key seconds //为一个键设置过期时间、单位秒
pexpire key millionSeconds //为一个键设置过期时间、单位毫秒
ttl key //查看键的剩余过期时间,单位秒
pttl key //查看键的过期时间,单位毫秒
keys * //查看所有的键
persist key //持久化键,相当于永不过期
type key //查看键的类型
select 0~15 //选择0~15之间的一个数据库
move key db //将一个键移动到另一个数据库中
flushdb //刷新数据库
dbsize //查看当前数据库中键的数量(当前数据库的大小)
lastsave //查看最近一次操作时间,时间戳
monitor //实时监控Redis接收到的命令
6、字符串命令
字符串(string)是Redis中的一种基本数据类型
set key value //为键设置一个值,如果存在就是修改,不存在就是添加
get key //获取一个键的值
mset key1 value1 key2 value2... //同时为多个键设置值
mget key1 key2 key3... //同时获取多个键的值
setex key seconds value //设置键的值,同时设置过期时间,单位秒
setnx key value //如果不存在键就为其设置值,如果存在就返回0,不改变原来的值
incr key //为整数键自增1
incrby key increment //指定键增加increment增量
decr key //为整数键自减1
decrby key increment //指定键减少increment增量
append key value //将value追加到指定key的末尾
7、Hash命令
Hash也是Redis中的一种基本数据类型
hset key field value //为键设置字段和值
hget key field //获取键中的字段值
hmset key field1 value1 field2 value2... //同时为键的多个字段设置值
hmget key field1 field2 filed3... //同时获取键中多个字段的值
hincrby key field increment //为键的字段设置增量
hsetnx key field value //不存在就添加字段和值
hexists key field //检查键中是否存在字段
hdel key field1 field2 ... //删除键中的字段
hgetall key //获取键中的所有字段和值
hkeys key //获取键中所有的字段
hvals key //获取键中所有的值
hlen key //获取键中字段的数量
8、List命令
lpush key value1 value2 value3.. //从左往右依次添加值到列表
rpush key value1 value2 value3.. //从右往左依次添加值到列表
lpushx key value //添加一个值到列表的头部
rpushx key value //添加一个值到列表的尾部
lpop key //从列表的尾部弹出一个值
rpop key //从列表的头部弹出一个值
lrange key start stop //从下标为start到下标为stop为列表进行切片,stop为-1表示最后一个
lindex key index //通过下标index取出列表中对用的值
llen key //获取列表的长度
lrem key count value //从列表中删除指定count数量的value,count>0代表从左往右依次寻找,count<0代表从右往左依次寻找,count==0代表全部删除
ltrim key start stop //对从下标start到stop区间的数据进行修剪,不在范围内的数据将会被删除
rpoplpush key1 key2 //从一个列表的尾部弹出一个值添加到另一个列表的头部
9、Set命令
使用场景:黑白名单、
sadd key member1 member2 ... //向set中添加成员
smembers key //获取set中所有的成员
spop key count //随机获取set中count数量(不写为1)的成员
sinter set1 set2 ... //获取set1和set2的交集
sunion set1 set2 ... //获取set1和set2的并集
sdiff set1 set2 ... //获取set1和set2的差集(以set顺序为准)
srem key member1 member2 ... //从set中删除指定的成员,不存在返回0
sismember key member //判断一个成员是否在set中,存在返回1,不存在返回0
10、Zset
使用场景:排行
zadd key score1 member1 score2 member2... //添加key设置成员及其分数
zincrby key increment member //为成员的分数设置增量(增量可以为正数或者负数)
zscore key member //获取成员的分数
zcard key //查看key中成员数量
zcount key min max //查找min到max范围的成员
zrem key member1 member2 ... //删除成员
zrange key start stop [withscores] //从下标start到stop根据分数对成员进行顺序排序
zrevrange key start stop [withscores] //从下标start到stop根据分数对成员进行倒序排序
zrangebyscore key min max [withscores] [limit offset count] //指定最小值到最大值区间内的成员进行顺序排序
zrevrangebyscore key max min [withscores] [limit offset count] //指定最大值到最小值区间内的成员进行逆序排序
11、Java连接redis
11.1、使用Socket与Redis交互
由于Redis的底层使用的是tcp传输,则可以通过Socket编程实现Java与Redis进行交互
[!note]
中文乱码未解决
public class RedisTest {
public static void main(String[] args) throws IOException {
// 创建Socket连接
Socket socket = new Socket("localhost", 6379);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
// 构建Redis命令
String[] commands = {
"set", "name", "liuxu"};
// 组装命令
StringBuilder sb = new StringBuilder();
int cc_len = commands.length;
sb.append("*").append(cc_len).append("\r\n");
for (String command : commands) {
int c_len = command.length();
sb.append("$").append(c_len).append("\r\n").append(command).append("\r\n");
}
// 将命令转化为RESP协议字符串
String command = sb.toString();
System.out.println(command);
// 发送命令
out.write(command.getBytes(StandardCharsets.UTF_8));
out.flush();
// 读取Redis响应
byte[] bytes = new byte[1024];
int length = in.read(bytes);
String response = new String(bytes, 0, length, StandardCharsets.UTF_8);
// 查询结果
String[] split = response.split("\r\n");
// 存储结果
List<Object> list = new ArrayList<>();
// 匹配开始符号:+ - $ : *
String start = String.valueOf(split[0].toCharArray()[0]);
// 匹配不同结果
if (split.length == 1 && !"-".equals(start)) {
list.add(Integer.parseInt(String.valueOf(split[0].toCharArray()[1])));
} else if (split.length == 1) {
list.add(split[0]);
} else {
list.addAll(Arrays.asList(split).subList(1, split.length));
}
// 处理不同结果
Object res;
// System.out.println(start);
switch (start) {
case "*":// 处理数组
res = list.stream().filter(i -> list.size() % 2 == 0).collect(Collectors.toList());
System.out.println(res);
break;
case ":":// 处理数字
Optional<Object> num = list.stream().findFirst();
res = num.orElse(null);
System.out.println(res);
break;
case "-":// 遇到错误信息
String error = list.get(0).toString();
throw new RuntimeException(error);
case "$":// 处理多行字符串
Optional<Object> first = list.stream().findFirst();
res = first.orElse(null);
System.out.println(res);
break;
case "+":// 处理单行字符串
Optional<Object> str = list.stream().findFirst();
res = str.orElse(null);
System.out.println(res);
break;
}
}
}
11.2、使用Jedis
导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.1.2</version>
</dependency>
11.2.1、存储字符串
创建一个简单的命令
@Test
public static void set() {
// 1. 连接Redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. 操作Redis - 因为Redis的命令是什么,Jedis的方法就是什么
jedis.set("name", "李四");
// 3. 释放资源
jedis.close();
}
@Test
public static void get(){
// 1. 连接Redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 2. 操作Redis - 因为Redis的命令是什么,Jedis的方法就是什么
String s = jedis.get("name");
System.out.println(s); //李四
// 3. 释放资源
jedis.close();
}
11.2.2、存储字符数组
11.2.3、SCAN 操作
使用命令keys *
来查询所有的键是阻塞的,但是使用scan
是非阻塞的
11.2.3.1、字符串的scan
public void scan() {
Jedis jedis = new Jedis("localhost", 6379);
String cursor = ScanParams.SCAN_POINTER_START; // 游标,记录的是下标
ScanParams params = new ScanParams().match("*a*").count(3); // 参数,对应的是扫描条件
do {
ScanResult<String> result = jedis.scan(cursor, params);// 每次扫描的结果集
List<String> list = result.getResult();// 从结果集中获取查询的结果
list.forEach(System.out::println);
cursor = result.getCursor();// 从结果集中获取游标
System.out.println(cursor);
System.out.println("==========================");
} while (!cursor.equals(ScanParams.SCAN_POINTER_START));// 当游标归0的时候代表扫描完成
jedis.close();
}
11.2.3.2、Hash的hscan
public void hscan() {
Jedis jedis = new Jedis("localhost", 6379);
String cursor = ScanParams.SCAN_POINTER_START; // 游标,记录的是下标
ScanParams params = new ScanParams().match("*a*").count(3); // 参数,对应的是扫描条件
do {
//hscan传入的参数: key cursor params
ScanResult<Map.Entry<String, String>> result = jedis.hscan("hash", cursor, params);// 每次扫描的结果集
List<Map.Entry<String, String>> list = result.getResult();// 从结果集中获取查询的结果
list.forEach(entry -> System.out.println(entry.getKey() + ":" + entry.getValue()));
cursor = result.getCursor();// 从结果集中获取游标
// System.out.println(cursor);
System.out.println("==========================");
} while (!cursor.equals(ScanParams.SCAN_POINTER_START));// 当游标归0的时候代表扫描完成
jedis.close();
}
11.2.3.3、Set的sscan
public void sscan() {
Jedis jedis = new Jedis("localhost", 6379);
String cursor = ScanParams.SCAN_POINTER_START; // 游标,记录的是下标
ScanParams params = new ScanParams().match