教学视频地址:B站尚硅谷redis教学视频
文章目录
一、NoSQL简介
- 用途
- 简介
- 适用于
- 不适用
- 具体数据库
- 数据库分类
二、概述和安装
- 概述
- 安装(直接在linux中安装)
测试是否有没有安装gcc命令:gcc --version
安装目录:
- 启动
前台启动(不推荐):redis-server
后台启动:
将解压后的安装包目录中的redis.conf复制到etc(不复制也可以)
编辑etc中的redis.conf
搜索:/daemonize
改为daemonize yes,开启后台启动功能
后台启动命令:redis-server /etc/redis.conf(修改后的配置文件路径) - 连接redis:redis-cli
- 关闭redis:
- 相关知识介绍
单线程+多路io复用:异步调用
三、常用五大数据类型
复习一下java的数据类型
1、基本操作
- 设置键值对:set key value
- 查询所有键:keys *
- 判断键是否存在:exists key
- 输出key对应的值得数据类型:type key
- 删除key对应的键值对:del key
- 异步(非阻塞)删除key:unlink key
- 设置key过期时间:expire key index
- 查看key离过期有少时间(-1为永不过期,-2为已经过期):ttl key
- 切换数据库(0-15,0为默认):select index
- 查看当前数据库key的总量:dbsize
- 清空当前库:flushdb
- 清空所有库:flushall
2、String类型
- 基本概念:
二进制安全:可以被序列化为二进制的数据(即所有数据)都可以用String类型来存贮
最基本类型,一对一,一个key最多可以存放512M的数据 - 基本操作
- 设置键值对(设置相同key会覆盖之前的value):set key value
- 设置键值对(只有key不存在时才能生效,成功返回1,失败返回0):setnx key value
- 获取key的值:get key
- 追加字符,返回追加后的总长度:append key value
- 获取key的长度:strlen key
- 将key的value值加一(原子性,只有value全为数字才可以,如果值为空则新增值为1):incr key
- 将key的value值减一(只有value全为数字才可以):decr key
- 将key的value加x:incrby key x
- 将key的value减x:decrby key x
- 设置多个key-value:mset key1 value1 key2 value2
- 获取多个value:mget k1 k2 k3
- 设置多个key-value(原子性,当且仅当可以不存在才可以成功):msetnx k1 v1 k2 v2
- 获取key的切片(左闭右闭):getrange key start end
- 在指定位置插入:setrange key x value
- 设置带有过期时间的kv:setex key time value
- 取旧换新(返回旧值,写入新值):getset key
- 数据结构
3、List类型
- 基本概念
- 常用操作
- 在key中从左边放入多个value:lpush k v1 v2 v3
- 在key中从右边放入多个value:rpush k v1 v2 v3
- 在key中从左边拿出一个value:lpop k
- 在key中从右边拿出一个value:rpop k
- 从k1右边弹出从左边放入k2:rpoplpush k1 k2
- 在key中从左边取出多个value(-1代表从右往左数第一个):lrange k start end
- 取出从左往右数第x个数:lindex k x
- 获取长度:llen k
- 在value前面或后面加入newvalue:linsert k before/after value newvalue
- 从左往右数删掉x个value:lrem k x value
- 从左往右数将第x个换成value:lset k x value
- 数据结构
4、Set类型
- 简介
- 常用命令
- 添加(已存在的忽略):sadd k v1 v2 v3
- 取出所有:smembers k
- 是否含有v:sismember k v
- 个数:scard k
- 删除:srem k v1 v2 v3
- 随机弹出一个:spop k
- 随机取出x个:srandmember k x
- 将v从k1弹出再存入k2:smove k1 k2 v
- 返回交集:sinter k1 k2 k3
- 返回并集:sunion k1 k2 k3
- 返回差集(k1有k2无):sdiff k1 k2
- 数据结构
5、Hash类型
- 简介
field相当于一个二级key
- 常用命令
- 添加:hset k field v [f v f v…]
- 添加(基本一样):hmset k f v [f v f v…]
- 添加(不存在时才能添加):hsetnx k f v
- 取出:hget k field
- 判断是否存在:hexists k f
- 查询所有f:hkeys k
- 查询所有v:hvals k
- 将k的f加x:hincrby k f x
- 数据结构
6、Zset类型
- 简介
- 常用命令
- 添加:zadd key score value [s v…]
- 根据下标获取(显示评分):zrange k start end [withscores]
- 根据评分 从小到大/从大到小 获取(显示评分):zrangebyscore k min/max max/min [withscores]
- 增加x:zincrby k x v
- 删除v:zrem k v
- 统计评分范围内的元素个数:zcount k min max
- 查看排名(从大到小):zrank k v
- 数据结构
四、redis配置文件
只能本机连接,注释掉即可支持远程连接
是否开启保护模式(只能本机连接),改为no即可关闭保护模式
端口号
连接后经过x秒回被判定为超时,0表示永不超时
tcp长连接超时时间
是否支持后台启动
日志级别(输出的东西详细与否)
日志内容保存文件,默认为空
数据库数量
访问密码的查看设置和取消的权限(默认注销,没有密码),命令行中设置是临时的,配置文件才能永久
五、发布和订阅
- 简介
类似观察者模式,发布者有消息更新就会推送给所有订阅者,好像有其他组件(ksfka)更适合做这个
- 基本使用
两个redis
六、redis6新数据类型
1、Bitmaps
- 简介
- 常用命令
- 在offset(偏移量从零开始)位置设置0/1(初始都是0):setbit k offset 0/1
- 获取offset的值:getbit k offset
- 统计为1的数量(s、t组号,0开始,八位一组,start到end中的):bitcount k [start end]
- bitop
2、Hyperloglog
- 简介
- 常用命令
- 添加:pfadd k v [v2 v3]
- 查询数量:pfcount k
- 合并k1,k2到第三个k:pfmerge k k1 k2
3、Geospatial
- 简介
- 命令
- 添加:geoadd k 经度 纬度 城市名
- 获取:geopos k 城市名
- 两个城市的直线距离:geodist k 城市1 城市2 m
- 查找指定地点x半径范围内的城市:georadius k 经度 纬度 x m
- 添加:geoadd k 经度 纬度 城市名
七、Jedis操作redis
-
简介
在java中操作redis的一个客户端 -
使用
- 将配置文件中的bind注释,protected-mode改为no才可以被外界连接,还要考虑防火墙问题
- 引入依赖
- 使用(与命令行类似):jedis.操作指令(“参数1”,“参数2”,“参数3”)
-
案例
public class PhoneCode {
public static void main(String[] args) {
String phone = "123456789";
String code = getCode(phone);
System.out.println(code);
veryfyCode(phone,code);
}
//验证码是否有效(手机号一天最多发三次,验证码两分钟有效)
public static boolean veryfyCode(String phone,String code){
Jedis jedis = new Jedis("192.168.100.128",6379);
String tempPhone = jedis.get(phone);
String tempCode = jedis.get(phone+"code");
if (tempPhone==null){
//验证用户
System.out.println("查无此用户");
return false;
}else if (tempCode==null){
//验证code
System.out.println("验证码已过期");
return false;
}else if (tempCode.equals(code)){
System.out.println("验证通过");
return true;
}else{
System.out.println("验证码不正确");
}
jedis.close();
return false;
}
//申请验证码:生成6位数随机验证码
public static String getCode(String phone){
Jedis jedis = new Jedis("192.168.100.128",6379);
String phoneCode = phone+"code";
if(jedis.get(phone)==null){
//该手机号从未申请过验证码
//先添加手机,有效时间一天,已经发过一次
jedis.setex(phone,24*60*60,"1");
//生成验证码
Random random = new Random();
String code = "";
for (int i = 0; i < 6; i++) {
code+= String.valueOf(random.nextInt(10));
}
//添加验证码
jedis.setex(phoneCode,120,code);
jedis.close();
return code;
}else if (jedis.get(phone)!=null && Integer.parseInt(jedis.get(phone))>=2){
//已经申请过两次了
System.out.println("今天申请次数已用完");
jedis.close();
return null;
}else if (jedis.get(phone+"code")!=null){
//申请过,已经有验证码存在
System.out.println("请60秒后再发送请求");
jedis.close();
return null;
}else {
//第二次申请
//次数加一
jedis.incr(phone);
//生成验证码
Random random = new Random();
String code = "";
for (int i = 0; i < 6; i++) {
code+= String.valueOf(random.nextInt(10));
}
//添加验证码
jedis.setex(phoneCode,120,code);
jedis.close();
return code;
}
}
}
八、springboot整合redis
略
九 、事务操作
-
简介
-
使用
-
错误处理
若在组队的过程中出错,则不会执行这个事务
若在执行过程中出错,则出错的那个语句失败,其他语句成功 -
事务冲突问题
解决方案(悲观/乐观锁)
-
锁的使用
乐观锁
-
事务的三个特性
-
案例:秒杀购物
- 基本逻辑:具体代码略
- redis连接超时:用单例创建redis连接池解决
- 超卖问题:乐观锁+事务
- 库存遗留问题:watch乐观锁导致很多线程的事务被取消执行(单例也可能同时改变某些key),用lua脚本(具有原子性)代替需要乐观锁的事务内容即可
十、持久化—RDB
- 基本概念
持久化:将redis的内存数据存入硬盘的过程,可以通过RDB、AOF的方式实现
RDB:redis database
- 执行过程:
读时共享、写时复制
由于直接将内存中的数据存入硬盘是属于io操作,redis是单线程程序,所以会非常占用性能,速度很慢,而且复制过程中有可能发生数据更新,倒是发生数据差错(如123456复制到硬盘过程中,存到4的时候数据更新为098765,则最后存入硬盘的数据会变成123465),于是采用写时复制技术:在某个瞬间将数据定格住,若发生数据更新则在内存中创建新的数据,保证旧数据的定格,待持久化完成后再将内存中的新旧数据合并。详情参见:RDB写时复制技术 - 配置文件
持久化文件的文件名
持久化文件保存位置(默认是当前目录下即Redis的启动目录也就是安装目录)
如果硬盘空间不够时,是否停止持久化,推荐选择是(yes)
持久化数据是否进行压缩存贮,会耗费一定的cpu,推荐开启
持久化文件是否进行校验
save:一定时间内当多少key发生变化会进行持久化,即持久化频率
900秒内1个k发生改变,300秒内10个k发生变化,60秒内10000个k发生变化则进行持久话
save与bgsave
- 优势
- 劣势
- 备份dump.rdb文件
可以提前将dump.rdb文件即硬盘中的存贮文件备份,若原先的dump.rdb文件丢失,就可以将原先复制的文件放到对应持久化文件存贮目录中,改名为dump.rdb,启动redis之后就可以自动恢复dump.rdb中的数据了
十一、持久化—AOF
-
基本概念
append only file
-
执行过程
-
配置文件
是否开启功能,默认不开启
日志文件的名字
保存路径与rdb相同路径,默认启动目录(安装目录) -
rdb和aof同时开启,读取谁的数据
-
appendonly.aof日志备份
与rdb一样 -
appendonly.aof的异常修复
当appendonly.aof出现错误时启动redis会拒绝连接,此时可以用bin目录中的文件来修复appendonly.aof文件
-
同步频率设置
-
rewrite压缩
- 原理:将多个命令的最终结果合并为一个命令,如:set k a,set k b则会被合并为set k b一条命令
- 过程:写时复制技术,当进程有空时才将新复制出来的文件即重写之后的文件与旧文件合并
- 触发时机达到上次重写内容大小且大于64M时即会触发重写
-
优势
-
劣势
-
rdb与aof用哪个好
十二、主从复制
-
基本概念
一主多从:只有一个主服务器,才不会造成数据混乱,防止主服务器宕机,可以进行主服务器集群 -
配置
1、关掉配置文件中的appendonly
2、新建文件夹 /myredis,复制redis.conf到/myredis
3、新建redis6379.config,
导入原来的配置文件(include /myredis/redis.conf),
配置pid(pidfile /var/run/redis_6379.pid),
配置端口号(port 6379),
配置持久化文件名称(dbfilename dump6379.rdb)
4、以类似方式创建redis6380.conf,和redis6381.conf
5、启动三个redis
6、连接三个redis
redis-cli -p 6379
redis-cli -p 6380
redis-cli -p 6381
7、查看redis基本信息(info replication)
8、配置主从服务器(在从机中执行slaveof 主机ip 主机端口号)
10、分别查看三个redis的信息(info replication),看是否配置成功 -
注意事项
- 用命令行配置的从服务器不是永久的,在从服务器宕机之后再重启redis则会重新变成主服务器,但是会同步主机的所有数据,即便是在宕机期间添加的数据
- 主服务器宕机后,从服务器角色并没有发生变化,主服务器重新开启之后即可正常工作
-
工作原理
从服务器连接上主服务器后主动提出同步申请,进行全量复制
当主服务器数据变化之后主动向从服务器同步,进行增量复制
-
薪火相传
一台从服务器也可以作为其他服务器的主服务器,从而进行树状图分布,数据会自伤而下进行同步,减轻主服务器的同步压力,但增加了同步风险(某个节点宕机,该节点后面的从服务器就无法进行同步) -
反客为主
在从机中使用命令slaveof no one,在主服务器宕机后自己可以变为主服务器,不是代替主服务器的位置,而是自己变为主机,可以继续向下同步 -
哨兵模式
- 概念
端口号默认26379
- 配置
在/myredis下创建sentinel.conf文件,写入sentinel monitor mymaster 127.0.0.1 6379 1
mymaster代表主机名字,1代表需要多少哨兵统一才可以进行反客为主 - 启动:redis-sentinel /myredis/sentinel.conf
- 注意:哨兵模式与反客为主不太一样,哨兵模式在监视到主机宕机后会选取从机中的一个代替主机的位置,继承旧主机的所有从机,并且在旧主机重启后也会被新主机作为从机并入,而反客为主则是直接封地为王,不管他的兄弟,只管手下,旧主机重启后也不管
- 继承优先级(或replica-priority)
若优先级相同则偏移量高的优先,偏移量:与主机的同步率
若偏移量也相同则选择runid最小的,runidredis启动时随机生成的40位id - java中的实现
- 概念
十三、集群
- 基本概念
只有一台主服务器数据写入的压力很大,所以要进行集群,之前使用反向代理的方式解决,但是redis3.0开始就可以使用无中心化配置
即可以通过任意一台主机进入集群,然后在进行转发,直到到大目的主机为止
- 搭建
-
删除所有redis的dump文件
-
修改配置文件
-
创建redis6380、redis6381、redis6389、redis6390、redis6391,修改配置文件方法类似
-
启动六个redis
检查进程以及节点配置文件的生成 -
进入redis安装目录src执行集群命令:
cd /opt/redis-7.0-rc2 /src
redis-cli --cluster create --cluster-replicas 1 192.168.100.128:6379 192.168.100.128:6380 192.168.100.128:6381 192.168.100.128:6389 192.168.100.128:6390 192.168.100.128:6391(其中ip地址不能使用127.0.0.1,要写实际的本机地址或者确切的ip,replicas 1代表使用最简单的方式集群即一主一从)
-
集群连接方式:redis-cli -c -p 6379
-
查看节点信息:cluster nodes
-
- 注意事项
不同主机之间、主机与从机之间尽量运行在不同ip的物理主机中,避免宕机的风险 - slots
为了负载均衡而存在的
- 常用命令
- 添加数据
由于是通过k来计算插槽值,所以如果要同时添加多个值,就需要在k后面加上相同的组名,用组名来计算插槽值 - 计算k的插槽值:cluster keyslot k
- 查看插槽值中的k(只能查看本库中的插槽):cluster countkeysinslot 插槽值
- 返回count个slot插槽中的键:cluster getkeysinslot slot count
- 添加数据
- 故障恢复
主机宕机后由从机代替主机,旧主机重启后则变为新主机的从机
主从都宕机的情况
- Jedis的集群开发
- 优缺点
十四、应用问题解决
1、缓存穿透
- 基本概念
- 解决方案
布隆过滤器相当于白名单的优化方案
2、缓存击穿
- 基本概念
本来热门key过期随机这个key的重新访问是可以自动将数据放入redis缓存中的,但是有可能缓存是一个异步操作或者其他原因没有第一时间数据缓存,后台数据库被瞬间击穿 - 解决方案
缓存不命中(有可能是其他用户在操作这个k),不立刻去访问mysql,而是设置锁,等待其他用户释放锁之后再重新访问
3、缓存雪崩
- 基本概念
- 解决方案
4、分布式锁
-
基本概念
-
多种解决方案及优缺点
这里只针对redis的解决方案:
使用setnx来上锁,当这个k完成业务时候在del k就释放了(删除,其他人重新创建),本来setnx是当k不存在时才可以创建kv,因此我们可以约定在操作某个对象时就要进行这个k的创建,这就相当于所得作用若长久不释放也会造成阻塞,可以通过设置过期时间解决
由于上锁和设置过期时间不具备原子性,所以可以通过set k v nx expire/ex time来一次性操作
Jedis中的实现:
加上过期时间 -
UUID防止误释放锁
锁的误释放:
解决方案:
由于在创建锁的时候v是没有其他作用的,所以这里就利用v来区分不同的锁,在释放时加以判断,以防误释放
-
LUA保证删除原子性
问题
解决:
十五、redis6新功能
- 关于用户管理
- 查看访问控制列表:acl list
- 查看所有acl[关于string的]操作命令:acl cat [string]
- 查看当前用户:acl whoami
- 添加用户acl setuser username
- 设置密码权限:acl setuser username on >password ~cache:+get
username:用户名
on:启用该用户
》password:用户密码,nopass就是不用密码
~cache::操作的k只能带有“cache:”
get:只能进行get操作 - 切换用户:auth username password
- io多线程
redis6依旧是单线程+多路io复用,但是可以支持网络数据读写和协议解析的多线程
io-thread:io线程数量 - 工具支持cluster
不用ruby工具就可以进行集群