博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis整理
阅读量:2339 次
发布时间:2019-05-10

本文共 9945 字,大约阅读时间需要 33 分钟。

Redis总结

一、什么是Redis?

Redis是一个高性能分布式内存数据库,基于内存运行并支持持久化的noSQL数据库。

二、为什么分布式一定要有redis?

1、性能上来说,redis缓存的出现避免了频繁的访问数据库,并且直接从缓存中拿数据速度更快。

2、在大并发的情况下,如果所有的请求直接访问数据库,数据库会出现连接异常。这个时候,需要redis做一个缓存操作,让请求先访问到redis,而不是直接访问数据库。

3、(还有就是分布式的锁的功能,当然分布式锁可以由zookeeper等代替)

三、Redis的特点

1、Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候,可以再次加载进行使用。

2、Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

3、Redis支持数据的备份,即master-slave模式的数据备份。

四、使用redis有什么缺点?

主要是5个问题:

1、缓存穿透

2、缓存雪崩

3、缓存击穿

4、缓存和数据库双写一致性问题

5、缓存的并发竞争问题(如多个子系统并发set一个key,1、分布式锁处理无顺序2、分布式锁+时间戳处理有顺序)

五、单线程的redis为什么这么快?

1)redis是纯内存操作

2)单线程操作,避免了频繁的上下文切换

3)采用了非阻塞I/O多路复用机制(用到了一些函数库)

六、Redis的数据类型

1、String(字符串):这个其实没什么好说的,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存

2、Hash(哈希,类似java里的Map):这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。博主在做单点登录的时候,就是用这种数据结构存储用户信息,以cookieId作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。

3、List:使用List的数据结构,可以做简单的消息队列的功能。另外还有一个就是,可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好。

4、Set:因为set堆放的是一堆不重复值的集合。所以可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。

另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。

5、Zset(sorted set:有序集合):sorted set多了一个权重参数score,集合中的元素能够按score进行排列。可以做排行榜应用,取TOP N操作。另外,参照另一篇《分布式之延时任务方案解析》,该文指出了sorted set可以用来做延时任务。最后一个应用就是可以做范围查找

七、Redis的持久化

Redis持久化,就是将内存数据保存到硬盘,Redis持久化存储分为AOF和RDB两种模式,默认开启RDB

1、RDB持久化(二进制文件)

redis会在进行rdb持久化的时候,会单独fork一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程后结束后,再用这个临时文件替换上次持久好的文件。

优点:使用单独子进程来进行持久化,主进程不会进行的任何io操作,保证了redis的高性能

缺点:RDB是间隔一段时间进行持久化,如果持久化的时候redis发生故障,会发生数据丢失。所以这种方式更适合数据要求不那么严谨的时候。

fork的时候,内存中的数据被复制了一份,大致两倍的膨胀性需要考虑到。

rdb的snapshots功能:将数据写入到临时文件的时间点是可以通过配置来自己确定的,通过配置redis***在n秒内如果超过m个key被修改***,来执行一次rdb操作。 (save)

#dbfilename:持久化数据存储在本地的文件dbfilename dump.rdb#dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下dir ./##snapshot触发的时机,save    ##如900秒后,至少有一个变更操作,才会snapshot  ##对于此值的设置,需要谨慎,评估系统的变更操作密集程度  ##可以通过"save """来关闭snapshot功能  #save时间,以下分别表示更改了1个key时间隔900s进行持久化存储;更改了10个key300s进行存储;更改10000个key60s进行存储。save 900 1save 300 10save 60 10000##当snapshot时出现错误无法继续时,是否阻塞客户端"变更操作","错误"可能因为磁盘已满/磁盘故障/OS级别异常等  stop-writes-on-bgsave-error yes  ##是否启用rdb文件压缩,默认为"yes",压缩往往意味着"额外的cpu消耗",同时也意味这较小的文件尺寸以及较短的网络传输时间  rdbcompression yes

实际上,数据备份与恢复跟redis的持久化息息相关。对于rdb来说,dump.rdb就是redis持久化文件,同时也是通过dump.rdb来进行数据的备份和恢复的。

2、AOF持久化(字符串文件)

1)、以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读的操作不记录),只需追加文件但不可以改写文件,redis刚启动的时候会读取该文件,重新构建数据,redis重启的话,就根据日志文件的内容将写的指令从前到后执行一次以完成数据的恢复工作。

2)、开启aof需要修改 redis.conf 中的配置 appendonly no ,即关闭默认的持久化方式

3)、redis.conf 中的配置 appendfsync=everysec,出厂默认值,异步操作,每秒记录,若一秒内宕机,有数据丢失。

appendfsync=always:同步持久化,每次发生数据变更会被立即记录到磁盘,性能较差但是数据完整性比较好。

rewrite:

1、aof采用文件追加方式,文件会越来越大,为避免出现此种情况,新增了重写机制,当aof文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof

2、重写原理:AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据;重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

3、rewrite触发机制: Redis会记录上次重写时的aof大小,默认配置是当aof文件大小是上次rewrite后大小的一倍且文件大于64M时触发。

aof的优势和劣势:

优势: 同步持久化,appendfsync=always;

​ 每秒记录同步,appendfsync=everysec;

劣势:相同数据集的数据而言,aof文件要远大于rdb文件,恢复速度慢于rdb

​ aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同。

3、redis和aof概述

1)、RDB持久化方式能够在指定的时间间隔,对数据进行快照存储;

2)、aof持久化方式记录每次对服务器写的操作,当服务器重启的时候,会重新执行这些命令来恢复原始的数据,aof命令以redis协议追加保存每次写的操作到文件末尾,redis还能对aof文件进行后台重写,使得aof文件的体积不至于过大;

3)、同时开启两种持久化方式:

​ 在这种情况下,当redis重启的时候,会有限加载aof文件来恢复原始的数据,因为在通常情况下,aof文件保存的数据集要比rdb文件保存的数据集完整。

八、Redis有哪些架构模式?

1、单机:

在这里插入图片描述

问题:

1、内存容量有限 2、处理能力有限 3、无法高可用

2、主从复制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VLlMKhz-1572417855766)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571226298877.png)]

Redis的复制(replication)功能允许用户根据一个redis服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。

只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步给从服务器,从而保证主从服务器的数据相同。

特点:

有从服务器,主从服务器数据相同,能降低主服务读的压力。

问题:1、无法保证高可用;2、没有解决master写的压力

3、哨兵:

在这里插入图片描述

Redis sentinel 是一个分布式系统中监控redis主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:

监控(Monitoring):Sentinel会不断地检查你的主服务器和从服务器是否正常

提醒(Notification):当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。

自动故障迁移(Automatic failover):当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作。

特点:

​ 1、保证高可用

​ 2、监控各个节点

​ 3、自动故障迁移

缺点:主从模式,切换需要时间丢数据

没有解决master写的压力

4、集群proxy型

在这里插入图片描述

twemproxy是一个Twitter开源的一个redis和memcahce快速/轻量级代理服务器;它是一个快速的单线程代理程序,支持Memcached ASCII协议和redis协议。

特点:1、多种hash算法:MD5、CRC16、CRC32、CRC32a、hsieh、murmur、Jenkins

​ 2、支持失败节点自动删除

​ 3、后端Sharding分片逻辑 对业务透明,业务方的读写方式和操作单个redis一致

缺点:增加了新的proxy,需要维护其高可用

​ failover(故障转移)逻辑需要自己实现,其本身不支持故障的自动转移可扩展性差,进行扩容缩容都需要手动干预。

5、集群直连型

在这里插入图片描述

从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

特点 :

1、无中心架构(不存在哪个节点影响性能瓶颈),少了proxy层。

2、数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布。

3、可扩展性,可将线程安全扩展到1000个节点,节点可动态添加或删除

4、高可用,部分节点不可用时,集群仍可用。通过增加Slave做备份数据副本。

5、实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升。

缺点:

1、资源隔离性差,容易出现互相影响的情况

2、数据通过异步复制,不保证数据的强一致性

九、redis的过期策略以及内存淘汰机制

分析:比如你 redis只能存5G数据,可是你写了10G,那会删5G数据。怎么删的,这个问题考虑过没?

​ 还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有考虑过原因没?

回答

​ redis采用的是定期删除+惰性删除策略(+内存淘汰机制)

为什么不使用定时删除策略?

定时删除,用一个定时器来负责监控key,过期了就自动删除。虽然内存及时释放,但是十分消耗cpu资源。在大并发请求下,cpu要将时间 应用在 处理请求上,而不是删除key。因此没有采用这一策略。

定期删除+惰性删除是如何工作的?

定期删除,redis默认每隔100ms检查,是否有过期的key,有过期的key则删除。当然redis并不是每隔100ms将所有的key检查一次,如果这样做跟定时删除策略就没什么区别了。它是随机抽取进行检查。

因此,如果只采用定期删除策略,会导致很多key到时间但是没有删除。于是惰性删除就派上用场了。也就是说在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除。

采用定期删除+惰性删除就没其他问题了吗?

不是的,如果定期删除没有抽查到key,然后又没有请求该key,那么这样的key到期得不到删除。这样redis的内存压力越来越大。所以应当另外加上内存淘汰机制,内存淘汰算法使用lru算法

在redis.conf中有一行配置:

# maxmemory-policy allkeys-lru

意思是:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。

十、redis和数据库数据双写一致性的问题

通过先更新数据库,再删除redis缓存,再同步数据到redis。(其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。)

十一、如何应对缓存穿透、缓存雪崩和缓存击穿问题

分析:这种问题在一般中小型企业很难遇到,如果有大并发的项目,流量有几百万左右。这两个问题一定要深思熟虑。

1、缓存穿透

缓存穿透,是指查询一个数据库一定不存在的数据。

正常的使用缓存的流程大致是,数据查询先进行缓存查询,如果key不存在或已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。假如有恶意攻击,就可以利用这个漏洞,对数据库造成压力,甚至压垮数据库。

@Overridepublic Goods searchArticleById(Long goodsId){
Object object = redisTemplate.opsForValue().get(String.valueOf(goodsId)); if(object!=null){
//缓存查询没有命中 return (Goods)object; } //开始查询数据库 Goods goods = goodsMapper.selectByPrimaryKey(goodsId); if(goods!=null){
redisTemplate.opsForValue.set(String.valueOf(goodsId),goods,60,TimeUnit.MINUTES); } return goods;}

对缓存穿透的解决办法:如果数据库没有该数据,就将该数据的值设为null,放在缓存中设置过期时间为60秒。

redisTemplate.opsForValue.set(String.valueOf(goodsId),goods,60,TimeUnit.SECONDS);
2、缓存雪崩

缓存雪崩,是指在同一时间段,缓存集中过期失效。

例子:0点抢购,某波商品比较集中的放入缓存,假设缓存一个小时,抢购期间缓存集中失效,而对这批商品的访问查询,都落到了数据库上,对数据库产生周期性的压力波峰。 还有就是缓存服务节点的宕机,造成缓存雪崩的影响更大。

解决方法:对热门商品的缓存过期时间设置的长度些,对冷门的商品的缓存时间设置的短一些。

3、缓存击穿

缓存击穿,是指一个的key(键)非常热门,在不停地扛着大并发,大并发集中对这一个点进行访问,当这个key在失效瞬间,持续的大并发就击穿缓存,直接请求数据库,这就是我的缓存击穿。

解决办法:将该key设置为永不过期。

十二、如何解决redis的并发竞争key的问题(了解)

同时有多个子系统去set一个key。

1、如果对这个 key操作,不要求顺序

这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可。

2、如果对这个key操作,要求顺序

假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC,期望按照key1的value值按照 valueA–>valueB–>valueC的顺序变化。

这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下

系统A key 1 {valueA  3:00}    系统B key 1 {valueB  3:05}    系统C key 1 {valueC  3:10}

假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

其他方法,比如利用队列,将set方法变成串行访问也可以。总之,灵活变通。

十三、redis集群最大节点个数是多少?

redis集群预分配好16384个桶(哈希槽)

十四、redis的主从复制模型是怎样的?***

十五、redis集群会有写操作丢失吗?为什么?

redis并不能保证数据的强一致性,实际中就集群在 特性环境下可能丢失写操作。

十六、redis集群之间如何复制?

异步复制。

意思就是问 主从服务器怎么保持数据同步。

十七、redis如何做内存优化

https://www.jianshu.com/p/9830460fbb49

1、尽量使用hash数据结构、减少key的数量

尽可能的使用散列表,比如说有个用户对象,不要为这个用户的姓名,性别,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表中。

2、用好Redis提供的内存回收策略,根据使用的情况选择适当的回收策略,比如过期数据清除,设置数据过期时间。

3、用好了Redis提供的内存共享策略,服务器启动时,会自动创建0-9999的数字对象,其他地方使用,可以直接引用。

4、根据业务场景,考虑使用BITMAP来存储数据。

5、。。。。。

十八、redis回收进程如何工作?

一个客户端运行了新命令,添加了新的数据

Redis检查内存使用情况,如果大于maxmemory的限制,则根据设定好的策略进行回收(定期删除+惰性删除)

十九、redis回收使用的是什么算法?

默认LRU算法

redis的过期策略:过期删除+惰性删除策略

redis的内存淘汰机制: maxmemory-policy allkeys-lru,使用lru内存淘汰机制

二十、redis有哪些合适的场景

Session共享(单点登录)

页面缓存

队列

排行榜/计数器

发布/订阅

二十一、什么是分布式锁及正确使用redis实现分布式锁

https://www.cnblogs.com/cmyxn/p/9047848.html

1、分布式锁

分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。

2、实现原理

a、互斥性

​ 保证同一时间只有一个客户端可以拿到锁,也就是可以对共享资源进行操作。

b、安全性

​ 只有加锁的服务才能有解锁权限

​ 可能出现的情况就是a去查询发现持有锁,就在准备解锁,这时候忽然a持有的锁过期了,然后b去获得锁,因为a锁过期,b拿到锁,这时候a继续执行第二步进行解锁如果不加校验,就将b持有的锁就给删除了

c、避免死锁

​ 出现死锁就会导致后面的服务都拿不到锁,不能再对资源进行任何操作

d、保证加锁与解锁操作是原子性操作

3、如何实现分布式锁

​ 实现分布式锁的方式有很多,只要满足上述条件的都可以实现分布式锁,比如数据库,redis,zookeeper。

4、使用redis实现分布式锁

​ a、使用redis命令set key value NX EX max-lock-time实现加锁

​ b、使用 redis命令 EVAL实现解锁

加锁

Jedis jedis = new Jedis("127.0.0.1",6379);private static final String SUCCESS = "OK";/***加锁操作*@param key 锁标识*@param value 客户端标识*@param timeout	过期时间*/public Boolean lock(String key,String value,Long timeOut){
String var1 = jedis.set(key,value,"NX","EX",timeOut); if(LOCK_SUCCESS.equals(var1)){
return true; } return false;}

解读:

1、加锁操作:jedis.set(key,value,“NX”,“EX”,timeOut); 【保证加锁的原子操作】

2、key就是redis的key值作为锁的标识,value在这里作为客户端的标识,只有key-value都匹配才有删除锁的权利【保证安全性】

3、通过timeOut设置过期时间保证不会出现死锁【避免死锁】

4、NX,EX什么意思?

​ NX:只有这个key不存在的时候才会进行操作,if not exists

​ EX:设置key的过期时间为秒,具体时间由第5个参数决定

解锁

Jedis jedis = new Jedis("127.0.0.1", 6379); private static final Long UNLOCK_SUCCESS = 1L;/***解锁操作*@param key 锁标识*@param value 客户端标识*@return*/public static Boolean unlock(String key,String value){
String luaScript = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"; Object var2 = jedis.eval(luaScript.Collection.singletonList(key),Collection.singleTonList(value)); if(UNLOCK_SUCEESS == var2){
return true; } return false;}

解读:

1、luaScript这个字符串是个lua脚本,代表的意思是如果根据 key拿到的value跟传入的value相同就执行del,否则就返回0【保证安全性】

2、jedis.eval(String,list,list);这个命令就是执行lua脚本,KEYS的集合就是第二个参数,ARGV的集合就是第三个参数【保证解锁的原子操作】

上述就实现了怎么使用redis去正确的去实现分布式锁,但是有个小缺陷就是锁过期时间要设置为多少合适,这个其实还是需要去根据业务场景考量一下的


上面那只是讲了加锁与解锁的操作,试想一下如果在业务中去拿锁如果没有拿到是应该阻塞着一直等待还是直接返回,这个问题其实可以写一个重试机制,根据重试次数和重试时间做一个循环去拿锁,当然这个重试的次数和时间设多少合适,是需要根据自身业务去衡量的

/**  * 重试机制  * @param key 锁标识  * @param value 客户端标识  * @param timeOut 过期时间  * @param retry 重试次数  * @param sleepTime 重试间隔时间  * @return  */ public Boolean lockRetry(String key,String value,Long timeOut,Integer retry,Long sleepTime){
Boolean flag = false; try {
for (int i=0;i
你可能感兴趣的文章
简单的学生信息管理系统
查看>>
条理性搭建SSH框架
查看>>
整合Struts和Spring
查看>>
Hibernate和Spring的整合
查看>>
我的校招——同花顺
查看>>
Ego Surfing = Ego + Surfing
查看>>
13日cnblog会谈摘要
查看>>
尝试解决MT的Add to My Yahoo!的字符集问题
查看>>
MoreGoogle提供的网页缩略图服务
查看>>
每天到REFERER到我的网站上来的主页上去溜达一下
查看>>
北京羽毛球场地预定电话
查看>>
本周CNBlog例会:Grassland搜索的后台迁移
查看>>
Flickr的网络收藏夹服务
查看>>
BLOG="Better Listings On Google" ? Google BlogSearch上的 BSP索引收录量比较
查看>>
用sed批量替换文件中的字符
查看>>
九型性格心理测试 (From Ulla Zang荣格的个人性格测验题目)
查看>>
MT模板修改2则: 评论分段和firefox的缺省字体适应
查看>>
[MT] 3.32升级备忘
查看>>
MT 3.33发布: 安全漏洞修正
查看>>
给Blog加上雅虎通PingMe服务:和网站用户即时聊天
查看>>