基于.NET的电子商务网站开发,网站推广指标包括,免费简单门户网站开发,西部数码上传网站Redis 什么是redis
redis是一款基于内存的k-v数据结构的非关系型数据库#xff0c;读写速度非常快#xff0c;常用于缓存#xff0c;消息队列、分布式锁等场景。
redis的数据类型
string#xff1a;字符串 缓存对象#xff0c;分布式ID#xff0c;token#xff0c;se…Redis 什么是redis
redis是一款基于内存的k-v数据结构的非关系型数据库读写速度非常快常用于缓存消息队列、分布式锁等场景。
redis的数据类型
string字符串 缓存对象分布式IDtokensession数字自增分布式锁JWT
list列表 消息队列
hash哈希 缓存对象 购物车
set集合 缓存对象 点赞可能认识的人共同好友。社交功能 收藏夹
zset有序集合 排行榜
geo地理 地理位置附近的人滴滴
hyperloglog基数统计 网站pu统计
stream流 消息队列
bitmap位图 登录状态连续签到二值统计
Redis还支持事务发布-订阅切片集群主从复制哨兵持久化内存淘汰机制lua脚本过期删除策略
为什么使用redis作为mysql的缓存
高性能
高并发
常见数据类型的底层实现
数据类型3.07.0stringSDSSDSlist双向链表压缩列表quicklisthash压缩列表哈希表listpack哈希表set哈希表整数集合哈希表整数集合zset压缩列表跳表listpack跳表
元素数量小于512元素长度小于64字节
string简单动态字符串
listquicklist压缩列表双向链表
hashlistpack压缩列表哈希表
set元素少于512个且为整数整数集合否则哈希表
zset元素小于128元素值小于64字节时压缩列表否则跳表
7.0后压缩列表又listpack代替
Redis线程模型 redis是单线程吗
先指出什么是redis的单线程redis的单线程指的是接受客户端请求解析请求处理请求进行数据读写操作发送请求结果是由主线程处理的
然后分析redis程序不是单线程启动redis程序不仅由主线程还有后台线程关闭处理文件aof刷盘lazyfree线程异步释放内存
redis采用单线程为什么还那么快
redis内存操作
I/O多路复用机制
单线程模型避免多线程频繁的创建和切换
redis之前为什么是单线程模型
一方面redis性能瓶颈不在于cpu而在于网络IO多线程也仅仅是处理网络IO时使用多线程指令处理过程还是单线程。 另一方面多线程频繁的创建销毁和切换也是不小的cpu开销增加了系统复杂性还需要考虑加锁场景。aaaaaa
Redis持久化
AOF日志执行完一条写命令就将命令追加到server.aof_biuf缓冲区然后通过系统write调用将aof_buf缓冲区的数据写入到aof文件此时数据并没有写入到硬盘而是拷贝到了内核缓冲区page_cache,等待内核将数据写入磁盘。在redis重启时会读取aof文件中的命令进行数据恢复。
这个过程是先执行命令再去存储命令一方面避免了对命令的语法检查另一方面不会阻塞写命令运行但是可能会发生数据丢失和阻塞后续命令
AOF写回策略有三种
alway立即写回磁盘
severysec每秒写入一次
no有内核控制写入时机
写回策略写回时机优点缺点always立即写回可靠性高几乎不会丢失数据磁盘压力大性能开销大everysec每秒写回性能适中宕机时最多损失1s内数据no内核决定性能好宕机时可能损失最多数据
AOF日志过大时会触发AOF重写读取当前数据库中所有的键值对记录到新的aof文件从后将文件重命名替换原来的aof文件先生成后替换模式避免了源文件发生污染。
重写AOF过程是由后台子进程bgrewriteaof完成的1.避免了主进程阻塞主进程可能继续处理请求 2.使用主进程而不是主线程多线程之间会共享内存修改共享内存数据时需要加锁来保证数据安全。而父子进程是以只读方式共享内存数据的当一方发生写操作时就会触发写时复制形成两个独立数据副本就不用加锁来保证数据安全
如果AOF日志重写过程中主线程处理了写请求那么写请求命令会被同时写进AOF缓冲区和AOF重写缓冲区当子进程完成AOF重写后主进程会将AOF重写缓冲区的所有内容追加到新的AOF文件中改名覆盖原来文件。
RDB快照记录某一个瞬间的内存数据。这种方法重启时恢复数据比AOF重启快得多。AOF重启是将命令重新执行一遍速度非常慢
RDB快照又save和bgsave两个命令控制。
save主线程进行RDB快照生成会阻塞主线程
bgsave创建一个子进程来处理生成RDB文件
Redis 的快照是全量快照也就是说每次执行快照都是把内存中的「所有数据」都记录到磁盘中。所以执行快照是一个比较重的操作如果频率太频繁可能会对 Redis 性能产生影响。如果频率太低服务器故障时丢失的数据会更多
RDB执行快照的时候数据还是可以被修改的写时复制技术。执行bgsave命令时会fork一个子进程会将主线程的页表复制一份给子进程这两份页表指向同一片内存区域当一方尝试进行写操作时就会触发写时复制创建两个独立的数据副本RDB快照继续对副本进行快照主线程又可以进行写命令操作。
混合持久化RDB快照重启时恢复快但是宕机会损失较多数据AOF重启式恢复较慢但是宕机时损失数据较少。这样既保证了重启数据恢复速度又降低了数据丢失风险。
混合持久化工作在AOF日志重写的过程中当执行aof日志重写时主进程fork的子进程就会先将与主进程共享的数据以RDB快照的形式存入aof文件然后主进程记录在aof重写缓冲区的数据会以aof日志的形式追加到aof文件这样就形成了一个混合持久化文件然后替换原来的aof文件。这样重启时会先加载保存大量数据的rdb文件然后加载较少的aof文件使得重启数据恢复数据较快。因为原来的一部分命令以快照的形式存储了起来。这个重启是指数据全部加载完毕因为rdb里面的key可能被修改入药aof文件进行修正这样才算重启全部完成。
持久化方式数据重启AOF宕机数据丢失少重启数据恢复较慢RDB宕机丢失数据较多重启数据恢复快混合持久化宕机数据丢失较少重启数据较快
Redis如何实现高可用
主从复制
主从复制就是将一台redis服务器同步数据到多台从服务器就是一主多从的模式并且主从服务器之间采用读写分离的模式。
主服务器可以进行读写操作当发生写操作时自动将写操作同步给从服务器从服务器一般只读并接受主服务器同步的写操作命令。
所有的写操作都是由主服务器完成的主从服务器的数据是一致的无法保证强一致性。在从节点进行写操作会报错因为从节点默认是只读模式
哨兵模式
哨兵模式是为了解决Redis主从模式主服务器宕机时需要手动恢复的问题哨兵模式可以监控主从服务器并提供主从节点故障转移功能。
切片集群
当redis缓存数据达到一台服务器无法缓存的时候就需要使用redis切片集群方案。将数据散发到不同的服务器上。
Redis Cluster采用hash槽来处理数据与节点之间的映射关系。一个切片集群共有16384个hash槽。
具体过程发了为两步
根据键值对的 key按照 CRC16 算法 计算一个 16 bit 的值。再用 16bit 值对 16384 取模得到 0~16383 范围内的模数每个模数代表一个相应编号的哈希槽。
具体映射方案
平均分配 在使用 cluster create 命令创建 Redis 集群时Redis 会自动把所有哈希槽平均分布到集群节点上。比如集群中有 9 个节点则每个节点上槽的个数为 16384/9 个。手动分配 可以使用 cluster meet 命令手动建立节点间的连接组成集群再使用 cluster addslots 命令指定每个节点上的哈希槽个数。
集群脑裂导致数据丢失问题
主从架构中主节点与所有从节点因为网络波动失去联系但与客户端依旧保持连接客户端依旧向这个主节点写数据但是这些数据无法同步给从节点哨兵发现主节点失联重新选举主节点这样一个集群就出现了两个主节点。当原来主节点重连时会降级为从节点并且清空数据与主节点进行数据同步。那么失联那段时间写入的数据就会丢失。
解决方案当主节点下线或者通信时间超时的总数量小于阈值的时候紧张主节点写入数据客户端返回错误。
min-slaves-to-write 主节点至少需要与几个节点连接小于这个数禁止写数据
min-slaves-max-lag 主从数据复制和同步延迟不能超过多少秒超时就会禁止写入数据
即使原主库是假故障期间也无法响应哨兵心跳和从库进行同步自然也就无法和从库进行 ACK 确认了。这样一来min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足原主库就会被限制接收客户端写请求客户端也就不能在原主库中写入新数据了。
等到新主库上线时就只有新主库能接收和处理客户端请求此时新写的数据会被直接写到新主库中。而原主库会被哨兵降为从库即使它的数据被清空了也不会有新数据丢失。
Redis过期删除和内存淘汰策略
如果key设置了过期时间就会把key带上过期时间存入到过期字典中。
过期删除惰性删除定时删除定期删除 redis使用定期删除和惰性删除结合的策略。
定时删除顶个计数器到期自动删除
惰性删除等查询到key时去判断是否过期
定期删除过一段时间随机抽取20个键如果超过五个就会再抽取20个执行时间有一个阈值默认是25ms
过期删除策略优点缺点定时删除能及时清理过期键值会占用大量的cpu资源惰性删除占用很少的系统资源对cpu时间最友好key不被访问一直存在造成内存浪费定期删除限制执行时长和频率削减对cpu的占用减少了过期的内存消耗难以去确定执行的时长和频率
Redis持久化时会对过期做什么处理
RDB文件生成阶段RDB文件生成期间会对key进行检查过期的不会被保存到新的RDB文件中。 RDB文件加载阶段主服务器会对进行检查过期键不会被加载到内存中从服务器接收到主服务器的RDB文件不会对键的状态进行检查从节点在执行只读操作时如果是有expire设定的key则会根据自己机器上的时钟来判断是否已过期如果未过期则返回给客户端。但从节点本身不执行删除操作而是会等待后面的del同步操作。
AOF重写阶段会对键值进行过期检查过期的不会被保留到新生成的aof文件 aof文件过大 AOF写入阶段如果过期键没被删除aof文件会保留此过期键当过期间被删除后会向aof文件显式追加一条删除命令 (生成aof)
Redis主从复制时对过期键如何处理
只有主服务器进行过期扫描从库过期键依靠主服务器控制主服务器会向AOF文件追加del命令同步到从库。
Redis内存满了会发生什么
就会触发内存淘汰机制
不做数据淘汰满了就无法进行写操作命令但是可以执行删除查询等操作 。maxmemory可以设置最大运行内存
执行数据淘汰
1.对过期时间中数据进行淘汰volatile-random,volatile-ttl,volatile-lru,volatile-lfu
2.对所有键进行数据淘汰: allkeys-random,allkeys-lru,allkeys-lfu
LRU 算法和 LFU 算法有什么区别
传统的LRU算法基于链表实现链表中的元素按照操作顺序从前往后排列最新操作的元素会一道链表头部删除末尾元素。
redis没有采用传统的方法因为这样需要维护一个链表会带来额外的空间开销并且存储数据非常多的时候链表会很大大量访问时就会产生很多移动操作极大的耗费性能。
redis在对象头中添加了一个24位的lru字段用于记录最后一次访问的时间随机淘汰时就是比较这个时间来决定是否淘汰这样就不用维护链表节省了空间提高了效率和性能。但是当一次性读取大量的数据而这些数据只会被读取一次却能保存相当长的时间造成缓存污染。
LFU 算法会记录每个数据的访问次数。当一个数据被再次访问时就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后数据留存在缓存中很长一段时间的问题。
redis在对象头中添加一个24位的lfu字段高16位用来记录上次访问的时间戳ldt低八位用来记录拼凑logc这个是按数学概率来进行增加削减的。
Redis缓存设计
如何避免缓存雪崩缓存击穿缓存穿透
缓存雪崩过期时间更加均匀比如给过期时间加上随机数值不设置过期时间由后台服务决定更新时机
缓存击穿设置互斥锁如双重检查锁不设置过期时间有后台决定更新时机进行限流一次只允许几个请求
缓存穿透对于空请求返回一个默认值使用布隆过滤器限制非法请求。
Redis缓存是策略动态缓存热点数据
数据存储受限系统并不是将所有的数据都添加到缓存中而只是将其中一部分热点数据缓存起来。
动态热点缓存的策略是根据数据最新访问时间来进行排名并过滤到不常用的数据只留下经常访问的数据。
在 Redis 中可以用 zadd 方法和 zrange 方法来完成排序队列和获取 200 个商品的操作。
常见的缓存更新策略
Write Back写回策略
Write Back写回策略在更新数据的时候只更新缓存同时将缓存数据设置为脏的然后立马返回并不会更新数据库。对于数据库的更新会通过批量异步更新的方式进行。
redis没有异步更新数据库的功能
常用在计算机体系结构中的设计比如 CPU 的缓存、操作系统中文件系统的缓存
Cache Aside旁路缓存策略
写更新数据库数据然后更新缓存数据
读如果缓存命中直接返回数据缓存不在就去数据库查找然后更新缓存
不能先删除缓存再更新数据库会出现缓存和数据库数据不一致性的问题
适合读多写少的场景。
Write / Read Through写穿读穿策略
read through先查询缓存中数据是否存在如果存在则直接返回如果不存在则由缓存组件负责从数据库查询数据并将结果写入到缓存组件最后缓存组件将数据返回给应用。
write through当有数据更新的时候先查询要写入的数据在缓存中是否已经存在
如果缓存中数据已经存在则更新缓存中的数据并且由缓存组件同步更新到数据库中然后缓存组件告知应用程序更新完成。如果缓存中数据不存在直接更新数据库然后返回
本地缓存可以这种策略分布式缓存无法实现因为没有此类功能
Redis如何实现延迟队列
使用场景
订单超时未支付自动关闭外卖十分钟没有接单会自动取消
Redis可以使用有序集合ZSet来实现延迟消息队列ZSet的score属性可以赋值给延迟执行的时间轮询比对利用arangebyscore来查询所有待处理任务循环执行即可
Redis的大Key问题如何处理
大key
String类型的值大于10kb
HashListSetZSet类型元素超过5000个
大key带来的问题
操作大key比较耗时客户端阻塞可能响应超时获取大key产生的流量较大可能引发网络阻塞del删除大key引发主线程阻塞集群模型在slot分片均匀的情况下会产生数据和查询倾斜的问题部分大key的几点内存占用多QPS也比较大
如何找到大key redis-cli -a “密码” --bigkeys 从节点或者小流量时期该检测会阻塞主线程 方法只会返回bigest key无法返回前n个数据集合类型 只会统计menber数量不会计算占用大小。 scan 命令 scan是基于游标的迭代器scan命令对数据库扫描然后type获取类型根据不同类型使用不同命令来获取长度 string: STRLEN来获取字符串长度 集合类型MEMORY USAGE来查询 RdbTools工具 RdbTools 第三方开源工具可以用来解析 Redis 快照RDB文件找到其中的大 key rdb dump.rdb -c memory --bytes 10240 -f redis.csv
如何删除大key 不能直接删除大key在应用程序释放内存时操作系统需要把释放掉的内存块插入一个空闲内存块的链表以便后续进行管理和再分配。这个过程本身需要一定时间而且会阻塞当前释放内存的应用程序。所以如果一下子释放了大量内存空闲内存块链表操作时间就会增加相应地就会造成 Redis 主线程的阻塞如果主线程发生了阻塞其他所有请求可能都会超时超时越来越多会造成 Redis 连接耗尽产生各种异常。
分批删除大Key删除大 Hash使用 hscan 命令每次获取 100 个字段再用 hdel 命令每次删除 1 个字段。 删除大 List通过 ltrim 命令每次删除少量元素。 删除大 Set使用 sscan 命令每次扫描集合中 100 个元素再用 srem 命令每次删除一个键。 删除大 ZSet使用 zremrangebyrank 命令每次删除 top 100个元素。 异步删除用 unlink 命令代替 del 来删除, Redis 会将这个 key 放入到一个异步线程中进行删除这样不会阻塞主线程。
redis管道有什么作用
管道技术可以解决多个命令执行时的网络等待它是把多个命令整合到一起发送给服务器端处理之后统一返回给客户端这样就免去了每条命令执行后都要等待的情况从而有效地提高了程序的执行效率。但使用管道技术也要注意避免发送的命令过大或管道内的数据太多而导致的网络阻塞。
管道技术本质上是客户端提供的功能而非 Redis 服务器端的功能。
Redis支持事务回滚吗
Redis 中并没有提供回滚机制虽然 Redis 提供了 DISCARD 命令但是这个命令只能用来主动放弃事务执行把暂存的命令队列清空已经执行过的命令结果也不会删除起不到回滚的效果。
如何使用redis实现分布式锁
分布式锁用于控制分布式场景下某一个资源只能被某一个应用所使用。 Redis 的 SET 命令有个 NX 参数可以实现「key不存在才插入」所以可以用它来实现分布式锁
Redis 节点实现分布式锁时对于加锁操作我们需要满足三个条件
加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作但需要以原子操作的方式完成所以我们使用 SET 命令带上 NX 选项来实现加锁锁变量需要设置过期时间以免客户端拿到锁后发生异常导致锁一直无法释放所以我们在 SET 命令执行时加上 EX/PX 选项设置其过期时间锁变量的值需要能区分来自不同客户端的加锁操作以免在释放锁时出现误释放操作所以我们使用 SET 命令设置锁变量值时每个客户端设置的值是一个唯一值用于标识客户端
SET lock_key unique_value NX PX 10000 解锁的过程就是将 lock_key 键删除del lock_key要保证执行操作的客户端就是加锁的客户端。 解锁的时候我们要先判断锁的 unique_value 是否为加锁客户端是的话才将 lock_key 键删除。
解锁是有两个操作需要 Lua 脚本来保证解锁的原子性因为 Redis 在执行 Lua 脚本时可以以原子性的方式执行保证了锁释放操作的原子性。
if redis.call(get,KEY[1]) ARGV[1] thenreturn redis.call(del,KEYS[1])
elsereturn 0
end基于rediss的分布式锁优点
性能高效redis本身提供了setnx方法实现简单redis可以集群部署能够避免单点故障
缺点
超时时间不好设置。如果锁的超时时间设置过长会影响性能如果设置的超时时间过短会保护不到共享资源。看门狗机制去自动续约。Redis 主从复制模式中的数据是异步复制的这样导致分布式锁的不可靠性。主节点获取到锁后还没同步就宕机了新的主节点还能获取到锁。就失效了
分布式锁算法 Redlock红锁是让客户端和多个独立的 Redis 节点依次请求申请加锁如果客户端能够和半数以上的节点成功地完成加锁操作那么我们就认为客户端成功地获得分布式锁否则加锁失败。这种算法对于集群设备之间的时序和系统时钟苛刻要求。
有界网络延迟可以保证数据包始终在某个保证的最大值内到达 延迟有界进程暂停换句话说硬实时约束通常只有 在汽车安全气囊系统等中找到以及有界时钟错误
系统 有 5 个 Redis 节点A、B、C、D 和 E和 2 个客户端1 和 2。如果时钟在一个上会发生什么 的 Redis 节点向前跳跃 客户端 1 在节点 A、B、C 上获得锁定由于网络问题无法访问 D 和 E。 节点 C 上的时钟向前跳跃导致锁过期。 客户端 2 在节点 C、D、E 上获得锁定由于网络问题无法访问 A 和 B。 客户端 1 和 2 现在都认为他们掌握着锁。 客户端 1 请求锁定节点 A、B、C、D、E。 当对客户端 1 的响应在传输中时客户端 1 进入停止世界的 GC。 所有 Redis 节点上的锁都会过期。 客户端 2 在节点 A、B、C、D、E 上获得锁定。 客户端 1 完成 GC并收到来自 Redis 节点的响应指示其成功 获取了锁当进程 暂停。 客户端 1 和 2 现在都认为他们掌握着锁。
如何进行分布式锁定 — Martin Kleppmann 的博客
详解Redis底层数据结构
Dict
SDS
SDS简单动态字符串
结构组成
len字符串长度alloc分配的空间大小修改字符串时去判断已分配空间是否满足否则就去扩容避免了缓冲区溢出flags结构类型SDS支持多种结构类型根据数据大小灵活分配合适类型比如一个人的年龄用byte存储足够那就不需要使用long或者int类型buf数组存放具体数据
SDS与传统c语言的char类型对比有何优势
传统的char必须以 \0 结尾SDS通过len来标记出buf数据长度即使中间有\0也不会中途被判结束因此避免了二进制安全问题并且能够存储图片、视频等二进制数据通过len字段记录数据长度更够避免在数据修改时造成缓冲区溢出支持自动扩容使用flags灵活选取合适的SDS数据结构类型取消了结构体对齐填充节省了空间
双向链表
就是自定义节点实现的双向链表包含前驱节点后继节点value同时封装List包含了链表长度头节点尾节点。
优点
定义了pre和next获取某个节点的前置节点和后置节点只需要O(1)的复杂度len属性表示节点数量可以直接获取链表长度缺点
缺点
链表利用的是碎片化地址无法很好的利用到CPU缓存只有数组能。需要保存额外字段增加了内存开销
压缩列表 zlbytes: 整个压缩列表占用的字节总数zltail列表尾的偏移量zllen节点数量zlend压缩列表结束点prevlen: 前一个节点长度encoding编码方式data节点数据
查询首尾节点元素只需要o(1)复杂度查询其他节点只能遍历O(n)复杂度因此压缩列表不适合存储较多数据
如果前一个节点长度小于254prevlen只需要1个字节如果大于254就需要5个字节
当插入或者更新节点时如果空间不够就会内存重分配如果插入节点元素较大可能导致后续元素的prvlen都发生变化进而造成连锁更新原因就在于增加的4个字节
Listpack listpack 头包含两个属性分别记录了 listpack 总字节数和元素数量然后 listpack 末尾也有个结尾标识。图中的 listpack entry 就是 listpack 的节点了。
每个 listpack 节点结构如下 主要包含三个方面内容
encoding定义该元素的编码类型会对不同长度的整数和字符串进行编码data实际存放的数据lenencodingdata的总长度
可以看到listpack 没有压缩列表中记录前一个节点长度的字段了listpack 只记录当前节点的长度当我们向 listpack 加入一个新元素的时候不会影响其他节点的长度字段的变化从而避免了压缩列表的连锁更新问题。
跳表
zset 结构体里有两个数据结构一个是跳表一个是哈希表。这样的好处是既能进行高效的范围查询也能进行高效单点查询。
哈希表
Redis采用拉链法解决Hash冲突将Hash冲突的元素通过链表串联存储
Redis哈希表结构包含了两个哈希表当hash表数据增多时便会触发rehash过程
此时将ht[1]初始化为ht[0]二倍大小让后将ht[1]中数据rehash后存入ht[2]最后互换地址
由于rehash过程涉及大量数据的拷贝可能会造成阻塞影响功能因此采用渐进式rehash过程客户端请求驱动rehash过程也就是有请求就处理迁移一点数据。
QuickList 双向链表和压缩列表的组合QuickList就是一个链表而链表中的每个元素又是一个压缩链表
压缩列表的不足虽然压缩列表是通过紧凑型的内存布局节省了内存开销但是因为它的结构设计如果保存的元素数量增加或者元素变大了压缩列表会有「连锁更新」的风险一旦发生会造成性能下降。
quicklist 解决办法通过控制每个链表节点中的压缩列表的大小或者元素个数来规避连锁更新的问题。因为压缩列表元素越少或越小连锁更新带来的影响就越小从而提供了更好的访问性能。
quicklist 会控制 quicklistNode 结构里的压缩列表的大小或者元素个数来规避潜在的连锁更新的风险但是这并没有完全解决连锁更新的问题。
整数集合
当Set全部包含整数并且数量不多时就会使用整数集合
typedef struct intset {//编码方式uint32_t encoding;//集合包含的元素数量uint32_t length;//保存元素的数组int8_t contents[];
} intset;整数集合的升级操作
当新元素类型比所有元素的类型都要长时整数集合就会升级拓展空间能节约内存资源
整数集合不支持降级操作主要是频繁的升降级会消耗CPU资源
本文自小林Coding总结而来感谢小林图解Redis