Redis入门笔记
redis 是一款 NoSQL 数据库,是最常见的一款非关系型数据库,主要使用 key - value 的形式存储数据,和 mysql 不同,redis 并不会直接把数据存储到硬盘中,而是存储在内存中,也正是这样的设定让 redis 的存取操作特别的快。
Redis 的下载及安装
redis 的下载
首先要去 redis 的官网下载他的压缩包,官网 [ 中文 ] 直连地址如下:
1 | http://www.redis.cn/ |
进入官网后直接下载即可,然后远程连接 服务器/虚拟机 将下载好的文件上传至 /usr/local
目录下
1 | 进入到usr/local目录 |
redis 的安装
在执行安装命令之前,首选需要安装 gcc 的依赖,如已安装请忽略
1 | yum -y install gcc |
然后切换到 redis 目录下开始执行安装命令
1 | 切换目录到redis下 |
这样一来,安装就顺利结束了,如果提示 -bash: make: command not found 则代表 gcc 没有装,需要重新安装 gcc 环境
redis 的配置
实际上这个时候 redis 已经可以正常运行了,运行 redis 的命令为 redis-server
,但是当我们运行的时候会发现,redis 直接将窗口锁定了,我们不能进行任何的操作,一旦解锁也就意味着 redis 停止工作了
1 | 19196:C 15 Jun 2020 22:13:31.574 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo |
这时我们需要对配置文件做一些修改
1 | # 建议弄个备份,玩儿备份就好,redis 的配置文件还是很重要的 |
运行 redis 并创建连接
首先我们来运行服务端 redis-sever
,需要注意的是这里要指定配置文件进行启动,否则还是会以默认配置执行,还是会锁定当前窗口
1 | # 默认执行,会锁定窗口 |
接下来让我们启动客户端 redis-cli
1 | 如果直接输入则默认连接本地的6379端口 |
尝试输入一个 ping 命令,测试服务是否正常工作,如果返回 PONG 就证明一切正常
1 | 127.0.0.1:6379> PING |
关闭 redis
如果不时用任何配置让他默认启动的话,直接 Ctrl + C 就可以关闭 redis,那么后台运行应该怎么关?
1 | # 连接到指定的服务后,执行关闭命令即可 |
设置访问密码
我们刚刚成功连接到了 redis 服务器,但是这仅仅是本地的,我们目前并不能够远程 ip 地址直接访问,因为我们的 redis 默认开启了保护模式,将来我们的项目是要跑在网络上要用 java 代码操作的,想要远程连接必须要先设置密码!
临时设置密码
为了可以远程操作,我们可以设置一个临时的密码,连接上 redis 客户端,命令如下:
1 | # 设置密码 |
这样一个临时的密码就设置成功了,只要 redis 始终保持正常运行,秘密是不会失效的
设置永久性密码
如果觉得临时密码并不能够满足你的安全感,那么可以通过修改配置文件来设置密码:
1 | # 在redis.conf中找到requirepass然后在后面追加密码,重启redis即可 |
配置文件讲解
学习 redis 最好是跟着官方文档走,记笔记只是为了让自己更加熟练,推荐 redis 中文网:
1 | https://www.redis.net.cn/order/ |
刚刚在安装 redis 的时候简单修改了一下 daemonize 属性的值,让他可以不锁定当前命令行且后台运行,redis 有很多需要了解的常规性配置,这里大概记个笔记:
redis 的配置文件中注释非常多,官方很友好的将模块划分开来,这里就记几个我个人觉得常用的配置
################## NETWORK ######################## 网络
1 | #### 绑定客户端IP |
1 | #### 保护模式 |
1 | #### 启动端口号,不多赘述 |
1 | #### 客户端超时时间/s |
################## GENERAL ######################## 常用
1 | #### 守护进程 |
1 | #### PID文件 |
1 | #### 日志级别 |
1 | #### 日志文件名称 |
1 | #### 默认数据库个数 |
################## SECURITY ######################## 安全
1 | #### 密码 |
Redis 基础命令
redis 中也有库的概念,初始数量为 16 个,可以在配置文件中进行修改,他是按照从 0 开始的递增顺序命名的,和 mysql 不同,redis 不支持为某个数据库单独命名,也不支持为每个数据库设置单独的访问密码,默认我们启动 redis 时使用的是第 0 个数据库。
==切换数据库==应该怎么操作呢?只需要执行 select 命令
1 | # 当我们使用select切换到其他数据库的时候,端口号后面会提示当前使用数据库 [1],默认库就不会提示 |
在我们学习 mysql 的时候,需要通过使用 sql 语句对数据进行 CRUD,而在 redis 我们需要记的是命令.
验证登录密码
1 | 127.0.0.1:6379> auth 123456 |
数据的简单 CRUD
1 | # set命令,因为key是唯一的,所以set既是添加,也是修改,如果有空格需要用双引号引起来 |
查看当前库数据总计数
1 | 127.0.0.1:6379[1]> dbsize |
查看当前库所有的 key,* 代表通配符
1 | 127.0.0.1:6379[1]> keys * |
清空当前数据库
1 | 127.0.0.1:6379[1]> flushdb |
清空所有数据库
1 | 127.0.0.1:6379> flushall |
查看当前库是否存在该 key,只要不返回 0 就代表存在,exists 是否存在
1 | 127.0.0.1:6379> exists name |
将当前库的数据移动到其他库 move 移动,name 为 key,1 为目标数据库
1 | 127.0.0.1:6379> move name 1 |
设置消亡时间 ( 过期时间 ),expire 单位/秒,ttl 查询
1 | # 设置user |
查看 key 的数据类型
1 | 127.0.0.1:6379> type name |
五大基本类型
在上面我们介绍了最后一个命令,是一个 type
命令,使用它可以查看指定 key 的数据类型,这里接触了一个新的知识,就是数据类型,redis 中有有多少数据类型呢?
String 字符串
string 就是字符串啦,我们使用简单的 set 命令设置的数据,如果没有指定,默认就是 string 类型的,就像 java 的 api 一样,string 类型有自己专属的命令
string 类型的命令大多是都是以 str 开头的
字符串操作就是之前学习的 get set 命令,就不多赘述了
组合命令 返回并修改
1 | # 组合命令,修改新的value同时返回修改前的value |
数据的批量操作
1 | 127.0.0.1:6379> mset user zhang name hanzhe age 21 |
带有验证的 setnx
1 | # 同样是设置值,因为key是唯一的,所以他在设置之前会有一个效验,如果已存在就不修改 |
追加字符 append
1 | 127.0.0.1:6379> set name zhang |
返回字符串长度 strlen
1 | 127.0.0.1:6379> strlen name |
自增,自减 incr decr
在 java 中,如果想要对字符串 +1 需要转换类型,而在 redis 中他们自动为我们进行了处理
1 | 127.0.0.1:6379> set view 1 |
截取字符串 getrange,==截取是将获取的结果进行截取,并不会对 key 本身造成改变==
1 | 127.0.0.1:6379> set name zhanghanzhe |
替换字符串 setrange
1 | # 从下标5开始替换,直至替换结束,这个会对name进行修改。 |
设置消亡时间 ( 过期时间 ) setex 单位/秒
之前我们使用过一个叫 expire
的命令,也是用来设置消亡时间的,但是他执行设置已存在的 key,而这个命令可以在设置变量的同时设置消亡时间
1 | 127.0.0.1:6379> setex name 10 zhang #设置name的值为"zhang" 10s后过期 |
List 列表集合
list 是用来存储 string 的一个列表, ==redis 的列表是双向的==,list 默认从右向左延伸,也就是每次添加元素都会添加在左面,==list 列表允许重复元素的出现==
list中的命令,大多数都是以小写字母 l 开头的
创建-添加元素 lpush,rpush
lpush 默认从左侧添加,如需从右侧添加可以使用 r
开头声明
1 | 127.0.0.1:6379> lpush name han |
查看列表 lrange
1 | # 查看之前创建的list,仅支持从左侧查看 |
添加,追加元素 2
之前介绍了 push
命令可以添加元素,通过 l 开头或者 r 开头可以控制左侧添加还是右侧添加,其实添加元素还可以使用 linsert
命令,和 push
不同的是, push
是在首位添加,而 linsert
是从 左面开始在第一个目标 key 的前后添加
1 | 127.0.0.1:6379> lpush name han |
修改指定下标的 value
1 | 127.0.0.1:6379> lrange name 0 -1 |
移除首尾 pop,移除多个 lrem
1 | # 移除左侧第一个,返回被移除的数据 |
返回长度 llen
1 | 127.0.0.1:6379> lpush name han zhang |
获取指定下标的value
1 | 127.0.0.1:6379> lrange name 0 -1 |
截取列表 ltrim
1 | 127.0.0.1:6379> lrange list 0 -1 |
获取排序结果 sort,不会影响本体,
字母排序/alpha
,倒序排序/desc
1 | 127.0.0.1:6379> lpush num 10 30 20 40 |
1 | # 字母排序 |
组合命令:移动元素
1 | 127.0.0.1:6379> lrange name 0 -1 |
Set 无序集合
set 集合是 string 类型的 ==无序集合==,且==不允许存入重复数据==,如果重复存入相同的值会报错
还是老规矩,set 集合的命令特点,就是以 s 开头
set 集合的 CRUD
1 | # 添加 sadd |
1 | # 查看 smembers,这里就可以看出,set集合是无序的 |
1 | # 删除元素 |
查看目标 set 集合长度 scard
1 | 127.0.0.1:6379> scard code |
检查 set 集合中是否包含指定的值 sismember,存在返回 1,不存在返回 0
1 | 127.0.0.1:6379> sismember code java |
随机性的获取和移除
1 | 127.0.0.1:6379> sadd num 1 2 3 4 5 6 7 8 9 |
获取多个集合中的 ==差集==,==交集==,==并集==
1 | ## 前置条件--拥有三个set集合 |
差集 sdiff
,取出两个集合中不同的元素
1 | # 以第一个key为主,依次与每个key取差集 |
交集 sinter
,取出两个集合中相同的元素
1 | # 以第一个key为主,依次与每个key取交集 |
并集 sunion
,取出两个集合中所有的元素
1 | # 以第一个key为主,依次与每个key取并集 |
Hash 散列集合
hash 类型就像 map,是由一个个的 key-value 组成的对象,而且 ==key 不能重复==,存入相同的 key 和 value 后,最后一个存入的会覆盖之前存入的结果
老规矩,几乎所有 hash 的指令都以 h 开头
添加,批量添加,hash 没有修改,覆盖就是修改
1 | 127.0.0.1:6379> hset user name zhanghanzhe |
获取,批量获取
1 | # 通过key获取value |
1 | # 获取所有key |
1 | # 获取所有的key和value |
删除指令
1 | 127.0.0.1:6379> hdel user name |
自增和自减
1 | 127.0.0.1:6379> hset map key1 5 |
判断是否存在,存在返回 1,不存在返回 2
1 | 127.0.0.1:6379> hexists map key1 |
Zset 有序集合
zset 有序集合和 set 集合类似,都是不允许重复的值出现,只不过相比于 set 集合,zset 多了一个排序的功能,在添加值得时候需要给他一个 分值
,分值
可以重复但 value
不允许重复,默认按照分值从小到大排序
老规矩,几乎所有 zset 的指令都以 z开头
添加元素 zadd,第一个是分值,第二个是 value
1 | 127.0.0.1:6379> zadd user 1 zhang |
删除元素
1 | 127.0.0.1:6379> zrem user zhang han zhe |
查看集合
1 | # 正常查看集合 |
获取集合内元素的数量 zcard zcount
这里需要注意:==- inf 代表无穷小,+ inf 代表无穷大==
1 | 127.0.0.1:6379> zadd user 10 zhang 12 wang 18 li 20 zhao 28 bai 30 guo |
三大特殊类型
地理位置
Geospatial
类型用来存储有关经纬度的地理位置信息,默认经度第一位,维度第二位,redis 中针对经纬度的存储范围有一定的限制:
- 经度有效范围:-180 ~ 180
- 维度有效范围:-85.05112878 ~ 85.05112878
老规矩,几乎所有指令都是以 geo开头的
添加地理位置信息 geoadd
在添加经纬度的时候如果超出了范围会报错
1 | 127.0.0.1:6379> geoadd point 126.64 45.75 hei |
查看指定地区的经纬度 ( 已存入的 ),同理,添加也是修改
1 | 127.0.0.1:6379> geopos point ji |
计算两地的距离 geodist
使用 geodist
获取距离是通过经纬度计算出来的 直线距离,并不是路途距离,默认获取单位是米,可以通过制定后缀来设置获取的单位: m/米
,km/千米
,mi英里
,ft英尺
1 | # 获取吉林到黑龙江的直线距离 |
雷达方式获取目标经纬度附近的地理位置 georadius
雷达方式,以经纬度为中心,距离为半径按照圆形扫描,类似微信附近的人功能
withdist距离km
,withdist经纬度
,count 1 显示第一个符合要求的
1 | # 返回经度125维度43附近300km的位置信息 |
1 | # 多要求获取 |
1 | # 使用已存在的地理位置进行获取 |
移除元素
Geospatial
类型比较特殊,他没有给咱们提供移除的指令,但是 Geospatial
的底层是基于 zset
实现的,我们可以通过 zset
来移除指定的 key 即可。
1 | 127.0.0.1:6379> zrange point 0 -1 |
基数统计
hyperloglog
是一种专门用来统计基数的类型,当然其中的元素不允许重复,相比于 set 集合,例如计算网站访问量时,可以交给 hyperloglog
进行处理,他的内存占用是固定的 12KB。
但是因为他仅仅是计算基数的类型,所以并不能像 set 集合一样,获取到元素的具体的值,而且该类型有一定的误差,如果对精准度没有太大要求,那么推荐使用 hyperloglog
老规矩,几乎所有指令都是以 pf开头的
添加元素 pfadd
1 | 127.0.0.1:6379> pfadd num1 1 2 3 4 5 |
查看元素个数
1 | 127.0.0.1:6379> pfcount num1 |
将两个集合合并为一个集合
1 | 127.0.0.1:6379> pfmerge num3 num1 num2 |
进制存储
bitmap
是基于二进制进行存储的,二进制只有 0 和 1 两个值,可以分别用来代表两种相对不同的状态,例如 打卡 <=> 未打卡,可以抽象的理解为 boolean 类型中的 true 和 false 的感觉。
命令几乎都以 bit 结尾
添加元素 [ 0 代表未打卡,1 代表打卡 ]
1 | 127.0.0.1:6379> setbit sign 0 1 |
查看某个状态 — 返回 1 代表打卡,0 代表未打卡
1 | 127.0.0.1:6379> getbit sign 1 |
查看多少人符合条件(即有多少个1)
1 | 127.0.0.1:6379> bitcount sign |
Redis 的事务
事务就是一组命令的集合,将平时多次执行的命令放在一起,然后按照命令顺序依次执行,而且执行过程中不会被干扰,事务执行结束后不会保留,也就意味着每次执行事务都需要重新创建,可得出 redis 事务的三个特点:顺序性,排他性,一次性。
还有几点需要注意:==redis 的事务中,没有原子性和隔离性的概念,也不包含回滚==,所有命令在加入事务的时候,并没有直接执行,而是被放在了执行的队列中,也就不存在隔离性。
事务涉及到的关键字:开启/multi
,执行/exec
,放弃/discard
,监视/watch
,关闭监视/unwatch
使用事务
事务的执行
1 | # 当输入multi的时候就表示事务开始了,当使用exec的时候,就表示要执行了 |
1 | # 当我事务添加中途,不想执行了,那么可以使用discard命令来放弃当前事务 |
事务的异常处理机制
和 java 有点类似,redis 针对事务也分所谓的 编译时异常
和 运行时异常
,只不过这里的 编译时异常
指的是命令是否正确,针对不同的异常,redis 事务处理的方式也不一致
- 命令错误的处理方式
1 | # 当我在执行命令的时候,命令输入有误,这时整个事务都不会执行 |
- 逻辑错误的处理方式
1 | # 自增的变量是字符而不是数字 |
因为 redis 的事务管理并不严格,所以 redis 的事务又被人戏称 伪事务
乐观锁
在我们执行事务的时候,如果是处在多线程的环境下,我通过事务对某个数据进行改变,但是在我命令缓存完成还没有执行的时候,另一条线程进来对这个数据进行了修改,那么就会发生难以想象的改变。
模拟举例说明:
1 | ## 线程1 启动事务 |
1 | ## 线程2 中途插入 |
1 | ## 线程1 这时刚刚提交事务 |
可以发现,在事务执行的过程中数据被改变了,结果也造成影响了
什么是乐观锁?
关于锁,有两个概念,一个是 悲观锁
,还有一个是 乐观锁
,悲观锁类似 java 中的多线程锁 synchronized
,将整个方法上锁,无论是否有多条线程访问都会工作,在安全的前提下影响了性能,而 乐观锁
是在指令添加前将被操作的那个 key 监视起来,如果在执行的时候发现目标 key 发生了改变,那么就将当前事务取消不执行。
1 | ## 线程1 |
1 | ## 线程2 中途插入 |
1 | ## 线程1 这时刚刚提交事务 |
可以发现,被监视的 key 发生改变后,事务执行就被中断了,那么之后应该如何处理呢?
乐观锁善后
1 | # 1.关闭当前事务 |
持久化保存策略
我们都是到,redis 是操作内存的数据库,正因为这样的特点才让他的存取速度特别快,但是在内存中存放的数据,当我们重新启动服务器的时候就会丢失,这个时候就需要接触到 持久化
这个技术了,持久化就是把当前进程数据生成快照保存到硬盘的过程。
redis 的持久化操作分为两种,分别为 RDB
和 AOF
,两种方式只能同时使用一种,redis 使用的 默认持久化方式为 RDB,
RDB 方式
RDB ( Redis DataBase ) 持久方式,会单起一条线程,在指定的时间间隔内将内存中的数据以二进制形式写入、临时文件中,写入成功后默认存放到 redis 的安装目录下的 dump.rdb
文件中,如果你使用自己的配置启动的 redis,那么 dump.rdb
会和你的配置文件同级。
RDB 的持久化有两种触发机制,一种是手动命令持久化,一种是自动持久化。
手动持久化
save
,bgsave
1 | 127.0.0.1:6379> save |
save
命令会阻塞当前 redis 服务器,期间不能正常提供服务,这一现象直至数据保存完毕后恢复正常。bgsave
会执行 fork 子进程负责持久化操作,在创建子进程的时候会有短暂的阻塞,时间很短。
除开这种主动的持久化之外,一些其他的命令也会完成持久化的操作,例如:flushall
,shutdown
等等。
自动持久化
配置文件中的 SNAPSHOTTING 模块就是用来做 RDB 持久化的,里面有这样几句配置命令:
1 | ################################ SNAPSHOTTING ################################ 快照 |
一般我们不需要该他的配置文件,预设的就够用了。
#####AOF 方式
同样还是在配置文件中,APPEND ONLY MODE 模块就是负责 AOF 持久化的,和 RDB 不同, AOF 的原理是将所有曾经使用过的存入操作的命令都记录下来,存放到 appendonly.aof
文件中,是可以看个模糊的大概的。
AOF 持久化配置
AOF 也可以手动触发,只需要执行 bgrewriteaof
命令即可,但是一般都用配置文件管理,配置信息如下:
1 | ############################## APPEND ONLY MODE ############################### 仅附加模式 |
比较RDB和AOF
RDB
:RDB 是二进制文件,按照时间进行数据同步,每次同步都会执行 fork 操作,如果为了追求数据数据完整性不停的同步,会极大的影响 redis 工作效率,更==适合做定期备份,用于灾难恢复==
AOF
:AOF 通过记录命令实现持久化,通过控制参数可以精确到秒级。
有关于持久化策略,以后再细学
文件损坏修复
当我们的数据文件损坏导致 redis 无法启动的时候,我们可以尝试运行 redis 的修复工具
1 | redis-check-aof --fix 配置文件 |
有关于持久化策略,以后再细学
Jedis整合使用
所有程序均是以 maven 为基础搭建的
Java整合
使用 java 项目整合 redis 进行操作,首先要添加 maven 依赖
正常操作 redis
1 | <dependency> |
添加依赖完成后,便可以直接通过 java 代码创建连接并操作数据库了
1 | public class Demo1 { |
直接创建 jedis 对象就可以创建远程连接,而且实例内的方法和 redis 的指令用法等等几乎一模一样,非常好用。
redis 连接池
一般来说 jedis 已经可以很好的操作 redis 数据库了,但是在项目中如果频繁的创建和关闭连接,是很耗费服务器资源的,所以这里可以使用 jedis 的连接池进行操作
1 | // 使用默认的连接池操作 |
1 | // 使用自定义配置的连接池 |
检查 redis 数据库
1 | 127.0.0.1:6379> keys * |
经过检查发现,java 操作 redis 不存在其他的问题,整合基本完成。
控制台打印警告问题
1 | SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". |
如果控制台打印如上的警报信息,可以引入 slf4j 的 maven 依赖进行解决
1 | <dependency> |
Springboot整合
springboot 整合同样需要引入对应的 maven 依赖,和 jedis 有些许的不同,springboot 中封装的类并不可以直接调用类似命令的函数,而是对他们进行了二次封装
1 | <dependency> |
修改配置文件
1 | # IP地址 |
简单操作数据库
操作数据库,需要注入 RedisTemplate
对象进行操作
1 |
|
RedisTemplate
将数据类型对应的指令函数分别命名为 opsForValue()
,opsForList()
,opsForSet()
,opsForHash()
,opsForHyperLogLog()
,opsForZSet()
高级操作数据库
上面的方法封装了各种数据类型的简单操作,接下来就是一些高级的操作了。
1 |
|
乱码问题
1 | // Java 代码 |
1 | # redis 数据库 |
我们通过 springboot 向 redis 中插入一个字符串,发现存进去的字符串存在乱码问题,这时候我们可以通过使用 RedisTemplate
的子类 StringRedisTemplate
进行操作
1 |
|
1 | # name 也可以正常存取 |
自定义 RedisTemplate
字符串可以通过 StringRedisTemplate
来解决问题,但是存入其他类型的还是会出现问题,这时可自己创建一个 RedisTemplate
来代替原本的类工作。代码来自互联网
1.首先要引入 maven 依赖
1 | <dependency> |
2.创建配置类
1 |
|
这个时候自定义的 就已经完成了,现在再来测试一遍是否乱码
1 |
|
1 | # 乱码问题已经解决,这个特殊符号是字符串的转义 |
通过序列化将对象存储到 redis 中
1 | // implements Serializable 序列化接口一定要实现 |
1 |
|
订阅与发布
redis 的订阅发布是一种通讯模式,分别为 发送者/sub
和 订阅者/pub
两种身份,发布者负责发送一些信息,然后由订阅者接收,涉及到的命令也非常少
最常用:简单使用订阅和发布完成交互
subscribe
,publish
1 | # 订阅者 当我订阅s1频道的时候,命令行就会被锁定,静等s1频道发送信息 |
1 | # 发布者 只需要在固定的频道发送消息即可,不用考虑订阅者状态 |
1 | # 订阅者 命令行状态发生改变: |
主从复制
在我们的项目越做越大的情况下,一个 redis 服务可能已经不支持我们的读写效率了,这个时候我们需要配置多个服务器,在每个服务器上都配置 redis 的环境,让他们分别为一个程序提供服务,这种工作方式被称之为集群
在多个服务器中选中一台服务器为主机 ( Master ),其他为从机 ( Slave ),主机负责写入数据 ( set.. ),而从机负责读取数据 ( get ),为了多个服务器之间数据同步的问题,所以有了主从复制的技术。
这里可以通过复制多个配置文件,修改端口号来实现 伪集群
,设置 6379 为主机,6380 为从机,后面简称为 79 和 80
通过命令实现
选择 79 为主机,连接主机输入 info replication
命令即可查看当前机器配置状态
1 | 127.0.0.1:6379> info replication |
第二行位置显示 role:master,代表当前端口是主机,==redis 默认每台机器都是主机==,配置主从复制只需要配置从机就可以了。
配置从机 slaveof
认主,只需要找到目标 Redis 服务器作为自己的主人就可以了,这样一来从机就可以获取到主机的数据了。
1 | # 80,81 端口 |