设计一个排行榜通常需要使用有序集合(Sorted Set)数据结构,而Redis中的有序集合(Sorted Set)正是用来处理这种场景的理想选择。以下是如何设计一个排行榜的一般步骤:
创建有序集合: 在Redis中,可以使用ZADD命令创建一个有序集合,每个成员都有一个分数,表示该成员的排名。初始时,排行榜是空的。
添加成员: 使用ZADD命令将用户及其分数添加到有序集合中。分数可以根据用户的某种指标,如分数、积分、得分等来设置。
查询排名: 使用ZREVRANK命令或ZRANK命令可以查询指定成员在排行榜中的排名。排名通常从0开始,表示第一名。
查询分数: 使用ZSCORE命令可以查询指定成员的分数,这个分数可以用来表示该成员的排名依据。
获取排行榜: 使用ZRANGE或ZREVRANGE命令可以获取排行榜中的前N名或后N名成员,这可以用来展示排行榜的内容。
更新成员分数: 使用ZINCRBY命令可以增加或减少指定成员的分数,用来更新成员在排行榜中的排名。
移除成员: 使用ZREM命令可以从排行榜中移除指定的成员。
设置过期时间: 可以使用EXPIRE命令来设置排行榜的过期时间,以便在一段时间后自动清除排行榜数据。
# 创建排行榜
ZADD leaderboard 1000 "UserA"
ZADD leaderboard 950 "UserB"
ZADD leaderboard 1100 "UserC"
# 查询排名
ZRANK leaderboard "UserA" # 返回0,表示UserA排在第一名
# 查询分数
ZSCORE leaderboard "UserB" # 返回950
# 获取前N名
ZREVRANGE leaderboard 0 2 WITHSCORES # 返回前3名成员及其分数
# 更新成员分数
ZINCRBY leaderboard 50 "UserA" # 将UserA的分数增加50
# 移除成员
ZREM leaderboard "UserB" # 从排行榜中移除UserB
使用淘汰策略:Redis 支持几种不同的淘汰策略,如 LRU (Least Recently Used) 或 LFU (Least Frequently Used)。这些策略可以自动删除最不常用的数据,以确保内存中始终保存最热的数据。
设置过期时间:对于每个存储在 Redis 中的值,都可以设置一个过期时间。过期时间到达后,Redis 会自动删除该值。这种机制可以确保 Redis 中的数据始终是最新和最热的。
优化查询:对于频繁执行的查询,可以通过使用 Redis 的查询缓存功能来提高效率。这个功能可以将查询结果缓存起来,这样在相同查询再次执行时,就可以直接从缓存中获取结果,而不需要重新执行查询。
一般在生产上的话,在Redis重启后,两个一起用的,因为两种持久化方式优缺点会互补,在应急情况下先使用RDB将大量数据读取出来,在使用AOF将数据补全。
使用setnx。
setnx(“key“,”value“)的作用是当且仅当,在redis中key的值是不存在的时候,setnx(“key“,”value“)的操作会返回true,如果key语句存在,那么始终是返回false。根据setnx这样的特性, 我们可以使用setnx实现一个分布式锁,但多个线程进来的时候,当其中一个线程执行上述的第5行代码(上锁操作),其他线程在第一个线程没有执行完毕后,都是处于自旋的状态(拿不到锁),只有第一个线程执行完毕释放锁后(第17代码),其他的线程才有可能拿到。setIfAbsend是setnx的客户端用法。
(1)volatile-lru:使用LRU
算法淘汰上次使用时间最早的,且使用次数最少的key
,只淘汰设定了有效期的key。
(2)allkeys-lru:从所有key的哈希表(server.db[i].dict)中随机挑选多个key,然后再选到的key中利用LRU算法淘汰最近最少使用的数据。
(3)volatile-random:随机淘汰数据,只淘汰设定了有效期的key
(4)allkeys-random:从所有key的哈希表中随机挑选多个key淘汰掉。
(5)volatile-ttl:从已设置过期时间的哈希表(server.db[i].expires)中随机挑选多个key,然后在选到的key中选择过期时间最小的数据淘汰掉。
(6)no-enviction(驱逐):禁止驱逐数据。
(1)redis是基于内存的,内存的读写速度非常快
(2)redis是单线程的,省去了很多上下文切换线程的时间
(3)redis使用多路复用技术,可以处理并发的连接,非阻塞IO内部实现epoll,使用了单线程来轮询描述符,将数据库的开、关、读、写都转换成了事件,减少了线程切换时上下文的切换和竞争。
支持
(1)DISCARD 取消事务,放弃执行事务块内的所有命令
(2)EXEC 执行所有事务块内的命令
(3)MULTI 标记一个事务块的开始
(4)UNWATCH 取消 WATCH 命令对所有 key 的监视
(5)WATCH key [key…] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命
令所改动,那么事务将被打断
(1)memcache把数据都放到了内存中,当服务宕机或者是断点时,数据会丢失,而且memcache存储的数据不能超过内存大小。redis支持数据持久化。
(2)memcache支持的都是简单的字符串,redis有更为丰富的数据类型,提供了String、Hash、set、zeset、list五中数据类型
(3)使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。 Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
(4)value值大小不同,redis最大考验达到512m,而memcache只有1mb
(5)redis的速度比memcache快很多
(6)redis支持master-slave模式的数据备份。
1.使用MySQL的binlog向Redis推送增量数据
异步更新缓存(基于订阅binlog的同步机制),步骤如下:
(1)当MySQL变更数据时,binlog中会存在更新的增量数据。
(2)将这些增量数据推送给Redis
(3)Redis根据binlong的记录,对缓存进行更新。
这里消息的推送可以使用MQ来进行实现。
2.延时双删
步骤如下:
(1)先删除缓存
(2)再写数据库
(3)休眠一段时间
(4)再次删除缓存
那么,这个500毫秒怎么确定的,具体该休眠多久呢?需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。当然这种策略还要考虑redis和数据库主从同步的耗时。最后的的写数据的休眠时间:则在读数据业务逻辑的耗时基础上,加几百ms即可。比如:休眠1秒。
这样做的弊端是:最差的情况是在休眠时间,用户读到的数据和DB不一致,增加了请求的耗时。
解释一:缓存雪崩,是指在某一时间段,缓存集中失效。 比如:在写文本的时候,马上就要到双十二零点了,产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。
解释二:其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,那么那个时候数据库能顶住压力,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
解决方案:
(1)redis高可用
我们设置多台redis,最好三主三从,这样一台宕机后,其它的还能正常工作,其实就是搭建集群
(2)限流降级
在缓存失效后,通过加锁或者队列来控制的°数据库的写缓存的数量。比如对某个key只允许一个线程查询数据和写缓存,其它线程等待。
(3)数据预热
数据预热就是在项目正式部署前,我们先把可能的数据提前访问一遍,这样部分大量访问的数据就会被加载到缓存中。在即将发生高并发前手动触发加载缓存中的不同的key,设置不同的失效时间,缓存失效的时间点尽量匀称。
(4)分散Key的失效时间
尽量让key的失效时间分散一些,设置不同的过期事件
(5)做二级缓存
A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
redis缓存穿透就是,在客户端查询数据的时候,缓存中没有,缓存命中率为0,于是去持久层数据库中去查询,结果发现也没有。当用户很多时,因为缓存中没有都去了数据库中查询,导致数据库压力过大,这时候就出现了缓存穿透的问题。
解决方案:
(1)布隆过滤器
布隆过滤器的工作原理是,现规定好,redis缓存中可能存入的数据(比如:商品信息,就规定商品信息可能被存入;身份证号码,就规定身份证号码可能会存入;如果两者都有可能,就两者都规定)规定完毕后, 寻找这些数据所对应的哈希值的所有可能(比如:面包m,所有的排列的可能性都列出来,就是:面包m、面m包、包面m等等…),把列出来的所有可能性的数据全部转化为哈希值, 再把这些所有可能出现的哈希值(非常大),存入一个很大的bitmap中,订单有一个不可能的查询数据过来的时候,bitmap会直接拦截,不会让他们去查询缓存。可以有效的避免redis缓存穿透。
(2)设置缓存空对象
当缓存不命中后,即使返回空的对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,而不会再去访问持久层的数据库了。