Redis回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据时,Redis检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。所以通过不断达到边界然后不断地回收回到边界以下。
Redis回收使用的是什么算法?原理是什么?
Redis回收使用采用的是近似LRU算法(least recently used )
也就是跟LRU算法(淘汰最近最少使用的数据)很像,但是不是LRU算法,适合于热点数据的处理上
首先,针对问题本身,我们需要淘汰的是最近未使用的相对比较旧的数据淘汰掉,那么,我们是否一定得非常精确地淘汰掉最旧的数据还是只需要淘汰掉比较旧的数据?
咱们来看下Redis是如何实现的。Redis做法很简单:随机取若干个key,然后按照访问时间排序,淘汰掉最不经常使用的数据。为此,Redis给每个key额外增加了一个24bit长度的字段,用于保存最后一次被访问的时钟(Redis维护了一个全局LRU时钟lruclock:REDIS_LUR_BITS,时钟分辨率默认1秒)。
// 评估object空闲时间
unsigned long estimateObjectIdleTime(robj *o) {
if (server.lruclock >= o->lru) {
return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
} else {
return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
REDIS_LRU_CLOCK_RESOLUTION;
}
}
// LRU淘汰算法实现
......
/* volatile-lru and allkeys-lru policy */
else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
{
for (k = 0; k < server.maxmemory_samples; k++) {
sds thiskey;
long thisval;
robj *o;
de = dictGetRandomKey(dict);
thiskey = dictGetKey(de);
if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
de = dictFind(db->dict, thiskey);
o = dictGetVal(de);
thisval = estimateObjectIdleTime(o);
/* Higher idle time is better candidate for deletion */
if (bestkey == NULL || thisval > bestval) {
bestkey = thiskey;
bestval = thisval;
}
}
}
......
redis会基于server.maxmemory_samples配置选取固定数目的key,然后比较它们的lru访问时间,然后淘汰最近最久没有访问的key,maxmemory_samples的值越大,Redis的近似LRU算法就越接近于严格LRU算法,但是相应消耗也变高,对性能有一定影响,样本值默认为5。Redis3.0以上采用近视LRU算法获得了不错的效果,在商业世界,为了追求空间的利用率,也会采用权衡的实现方案。
总结
LRU是缓存系统中常见的淘汰策略,当内存不足时,我们需要淘汰掉最近最少使用的数据,LRU就是实现这种策略的统称。LRU算法实现可以基于HashMap + 双向链表的数据结构实现高效数据读写,于此同时,高效查询却带来了高内存消耗的的问题,为此Redis选择了近似LRU算法,随机采用一组key,选择最老的数据进行删除,能够达到类似的效果。
布隆过滤器的原理是什么?它的优点是什么?缺陷是什么?
布隆过滤器(BloomFilter)是什么?
布隆过滤器是一个很大的二进制数组(bit数组里面只存0和1)和多个哈希函数组成的,主要用来快速判断某个元素是否在集合内,它具有运行速度快(时间效率),占用内存小的优点(空间效率),但是有一定的误识别率和不能删除元素的问题。它只能告诉你某个元素一定不在集合内或可能在集合内(也就是布隆过滤器判断某个元素在集合里那不一定在,只是可能在集合里,但是如果判断不在集合里那就一定不在)
布隆过滤器(BloomFilter)的原理是什么?
一个元素插入布隆过滤器时经过多个hash函数,每个hash函数都有一个hash值,然后把对应的数组下标的值改成1就可(默认初始化的时候数组每个元素都是值为0),查询的时候只要对应的哈希值在对应的数组下标的值有一个是0那这个元素就一定不存在集合中
插入数据
实际布隆过滤器是非常大的数组(这里的大是指它的长度大,并不是指它所占的内存空间大),一个数据进行存入布隆过滤器的时候,会经过若干个哈希函数得到若干个哈希值,然后把的哈希值作为数组的下标,然后将初始化的位数组对应的下标的值修改为1
插入流程
- 将要添加的元素给k个哈希函数
- 得到对应于位数组上的k个位置
- 将这k个位置设为1

如上图,插入了两个元素,X和Y,X经过两个hash函数后得到的hash值分别为4,9,因此,4和9位被置成1;Y经过两个hash函数后得到的hash值分别为14和19,因此,14和19位被置成1(这里假设是有两个hash函数,可以有多个hash函数,每个hash函数都有一个hash值,然后把对应的数组下标的值改成1就可)
查询数据
查询的时候只要对应的哈希值在对应的数组下标的值有一个是0那这个元素就一定不存在集合中
为什么不能删除数据?
BloomFilter中不允许有删除操作,因为删除元素后,将对应元素的下标设置为零,可能这些归零的下标被其他元素共用,把这些共用的下标归零就会导致别的元素的判断就会受到影响,这是不被允许的,还是以一个例子说明:

上图中,刚开始时,有元素X,Y和Z,其hash的bit如图中所示,当删除X后,会把bit 4和9置成0,这同时会造成查询Z时,报不存在的问题,这对于BloomFilter来讲是不能容忍的,因为它要么返回绝对不存在,要么返回可能存在。
注意:BloomFilter中不允许删除的机制会导致其中的无效元素可能会越来越多,即实际已经在磁盘删除中的元素,但在bloomfilter中还认为可能存在,这会造成越来越多的false positive。
那么为什么会有误判率呢?

比如查询a这个数,实际中a这个数是不存在布隆过滤器中的,经过2个哈希函数计算后得到a的哈希值分别为4和14,经过查询后,发现4和14位置所存储的值都为1,但是4和14的下标分别是x和y经过计算后的下标位置的修改,该布隆过滤器中实际不存在a,那么布隆过滤器就会误判该值可能存在,所以存在误判率。
影响准确率两个因素
- 布隆过滤器大小:越大,误判率就越小,所以说布隆过滤器一般长度都是非常大的。
- 哈希函数的个数:哈希函数的个数越多,那么误判率就越小。
它的优点是什么?
增加和查询元素的时间复杂度为:O(K), (K为哈希函数的个数,通常比较小),与数据量大小无关
哈希函数相互之间没有关系,方便硬件并行运算
布隆过滤器不须要存储实际的元素,在某些对保密要求比较严格的场合有很大优点
在可以承受必定的误判时,布隆过滤器比其余数据结构有这很大的空间优点
数据量很大时,布隆过滤器能够表示全集,其余数据结构不能
使用同一组散列函数的布隆过滤器能够进行交、并、差运算
缺陷是什么?
- 有误判率,即存在假阳性(False Position),即不能准确判断元素是否在集合中(补救方法:再创建一个白名单,存储可能会误判的数据)
- 不能获取元素自己(也就是不能存取元素本身)
- 通常状况下不能从布隆过滤器中删除元素
- 若是采用计数方式删除,可能会存在计数回绕问题
BloomFilter的使用-缓存穿透
在大多应用中,当业务系统中发送一个请求时,会先从缓存中查询;若缓存中存在,则直接返回;若返回中不存在,则查询数据库。
缓存穿透:当请求数据库中不存在的数据,这时候所有的请求都会打到数据库上,这种情况就是缓存穿透。如果当请求较多的话,这将会严重浪费数据库资源甚至导致数据库假死
BloomFilter解决缓存穿透的思路
这种技术在缓存之前再加一层屏障,里面存储目前数据库中存在的所有key,当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。
Redis除了常用的数据结构外,还熟悉其他的数据结构吗?

Redis存储地图的数据结构,即GEO(地理位置)数据类型,有什么特点呢?有没有用过
1、地理坐标简介
Redis GEO是Redis在3.2版本中新添加的特性,可以将经纬度格式的地理坐标存储到Redis中,并对这些坐标执行距离计算、范围查找等操作。
2、地理坐标常用操作
1.存储坐标(GEOADD命令)
通过使用GEOADD命令,用户可以将给定的一个或多个经纬度坐标存储到位置集合中,并为这些坐标设置相应的名字。
语法格式:GEOADD key longitude(经度) latitude(纬度) name(地方名称)
GEOADD cities 113.2278442 23.1255978 Guangzhou 113.2099647 23.593675 Qingyuan
GEOADD命令会返回新添加至位置集合的坐标数量作为返回值。如果给定的位置在集合中已经有了与之相关联的坐标,那么GEOADD命令将使用用户给定的新坐标去代替已有的旧坐标。
2.获取指定位置的坐标(GEOPOS命令)
在使用GEOADD命令将位置及其坐标存储到位置集合之后,可以使用GEOPOS命令去获取给定位置的坐标
语法格式:GEOPOS key 地方名称 …,如
GEOPOS cities Guangzhou
GEOPOS命令会返回一个数组作为执行结果,数组中的每项都包含经度和维度两个元素,且与用户给定的位置相对应。如果用户给定的位置并不存在于位置集合当中,那么GEOPOS命令将返回一个空值。
3.计算两个位置之间的直线距离(GEODIST命令)
GEODIST命令可用于计算两个给定位置之间的直线距离,该命令的DIST是Distance的简写,意味距离
语法格式:GEODIST key 地方名称1 地方名称2 [unit]
其中,unit用于指定自己想使用的距离单位,可以是以下单位中的一个:
·m——以米为单位,为默认单位。
·km——以千米为单位。
·mi——以英里为单位,1英里≈1.61千米。
·ft——以英尺为单位,1英尺≈0.30米。
GEODIST cities Guangzhou Qingyuan km
在默认情况下,GEODIST命令将以米为单位,返回两个给定位置之间的直线距离。
在调用GEODIST命令时,如果用户给定的某个位置并不存在于位置集合中,那么命令将返回空值,表示计算失败。
4.查找指定坐标半径范围内的其他位置(GEORADIUS命令)
通过使用GEORADIUS命令,可以指定一个经纬度作为中心点,并从位置集合中找出位于中心点指定半径范围内的其他位置
语法格式:GEORADIUS key longitude latitude radius unit [WITHDIST]
其中,可选项WITHDIST如果使用,那么GEORADIUS(georadius)命令不仅会返回位于指定半径范围内的位置,还会返回这些位置与中心点之间的距离,如
# 以广州的经纬度作为中心点,查找cities中位于其半径50km内的所有其他城市
# 并返回距离,在返回距离时所使用的单位与进行范围查找时所使用的单位一致
GEORADIUS cities 113.2278442 23.1255978 50 km WITHDIST
除了WITHDIST之外,GEORADIUS命令还提供了另一个可选项WITHCOORD,通过使用这个选项,用户可以让GEORADIUS命令在返回被匹配位置的同时,将这些位置的坐标也一并返回。
GEORADIUS命令在默认情况下会以无序方式返回被匹配的位置,但是通过使用可选的ASC选项或DESC选项,用户可以改变这一行为,让GEORADIUS命令以有序方式返回结果。如果使用了ASC选项,那么GEORADIUS将根据中心点与被匹配位置之间的距离,按照由近到远的顺序返回被匹配的位置;相反,如果用户使用的是DESC选项,那么GEORADIUS将按照由远到近的顺序返回被匹配的位置。
默认情况下,GEORADIUS命令将返回指定半径范围内的所有其他位置,但是通过可选的COUNT选项,我们可以限制命令返回的最大位置数量,如
GEORADIUS cities 113.2278442 23.1255978 50 km COUNT 2
除了GEORADIUS命令之外,还有一个GEORADIUSBYMEMBER命令也可以用于查找指定位置半径范围内的其他位置;这两个命令的主要区别在于GEORADIUS命令通过给定经纬度来指定中心点,而GEORADIUSBYMEMBER命令则通过选择位置集合中的一个位置作为中心点;除了指定中心点时使用的参数不一样之外,GEORADIUSBYMEMBER命令中的其他参数和选项的意义都与GEORADIUS命令一样。如
# 最多只返回两个位置
GEORADIUS cities Guangzhou 50 km COUNT 2
除此之外,EORADIUSBYMEMBER命令在返回结果的时候,会把作为中心点的位置也一并返回。
GEORADIUS命令和GEORADIUSBYMEMBER命令都支持WITHHASH选项,使用了这个选项的命令将会在结果中包含被匹配位置的Geohash值。需要注意的是,与GEOHASH命令不一样,GEORADIUS命令和GEORADIUSBYMEMBER命令返回的是被解释为数字的Geohash值。而GEOHASH命令返回的则是被解释为字符串的Geohash值。
5.获取指定位置的Geohash值(GEOHASH命令)
可以通过向GEOHASH命令传入一个或多个位置来获得这些位置对应的经纬度坐标的Geohash表示;
语法格式:GEOHASH key name1 name2 …,如
GEOHASH cities Guangzhou Qingyuan
Geohash是一种编码格式,这种格式可以将用户给定的经度和纬度转换成单个Geohash值,也可以根据给定的Geohash值还原出被转换的经度和纬度。比如,通过使用Geohash编码程序,我们可以将清远市的经纬度(113.20996731519699097,23.59367501967128788)编码为Geohash值"ws0w0phgp70",也可以根据这个Geohash值还原出清远市的经纬度。
当应用程序因为某些原因只能使用单个值去表示位置的经纬度时,我们就可以考虑使用GEOHASH命令去获取位置坐标的Geohash值,而不是直接使用GEOPOS命令去获取位置的经纬度。
6.使用有序集合命令操作GEO数据
一个位置集合实际上就是一个有序集合,当用户调用GEO命令对位置集合进行操作时,这些命令实际上是在操作一个有序集合。例如,当我们使用GEOADD命令将广州市的经纬度添加到cities位置集合时,Redis会把给定的经纬度转换成数字形式的Geohash值,然后调用ZADD命令,将位置名及其Geohash值添加到有序集合中,其中Geohash值作为分值,位置名作为元素项。
除了GEOADD之外,包括GEOPOS、GEODIST、GEORADIUS、GEORADIUSBYMEMBER和GEOHASH在内的所有GEO命令都是在有序集合的基础上实现的,这也使得我们可以直接使用有序集合命令对位置集合进行操作。
比如,可以使用ZRANGE命令查看位置集合存储的所有位置,以及这些位置的Geohash值:
ZRANGE cities 0 -1 WITHSCORES
在哪里用到redis
1、热点数据的缓存
由于redis访问速度块、支持的数据类型比较丰富,所以redis很适合用来存储热点数据,另外结合expire,我们可以设置过期时间然后再进行缓存更新操作,这个功能最为常见,我们几乎所有的项目都有所运用。商城里的秒杀商品预热就用到Redis
2、限时业务的运用
redis中可以使用expire命令设置一个键的生存时间,到时间后redis会删除它。利用这一特性可以运用在限时的优惠活动信息、手机验证码等业务场景。
3、计数器相关问题
redis由于incrby命令可以实现原子性的递增,所以可以运用于高并发的秒杀活动、分布式序列号的生成、具体业务还体现在比如限制一个手机号发多少条短信、一个接口一分钟限制多少请求、一个接口一天限制调用多少次等等。
使用int类型,incr方法
例如:文章的阅读量、微博点赞数、允许一定的延迟,先写入Redis再定时同步到数据库
计数功能应该是最适合 Redis 的使用场景之一了,因为它高频率读写的特征可以完全发挥 Redis 作为内存数据库的高效。在 Redis 的数据结构中,string、hash和sorted set都提供了incr方法用于原子性的自增操作,下面举例说明一下它们各自的使用场景:
如果应用需要显示每天的注册用户数,便可以使用string作为计数器,设定一个名为REGISTERED_COUNT_TODAY的 key,并在初始化时给它设置一个到凌晨 0 点的过期时间,每当用户注册成功后便使用incr命令使该 key 增长 1,同时当每天凌晨 0 点后,这个计数器都会因为 key 过期使值清零。
每条微博都有点赞数、评论数、转发数和浏览数四条属性,这时用hash进行计数会更好,将该计数器的 key 设为weibo:weibo_id,hash的 field 为like_number、comment_number、forward_number和view_number,在对应操作后通过hincrby使hash 中的 field 自增。
如果应用有一个发帖排行榜的功能,便选择sorted set吧,将集合的 key 设为POST_RANK。当用户发帖后,使用zincrby将该用户 id 的 score 增长 1。sorted set会重新进行排序,用户所在排行榜的位置也就会得到实时的更新。
4、排行榜相关问题
关系型数据库在排行榜方面查询速度普遍偏慢,所以可以借助redis的SortedSet进行热点数据的排序。
在奶茶活动中,我们需要展示各个部门的点赞排行榜, 所以我针对每个部门做了一个SortedSet,然后以用户的openid作为上面的username,以用户的点赞数作为上面的score, 然后针对每个用户做一个hash,通过zrangebyscore就可以按照点赞数获取排行榜,然后再根据username获取用户的hash信息,这个当时在实际运用中性能体验也蛮不错的。
5、分布式锁
这个主要利用redis的setnx命令进行,setnx:"set if not exists"就是如果不存在则成功设置缓存同时返回1,否则返回0 ,我们服务器是集群的,定时任务可能在两台机器上都会运行,所以在定时任务中首先 通过setnx设置一个lock,如果成功设置则执行,如果没有成功设置,则表明该定时任务已执行。 当然结合具体业务,我们可以给这个lock加一个过期时间,比如说30分钟执行一次的定时任务,那么这个过期时间设置为小于30分钟的一个时间 就可以,这个与定时任务的周期以及定时任务执行消耗时间相关。
当然我们可以将这个特性运用于其他需要分布式锁的场景中,结合过期时间主要是防止死锁的出现。
6、延时操作
这个目前我做过相关测试,但是还没有运用到我们的实际项目中,下面我举个该特性的应用场景。 比如在订单生产后我们占用了库存,10分钟后去检验用户是够真正购买,如果没有购买将该单据设置无效,同时还原库存。 由于redis自2.8.0之后版本提供Keyspace Notifications功能,允许客户订阅Pub/Sub频道,以便以某种方式接收影响Redis数据集的事件。 所以我们对于上面的需求就可以用以下解决方案,我们在订单生产时,设置一个key,同时设置10分钟后过期, 我们在后台实现一个监听器,监听key的实效,监听到key失效时将后续逻辑加上。 当然我们也可以利用rabbitmq、activemq等消息中间件的延迟队列服务实现该需求。
7、分页、模糊搜索
redis的set集合中提供了一个zrangebylex方法,语法如下:
ZRANGEBYLEX key min max [LIMIT offset count]
通过ZRANGEBYLEX zset - + LIMIT 0 10 可以进行分页数据查询,其中- +表示获取全部数据
zrangebylex key min max 这个就可以返回字典区间的数据,利用这个特性可以进行模糊查询功能,这个也是目前我在redis中发现的唯一一个支持对存储内容进行模糊查询的特性。
前几天我通过这个特性,对学校数据进行了模拟测试,学校数据60万左右,响应时间在700ms左右,比mysql的like查询稍微快一点,但是由于它可以避免大量的数据库io操作,所以总体还是比直接mysql查询更利于系统的性能保障。
8、点赞、好友等相互关系的存储
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。 又或者在微博应用中,每个用户关注的人存在一个集合中,就很容易实现求两个人的共同好友功能。
这个在奶茶活动中有运用,就是利用set存储用户之间的点赞关联的,另外在点赞前判断是否点赞过就利用了sismember方法,当时这个接口的响应时间控制在10毫秒内,十分高效。
9、队列
由于redis有list push和list pop这样的命令,所以能够很方便的执行队列操作。