目录
1.1、NoSQL(Not Only SQL),不仅仅是是数据库,泛指非关系型数据库,以简单的key-value形式存储
2.1.2、多样的数据结构存储持久化数据(也就是利用Redis可以做什么)
2.2、Redis安装:https://www.bilibili.com/video/BV1ga4y1v79L?p=7
5、eclipse-spring boot访问Redis(maven)
5.3.2、application.properties配置
官方网站:https://redis.io/
中文官网:http://www.Redis.net.cn
Redis数据类型命令参考:http://redisdoc.com/
1、NoSQL简介
1.1、NoSQL(Not Only SQL),不仅仅是是数据库,泛指非关系型数据库,以简单的key-value形式存储
- 不支持ACID(事务的四个特性,原子性、一致性、隔离性、持久性)
- 不遵循SQL标准。指不用写SQL就可以存储
- 远超SQL性能
1.2、适用场景
- 对数据的高并发的读写操作
- 海量数据读写
- 对数据的高可扩展性
1.3、不适合场景
- 需要事务的支持
- 基于SQL的结构化查询存储,处理复杂的关系,需要即席查询。即席:立即就查询出来的。指NoSQL适合存储和查询,不适合统计
2、Redis概述
- 开源的key-value存储系统
- 支持的value的类型:String(字符串)、list(链表)、set(集合)、zset(有序集合)、hash(map)
- 支持存值/取值,取交集、并集和差集操作,这些操作都是原子性的
- 支持不同方式的排序
- 为了保证效率,数据都是缓存在内存中的
- 支持持久化,周期性的将内存数据持久化存在磁盘内
- 支持主从复制(master-slave),主机负责写操作,且主机数据会同步到从机,从机负责读操作。
2.1、应用场景
2.1.1、配合关系型数据库做高速缓存
- 高频次,热门访问数据,降低数据库IO。例如某个新闻的访问量
- 分布式架构,做session共享
2.1.2、多样的数据结构存储持久化数据(也就是利用Redis可以做什么)
- zset:可以实现排行榜
- 过期操作,例如手机的短信验证码失效时间
- 实现商品的秒杀功能、计算器
- set:彩票的随机数
2.2、Redis安装:https://www.bilibili.com/video/BV1ga4y1v79L?p=7
2.3、Redis启动
- (建议)将Redis安装目录里的redis.conf文件拷贝出去(/myredis 目录),在复制后的redis.conf文件里修改
- 将bind 127.0.0.1 注释掉,不注释掉表示只能本机访问redis服务器(redis所在的服务器)
- protected-mode yes (保护模式),改为no
- daemonize no (后台启动),改为yes
由于是编译安装的,它的命令都在/usr/local/bin 中(属于6个bin命令之一),所以在任何命令下执行
redis-server /myredis/redis.conf
2.4、客户端访问:redis-cli -p 6379
2.4.1、测试是否连通:ping
2.5、服务器的关闭
- redis-cli -p 6379 shutdown
- 进入终端后再关闭
2.6、Redis相关知识
- 默认16个库,下标从0开始,默认使用0号库
- 使用select 下标 来切换库。例如:select 8 切换到了第九号库
- dbsize 查看当前数据库key的数量,keys * 可以查看所有key
- flushdb清空当前库,flushall清空所有库
2.7、Redis底层采用的策略
redis是单线程+多路IO复用技术,多路复用是指用一个线程检查多个文件的就绪状态。采用select或者poll函数或者xpoll函数,传入多个文件的状态,如果一个文件状态就绪,则返回,否则阻塞至超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(例如使用线程池)
知识点:
- select策略,线程循环的对每个文件进行询问就绪状态(询问是否就绪了),如果没有文件处于就绪状态,就会死循环的询问每个文件,直到有文件处于就绪状态,线程对该文件进行IO操作
- poll策略,线程不主动的询问文件的就绪状态,当某个文件处于就绪状态,会告知线程,这时线程会对每个文件进行询问就绪状态(一次循环的询问),找到该文件进行IO操作
- xpoll策略,和poll策略类似,线程不主动的询问文件的就绪状态,当某个文件处于就绪状态,会告知线程,这时线程不用询问每个文件,直接就知道是那个文件处于就绪状态,对该文件进行IO操作
2.7.1、与memcache的不同
- 支持多数据类型
- 支持持久化
- 单线程+多路IO复用
3、常用五大类型
3.1、Redis键(key)
- keys * 查看当前库所有key
- exists key 判断某个可以是否存在
- type key 查看key是什么类型
- del key 删除key
- expire key 10 为给定的key设置过期时间(10秒)
- ttl key 查看key还有多少秒过期,-1表示永不过期,-2表示已经过期
- select 下标 切换数据库,下标从0开始到15,一共16个库
- dbsize 查看当前数据库的key的数量
- flushdb 清空当前数据库
- flushall 清空所有数据库
3.2、Redis字符串(String)
String是二进制安全的。说明Redis的string可以包含任何数据。例如图片或者序列化的对象。最大是512M。
- set key value 添加键值对,覆盖值
- get key 查询值
- append key value 将给定的value追加到原值的末尾
- strlen key 获取值的长度
- setnx key value 只有key不存在时设置key的值,如果存在则不进行覆盖值
- incr key 将key中的值加1,只能对数值进行操作,如果为空,新增值为1
- decr key 将key中的值减1,只能对数值进行操作,如果为空,新增值为-1
- incrby key <步长> 将key的值根据步长新增
- decrby key <步长> 将key的值根据步长减少
- mset key1 value1 key2 value2... 同时设置多个key-value值
- mget key1 key2 key3 同时获取多个key的值
- msetnx key1 value1 key2 value2... 同时设置多个key-value值,当且仅当所有的key都不存在。该操作是原子性的,一个失败都失败
- getrange key <起始位置> <结束位置> 获取值的范围,类似java中的substring,前包后包
- setrang key <起始位置> value 用给定的value值,覆写key的值,从起始位置开始,下标从0开始
- setex key <过期时间> value 设置键值的同时设置过期时间,单位秒
- getset key value 以新换旧,设置新值同时获取旧值
知识点:
redis中的加加减减是原子性的,是安全的,在多线程中,该线程的操作不能被其他线程打断。
面试点:
java中的i++是原子性的吗?
答:不是。
i=0;两个线程分别对i进行++100次,最后i的值是多少?
答:2~200。
讲解的案列视频:https://www.bilibili.com/video/BV1ga4y1v79L?p=13 大概八分钟
3.3、Redis列表(list)
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加元素到列表的头部(左边)或者尾部(右边)。它的底层实际是个双向链表,对两端的操作性高。
- lpush/rpush key value1 value2 value3 ... 从左边/右边插入一个或者多个值
- lpop/rpop key 从左侧/右侧吐出一个值。该值从list中删除了
- rpoplpush key1 key2 从key1右侧取出一个值,从左侧放到key2中
- lrange key start end 按照索引下标获取元素(从左到右)。列表中的值还在。lrange key 0 -1 从0开始,-1表示所有
- lindex key index 按照索引下标获取元素(从左到右)。列表中的值还在。
- llen key 获取列表长度
- linsert key before/after value newvalue 在原有的值(value:key中的某个下标的值)后面插入新值
- lrem key n value 从左边删除n个value(从左到右),例如删除key中的2个“A”
3.4、Redis集合(set)
Redis的set是string类型的无序集合。它的底层其实是一个value为null的hash表,所以删除、添加、查找的复杂度都是O(1)
- sadd key value1 value2 将一个或多个元素添加到集合中,已存在的忽略。
- smembers key 取出所有值
- sismember key value 判断集合中是否含有该value值,存在返回1,没有返回0
- scard key 返回集合元素个数
- srem key value1 value2 删除集合中的某个元素
- spop key 随机从集合中吐出一个值,会删除元素,元素全部吐出后,key消失
- srandmember key n 随机从集合中吐出n个值,不会删除元素
- sinter key1 key2 返回交集元素
- sunion key1 key2 返回并集元素
- sdiff key1 key2 返回差集元素,注意:key1和key2的前后顺序不同,会造成结果的不同
3.5、Redis哈希(hash)
Redis hash是一个键值对集合,相当于 value的值是Map,是一个string类型的field和value的映射表,特别适合存储对象。
- hset key field value 给key中的field键赋值value
- hget key1 field 从key1集合的field键中取出值
- hmset key1 field1 value1 field2 value2 批量设置值
- hexists key field 查看key中是否存在field,0表示没有,1表示有
- hkeys key 列出key中所有的field
- hvals key 列出key中所有的value值
- hincrby key field increment 为key中的field的值加上增量increment
- hsetnx key field value 将key中的field的值设置成value,当且仅当field不存在
3.6、Redis有序集合(zset)
Redis的zset 和set非常相似,是一个没有重复元素的字符串集合。不同是zset关联了一个评分(score),这个score被用来按照从最低分到最高分的方式排序集合中的元素。score是可以重复的。可用zset来做排行榜功能。
- zadd key score1 value1 score2 value2 将一个或多个元素及其score值加入到可以中
- zrange key start end [withscores] 将key中下标在start和end之间的元素返回,带withscores,可以让分数和元素一起返回。zrange key 0 -1 表示全部取出。
- zrangebyscore key min max [withscores] [limit offset count] 返回key中所有score值在min和max之间(min<=score<= max)的元素。按score的值从小到大排序。可选的 limit参数指定返回结果的数量及区间(就像SQL中的 select
limit offset, count
),注意当offset
很大时,定位offset
的操作可能需要遍历整个有序集
- zrevrangebyscore key max min [withscores] [limit offset count] 返回key中所有score值在min和max之间(min<=score<= max)的元素。按score的值从大到小排序。
- zincrby key increment value 为元素的score加上增量increment
- zrem key value 删除指定值的元素
- zcount key min max 统计集合,分数区间内的元素个数
- zrank key value 返回该值在集合中的排名,从0开始
4、Redis配置文件介绍
- bind 127.0.0.1 表是支持绑定ip的机器可以访问Redis服务,多个ip之间用空格分开,一般注释掉(推荐),表示所有机器都可以访问Redis
- protected-mode yes 保护模式,一般改为no,和bind一起配置的
- port 端口号
- daemonize no 启动方式,改为yes(后台启动)
- dbfilename dump.rdb 持久化RDB文件的名称配置
- appendfsync everysec AOF文件的同步频率,默认每秒同步操作步骤
5、eclipse-spring boot访问Redis(maven)
5.1、所需jar
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
5.2、java简单构建短信验证码功能
/**
* 获取短信验证码
*/
void getCode() {
// 前台获取的手机号
String phone = "13694198150";
// 设置每天发送次数
int maxNum = 3;
// 连接redis,ip+port
Jedis jedis = new Jedis("211.137.43.228", 6379);
// ValueOperations opsForValue = redisTemplate.opsForValue();
// 发送次数key
String countKey = phone + "_count";
// String string = (String) opsForValue.get(countKey);
// 获取历史发送次数
String phoneCount = jedis.get(countKey);
if (phoneCount == null) {// 当天没发送过验证码
// 设置发送次数(第一次),且设置失效时间:一天
jedis.setex(countKey, 24 * 60 * 60, "1");
// opsForValue.set(countKey, "1", 24 * 60 * 60, TimeUnit.SECONDS);//会自动关闭连接
} else {// 当天发送过
// 判断是否超过每天的发送限制
if (Integer.valueOf(phoneCount) >= maxNum) {
System.err.println("每天发送短信超过次数了");
jedis.close();
} else {
jedis.incr(countKey);
}
}
// 获取验证码
String code = "1234";
String codeKey = phone + "_code";
// 设置5分钟有效
jedis.setex(codeKey, 60 * 5, code);
jedis.close();
// 发送验证码给手机
System.out.println("验证码:" + code);
}
/**
* 校验验证码
*/
void checkCode() {
// 获取前台手机号
String phone = "13694198150";
// 获取前台验证码
String code = "123456";
// 从redis中获取验证码
String codeKey = phone + "_code";
Jedis jedis = new Jedis("211.137.43.228", 6379);
String redisCode = jedis.get(codeKey);
jedis.close();
if (redisCode == null) {
System.out.println("验证码已失效");
} else if (!code.equals(redisCode)) {
System.out.println("验证码不正确");
} else {
System.out.println("验证成功");
}
}
5.3、spring boot使用连接池构建jedis
5.3.1、所需jar
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
5.3.2、application.properties配置
# Redis配置
## Redis数据库索引(默认为0)
spring.redis.database=0
## Redis服务器地址
spring.redis.host=127.0.0.1
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=
## 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=8
## 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
## 连接池最大空闲连接
spring.redis.jedis.pool.max-idle=8
## 连接池最小空闲连接
spring.redis.jedis.pool.min-idle=0
## 连接超时时间(毫秒)
spring.redis.timeout=0
5.3.3、java代码
@Resource
RedisTemplate redisTemplate;
void test(){
ValueOperations opsForValue = redisTemplate.opsForValue();
String string = (String) opsForValue.get(countKey);
// 设置发送次数(第一次),且设置失效时间:一天
opsForValue.set(countKey, "1", 24 * 60 * 60, TimeUnit.SECONDS);//会自动关闭连接
}
6、Redis事务
Redis的事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他客户端发来的命令打断。
Redis事务的主要作用就是串联多个命令,防止其他命令插队
6.1、multi、exec、discard
从输入multi开始,输入的命令一次进入队列,但不会执行,知道输入exec后,命令才会依次执行。组队过程中可以用discard来放弃组队
6.2.事务的错误处理
- 1、组队过程中某个命令出现错误,执行时整个队列中的命令都取消,一个都没执行
- 2、执行过程中某个命令出现错误,则只有报错的命令不会执行,其他命令继续执行
6.3、实现秒杀功能:
https://www.bilibili.com/video/BV154411r71N?p=10 32:32秒
6.4、Redis事务的特性
- 单独的隔离操作。所有的命令都序列化。按顺序的执行,不能被打断
- 没有隔离级别的概念。队列中的命令在没有提交前,不会被真正的执行
- 不保证原子性。参考6.2
7、Redis持久化
Redis持久化提供了2中形式,RDB和AOF
7.1、RDB
在指定时间间隔内将内存中的数据快照到磁盘内。文件后缀 .rdb
7.1.1、RDB原理
操作Redis的线程会复制一个和自己任何东西都相同的线程fork(子线程),fork会将数据写入到一个临时的文件中,待持久化结束,再用这个临时文件替换磁盘内的已经持久化的文件。整个过程主线程不做任何IO操作。当主进程没做任何操作时,主进程和子进程共用同一个物理内存,当主进程进行写入时,子进程会复制一份内存。也就是Linux的“写时复制技术”。
7.1.2、RDB优缺点
- 有点对于恢复大规模数据快
- 最后一次持久化后的数据可能会丢失
- 对于内存大小要求高,因为会存在两倍的数据(主线程的redis数据和fork临时文件数据)
7.2、AOF
将Redis执行的所有操作以日志的形式记录(读操作不记录),redis启动之初会读取该文件重新构建数据,也就是将所有写操作执行一遍。
- AOF默认不开启,appendonly no 改为yes,
- 持久化的文件名称默认是appendonly.aof,它是可编辑的文件(vim),文件同RDB文件同目录。
- 如果修改aof文件,使文件损坏,执行redis-check-aof --fix appendonly.aof 进行恢复
- aof采用文件追加的方式(在aof文件的末尾追加写命令),指aof的文件会越来越大,为了避免此情况,新增了重写机制,当aof文件大小超过设定的值时,Redis会启动aof文件内容压缩,只保留可以恢复数据的最小指令集。可以使用bgrewriteof。例如set k1 100, incr k1,两个命令可以直接改写成set k1 101
7.2.1、aof重写配置
- auto-aof-rewrite-precentage 100 达到设定重写大小值的百分比(1+100%),设置重写大小为64M,达到128M开始重写。公式:AOF当前大小>=baseSize + baseSize * 100%,且当前大小大于等于64MB(默认)的情况下。
- auto-aof-rewrite-min-size 64mb 设定重写的大小
7.2.2、aof优缺点
- 丢失数据小
- 可读写文件,通过操作aof文件可以处理失误操作
- 比RDB占用磁盘多
- 恢复速度慢
- 每次写操作都需要记录,有一定性能压力
8、Redis主从复制
主从复制,就是主机数据更新后根据配置和策略,自动更新同步到从机上的机制。Master以写为主,Slave以读为主。
8.1、配置主从复制(一主二仆模式)
本次配置一台主机,两台从机
- 新建3份conf文件(空白的),命名为redis_6379.conf,(名字随便)此为主机配置文件;其他两个分别为redis_6380.conf和redis_6381.conf,此两个为从机配置文件,
- 主机配置文件中编辑,新增: include /redis.conf 的绝对路径/redis.conf,表示包含原有的redis.conf的所有配置;但是需要从新覆盖一个配置(如果在原有的redis.conf 里改过就不需要改了),例如daemonize 改为yes,pidfile /路径/redis_6379.pid,port 改为 6379 ,dbfilename 重命名dump_6379.rdb,关掉aof,appendonly 改为 no
- 其他从机和主机配置类似
- 根据相应的redis_*.conf 来启动Redis
- Redis客户端分别连接Redis服务器,redis-cli -p 6379,redis-cli -p 6380,redis-cli -p 6381
- 打印三台Redis的主从关系,info replication ,刚开始三台机器都是主机,需要配置主从关系
- 在从机上(你想要此机器成为从机的机器上),执行slaveof <ip> <port> ,表示该机器的redis成为某个redis的从机。例如:6380端口的redis想要成为6379端口的redis的从机,slaveof 211.137.0.0.1(6379所在机器的IP) 6379,表示成为211.137.0.0.1 IP上的端口号为6379的Redis的从机
主机配置:
从机配置:
启动redis:
主从关系:配置前三台机器的信息都一样
主从关系:配置后
8.1.1、知识点
- 后加入的从机的数据是从主机数据从头开始复制的,就是说从机数据和主机数据一样
- 从机不能有写操作(写操作或报错)
- 主机shutdown后,从机原地待命,还是从机角色,不能变成主机,待到主机重启后再次连接
- 从机shutdown后再次启动,则不能自动连接原来的主机,该从机角色变成主机了,解决:在从机配置文件内加上:slaveof <ip> <port>,则从机shutdown后再重启就会执行该操作,角色变成从机
8.1.2、复制原理
- 每次从机联通后,会给主机发送同步命令
- 主机接收到后立即进行存盘操作,发送rdb文件给从机
- 从机接收到rdb文件后,进行全盘加载
- 之后每次主机的写操作,都会立即发送从机,从机执行相同的命令
8.2、薪火相传模式
经过配置后,中间的从机的信息:
8.2.1、薪火相传优缺点
- 提升主机性能,减轻主机发送给从机数据的压力
- 一旦某个中间节点的从机宕机,该从机后面的从机无法获取数据
8.2.2、反客为主
主机宕机后,如何让从机变成主机。从机上执行 slaveof no one 命令
8.3、哨兵模式(一主二仆模式)
后台监控主机是否宕机(心跳机制:每隔一段时间发送 ping命令,看是否 有pong 回来),如果故障了根据投票数自动将从机变成主机
8.3.1配置哨兵模式
- 调整为一主二仆模式
- 在redis_6379.conf 同目录下新增sentinel.conf 文件,文件名称不能改,
- 在文件里编辑,sentinel monitor mymaster 127.0.0.1 6379 1,其中mymaster 为监控对象起的服务器名称(哨兵的别名,随便起),127.0.0.1 为主机地址,1 为至少有多少个哨兵同意主机死掉了,可以将从机变成主机
- 启动哨兵,执行 redis-sentinel /地址/sentinel.conf
当主机宕机后,哨兵将从机变成主机,如果之前的主机再次启动后,将变成从机
8.3.2、哨兵原理
- 在从机中首先挑选优先级大的从机。优先级:redis.conf 配置文件中:slave-priority 100,100最小 ,如果值是 0 表示永远不能当主机
- 其次优先级相同的从机,选择偏移量最大的。偏移量:指获得原主数据最多的
- 最后优先级和偏移量都相同,选择runid最小的从机。runid:每个redis实例启动后,都会随机生成一个40位的runid
- 选出从机后,sentinel向从机发送slaveof no one 命令。
- 如果原主机重启后,sentinel向该主机发送 slaveof <ip> <port>命令。
9、Redis集群
9.1、什么是集群
- Redis集群实现了对Redis的水平扩容,即启动N个redis节点,将整个redis数据库分布储存在N个节点上,每个节点的数据是总数据的1/N。
- Redis集群通过分区来提供一定程度的可用性:即使集群中的某个节点失效,集群也可以继续处理请求。
9.2、什么时候使用Redis集群?
- 当一台主机的内存不够时。
- 当高并发的写操作时。
9.3、配置集群(最少6个redis,3主3从)
注意:配置集群需要数据从0开始,即redis不能存数据,将rdb文件和aof文件删除
- 安装集群环境:https://www.bilibili.com/video/BV154411r71N?p=15 38:26
- 新建6个redis.conf文件
- 每个文件配置集群信息。下面图文,其中超时时间单位:毫秒
- 将6个redis节点合成集群,进入redis安装包目录的src 文件夹下
- 执行:./redis-trib.rb create --replicas 1 <ip>:<port> <ip>:<port> <ip>:<port> <ip>:<port> <ip>:<port> <ip>:<port>,此处的IP为真实的IP,不能是127.0.0.1。其中 --replicas 1 表示为每个主节点配置几个从节点
- 连接集群,连接集群中任何的主机都可以,三个主机相当于一个整体,redis-cli -c -p 6380
当进行写操作时,集群会计算你设置的key(set k1 v1 中的k1)属于哪个插槽,然后重定向到该插槽所在的redis
9.3.1、集群命令
- cluster nodes 查看集群信息
- cluster keyslot <key> 计算key应该放在哪个槽点上,返回的是插槽下标
- cluster countkeysinslot <slot> 返回插槽slot 中包含的键值对数量
- cluster getkeysinslot <slot> <count> 返回count个slot中的键
9.3.2、解决批量插入值报错问题
Redis集群设置的key,如果不在同一个节点上,例如:键k1、k2、k3,经过集群计算,分别属于不同的Master,则不能执行批量的操作,如mset 命令,mget 命令
解决:在键后面加上{} ,可以通过{}来定义组的概念,大括号里是别名,随便起
9.3.3、故障恢复
- 主节点下线,从节点自动变成主节点
- 主节点恢复后变成从节点
- 如果所有某一段插槽的主从节点都停掉,
- 若果关心数据的话,在配置文件中配置,进群停掉
- 如果不关心数据,集群不停掉,继续使用,但某些数据获取不到了。
- redis.conf 配置:cluster-require-full-coverage,当为 yes 时是情况1,当为 no 时是情况2
将某个主节点停掉后集群信息:6379端口的主节点停掉,6389端口的从节点变成主节点
当再次将原主节点启动,原主节点变成从节点
9.3.4、集群优缺点
优点:
- 实现扩容
- 分摊压力
- 无中心配置
缺点:
- 多键操作不支持,实际是支持的不好,需要定义组概念,{}。
- 多键事务不支持,lua脚本不支持
- 集群的迁移麻烦,复杂度大
9.4、插槽
一个Redis集群包含16384个插槽,插槽下标从0开始,数据库中的每个key都属于这些插槽中,集群使用公式CRC16(key)% 16384,来计算key属于哪个插槽。上面图片第一个Master分配了5461个槽点,下标为 0~5460