经验整理-1-Redis-1-100-@

否使用过Redis集群,集群的高可用怎么保证,三种集群的原理是什么?

主从模式:若master挂掉,则redis无法对外提供写服务。一主多从,从库只读,同步接收主库数据
Sentinel哨兵模式:着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务
Redis Cluster:着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储

 

?为什么选Redis?为什么使用Redis集群?怎么安装集群Redis?

为什么选Redis:因为线程安全的
为什么使用集群:单机无法发挥多核CPU性能
怎么安装集群Redis:ruby来安装集群redis,选择redis-cluster集群搭建方案, 把集群数据库分成了 16384 个哈希槽;Redis集群使用CRC16对key进行hash,当我们的存取的key到达的时候,redis会根据crc16的算法得出一个结果,然后把结果对 16384 求余数,对应一个编号在 0-16383 之间的哈希槽(每一个节点对应着一部分哈希槽)
安装过程:
1.
建起一台 redis.conf设置为启动集群模式的redis实例,比如9001端口,然后把 9001 实例 复制到另外五个文件夹中(cp -rf ),唯一要修改的就是 redis.conf 中的所有和端口的相关的信息.
2.启动9001-9006六个节点:比如,/usr/local/redis/bin/redis-server  /usr/local/redis-cluster/9006/redis/etc/redis.conf
3安装 ruby 和相关接口
4.使用 ruby命令来创建集群模式,比如,设置主从复制比例为1:1,前三个为主节点端口,后三个为对应从节点端
5。程序里需要配置cluster:对应的节点6个端口
四、原理:

?动态扩容、增加节点和减少节点,重新分配槽大小?

Redis 集群中总共有且仅有 16383 个 solt,默认情况会给我们平均分配,当然你可以指定,后续的增减节点也可以重新分配

Redis集群支持事物吗

Redis集群不支持事物,只有节点内部才支持。如果想实现,只能靠三方插件,比如lua来写脚本实现事物、、

?redis如何使用二级缓存?

mvn引入Ehcache工具类,通过Redis+ehCache实现两级级缓存,相当于在前面加了ehCache做为一级缓存,查时优先查它,如果不存在,再查redis(同时更新一级缓存ehCache),最后查不到再查mysql(同时更新二级缓存redis)

缓存雪崩是什么?解决方案?(并发查数据库,搞坏数据库),缓存击穿呢(恶意攻击一条)

缓存击穿缓存雪崩有点像.缓存雪崩是因为大面积的缓存失效,打崩了DB,而缓存击穿不同的是缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个完好无损的桶上凿开了一个洞。)
答:当某个key失效的时候,有大量线程来访问查询大量原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机,造成系统的崩溃

答:1:在缓存失效后,通过加锁(分布式锁zk)或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数库和写redis缓存,其他线程等待。(影响吞吐量)
1:在缓存失效后,通过队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数库和写redis缓存,其他线程等待。(最好的方法)
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。(每个Key的失效时间都加个随机值)
还有一种方案:
事前:尽量保证整个redis集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
事中:本地ehcache缓存+ hystrix限流&降级,避免MySQL崩掉
事后:利用redis持久化机制保存的数据尽快恢复缓存

答:巧记:三个区别
1)缓存雪崩和缓存击穿都是redis过期查mysql,区别是
缓存雪崩是正常业务大量不同key同时到期,才都去查mysql。---解决
缓存击穿是被恶意攻击频繁查询一条数据,这条数据刚好到期;---解决
2)缓存穿透是redis没有,绕过redis穿透到了mysql----解决

通用的解决方案:
 

setRedis(Key,value,time + Math.random() * 10000);

4。热点数据永远不过期(哈哈,不太可取)
5:Ehcache做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期(此点为补充)

举个简单的例子我了解的,目前电商首页以及热点数据都会去做缓存 ,一般缓存都是定时任务去刷新,或者是查不到之后去更新的,但是有一个问题。如果所有首页的Key失效时间都是12小时,中午12点刷新的,我零点有个秒杀活动大量用户涌入,假设当时每秒 6000 个请求,本来缓存在可以扛住每秒 5000 个请求,但是缓存当时所有的Key都失效了。此时 1 秒 6000 个请求全部落数据库,数据库必然扛不住,它会报一下警,真实情况可能DBA都没反应过来就直接挂了。此时,如果没用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。这就是我理解的缓存雪崩。同一时间大面积失效,那一瞬间Redis跟没有一样,那这个数量级别的请求直接打到数据库几乎是灾难性的,你想想如果打挂的是一个用户服务的库,那其他依赖他的库所有的接口几乎都会报错,如果没做熔断等策略基本上就是瞬间挂一片的节奏,你怎么重启用户都会把你打挂,等你能重启的时候,用户早就睡觉去了,并且对你的产品失去了信心,什么垃圾产品。

缓存穿透是什么?解决方案?(并发查数据库,搞坏数据库)?

答:Redis缓存穿透:比如用户查询数据,本身没有创建过数据,在缓存中找不到,比如,id为“-1”每次都要去数据库再查询一遍(低效地查两次,之后还频繁来查,这样就多次穿过缓存,给数据库增加压力),然后返回空。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题
答:

1、如果查询数据库也为空,直接设置一个默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库,这种办法最简单粗暴。(比如我们都要去查用户信息是否存在,不存在,就标记为notfound,存入缓存,下次再找不到就返回这个缓存里的标记作为找不到的标识,不会查数据库)
注意:再给对应的ip存放真值的时候,需要先清除对应的之前的空缓存。
2、最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
 

热点key是什么?解决方案?(并发存redis,搞坏redis)

答:热点key即失效热点访问并发:比如,当某个key失效的时候,有大量线程来访问查询,刚好都从数据库回来查到数据同时去redis构建缓存导致负载增加,系统崩溃

答:解决办法:

①使用锁,单机用synchronized,lock等分布式用分布式锁zk,只允许一个线程查询数库和写redis缓存

②缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。

③在value设置一个比过期时间t0小的过期时间值t1,当t1过期的时候,延长t1并做更新缓存操作。

4、过期时间后面带上一些值

?你们如何解决分布式定时任务幂等性重复性的?

redis分布式锁,发现存在了,就跳过

?redis分布式锁原理与实现?

过程分析:

1、A通过setnx(lockkey,currenttime+timeout)命令,对lockkey进行setnx,将value值设置为当前时间+锁超时时间;
2、如果返回值为1,说明前面没有人,也就是没有其他用户拥有这个锁,A就能获取锁成功;
3、A完成后,要释放拥有的锁,del(lockkey),也就是删除redis中该锁的内容,接下来的用户才能进行重新设置锁新值。
 

?redis定时取消订单还原库存,延时操作的原理(订阅Pub/Sub频道 )?

在订单生产时,设置一个key,同时设置10分钟后过期, 我们在后台实现一个监听器,监听key的实效,监听到key失效时将后续逻辑加上。 当然我们也可以利用rabbitmq、activemq等消息中间件的延迟队列服务实现该需求。

?点赞排名、好友列表的原理(set--sismember/sadd )?

一、点赞排名还是使用zset好一点,如果没有排名也可以用set;
二、好友列表只需要考虑去重,用set;如下:

sismember 我的ID 好友ID   (如果存在会返回好友信息,就不要在插入了)

 

?redis对账找差异的原理(set--sinterstore、sdiffstore)?

1、分别得到我方的交易集合set1和三方的交易集合set2。
sadd {account}:localSet  订单信息1 订单信息2 ,表示给我方添加两个订单进redis这个{account}:localSet集合。{account}这样写是为了集群到同一个槽里,集群只看{}里面的值。
sadd {account}:outerSet  订单信息11 订单信息22 同理,和到第三方。

2、然后使用redis的sinterstore功能对两个SET集合进行交集得到sinter1集合,
sinterstore 新名称sinters1 {account}:localSet {account}:outerSet 

3、把三方账务集合与我方记录集合分别和交集结果比较,
sdiffstore 新名称localDiff {account}:localSet sinter1,表示得出我方单边多的。
sdiffstore 新名称outerDiff {outerDiff}:outerSet sinter1,表示得出第三方单边多的。

4、然后把差集拿出来,
smembers localDiff 得到我方单边多的。
smembers outerDiff 得出第三方单边多的。

逐个对比一下两个集合具体错在哪,记录差异账,等待人工审核。如果是我方单边多账,可能是三方还未结算,这种会等第二天对账发现三方单边多账,再来查一下前一天的对账结果里是否有多的记录,如果有就抹掉,没有就是差账(可能是漏记)。

 

?redis实时排行榜的原理(zset--zunionstore)?

举例,商品收藏月排行榜实现。
(首次如果需要添加,使用语句:zadd rank:202001 3 商品ID1 5 商品ID2 ,表示给两个商品初始3和5的收藏数)
1、创建收藏的时候,集合名称=商品_当前年月,增加减少语句(不存在就新增):
zincrby 集合名称 该成员的变化量 集合成员姓名
比如 
zincrby  rank:202001 1 商品ID1   ,表示该商品ID1累加一个收藏,如 zincrby rank:202001 1 商品ID2   ,表示该商品ID2增加一个收藏
2、获取自然月排行榜前10的方法如下,zrange  rank:202001 0 9 withscores,倒序;zrevrange rank:202001 0 9 withscores,升序
3、如果要统计日榜,就重新建一个有序集合,名称以rank:20200101开头。
如果是周榜,就得算是哪周了,算到哪周的方法:new SimpleDateFormat("yyyy-MM-dd").applyPattern("W").format(date);//算一月中的第几周
4.如果是实时周期性,从当前时间至前面一周内,那就得用:zunionstore 放入新集合的名称 成员个数 原集合1名称 原集合2名称 原集合3名称 weights 1 1 1 ,表示合并3个原集合至新集合里,权重都是1(weights 权重可以不设置,默认就是1),每个集合中相同的商品ID因为是唯一的,所以对应的收藏数会加相的
计算给定的一个或多个有序集的并集,并存储在新的 key 中
实时周榜获取方法:
1)zunionstore rank:last_week 7 rank:20200101 rank:20200102 rank:20200103 rank:20200104 rank:20200105 rank:20200106 rank:20200107 WEIGHTS 1 1 1 1 1 1 1
2)实时月榜获取方法,也用zunionstore ,把最近一个月的每天的给并集在一起。
如果要精确到秒的话,那只能按秒存了,再并集秒

?redis秒杀减库存,集群高并发的原理(列表(队列性质)list--rpush、lpop,Hash---hset )?Hash(商品及规格-用户-数量)

高性能系统的优化原则:写入内存而不是写入硬盘,异步处理而不是同步处理,分布式处理
一、最初版,用户可以买多单。
1、ZK实现的分布式计数器DistributedAtomicInteger存库存,到了100个就拦截
2、list存抢购成功的用户信息及商品序号。
rpush key value,插入秒杀请求。然后快速返回。
lpop key,(异步)读取秒杀成功者,依次做下单后续处理。

二、升级版,防刷单,限用户每人一单
1、list存库存,将库存100先放进一个redis的list结构中,来一个用户pop一个,直到最后pop出第100个秒杀结束。提示“抢光了!”pop成功则进入下单流程,插入新订单到数据库,初始状态0未支付(0:未支付 1:已支付,2:30min已过期)。对于过期的则归还库存到redis的库存list中等待pop。命令如下:
rpush key value,插入库存。
lpop key,减库存。
2、Hash来存放抢购成功的用户ID,防止重复抢购刷单(商品key->(用户key,val))。等到,命令如下:
hset  商品key 用户key MapValue  (如果存在会覆盖,但返回0)
3、list存抢购成功的用户信息及商品序号。(最好用mq来异步处理,统一消费,商品类型区分不同商品
rpush key value,插入秒杀请求。然后快速返回。
lpop key,(异步)读取秒杀成功者,依次做下单后续处理。

(string类型如果value是Int等数据类型,可以用Incre key 1 //实现加1
,decrby key 1 //是减)

?redis购物车基本功能,集群高并发的原理(列表(队列性质)list--rpush、lpop,Hash---hset )?Hash(用户-商品及规格-数量)

spu(商品)和sku(规格单品)

hset 存/修改:hset appid:openid:cart 3:1 10  (hset 用户ID相关  spu_id:sku_id   数量)
hgetall 取所有:hgetall appid:openid:cart
hdel 删除:hdel appid:openid:cart 3:1
hincrby 增加,减少:hincrby appid:openid:cart 3:1 -1

?发布与订阅(pub/sub)?

订阅一个频道:subscribe   频道名
通过命令publish向频道发送信息:publish   频道名    “消息”


如果对方究极TM追问Redis如何实现延时队列?(zset--zadd、zrangebyscore)


延时队列:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理
zadd  某点前的时间戳long值 key 
zrangebyscore key -inf 某点前的时间戳long值


zrange 和zrangebyscore 区别:
zrange 是指索引:zrangeby key 0 10
zrangebyscore 是指值:

zrangebyscore key -inf 时间戳long值//小于等于
zrangebyscore key -inf 时间戳long值//小于等于

 

?redis持久化原理?结合 redis 宕机了再重启说明一下处理过程?()

一、持久化原理:(启动时会先检查AOF文件是否存在,如果不存在就尝试加载RDB)。
redis 持久化的两种方式,RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化,恢复速度快,丢失多。AOF:AOF 机制对每条写入命令作为日志,,恢复速度快慢,日志文件大,丢失少。
一般采用同时开启开启两种持久化方式,优先用 增量日志文件AOF 来保证数据不丢失,作为数据恢复的第一选择; 其次AOF损坏不可用时,用数据RDB快照快迅恢复大体数据,避免所有数据丢失;
二、redis 宕机了再重启:如果 redis 宕机重启,自动从磁盘上加载增量日志文件AOF恢复数据,如果AOF损坏或恢复故障,会从RDB日志快照里拿到数据恢复。
(在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。?)

?redis 宕机怎么办,怎么保持数据一致性?平时没宕机呢,怎么保持数据一致性?

答1:1)redis有持久化,启动时会先检查AOF文件是否存在,如果不存在就尝试加载RDB,恢复数据。
2)如果你的系统不是严格要求缓存+数据库必须一致性的话,就重启就好了。
答2:
1)如果你的系统不是严格要求缓存+数据库必须一致性的话,不用优化。定时任务扫秒会重置。或cannal订况MYSQL更准
2)redis读行串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致
的情况----导致系统的吞吐量会大幅度的降低,加机器了

Redis的同步机制了解么?

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录命令(好像是AOF)同步到复制节点进行重放就完成了同步过程后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog

数据怎么主从同步的呢?

slave启动,会发送一个psync命令给master ,
1)如果是第一次连接到master,他会触发全量复制。master就会启动一个线程,生成RDB快照,同步给从节点。slave写入RDB至本地。
2)master会把新的写请求命令,同步给slave。

Redis里面所有的key过期时间了怎么处理的?(清除策略)

一、Redis删除策略(过期策略),是有定期删除+惰性删除两种。
定期删除,好理解,默认100s就随机抽一些设置了过期时间的key,去检查是否过期,过期了就删了。
惰性删除,见名知意,惰性嘛,我不主动删,我懒,我等你来查询了我看看你过期没,过期就删了还不给你返回
二、内存淘汰策略机制!
官网上给到的内存淘汰机制是以下几个:
巧记:最久内存随机频率
           1:内存不够,写就报错
            2:通过LRU算法驱逐最久没有使用的键
            3:从设置了过期时间的键集合中驱逐最久没有使用的键
            4:从所有key随机删除
            5:从过期键的集合中随机驱逐
            6:从配置了过期时间的键中驱逐马上就要过期的键
            7:从所有配置了过期时间的键中驱逐使用频率最少的键
            8:从所有键中驱逐使用频率最少的键
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。

 


  2.1 redis 客户端

    答:Lettuce (springboot默认使用的客户端),Jedis
    jedis和lettuce有什么区别:
    jedis采用的是直连redis server,单jedis实例时,是线程不安全的(发送命令和获取返回值时使用全局变量RedisOutputStream和RedisInputStream)。要使用连接池pool(配置引用jedis.pool即可),这样每个线程单独使用一个jedis实例。由此带来的问题时,如果线程数过多,带来redis server的负载加大。有点类似于BIO的模式。

lettuce采用netty连接redis server,实例可以在多个线程间共享,不存在线程不安全的情况,这样可以减少线程数量。当然,在特殊情况下,lettuce也可以使用多个实例。有点类似于NIO的模式

性能差不多,jedis连接池比lettuce弱一点

 redis高危命令

    答:keys flushdb (清空数据库) flushall (清空所有记录)


  2.3 redis清除策略
  https://www.cnblogs.com/rinack/p/11549362.html
  删除策略:定期删除+惰性删除(使用时,检查是否过期,过期删除)
  淘汰策略:1:内存不够时,写时,返回错误
            2:通过LRU算法驱逐最久没有使用的键
            3:从设置了过期时间的键集合中驱逐最久没有使用的键
            4:从所有key随机删除
            5:从过期键的集合中随机驱逐
            6:从配置了过期时间的键中驱逐马上就要过期的键
            7:从所有配置了过期时间的键中驱逐使用频率最少的键
            8:从所有键中驱逐使用频率最少的键

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章