一:NoSQL数据库简介
1.技术的发展
- 技术的分类:
- 解决功能性的问题:java、jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN
- 解决扩展性的问题:Struts、Spring、SpringMVC、Hibernate、Mybatis
- 解决性能的问题:NoSQL、java线程、Hadop、Nginx、MQ、ElasticSearch
2.NoSQL数据库
- Not Onley SQL 不仅仅是SQL,泛指非关系型的数据库
- NoSQL不依赖业务逻辑方式存储,而以简单的Key-value模式存储。大大的增加了数据库的扩展能力
- MySQL是关系型数据库
- Redis是NoSQL数据库
- 不遵循SQL标准
- 不支持ACID(一致性,原子性…)
- 远程于SQL的性能
二:Redis–安装和概述
1.Redis应用场景
- 配合关系型数据库做高速缓存
- 分布式架构,做session共享
2.多样的数据结构存储持久化数据
- 最新N个数据 ==通过List实现按自然时间排序的数据
- 排行榜 == 利用zset(有序集合)
- 实效性的数据(手机验证码)==Expire过期
- 计数器、秒杀==原子性、自增方法INCR、DECR
- 去除大量数据中的重复数据==利用Set集合
- 构建队列==利用List集合
- 发布订阅消息系统==pub/sub模式
3.Redis安装
- 在Redis找到安装包:redis7.0.9.tar.gz(其他版本也可以)
- 通过Xftp将文件放到Liunx的/opt文件夹中
- 在Xshell中输入命令
- cd /opt == 进入opt目录
- gcc --version == 查看是否安装了C语言编译环境
- yum install gcc ==安装c语言的编译环境
- tar -zxvf redis7.0.9.tar.gz == 解压
- cd /usr/local/bin == 进入默认安装目录
4.查看默认安装目录:
- redis-benchmark:性能测试工具
- redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
- redis-check-dump:修复有问题的dump.rdb文件
- redis-sentinel:Redis集群使用
- redis-server:Redis服务启动命令
- redis-cli:客户端操纵入口
5.前台启动(不推荐)
- 命令行:/usr/local/bin: redis-server
6.后台启动(推荐)
-
将redis-7.0.9目录中的redis.conf复制到/etc目录中
- cp redis.conf /etc/redis.cong
-
后台启动设置 daemonize no 改成 yes
- 修改etc目录中的redis.conf
- vi redis.conf 找到daemonize(#GENERAL #附近) 修改即可
-
输入 redis-server /etc/redis.conf
进入:cd /usr/local/bin
-
完成:
- 查看:ps -ef | grep redis
-
连接/退出客户端:
- redis-cli
-
拓展:在远程服务上执行命令
如果需要在远程 redis 服务上执行命令,同样我们使用的也是 redis-cli 命令。
$ redis-cli -h host -p port -a password $redis-cli -h 127.0.0.1 -p 6379 -a "mypass"
- exit
- 关闭Redis
- 方法一:在客户端输入:shutdow
- 方法二:退出客户端后输入:kill -9 端口号(在上面查看命令的时候可以看到端口号)、
7.Redis介绍相关知识
- 端口号:6379(Merz)
- 默认16个数据库,类似数组下标从0开始,初始默认使用0号库
- 使用:
- select x == 来且换数据库
- dbsize == 查看当前数据库的key数量
- flushdb == 清空当前库
- flushall == 通杀全部库
8.Redis是单线程 + 多路IO复用技术
- 例子:线下火车站买票,
三:常用五大数据类型操作
- 数据类型指的是value的数据类型,key固定是字符串
1.Redis的键(Key)
- **keys *** == 查看当前库所有的key
- exists key == 判断某个key是否存在
- type key == 查看key是上面类型
- del key == 删除z指定key
- 不管三七二十一直接删除
- unlink key == 根据value选择非阻塞删除
- 仅仅将key从keyspace元数据中删除(先告诉你删除了实际还没有),真正的删除会在后续异步操作
- expire key 10 == 设置key过期时间
- ttl key == 查看还有多少秒过期,-1表示永不过期,-2表示已过期
- select x == 来且换数据库
- dbsize == 查看当前数据库的key数量
- flushdb == 清空当前库
- flushall == 通杀全部库
2.Redis字符串(String)
-
简介
- Redis最基本的数据类型
- String是二进制安全的,只要能形成字符串都能存储进去(如:jpg图片或者序列化的对象)
- 一个Redis中字符串value最多可以是512M
-
命令
-
set key value == 添加键值对
-
get key == 取值
-
append key value== 追加(拼接)
-
strlen key == 获得值的长度
-
setnx key value == 只有在key不存在时才能设置key的值(防止key覆盖)
-
incr key == 将key中存储的数组值增 1
-
decr key == 将key中存储的数组值减 1
-
incrby/decrby key 步长 == 增/减自定大小
-
扩展:incr/decr具有原子性(Redis的原子性)
- 所谓原子操作是值不会被线程调度机制打断的操作(即:最小工作单位)
- 这种操作一旦开始,就一直运行到结束,中间不会有任何context switch(切换到另一个线程,这样就不会引起数据混乱,例如出现银行存钱取钱数据错误的问题)
- 在单线程中,能够在单指令中完成的操作都可以认为是“原子操作”,因为中断只能发生于指令之间
- 在多线程中,不能被其它进程(线程)打断的操作就叫原子操作。
- Redis单命令的原子性主要得益于Redis的单线程
-
java中的 i ++是否是原子操作的 ?不是
-
例:i = 0;两个线程分别对i进行++100次,值是多少?
2 <= i <= 200
-
-
mset key1 value1 key2 value2… == 同时设置多个键值对
-
mget key1 key2 key3… == 同时获取一个或多个value
-
msetnx key1 value…==设置不存在的n个键值对
- 原子性,一个不成功全部不成功
-
getrang key start end == 获取值的范围,类似java的substring
-
setrange key start value == 从start开始,覆盖key所存储的字符串
-
setex key 过期时间 value == 设置键值对同时设置过期时间
-
getset key value == 以旧换新
-
-
数据结构
-
String的数据结构为简单动态字符串(SDS).是可以修改的字符串,内部结构实现上类似于java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配
-
如图所示,内部为当前字符串实际分配的空间cappacity一般要高于实际字符串长度len.当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间,最大到512M
-
3.Redis列表(List)
-
简介
- 单键多值
- Redis列表是简单的字符串列表,按照插入顺序排序(有序),可以将元素添加到列表的头部(左),或尾尾部(右)
- 底层实际是个双向链表,对两端的操作性能很高,通过索引下标操作中间的节点性能较差
-
常用命令
-
lpush/rpush key value1 value2…==从左/右边插入一个或多个值
命令:lpush k1 v1 v2 v3 (按顺序一直在左边添加,把之前的挤到右边,rpush和它类似,反之来) 存储顺序:v3 v2 v1
-
lrange key stat end == 显示key的值(0是左边第一个,-1是右边第一个) (没有rrange的形式)
-
lpop/rpop key == 从左/边取出一个值,值全部取出key就死亡
-
rpoplpush k1 k2 == 从k1右边取出一个值,插到k2左边中(只有这个,没有rr,lr,ll)
-
lindex key index ==从左到右索引下标index获取元素
-
llen key == 获取列表长度
-
linsert key before value newvalue == 根据value指定位置在左边插入新值
-
lrem key n value == 从左边删除n个value(从左到)
-
lset key index value == 将列表key下标为index的值替换成value
-
-
数据结构
- List的数据结构为快速链表:quickList
- 在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,即压缩列表
- 当数据量较大时才会改成:quicklist
4.Redis集合(Set)
-
简介
-
Redis set 对外提供的功能与list类似是一个列表的功能,特殊之处在于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 == 随机取出一个值,会从集合中删除
- srandmember key n == 随机从集合中取出n个值,不会从集合中删除
- smove k1 k2 value == 把集合(k1)中一个值从集合移动到另一个集合(k2)
- sinter key1 key2 == 返回两个集合的交集元素
- sunion key1 key2 ==返回两个集合的并集元素
- sdiff key1 key2 == 返回两个集合的差集元素(key1 - key2)
-
数据结构
- Set数据结构是dict字典,字典是用哈希表实现的
5.Redis哈希(Hash)
-
简介
- Redis hash 是一个键值对集合(再一次强调:这些数据类型的value的)
- Redis hash 是一个String类型的field和value的映射表,hash特别适合用于存储对象类型的java里面的Map<String, Object>
- 存储方便,取值改值方便
-
常用命令
- hset key field value == 给key集合中的field赋值(一次一个)
- hget key field == 从key集合field取出value(一次一个)
- hmset key field1 value1 field2 value2… == 批量设置
- hexists key field == 查看key中,field是否存在
- hkeys key == 列出key中所有的field
- hvals key == 列出key中所有的field的值
- hincrby key field increment == 为key中的域field的值加 1
- hsetnx key field value == 为key中的域field赋值(field不存在时)
-
数据结构
- Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表).当field-value长度较短且个数较少时,使用ziplist否则使用hashtable.
6.Redis有序集合(Zset)
-
简介
- Redis有序集合与普通的set非常相似,是一个没有重复元素的字符串集合(set和hash是无序的)
- 不同之处是有序集合的每个成员都关联了一个评分,这个评分被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的
- 因为元素是有序的,所以可以很快的根据评分或者次序来获取一个范围的元素
- 访问有序集合的中间元素也是非常快的
-
常用命令
-
zadd key score1 value1 score2 value == 将一个或多个member元素及其score值加入到有序集key中
-
zrange key start stop [withscores]==返回有序集合key中,下标为start到stop之间的元素
-
zrangebyscore key min max [withscores] [limit offset count] == 返回key中,所有score值介于且包括min和max之间的成员 [返回分数(score)] [从大到小排序(默认是从小到大的)]
-
zincrby key increment value == 为元素的sroce加上增量
-
zrem key value == 删除该集合下,值为value的元素
-
zcount key min max == 统计该集合,分数区间元素的个数
-
zrank key value == 返回该值在集合中的排名,从0开始(小到大)
-
-
数据结构
- SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等阶于java的数据结构Map<String, Double>,可以给每个元素value赋予一个权重score,另外一方面又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每一个元素的名次,还可以通过score的范围来获取元素列表
- 底层使用了两个数据结构
-
hash,hash的作用就是关联元素value和权重,保障value的唯一性,可以通过元素value找到对应的score值
-
跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表
-
四:Redis配置文件详解
-
进入配置文件:vi /etc/redis.conf
-
Units单位
- 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit,大小写不敏感
-
INCLUDES
-
NETWORK网络相关配置
-
bind 127.0.0.1 -::1:只能在本机中(想要远程操作要注销掉)
-
protected -mode yes :保护(想要远程操作需要改成no)
-
Port:端口号,默认6379
-
tcp-backlog 511:backlog其实是一个连接队列,默认值511
backlog队列总和=未完成三次握手队列+已完成三次握手队列
-
tcp-keepalive 300:每隔300秒检测心跳(看是否活着)
-
-
GENERAL通用
- pidfile /var/run/redis_6329.pid:保存进程号的地址
- loglevel notice:表示日志级别
- logfile “” :设置日志输出文件路径(默认为空)
- databases 16:Redis默认16个库
-
SECURITY安全
- requirepass foobared:设置密码,默认关闭
- 或者通过命令设置:config get requirepass “123456”
-
LIMITS限制
- maxclients:设置同时可以与多少个客户端进行连接,默认1000
- maxmemory:建议必须设置,否则,将内存占满,造成服务器宕机,设置redis可以使用的内存量。一旦到达内存使用上限redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定
- maxmemory-policy:
- volatile-lru:使用LRU算法移除key,只对设置了过期时间的键(最近较少使用)
- allkeys-lru:在所有集合key中,使用LRU算法移除key
- volatile-random:在过期集合key中,移除随机的key
- allkeys-random:在所有集合key中,随机移除key
- volatile-ttl:移除那些TTL值最小的key,即即将过期的
- noeviction:不进行移除。针对写操作,只是返回错误信息
五:Redis的发布与订阅
1.什么是发布和订阅
- Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
- Redis客户端可以订阅任意数量的频道
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBsvrrBF-1679287551619)(D:\Typora\Typora图片\Redis\Redis发布和订阅.png)]
2.发布订阅命令行实现
-
打开一个客户端订阅 channel1
- SUBSCRIBE channel1
-
打开另一个客户端,给channel1发布hello
-
publish channel1 hello
-
返回的1是订阅者数量
-
-
打开第一个客户端可以看到发送的消息
六:Redis6新数据类型
1.Bitmaps
-
简介
-
Bitmaps本身不是一种数据类型,实际上它就是字符串,但是它可以对字符串的位进行操作
-
Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同,可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储0和1,数组的下标在Bitmaps中叫做偏移量
-
合理使用操作位能够有效地提高内存使用率和开发效率
-
-
命令
- setbit key offset value == 设置Bitmaps中某个偏移量的值0/1
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VAIsNNkh-1679287551621)(D:\Typora\Typora图片\Redis\Bitmaps-setbit.png)]
- getbit key offset == 查看偏移量为offset的值
- bitcount key == 统计字符串被设置为1的bit数
- bitcount key start stop == 统计start到stop为1的数量(0,1代表了从第1个开始到第16个)
- bitop and/or/not/sor newkey key1 key2
- and:交集 or:并集 not:非 xor:异或
-
Bitmaps和Set对比
-
Bitmaps并不是万金油,假如该网站每天的独立访问用户很少,有大量的“僵尸用户”,活跃用户较少,Bitmap就不合适了
2.HyperLogLog
-
简介
- 经常遇到与统计相关的功能需求,比如统计网站PV(页面访问量),可以使用Redis的incr、incrby(增值)轻松实现
- 但像UV(独立访客),独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题
- 解决方案:Redis提供的hash、set、bitmaps等数据结构来处理
- 以上的方案结果精确,但是随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的
- Redis推出了HyperLogLog–降低一定的精确度来平衡存储空间
- Redis HyperLogLog是用来做基数统计的算法
- 每个HyperLogLog键只需要花费12KB内存,就能计算接近2^64个不同元素的基数
-
命令
-
pfadd key element [element] == 加入数据,成功返回1、反之0
-
**pfcount key ** == 计算key的近似基数
-
pfmerge newkey key1 key2… == 将一个或多个key合并后的结果存储到另一个key中
-
3.Geospatial
-
简介
- Redis3.2中增加了GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作
-
命令
-
geoadd key longitude latitude member [longitude…]
== 添加地理位置(经度,维度,名称)
两极无法直接添加,一般会下载城市数据,直接通过java程序一次性导入
有效的经度(-180180),有效维度(-85.05…85.01…),超出返回一个错误,已经添加的无法再次添加
-
geopos key member [member] == 获得指定地区的坐标
-
geodist key member1 member2 [m|km|ft|mi] == 获取两个位置之间的直线距离 [单位]
-
georadius key longitude latitude radius m|km|ft|mi == 以给定的经纬度为中心找出某一半径内的元素
-
七:Jedis操作Redis6
- Jedis通过java操作Redis,类似用JDBC通过java操作mysql
连接Redis的注意事项
-
引入依赖
<!-- 引入jedis依赖--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> </dependency>
-
在java中连接虚拟机中的Redis
/** * 1.引入jedis依赖 * 2.需要创建一个jedis对象 * host:主机地址,port:端口号 * 3.测试 */ Jedis jedis = new Jedis("192.168.64.134",6379); /** * 主要要修改redis.conf配置文件的相关信息: * - bind 127.0.0.1 -::1:只能在本机中(想要远程操作要注销掉) * - protected -mode yes :保护(想要远程操作需要改成no) * - 关闭防火墙 * -systemctl status firewalld : 查看防火墙是否开启 * -systemctl stop firewalld :临时关闭防火墙 */ String ping = jedis.ping(); System.out.printf("ping-检测是否连接成功", ping);
Jedis常用操作
-
Jedis实例-手机验证码
-
要求:
- 输入手机号,点击发送后随机生成6为数字码,2分钟有效
- Random生成
- 把验证码放入Redis里面,随着过期时间120秒
- 输入验证码,点击验证,返回成功或失败
- 从redis中获取验证码和输入的验证码进行比较
- 每个手机号每天只能输入3次
- incr 每次发送之后 +1
- 大于2时,提交不能发送
- 输入手机号,点击发送后随机生成6为数字码,2分钟有效
-
package com.gdpowernode.jedis; import redis.clients.jedis.Jedis; import java.util.Random; /** * Author 43230 * Date 2023/3/16 * Description: */ public class PhoneCode { public static void main(String[] args) throws InterruptedException { String code = getCode(); String phone = "15915617731"; verifyCode(phone,code); //Thread.sleep(2000); if (getRedisCode(phone,code)) { System.out.println("成功"); }else { System.out.println("失败"); } } /** * 验证码校验 * @param phone * @param code * @return */ public static boolean getRedisCode(String phone,String code){ Jedis jedis = new Jedis("192.168.64.134",6379); String codeKey = "VerifyCode"+phone+"code"; if (code.equals(jedis.get(codeKey))){ //注意jedis.get(codeKey)有可能为空 jedis.close(); return true; }else { jedis.close(); return false; } } /** * 将验证码和手机发送次数存入redis中去 * @param phone * @param code */ public static void verifyCode(String phone,String code){ Jedis jedis = new Jedis("192.168.64.134",6379); // 拼接手机key String countKey = "VerifyCode"+phone+"count"; // 拼接验证码key String codeKey = "VerifyCode"+phone+"code"; // 限制手机发送次数 if(jedis.get(countKey)==null){ jedis.setex(countKey,24*60*60,"1"); jedis.setex(codeKey,1,code); }else if(Integer.parseInt(jedis.get(countKey)) <= 2){ jedis.incr(countKey); jedis.setex(codeKey,1,code); }else { System.out.println("今天发送超过3次,改天再来"); } jedis.close(); } public static String getCode(){ Random random = new Random(); String code = ""; for (int i = 0; i < 6; i++){ int rand = random.nextInt(10); code += rand; } return code; } }
八:Redis与SpringBoot整合
整合步骤
-
在pom.xml文件中引入redis相关依赖
<!-- redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring2.x集成redis所需common-pool2(连接池)--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency>
-
application.properties配置Redis配置
#Redis 服务器地址 spring.redis.host=192.168.64.134 #Redis 服务器连接端口 spring.redis.port=6379 #Redis 数据库索引(默认为0) spring.redis.database=0 #连接超时时间(毫秒) spring.redis.timeout=1800000 #连接池最大连接数(使用负值表示没有限制) spring.redis.lettuce.pool.max-active=20 #z最大阻塞等待时间(负数表示没有限制) spring.redis.lettuce.pool.max-wait=-1 #连接池中最大空闲连接 spring.redis.lettuce.pool.max-idle=5 #连接池中的最小空闲连接 spring.redis.lettuce.pool.min-idle=0
-
创建配置类
九:Redis6的事务操作
1.事务的定义
- Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化,按顺序地执行。事务在执行过程中,不会被其他客户端发送来的命令请求所打断。
- Redis事务的主要作用就是串联多个命令防止别的命令插队
2.Multi、Exec、discard
-
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
-
组队的过程中可以通过discard来放弃组队
-
案例:
3.事务错误处理
- 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消
- 如果执行阶段某个命令报出了错误,则只有报错的命名不会被执行,而其他的命名都会执行,不会回滚
4.事务的冲突问题
-
悲观锁
- 每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人就不能拿着数据去修改。传统的关系型数据库很多用的就是这种锁机制,如:行锁,表锁,读锁,写锁
-
乐观锁
- 每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下(对比版本号)在此期间别人有没有更新这个数据,可以使用版本号等机制。乐视锁适用于多读的应用类型,这样可以提供吞吐量。Redis就是利用这种check-and-set机制实现事务的
5.WATCH key [key…]
-
在执行multi之前,先执行watch key1 [key2]可以监视一个或多个key,如果在事务执行之前这些key被其他命令所改动,那么事务将被打断
-
注意:如果是库存的话,一直减少,到0为止,不加watch高并发的情况下可能会引起库存变成负数(超卖问题)
6.Redis事务的三特性
-
单独的隔离操作
- 事务中的命令都会被序列号,按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求打断
-
没有隔离级别的概念
- 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
-
不保证原子性
- 事务中如果有一条命令执行失败,其他的命令仍然会被执行,没有回滚
7.Redis事务秒杀案例(p24未完成)
8.ab工具模拟并发
- 在liunx安装:yum install httpd-tools
- ab -n requests(100) == 请求次数(100次)
- ab -c concurrency == 并发数
- ab -n 1000 -c 100 == 表示请求次数1000次,同时进行100
- ab -T content-type ==
- ab -p postfile ==
9.超卖问题和连接超时问题
10.乐观锁造成库存遗留问题
-
LUA脚本
- Lua脚本语言,可以很容易的被c/c++代码调用,也可以反过来调用c/c++代码,Lua并没有提供强大的库,一个完整的Lua解释器不过200K,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本.
- 很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性和扩展性。众多游戏插件或外挂
-
LUA脚本在Redis中的优势
- 将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis次数。提升性能
- LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作
- 但是注意redis的lua脚本功能,只有在Redis2.6以上版本才能使用
- 利用lua脚本淘汰用户,解决超卖问题
- redis2.6版本之后,通过lua脚本解决争抢问题,实际上是redis利用其单线程的特性,用任务队列的方式解决多任务并发问题
十:Redis持久化之RDB
1.简介
- 在指定时间间隔内将内存中的数据集快照写入磁盘(硬盘),也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
2.备份如何执行
- Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入一个临时文件中,持久化过程结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO读写的,这确保了极高的性能,如果需要进行大规模数据恢复,且对于数据恢复的完整性不是很敏感,那么RDB方式要比AOF方式更加的高效,RDB的缺点是最后一次持久化后的数据可能丢失
3.Fork
- Fork的作用是复制一个与当前进程一样的进程,并作为原先进程的子进程
dump.rdb文件
配置位置
-
配置文件中默认的快照
- vi /etc/redis.conf == > =SNAPSHOTTING==
-
命令sav和bgsave的区别
-
save:save时只管保存,其他不管,全部阻塞。手动保存(即在配置文件中快照部分配置,保存规则自己设定),不建议使用。
- 例:save 20 3 == 在20秒内只要有3个key发生变化就保存
-
bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求
-
可以通过lastsave命令获取最后一次成功执行快照的时间
-
优势和劣势
-
优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
-
劣势
- Fork的时候,内存中的数据被克隆一份,大致2倍的膨胀性想要考虑
- 虽然Redsi在fork的时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
- 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后所有修改
-
rdb的备份恢复
- 复制rdb文件:cp dump.rdb d.rdb
- 关闭redis:kill -9 port
- 删除dump.rdb:rm -f dump.rdb
- 修改d.rdb文件名:mv d.rdb dump.rdb
- 恢复成功
- 尝试不关闭redis的情况下,是否能成功删除dump.rdb文件
- 不可以,表面删除了,其实还在内存中,还重新生成dump.edb文件
- 尝试虚拟机断开dump.rdb文件是否还存在 存在
- 尝试不关闭redis的情况下,是否能成功删除dump.rdb文件
-
如何停止
- 动态停止RDB:redis-cli config set save “” (暂时停止)
- 静态停止RDB:修改/etc/redis.conf文件的save
十一:Redis持久化之AOF
-
AOF(Append Only File):只追加文件
-
是什么
- 以日志的形式来记录每个写操作(增量保存),将Redis执行的所有写指令记录下来(读操作不记录),只许追加文件但是不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将指令从前到后执行一次以完成数据的恢复
-
AOF持久化流程
-
客户端的请求写命令会被append追加到AOF缓冲区内
-
AOF缓冲区根据AOF持久化策略将操作sync同步到磁盘的AOF文件中;
-
AOF文件大小超过重写策略或手动重写时,会对AOF文件rewite重写、压缩
-
Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的
-
-
AOF默认不开启
- 可以在redis.conf中配置文件名称,默认为appendonly.aof
- AOF文件的保存路径,同RDB的路径一致
-
AOF启动/修复/恢复
-
启动(修改Redis的配置文件要重启Redis)
-
异常恢复(异常的话,尝试进入redis0-cli会报错)
-
修复默认的appendonly no ,改为 yes
-
进入:/ust/local/bin
-
通过 ls 可以查看可执行的命令:存在redis-check-aof
-
如遇AOF文件损坏,通过
redis-check-aof --fix appendonly.aof
-
-
恢复
- 与RDB基本一样
-
-
AOF和RDB同时开启,redis听谁的
- AOF和RDB同时开启,**系统默认取AOF的数据(**数据不会存在丢失),不再取RDB的数据
-
AOF同步频率
-
Rewrite压缩
-
是什么
- AOF采用追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof
-
重写原理
- AOF文件持续增长而过大时,会fork出一条新的进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb快照,以二进制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作
-
-
优势
- 备份机制更稳健,丢失数据概率更低
- 可读的日志文本,通过操作AOF稳健,可以处理误操作
-
劣势:
- 比起RDB占用更多的磁盘空间
- 恢复备份速度更慢
- 每次读写都同步的话,有一定的性能压力
- 存在个别Bug,造成恢复不能
-
总结
- AOF保存的是修改命令,RDB保存的是key数据
- 用哪个好?
- 官方推荐两个一起使用
- 如果对数据不敏感,可以单独用RDB
- 不建议单独使用AOF,因为可能出现Bug
- 如果只是做纯内存缓存,可以都不用
十二:Redis—主从复制
1.简介
-
是什么?(主:主服务器,从:从服务器)
-
主机数据更新后根据配置和策略,自动同步到备机的 master/slaver机制,Master以写为主,Slaver以读为主
-
-
能干嘛?
- 读写分离,性能扩展。在主服务器中写,在从服务器 中读
- 容灾快速恢复。一它从挂掉了,可以快速切换到另外一台从服务器,继续进行读操作。一般都是一主多从
2.搭建一主多从
-
创建 /myredis文件夹:mkdir /myredis
-
复制redis.conf配置文件到文件中:cp /etc/redis.conf /myredis/redis.conf
-
配置一主两从,创建三个配置文件
- redis6379.conf
- redis6380.conf
- redis6381.conf
-
在三个配置文件写入内容(将redis.conf中的AOF关闭,也可以不关,不过要和RDB一样修改文件名)
-
启动三台redis服务
-
查看是否开启
-
查看三台主机运行情况(还没设置主从关系)
-
配从(库)不配主(库)
-
测试(主写从读)
3.常用3招
-
一主二仆
- 从服务器挂掉的情况
- 当从服务器挂掉之后,再重启,那从服务器又变成了主服务器
- 新加入的服务器会获取主服务器的数据
- 主服务器挂掉的情况
- 从服务器仍是从服务器,还是原先主服务器的从服务器
- 主服务器重启还认之前的从服务器
- 从挂主忘,从启失忆。主挂从记,主启从跟。
- 从服务器挂掉的情况
-
薪火相传
- 挂断情况和一主二仆一样
-
反客为主
- 当一个master(主服务器)宕机(挂掉)后,后面的slave(从服务器)可以立刻升为master(要给一个命令),其后面的slave不用做任何的修改(之前主服务器的从服务器,不会变成刚升为主服务器的从服务器)
- slaveof no one :将从机变为主机
4.主从复制原理
5.哨兵模式
-
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
-
先让6379为主,6380和6381是它的从机
-
自定义/myredis 目录下新建sentinel.conf文件(名字不能出错)
-
配置哨兵:sentinel monitor mymaster 127.0.0.1 6379 1
其中:mymaster为监控对象起的服务器名称,1为至少有多少个哨兵同意迁移的数量如果设置了密码还要配置:sentinel auth-path mymaster 密码
-
启动哨兵: redis-sentinel sentinel.conf
-
当主机宕机后,选举出新的主机后,之前主机的从机会变成新的主机的从机
-
复制延迟
- 由于所有的写操作都是先在Master上操作,然后同步更新到slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重
-
6.故障恢复
-
优先级在redis.conf中默认:slave-priority 100,越小优先级越高
-
偏移量是指获得原主机数据最全的
-
每个redis实例启动后都会随机生成一个40位的runid
7.主从复制
- p35集-java中配置的
十三:Redis集群
-
问题
-
容量不够,redis如何进行扩容?
-
并发写操作,redis如何分摊?
-
主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址,端口号等信息
-
之前通过代理主机来解决,但是redis3.0中提供了解决方案,就是无中心化集群配置
-
-
什么是集群
-
Redis集群实现了Redis的水平扩容,有N个节点,每个节点存储总数据的1/N
-
- Rediected to slot lacted at…:重定向到槽位…
-
Redis集群通过分区来提供一定程度的可用性:即使集群中有一部分节点失效或者无法进行通信,集群也可以继续处理命令请求
-
-
模拟
-
删除之前的持久化数据:rm -rf /myredis/dump63*
-
制作6个实例:6379、6380、6381、6389、6390、6391
-
include /myredis/redis.conf pidfile /var/run/redis_6379.pid port 6379 dbfilename dump6379.rdb cluster-enabled yes //打开集群模式 cluster-config-file nodes-6379.conf //设定节点配置文件名 cluster-node-timeout 15000 //设定节点失联时间,超过多少毫秒集群自动进入主从切换
-
-
启动6个redis服务:redis-server redis6379.conf …
-
将6个节点合成一个集群(完成)
-
进入:cd /opt/redis-7.0.9/src
-
命令:
redis-cli --cluster create --cluster-replicas 1 192.168.64.134:6379 192.168.64.134:6380 192.168.64.134:6381 192.168.64.134:6389 192.168.64.134:6390 192.168.64.134:6391 //-replicas 1 表示:采用最简单的方式配置集群,一台主机一台从机,正好三组
-
然后自动选择一种主从方案(哪些做为主机哪些做为从机),再问你同不同意这种方案,就配置完整了(不用自己配置一主n扑了)
-
-
-c 采用集群策略连接,设置数据会自动切换到相应的写主机
redis-cli -c -p 6379
-
十四:Redis应用问题解决
1.缓存穿透
-
问题描述
-
redis一直查询不到数据
-
出现很多非正常url访问(可能遇到黑客了,一直访问缓存中没有的数据,一直访问数据库)
-
解决方案
- 一个一定查询不到缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查询不到的数据则不写入缓存,这导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
- 对空值缓存:(简单应急方案)
- 如果一个查询返回的数据为空(不管数据是否存在)我们仍然将这个空结果进行缓存,设置空结果的过期时间会很短,最长不超过5分钟
- 设置可访问的名单(白名单):(效率低)
- 使用bitmaps类型定义一个可访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,如果访问id不在bitmaps里面,进行拦截不允许访问
- 采用布隆过滤器:(底层是一个bitmap,存在一定误识别率和删除困难)
- 进行实时监控:
- 当发现Redis的命中率开始急速降低,需要排除访问对象和访问数据,和运维人员配合,可以设置黑名单限制服务
2.缓存击穿
-
问题描述
- 有某个被大量访问的key在redis中过期了,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮(redis是正常运行的)
-
解决方案
- 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
- 实时调整:现在监控哪些数据热门,实时调整key的过期时长
- 使用锁:
- 就是在缓存失效的时候(判断拿出来的值为空),不是立即去laod db
- 先使用缓存工具的某些带成功操作返回值的操作(如:setnx)去set 一个muten key
- 当操作返回成功时,再进行load db大操作,并回设缓存,最后删除mutex key
- 当操作返回失败,证明线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法
3.缓存雪崩
-
问题表述
- 和缓存击穿的描述相似,不同的是缓存雪崩有大量的key在同一时间过期了。
-
解决方案
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存(ehcache等)
- 使用锁或队列:用加锁或队列的方式保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适合高并发情况
- 设置过期标志更新缓存:记录缓存数据是否过期,如果过期回触发通知另外的线程在后台去更新实际key的缓存
- 将缓存失效时间分开
4.分布式锁(共享锁)
-
让锁在一整个集群都有效
-
注:学习内容为哔哩哔哩尚硅谷学习课程“Redis6 入门到精通”https://www.bilibili.com/video/BV1Rv41177Af/?p=45&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=349c240fdd351f91c9fed51939746a02