思维导图地址:
思维导图内容
Redis
- Redis基础
- Redis基本命令
- 遍历键
- keys:全量遍历键,用来列出所有满足特定正则字符串规则的key,当redis数据量比较大时,性能比较差(单线程)
- scan:渐进式遍历键不能保证完整的遍历出来所有的键
- 遍历键
- 基本数据结构
- String
- 常用操作
- SET key value //存入字符串键值对
- MSET key value [key value ...] //批量存储字符串键值对
- SETNX key value //存入一个不存在的字符串键值对
- GET key //获取一个字符串键值
- MGET key [key ...] //批量获取字符串键值
- DEL key [key ...] //删除一个键
- EXPIRE key seconds //设置一个键的过期时间(秒)
- 原子加减
-
- INCR key //将key中储存的数字值加1
- DECR key //将key中储存的数字值减1
- INCRBY key increment //将key所储存的值加上increment
- DECRBY key decrement //将key所储存的值减去decrement
- 应用场景
- 单值缓存
- 对象缓存
- 分布式锁
-
- SETNX product:10001 true //返回1代表获取锁成功
- SETNX product:10001 true //返回0代表获取锁失败
- 。。。执行业务操作
- DEL product:10001 //执行完业务释放锁
- SET product:10001 true ex 10 nx //防止程序意外终止导致死锁
- 计数器
-
- INCR article:readcount:{文章id}
- GET article:readcount:{文章id}
- Web集群session共享:spring session + redis实现session共享
- 分布式系统全局序列号 :INCRBY orderId 1000 //redis批量生成序列号提升性能
- 常用操作
- Hash 哈希
- 常用操作
- HSET key field value //存储一个哈希表key的键值HSETNX key field value //存储一个不存在的哈希表key的键值HMSET key field value [field value ...] //在一个哈希表key中存储多个键值对HGET key field //获取哈希表key对应的field键值HMGET key field [field ...] //批量获取哈希表key中多个field键值HDEL key field [field ...] //删除哈希表key中的field键值HLEN key //返回哈希表key中field的数量HGETALL key //返回哈希表key中所有的键值HINCRBY key field increment //为哈希表key中field键的值加上增量increment
- 应用场景
- 对象缓存
- 电商购物车
- 1)以用户id为key
- 2)商品id为field
- 3)商品数量为value
- 购物车操作
- 添加商品hset cart:1001 10088 1
- 增加数量hincrby cart:1001 10088 1
- 商品总数hlen cart:1001
- 删除商品hdel cart:1001 10088
- 获取购物车所有商品hgetall cart:1001
- 优点
- 1)同类数据归类整合储存,方便数据管理
- 2)相比string操作消耗内存与cpu更小
- 3)相比string储存更节省空间
- 缺点
-
- 1)过期功能不能使用在field上,只能用在key上2)Redis集群架构下不适合大规模使用
- 常用操作
- List列表
- 常用操作
- LPUSH key value [value ...] //将一个或多个值value插入到key列表的表头(最左边)
- RPUSH key value [value ...] //将一个或多个值value插入到key列表的表尾(最右边)
- LPOP key //移除并返回key列表的头元素
- RPOP key //移除并返回key列表的尾元素
- LRANGE key start stop //返回列表key中指定区间内的元素,区间以偏移量start和stop指定
- BLPOP key [key ...] timeout //从key列表表头弹出一个元素,若列表中没有元素,阻塞等待 timeout秒,如果timeout=0,一直阻塞等待
- BRPOP key [key ...] timeout //从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待 timeout秒,如果timeout=0,一直阻塞等待
- 应用场景
- 常用数据结构
-
- Stack(栈) = LPUSH + LPOP
- Queue(队列)= LPUSH + RPOP
- Blocking MQ(阻塞队列)= LPUSH + BRPOP
- 微博消息和微信公号消息
- 诸葛老师关注了MacTalk,备胎说车等大V
- 1)MacTalk发微博,消息ID为10018
- LPUSH msg:{诸葛老师-ID} 10018
- 2)备胎说车发微博,消息ID为10086
- LPUSH msg:{诸葛老师-ID} 10086
- 3)查看最新微博消息
- LRANGE msg:{诸葛老师-ID} 0 4
- 常用操作
- Set集合
- 常用操作
- SADD key member [member ...] //往集合key中存入元素,元素存在则忽略,
- 若key不存在则新建
- SREM key member [member ...] //从集合key中删除元素
- SMEMBERS key //获取集合key中所有元素
- SCARD key //获取集合key的元素个数
- SISMEMBER key member //判断member元素是否存在于集合key中
- SRANDMEMBER key [count] //从集合key中选出count个元素,元素不从key中删除
- SPOP key [count] //从集合key中选出count个元素,元素从key中删除
- 运算操作
- SINTER key [key ...] //交集运算
- SINTERSTORE destination key [key ..] //将交集结果存入新集合destination中
- SUNION key [key ..] //并集运算
- SUNIONSTORE destination key [key ...] //将并集结果存入新集合destination中
- SDIFF key [key ...] //差集运算
- SDIFFSTORE destination key [key ...] //将差集结果存入新集合destination中
- 应用场景
- 微信抽奖小程序
-
- 1)点击参与抽奖加入集合
- SADD key {userlD}
- 2)查看参与抽奖所有用户
- SMEMBERS key
- 3)抽取count名中奖者
- SRANDMEMBER key [count] / SPOP key [count]
- 微信微博点赞,收藏,标签
- 1) 点赞
- SADD like:{消息ID} {用户ID}
- 2) 取消点赞
- SREM like:{消息ID} {用户ID}
- 3) 检查用户是否点过赞
- SISMEMBER like:{消息ID} {用户ID}
- 4) 获取点赞的用户列表
- SMEMBERS like:{消息ID}
- 5) 获取点赞用户数
- SCARD like:{消息ID}
- 集合操作实现微博微信关注模型
- 1) 诸葛老师关注的人:
- zhugeSet-> {guojia, xushu}
- 2) 杨过老师关注的人:
- yangguoSet--> {zhuge, baiqi, guojia, xushu}
- 3) 郭嘉老师关注的人:
- guojiaSet-> {zhuge, yangguo, baiqi, xushu, xunyu)
- 4) 我和杨过老师共同关注:
- SINTER zhugeSet yangguoSet--> {guojia, xushu}
- 5) 我关注的人也关注他(杨过老师):
- SISMEMBER guojiaSet yangguo
- SISMEMBER xushuSet yangguo
- 6) 我可能认识的人:
- SDIFF yangguoSet zhugeSet->(zhuge, baiqi}
- 集合操作实现电商商品筛选
- SADD brand:huawei P40
- SADD brand:xiaomi mi-10
- SADD brand:iPhone iphone12
- SADD os:android P40 mi-10
- SADD cpu:brand:intel P40 mi-10
- SADD ram:8G P40 mi-10 iphone12
- SINTER os:android cpu:brand:intel ram:8G {P40,mi-10}
- 常用操作
- zset有序集合
- 常用操作
- ZADD key score member [[score member]…] //往有序集合key中加入带分值元素
- ZREM key member [member …] //从有序集合key中删除元素
- ZSCORE key member //返回有序集合key中元素member的分值
- ZINCRBY key increment member //为有序集合key中元素member的分值加上increment
- ZCARD key //返回有序集合key中元素个数
- ZRANGE key start stop [WITHSCORES] //正序获取有序集合key从start下标到stop下标的元素
- ZREVRANGE key start stop [WITHSCORES] //倒序获取有序集合key从start下标到stop下标的元素
- Zset集合操作
-
- ZUNIONSTORE destkey numkeys key [key ...] //并集计算
- ZINTERSTORE destkey numkeys key [key …] //交集计算
- 应用场景
- Zset集合操作实现排行榜
- 1)点击新闻
- ZINCRBY hotNews:20190819 1 守护香港
- 2)展示当日排行前十
- ZREVRANGE hotNews:20190819 0 9 WITHSCORES
- 3)七日搜索榜单计算
- ZUNIONSTORE hotNews:20190813-20190819 7
- hotNews:20190813 hotNews:20190814... hotNews:20190819
- 4)展示七日排行前十
- ZREVRANGE hotNews:20190813-20190819 0 9 WITHSCORES
- 常用操作
- String
- 管道(Pipeline)
- 客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。
- pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令不会有影响,继续执行。
- Redis Lua脚本
- 1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
- 2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过redis的批量操作命令(类似mset)是原子的。
- 3、替代redis的事务功能
- Lua脚本中出现死循环和耗时的运算,否则redis会阻塞
- Redis基本命令
- Redis持久化
- RDB快照(snapshot)
- Redis 将内存数据库快照保存在名字为 dump.rdb 的二进制文件中
- save
- bgsave的写时复制(默认):生成快照的同时,依然可以正常处理写命令
- save与bgsave对比
- 缺点:Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。
- AOF(append-only file)
- AOF会保存服务器执行的所有写操作到日志文件appendonly.aof中,在服务重启以后,会执行这些命令来恢复数据。
- AOF持久化的实现
- (1)命令追加(append):Redis 服务器每执行一条写命令,这条写命令都会被追加到缓存区 aof_buf 中。
- (2)AOF 持久化文件写入(write)和文件同步(sync):根据 appendfsync 参数设置的不同的同步策略,将缓存区 中的数据内容同步到硬盘中。
- 持久化三种策略
- 1.Always:服务器每写入一个命令,就调用一次fdatasync,将缓冲区里面的命令写入到磁盘里面,
- 2.Everysec(默认):服务器每一秒重调用一次fdatasync,将缓冲区里面的命令写入到磁盘里面,在这种模式写
- 3.NO:服务器不主动调用fdatasync,由操作系统决定任何将缓冲区里面的命令写入磁盘里面
- AOF重写:达到压缩的目的,子线程重写
- 优缺点:能保证数据安全不丢失;持久化启动子线程,占用资源,速度较慢
- AOF持久化的实现
- Redis 4.0 混合持久化
- 将重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改名,覆盖原有的AOF文件,完成新旧两个AOF文件的替换。
- 于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。
- Redis的数据备份策略
- 通过定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48小时的备份
- 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份
- 每次copy备份的时候,都把太旧的备份给删了
- 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏
- Redis主从架构
- Redis主从工作原理
- 如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个PSYNC命令给master请求复制数据。
- master收到PSYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
- 当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。
- 主从复制(全量复制)流程图
- 数据部分复制
- 当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。
- master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。
- 主从复制(部分复制,断点续传)流程图
- Redis哨兵
- sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
- 哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
- Redis集群
- 原理分析
- 当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
- 槽:将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。
- 当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
- 槽位定位算法:Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。
- 跳转重定位
- 当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。
- Redis集群节点间的通信机制
- 维护集群的元数据
- 集群节点信息,主从角色,节点数量,各节点共享的数据
- 集中式
- 优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节点读取的时候立即就可以立即感知到;不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储压力。 很多中间件都会借助zookeeper集中式存储元数据。
- gossip
- 每个节点都有一个专门用于节点间gossip通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口。 每个节点每隔一段时间都会往另外几个节点发送ping消息,同时其他几点接收到ping消息之后返回pong消息。
- 解决网络抖动:提供了一种选项cluster-node-timeout,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重新复制)。
- Redis集群选举原理
- 当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
- 1.slave发现自己的master变为FAIL
- 2.将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息
- 3.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发送一次ack
- 4.尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
- 5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的)
- 6.slave广播Pong消息通知其他集群节点。
- 从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
- •延迟计算公式:
- DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
- •SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。
- Redis分布式锁
- 缓存穿透
- 缓存穿透:查询一个根本不存在的数据, 缓存层和存储层都不会命中(缓存和数据库都穿透)
- 原因
- 1、自身业务代码或者数据出现问题。2、一些恶意攻击、 爬虫等造成大量空命中
- 解决方案
- 1、缓存空对象
- 2、布隆过滤器(计算key是否存在)
- 缓存失效(击穿)
- 缓存失效(击穿):由于大批量缓存在同一时间失效可能导致大量请求同时穿透缓存直达数据库,可能会造成数据库瞬间压力过大甚至挂掉
- 解决方案:将缓存过期时间设置为一个时间段内的不同时间(随机时间)
- 缓存雪崩
- 原因:缓存层宕机导致大量请求访问数据库,最终导致数据库也宕机
- 解决方案
- 1、保证缓存层服务高可用性,比如使用Redis Sentinel或Redis Cluster。
- 2、依赖隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix限流降级组件。
- 热点缓存key重建优化
- 场景:在缓存失效的瞬间, 有大量线程来重建缓存, 造成后端负载加大, 甚至可能会让应用崩溃
- 解决方案:利用互斥锁来解决,此方法只允许一个线程重建缓存, 其他线程等待重建缓存的线程执行完, 重新从缓存获取数据即可
- 缓存与数据库双写不一致
- 解决方案(读多写少)
- 1、对于并发几率很小的数据,很少会发生缓存不一致,可以给缓存数据加上过期时间,每隔一段时间触发读的主动更新即可。
- 2、就算并发很高,业务上能容忍短时间的缓存数据不一致(如商品名称,商品分类菜单等),缓存加上过期时间依然可以解决大部分业务对于缓存的要求。
- 3、如果不能容忍缓存数据不一致,可以通过加分布式读写锁保证并发读写或写写的时候按顺序排好队,读读的时候相当于无锁。
- 4、也可以用阿里开源的canal通过监听数据库的binlog日志及时的去修改缓存,但是引入了新的中间件,增加了系统的复杂度。
- 解决方案(读少写多)
- 1、直接操作数据库
- 2、把缓存作为数据读写的主存储,异步将数据同步到数据库
- 解决方案(读多写少)
- Redisson分布式锁
- 代码实现(解决上述问题)
- @Service
- public class ProductService {
- @Autowired
- private ProductDao productDao;
- @Autowired
- private RedisUtil redisUtil;
- @Autowired
- private Redisson redisson;
- public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;
- public static final String EMPTY_CACHE = '{}';
- public static final String LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX = 'lock:product:hot_cache_create:';
- public static final String LOCK_PRODUCT_UPDATE_PREFIX = 'lock:product:update:';
- public static Map<String, Product> productMap = new HashMap<>();
- @Transactional
- public Product create(Product product) {
- Product productResult = productDao.create(product);
- redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult));
- return productResult;
- }
- @Transactional
- public Product update(Product product) {
- Product productResult = null;
- //RLock productUpdateLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
- RReadWriteLock productUpdateLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
- RLock writeLock = productUpdateLock.writeLock();
- //加分布式写锁解决缓存双写不一致问题
- writeLock.lock();
- try {
- productResult = productDao.update(product);
- redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
- genProductCacheTimeout(), TimeUnit.SECONDS);
- } finally {
- writeLock.unlock();
- }
- return productResult;
- }
- public Product get(Long productId) {
- Product product = null;
- String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;
- //从缓存里查数据
- product = getProductFromCache(productCacheKey);
- if (product != null) {
- return product;
- }
- //加分布式锁解决热点缓存并发重建问题
- RLock hotCreateCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_CREATE_PREFIX + productId);
- hotCreateCacheLock.lock();
- // 这个优化谨慎使用,防止超时导致的大规模并发重建问题
- // hotCreateCacheLock.tryLock(1, TimeUnit.SECONDS);
- try {
- product = getProductFromCache(productCacheKey);
- if (product != null) {
- return product;
- }
- //RLock productUpdateLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
- RReadWriteLock productUpdateLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
- RLock rLock = productUpdateLock.readLock();
- //加分布式读锁解决缓存双写不一致问题
- rLock.lock();
- try {
- product = productDao.get(productId);
- if (product != null) {
- redisUtil.set(productCacheKey, JSON.toJSONString(product),
- genProductCacheTimeout(), TimeUnit.SECONDS);
- } else {
- //设置空缓存解决缓存穿透问题
- redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
- }
- } finally {
- rLock.unlock();
- }
- } finally {
- hotCreateCacheLock.unlock();
- }
- return product;
- }
- private Integer genProductCacheTimeout() {
- //加随机超时机制解决缓存批量失效(击穿)问题
- return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;
- }
- private Integer genEmptyCacheTimeout() {
- return 60 + new Random().nextInt(30);
- }
- private Product getProductFromCache(String productCacheKey) {
- Product product = null;
- //多级缓存查询,jvm级别缓存可以交给单独的热点缓存系统统一维护,有变动推送到各个web应用系统自行更新
- product = productMap.get(productCacheKey);
- if (product != null) {
- return product;
- }
- String productStr = redisUtil.get(productCacheKey);
- if (!StringUtils.isEmpty(productStr)) {
- if (EMPTY_CACHE.equals(productStr)) {
- redisUtil.expire(productCacheKey, genEmptyCacheTimeout(), TimeUnit.SECONDS);
- return new Product();
- }
- product = JSON.parseObject(productStr, Product.class);
- //缓存读延期
- redisUtil.expire(productCacheKey, genProductCacheTimeout(), TimeUnit.SECONDS);
- }
- return product;
- }
- }
- 问题:在redis master实例宕机的时候,可能导致多个客户端同时实现加锁(半数以上加锁成功,reids则认为获取锁成功)
- 缓存穿透
主从复制(部分复制,断点续传)流程图

save与bgsave对比

基本数据结构

Redisson分布式锁

Redis哨兵

缓存与数据库双写不一致

主从复制(全量复制)流程图

常用操作

Zset集合操作

Redis主从架构

本文详细介绍了Redis中的各种数据结构,包括String、Hash、List、Set和Zset,以及它们的应用场景,如缓存、分布式锁、计数器、消息队列等。此外,还讲解了Redis的持久化策略、主从复制、哨兵系统和集群,并讨论了缓存穿透、击穿和雪崩等问题及解决方案。最后,展示了如何使用Redisson实现分布式锁。
&spm=1001.2101.3001.5002&articleId=123329476&d=1&t=3&u=762f13b0dd55414989f1a3d9f5a3351c)
7223

被折叠的 条评论
为什么被折叠?



