前言:redis现在可是就业面试的重中之重了,不得不深度了解学习一下了。
一、概述
1.什么是Redis?
远程字典服务。由C语言编写,支持网络,可基于内存亦可持久化的日志型。提供多种语言的API。也被人们称之为结构化数据。
2.Redis可用来做什么?
根据官方文档介绍,redis可以用作数据库、缓存、消息队列
1.内存存储、持久化,内存中数据是断电即失。所以说持久化很重要
2.效率高,用于高速缓存,一秒钟可支持10万次读操作,8万次写操作
3.发布订阅信息,微博推送、微信公众号等
4.地图信息分析,使用geo数据类型,支持经纬度存储,可计算位置等
5.计时器,计数器
3.Redis的特性?
性能极高 – Redis 能读的速度是 110000 次/s,写的速度是 81000 次/s
丰富的数据类型 – Redis 支持二进制案例的 Strings, Lists, Hashes,
Sets 及Ordered Sets 数据类型操作。还有一些特殊的数据类型
3.原子 – Redis 的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,通过 MULTI 和 EXEC指令包起来。redis中的事务不保证原子性
4.丰富的特性 – Redis 还支持 publish/subscribe, 通知, key 过期等等特性。
5.持久化存储
6.可以搭建集群
4.Redis基础知识
redis默认有16个数据库,默认使用第0个。(编号0-15)
使用select 编号 命令可以切换数据库
dbsize 命令查看当前数据库存储数据的大小
flushdb 命令清空当前数据库
flushall 命令清空所有的数据库
Redis是单进程单线程的。Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的瓶颈是根据内存和网络带宽决定的。那么既然可以使用单线程实现,那就使用单线程。利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销
Redis是单线程的,速度为什么这么快?
redis将所有的数据全部放在内存中,所以说使用单线程去操作的效率就是最高的。多线程存在CPU上下文切换,对于内存系统来说,没有上下文切换的效率就是最高的。多次读写都是在一个CPU上,在内存中这个就是最佳方案
二、数据类型
1.五大基本数据类型
a.字符串(String)
//常用的APIset key value 设置键和值get key 取值append key value 往key中追加值strlen key 获取当前字符串的长度incr key 当前键对应的值加1decr key 当前键对应的值减1incrby key 10 当前键对应的值加10decrby key 10 当前键对应的值减10getrange key 0 3 截取【0,3】的字符串内容setrange key 1 xx 将字符串中下标为1的内容替换成xxsetex key 5 value (set with expire) #设置过期时间setnx (set if not exist) #如果不存在则设置值mset k1 v1 k2 v2 k3 v3 批量存储mget k1 k2 k3 批量获取msetnx k1 v1 k4 v4 #由于k1存在,所以会导致操作失败。因为msetnx是原子操作#设置对象set user:1 {name:zhangsan,age:3}mset user:1:name zhangsan user:1:age 2getset key value #先get再set
String使用场景:字符串中的value值可以是字符串也可以是数字(Integer)
计数器
对象缓存存储
b.列表(List)
//常用APIlpush key value #将值插入到列表的头部lrange key 0 2 # 获取指定范围的值rpush key value #将值插入列表的尾部lpop key #从左侧弹出值rpop key #从右侧弹出值lindex key 1 #通过下标获得list中的某一个值llen key #返回列表长度lrem key 2 value # 从List移除2个value值ltrim key 1 2 # 通过下标截取指定长度,List被改变,只剩下截取的元素rpoplpush mylist myotherlist #移除列表的最后一个元素,插入到目标列表的第一个位置lset list index value #将list中指定下标位置的值替换linsert mylist after "指定指定字符串" "插入字符串"
List类型一般可当做栈和队列来使用
c.哈希(Hash)
hset myhash field aa #往hash中放入键值对hget myhash field #取hash中的值hmset myhash field1 hello field2 world #批量往hash中存储键值对hmget myhash field1 field2 #批量获取值hgetall myhash #获取hash中所有键值对hdel myhash field1 #删除hash指定的key字段hlen myhash #获取hash的字段数量hexists myhash field1 #判断hash中的字段是否存在hkeys myhash #获取所有的字段hvals myhash #获取所有的 值hincrby myhash field 5 #给hash中的字段增加5hincrby myhash filed 5 #给hash中的字段减去5hsetnx myhash field4 hello #如果字段不存在则设计值#设置对象hset user:1 name xuhi
hash更适合对象的存储,string适合存储字符串
d.集合(Set)
//常用APIsadd key value #往集合中放数据smembers key #查看指定set中的元素sismember key value #查看当前的value是否在set存在scard key #获取set集合中元素个数srem key value #移除set中的某一个元素srandmember key #随机抽出一个元素spop key #随机删除集合中的一些元素smove "指定集合" “目标集合” value #将一个指定的值移动到另一个set集合sdiff key1 key2 #求两个集合的差集sinter key1 key2 #求两个集合的交集sunion key1 key2 #求两个集合的并集
集合中的元素是不可重复的,并且还支持求交集、并集、差集等操作
e.有序集合(SortedSet)
//常用APIzadd k1 score v1 #往set中添加值,set根据score来排序zadd myset 2 two 3 three #增加多个值zrangebyscore salary -inf +inf #排序zrangebyscore salary -inf +inf withscores #显示score排序zrevrangebyscore salary -inf +inf #倒叙zrange salary 0 -1 #展示set中的值zrevrange salary 0 -1 #倒叙展示zrem salary xiaohong #移除set中的指定值zcard salary #获取set中元素个数zcount salary 1 3 获取指定区间的元素个数
使用场景:一些排序或者排行榜
2.三大特殊数据类型
a.geospatial地理位置
规则:两极无法直接添加,一般会下载城市数据,通过java程序一次性导入geoadd china:city 121.47 31.23 shanghai #添加城市的经度、纬度geopos china:city beijin #获取指定城市的经度和纬度geodist china:city beijin shanghai km #获取城市距离#单位 m(米) km (千米) mi (英里) ft(英尺)georadius china:city 110.00 30.00 1000km #查找当前经纬度下,半径1000km的城市georadius china:city 110.00 30.00 1000km withdist #查找当前经纬度下,半径1000km的城市和直线距离georadius china:city 110.00 30.00 1000km withcoord #查找当前经纬度下,半径1000km的城市经纬度georadiusbymember china:city beijin 1000km #找出指定城市1000km附近的城市#将二维的经纬度转换成一维的字符串#返回一个或多个位置的geohashgeohash china:city beijin chongqinzrange china:city 0 -1 #查看当前键下的城市zrem china:city beijin #删除指定城市
使用场景:地图
b.Hyperloglog
pfadd mykey a b c #创建一组元素pfcount mykey #统计mykey元素的基数数量pf merge mykey3 mykey1 mykey2 # 合并mykey1和mykey2到mykey3
优点:占用内存固定,2^64个不同的元素只需要12kb
场景:统计一个网站的访问人数
传统方式,使用set来存储用户id,然后统计set的长度。这种方式如果保存了大量的用户id就会比较麻烦
我们的目的是计数而不是保存用户id
c.Bitmap
位存储
setbit sign 1 0 #设置值getbit sign 1 #取值bitcount sign #统计这周的打卡记录
使用场景:统计打卡,考勤
三、事务
1.Redsi事务的本质:一组命令的集合,一个事务中所有的命令都会被序列化,在事务执行的过程中,会按照顺序执行
2.Redis中的事务具有一致性、顺序性、排他性
3.redis单条命令保持原子性,但是事务不保证原子性
4.redis中没有隔离级别的概念
5.开启事务后,所有的命令在事务中并没有被执行,只有在发起执行命令的时候才会执行
redis事务步骤:
开启事务(multi)
命令入队
执行事务(exec)
取消事务(discard)
注意:事务执行的过程中可能存在异常
编译时异常(命令有错),事务中所有的命令都不会被执行
运行时异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行
监控
redis可以实现乐观锁机制
悲观锁:类似于人的悲观性格,认为什么时候都会出现问题,无论做什么都会加锁
乐观锁:
认为什么时候都不会出现问题,所有不会加锁,更新数据的时候判断一下,在此期间是否有人修改过这个数据
获取version
更新时比较version
如何实现乐观锁呢?
使用watch命令监控就可以了
watch money 实现监控unwatch #放弃监视#如果事务执行失败,就先解锁# 获取最新的值,再次监视#如果没有发生变化,执行成功。否则,执行失败
四、Redis持久化
redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一但服务器进程退出,服务器中数据库的状态也会消失,所以redis提供了持久化功能。这里redis提供了两种持久化机制
1.RDB
redis默认使用的是rdb持久化机制,保存在dump.rdb文件中
触发机制
1.save的规则满足的情况下,会自动触发rdb规则
2.执行flushall命令,也会触发我们的rdb规则
3.退出redis,也会产生rdb文件
如何恢复rdb中的数据文件
1.只需要将rdb文件放在redis的启动目录即可(即配置文件中配置的路径),redis启动的时候会自动检查dump.rdb恢复其中的数据
2.查看文件需要存在的位置,使用命令config get dir
优点:
1.适合大规模的数据恢复
2.对数据完整性要求不高
缺点:
1.需要一定的时间间隔进行操作,如果redis意外宕机了,那么最后一次修改的数据就没有了
2.fork进程的时候会占用一定的内存空间
2.AOF
redis中默认没有开启aof机制,需要手动配置开启,然后重启redis生效。它会将所有的执行过的命令记录下来(只记录写操作,不记录读操作),保存在appendonly.aof文件中,如果aof文件中有错位,那么这时候redis是启动不起来的,我们需要修复这个aof文件。redis提供了一个工具 redis-check-aof --fix
优点:
1.每一次修改都同步,文件的完整性会更好
2.每秒同步一次,可能丢失你一秒的数据
3.从不同步,效率最高的
缺点:
1.相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
2.aof的运行效率也比rdb慢
如果aof文件大于64M,会触发重写
五、Redis发布订阅
Redis发布订阅(oub/sub)是一种消息通信的模式,发送者(pub)发送消息,订阅者(sub)接收消息Redis客户端可以订阅任意数量的频道

subscrible mychanel #订阅一个频道publish mychanel message #在频道上发布一个消息
使用场景:
实时消息系统
群聊天室
复杂的场景用消息中间件来做
六、Redis主从复制
主从复制,是指一台redis服务器的数据,复制到其他redis服务器,前者称为主节点,后者称为从节点。数据的复制是单向的,只能由主节点到从节点。主节点以写为主,从节点以读为主
默认情况下,每台redis服务器都是主节点,且一个主节点可以有多个从节点,或者没有节点,但一个从节点只能有一个主节点
主从复制作用:
1.数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式
2.故障恢复:当主节点出现问题时,可以有从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余
3.负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高redis服务器的并发量
4.高可用基石:主从复制还是哨兵模式和集群能够实施的基础,因此说是redsi高可用的基石
主从复制,读写分离。80%都在进行读操作,减轻服务器的压力
默认情况下,每台redis服务器都是主节点
slaveof 127.0.0.1 6379 #认老大,本机成为从机
配置好主从关系后
1.主机断开连接,从机依旧连接到主机,但是没有写操作,这个时候,如果主机回来了,从机依旧可以直接获取到主机写入的数据
2.如果使用命令行配置的主从关系。这个时候如果从机重启了,主从关系就失效了。重写配置后立马就会从主机中取值
七、哨兵模式
哨兵模式是一种特殊的模式,首先redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例


假设服务器宕机了,哨兵1首先检测到这个结果,系统并不会马上进行failover(故障转移)过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称之为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票是由一个哨兵发起的,进行failover(故障转移)操作,切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器切换成主机,这个过程称之为客观下线。
如果master主机断开了,就会投票选择一个从机作为主机。如果主机回来了,这个时候它只能当做从机归并到新的主机下。这就是哨兵模式的规则
优点:
1.哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
2.主从可以切换,故障可以转移,系统的可用性会更好
3.哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
redis不好在线扩容,集群容量一但达到上限,在线扩容十分麻烦
实现哨兵模式的配置是十分麻烦的
八、缓存相关问题
1.缓存穿透(查不到数据)

缓存穿透是指,用户想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层数据库,这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透
解决方式:
1.布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力

2.缓存空对象
当存储层不命中后,即使返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据会从缓存中获取,保护了后端的数据源
但这种方式会存在两个问题
如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多空值的键
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间的不一致,这对于需要保持一致性的业务会有影响
2.缓存击穿(热点数据过期)
缓存击穿,是指一个key非常的热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求在数据库上,就像在屏障上面开了一个洞
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新的数据,并且写会缓存,会导致数据库瞬间压力过大
解决方式:
1.设置热点数据永不过期
从缓存层面来看,没有设置过期时间,就不会产生这个问题
2.加互斥锁
分布式锁,使用分布式锁,保证对于每个Key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此需要等待。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大

3.缓存雪崩
缓存雪崩,是指在某一个时间段,缓存集中过期失效,redis宕机产生一系列的连锁反应,造成整个系统崩溃
举例说明:
双十一零点时,会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一小时,那么到了凌晨一点,这波商品的缓存就过期了,而对这波商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰,所有的请求都会达到存储层,存储层的调用量就会暴增,会造成存储层垮掉的情况
4.缓存预热
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
解决思路:
1、直接写个缓存刷新页面,上线时手工操作下;
2、数据量不大,可以在项目启动的时候自动进行加载;
3、定时刷新缓存
5.缓存更新
除了缓存服务器自带的缓存失效策略之外(Redis默认的有6种策略可供选择),我们还可以根据具体的业务需求进行自定义的缓存淘汰,常见的策略有两种:
(1)定时去清理过期的缓存;
(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较
复杂!具体用哪种方案,大家可以根据自己的应用场景来权衡
6.缓存降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。
降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
以参考日志级别设置预案:
(1)一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;
(2)警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;
(3)错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;
(4)严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。服务降级的目的,是为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略,例如一个比较常见的做法就是,Redis出现问题,不去数据库查询,而是直接返回默认值给用户
举例:双十一你购买东西后,可能完成不了退货的操作,需等待到第二天,是因为他将退货的服务停掉了或者降级了。将服务的中心转移的秒杀商品上了。




