百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

一文清晰了解-什么是Redis?能干什么?

mhr18 2024-10-23 11:35 34 浏览 0 评论

1. 基础篇

1.1 前言

之前学完狂神讲的Redis,就自我感觉Redis学懂了,看到了尚硅谷发的Redis篇章,才发现自己只不过才学了点基础皮毛,实际高级内容、实战演练、原理都没有了解,果然还得是谦逊前进呀。

1.2 基本类型

1.2.1 String

setnx mykey 111

1.2.2 List

先进去的后出来,可看成栈,也可以做队列

1.2.3 set

值不可重复

1.2.4 Hash

key map

1.2.5 Zset

zset key score value

-- z1集合添加元素m2,分数为2
zadd z1 2 m2
--逆序从索引1开始输出2个元素
zrevrange z1 1 2 withscores
-- 查询z1中最大1000分最小0分,0:偏移量小于等于1000的3个元素
zrevrangeByScore z1 1000 0 withscores limit 0 3
-- 1:代表小于上次返回最大分数的3个元素(分数相同会存在重复返回)
zrevrangeByScore z1 上次返回最大分数 0 withscores limit 1 3

1.3 连接池

1.3.1 连接池类型

1.jedis

2.SpringDataRedis:提供了redisTemplate

1.3.2 RedisTempalte

1.3.2.1 基本概念

1.内部实现了jdk序列化

2.关于乱码:由于SpringMVC内置了jackson-Binder之类的序列化

?2. 实战篇

2.1. 短信登录(略)

之前做过很多了,个人简单看了写原理就跳过了

2.2. 数据缓存

2.2.1 基本概念

2.2.1.1 缓存更新策略

  1. 采用删除缓存
  2. 如何保证缓存和数据库数据一致性单体项目:缓存和数据库放到同一个事务中分布式事务:TCC事务方案
  3. 先操作数据库or先删除缓存(这里一般指的是用户请求,商家后台更新数据的情况,当然,整个操作用事务或者分布式锁实现也行)先删缓存,再操作数据库:A先删了,B在A未更新前读取写入缓存就会写入错误数据先操作数据库,再操作缓存:A先操作数据库,B在A操作前读取数据库写入缓存也会读取到错误数据延时双删(请求前删除,更新库后延时3~5秒,然后再删):之所以延时就是为了保证A删缓存操作数据库后未执行完,B一来又读取错误数据,然后发生脏读,就是为了保证B执行完,不过俺觉得意义不大,并发情况下还是得用事务或者分布式锁。简单了解下吧。

2.2.1.2 乐观锁/悲观锁

乐观锁 + 事务:能实现数据一致性,适合并发竞争小的情况(因为避免了数据频繁更新,采用的是CAS capare and swap自旋或版本号机制2种方式)

悲观锁 + 事务:每次读取前加锁

2.3 分布式锁

2.3.1 基本概念

分布式锁:分布式系统或集群模式下多线程之间互相可见并且互斥的锁

2.3.2 Redisson原理

2.3.2.0 分布式锁问题

基于setnx导致的分布式锁存在4个问题,而Redisson可以解决:

1. 不可重入:setx线程同一个线程可以重复获取同一把锁
1. 不可重试:setnt获取锁只尝试一次没有重试
1. 超时释放:setnx业务执行过长就会自动被删除了锁。(但是如果你不设置超时时间服务重启了的话那后续key值一直存在)
1. 主从一致性:setnx中,主设置了锁,宕机后从库变成主库,然后外部依然能获取锁

2. Redisson可重入锁

获取锁:

// 判断是否存在,能获取则获取
if (redis.call('exists', KEYS[1]) == 0) 
	then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
	redis.call('pexpire', KEYS[1], ARGV[1]); 
	return nil; 
end; 
//根据threadid判断锁是不是自己的,获取锁次数加一,然后重置有效期
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) 
    then 
    redis.call('hincrby', KEYS[1], ARGV[2], 1); 
	redis.call('pexpire', KEYS[1], ARGV[1]); 
	return nil; 
end; 
//否则返回锁获取失败
return redis.call('pttl', KEYS[1]);

? 释放锁:

//如果锁次数=0,则直接释放
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) 
    then return nil;
end; 
//如果锁次数大于0,重置有效期,数减一然后返回。否则说明锁==0,可以直接删除发布订阅
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if (counter > 0) 
    then 
    redis.call('pexpire', KEYS[1], ARGV[2]); 
	return 0; 
else 
    redis.call('del', KEYS[1]); 
	redis.call('publish', KEYS[2], ARGV[1]);   //删除锁有订阅,供后续获取锁失败重试机制的线程使用
	return 1; 
end; 
return nil;

2.3.2.2 基本流程

//获取锁和释放锁的源码在这2个函数内容
// tryLock的3个参数: 获取锁等待时间(默认不等待)   释放锁时间, 时间单位
boolean isLock = rLock.tryLock(1, 10, TimeUnit.SECONDS);
rLock.unlock();
1. 加锁
 	1. 线程a先执行Lua脚本获取锁,获取成功(默认释放锁时间30秒)LUA脚本保证了同样线程id获取锁**可重入**
     	1. 若设置了锁释放时间,则不执行看门狗的锁刷新机制
     	2. 若没设置,则默认使用看门口锁释放时间30秒,执行看门狗刷新机制。
         	1. 递归每看门口时间 / 3 = 10秒刷新该线程id的有效期,保证了不会**超时就删锁**(因为默认30秒比10秒多)
 	2. 线程b执行Lua脚本失败,进行等待时间的订阅,然后重新获取锁,收到了订阅就按照等待时间是否剩余进行重试,没收到订阅时间到了就退出----保证了等待时间优先级最高,实现了**可重试**

		2. 解锁
     			1.  释放订阅消息
     			2. 取消看门狗

Redisson分布式锁

? 1.可重入:利用hash 存储线程id与重入次数

? 2.可重试:利用发布订阅和循环等待实现锁重试(保证在等待时间内)

? 3.超时续约:利用watchDog,每隔一段时间(releaseTime / 3)重置

? 4.主从一致性:利用multiLock获取全部主节点锁才能成功(安置了多个主节点)

2.4 商品秒杀

2.4.1 基本概念

? 1、Redis秒杀资格判断(Lua脚本保证原子性)。保存相关信息到阻塞队列

? 2、异步处理订单,扣减库存

2.4.2 秒杀资格–Lua脚本

-- 判断库存、判断用户是否下单,扣减库存

-- 获取参数的券id、用户id
local voucherId = ARGV[1]
local userId = ARGV[2]
local stockKey = 'seckill:stock:' .. voucherId
local orderKey = 'seckill:order:' .. voucherId

--
if(tonumber(redis.call('get', stockKey)) <= 0) then
    return 1
end
if(redis.call("sismember", orderKey, userId) == 1) then
    return 2
end
redis.call('incrby', stockKey, -1)
redis.call("sadd", orderKey, userId)
return 0

2.4.3 秒杀下单–阻塞队列异步实现

? 0、提前加载阻塞队列读取

? 1、Redis进行资格判断,将有资格的放入队列

? 2、线程池异步读取队列数据

(问题:阻塞队列是基于jvm内存来存储数据,服务宕机,高并发都会导致数据有问题)

2.4.4 Redis-消息队列

2.4.4.1 基本概念

? 存放消息的队列,双向链表,采用[LPush, BRPOP],或[Rpush, BLPOP]这些实现阻塞队列。

2.4.4.2 List消息队列

? 优点:

1. 基于Redis存储,不受限与JVM内存
	2. 基于Redis数据持久化

? 缺点:

1. 无法避免消息丢失
		2. 只能支持单消费者,无法让多个消费者使用

2.4.4.3 PubSub消息队列

-- 发布
publish order.q1.hello
--订阅
subscribe order.q1
--订阅: 支持通配符 *0个或多个 ?一个或多个 []满足内部条件
PSUBSCRIBE order.*

? 支持多生产,多消费

? 不支持数据持久化,无法避免消息丢失,消息易堆积

2.4.4.4 Stream消息队列

--添加stream队列,*表示由redis生成消息id
-- 队列一:类似于队列中存所有,但是阻塞读取却读取最新的,会存在覆盖
xadd s1 * k1 v1
--队列长度
xlen s1
--读取一条队列消息,从0开始读取
xread count 1 streams s1 0
--读取一条消息,从最新开始读取,永久等待
xread count 1 block 0 streams s1 $
  1. 消息永久存在,可回溯,可阻塞读取,可被多个消费者读取
  2. 由于每次读取最新的,会存在读取前消息漏读被覆盖的风险,和pubsub类型

消息分流:消息分给不同的组里面去

消息标识:类似于书签,标记读到了哪个消息,服务宕机重启后继续从标识读取(这样就避免了消息漏读情况)

消息确认:消息发送后会处于pending,发送完成后发送XACK确认,然后队列移除该消息

消息组模式

? 1、多个消费者争抢读取消息

? 2、可以阻塞读取,消息可回溯

? 3、没有消息漏读风险,消息可回溯

-- 0代表第一个消息,$代表最后一个消息
-- >表示下一个未消费的消息,"其他"表示已消费但未确认的消息. c1表示消费者名。
-- 队列二:发送失败没有被确认的消息进入pengding-list,类似于一个指针标记第一个位置,另一个指针不断往后走
--把stream流s1放入到g1中,从第一个开始
xgroup create s1 g1 0
xreadgroup group g1 c1  count 1 block 2000 streams s1 > 
-- 根据消息id确认消息
xack s1 g1 1655878247936-0
--查看pending-list中所有时间段内的10条消息
xpending s1 g1 - + 10
--读取pending-list中未被确认的第一条消息
xreadgroup group g1 c1 count 1 block 2000 streams s1 0

? [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWIDBab1-1656671557221)(…/…/…/…/…/Roaming/Typora/typora-user-images/image-20220622145459299.png)]

实战

--MKSTREAM: 流不存在则自动创建
xgroup create stream.orders g1 0 MKSTREAM

--Lua脚本往队列丢数据。另起一个线程循环读取队列数据,并做ack确认

2.5 业务汇总

2.5.1 点赞功能

Redis根据文章id—key值增减,然后同步到库表中

2.5.2 关注功能

? 在每次关注用户后,利用follows-id作为key,采用set修改redis中关注情况,然后同步到库表中

2.5.3 Feed流

2.5.3.1 基本概念

? Feed流:主动给用户推送用户感兴趣的东西

? 1.TimeLine:简单内容列表筛选

? 2.智能排序:智能算法过滤

2.5.3.2 实现方案

? 拉模式:用户拉取自己关注人信息,但是关注太多容易耗内存

? 推模式:博主每次发布都推送给自己关注的人,自己粉丝太多也不现实

? 推拉模式:人分成大V和普通人,粉丝分成活跃粉丝和僵尸粉,普通人就拉自己关注人的信息,普通人就推送自己信息给粉丝

2.5.3.3 滚动分页

? 由于传统id排序逆序时会导致数据不断刷新,所有原始limit offset不可取,采用redis中sortset,利用一个定点score来保证读取固定。redis中就采用时间来作为scoer

2.5.3.4 店铺距离

? 采用Geo举例导入计算

2.5.3.5 用户签到

? 由于在库表中存每个用户每日签到数据量太大,所以可以采用bit位来存储,只要把时间作为key值,也极大方便后续查询统计日期

2.5.3.6 PV/UV

? 统计PV,UV这些可以使用hyperloglog允许些许误差。

?3.高级篇

3.1. 分布式缓存

3.1.1 RDB

? RDB:默认save是主进程保存,bgsave是fork一个子进程进行保存

3.1.2 AOF

? AOF: 记录指令,然后替换上个aof文件

3.1.3 进程缓存

? 分布式缓存:访问缓存有网络开销,集群可以共享,缓存数据量大

? 进程缓存:访问本地内存速度更快,但容量有限

3.1.4 Canal

? canal就是模拟salve把Redis-master的binlog读取到slave运行。

? 每当数据库数据修改,canal就监听然后修改数据到数据库中

3.2. 实践设计

3.2.1 基本设计

? 1.key 的设计 [业务名称]:[数据名]:[id]

2. 删除bigkey:另起一个线程异步删除值
2. **建议使用hash存储**,key可以使用数据量 %100这种,field使用key,value(内存占用非常少,因为使用zipList,默认不要超过key值500)
2. 批处理入库数据:sadd,mset入库快
2. Redis主从能达到上w级别的QPS,尽量不要搭建集群。

3.2.2 服务器优化

? 1.尽量不要开启持久化功能

? 2.建议开启AOF持久化

?4.原理篇

4.1 数据结构

4.1.1 基本概念

? Redis:底层是C写的

4.1.2 动态字符串SDS

? Redis中key, value都是采用单个或多个字符串SDS来存储,SDS本质是个结构体,分成字符串头(记录字符串长度)、体(存储真实数据)

4.1.3 IntSet

? int的set:整数唯一数组,内部采用二分查询查询

4.1.4 Dict

? 由3部分组成:dictHashTable哈希表,dictEntry哈希结点,dict字典

哈希表构成就是dicEntry这种key, value值

Dict字典包含2个哈希表,用于扩容和收缩,底层就是数组+链表来解决hash冲突

(总结dict底层就是hash表,是有数组和单向链表来实现,其中保存的就是key, value的Entry键值对,用指针这种来SDS对象)

4.1.5 ZipList

? 压缩列表是由连续内存空间的列表,没有使用指针链接,而是记录了上个节点寻址。因为如果采用dict这种指针选择,内存碎片太多,指针字节占用太多。

4.1.6 QuickList

? 节点为zipList的双向链表,兼容了zipList申请连续过多的内存空间,与链表指针消耗过多内存空间的优点。

4.1.7 SkipList跳表 *

? 跳表是链表,元素按升序排序,然后不断在元素之间往上建立指针,和MySQL索引建立类似,方便后续查找,CURD效率与红黑树一样log(n)

4.1.8 RedisObject

? RedisObject包含了5中数据类型,然后5中下面编码方式就是上面跳表,zipList,quickList这些。(typerloglog,bitmap,bitmap底层也就是string,zset)

4.2 网络模型

4.2.1 IO多路复用

? 利用单线程同时监听多个服务。分为select,poll, epoll。

select每次都要拷贝监听服务的FD从用户到内核耗时

epoll就把就绪的监听服务FD保存下来,然后每次就不用遍历所有FD

4.2.2 模型比较

? 目前还是IO多路复用用的比较多

4.2.3 Redis网络模型

? Redis6.0之后引入了多线程,之前都是单线程

4.2.4 Redis策略

? Redis中采用dict记录了key的TTL时间

4.2.4.1 过期-删除策略

1. 惰性清理:每次查找key,过期就删除
1. 定期清理:定期抽取key,过期就删除

4.2.4.2 淘汰策略

? 8种策略:默认不淘汰任何key,内存满了不允许写入行数据

相关推荐

Redis合集-使用benchmark性能测试

采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...

Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好

最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...

redission YYDS(redission官网)

每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...

从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道

2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...

SpringBoot系列——实战11:接口幂等性的形而上思...

欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...

Java面试题: 项目开发中的有哪些成长?该如何回答

在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...

互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?

你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...

每日一题 |10W QPS高并发限流方案设计(含真实代码)

面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...

Java面试题:服务雪崩如何解决?90%人栽了

服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...

面试题官:高并发经验有吗,并发量多少,如何回复?

一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...

瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪

“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...

PHP技能评测(php等级考试)

公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...

你的简历在HR眼里是青铜还是王者?

你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...

取消回复欢迎 发表评论: