# Redis_learn_quick **Repository Path**: LA-admin/redis_learn_quick ## Basic Information - **Project Name**: Redis_learn_quick - **Description**: Redis基础知识快速入门,包括Jedis SpringDataRedis - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-08-08 - **Last Updated**: 2024-08-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Redis快速入门 Redis的常见命令和客户端使用 # 1.初识Redis Redis是一种键值型的NoSql数据库,这里有两个关键字: - 键值型 - NoSql 其中**键值型**,是指Redis中存储的数据都是以key、value对的形式存储,而value的形式多种多样,可以是字符串、数值、甚至json: ![image-20220502190959608](assets/6U1Rhxo.png) 而NoSql则是相对于传统关系型数据库而言,有很大差异的一种数据库。 ## 1.1.认识NoSQL **NoSql**可以翻译做Not Only Sql(不仅仅是SQL),或者是No Sql(非Sql的)数据库。是相对于传统关系型数据库而言,有很大差异的一种特殊的数据库,因此也称之为**非关系型数据库**。 ### 1.1.1.结构化与非结构化 传统关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名、字段数据类型、字段约束等等信息,插入的数据必须遵守这些约束: ![](assets/4tUgFo6.png) 而NoSql则对数据库格式没有严格约束,往往形式松散,自由。 可以是键值型: ![](assets/GdqOSsj.png) 也可以是文档型: ![](assets/zBBQfcc.png) 甚至可以是图格式: ![](assets/zBnKxWf.png) ### 1.1.2.关联和非关联 传统数据库的表与表之 间往往存在关联,例如外键: ![](assets/tXYSl5x.png) 而非关系型数据库不存在关联关系,要维护关系要么靠代码中的业务逻辑,要么靠数据之间的耦合: ```json { id: 1, name: "张三", orders: [ { id: 1, item: { id: 10, title: "荣耀6", price: 4999 } }, { id: 2, item: { id: 20, title: "小米11", price: 3999 } } ] } ``` 此处要维护“张三”的订单与商品“荣耀”和“小米11”的关系,不得不冗余的将这两个商品保存在张三的订单文档中,不够优雅。还是建议用业务来维护关联关系。 ### 1.1.3.查询方式 传统关系型数据库会基于Sql语句做查询,语法有统一标准; 而不同的非关系数据库查询语法差异极大,五花八门各种各样。 ![](assets/AzaHOTF.png) ### 1.1.4.事务 传统关系型数据库能满足事务ACID的原则。 ![](assets/J1MqOJM.png) 而非关系型数据库往往不支持事务,或者不能严格保证ACID的特性,只能实现基本的一致性。 ### 1.1.5.总结 除了上述四点以外,在存储方式、扩展性、查询性能上关系型与非关系型也都有着显著差异,总结如下: ![](assets/kZP40dQ.png) - 存储方式 - 关系型数据库基于磁盘进行存储,会有大量的磁盘IO,对性能有一定影响 - 非关系型数据库,他们的操作更多的是依赖于内存来操作,内存的读写速度会非常快,性能自然会好一些 * 扩展性 * 关系型数据库集群模式一般是主从,主从数据一致,起到数据备份的作用,称为垂直扩展。 * 非关系型数据库可以将数据拆分,存储在不同机器上,可以保存海量数据,解决内存大小有限的问题。称为水平扩展。 * 关系型数据库因为表之间存在关联关系,如果做水平扩展会给数据查询带来很多麻烦 ## 1.2.认识Redis Redis诞生于2009年全称是**Re**mote **D**ictionary **S**erver 远程词典服务器,是一个基于内存的键值型NoSQL数据库。 **特征**: - 键值(key-value)型,value支持多种不同数据结构,功能丰富 - 单线程,每个命令具备原子性 - 低延迟,速度快(基于内存、IO多路复用、良好的编码)。 - 支持数据持久化 - 支持主从集群、分片集群 - 支持多语言客户端 **作者**:Antirez Redis的官方网站地址:https://redis.io/ ## 1.3.安装Redis 大多数企业都是基于Linux服务器来部署项目,而且Redis官方也没有提供Windows版本的安装包。因此课程中我们会基于Linux系统来安装Redis. 此处选择的Linux版本为CentOS 7. ### 1.3.1.依赖库 Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖: ```sh yum install -y gcc tcl ``` 这里会报错,默认的yum源是国外的,可能会找不到,我们可以从国内的镜像源下载新的yum源配置文件,并替换原有的文件。以下是一些常用的国内yum源地址: - **阿里云** ```bash wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo ``` - **网易163** ```bash wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo ``` - **搜狐** ```bash wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.sohu.com/help/CentOS7-Base-sohu.repo ``` - **华为云** ```bash wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.myhuaweicloud.com/repo/CentOS-7.repo ``` - **中国科学技术大学** ```bash wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.ustc.edu.cn/centos/7/os/x86_64/ ``` 更改yum源后,需要清理旧的缓存并生成新的缓存,以确保yum能够正确地使用新的源。 ```bash yum clean all yum makecache ``` `yum clean all`命令会删除所有已下载的包和头文件缓存,以及任何已启用的插件数据和旧的内核数据。`yum makecache`命令则会重新生成yum缓存,下载软件包清单和元数据,并将其缓存到本地。 下载上述yum源需要使用wget,wget安装方式如下: ```bash sudo yum install wget # CentOS 7及以前版本 # 或者 sudo dnf install wget # CentOS 8及更高版本 ``` ### 1.3.2.上传安装包并解压 然后将课前资料提供的Redis安装包上传到虚拟机的任意目录: ![](assets/SyjanS5.png) 例如,我放到了/usr/local/src 目录: ![](assets/01DTNCf.png) 解压缩: ```sh tar -xzf redis-6.2.6.tar.gz ``` 解压后: ![image-20211211080339076](assets/8V6zvCD.png) 进入redis目录: ```sh cd redis-6.2.6 ``` 运行编译命令: ```sh make && make install ``` 如果没有出错,应该就安装成功了。 默认的安装路径是在 `/usr/local/bin`目录下: ![image-20240807125448463](assets/image-20240807125448463.png) 该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。其中: - redis-cli:是redis提供的命令行客户端 - redis-server:是redis的服务端启动脚本 - redis-sentinel:是redis的哨兵启动脚本 ### 1.3.3.启动 redis的启动方式有很多种,例如: - 默认启动 - 指定配置启动 - 开机自启 ### 1.3.4.默认启动 安装完成后,在任意目录输入redis-server命令即可启动Redis: ``` redis-server ``` 如图: ![](assets/v7xWsqC.png) 这种启动属于`前台启动`,会阻塞整个会话窗口,窗口关闭或者按下`CTRL + C`则Redis停止。不推荐使用。 ### 1.3.5.指定配置启动 如果要让Redis以`后台`方式启动,则必须修改Redis配置文件,就在我们之前解压的redis安装包下(`/usr/local/src/redis-6.2.6`),名字叫redis.conf: ![image-20211211082225509](assets/image-20211211082225509.png) 我们先将这个配置文件备份一份: ``` cp redis.conf redis.conf.bck ``` 然后修改redis.conf文件中的一些配置: ```properties # 允许访问的地址,默认是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的其它常见配置: ```properties # 监听的端口 port 6379 # 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录 dir . # 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15 databases 1 # 设置redis能够使用的最大内存 maxmemory 512mb # 日志文件,默认为空,不记录日志,可以指定日志文件名 logfile "redis.log" ``` 启动Redis: ```sh # 进入redis安装目录 cd /usr/local/src/redis-6.2.6 # 启动 redis-server redis.conf ``` 停止服务: ```bash #杀死进程 kill -9 ``` > 由于 kill -9 会立即终止进程,不给进程任何清理的机会,因此它可能会导致数据丢失、文件损坏或其他不可预见的问题。通常,建议首先尝试使用 kill(不带 -9)命令发送 SIGTERM 信号,让进程有机会正常退出。如果进程没有响应 SIGTERM 信号,再考虑使用 kill -9 ```sh # 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务, # 因为之前配置了密码,因此需要通过 -u 来指定密码 redis-cli -u 123321 shutdown ``` ### 1.3.6.开机自启 我们也可以通过配置来实现开机自启。 `systemctl`是Linux系统中,特别是RHEL 7及更高版本上,用于管理系统服务的命令行工具 > ##### 常用命令 > > 1. **启动服务**:`systemctl start 服务名称` > 2. **停止服务**:`systemctl stop 服务名称` > 3. **重启服务**:`systemctl restart 服务名称` > 4. **查看服务状态**:`systemctl status 服务名称` > 5. **设置服务开机自启动**:`systemctl enable 服务名称` > 6. **取消服务开机自启动**:`systemctl disable 服务名称` > 7. **重新加载服务配置**:`systemctl reload 服务名称` > 8. **列出所有已启用的服务**:`systemctl list-unit-files --state=enabled` > > 如何将服务交给systemctl管理: > > **1.创建或编辑服务的Unit文件:** > > Unit文件是systemd用来管理服务的脚本文件,通常包含服务的启动、停止、重启等指令。这些文件通常位于`/usr/lib/systemd/system/`(系统级别的服务)或`/etc/systemd/system/`(用户自定义的服务)目录下。 > > Unit文件的基本结构: > > - **[Unit]**:定义了unit的元数据以及与其他units的依赖关系。 > - **[Service]**:定义了服务的具体运行参数。 > - **[Install]**:定义了如何安装这个unit,即如何设置服务的开机启动。 > > **2.将Unit文件放置在正确的位置** > > **3.重新加载systemd的配置** > > ``` > systemctl daemon-reload > ``` 首先,新建一个系统服务文件: ```sh vi /etc/systemd/system/redis.service ``` 内容如下: ```conf [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 ``` 然后重载系统服务: ```sh systemctl daemon-reload ``` 现在,我们可以用下面这组命令来操作redis了: ```sh # 启动 systemctl start redis # 停止 systemctl stop redis # 重启 systemctl restart redis # 查看状态 systemctl status redis ``` 执行下面的命令,可以让redis开机自启: ```sh systemctl enable redis ``` ## 1.4.Redis桌面客户端 安装完成Redis,我们就可以操作Redis,实现数据的CRUD了。这需要用到Redis客户端,包括: - 命令行客户端 - 图形化桌面客户端 - 编程客户端 ### 1.4.1.Redis命令行客户端 Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下: ```sh 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`的交互控制台: ![](assets/OYYWPNo.png) ### 1.4.2.图形化桌面客户端 GitHub上的大神编写了Redis的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager 不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包。 在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases ### 1.4.3.安装 在课前资料中可以找到Redis的图形化桌面客户端: ![](assets/BZ4Agbi.png) 解压缩后,运行安装程序即可安装: ![](assets/hguGHbX.png) 安装完成后,在安装目录下找到rdm.exe文件: ![](assets/hwK5LQ8.png) 双击即可运行: ![](assets/6hUqslY.png) ### 1.4.4.建立连接 点击左上角的`连接到Redis服务器`按钮: ![](assets/9qTGyoN.png) 在弹出的窗口中填写Redis服务信息: ![](assets/DshNnKC.png) 点击确定后,在左侧菜单会出现这个链接: ![](assets/A2cOm7Q.png) 点击即可建立连接了。 ![](assets/ja8Fd9s.png) Redis默认有16个仓库,编号从0至15. 通过配置文件可以设置仓库数量,但是不超过16,并且不能自定义仓库名称。 如果是基于redis-cli连接Redis服务,可以通过select命令来选择数据库: ```sh # 选择 0号库 select 0 ``` # 2.Redis常见命令 Redis是典型的key-value数据库,key一般是字符串,而value包含很多不同的数据类型: ![](assets/8tli2o9.png) Redis为了方便我们学习,将操作不同数据类型的命令也做了分组,在官网( [https://redis.io/commands ](https://redis.io/commands))可以查看到不同的命令: ![](assets/5Lcr3BE.png) 不同类型的命令称为一个group,我们也可以通过help命令来查看各种不同group的命令: ![](assets/suevOIR.png) 接下来,我们就学习常见的五种基本数据类型的相关命令。 ## 2.1.Redis数据结构介绍 image-20240807130052548 ## 2.2.Redis通用命令 通用指令是部分数据类型的,都可以使用的指令,常见的有: - KEYS:查看符合模板的所有key - DEL:删除一个指定的key - EXISTS:判断key是否存在 - EXPIRE:给一个key设置有效期,有效期到期时该key会被自动删除 - TTL:查看一个KEY的剩余有效期 通过help [command] 可以查看一个命令的具体用法,例如: ```sh # 查看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.3.String类型 String类型,也就是字符串类型,是Redis中最简单的存储类型。 其value是字符串,不过根据字符串的格式不同,又可以分为3类: - string:普通字符串 - int:整数类型,可以做自增、自减操作 - float:浮点类型,可以做自增、自减操作 不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m. ![](assets/VZqpv73.png) ### 2.2.1.String的常见命令 String类型,也就是字符串类型,是Redis中最简单的存储类型。 其value是字符串,不过根据字符串的格式不同,又可以分为3类: - string:普通字符串 - int:整数类型,可以做自增、自减操作 - float:浮点类型,可以做自增、自减操作 不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m. image-20240807130332065 String的常见命令有: - SET:添加或者修改已经存在的一个String类型的键值对 - GET:根据key获取String类型的value - MSET:批量添加多个String类型的键值对 - MGET:根据多个key获取多个String类型的value - INCR:让一个整型的key自增1 - INCRBY:让一个整型的key自增并指定步长,例如:incrby num 2 让num值自增2 - INCRBYFLOAT:让一个浮点类型的数字自增并指定步长 - SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行 - SETEX:添加一个String类型的键值对,并且指定有效期 ### 2.2.2.Key结构 Redis没有类似MySQL中的Table的概念,我们该如何区分不同类型的key呢? 例如,需要存储用户、商品信息到redis,有一个用户id是1,有一个商品id恰好也是1,此时如果使用id作为key,那就会冲突了,该怎么办? 我们可以通过给key添加前缀加以区分,不过这个前缀不是随便加的,有一定的规范: Redis的key允许有多个单词形成层级结构,多个单词之间用':'隔开,格式如下: ``` 项目名:业务名:类型:id ``` 这个格式并非固定,也可以根据自己的需求来删除或添加词条。这样以来,我们就可以把不同类型的数据区分开了。从而避免了key的冲突问题。 例如我们的项目名称叫 heima,有user和product两种不同类型的数据,我们可以这样定义key: - user相关的key:**heima:user:1** - product相关的key:**heima:product:1** 如果Value是一个Java对象,例如一个User对象,则可以将对象序列化为JSON字符串后存储: | **KEY** | **VALUE** | | --------------- | ------------------------------------------ | | heima:user:1 | {"id":1, "name": "Jack", "age": 21} | | heima:product:1 | {"id":1, "name": "小米11", "price": 4999} | 并且,在Redis的桌面客户端中,还会以相同前缀作为层级结构,让数据看起来层次分明,关系清晰: ![](assets/InWMfeD.png) ## 2.3.Hash类型 Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。 String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便: ![](assets/x2zDBjf.png) Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD: ![](assets/VF2EPt0.png) Hash的常见命令有: - 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 - HINCRBY:让一个hash类型key的字段值自增并指定步长 - HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行 ## 2.4.List类型 Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。 特征也与LinkedList类似: - 有序 - 元素可以重复 - 插入和删除快 - 查询速度一般 常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。 List的常见命令有: - LPUSH key element ... :向列表左侧插入一个或多个元素 - LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil - RPUSH key element ... :向列表右侧插入一个或多个元素 - RPOP key:移除并返回列表右侧的第一个元素 - LRANGE key star end:返回一段角标范围内的所有元素 - BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil ## 2.5.Set类型 Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征: - 无序 - 元素不可重复 - 查找快 - 支持交集、并集、差集等功能 Set的常见命令有: - SADD key member ... :向set中添加一个或多个元素 - SREM key member ... : 移除set中的指定元素 - SCARD key: 返回set中元素的个数 - SISMEMBER key member:判断一个元素是否存在于set中 - SMEMBERS:获取set中的所有元素 - SINTER key1 key2 ... :求key1与key2的交集 例如两个集合:s1和s2: ![](assets/ha8x86R.png) 求交集:SINTER s1 s2 求s1与s2的不同:SDIFF s1 s2 ![](assets/L9vTv2X.png) 练习: 1. 将下列数据用Redis的Set集合来存储: - 张三的好友有:李四、王五、赵六 - 李四的好友有:王五、麻子、二狗 2. 利用Set的命令实现下列功能: - 计算张三的好友有几人 - 计算张三和李四有哪些共同好友 - 查询哪些人是张三的好友却不是李四的好友 - 查询张三和李四的好友总共有哪些人 - 判断李四是否是张三的好友 - 判断张三是否是李四的好友 - 将李四从张三的好友列表中移除 ## 2.6.SortedSet类型 Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。 SortedSet具备下列特性: - 可排序 - 元素不重复 - 查询速度快 因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。 SortedSet的常见命令有: - 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即可,例如: - **升序**获取sorted set 中的指定元素的排名:ZRANK key member - **降序**获取sorted set 中的指定元素的排名:ZREVRANK key memeber 练习题: 将班级的下列学生得分存入Redis的SortedSet中: Jack 85, Lucy 89, Rose 82, Tom 95, Jerry 78, Amy 92, Miles 76 并实现下列功能: - 删除Tom同学 - 获取Amy同学的分数 - 获取Rose同学的排名 - 查询80分以下有几个学生 - 给Amy同学加2分 - 查出成绩前3名的同学 - 查出成绩80分以下的所有同学 # 3.Redis的Java客户端 在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/ 其中Java客户端也包含很多: image-20220609102817435 标记为*的就是推荐使用的java客户端,包括: - Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。 - Redisson:是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map、Queue等,而且支持跨进程的同步机制:Lock、Semaphore等待,比较适合用来实现特殊的功能需求。 ![image-20240807130541738](assets/image-20240807130541738.png) ## 3.1.Jedis客户端 Jedis的官网地址: https://github.com/redis/jedis ### 3.1.1.快速入门 我们先来个快速入门: 1)引入依赖: ```xml redis.clients jedis 3.7.0 org.junit.jupiter junit-jupiter 5.7.0 test ``` 2)建立连接 新建一个单元测试类,内容如下: ```java private Jedis jedis; @BeforeEach //是JUnit5中用于定义在每个测试方法执行之前执行的代码块的一个注解。会在每个测试方法(即带有 @Test 注解的方法)执行之前被自动调用。 void setUp() { // 1.建立连接 // jedis = new Jedis("192.168.150.101", 6379); jedis = JedisConnectionFactory.getJedis(); // 2.设置密码 jedis.auth("123321"); // 3.选择库 jedis.select(0); } ``` 3)测试: ```java @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 map = jedis.hgetAll("user:1"); System.out.println(map); } ``` 4)释放资源 ```java @AfterEach //用于定义在每个测试方法执行之后执行的代码块。这个注解通常用于清理测试过程中产生的资源或恢复测试前的状态 void tearDown() { if (jedis != null) { jedis.close(); } } ``` ### 3.1.2.连接池 Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式。 ```java package com.LA.Jedis.utils; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; /** * @Author LA * @Date 2024/8/7 14:26 * @Description: 工具类 */ public class JedisConnectionFactory { //Jedis连接池 private static final JedisPool jedispool; //静态初始块 用于初始化类变量 仅在类被加载到JVM时执行一次,而不是在每次创建类的实例时执行。 static { //配置连接池 JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); //最大连接 jedisPoolConfig.setMaxTotal(8); //最大空闲连接 jedisPoolConfig.setMaxIdle(8); //最小空闲连接 jedisPoolConfig.setMinIdle(0); //设置最长等待时间 ms(当连接池里没有可用的空闲连接的时候我们要不要等待,等待多长时间) jedisPoolConfig.setMaxWaitMillis(1000); // 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码 jedispool=new JedisPool(jedisPoolConfig,"192.168.37.13",6379,1000,"011034"); } //获取Jedis实例 public static Jedis getJedis(){ return jedispool.getResource(); } } ``` ## **静态初始块与实例初始块 **静态初始块** > 静态初始块(Static Initialization Block)在Java中是一种特殊的代码块,它用于初始化类变量。静态初始块仅在类被加载到JVM时执行一次,而不是在每次创建类的实例时执行。 > > 静态初始块的主要用途包括: > > 1. **初始化静态变量**:当类中的静态变量需要在类加载时就被赋予特定的初始值时,可以使用静态初始块来完成。这些值可能是计算得到的,或者依赖于某些复杂的逻辑。 > 2. **执行只需执行一次的静态资源初始化**:例如,加载配置文件、初始化数据库连接池、注册驱动等,这些操作通常只需要在类加载时执行一次。 > 3. **确保类的状态在首次使用前被正确设置**:静态初始块可以确保在类的任何静态成员(包括静态方法和静态变量)被访问之前,类的状态已经被正确地设置。 > > 静态初始块的语法很简单,就是使用`static`关键字修饰的代码块: > > ```java > class MyClass { > static { > // 这里是静态初始块的内容 > // 可以初始化静态变量,执行静态资源的加载等 > } > > // 静态变量 > static int myStaticVar = 0; > > // ... 其他成员和方法 > } > ``` > > 需要注意的是,静态初始块没有名字,也没有返回类型(甚至不是`void`)。此外,静态初始块可以在类中的任何位置出现,但无论它们声明的顺序如何,都将在类的构造函数之前执行(且只执行一次)。 > > 如果有多个静态初始块,它们将按照它们在类中出现的顺序执行。此外,静态初始块也可以访问类的静态成员(包括静态变量和静态方法),但不能访问类的实例成员,因为类的实例在静态初始块执行时尚未创建。 > > 静态初始块与静态变量的初始化器(即直接在静态变量声明时赋值)的区别在于,静态初始块可以提供更复杂的初始化逻辑,而不仅仅是简单的赋值操作。然而,在大多数情况下,如果初始化逻辑不是很复杂,直接使用静态变量的初始化器会更简洁明了。 **实例初始块** > 实例初始块(Instance Initialization Block)是Java中一种特殊的代码块,它用于在创建类的实例时执行初始化代码。与构造方法(Constructor)不同,实例初始块不是方法,因此它不能有名称、返回类型或参数。实例初始块的主要目的是提供一种在对象创建时自动执行初始化代码的机制,这些代码可以访问类的实例成员(包括实例变量和实例方法)。 > > 实例初始块在类的构造方法之前执行,并且每次创建类的实例时都会执行一次。如果有多个实例初始块,它们将按照在类中出现的顺序执行。 > > 实例初始块通常用于以下情况: > > 1. **初始化实例变量**:当实例变量的初始化依赖于复杂的逻辑或计算时,可以使用实例初始块来执行这些操作。 > 2. **共享初始化代码**:如果类的多个构造方法中有共同的初始化代码,可以将这些代码放在实例初始块中,以避免在每个构造方法中重复编写相同的代码。 > 3. **初始化依赖于其他实例变量的值**:在某些情况下,一个实例变量的初始化可能依赖于另一个实例变量的值。由于实例变量的初始化顺序是按照它们在类中声明的顺序进行的,因此可以使用实例初始块来确保在初始化某个变量之前,其他必要的变量已经被正确初始化。 > > 需要注意的是,虽然实例初始块提供了一种方便的初始化机制,但过度使用它们可能会使类的初始化逻辑变得难以理解和维护。因此,在可能的情况下,应该优先考虑使用构造方法来初始化对象。 > > 实例初始块的语法很简单,就是直接写在类体中,位于任何实例成员变量或实例方法之前,且不使用任何修饰符(如`static`)的代码块: > > ```java > public class MyClass { > // 实例变量 > int myVar = 0; > > { > // 实例初始块 > // 这里可以执行初始化代码 > System.out.println("实例初始块执行"); > // 可以访问实例变量,但不能访问静态变量(除非通过类名引用) > myVar = 10; // 示例:修改实例变量的值 > } > > // 构造方法 > public MyClass() { > // 构造方法中的代码 > System.out.println("构造方法执行"); > } > > // ... 其他成员和方法 > } > ``` > > 在上面的示例中,当创建`MyClass`的实例时,首先会执行实例初始块中的代码,然后执行构造方法中的代码。如果类中有多个构造方法,实例初始块会在每个构造方法之前执行一次。 **区别** > 静态初始块(Static Initialization Block)和实例初始化块(Instance Initialization Block)在Java中都是用于初始化操作的特殊代码块,但它们之间存在明显的区别。以下是两者之间的主要区别: > > **1. 执行时机和频率** > > - **静态初始块**:在类加载到JVM时执行,且只执行一次。无论创建多少个类的实例,静态初始块都只会在类被加载到JVM时执行一次。 > - **实例初始块**:在每次创建类的实例时执行。如果创建了多个类的实例,实例初始块会针对每个实例执行一次。 > > **2. 修饰符和声明方式** > > - **静态初始块**:使用`static`关键字进行修饰,且没有名称和返回类型。它直接写在类体中,位于任何静态成员变量或静态方法之前。 > - **实例初始块**:不使用任何关键字进行修饰,也没有名称和返回类型。它同样直接写在类体中,但位于任何实例成员变量或实例方法之前。 > > **3. 访问权限** > > - **静态初始块**:只能访问类的静态成员(包括静态变量和静态方法),不能访问类的实例成员(包括实例变量和实例方法)。 > - **实例初始块**:可以访问类的所有成员,包括静态成员和实例成员。但需要注意的是,由于实例初始块在实例创建时执行,它主要用于初始化实例成员。 > > **4. 用途** > > - **静态初始块**:主要用于初始化静态变量或执行与类相关的静态初始化任务,如加载配置文件、初始化静态资源等。 > - **实例初始块**:主要用于初始化实例变量或执行与实例相关的初始化任务。如果多个构造方法中有共同的初始化代码,可以将这些代码放在实例初始块中,以避免代码重复。 > > **5. 执行顺序** > > 在Java中,如果一个类同时包含静态初始块、实例初始块和构造方法,它们的执行顺序如下: > > 1. **静态初始块**(按在类中出现的顺序执行,且只执行一次)。 > 2. **实例初始块**(在每次创建类的实例时执行,且在构造方法之前执行,也按在类中出现的顺序执行)。 > 3. **构造方法**(在创建类的实例时执行,用于完成对象的初始化操作)。 > > **示例** > > 以下是一个简单的示例,展示了静态初始块和实例初始块的使用及执行顺序: > > ```java > public class Test { > static { > System.out.println("静态初始块执行"); > } > > { > System.out.println("实例初始块执行"); > } > > public Test() { > System.out.println("构造方法执行"); > } > > public static void main(String[] args) { > Test test1 = new Test(); // 输出:静态初始块执行、实例初始块执行、构造方法执行 > Test test2 = new Test(); // 输出:实例初始块执行、构造方法执行(静态初始块不再执行) > } > } > ``` > > 从上述示例可以看出,静态初始块只在类加载时执行一次,而实例初始块在每次创建类的实例时都会执行。 ## 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封装到了不同的类型中: ![](assets/UFlNIV0.png) ### 3.2.1.快速入门 SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单。 首先,新建一个maven项目,然后按照下面步骤执行: #### 1)引入依赖 ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.7 com.heima redis-demo 0.0.1-SNAPSHOT redis-demo Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 com.fasterxml.jackson.core jackson-databind org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok ``` #### 2)配置Redis ```yaml 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)注入RedisTemplate 因为有了SpringBoot的自动装配,我们可以拿来就用: ```java @SpringBootTest class RedisStringTests { @Autowired private RedisTemplate redisTemplate; } ``` #### 4)编写测试 ```java @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.2.自定义序列化 RedisTemplate可以接收任意Object作为值写入Redis: ![](assets/OEMcbuu.png) 只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的: ![](assets/5FjtWk5.png) 缺点: - 可读性差 - 内存占用较大 我们可以自定义RedisTemplate的序列化方式,代码如下: ```java @Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory){ // 创建RedisTemplate对象 RedisTemplate 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序列化方式。最终结果如图: ![](assets/XOAq3cN.png) 整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。 ### 3.2.3.StringRedisTemplate 为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。 ![](assets/Ip9TKSY.png) 因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis就不会将class信息写入Redis了。 这种用法比较普遍,因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。 ![](assets/zXH6Qn6.png) 省去了我们自定义RedisTemplate的序列化方式的步骤,而是直接使用: ```java @Autowired private StringRedisTemplate stringRedisTemplate; // JSON序列化工具 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); } ``` ![image-20240807183144140](assets/image-20240807183144140.png)