Redis基本操作以及Redis的Java客户端工具

Redis 基础篇

1、认识Redis

1、特征

1. 键值(Key-Value)型,value支持多种不同的数据结构,功能丰富
2. 单线程,每个命令具备原子性
3. 低延迟,速度快(基于内存、IO多路复用)
4. 支持数据持久化 
5. 支持主从集群、分片集群

2、Redis数据结构

Redis是一个 Key-Value 的数据库,Key一般是 String 类型,不过 Value的类型多种多样

2、Redis常用命令

  1. KEYS: 查看符合模板的所有key,不建议在生产环境中使用

    image-20220316203040704

  2. DEL: 删除一个指定的 Key

    image-20220316203155992

  3. EXISTS: 判断一个 Key 是否存在

    image-20220316203312923

  4. EXPIRE: 给一个Key设置一个有效期,有效期到了到Key会被自动删除,单位是秒

    image-20220316203457850

  5. TTL: 查看一个 Key 的剩余有效期,-2表示过期

    image-20220316203550073

通过 help + 命令 可查看该命令的用法

image-20220316203723776

3、Redis 常见类型

1、String 类型

String类型,也就是字符串类型,是Redis中最简单的存储类型
其Value是字符串,不过根据字符串格式不同,又可以分为三类:
1. string:普通字符串
2. int:整数类型,可以做自增、自减操作
3. float:浮点类型,可以做自增、自减操作

不管是哪种格式,底层都是字节数组形式存储,只不过是编码的方式不同。字符串类型的最大空间不能超过512M

image-20220316205248537

String类型的常见命令

1. 基本的 增、查
    SET:添加或者修改已经存在的一个String类型的键值对
    GET:根据key获取String类型的value
    MSET:批量添加多个String类型的键值对
    MGET:根据多个key获取多个String类型的value

2. 操作数值类型的,专门增、键
    INCR:让一个整型的key自增1
    INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2
    INCRBYFLOAT:让一个浮点类型的数字自增并指定步长

3. 组合命令
    SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行
    SETEX:添加一个String类型的键值对,并且指定有效期
    该命令等于:SET key value + EXPIRE key second

思考?

Redis没有类似 MySQL 中 table 的概念,我们该如何区分不同类型的Key呢?
例如:需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,这时会不会发生冲突呢?

在Redis中,Key允许有多个单词形成层级结构,多个单词之间用【:】冒号隔开,如下:

项目名:业务名:类型:id

health:addUser:user:1
health:findProduct:product:1

image-20220316210909771

image-20220316211023091

总结:

String类型的三种格式:
	字符串
	int
	float

Redis的Key格式:
	[项目名]:[业务名]:[类型]:[id]

2、Hash 类型

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

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

image-20220316220213194

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

image-20220316220301191

常用命令:

HSET key field value:添加或者修改hash类型key的field的值
HGET key field:获取一个hash类型key的field的值
HMSET:批量添加多个hash类型key的field的值
HMGET:批量获取多个hash类型key的field的值
HGETALL:获取一个hash类型的key中的所有的field和value
HKEYS:获取一个hash类型的key中的所有的field
HVALS:获取一个hash类型的key中的所有的value
HINCRBY:让一个hash类型key的字段值自增并指定步长
HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行

演示:

  1. 往同一个Key的不同field插入值

    image-20220316220841422

  2. 查看Key的值

    image-20220316221041650

  3. 插入多个字段,并查看

    image-20220316221251271

  4. 让key的字段值自增1

    image-20220316221709159

3、List 类型

1、简介

Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表。既可以正向检索也可以反向检索
特征也和LinkedList类似:
	1. 有序
	2. 元素可以重复
	3. 插入和删除快
	4. 查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表、评论列表等

2、常用命令

1. 在队首操作
    LPUSH key  element ... :向列表左侧插入一个或多个元素
    LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
    
2. 在队尾操作
    RPUSH key  element ... :向列表右侧插入一个或多个元素
    RPOP key:移除并返回列表右侧的第一个元素
    
3. 返回范围内的元素
LRANGE key star end:返回一段角标范围内的所有元素

4. 阻塞队列
BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil

3、演示

image-20220316223558648

4、思考

1. 如何用List结构模拟一个栈?
	入口和出口都在同一边
	
2. 如何利用List结构模拟一个队列?
	入口和出口不在同一边
	
3. 如何利用List结构模拟一个阻塞队列?
	先模拟一个队列:入口和出口不在同一边
	出队时采用阻塞式的:BLPOP或BRPOP

4、Set 类型

1、简介

Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap,因为也是一个hash表,因此具备与HashSet类似的特性:
	1. 无需
	2. 元素不可重复
	3. 查找快
	4. 支持交集、并集、差集等功能

2、常见命令

1. 单个集合的操作
    SADD key member ... :向set中添加一个或多个元素
    SREM key member ... : 移除set中的指定元素
    SCARD key: 返回set中元素的个数
    SISMEMBER key member:判断一个元素是否存在于set中
    SMEMBERS:获取set中的所有元素
    
2. 多个集合的操作
    SINTER key1 key2 ... :求key1与key2的交集
    SDIFF key1 key2 ... :求key1与key2的差集
    SUNION key1 key2 ... :求key1与key2的并集(重复元素只会记录一次)

3、演示

  1. 向 set集合中添加元素并查看元素

    image-20220316225152539

  2. 删除set集合中的元素a 并且查看元素a是否在集合中

    image-20220316225303923

  3. 查询集合中指定Key的元素数量

    image-20220316225404915

5、SortedSet 类型

1、简介

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

SortedSet具备以下特性:
	可排序
	元素不重复
	查询速度快
因为SoretedSet的可排序性,常被用来实现排行榜这样的功能

2、常见命令

ZADD key score member:添加一个或多个元素到sorted set ,如果已经存在则更新其score值
ZREM key member:删除sorted set中的一个指定元素
ZSCORE key member : 获取sorted set中的指定元素的score值
ZRANK key member:获取sorted set 中的指定元素的排名
ZCARD key:获取sorted set中的元素个数
ZCOUNT key min max:统计score值在给定范围内的所有元素的个数
ZINCRBY key increment member:让sorted set中的指定元素自增,步长为指定的increment值
ZRANGE key min 	max:按照score排序后,获取指定排名范围内的元素
ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素
ZDIFF、ZINTER、ZUNION:求差集、交集、并集
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可

3、SortedSet 命令练习

  1. 将班级的下列学生得分存入Redis的SortedSet中:

    Jack 85, Lucy 89, Rose 82, Tom 95, Jerry 78, Amy 92, Miles 76

    ZADD stus 85 Jack 89 Lucy 82 Rose 95 Tom 78 Jerry 92 Amy 76 Miles
    

    image-20220316234328122

  2. 删除Tom同学

    ZREM stus Tom
    
  3. 获取Amy同学的分数

    ZSCORE stus Amy
    

    image-20220316234457320

  4. 获取Rose同学的排名

    ZRANK stus Rose
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4OetwfqM-1650707246948)(https://gitee.com/rabbli_zli/typora-img/raw/master/img/20220316234527.png)]

  5. 查询80分以下有几个学生

    ZCOUNT stus 0 80
    
  6. 给Amy同学加2分

    ZINCRBY stus 2 Amy
    
  7. 查出成绩前3名的同学

    ZREVRANGE stus 0 2
    
  8. 查出成绩80分以下的所有同学

    ZRANGEBYSCORE stus 0 80
    

image-20220316235025429

4、Redis 的客戶端

1、Redis 的 Java客戶端

1. Jedis
	简介:以Redis命令作為方法名稱,学习成本低,简单实用。但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用。
	
2. lettuce
	简介:Lettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。
	
3. Redisson
	简介:Redisson是一个基于Redis实现的分布式、可伸缩的Java数据结构集合。包含了诸如:Map、Queue、Lock、Semaphore、AtomicLong等强大功能

2、Jedis使用

2.1 开发步骤

  1. 导入坐标

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
    
  2. 开启连接

    @BeforeEach
    public void init() {
        // 建立连接
        jedis = new Jedis("localhost",6379);
        // 选择库
        jedis.select(0);
    }
    
  3. 测试

    @Test
    public void testString() {
        String result = jedis.set("name", "Google");
        System.out.println(result);
        // 获取数据
        System.out.println(jedis.get("name"));
    }
    
    @Test
    public void testHash() {
       jedis.hset("user:1","name","Jack");
       jedis.hset("user:1","age","22");
        HashMap<String, String> map = new HashMap<>();
        map.put("name","rabbit");
        jedis.hset("user:2",map);
        // 获取数据
        Map<String, String> user1 = jedis.hgetAll("user:1");
        Map<String, String> user2 = jedis.hgetAll("user:2");
        System.out.println(user1);
        System.out.println(user2);
    }
    
  4. 关闭连接

    @AfterEach
    public void close() {
        // 释放连接
        if (jedis != null) {
            jedis.close();
        }
    }
    

2.2 Jedis 连接池

	Jedis本身是线程不安全的,所以每次访问的时候都需要新创建一个Jedis,但是频繁的创建和销毁会有性能损耗,因此我们需要采用池的思想,所以Jedis官方也推荐使用连接池

实现:

public class JedisConnectionFactory {

    private static final JedisPool jedisPool;

    static {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        // 最大连接数
        poolConfig.setMaxTotal(8);
        // 最大的空闲连接数
        poolConfig.setMaxIdle(8);
        // 最小空间连接数
        poolConfig.setMinIdle(4);
        // 创建池对象
        jedisPool = new JedisPool(poolConfig,"127.0.0.1",6379);
    }

    public static Jedis getJedis() {
        return jedisPool.getResource();
    }

}

3、SpringDataRedis

1、简介

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis
	1. 提供了对不同Redis客户端的整合(Lettuce和Jedis)
	2. 提供了RedisTemplate统一API来操作Redis
	3. 支持Redis的发布订阅模型
	4. 支持Redis哨兵和Redis集群
    5. 支持基于Lettuce的响应式编程
    6. 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
    7. 支持基于Redis的JDKCollection实现

2、API 介绍

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:

image-20220317153544726

3、使用

不管是基于 Jedis实现还是Lettuce,底层都是基于 commons-pool 来实现连接池效果,所以需要引入该依赖
		<!--  连接池依赖  -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!--  连接池依赖  -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    
  2. 配置Redis信息

    spring:
      redis:
        host: localhost
        port: 6379
        lettuce:
          pool:
            max-active: 8
            max-idle: 8
            min-idle: 4
            max-wait: 1000ms
    
  3. 编写测试类

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @RequestMapping("/findAll")
        public String findAll() {
            redisTemplate.opsForValue().set("name","rabbit");
            return (String) redisTemplate.opsForValue().get("name");
        }
    
    }
    

但是当我们使用工具 get key name 查看对应的value时,发现 get 不到,而 opsForValue().set() 方法接收的是 Object,并不是String类型,这是因为 SpringDataRedis 能接收任何类型的数据,然后帮我们转换为我们所需的字节,所以我们存进去的 key value 都被当成了 Java对象,而 redisTemplate 底层默认对存储的对象的方式就是利用JDK的序列化工具

image-20220317161821162

RedisTemplate可以接收任何Object作为值写入Redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,但是得到的结果如上,缺点如下:

  1. 可读性差
  2. 内存占用高

所以我们要求我们存进去的是什么,看到的就是什么,这时就需要修改 RedisTemplate 的序列化方式,修改底层 RedisSerializer 的实现方式,不要使用底层默认的 JdkSerializationRedisSerializer,那么它有哪些实现类呢?我们查看它的实现类

image-20220317162607968

StringRedisSerializer:
	作用:专门用来处理字符串的,我们知道,字符串要想转为字节存入Redis,就只需要简单的 getBytes() 就可以,没有必要使用JDK进行序列化,所以StringRedisSerializer做的事就是 getBytes(),而且还可以控制底层的编码,比如 UTF-8等。那什么时候使用它呢?就是当我们的 Key或者HashKey 都是字符串的情况下,使用它,而一般情况下,Key都是使用字符串,值就使用对象
	
GenericJackson2JsonRedisSerializer:
	作用:由于我们的 Value有可能是对象,所以我们可以使用它,转为JSON

4、修改RedisTemplate序列化方式

开发步骤:

  1. 创建一个修改RedisTemplate序列化方式的工具类

    @Configuration
    public class RedisConfig {
    
        /**
         * 用于修改RedisTemplate默认序列化方式的Bean
         * @param factory Redis连接工厂 SpringBoot为我们自动注入
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
            // 创建RedisTemplate对象
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            // 设置连接工厂
            redisTemplate.setConnectionFactory(factory);
            // 创建JSON序列化工具
            GenericJackson2JsonRedisSerializer redisSerializer = new GenericJackson2JsonRedisSerializer();
            // 设置Key序列化
            redisTemplate.setKeySerializer(RedisSerializer.string());
            redisTemplate.setHashKeySerializer(RedisSerializer.string());
            // 设置值序列化
            redisTemplate.setValueSerializer(redisSerializer);
            redisTemplate.setHashValueSerializer(redisSerializer);
            // 返回RedisTemplate
            return redisTemplate;
        }
    
    }
    
  2. 测试

    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @RequestMapping("/findAll")
        public String findAll() {
            redisTemplate.opsForValue().set("list:user:1",new User("rabbit",22));
            User user = (User) redisTemplate.opsForValue().get("list:user:1");
            return user.toString();
        }
    
    }
    

    image-20220317165548095

    可以看到,当我们存储对象的时候,帮我们自动转为JSON,而当取的时候,又会帮我们把json反序列为对象。我们查看结果可知,在写入json的同时,还帮我们写入了 @class 的属性,对应的就是User对象的字节码文件,就是因为有这个属性,所以在读取的时候才会帮我们把json转为对象

问题分析:

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

使用:

  1. 注入 StringRedisTemplate 对象

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
  2. 导入Spring提供的序列化和反序列对象 ObjectMapper

    private static final ObjectMapper objectMapper = new ObjectMapper();
    
  3. 测试

    @RequestMapping("/findUser")
    public String findUser() throws JsonProcessingException {
        
        User user = new User("zengli", 22);
        // 将对象序列化为Json
        String string_user = objectMapper.writeValueAsString(user);
        // 写入数据
        stringRedisTemplate.opsForValue().set("list:user:2",string_user);
        
        // 获取数据后反序列化
        String jsonUser = stringRedisTemplate.opsForValue().get("list:user:2");
        User user1 = objectMapper.readValue(jsonUser, User.class);
        
        return user1.toString();
    }
    

    image-20220317180018497

  4. 测试 Hash类型

    @RequestMapping("/findUserByHash")
    public String findUserByHash() throws JsonProcessingException {
        stringRedisTemplate.opsForHash().put("list:user:3","name","Jack");
        stringRedisTemplate.opsForHash().put("list:user:3","age","22");
        Map<Object, Object> map = stringRedisTemplate.opsForHash().entries("list:user:3");
        System.out.println(map);
        return "success";
    }
    

    image-20220317181423539

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rabbit_zli

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值