redis
1.1 .1
redis连接遇到的错误问题
一、当运行redis.cli.exe出现Could not connect to Redis at 127.0.0.1:6379: 由于目标计算机积极拒绝,无法连接。
二、如果在文件下双击redis-server.exe出现闪退的情况,可以在电脑命令框输入 redis-server.exe redis.windows.conf,之后不要关闭窗口
三、再双击打开redis.cli.exe文件就可以连接redis数据库
1.1.2
解决redis里面key乱码问题
1.2
Redis特性
reids属于NoSQL中的一种(not only SQL 不仅仅SQL),泛指非关系型的数据库,因此拥有以下特性:
—不遵循SQL标准
—不支持ACID(原子性 一致性 隔离性 持久性,不代表它不支持事务)
—远超SQL性能
1.3
redis常用命令 参考网站(Redis中文网:https://www.redis.net.cn)
2.1
在项目中导入redis
2.1.1
redis在项目里常用的工具
1.jedis
2.Spring Data Redis(目前用的比较多)
2.1.2
在Spring Boot项目中,导入依赖的Maven坐标
注:这边直接导入这个依赖会出现下面这个报错:
For artifact {org.springframework.boot:spring-boot-starter-com.czp.test.test:null:jar}: The version cannot be empty.
需要依赖commons-pool2:
1.apache commons-pool是apache基金会的一个开源对象池组件,我们常用的数据库连接池dpcp和redis的java客户端jedis都使用commons-pool来管理连接
2.common-pool2.jar包主要就是池化技术,如果我们多个连接想要达到数据池的那种效果。它就是最好的选择。
定义了各个方法的接口,来便于操作
要在yml文件里面导入redis的一些相关属性(类似与jdbc),便于链接本地redis数据库
因为这是springboot自动装配的序列化显示格式需要调整,所以在config配置类下创建一个类来调整序列化,改变输出格式
无论类型,可以用定义好的方法统一进行管理
3.1
redis事务的特性
3.1.1
redis事务是一个单独的隔离操作,食物中的所有命令都会被序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令所打断。
redis事务的主要作用就是串联多个命令防止别的命令插队。
3.1.2
一、Multi:开启事务 ;Exec:提交事务;discard:取消事务
二、①当Multi开启事务在组队阶段报错,Exec提交事务时整个队列都会报错
②当Multi组队没有报错而提交事务时报错,则执行阶段谁出现错误,谁在提交事务阶段就会提交失败,其他没 有在执行阶段报错则执行成功
3.1.3
watch监视
在执行Multi之前,先执行watch key1[key2]可以监视一个或者多个key,在执行事务时如果这个key被其他命令改动,那事务将被打断(incrby在这个key的value基础上添加数值),失败结果会返回一个nil值
unwatch取消监视
取消watch命令对所有key的监视,如果在被watch监视之后,事务被Exec或者Discard命令先执行了,就已经取消watch监视了,不再需要unwatch命令取消监视了
3.1.4
Rdis事务三大特性
①单独得隔离操作
事务中得所有命令都会序列化、按顺序地执行。事务在执行得过程中,不会被其他客户端发送来的命令请求所打断
②没有隔离级别的概念
队列的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
③不保证原子性
事务中如果有一条命令执行失败,其他的命令仍然会被执行,没有回滚
注意:redis里默认只能用乐观锁,不能使用悲观锁
3.1.5
像使用redis秒杀活动并发时会出现的问题:
①超买问题:使用乐观锁,先将商品的库存用watch监视,再开启事务进行商品的组队,如果在提交事务的时候判断刚刚队列里执行的事务集合为空(==null)||(size==0),就回滚事务执行失败无库存给用户再次下单
②连接超时问题:创建jedis连接池设置参数,再去创建jedis对象可以解决连接超时问题
③库存遗留问题:lua脚本的作用redis执行期间其他干预不了(有点像悲观锁),因此超卖问题用lua脚本也能实现;
4.1.1
RDB数据持久化
①是指定时间间隔内将内存中的数据集快照写入磁盘,称为Snapshot快照,他恢复时是将快照文件直接读入内存中。在存入磁盘过程中,redis会单独创建fork一个子进程进行持久化,会将数据存入创建出来的临时文件中去,等到持久化过程结束后,用这个临时文件替换掉上次持久化的文件(dump.rdb),可是这里RDB的缺点是最后一次持久化的数据可能会丢失(因为在写入过程中redis会宕机之类的)。
②fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等)数值都和原进程一样,但是是一个全新的进程,并作为原进程的子进程
③临时文件替换上次持久化的文件这个过程也叫写时复制技术
④RDB的备份操作可以copy一份dump.rdb在原本目录下,在dump.rdb失误给删除之后关掉redis,将copy文件名改成dump.rdb再重新启动redis,这时就会直接读出dump.rdb中的数据
⑤RDB优势:
-适合大规模的数据恢复
-对数据完整性和一致性要求不高更合适使用
-节省磁盘空间
-方法速度更快
⑥RDB劣势:
-Fork的时候,内存中的数据被拷贝了一份,大概两倍的膨胀性需要考虑
-虽然Redis在Fork中使用了写时复制技术,可是如果数据庞大还是比较消耗性能的
-在备份周期一次间隔做一次备份,如果redis出现意外down了,就会丢失最后一次快照后的所有修改
4.1.2
AOF数据持久化
①以日志的形式来记录每个操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件.(注意Redis中RDB默认开启,而AOF默认关闭需要到Redis的配置文件中手动打开(将配置文件里的appendonly.aof no改成appendonly.aof yes))
②在Redis配置文件中同时开启RDB和AOF,这时Redis在dump.rdb和appendonly.aof两个文件默认读取AOF的文件里面的数据
③AOF数据的备份和恢复和RDB一样。
④AOF文件异常修复在Linx里可以用文件加指令redis-check-aof —fix appendonly.aof完成修复,因为此时启动redis时会读取aof文件,而里面数据可能出现了无法被读取异常报错(连接redis失败)
⑤AOF同步频率设置:
-appendfsync always 始终同步,每次Redis的写入都会立刻记入日志;性能较差但数据完整性比较好
-appendfsync everysec 每秒同步,每秒记入日志一次,如果宕机,本秒数据可能会丢失
-appendfsync no redis不主动进行同步,把同步时机交给操作系统
⑥AOF的优势:
-备份机制更稳健,丢失数据概率更低
-可读的日志文本,通过操作AOF稳健,可以处理误操作
⑦AOF的劣势
-比起RDB占用更多磁盘空间
-恢复备份速度要慢
-每次读写都同步的话,有一定的性能压力
-存在个别bug,造成恢复不能
4.1.3
总结那个好
①官方推荐两个都启用
②如果对数据不敏感,可以选单独用RDB
③不建议单独用AOF,因为可能会出现Bug
④如果只是做纯内存缓存,可以都不用
5.1.1
主从复制
①主机数据更新后根据配置和策略, 自动同步到备机的 master/slaver 机制,Master 以写为主,Slave 以读为主,主从复制节点间数据是全量的。
作用:
-读写分离,性能扩展
-容灾快速恢复
②复制原理
-Slave 启动成功连接到 master 后会发送一个 sync 命令;
-Master 接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master 将传送整个数据文件到 slave,以完成一次完全同步。
-全量复制:slave 服务器在接收到数据库文件数据后,将其存盘并加载到内存中。
-增量复制:Master 继续将新的所有收集到的修改命令依次传给 slave,完成同步。
-但是只要是重新连接 master,一次完全同步(全量复制) 将被自动执行。
5.1.2
哨兵模式 (sentinel)
反客为主:当一个 master 宕机后,后面的 slave 可以立刻升为 master,其后面的 slave 不用做任何修改。用 slaveof no one 指令将从机变为主机。而哨兵模式是反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
①复制延时
由于所有的写操作都是先在 Master 上操作,然后同步更新到 Slave 上,所以从 Master 同步到 Slave 机器有一定的延迟,当系统很繁忙的时
候,延迟问题会更加严重,Slave 机器数量的增加也会使这个问题更加严重。
②故障恢复
优先级:在 redis.conf 中默认 slave-priority 100,值越小优先级越高。
偏移量:指获得原主机数据最全的概率。
runid:每个 redis 实例启动后都会随机生成一个 40 位的 runid。
5.1.3
Redis 集群(cluster 模式)
①Redis 集群(包括很多小集群)实现了对 Redis 的水平扩容,即启动 N 个 redis 节点,将整个数据库分布存储在这 N 个节点中,每个节点存储总数据的 1/N,即一个小集群存储 1/N 的数据,每个小集群里面维护好自己的 1/N 的数据。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
该模式的 redis 集群特点是:分治、分片。
问题:
-容量不够,redis 如何进行扩容?
-并发写操作, redis 如何分摊?
-另外,主从模式,薪火相传模式,主机宕机,导致 ip 地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。
-之前通过代理主机来解决,但是 redis3.0 中提供了解决方案。就是无中心化集群配置。
②集群连接
普通方式登录:可能直接进入读主机,存储数据时,会出现 MOVED 重定向操作,所以,应该以集群方式登录。
集群登录:redis-cli -c -p 6379 采用集群策略连接,设置数据会自动切换到相应的写主机.(随便连接一个枝点就可以自动连接上集群)
③redis cluster 如何分配这六个节点?
-一个集群至少要有三个主节点。
-选项 –cluster-replicas 1 :表示我们希望为集群中的每个主节点创建一个从节点。
-分配原则尽量保证每个主数据库运行在不同的 IP 地址,每个从库和主库不在一个 IP 地址上。
④什么是 slots
一个 Redis 集群包含 16384 个插槽(hash slot),数据库中的每个键都属于这 16384 个插槽的其中一个。集群使用公式 CRC16 (key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16 (key) 语句用于计算键 key 的 CRC16 校验和
集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
-节点 A 负责处理 0 号至 5460 号插槽。
-节点 B 负责处理 5461 号至 10922 号插槽。
-节点 C 负责处理 10923 号至 16383 号插槽。
⑤在集群中录入值
在 redis-cli 每次录入、查询键值,redis 都会计算出该 key 应该送往的插槽,如果不是该客户端对应服务器的插槽,redis 会报错,并告知应前往的 redis 实例地址和端口。
redis-cli 客户端提供了 –c 参数实现自动重定向。如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。不在一个 slot 下的键值,是不能使用 mget,mset 等多键操作(多键录入时需要像:mset name1{user} czp name2{user} czr分组来计算user的插槽数)。
⑥故障恢复
-如果主节点下线?从节点能否自动升为主节点?注意:15 秒超时
-主节点恢复后,主从关系会如何?主节点回来变成从机。
如果所有某一段插槽的主从节点都宕掉,redis 服务是否还能继续?
-如果某一段插槽的主从都挂掉,而 cluster-require-full-coverage 为 yes ,那么整个集群都挂掉。
-如果某一段插槽的主从都挂掉,而 cluster-require-full-coverage 为 no ,那么,该插槽数据全都不能使用,也无法存储。
⑦Redis 集群优点
-实现扩容
-分摊压力
-无中心配置相对简单
-⑧Redis 集群不足
多键操作是不被支持的。
-多键的 Redis 事务是不被支持的,lua 脚本不被支持。
-由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至 redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。
5.1.4
Redis 应用问题解决
①缓存穿透
问题描述
key 对应的数据在数据源并不存在,每次针对此 key 的请求从缓存获取不到,请求都会压到数据源(数据库),从而可能压垮数据源。比如
用一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
缓存穿透发生的条件:
应用服务器压力变大
redis 命中率降低
一直查询数据库,使得数据库压力太大而压垮
其实 redis 在这个过程中一直平稳运行,崩溃的是我们的数据库(如 MySQL)。
缓存穿透发生的原因:黑客或者其他非正常用户频繁进行很多非正常的 url 访问,使得 redis 查询不到数据库。
解决方案
对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
设置可访问的名单(白名单):使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,不允许访问。
采用布隆过滤器:布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量 (位图) 和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
进行实时监控:当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。
②缓存击穿
问题描述
key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。
缓存击穿的现象:
数据库访问压力瞬时增加,数据库崩溃
redis 里面没有出现大量 key 过期
redis 正常运行
缓存击穿发生的原因:redis 某个 key 过期了,大量访问使用这个 key(热门 key)。
解决方案
key 可能会在某些时间点被超高并发地访问,是一种非常 “热点” 的数据。
预先设置热门数据:在 redis 高峰访问之前,把一些热门数据提前存入到 redis 里面,加大这些热门数据 key 的时长。
实时调整:现场监控哪些数据热门,实时调整 key 的过期时长。
使用锁:
-就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load db。
-先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX)去 set 一个 mutex key。
-当操作返回成功时,再进行 load db 的操作,并回设缓存,最后删除 mutex key;
-当操作返回失败,证明有线程在 load db,当前线程睡眠一段时间再重试整个 get 缓存的方法。
③缓存雪崩
问题描述
key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多 key 缓存,前者则是某一个 key 正常访问。
缓存失效瞬间:
解决方案
构建多级缓存架构:nginx 缓存 + redis 缓存 + 其他缓存(ehcache 等)。
使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,该方法不适用高并发情况。
设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际 key 的缓存。
将缓存失效时间分散开:比如可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
④分布式锁
问题描述
随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程的特点以及分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的 Java API 并不能提供分布式锁的能力。为了解决这个问题就需要一种跨 JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!
分布式锁主流的实现方案:
基于数据库实现分布式锁
基于缓存(Redis 等)
基于 Zookeeper
根据实现方式,分布式锁还可以分为类 CAS 自旋式分布式锁以及 event 事件类型分布式锁:
类 CAS 自旋式分布式锁:询问的方式,类似 java 并发编程中的线程获询问的方式尝试加锁,如 mysql、redis。
另外一类是 event 事件通知进程后续锁的变化,轮询向外的过程,如 zookeeper、etcd。
每一种分布式锁解决方案都有各自的优缺点:
性能:redis 最高
可靠性:zookeeper 最高
解决方案:使用 redis 实现分布式锁
setnx:通过该命令尝试获得锁,没有获得锁的线程会不断等待尝试。
set key ex 3000nx:设置过期时间,自动释放锁,解决当某一个业务异常而导致锁无法释放的问题。但是当业务运行超过过期时间时,开辟监控线程增加该业务的运行时间,直到运行结束,释放锁。
uuid:设置 uuid,释放前获取这个值,判断是否自己的锁,防止误删锁,造成没锁的情况。
RedLock
Redlock 是一种算法,Redlock 也就是 Redis Distributed Lock,可用实现多节点 redis 的分布式锁。RedLock 官方推荐,Redisson 完成了对 Redlock 算法封装。
此种方式具有以下特性:
互斥访问:即永远只有一个 client 能拿到锁。
避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使锁定资源的服务崩溃或者分区,仍然能释放锁。
容错性:只要大部分 Redis 节点存活(一半以上),就可以正常提供服务
RedLock 原理(了解)
获取当前 Unix 时间,以毫秒为单位。
依次尝试从 N 个实例,使用相同的 key 和随机值获取锁。在步骤 2,当向 Redis 设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为 10 秒,则超时时间应该在 5-50 毫秒之间。这样可以避免服务器端 Redis 已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个 Redis 实例。
客户端使用当前时间减去开始获取锁时间(步骤 1 记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是 3 个节点)的 Redis 节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
如果取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间(步骤 3 计算的结果)。
如果因为某些原因,获取锁失败(没有在至少 N/2+1 个 Redis 实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的 Redis 实例上进行解锁(即便某些 Redis 实例根本就没有加锁成功)。
5.1.5
redis IO 多线程
简介
Redis6 终于支撑多线程了,告别单线程了吗?
IO 多线程其实指客户端交互部分的网络 IO 交互处理模块 多线程,而非执行命令多线程。Redis6 执行命令依然是单线程。
原理架构
Redis 6 加入多线程,但跟 Memcached 这种从 IO 处理到数据访问多线程的实现模式有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。整体的设计大体如下:
另外,多线程 IO 默认也是不开启的,需要再配置文件中配置:
io-threads-do-reads yes
io-threads 4