Redis
定义
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
Redis(Remote Dictionary Server ),即远程字典服务 !是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
也被人们称之为结构化数据库!
五大数据类型
String(字符串)
String是Redis中最常用的一种数据类型,也是Redis中最简单的一种数据类型。首先,表面上它是字符串,但其实他可以灵活的表示字符串、整数、浮点数3种值。Redis会自动的识别这3种值。
操作
- 添加、查询、追加、获取长度,判断是否存在的操作
- 自增、自减操作
- 截取、替换字符串操作
- 设置过期时间、不存在设置操作
- mset、mget操作
- 添加获取对象、getset操作
List(列表)
定义
实际上是一个链表,before Node after , left,right 都可以插入值
如果key 不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在!
在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~
消息排队!消息队列 (Lpush Rpop), 栈( Lpush Lpop)!
操作
- lpush(左插入)、lrange(查询集合)、rpush(右插入)操作
- lpop(左移除)、rpop(右移除)操作
- lindex(查询指定下标元素)、llen(获取集合长度) 操作
- lrem(根据value移除指定的值)
- ltrim(截取元素)、rpoplpush(移除指定集合中最后一个元素到一个新的集合中)操作
- lset(更新)、linsert操作
Set(集合)
操作
- sadd(添加)、smembers(查看所有元素)、sismember(判断是否存在)、scard(查看长度)、srem(移除指定元素)操作
- srandmember(抽随机)操作
- spop(随机删除元素)、smove(移动指定元素到新的集合中)操作
- sdiff(差集)、sinter(交集)、sunion(并集)操作
总结
可实现共同好友、共同关注等需求。
Hash(哈希)
操作
-
hset(添加hash)、hget(查询)、hgetall(查询所有)、hdel(删除hash中指定的值)、hlen(获取hash的长度)、hexists(判断key是否存在)操作
-
hkeys(获取所有key)、hvals(获取所有value)、hincrby(给值加增量)、hsetnx(存在不添加)操作
总结
比String更加适合存对象~
zSet(有序集合)
操作
- zadd(添加)、zrange(查询)、zrangebyscore(排序小-大)、zrevrange(排序大-小)、zrangebyscore withscores(查询所有值包含key)操作
- zrem(移除元素)、zcard(查看元素个数)、zcount(查询指定区间内的元素个数)操作
总结
成绩表排序,工资表排序,年龄排序等需求可以用zset来实现!
三大特殊类型
Geospatial(地理位置)
操作
- geoadd(添加)、geopos(查看)、geodist(计算距离)操作
- georadius(查询附近位置)操作
- georadiusbymember (查找指定元素指定范围内的元素)、geohash (返回经纬度的hash值)、zrange、zrem(使用zset命令操作geo)
注意
- 两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
- m 为米。km 为千米。mi 为英里。ft 为英尺。
总结
实际需求中,我们可以用来查询附近的人、计算两人之间的距离等。当然,那些所需的经纬度我们肯定要结合java代码来一次导入,手动查询和录入太过于浪费时间!
Hyperloglog(基数)
定义
再数学层面上可以说是:两个数据集中不重复的元素~
操作
- pfadd(添加数据集)、pfcount(统计数据集)、pfmegre(合并数据集-自动去重)
总结
如果在实际业务中,允许一定的误差值,我们可以使用基数统计来计算~效率非常高!比如:网站的访问量,就可以利用Hyperloglog来进行计算统计!
Bitmap(位存储)
定义
Bitmap 位图,数据结构! 都是操作二进制位来进行记录,就只有0 和 1 两个状态!
操作
- setbit(添加)、getset(获取)、bitcount(统计)操作
总结
实际需求中,可能需要我们统计用户的登陆信息,员工的打卡信息等等。只要是事务的只有两个状态的,我们都可以用Bitmap来进行操作!!!
实现事务
- 正常执行事务
- 放弃事务
- 编译时异常,代码有问题,或者命令有问题,所有的命令都不会被执行
- 运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行
注意
在Redis事务没有没有隔离级别的概念!
在Redis单条命令式保证原子性的,但是事务不保证原子性!
总结
总结:由以上可以得出结论,Redis是支持单条命令事务的,但是事务并不能保证原子性!
实现乐观锁
操作
- watch(监视)
- 多线程测试watch
注意
- 当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
- 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
总结
- 当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
- 没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
jedis
定义
Jedis是Redis官方推荐的Java连接开发工具!
总结
在Jedis中连接使用Redis,和Redis控制台命令完全一致
Springboot整合Redis
在1.×版本的时候,SpringBoot的底层还是使用Jedis来连接Redis的,但是在2.×版本后,就换成了Lettuce。
两者的区别
- Jedis: 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式!
- Lettuce: 采用netty,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式!
Redis配置文件
- 包含
- 搭建Redis集群时,可以使用includes包含其他配置文件
- 网络
- bind 127.0.0.1 # 绑定的ip protected-mode yes # 保护模式 port 6379 # 端口设置
-
通用GENERAL
-
快照(RDB)
- 持久化,在规定的时间内,执行了多少次操作则会持久化到文件 .rdb .aof文件
- Redis是内存数据库,如果没有持久化,那么数据断电即失!
- SECURITY 安全
- 可以在这里设置Redis的密码,默认是没有密码的。
- 限制CLIENTS
- 限制能连接上Redis数据库的客户端
- APPEND ONLY 模式 aof配置(持久化保存)
持久化
定义
在发生意外时可以及时保存数据,以便于在下次启动中恢复
原因
Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能 !
种类
RDB
定义
RDB(Redis Database)是Redis默认采用的持久化方式,它以快照的形式将进程数据持久化到硬盘中。
原理
- RDB会创建一个经过压缩的二进制文件,文件以“.rdb”结尾,内部存储了各个数据库的键值对数据等信息。
- Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。
优点
RDB生成紧凑压缩的二进制文件,体积小,使用该文件恢复数据的速度非常快;
缺点
BGSAVE每次运行都要执行fork操作创建子进程,属于重量级操作,不宜频繁执行,所以RDB持久化没办法做到实时的持久化。
触发方式
-
手动触发
通过SAVE或BGSAVE命令触发RDB持久化操作,创建“.rdb”文件
-
自动触发
通过配置选项,让服务器在满足指定条件时自动执行BGSAVE命令
命令
-
SAVE
SAVE命令执行期间,Redis服务器将阻塞,直到“.rdb”文件创建完毕为止。
-
BGSAVE
而BGSAVE命令是异步版本的SAVE命令,它会使用Redis服务器进程的子进程,创建“.rdb”文件。
AOF
定义
AOF(Append Only File),解决了数据持久化的实时性,是目前Redis持久化的主流方式。
原理
AOF以独立日志的方式,记录了每次写入命令,重启时再重新执行AOF文件中的命令来恢复数据。(AOF以文本协议格式写入命令)
优点
与RDB持久化可能丢失大量的数据相比,AOF持久化的安全性要高很多。通过使用everysec选项,用户可以将数据丢失的时间窗口限制在1秒之内。(文件同步机制改进)
缺点
AOF文件存储的是协议文本,它的体积要比二进制格式的”.rdb”文件大很多。AOF需要通过执行AOF文件中的命令来恢复数据库,其恢复速度比RDB慢很多。AOF在进行重写时也需要创建子进程,在数据库体积较大时将占用大量资源,会导致服务器的短暂阻塞。
工作流程
文本协议格式
*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
优点:
- 文本协议具有很好的兼容性;
- 直接采用文本协议格式,可以避免二次处理的开销;
- 文本协议具有可读性,方便直接修改和处理。
文件同步机制
- 特点
为了提高程序的写入性能,现代操作系统会把针对硬盘的多次写操作优化为一次写操作。
-
步骤
- 当程序调用write对文件写入时,系统不会直接把书记写入硬盘,而是先将数据写入内存的缓冲区中;
- 当达到特定的时间周期或缓冲区写满时,系统才会执行flush操作,将缓冲区中的数据冲洗至硬盘中;
-
改进
这种优化机制虽然提高了性能,但也给程序的写入操作带来了不确定性,为了消除上述机制的不确定性,Redis向用户提供了appendfsync选项,来控制系统冲洗AOF的频率
appendfsync选项的取值
RDB-AOF混合
定义
Redis从4.0开始引入RDB-AOF混合持久化模式,这种模式是基于AOF持久化构建而来的。用户可以通过配置文件中的“aof-use-rdb-preamble yes”配置项开启AOF混合持久化。
原理
通过使用RDB-AOF混合持久化,用户可以同时获得RDB持久化和AOF持久化的优点,服务器既可以通过AOF文件包含的RDB数据来实现快速的数据恢复操作,又可以通过AOF文件包含的AOF数据来将丢失数据的时间窗口限制在1s之内。
步骤
- 像执行BGSAVE命令一样,根据数据库当前的状态生成相应的RDB数据,并将其写入AOF文件中;
- 对于重写之后执行的Redis命令,则以协议文本的方式追加到AOF文件的末尾,即RDB数据之后。
Redis发布订阅
定义
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。(类似于消息队列)
图示
总结
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
主从复制
定义
是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave 以读为主。
作用
-
数据冗余
主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
-
故障恢复
当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
-
负载均衡
在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
-
高可用(集群)基石
除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
实现
-
Redis使用psync命令完成主从数据同步,同步过程分为全量复制和部分复制。全量复制一般用于初次复制的场景,部分复制则用于处理因网络中断等原因造成数据丢失的场景。
-
psync命令参数
- 复制偏移量
- 积压缓冲区
- 主节点运行ID
-
返回结果
哨兵模式
定义
Redis Sentinel(哨兵)是一个分布式架构,它包含若干个哨兵节点和数据节点。每个哨兵节点会对数据节点和其余的哨兵节点进行监控,当发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它就会与其他的哨兵节点进行协商,当多数哨兵节点都认为主节点不可达时,它们便会选举出一个哨兵节点来完成自动故障转移的工作,同时还会将这个变化实时地通知给应用方。整个过程是自动的,不需要人工介入,有效地解决了Redis的高可用问题!
原理
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的 进程 ,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
优点
- 哨兵集群,基于主从复制模式 ,所有的主从配置优点,它全有
- 主从可以切换,故障可以转移 ,系统的 可用性 就会更好
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮!
缺点
- Redis 不好在线扩容 的,集群容量一旦到达上限,在线扩容就十分麻烦!
- ②实现哨兵模式的配置其实是很 麻烦 的,里面有很多选择!
图示
特征
- 哨兵节点会定期监控数据节点,其他哨兵节点是否可达;
- 哨兵节点会将故障转移的结果通知给应用方;
- 哨兵节点可以将从节点晋升为主节点,并维护后续正确的主从关系;
- 哨兵模式下,客户端连接的是哨兵节点集合,从中获取主节点信息;
- 节点的故障判断是由多个哨兵节点共同完成的,可有效地防止误判;
- 哨兵节点集合是由多个哨兵节点组成的,即使个别哨兵节点不可用,整个集合依然是健壮的;
- 哨兵节点也是独立的Redis节点,是特殊的Redis节点,它们不存储数据,只支持部分命令。
缓存问题
缓存穿透
定义
用户需要查询一个数据,但是redis中没有(比如说mysql中id=-1的数),直接去请求MySQL,当很多用户同时请求并且都么有命中!于是都去请求了持久层的数据库,那么这样会给持久层数据库带来非常大的压力。一般出现这样的情况都不是正常用户,基本上都是恶意用户!
解决方案
-
布隆过滤器
- 布隆过滤器可以用很低的代价,估算出数据是否真实存在。例如:给用户推荐新闻时,要去掉重复的新闻,就可以利用布隆过滤器,判断该新闻是否已经推荐过。
- 核心
- 一个大型的位数组
- 若干个不一样的哈希函数,每个哈希函数都能将哈希值算的比较均匀
- 工作原理
- 添加key时,每个哈希函数都利用这个key计算出一个哈希值,再根据哈希值计算一个位置,并将位数组中这个位置的值设置为1。
- 询问key时,每个哈希函数都利用这个key计算出一个哈希值,再根据哈希值计算一个位置。然后对比这些哈希函数在位数组中对应位置的数值:
- 如果这几个位置中,有一个位置的值是0,就说明这个布隆过滤器中,不存在这个key。
- 如果这几个位置中,所有位置的值都是1,就说明这个布隆过滤器中,极有可能存在这个key。之所以不是百分之百确定,是因为也可能是其他的key运算导致该位置为1。
-
缓存空对象
-
原理
- 当存储层查不到,即使是空值,我们也将其存储起来并且在Redis中设置一个过期时间,之后再访问这个数据将会从Redis中访问,保护了持久层的数据库!
-
问题
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
-
图示
-
缓存击穿
定义
是指一个非常热点的key,在不停的扛着大并发,当这个key失效时,一瞬间大量的请求冲到持久层的数据库中,就像在一堵墙上某个点凿开了一个洞!
解决方案
- 设置热点key永不过期
- 从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
- 其实设置永不过期不合理!
- 加互斥锁
- 在查询持久层数据库时,保证了只有一个线程能够进行持久层数据查询,其他的线程让它睡眠几百毫秒,等待第一个线程查询完会回写到Redis缓存当中,剩下的线程可以正常查询Redis缓存,就不存在大量请求去冲击持久层数据库了!
热点Key问题
定义
热key问题就是某个瞬间有大量的请求去访问Redis上某个固定的key,导致缓存击穿,请求都打到了DB上,压垮了缓存服务和DB服务,从而影响到应用服务可用的可用性;
热点key判断
- 通常以Key被请求频率来判定,目前没有很具体的数值来定义热key
- QPS集中在特定的Key:Redis实例的总QPS(每秒查询率)为2W,而其中一个Key的每秒访问量达到了1W以上
- 带宽使用率集中在特定的Key:对一个拥有上千个成员且总大小为1MB以上的HASH Key,每秒发送大量的HGETALL操作请求
- CPU使用时间占比集中在特定的Key:对一个拥有数万个成员的Key(ZSET类型)每秒发送大量的ZRANGE操作请求
危害
- 流量集中,达到物理网卡上限
- 当某一热点Key的请求在某一节点所在的主机上超过该主机网卡流量上限时,由于流量的过度集中,会导致该节点的服务器中其它服务无法进行;
- 请求过多,缓存分片服务被打垮
- Redis单点查询性能是有限的,当热点key的查询超过Redis节点的性能阈值时,请求会占用大量的CPU资源,影响其他请求并导致整体性能降低;严重时会导致缓存分片服务被打垮,表现形式之一就是Redis节点自重启,此时该节点存储的所有key的查询都是不可用状态,会把影响辐射到其他业务上;
- 集群架构下,产生访问倾斜
- 即某个数据分片被大量访问,而其他数据分片处于空闲状态,可能引起该数据分片的连接数被耗尽,新的连接建立请求被拒绝等问题;
- DB 击穿,引起业务雪崩
发现热点key
-
凭借业务经验,进行预估哪些是热key
比如某一个整点秒杀活动,活动信息的key、存放头部楼层的秒杀商品的信息的key一般就是热点key;但是并不是每个热key都能被准确的预测,如对于电商平台来说,商家什么时候会上架相对火爆的秒杀活动就很难预测了,但是可以借助对不同商家的历史活动的数据分析来做一定的参考;
-
业务侧自行监控和收集
这个方式就是在操作redis之前,加入一行代码进行数据统计,异步上报行为;如类似日志采集,将单次redis命令的操作/结果/耗时等统计,异步消息发送给采集消息队列,
一般可以交给中间件加在自己包的redis二方包中;如果有做的好一点的Daas平台,可以在proxy层做监控,业务无需感知,统一在Daas平台查看redis监控;
-
用redis自带命令
- monitor命令:该命令可以实时抓取出redis服务器接收到的命令,然后写代码统计出热key是啥;当然,也有现成的分析工具可以给你使用,比如redis-faina;但是该命令在高并发的条件下,有内存增暴增的隐患,还会降低redis的性能。
- hotkeys参数:redis 4.0.3提供了redis-cli的热点key发现功能,执行redis-cli时加上–hotkeys选项即可;但是该参数在执行的时候,如果key比较多,执行起来比较慢;参考:Redis 4.0热点Key查询方法;
- 但是一般公司是不允许直接连接redis节点自己输入命令的,而是直接通过Daas平台查看热点key的分析和监控;
解决方案
- 使用二级缓存
- 使用本地缓存,如利用ehcache、GuavaCache等,甚至是一个HashMap都可以;在发现热key以后,把热key加载到系统的JVM中,针对这种热key请求,会直接从本地缓存中取,而不会直接请求redis;
- 本地缓存天然的将同一个key的大量请求,根据网络层的负载均衡,均匀分散到了不同的机器节点上,避免了对于固定key全部打到单个redis节点的情况,并且减少了1次网络交互;
- 使用本地缓存不可避免的遇到的问题就是,对于要求缓存强一致性的业务来说,需要花费更多的精力在保证分布式缓存一致性上,会增加系统的复杂度;
- 将热key分散到不同的服务器中
- 不要让固定key老是走到同一台redis节点上;我们把这个key,在多个redis节点上都备份一份即可,在有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据,就能缓解redis单点热key的查询压力;
- 因为redis是根据key分配哈希槽,因此在初始化时,可以将key拼接上随机尾缀,如下图的0-2N,生成的多个备份key散落在各个redis节点上,查询的时候也是随机拼接成这多个备份key中的一个,进行查询,从而让读写都不再集中于单个redis节点;
- 热key拆分
- 因此解决热key的思路之一就是能否想办法把这个key给细化拆分,让不同用户请求的key是不一样的
- 如秒杀活动场景,不同用户根据人群规则命中的活动策略ID可能是不同的,因此我们可以将整个活动元信息拆分成以策略为维度,把活动信息的key细化;
- 这样请求过来时,根据用户人群策略,只会去找该策略绑定的活动信息的key,
- 将核心/非核心业务做Redis的隔离
- 当热点key的查询超过Redis节点的性能阈值时,会导致缓存分片服务被打垮现象的产生,此时当前节点上的所有业务的redis的读写都是不可用的;
- 为了防止热点key引发问题时,核心业务不受影响,应当提前做好核心/非核心业务的Redis的隔离,至少热点key存在的redis集群应当与核心业务隔离开来;
缓存雪崩
定义
在某一个时间段,缓存的key大量集中同时过期了,所有的请求全部冲到持久层数据库上,导致持久层数据库挂掉!
举例
双十一零点抢购,这波商品比较集中的放在缓存,设置了失效时间为1个小时,那么到了零点,这批缓存全部失效了,而大量的请求过来时,全部冲过了缓存,冲到了持久层数据库!
解决方案
- Redis高可用
- 搭建Redis集群,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
- 限流降级
- 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 数据预热
- 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀