常见的redis数据结构以及对应使用场景
字符串string:
没有使用c语言的字符串,而是自己实现的简单动态字符串sds的抽象类型。记录了字符串长度。
存储的是整形,则使用int方式进行存储。若存储的是字符串值,则使用sds简单动态字符串。
sds包含内容:
1.len:存储字符串长度
2.buf数组:存储字符串的每一个元素
3.free:表示buf数组中未使用的字节数量
sds和c语言字符串比较:
1.sds获取字符串长度直接读取len字段,c语言需要遍历一遍
2.sds除了可以存储字符串,还可以存储二进制文件(图片,视频,音频等文件的二进制数据)。c语言只能存储字符串。
3.sds提供空间预分配和惰性空间释放,减少连续的执行字符串增长带来的内存重新分配的次数。
使用场景:常用key-value格式数据存储,常规计数。
哈希hash:
使用hashtable和ziplist压缩表(节约内存)实现
使用场景:适合存储对象数据
存储用户信息:用户ID作为唯一key,value中存储多个key-value对,(name:张三,age:30)。
存储用户购物车数据。
相对于字符串存储的话,字符串需要将用户数据组装,然后序列化后再作为value存储。修改时又需要取出做反序列化再修改。而使用哈希,则可以直接修改单个数据。减少性能的开销。
列表list:
使用ziplist和linkedlist链表实现。3.2版本后引入了quicklist。底层都是使用无环的双向链表实现,有自己的长度信息,可以直接获取。
使用场景:简单的消息队列,高性能分页(使用lpush,rpop)。
集合set:
底层实现:intset整数集合和hashtable
字符串类型的无序集合,元素都是唯一的,不会重复。使用哈希表实现,所以复杂度都o(1)
使用场景:去重,抽奖(spop:随机移除并返回一个集合中的元素),共同好友(返回多集合之间的交集和差集),好友列表,关注列表
有序集合sorted set: 基于集合,但是每个元素多一个score字段,用来对元素进行排序
底层实现:使用ziplist和skiplist跳跃表
使用场景:主要用于排序的场景。
例如:收听最多的10首音乐,粉丝打赏排行榜,阅读量从高到低的文章排序等
Geo:地理位置计算相关 场景:附近的人,附近的直播等
hyperloglog:技术统计
pub/sub发布订阅
Stream流:用于消息队列,提供消息的持久化和主备复制功能
bloom fliter布隆过滤器: 处理大数据量中查询,节省空间(使用位数组,位数组每个元素只占1bit)
原理:一个元素加入到过滤器时,会使用n个哈希函数,将这个元素进行n次计算,映射到位数组中的n个点,每个点设置为1.当查询新元素是否在其中时,将新元素的每个计算结果去位数组中判断是否都为1,都为1的话,大概率存在。只要有一个为0,那么说明这个元素就不存在这个过滤器中。
缺点:因为对元素使用哈希函数存在哈希冲突,所以有可能存在误差。但是不存在的情况一定是不存在的。
一般存入的元素不好删除。
注:使用n个哈希函数,是为了降低哈希冲突,降低误差率。误差率可以设置,但是误差率越小,需要的时间和存储空间越大,反之误差率越大,需要的时间和空间越小。 不要让实际元素个数远大于初始化元素个数
相关命令:bf.add bloomname value 将元素加入到过滤器中,不存在则新建过滤器
bf.exists bloomname value 判断元素是否存在过滤器当中
bf.madd bloomname value1 value2 将一个或者多个元素加入到新建的过滤器中。
使用场景:
1.判断一个数据是否存在于一个包含大量数据集(数据集很大那种)
2.防止缓存穿透(大量查询没有缓存的数据,直接打到db) 先拿数据在过滤器中查询,不在则直接返回空。在的话则去查询缓存,不在缓存中则去查db. 需要先初始化布隆过滤器数据,新增的时候需要去增加
3.黑名单、邮箱垃圾邮件过滤、爬虫排除爬过的url、内容推荐时判断数据是否已经推荐过
redis为什么快?
1.完全基于内存操作
2.使用c语言实现,使用几种基础的数据结构,做了大量优化,性能极高
3.使用单线程,无上下文切换成本
4.基于非阻塞的io多路复用机制
Io多路复用机制?
Io多路复用程序会监控所有的socket,当对应的socket准备好后,会将对应的socket压入一个队列中,然后一个个线性交给对应的事件处理器去处理。


单线程好处:
1.没有创建线程和销毁线程带来的开销
2.避免了上下文的切换带来的cpu开销
3.避免了线程之间的争抢带来的锁问题
注:redis单线程只是在执行命令时,单线程一个个执行。其他模块还是使用多线程的。例如无用连接的释放,大key删除。
因为是单线程的,所以不要在生产环境使用长命令,例如keys,flushall,flushdb
为什么redis6.0使用多线程?
使用多线程并不是抛弃单线程,命令执行还是单线程。多线程主要用来处理数据的读写和协议解析。
Redis的瓶颈在于网络io而不是cpu,使用多线程可以提高io读写效率,提高性能
热key怎么处理?
热key指大量的请求全部去访问redis的某一个key,流量过于集中,达到物理网卡的上限,使得redis服务器宕机
处理:
1.提前把热key打散到不同的机器上
2.加入二级缓存,提前加载热key数据到内存中。如果redis宕机,走内存查询
缓存击穿?
指大量的请求访问一个key,当过期时,请求就会直接走到mysql,这样会导致mysql宕机。
处理:
加锁更新。当查询key发现没有缓存时,对这个key进行加锁,然后去数据库中查询,再更新到缓存中,再解锁返回给用户。
缓存穿透?
指拿不存在缓存的key去请求,每次都需要查数据库。大量请求时,数据库可能会宕机(例如拿负数作为ID去查询对应数据)
处理:加入一层布隆过滤器。查询之前先去布隆过滤器中查询是否存在,不存在的话直接返回空值。存在的话则去缓存中中查询。
缓存雪崩?
指一段时间内大量的缓存失效,导致大量请求打到db,造成数据库宕机
处理:
1.不同的key设置不同的过期时间,避免同时过期
2.加入二级缓存,将一些数据写入内存中。redis挂了就走内存查询
3.限流。redis宕机,进行限流,避免同时刻大量请求导致db宕机
Redis过期删除策略?
主要有2种过期删除策略:
1.惰性删除:当查询到这个key时,会去判断这个key是否过期,过期删除返回空。
缺点:如果这个key长时间没有访问,则会一直不会被删除,占用内存。
2.定期删除:定期从数据库中获取一部分数据去检查是否过期,过期则删除。
因为每次只能拿一部分数据做检查,可能有的数据过期了也一直没有检查删除掉。
如果惰性删除和定期删除都没有删除过期的key的话,则采用redis的内存淘汰机制
内存淘汰机制:
1.volatile-lru:从已设置过期时间的key中,选取最近最少使用的key进行删除
2.allkeys-lru:从所有的key中选择最近最少使用的key进行淘汰
建议设置 volatile-lru作为内存淘汰机制。
redis持久化
1.rdb:快照的方式将某个节点状态保存在二进制文件dump.rdb中。
镜像全量持久化 。默认的持久化方式(开启了aof,则优先使用aof)
通过将某个时间点的数据库状态保存到rdb文件中。rdb文件是一个压缩的二进制文件,保存在硬盘中。所以即使redis宕机,也可以通过rdb文件恢复当时的数据。
触发方式:
自动触发:通过在redis.conf配置文件中设置触发的条件和生成文件路径和名称
手动触发:(一般在重启服务器和迁移数据时使用手动触发)
1.通过Save命令在执行rdb文件生成时,会阻塞后续的命令执行,处理完才去执行命令。不合适。
2.通过bigsave命令生成rdb文件:bigsave会fork出一个子进程去生成rdb文件,父进程则处理命令执行,不会阻塞进程。
2.aof:根据设置定时将写命令保存到aof文件中。 命令增量持久化
写命令执行完后,会将写命令追加到aof_buf缓存区的末尾。在服务器结束一个事件循环之前,会调用函数去决定是否将aof_buf缓冲区的数据写到aof文件中。默认设置为每秒写一次。
注:随着命令执行越多,aof文件越来越大。当文件大小超过了设置的阀值,会对aof进行一次压缩重写。将多个命令压缩成一个命令,生成一个新文件替代之前的aof文件。
注:rdb可以理解为每次将当前内存中所有的数据进行一次保存。而aof只将每次的写命令进行增量保存。可以使用rdb和aof配合使用。服务器重启后,先执行rdb二进制命令,得到全量的数据。这时可能最新的数据不在,这时再执行下aof中的命令,得到最新的数据。
Rdb数据的完整性不如aof,但是因为aof内容比较多,所以恢复时间较长。
redis高可用?
哨兵模式:
基于一主多从的模式,同时监控多个主从服务器,在master服务器宕机时,会从slave中选取一个作为新的master接收处理命令。故障
Sentinel会每隔一秒向所有实例(包括所有主从服务器和其他的sentinel)发送ping命令,会根据回复判断是否下线,这叫主观下线。这时会去询问其他的sentinel,超过半数认为下线的话,则会标记为客观下线,同时触发故障转移。
redis cluster集群?
一个redis集群由多个node节点构成。各个节点之间通过cluster meet命令连接。
Redis通过集群分片的形式来保存数据。整个集群被分成16384个slot槽。每个节点可以处理0-16384个slot。只要有一个slot没有节点去处理,集群都会处于下线状态。
客户端向节点发送命令,如果刚好找到slot属于当前节点,则执行命令。否则则会返回一个moved命令给客户端让它找到正确的节点
注:sentinel侧重于高可用。cluster侧重于可扩展
redis事务?
Redis事务就是批量执行一堆命令,每个命令执行失败与否互不影响。执行期间不会去执行客户端其他命令,直到事务完成
redis分布式锁?
使用setnx来争抢锁,抢到后使用expire给锁加一个过期时间。
如果锁住之后,加过期时间之前,进程crash了,会导致锁一直存在。这时可以使用set命令将setnx+expire合并为一个操作。
例如: set keyname ’xxx’ ex 10 nx 当这个key不存在时,设置过期时间为10秒。存在则返回false(加锁+过期时间)
keys命令和scan?
Keys命令可以扫出指定模式的key列表,但是会阻塞当前的线程,所以生产使用keys会造成短时间没办法使用redis,服务停顿。不建议在生产环境使用。
Scan可以无阻塞的找到指定模式的数据,但是会有一定的重复,需要在客户端进行去重。时间消耗上也会多一些。
Redis做异步队列?
使用列表list做异步队列,使用lpush生产消息,rpop消费消息(或者使用rpush和lpop)。当没有消息时,可以使用sleep一会再重试。
不使用sleep的话,可以使用brpop命令消费消息。brpop当获取不到数据时,会堵塞直到超时或者来了新数据。
生产一次消费多次?
使用发布订阅功能实现1:n。
注:使用发布订阅模式的话,如果消费者下线的话,会造成数据丢失。
Redis实现延迟队列?
使用有序集合sorted set的score字段实现。将每个消息内容作为key,时间戳作为score。定时任务进行轮训获取当前时间之前的数据进行处理。
pipeline管道?
将多次io往返时间缩短为1次。但是注意批量命令之间必须没有因果关联
Mysql redis缓存一致性 ?
先更新mysql,然后删除redis缓存。下次查询没有缓存的时候,会去查mysql,重写入redis。能保证是一致的。




