Redis常见问题

1 篇文章 0 订阅

参考文章:

史上最全Redis面试题及答案 - 拍片哥 - 博客园

https://www.cnblogs.com/jasontec/p/9699242.html

Redis 3.2.1集群搭建 - sunney - 博客园 (集群搭建)

推荐视频

【中华石衫】16_先平易近人的随口问你一句分布式缓存的第一个问题_哔哩哔哩_bilibili

中文网

redis中文官方网站

列表 — Redis 命令参考

在线测试

Try Redis

图形化工具

福州青格软件有限公司,软件开发,软件外包,APP开发,移动应用开发,treesoft数据库管理系统官网,数据同步,数据汇聚,数据统计分析,treeDMS,treeNMS

推荐TreeNMS,可以监控服务器状态。

Redis开发运维实践指南

Redis开发运维实践指南_w3cschool

IT学院

适用场景

减轻数据库压力,提高响应速度,加大吞吐量,线程安全控制。

对象存储,热点数据缓存,静态数据缓存,高频变化数据缓存(点击数等),分布式session,分布式/集群共享数据,分布式限流,时效性数据,限时功能/业务,分布式锁,计数器,秒杀,BitMap统计状态1/0,布隆过滤器,交/并/差集合,排行榜,最新动态(SortedSet +时间戳),消息队列List,订阅发布(调度、广播),HASH购物车(hincr实现线程安全数量更改)

https://blog.csdn.net/qq_40233503/article/details/88562909

阿里云 Redis 开发规范

阿里redis规范(转自别人的公众号)_涂作权的博客-CSDN博客

使用建议

使用Hash减少内存消耗,为key设置TTL、配置回收策略控制总内存占用。
单KEY不要过大,阻塞影响性能。
使用mget、管道、Lua减少通讯次数。
mget\hgetall\range\lua\管道...处理大量数据时最好分批获取,避免阻塞其他业务。100开始有明显性能削减。
热key可以在业务代码中做一级缓存,缓解redis压力。
尽量不要让Redis机器物理内存使用超过实际内存总量的60%,因为持久化时redis需要额外内存。
如果服务器有其它进程内存不足,可能导致系统杀死Redis以释放内存。
持久化会fork子线程,产生快照。瞬时性能、内存消耗大。
定时任务执行RDB,控制频率。避免单机多REDIS同时持久化耗尽服务器资源。
若对性能要求高,可以关闭master的持久化,由从机执行持久化。
持久化文件定期做冷备份,避免丢失,或主机误操作flash。
从机内存不能小于主机避免丢失。
主从同步要避免全量复制。
做好配置,避免脑裂后的数据不一致。
Redis实例的客户端连接数不超出5000。

提高整体吞吐量,增加读写能力

使用Cluster集群或多节点代理,将同一个业务的不同key打散到多个节点中,同时分散读压力和写压力,尽可能的将单线程串行化转换为多线程并行执行。

缺点:Cluster中lua只能操作同一节点的key。

测试

阿里云ECS 1核1G。SpringBoot+RedisTemplate。 以第二次执行为参考,因为首次执行有Bean初始化、创建链接等开销。

java对象序列化为json内容如下:

{"id": "1013","rq": "2020-03-08","time": 1583673941552,"xsy": "","shl": 7,"dj": 2896.9317188353816,"je": 20278.5220318476712}。

STRING:

查询测试:单key查询10ms以内。

ZSET:

新增测试:

从0插入10w的json数据,每次单线程1W条,执行10次,单次稳定在1分10秒左右。完成后内存一共增加16M。

查询测试:10w ZSET

zrangebyscore查询返回5000条数据用时4s,查1000条250ms,查20条20ms。

测试结果:

1、redis适合做缓存,可以将数据库中join好的结果以json缓存到redis中,避免每次请求都访问数据库做关联后再转json。

2、大KEY操作要考虑缩小范围+分页,避免阻塞。

连接池配置造成TPS瓶颈

从一次redis 秒杀场景性能优化看redis常规应用_swordwith的博客-CSDN博客

并发用户数到达一个数量时,发现系统整体TPS不再向上增长,且随着用户数增加 TPS下降,但redis服务器的CPU和内存利用率都不高(基本都在10%以内),AP的CPU和内存利用率也并未到达瓶颈。这个客户端连接数未随着压力的增长进行新增就是问题的关键所在。

原因:客户端连接池MaxTotal配置过小。

单指令线程安全

因为是单线程串行执行,所以自然地保证了单一指令的原子性,可见性,有序性。保证了线程安全执行。

复合命令需要配合事务/LUA保证原子性。

Redis如何实现单线程支持高并发?

C语言实现,纯内存操作,epoll多路复用

下图左边IO部分,ServerSocket通过 epoll多路复用非阻塞监听Socket,提交给任务QUEUE。

右边事件分派器消费queue处理不同的任务纯内存操作 ,且事件分派器是单线程没有线程上下文切换,所以Redis才叫单线程模型,并不是所有工作都是一个线程完成。

640?wx_fmt=png

Redis是单线程的,如何提高多核CPU的利用率?

一个机器通过不同端口开多个实例

Redis有哪些数据结构?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet,BitMap位操作(布隆过滤-查重过滤),

HyperLogLog :总数统计count,用极小的空间存储内容用于count总数。缺点:无法访问HyperLogLog中保存的内容,且count有极小误差

Geo地理位置:存储经纬度,计算两地距离,查找指定范围内结果(实现附近搜索)

Pub/Sub 发布订阅 :用于信息推送/广播。redission中实现并发组件的唤醒功能。

Redis 常用指令

Redis命令中心(Redis commands) -- Redis中国用户组(CRUG)

命令即使key不存在也会正常返回失败值不会报错,返回如0、null等。

甚至有些命令没有key也可以正确执行,比如:incr,sismember。

利用此特性可以让所有Set/Add命令执行时,再去创建对应KEY,而不用先创建key或判断key存在。类似懒加载。

Info: 服务器信息使用状态

String(O1):set,set nx px,get,mget,decr,incrby,append,scan key [begin] match **  [count],bitmap

Hash(O1):hset,hsetnx,hmset,hget,hgetall,hincrby,hlen,hkeys, hvals,hscan key [begin] match *key* [count]

Set:sadd, srem ,spop, smembers, sismember, scard, sinter, sunion, sdiff (交并差,以redis中保存的文本判断相等,差以第一个集合为主),sscan;

Sorted Set (skiplist): 有序,score可以重复,value不能重复。

zadd,zrange 以index查(top\分页\范围) limit,zrem,zcard,zrank,zscore,zincrby,zunionstore(合并权重-多条件排序),ZRANGEBYLEX(模糊查询、分页),ZRANGEBYSCORE 以score查(正/逆排序、范围查询、滑动窗口、分页、TOP)

List (双端链表): lpush,rpush,lpop,rpop(非阻塞),blpop,brpop(阻塞),lrange(分页)

HyperLogLog: 去重计数器  PFADD,PFCOUNT,PFMERGE

hash-max-zipmap-entries 512  //entry的总数不能大于512,否则就采用hashmap/链表/跳跃表结构存储
hash-max-zipmap-value 64 //每个entry值的长度不能大于64个字节,
list-max-ziplist-entries 512
list-max-ziplist-value 64
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

BitMap结构模型

byte[]={0,1,1,1,0,0.....} 是一个bit字节数组(默认都是0),offset偏移量是数组下标。bit值只有0/1。

setbit  key  offset  value

getbit  key  offset 

bitcount key   [下标区间]  统计此BitMap区间中1的数量。

bitop 逻辑计算,O(N),可实现多bitmap之间 AND 、 OR 、 NOT 、 XOR。(灵活运用逻辑计算可以解决多种问题)

BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,所有key的下标[i]全是1则destkey[i]等于1,并将结果保存到 destkey 。
BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,所有key的下标[i]中有一个是1则destkey[i]等于1,并将结果保存到 - destkey 。
BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,所有key的下标[i]中有不同则destkey[i]等于1,并将结果保存到 destkey 。
BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
除了 NOT 操作之外,其他操作都可以接受一个或多个 key 作为输入。
redis使用场景之位操作(大数据处理) - 仅此而已-远方 - 博客园

分布式缓存Redis之bitmap、setbit_ZhangRui的博客-CSDN博客_redis setbit

案例1:布隆过滤器

案例2:火车票检查。火车CT1途经3站,总共5个座位。设计一个选票检查系统,查看各站余票和座位是否可选。(此案例只是开阔思维,不是实际应用);

创建3个bitmap代表此火车CT1的3站,每个bitmap中5个下标空间代表座位号,形成一个[车站][座位号]二维bool矩阵,1代表此站此座位未售出。

bitmap_ct1_s1={1,1,1,1,1}

bitmap_ct1_s2={1,1,1,1,1}

bitmap_ct1_s3={1,1,1,1,1}

此时卖出一张2号座位起始s1至s2站的票,则将2号座位s1至s2的途经站全部改为0。

bitmap_ct1_s1={1,1,0,1,1}

bitmap_ct1_s2={1,1,0,1,1}

bitmap_ct1_s3={1,1,1,1,1}

统计s1站剩余总票数(座位):横向统计。 bitcount bitmap_ct1_s1 0 -1 。

统计s1上车至s3下车的剩余票数(可选座位):纵向统计。将s1 ~ s3 3个bitmap做and(与)操作 = {1,1,0,1,1},结果map中为1的就是剩余(可选)座位,可以通过bitcount统计剩余座位数。

Redis数据结构===Java数据结构

数据结构的特点是相通的,redis中的业务实现,同样可以用java中的数据结构实现,反之同理。

redis在数据量小时,集合底层使用ziplist、quicklist。

string = Map<String,String/Number> 

list (双端链表)= Map<String,LinkedList>  替代:ConcurrentLinkedQueue

hash (hashtable:数组+链表)= Map<String,HashMap> 替代:ConcurrentHashMap

set= Map<String,HashSet>

zset(skiplist)= Map<String,ConcurrentSkipListSet>    替代:TreeSet/TreeMap/ConcurrentSkipListMap

Redis6客户端本地缓存

Redis6 将「客户端缓存」称为「Client Key Tracking」,表示客户端对指定的 Key 感兴趣,通过Pub/Sub 它会订阅这些 Key 的修改通知,如果 Key 发生了变化,客户端会立即收到一个「缓存失效」通知。紧接着客户端就会清空并重建本地缓存。

使用 Client Key Tracking 的原则就是读多写少,比如业务系统使用的全局配置参数

  1. 变化频繁的 Key 不要本地缓存,缓存刷新过于频繁

  2. 读频率低的 Key 不要缓存,缓存意义不大

旧的客户端本地缓存失效方案

通过 MQ/ZK通知其他服务节点,本地缓存失效。

Redis 用SCAN代替Keys命令, 查询key列表避免阻塞

http://redisdoc.com/key/scan.html

redis是单线程执行命令,在生产环境中 Keys *,因为key太多导致阻塞几分钟。

这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

扫描集合s1 ,返回第一个值为已扫描游标,返回0说明已经全部扫描,返回大于零整数说明本次未扫描完成,下次扫描需要从此游标位置开始

sscan s1 0

1) "0"
2) 1) "1"
    2) "2"
    3) "3"

Redis命令:scan实现模糊查询_琦彦-CSDN博客_redis scan

Redis Scan的使用方式以及Spring redis的坑_张哈希的博客-CSDN博客

Redis对Key实现条件过滤,模糊查询

scan 命令 -- Redis中国用户组(CRUG)

当HASH、SET非常大时,不要使用MGET、HGETALL。使用SCAN避免卡死。

SCAN cursor [MATCH pattern] [COUNT count]

  • SCAN 命令用于迭代当前数据库中的key集合。
  • SSCAN 命令用于迭代SET集合中的元素。
  • HSCAN 命令用于迭代Hash类型中的键值对。
  • ZSCAN 命令用于迭代SortSet集合中的元素和元素对应的分值
//扫描string 不用指定key
scan 176 MATCH *11* COUNT 1000

//扫描set中元素 指定set的key
sscan myset 0 match f*  

SORT排序

sort 命令 -- Redis中国用户组(CRUG)

Sorted Set 多重条件排序

方案一:zunionstore将多个排序zset合并

方案二:zset的score通过正负、整数位、小数位三个维度做成多条件。

              比如:整数位做主条件、小数位做副条件。

Sorted Set 定时任务队列

我是如何用redis做实时订阅推送的 - 浮云骑士LIN - 博客园

Sorted Set Score精度丢失

redis zset score精度问题_ParallelUniverses的博客-CSDN博客

可以将浮点数值转化为int再存储。例如:3.14=》314

Redis中保存过多的KEY,需要整理规划。

可以在设置key的时候,将同类关系的KEY 加入相同的 "前缀:" ,方便归类管理。

例如:set p:sss 123  ; set p:f:aaa 123

Key设计

合理的key设计,搭配合理的数据结构可以有利于查询

例1:抢购活动在2019-10-01的10点开始11点结束,可以将时间信息放入key中 shop:2019100110。

         代码中找redis的活动时按照同样格式拼上当前时间,找不到key说明不在活动时间。

         当然也可以在redis中已hash/json保存活动时间信息,在代码中解析判断。

Redis中保存数据库表、对象?

方案1:利用HASH结构,KEY=表名,子KEY=各行主键id,VALUE=此行数据json对象。整张表保存到一个HASH中。

方案2:利用HASH结构,KEY=表名:索引id,子KEY=列名,VALUE=属性值。相对于方案1,方案2把每一行数据独立保存为一个HASH。使得单个KEY不会过大,集群时无法数据均匀分布  ,而且更新不好控制。

方案3:利用String结构,KEY=表名:id:属性名。使用MSET+MGET来一次性获取同一行所有属性。

方案4:利用String结构,KEY=表名:id 。保存json、序列化对象。

不经常被修改的数据适合直接保存json。经常修改的对象,最好把属性一一对应到redis的数据结构,方案2-3。

数据量过大(千万)的话HASH容易造成大KEY和热KEY问题,方案1性能影响很大,集群模式最为明显,因为hash不会被分片。

 

join关系

关系型数据不是redis的强项,特别复杂的关系可以使用mongo

1、直接在json中存储关联信息

2、父/子数据中记录关联数据的key值。

3、保存中间数据,记录 父key- 子key关系。

4、SET保存数据/关系,做交并差。类似3

例1:

mysql> select * from tag;
+---------+---------+
| tagname | book_id |
+---------+---------+
| ruby    |       1 |
| ruby    |       2 |
| web     |       2 |
| erlang  |       3 |
+---------+---------+

一个tag一个set,保存bookid。
sadd tag:ruby 1
sadd tag:ruby 2
sadd tag:web 2
sadd tag:erlang 3

book:tag  一对多关系
select b.name, b.author  from tag t1, tag t2, book b
where t1.book_id = t2.book_id and b.id = t1.book_id
and t1.tagname = 'web' and t2.tagname = 'ruby' 

set交并差:实现 and  union  notin 
ruby and web的书:      redis.sinter("tag.web", "tag:ruby")
in ruby not in web的书:redis.sdiff("tag.ruby", "tag:web")
ruby union web的书:    redis.sunion("tag.ruby", "tag:web")

Redis中实现关系型查询(范围查询,模糊查询等...)

Redis中的关系查询(范围查询,模糊查询等...)_weixin_34178244的博客-CSDN博客

Redis中的关系查询(范围查询,模糊查询等...) - SummerChill - 博客园

将数据库表存入redis(k=主键),并将需要查询的字段单独保存到redis不同的数据结构中其value为主键,相当于用字段/属性为数据主键建立索引

先在索引中按条件查出主键。

String K-V为主数据结构,k=主键(id),v=json。Set、Hash、Zset .....相当于索引key、子key、Score为主数据json中某个属性值,value是主键k值id。

zset:name 通过ZRANGEBYLEX实现模糊查询  where name like '%x%'。(只适合英文/数字)

                  SCAN也可以实现模糊查询,但是SCAN扫描的是整个库的Key范围太大,且Key中要带有关键字。性能不如zset。

hash:dept 实现指定条件查询   where dept=“xxx”,

zset:age  实现范围查询 where age>27 等问题。

zset:birthday  实现通过日期区间范围查询where birthday>20000101  and  birthday< 20000201

join 交并差关系通过set实现,请看上一个问题的示例。

可以通过索引查到满足条件的主键,再去K-V中查json。

模糊查询:ZRANGEBYLEX详解:Redis集合ZRANGEBYLEX排序搜索详解_qq_37058039的博客-CSDN博客_zrangebylex

Mysql数据通过linux管道导入redis

原文:将mysql表数据批量导入redis zset结构中 - Qxun_dream - 博客园

这种方法比通过程序查数据在插入redis快很多倍。

HASH有优缺点?

优点:1 整体方便管理 2 避免key冲突  3 内存占用非常小,因为子键不用保存TTL等信息

缺点:1 子KEY不独立,不好单独操作 2 集群时无法数据均匀分布  3 大key风险

 Redis如何做内存消耗优化

答:尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的所有信息存储到一张散列表里面.

TTL

1 建议一般Key都要设置TTL,避免内存超载。Redis通过 定期随机删除(默认100ms) + 惰性删除(操作key时判断) 来实现TTL

2 灵活运用TTL特性,实现限流、事件开始结束标志、限时令牌等等功能

一个Redis实例最多能存放多少的keys

每个实例至少存放了2亿5千万的keys。一个key或是value大小最大是512M。换句话说,Redis的存储极限是系统中的可用内存值。

常见题目

2018整理最全的50道Redis面试题!_剑雪封喉-CSDN博客

Pipeline有什么好处,为什么要用pipeline?

可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。

优化操作过于频繁的Redis?

Pipeline/LUA

使用Pipeline合并请求,减少TCP链接次数。客户端允许将多个请求一次发给服务器,过程中而不需要等待请求的回复,在最后再一并读取结果即可。

  • pipeline机制可以优化吞吐量,但无法提供原子性/事务保障,而这个可以通过Redis-Multi等命令实现。
  • 部分读写操作存在相关依赖,无法使用pipeline实现,可利用Script机制,但需要在可维护性方面做好取舍

redis通过pipeline提升吞吐量 - 美码师 - 博客园

修改配置

我们修改配置文件或者在命令行直接使用config set修改,在用config rewrite同步到配置文件。通过客户端修改好处是不用重启redis直接生效。 

什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?

网文:redis系列--redis4.0深入持久化 - W-D - 博客园

特性和触发redis系列--redis4.0深入持久化 - W-D - 博客园

持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。

Redis 提供了两种持久化方式:RDB(默认) 和AOF 

Redis4.0之后有了混合持久化的功能,将RDB的全量和AOF的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。

RDB:

rdb是Redis DataBase缩写,记录存储状态

功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数。

当满足配置的保存条件或手动执行save时触发fork子线程生成内存快照,替换上一个RDB文件。

fork子线程性能开销较大,当单机多redis实例时,必须错开各个实例的RDB时间,避免卡死。

触发:

1  conf配置触发条件。

2 定时任务执行bgsave,频率可控。

AOF:

Aof是Append-only file缩写,记录执行命令类似日志,每秒更新。AOF文件可以修改/修复。不推荐单独使用。

每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作

aof写入保存:

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件

SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

刷盘策略fsync的设置:

这个设置一般有3种方式:always、everysec、no

always就意味着每条命令都会同步刷盘,对磁盘IO消耗较大。no表示不刷盘不持久化。

建议设置everysec每秒更新。当这一次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。在fsync时写操作会阻塞。

重写:

由于AOF记录每一条指令所以会一直增大,需要间隙重新整理bgrewrite。bgrewrite也是一个既耗cpu又耗磁盘IO的操作,单cpu利用率最高可达100%。可以配置条件满足后触发重写aof文件,去掉无效冗余指令,重写时redis以当前存储内容生成aof指令文件。

auto-aof-rewrite-min-size 64m 默认每增长64m就启动重写。

重写过程很消耗机器性能,业务量大会频繁触发重写。建议把这个数值调大至少1G以上减少AOF重写频率

存储结构:

  内容是redis通讯协议(RESP )格式的命令文本存储。

  aof文件可以修改/修复。假如错误执行flushdb删除数据库,可以尝试删除aof中的flushdb指令后,恢复aof。

比较

1、aof文件比rdb更新频率高,丢失数据少,rdb+aof同时使用优先使用aof还原数据。(aof记录指令log,rdb间隔时间拷贝DB)

2、aof比rdb更安全也更大。

3、rdb恢复性能比aof好,rdb恢复更快速。

4、生成rdb时会创建内存快照,此时对内存消耗几乎翻倍。

5、如果两个都配了优先加载AOF。

6、官方推荐rdb+aof一起使用,但是不推荐单独使用aof,有bug风险。

混合持久化

4.0以后提供RDB+AOF混合持久化。默认关闭,需要配置aof-use-rdb-preamble yes开启。在一个AOF文件中前面是RDB格式后边是AOF格式。

持久化性能问题

RDB:性能消耗大,若影响主线程请修改配置降低RDB触发频率。

1 Fork操作:主线程发起fork,这是同步操作会影响redis主线程。拷贝内存页信息(不是整个内存)理论上非常快(速度与内存大小相关)。万一卡住整个redis停止响应。改善:使用物理机而非虚拟机、配置redis最大内存,限制不要过大。

2 子进程开销:CPU密集型瞬间占用大量CPU。不要和其他CPU密集型项目部署在同一机器、不做cpu绑定。

内存开销 rdb子进程COW内存副本。

硬盘开销 结合iotop、iostat观察,不要和高硬盘负载放在一起、配置no-appendfsync-on-rewirite=yes、使用ssd。

AOF:压缩重写消耗CPU,请修改配置降低触发频率。

AOF压缩重写过程很消耗机器性能,通过配置减少重写频率。

2 AOF追加阻塞主线程,优化IO性能。

冷备份

通过脚本定时备份持久化文件。当持久化文件较大时,需要测试冷备占用的CPU、内存、硬盘。 

使用过Redis做异步队列么,你是怎么用的?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

如果对方追问可不可以不用sleep呢?list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

如果对方追问能不能生产一次消费多次呢?使用pub/sub主题订阅者模式,可以实现1:N的消息队列。

如果对方追问pub/sub有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

Redis常见性能问题和解决方案? 

 https://www.cnblogs.com/chenpingzhao/p/6859041.html

通过主从架构实现一主多从,读写分离水平扩容

1.Master必须持久化,避免master重启后把空信息同步到所有从机,造成数据丢失。 

2.如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次。   

3.为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。   

4.尽量避免在压力较大的主库上增加从库  

5.为了Master的稳定性,主从复制不要用图状结构,用单向链表结构更稳定,即主从关系为:Master<--Slave1,Slave2<--Slave3.......,这样的结构也方便解决单点故障问题,实现Slave对Master的替换,也即,如果Master挂了,可以立马启用Slave1做Master,其他不变。

Redis 有哪些架构模式?讲讲各自的特点

 单机版

特点:简单

问题:

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

主从复制

Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来的服务器复制品则为从服务器(slave)。只需从机设置slaveof 。 只要主从服务器之间的网络连接正常,主从服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给从服务器,从而一直保证主从服务器的数据相同。推荐配置:slave-read-only=yes,主从复制要开启RDB,若在同一机器上部署RDB\LOG文件名、端口不能重复(推荐多机部署)

从机简要配置文件conf

特点:

1、master/slave 角色

2、master/slave 数据相同

3、降低 master 读压力在转交从库

问题

同步延迟难以避免,只能优化网络尽量减小延迟。

因为有延迟所以不能保证主从数据强一致。需要强一致读写场景(余额、库存等),只能全走主库。主库压力难以完全释放。

设置  min-slaves-max-lag 10    slave同步延迟超过10s以上,master不接收任何写请求,等待slave同步跟上。

只有备份,但是无法保证高可用。

没有解决 master 写的压力。

从机切换新主机时,会清除掉原有记录。

必须开启RDB。

3.2以前存在从机读到TTL过期数据。因为TTL部分是惰性删除。主机等待访问时才删除,但是从机没有删除权利。

主从最大内存配置不同,导致从数据丢失,当从机升级为主机时造成永久丢失。(解决:1、升级3.2;2、TTL检查)

全量复制

首次关注master时执行全量复制。runid为redis每次启动时重新分配的随记ID,offset为偏移量。当从机关注主机时记入主机runid+offset(偏移量),主机bgsave生成RDB文件,从机清除数据并复制主机RDB文件。RDB读取后再根据之前记录offset位置同步数据。注意:主机重启后runid改变,从机会以为是新主机执行全量复制。

触发全量复制

1 首次建立主从关系,复制RDB(主机runid改变)

2 主从偏移量差值大于主机缓冲区(repl_back_buffer 默认1M)

在主服务器上可以看到

master_repl_offset 主节点backlog偏移量

slave0: offset   从节点backlog偏移量

master_repl_offset - offset的差量为延迟

主从同步收到以下参数影响

redis主从同步收到以下参数影响 - vansky - 博客园

repl-ping-slave-period主从心跳ping的时间间隔。默认10

repl-timeout  从节点超时时间。默认60

repl-backlog-size  主节点保存操作日志的大小。默认1M 

repl-backlog-ttl   主节点保存操作日志的时间。默认3600秒

client-output-buffer-limit 这个参数分为3部分,默认是:256M 64M 60秒,意思是:如果output-buffer>256M则从节点需要重新全同步,如果256>output-buffer>64且持续时间60秒,则从节点需要重新全同步。

第二部分涉及slave。表示主节点输出给从节点的缓存(output-buffer)大小。

主从同步的健康监控项(info Replication ):

主节点:

master_repl_offset 主节点backlog偏移量

slave0: offset   从节点backlog偏移量

master_repl_offset-offset  master_repl_offset与offset的差量为延迟backlog

从节点:

master_last_io_seconds_ago  从节点超时时间

避免/优化全量复制

1 节点的最大内存不要过大,减轻bgsave/RDB传输读取压力

2 低峰时间段执行

3 扩大主机缓冲区(repl_back_buffer 以主机单位时间变更MB为基准)

延迟lag

redis主从同步收到以下参数影响 - vansky - 博客园

主从持久化策略与痛点

redis主从持久化讨论 - 行知散人 - 博客园

哨兵(主从升级版)

哨兵原理详解:Redis哨兵模式(sentinel)学习总结及部署记录(主从复制、读写分离、主从切换) - 散尽浮华 - 博客园

推荐启用sentinel2,客户端连接Sentinel节点即可。Redis sentinel 是一个分布式系统监控 redis 主从服务器,并在主服务器下线时自动进行重连+故障转移+切换主机。Sentinel 是不支持存储的特殊Redis节点,其主要作用是监控+故障转移+通知。其默认端口26379

官方给出的最简搭建架构: 

其中三个特性:

监控(Monitoring):    Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。一套Sentinel可以监控多个主从集群

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

自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel会投票选出新主机,开始一次自动故障迁移操作。Sentinel仍然监控原主机,当其修复上线后会自动变为从机。但是迁移过程小概率丢失数据。重启原主机前备份其持久化文件。

Sentinel 配置文件.conf

Sentinel最好是奇数个节点,推荐主观认定宕机票数=Sentinel节点数/2+1。只需要配置监控的主节点

 启动哨兵节点 :

启动Sentinel后,其会到监控的主节点中得到从节点信息、其他Sentinel信息。多个Sentinel只要配置监控同一个主节点就可以相互感知。客户端连接Sentinel节点即可。

启动后再看其配置文件发生了变化:1 识别了主节点的从节点,并去掉了一些默认配置。

客户端jedis连接哨兵:实现读写分离

特点:

0、主从架构实现一主多从,读写分离、水平扩容,提高吞吐量和可拓展性

1、保证高可用、自动换主、自动故障迁移

2、监控各个节点

3、API通知

缺点:

一主两从三哨兵、需要六个redis节点。(一主一从有脑裂风险)

主从模式,切换时可能丢失数据;

没有解决 master 写的压力;slave只分担读工作,写只有master。

哨兵必须3个节点起步(quorum=2 两票选主),因为同意故障转移由投票完成,两个节点挂掉一个后无法完成此工作。

springboot配置哨兵:springboot整合redis哨兵方式配置_这儿很凉的博客-CSDN博客_springboot 配置redis哨兵

坑:

protected-mode:这个属性设置为yes时只可以进行内网访问,如果主从切换失败了,可以试试把这个属性设置为no。

哨兵模式各种异常场景测试

测试案例:Redis 测试环境介绍_w3cschool

测试案例包含  主挂、单从挂、双从挂、单哨兵挂、双哨兵挂、主+哨兵挂、脑裂等场景。

测试环境:3台机器,一主两从三哨兵。集群2票(半数)选主,主机最少要求1从机 (双从宕机\脑裂测试中描述)。

总结:1、3机6节点环境下,只有在2个同种节点(半数)出现问题时,集群才会失效(变为只读)。

           2、原主节点重启后会自动变为从机。重启前最好先备份原主节点持久化文件。

主从模式常见数据丢失问题:

0 为提高性能master没有持久化,重启后集群全部同步成空。解决:要为从机持久化文件定时做备份。

1 集群切换master时可能数据丢失

2 脑裂,因为原mater与集群网络失联,所以集群选举新master。但是原master还正常运行,部分客户端仍然写原master。

   设置:min-slaves-to-write 1 (半数slave)  一主两从要求master至少有一个slave,达不到要求时开启只读模式(根据自己的集群架构而定)。

              min-slaves-max-lag 10    slave同步延迟超过10s以上,master不接收任何写请求,等待slave同步跟上。此时client也要适当降级限流异步减少写请求。上边设置放弃了写请求的高可用,换来的脑裂后数据一致性。

              quorum=半数主机以上票选主

Sentinel的选举流程

Sentinel集群正常运行的时候每个节点epoch相同,当需要故障转移的时候会在集群中选出Leader执行故障转移操作。Sentinel采用了Raft协议实现了Sentinel间选举Leader的算法,不过也不完全跟论文描述的步骤一致。Sentinel集群运行过程中故障转移完成,所有Sentinel又会恢复平等。Leader仅仅是故障转移操作出现的角色。

选举流程

  • 1、某个Sentinel认定master客观下线的节点后,该Sentinel会先看看自己有没有投过票,如果自己已经投过票给其他Sentinel了,在2倍故障转移的超时时间自己就不会成为Leader。相当于它是一个Follower。
  • 2、如果该Sentinel还没投过票,那么它就成为Candidate。
  • 3、和Raft协议描述的一样,成为Candidate,Sentinel需要完成几件事情
    • 1)更新故障转移状态为start
    • 2)当前epoch加1,相当于进入一个新term,在Sentinel中epoch就是Raft协议中的term。
    • 3)更新自己的超时时间为当前时间随机加上一段时间,随机时间为1s内的随机毫秒数。
    • 4)向其他节点发送is-master-down-by-addr命令请求投票。命令会带上自己的epoch。
    • 5)给自己投一票,在Sentinel中,投票的方式是把自己master结构体里的leader和leader_epoch改成投给的Sentinel和它的epoch。
  • 4、其他Sentinel会收到Candidate的is-master-down-by-addr命令。如果Sentinel当前epoch和Candidate传给他的epoch一样,说明他已经把自己master结构体里的leader和leader_epoch改成其他Candidate,相当于把票投给了其他Candidate。投过票给别的Sentinel后,在当前epoch内自己就只能成为Follower。
  • 5、Candidate会不断的统计自己的票数,直到他发现认同他成为Leader的票数超过一半而且超过它配置的quorum(quorum可以参考《redis sentinel设计与实现》)。Sentinel比Raft协议增加了quorum,这样一个Sentinel能否当选Leader还取决于它配置的quorum。
  • 6、如果在一个选举时间内,Candidate没有获得超过一半且超过它配置的quorum的票数,自己的这次选举就失败了。
  • 7、如果在一个epoch内,没有一个Candidate获得更多的票数。那么等待超过2倍故障转移的超时时间后,Candidate增加epoch重新投票。
  • 8、如果某个Candidate获得超过一半且超过它配置的quorum的票数,那么它就成为了Leader。
  • 9、与Raft协议不同,Leader并不会把自己成为Leader的消息发给其他Sentinel。其他Sentinel等待Leader从slave选出master后,检测到新的master正常工作后,就会去掉客观下线的标识,从而不需要进入故障转移流程。

集群(proxy 型):

Twemproxy(推特开源)、Codis (豌豆荚开源)。客户端连接代理操作就与连接Redis节点相同,由代理自己实现底层的数据分片查找功能。

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

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

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

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

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

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

集群(直连型):

从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。客服端链接任意节点都相当于访问集群。集群原理与搭建:Redis集群 - 废物大师兄 - 博客园

无论多少台主机、多大内容容量,一共都是16384槽,所有主机分槽。key通过算法内部命中槽位置。

优点:支持水平扩容、无中心化、主从保证高可用、从节点分担读压力

无中心化:客户端链接集群中任意一节点(主从随意)都可操作集群读写。实际是集群内部将实际操作转移到key对应主/从机上。

.conf 中设置 cluster enable yes,通过redis-3.0.0.gem(官方ruby脚本搭建集群)执行集群命令

replicas 1 代表每个主节点有一个从节点 

手动指定主从关系:Redis5 cluster人工指定主从关系_tianshi_rain的博客-CSDN博客_redis集群指定主从 

可将按照容量\性能高低分配

增加主节点到集群(扩容):增加后需要reshard为新节点分配槽,可以选则从就主节点平均分配,还是从指定某个主节点分配。

增加从节点到集群:可以指定为某个主节点增加从节点。

增删节点:(转)高性能网站架构之缓存篇—Redis集群增删节点 - 明天OoO你好 - 博客园

增加主节点后,  删除主节点前,都要执行./redis-trib.rb reshard 重新分配/转移数据槽

特点:

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

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

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

4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本,比如四台集群,cluster自动配置两主两从

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

缺点:

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

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

3、最小三主三从6节点。推荐至少3台主机,每台上部署一主一从两节点,三从交叉分布部署(避免同一数据源的主从同时挂掉)。

比如:1号机:主1,从2;2号机:主2,从3;3号机:主3,从1;

Redis集群方案什么情况下会导致整个集群不可用?

答:有A,B,C三个节点的集群,在没有复制模型的情况下,如果节点B失败了,那么整个集群就会以为缺少5501-11000这个范围的槽而不可用。

集群监控通知

1 最简单的方法是自己的服务中定时访问各个redis服务,执行get/ping。若redis某个服务器多次无响应则发短信通知运维。

强一致性协议Raft

演示:Raft Scope

原理:文章正在审核中... - 简书

           Raft算法之日志复制 - 触不可及` - 博客园

详细规则解读:Raft 实现日志复制同步 - Richaaaard - 博客园

一致性
raft算法:强一致、高可靠、高可用
强一致性:虽然所有节点的数据并非实时一致,但Raft算法保证Leader节点的数据最全,同时所有请求都由Leader处理,所以在客户端角度看是强一致性的。
高可靠性:Raft算法保证了Committed的日志不会被修改,State Matchine只应用Committed的日志,所以当客户端收到请求成功即代表数据不再改变。Committed日志在大多数节点上冗余存储,少于一半的磁盘故障数据不会丢失。
高可用性:从Raft算法原理可以看出,选举和日志同步都只需要大多数的节点正常互联即可,所以少量节点故障或网络异常不会影响系统的可用性。即使Leader故障,在选举超时到期后,集群自发选举新Leader,无需人工干预,不可用时间极小。但Leader故障时存在重复数据问题,需要业务去重或幂等性保证。
高性能:与必须将数据写到所有节点才能返回客户端成功的算法相比,Raft算法只需要大多数节点成功即可,少量节点处理缓慢不会延缓整体系统运行。 

多数派共识,至少三节点。
客户端请求只会发给leader。接到请求后leader产生一个log,RPC同步状态机log,Follower从机接收leader-RPC心跳。
leader每次广播一个log给从机,需等半数以上从机回复收到,leader才会本地提交Committed,响应回复客户端请求,最后广播给从机提交Committed。没有半数回复(总数是包含已下线机器的),leader是不会本地提交的。
且leader必须保证至少有一条自己任期的log被半数以上从机收到回复后,才本地提交以前的所有log。
比如:新leader有两个log需要同步,log1为上一任期的,log2为自己当前任期的。则只有log1+2都被半数同步后,leader才会本地提交Committed。
这是为了保证下次切换leader时,拥有最新任期(当前任期)且已提交log的候选者能获得最多票数。

切换leader
从机长时间没有收到leader心跳后,发起选举RPC通知(携带自己的log进度)其他机器,并投自己一票成为候选人。
其他机器收到选举通知后,判断候选人的LOG与自己的LOG进度,自己的LOG更新则拒绝投票。
拥有最新的已提交的log entry(先比任期最新 -> 在比index最大)的Follower才有资格成为Leader。(这点很重要,且注意是log最新不是最多)
半数以上选票同意才能当选leader,总数是包含已下线机器的,下线的机器算否决票。
极端情况出现从机与leader机log不一致时,则leader会强制将从机log改为与自己一致,保证强一致性。

Redis大Key

潜在威胁:

操作、读取时间长,IO开销大。redis单线程执行,影响其他操作。

处理方法:

拆分:

核心思想就是合理的拆分为小key通过mget/mset原子性操作。容器结构拆分成多个小容器(hash%i)。

redis大Key多Key分拆方案_憧憬的专栏-CSDN博客

裁剪:

去掉不需要缓存的内容,节省空间。

本地缓存:

客户端本地缓存,减少访问redis,缓解redis压力。

定位:

使用rdb_bigkeys工具分析rdb,得出大key的csv文件目录。

Redis热Key

热key会造成大量请求同时命中redis集群中的某一台机器造成瘫痪。

发现:1 业务经验   2 客户端统计  3 twemproxy代理层统计  4 redis指令 monitor 监控redis执行的指令  5 redis指令 hotkeys 

           6 tcp抓包

解决:1 客户端本地缓存 2 读写分离,配置多个readonly节点

monitor 返回ok 进入监控模式,quit退出监控。redis只要执行命令,监控就会打印。但是对redis的性能有很大影响。

monitor >19-2.txt 记录执行命令

"1565069818.486737 [0 124.205.250.226:13541] "set" "2" "1""

什么是一致性哈希算法?什么是哈希槽?

这两个问题篇幅过长 网上找了两个解锁的不错的文章

https://www.cnblogs.com/lpfuture/p/5796398.html

https://blog.csdn.net/z15732621582/article/details/79121213

使用过Redis分布式锁么,它是怎么实现的?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

SET key value PX time(ms) NX 

或可以使用LUA保证原子性 

RedisTemplate+lua脚本 实现分布式锁:使用RedisTemplate配合lua脚本实现分布式锁_u014495560的博客-CSDN博客

缓存并发?缓存穿透?缓存击穿?缓存雪崩?何如避免?

缓存并发

主要是控制并发操作的原子性问题,比如先查询,再判断,再修改这种复合操作,在高并发时如果不控制原子性会经常出错。

1 分布式锁  2 LUA脚本  3 通过修改后API返回值判断

缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,造成缓存不命中,一直访问DB。就会对后端系统造成很大的压力。这就叫做缓存穿透。

如何避免?

1:对查询结果为空的KEY也进行缓存,缓存时间TTL设置短一点,或者该key对应的数据insert了之后清理缓存。

2:对一定不存在的key进行过滤。可以把所有存在的key放到一个大的Bitmap中,查询时先通过该bitmap过滤。(布隆过滤)

布隆过滤:有误判率,原因是hash碰撞和槽长度有限。但是比方案1更加节省内存。

缓存击穿

当没有缓存时,可能同时有大量线程访问数据库去查询相同的内容,对数据库造成瞬时压力。

解决:

可以在同种数据的查询代码中加分布式锁,这样保证一个SQL执行完后,其他都可见。这样可以减轻数据库的瞬时压力,但是会造成大量线程竞争锁的情况,大量读操作串行化。

还有一种是通过CAS无锁话机制,更新数值状态。判断是否有缓存。推荐这种可以避免串行化读的问题

实现代码:

https://gitee.com/sw008/Test1/blob/master/dubboservice/src/main/java/com/example/dubboservice/service/impl/SimServiceImpl.java

查询缓存
没有则AtomicLong.CAS取得DB查询权利?
获权:二次检查缓存  (存在)?是返回:否 ->查询DB->更新缓存->放权->RCountDowbLatch唤醒
无权:RCountDowbLatch.wait(TTL) 等待唤醒->查缓存
//CountDowbLatch可以用Future替换。

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统停顿崩溃。

如何避免?

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期

3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。批量添加KEY时 TTL=固定值+随机数 。

Redis事务

REDIS 事务处理 -- Redis中国用户组(CRUG)

watch、unwatch、multi、exec、discard 

可以实现CAS,按顺序地串行化整体执行而不会被其它命令插入(redis单线程)。multi命令使Redis将这些命令放入queue,而不是执行这些命令。当放入queue失败时(例如:语法错误),EXEC命令可以检测到并且不执行。一旦调用EXEC命令成功,那么Redis就会一次性执行事务中所有命令,redis单线程执行queue中间不会插入其他命令,但是中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。相反,调用DISCARD命令将会清除事务队列,然后退出事务。EXEC命令的返回值是一个数组,其中的每个元素都分别是事务中的每个命令的返回值,返回值的顺序和命令的发出顺序是相同的。watch必须写在multi之前。

//CAS 伪代码
//注意MULTI中所有命令 返回NULL,包括GET
//所以 取值操作 要在MULTI前执行

WATCH mykey  //监视 要在MULTI前执行
val = GET mykey
val = val + 1

MULTI
SET mykey $val //加入queue
EXEC  //queue执行

SpringBoot 配置

# Redis数据库索引(默认为0)
spring.redis.database=0  
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=50  
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1  
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8  
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0  
# 连接超时时间(毫秒)
spring.redis.timeout=0 

Redis应用场景注意点

1 秒杀业务防止超卖:保证查询和更新等复合操作的整体原子性

秒杀思路:

简单:MQ+数据库乐观锁(幂等)

复杂:Redis限流+Redis int/List/Set 限制库存/用户(可以使用LUA脚本) + MQ+数据库条件乐观锁(幂等)

推荐使用LUA脚本把【判断用户是否已秒杀?+减库存成功?+添加用户集合】做成原子性操作。限制超卖和重卖。

简单实现:redis+lua与秒杀--lua入门_snoopy-CSDN博客_lua redis

Redis限流:限制用户5秒内不超过5次请求。 可以为每个请求用户设置一个KEY int,TTL=5s,拦截请求一次incr并时判断其返回值,超过次数请求失败。每过5s此Key失效重新计次数。

秒杀:根据用户提交的产品信息,获取到redis中需要的key值,查询缓存,判断当前库存是否大于零,如果>0,判断当前的set集合中是否用该用户ID。如果没有,decr对库存减1得到减后库存量(或List.pop),并且将用户的ID放入集合中,如果减后返回库存<0(或List.pop返回NULL),提示用户商品已售完信息。注意高并发下不能用 kc == 0做判断。如果集合中已经存在该用户id,则不做任何处理(考虑库存回滚1),直接处理下一个请求直到最后库存售完。上面的过程也可以利用redis事务和watch功能完成对数据一致性的控制即超卖问题。用户信息存入SET,甚至可以控制请求IP,通过MQ做流量消峰,最终数据库端还可以用乐观锁再进行超卖保护(update XX set  kcshl=kcshl-1  where kcshl>0 and spid='XX')

//简单模拟 实际业务更为复杂
//注意:不要限制当前进程的redis连接数
//skillList预装被抢购商品信息(例如 10个phoneX)

volatile boolean have=true;

public void skill(String userid) {
	if (have) { 
                 //从skillList中抢购商品
		String imei=redisTemplate.opsForList().leftPop("skillList");	
		if (imei != null && !"".equals(imei)) {	
                        //放入成功用户集合skillSet-user(可以再判断用户唯一性,重复抢购则把商品imei返回skillList)
			redisTemplate.opsForSet().add("skillSet-user",userid); 
			log.info("手机imei:"+imei+" user:"+userid);
                        //可以放入MQ,交给下游服务记录数据库(幂等+乐观锁)
                        //数据库结果,决定是否最终秒杀成功
		}else {
			have=false;
		}
	}	
}

    1.1 Redission 提供全局AtomicInteger实现类 可以作为库存,防止超卖

    1.2 RedisTemplate  提供 opsForValue().decrement(key,Long):Long  方法实现 更新并返回计算结果,判断返回值>=0再生成订单,可以作为库存,防止超卖。但是这种可能高并发瞬间被减成负值,返回值要使用范围判断,不能使用=0来判断。

    1.3 RedisTemplate 提供 opsForList().leftPop(key) :String 使用List装入库存数量相同个商品对象,抢购时使用POP方法,防止超卖,List空则返回NULL。对比1.2更加严格

    1.4 分布式锁控制,但是效率降低(锁一定放在事务之外,否则会出现超卖,因为隔离性问题)

    1.5 SET存放抢单用户信息,防止多抢。不要用LIST,因为SET可以去重复

    1.6 LUA保证复合指令原子性操作。

2、匀速放货策略

与传统秒杀瞬间抢光不同,业务场景需要控制商品在整个活动期间均匀放货。下图中用list做令牌存储,一个令牌对应一个奖品。令牌自带时间,且按时间顺序入list。list左侧是是消费者抢令牌的操作,取出令牌判断时间,到时则成功,未到时则放回list左侧。取出+放回必须是原子性操作(LUA),才能保证并发取出再返回后队列依然按时间排序。

3、分布式锁实现

   3.1 SET key value PX time(ms) NX 实现分布式锁  (Redission 通过lua实现了Lock和JDK并发组件接口)

   3.2 可以通过原子性的整数更新实现 自旋锁/CAS 等问题

4、作为热点数据缓存,注意防止穿透,配置淘汰规则

LUA实现复合redis指令原子性执行

eval 命令 -- Redis中国用户组(CRUG)

串行化执行保证:原子性、有序性、可见性、减少通讯次数

使用LUA脚本可以原子的执行redis指令集,可以实现简单的逻辑判断,保证并发的原子性执行。

复合指令原子性:redis是单线程,执行LUA脚本时其他redis指令排队等待脚本全部执行完成,这样LUA脚本中的所有指令执行过程中不被打断,因此在编写脚本的过程中无需担心会出现竞态条件.

LUA不会回滚,中间出现错误,已经执行的命令就是既定事实。

lua脚本相对于redis事务的优势:

1 redis事务是进入queue中一起执行,每一步的结果是最后整体返回,所以中间无法通过java代码对每一步执行结果进行判断。

   redis事务只能通过watch监视KEY, 发生变化queue不执行。

   lua脚本在保证原子性同时,还可以根据每一步的执行结果,选择不同代码逻辑分支,更加灵活。

2 lua脚本执行效率高。事务的watch相当于乐观锁CAS,可能会出现反复失败重试的情况。而LUA的特性不会出现此问题。

redis执行lua脚本: 在redis中使用lua脚本让你的灵活性提高5个逼格 - 一线码农 - 博客园

可以直接EVAL执行,也可先执行scriptLoad缓存脚本再执行EVAL,返回SHA加密代码, 之后可以执行EVALSHA+代码直接使用缓存脚本,减少网络传输的开销。

EVAL script numkeys key [key ...] arg [arg ...]
 <1> script:     你的lua脚本

 <2> numkeys:  参数中前几个是KEY

 <3> key:         redis中各种数据结构的替代符号

 <4> arg:         你的自定义参数


eval "local var=redis.call('get',KEYS[1]) return var" 1 skey
注意:脚本里边用单引号。
一个简单的lua脚本 KEYS,ARGV都是脚本入参

local num=redis.call('incr',KEYS[1]) 
if (tonumber(num)==1) then 
  redis.call('expire',KEYS[1],ARGV[1]) 
  return 1 
elseif (tonumber(num)>tonumber(ARGV[2])) then 
  return 0 
else 
  return 1 
end


redis对象是REDIS客户端为LUA脚本添加的内置对象,在REDIS中执行的lua自带这个对象
redis.call :执行redis命令,不捕获异常,报错直接终止
redis.pcall :执行redis命令,捕获异常

红包功能

利用Redis和Lua的原子性实现抢红包功能 - 简书

秒杀

redis+lua与秒杀--lua入门_snoopy-CSDN博客_lua redis

RedisTemplate+lua脚本 实现分布式锁:使用RedisTemplate配合lua脚本实现分布式锁_u014495560的博客-CSDN博客

LUA实现线程安全的CAS操作

利用lua脚本线程安全的特性,轻松实现原子性CAS操作。

LUA脚本回滚补偿思路

简单思路:在执行LUA脚本前,上锁(分布式锁)业务代码块,取得一个SN号码作为Log唯一号码。将SN传入LUA执行,LUA脚本第一步先在Redis中将脚本相关的各个关键变量的当前值记入Log中。Log可以是任意合适的数据结构(String,Hash等)。若外层业务代码(Java)出现异常,则在异常捕获后通过SN号码找到Log记录进行回滚补偿,最终放锁。此过程需要通过锁保证串行化,避免并发回滚Log的值相互覆盖。

伪代码
lock.lock();
try{
    //....业务代码
    sn = redis.incr("sn"); //jedis、redisTemplate...
    result = redis.exec("lua-script",keys,sn);    
    //....业务代码
}catch(e){
    redis.exec("lua-rollback",keys,sn);
}
finally{
    lock.unlock();
}

复杂思路:

借鉴Seata-AT的思路,引入全局锁概念。执行前取得各个key的全局锁(分布式锁),然后执行记录各个key的前镜像log+后镜像log,全部执行成功或捕获异常回滚后,finally释放各个key全局锁。回滚时若key值与后镜像不一致(说明有程序或人工在非全局锁条件下修改了key),则不回滚等待人工处理。

使用Lua脚本+Redis实现分布式可重入锁样例

原贴https://github.com/qiurunze123/miaosha/blob/master/docs/redis-good.md

RedisTemplate执行LUARedis + Lua 实现分布式应用限流_晏霖/胖虎的博客-CSDN博客

RedisTemplate+lua脚本 实现分布式锁:使用RedisTemplate配合lua脚本实现分布式锁_u014495560的博客-CSDN博客

思路是使用setnx这是Key的值,值是一个唯一标识。通过值判断当期线程是否可以重入。

这里应该放计数器因为是可重入,释放时不能直接del,而是计次为0后再删除

不要在LUA创建redis链接(conn = redis.Redis()),这样每次都会重建链接执行速度很慢。应配合RedisTemplate中的链接池执行

预加载LUA

启动时redis预加载LUA脚本
* https://www.cnblogs.com/selfchange/p/redis.html
redisTemplate.execute执行lua时,RedisScript计算lua脚本sha1值,尝试使用evalSha命令执行Lua脚本;
evalSha失败时,使用eval命令执行Lua脚本;
通过execute执行lua是不会将lua加载到redis中的,所以每次执行都要传输脚本内容。
预加载lua脚本后,可以节省传输脚本的时间和带宽。
@Autowired
    @Qualifier("simlockredisluaScriptLua")
    private DefaultRedisScript<Long> simlockredisluaScriptLong;

    @Autowired
    @Qualifier("jsonScriptLua")
    private DefaultRedisScript<Object> jsonScript;


@PostConstruct
    public void loadScript() {
        //预加载
        String execute = redisTemplate.execute((RedisCallback<String>) connection -> {
            return connection.scriptLoad(simlockredisluaScriptLong.getScriptAsString().getBytes());
        });

        execute =redisTemplate.execute((RedisCallback<String>) connection -> {
            return connection.scriptLoad(jsonScript.getScriptAsString().getBytes());
        });

    }

集群CLUSTER模式下执行LUA,通过{hash_tag}控制key节点分布

redis集群执行lua_xixingzhe2的博客-CSDN博客_redis集群支持lua脚本吗

两种异常:SpringBoot RedisTemplate 集群使用lua - 简书

redis集群要求lua脚本中的key在同一个节点中才能执行,否则会报错。

在CLUSTER模式下保存key是可以通过xxx:{hash_tag}=value保存(实际key=xxx:hash_tag)。这样CLUSTER会通过{}中的hash_tag内容计算key要保存的槽。例如:{}中可以存放业务名称,这样同一个业务的key就会存放到同一个节点中,但是这样不能分散服务器的读写压力。

Redis的内存用完了会发生什么?

redis设置配置文件(redis.conf)的maxmemory参数,可以控制其最大可用内存大小(字节)。

那么当所需内存,超过maxmemory怎么办?这个时候就该配置文件中的maxmemory-policy出场了。其默认值是noeviction。

下面我将列出当可用内存不足时,删除redis键具有的淘汰规则。

规则名称

规则说明

volatile-lru

使用LRU算法删除一个键(只对设置了生存时间的键)

allkeys-lru

使用LRU算法删除一个键

volatile-random

随机删除一个键(只对设置了生存时间的键)

allkeys-random

随机删除一个键

volatile-ttl

删除生存时间最近的一个键

noeviction

不删除键,写返回错误,读正常(默认)

注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。

LRU算法,least RecentlyUsed,最近最少使用算法。也就是说默认删除最近最少使用的键。

但是一定要注意一点!redis中并不会准确的删除所有键中最近最少使用的键,而是随机抽取3个键,删除这三个键中最近最少使用的键。那么3这个数字也是可以设置的,对应位置是配置文件中的maxmeory-samples

使用策略规则:

1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru

2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

REDIS与SQL双写一致性方案

并发直接更新缓存的隐患(不容易保证更新顺序)

解决方法:lua更新+版本号

一下无论哪种方案都是不能100%保证绝对一致性

(粗体为推荐方案)

方案1:先更新SQL,再删缓存。最终一致性,只更新数据库和数据库中的消息表。定时任务/消息队列处理消息表中的缓存删除任务。将更新缓存的任务交给读请求。由读请求添加新缓存。

风险:sql成功,删除缓存失败,有概率脏读

方案2延时双删策略。读方更新缓存。先删缓存(保证缓存能正常删除),再更新SQL,延迟/MQ 删缓存。在1基础上加强了缓存数据的删除操作。延迟删除缓存是为了避免在更新sql执行的同时查询sql也在执行,查询sql获得旧结果,却在删除缓存之后把旧结果更新到缓存。

并发量大时,MQ可以设置多条队列提高效率,由key-mod使同一key分到同一队列中,保证有序性。

风险:

1 删除缓存后,SQL更新成功之前。并发高时可能大量读请求会查数据库且更新缓存,使数据库压力变大,需要防止击穿,只让一个线程读数据库,提前暴露future,其他线程future.get等待结果。又因为sql还没更新成功,所以读出的数据是脏读。

2 小概率脏读:延迟删除减小以下情况脏读可能性。保证写线程删除的延迟时间不能过短,读线程 查->写 间隔不要大。

线程1 读:  没有缓存---->查数据库返回=a(防击穿)----------------等很久-------------------------------->设置缓存=a (此时出现脏读)

线程2 写:  删除缓存------------------------------------->修改数据库=b-------->异步延迟删除缓存成功

可参考此文章:redis系列之数据库与缓存数据一致性解决方案 - chenxiangxiang - 博客园

方案3:分段读写锁、分段排它锁。同KEY串行化,强一致性最有保证,但是吞吐量影响较大。

            实现方式:1-分布式锁同步+分段锁思想;2-同KEY同一队列+单一消费者

方案4:写方更新缓存,DB有数据自增版本号,缓存中也记录版本号。更新缓存时原子性执行 (比较版本号+更新缓存) 。解决高并发写时,多个写线程竞争更新缓存,无法确定更新顺序问题。

方案 5:mysql-binlog订阅,注意有序性。方案4的升级版。

方案 6定时任务同步DB数据到redis 或是 补偿机制。执行同步时 最好有版本号乐观锁、分段悲观锁、或是其他逻辑判断,避免高并发下  同步服务查出的数据库值 落后于 当前redis和db库中的实际值,造成旧值覆盖 redis中新值得现象。

方案 7:5+6的结合版。编写独立的同步服务,支持接收MQ(实时)、mysql-binlog订阅(实时)、定时任务 多种方式同步DB数据到redis。支持实时、定时两种方式。数据携带版本号,避免并发冲突。

redis和数据库双写一致性问题_qq_37779352的博客-CSDN博客_redis和数据库双写一致性

常用排查指令

info:显示系统、版本、内存使用等信息,最后显示CPU使用 ,Keyspace各分区状态等信息

keys代表当前key数量,expires代表设置TTL的数量

redis-cli –bigkeys 或 findBigKeys :找到各个数据类型中内存最大的key

SET的使用场景 

https://blog.csdn.net/qq_40233503/article/details/88562909

 

ZSET的使用场景 

 

统计在线人数

方案1:scan所有jwt、session统计个数。

方案2:利用ZSET 使用 Redis 统计在线用户人数 - 笑笑别人 - 博客园

方案3:前端定时推送心跳

关注和动态推送

每个用户一个关注列表  followSet:{uid}

每个用户一个粉丝列表  fansSet:{uid}

每个用户一个动态列表  msgZset:{uid}

【可选】一个用户一个未读动态列表  msgZset:{uid}

用户发布一条动态后,遍历粉丝列表,为每个粉丝的动态列表中投放动态信息或信息的key,score为发布时间。

精确的已读未读

已读后动态列表中原未读标志信息,替换为已读标志信息。

或是单独用一个zset纪录个人未读动态列表。(结构修改为每个用户一个已读动态列表+一个未读动态列表)

不精确的已读未读:

纪录用户最后一次读取动态列表  msgZset:{uid}中的最大score。下次未读只查询msgZset中大于这个score的。

限流

时间窗口限流

每个用户一个ZSet,score=时间戳。每次删除过期score后(zremrangeByScore),统计count为次数(zcount)。value 值没有特别的意义,只需要保证它是唯一的就可以了。

计数器限流

设置一个数字,TTL=1S。使用incr增加后返回值,判断是否超过阈值。

日志Log

利用ZSET的有序性,以时间戳作为score。

新闻列表

原文Redis有序集合命令ZREVRANGEBYSCORE详解与应用_大大的微笑的专栏-CSDN博客_zrevrangebyscore

Zset保存热点新闻,以时间为顺序。通过正序倒序遍历、范围查询、分页实现类似新闻类app的效果。

假如:图中红色为当前用户页的首条新闻纪录,每页两条新闻。

首次加载:ZREVRANGEBYSCORE news +inf -inf LIMIT 0 2

向上加载两条最新新闻:ZRANGEBYSCORE news 201610030000 +inf LIMIT 0 2

向下加载两条历史新闻:ZREVRANGEBYSCORE news 201610030000 -inf LIMIT 2 2

图片1

语法

ZREVRANGEBYSCORE key max min WITHSCORES LIMIT offset count

慢查询 

慢查询保存在内存中

设置:

config set slowlog-max-len 128(默认)  先进先出记录队列,记录最近的128次慢查询

config set slowlog-log-slower-than 10000(默认10毫秒) 慢查询阈值

检索:

slowlog get

slowlog get[n]  取得队列

slowlog len 队列长度

slowlog reset 清空

大HSAH操作HGETALL\HMGET\HMSET性能损耗严重 甚至卡死

Redis命令之HGetAll性能问题解决方案_u012538947的专栏-CSDN博客_hgetall

1、分批次查询再归并结果

2、HSCAN扫描

监听Key超时失效事件

Spring boot实现监听Redis key失效事件实现和其它方式 - 森林木马 - 博客园

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用提到了Redis启动的闪退问题,引用介绍了两种启动Redis的方法,但都会导致闪退。下面我将总结一些常见的Redis启动问题。 1. Redis配置错误:启动Redis时,如果配置文件有错误或者缺少必要的配置,可能会导致启动失败。可以检查redis.conf文件中的配置是否正确,并确保所有必要的配置项都被设置。 2. 端口被占用:如果Redis要使用的端口已经被其他程序占用,那么Redis启动时会失败。可以通过检查端口占用情况,或者修改Redis配置文件中的端口号来解决该问题。 3. 内存不足:Redis需要一定的内存来运行,如果系统内存不足,可能会导致Redis启动失败。可以通过增加系统内存或者减少Redis的内存使用来解决该问题。 4. 权限问题:如果Redis的执行文件或者配置文件没有足够的权限,可能会导致启动失败。可以检查文件的权限设置,并确保Redis所在的目录对当前用户有足够的访问权限。 5. 版本兼容性问题:在某些情况下,Redis版本与操作系统或其他依赖软件之间存在兼容性问题,可能会导致启动失败。可以尝试升级或降级Redis版本,或者解决相关的依赖冲突来解决该问题。 需要注意的是,这只是一些常见的Redis启动问题,具体问题可能因环境和配置的不同而有所差异。如果还遇到其他问题,建议查看Redis的文档或社区中的解决方案,或者尝试搜索类似的问题和解决方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [windows下Redis启动闪退问题解决经验汇总](https://blog.csdn.net/httpmc2018/article/details/121082182)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [redis 常见问题](https://blog.csdn.net/u013743253/article/details/124093790)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值