项目亮点:
- 通过RocketMQ延时消息特性,完成用户购票10分钟后未支付情况下取消订单功能
- 封装缓存组件库避免注册用户时,用户名全局唯一带来的缓存穿透问题,减轻数据库访问压力
- 通过Redis Lua脚本原子特性,完成用户购票令牌分配,通过令牌限流以应对海量用户需求
- 通过订单号和用户信息复合分片算法完成订单数据分库分表,支持订单号和用户查询维度
(一)RocketMQ
- 针对延期关闭订单,除了用 RocketMQ 还有什么方案?各自优缺点?
2.MQ的优势/特点主要体现在哪些方面?优点
缺点
适用场景
数据库轮询
·实现简单,不需要额外中间件
·数据库负载增加
·实时性差
小型项目
信息队列Rocket MQ
·实时性好
·用中间件,成本高,复杂
大型项目高并发项目
·削峰填谷:MQ可以平滑处理系统的峰值流量,通过缓冲和调节消息处理速率,避免了系统因突发高负载而崩溃或响应变慢的情况
·异步通信:发送者可以将消息发送到队列后立即返回,而不必等待接收者处理完毕。这种异步通信可以提高系统的并发性和响应速度,增强系统的可伸缩性。
3.还有哪些其他消息队列?
4.解决高并发问题除了rocketMQ还有哪些方式?
- (二)缓存穿透-用户注册
- 通俗解释:
- 缓存是用来存储经常使用的数据的一种高速存储介质,以便快速访问。
- 而缓存穿透指的是当有大量的请求查询一个不存在于缓存中的数据时,这些请求会穿过缓存层直接查询数据库或其他存储,导致数据库负载过大,甚至宕机。
对不存在的key(eg. username)进行缓存,值设为null | 布隆过滤器 | Redis的 Set 集合,存储已注册用户名 | 先查缓存,用分布式锁保证只有一个线程访问数据库 | |
原理 | input: username output: ·可能在里面 ·一定不在里面 | ·Set集合特点:不会存储重复的元素 ·过程:每次有人想新注册用户名,系统会查询这个集合判断用户名是否存在。 | ·在高并发的情况下,先去缓存查询,如果缓存没有,再使用分布式锁,确保只有一个人能够去查询数据库 | |
缺点 | 用户体验不友好 (eg. user A请求注册 nb,但是没有用这个名字 ;user B想用这个名词,60s内不可被注册) | ·用概率性的方法判断元素是否存在,可能会出现误判; ·不提供删除元素的操作(初衷是高效搜索) | ·占用内存较多,复杂。不适合实际应用。 | 如果在用户注册高峰期,只有一个线程访问数据库,这可能会导致大量用户的注册请求缓慢或超时。 |
适用场景 | 误判可以被容忍 eg大规模数据、需要快速判断数据是否存在。 |
最终方案:
缺点:
1.查询性能消耗增加:一次查询-->两次查询
2.存储损耗增加
3.如果用户频繁申请账号再注销
可能导致redis set结构变得庞大,增加存储和查询负担。
针对缺点3.解决方案:
- 异常行为限制:同一个card_id只可以用于注销5次,超过这个次数,禁止该证件号再次用于注册账号
- 缓存分片处理:对redis进行分片。以防 “存储注销user” 的set 过大,出现redis 大key(一个key里面值太多)问题。
(三)令牌分配和令牌限流
- Q:如何保障购票时系统承载高并发而不出问题?
- A:通过Redis Lua脚本原子特性,完成用户购票令牌分配,通过令牌限流以应对海量用户需求
原子性操作:
在单个步骤中完成的操作,不会被中断,也不会被其他并发操作影响。
2. redis的底层实现数据结构是什么?
5种:string list hash set zset;
Redis 的底层使用键值对(key-value pairs)来实现的。
Redis 将数据存储在内存中,并使用类似于字典的数据结构来管理这些键值对。
每个键都是一个唯一的字符串,而值则可以是各种不同类型的数据结构,如字符串、哈希表、列表、集合、有序集合等。
再挖掘底层使用什么实现redis?????
举例:
1.字符串类型键值对:
"username" -> "john_doe"
"age" -> "30"
"city" -> "New York"
2.哈希表类型键值对hash:
"user:123" -> {"username": "john_doe", "age": "30", "city": "New York"}
"product:456" -> {"name": "RedisBook", "price": "25.00", "quantity": "100"}
Q:哈希表数据结构的键为什么可以是字符串?字符串是怎样通过哈希函数获得最终的key值呢??
3.列表类型键值对list:
"recent_logins" -> ["user123", "user456", "user789"]
"queue:tasks" -> ["task1", "task2", "task3"]
4.集合类型键值对set
"followers:user123" -> {"user456", "user789", "user890"}
"tags:post123" -> {"redis", "database", "nosql"}
5.有序集合类型键值对zset:
"leaderboard:weekly" -> {("user123", 500), ("user456", 400), ("user789", 300)}
"top_posts:category:redis" -> {("post123", 100), ("post456", 90), ("post789", 80)}
(四)分库分表
通过 "订单号和用户信息 "复合分片算法完成订单数据分库分表,
支持订单号和用户查询维度
· 通俗解释:
假设我们有多个数据库实例(DB1、DB2、DB3...)和每个数据库实例中有多个数据 表 (Table1、Table2、Table3...)。我们可以使用订单号和用户信息来生成一个哈希值,然 后根据这个哈希值来确定订单数据存储在哪个数据库实例和哪个数据表中。
举个例子,假设我们有订单号为1001的订单,并且订单是由用户 "Alice" 下的。我们可以将 订 单号和用户信息进行哈希运算,得到一个哈希值,比如说这个哈希值为 123。然后,我们 根据这个哈希值来确定这个订单数据存储在哪个数据库实例和数据表中,比如说存储在 DB2 的 Table3 中。
哈希函数---计算出----哈希值:
生成哈希值通常使用哈希函数进行计算,哈希函数是一种将任意长度的输入(订单号和用户 信息)映射为固定长度的输出(哈希值)的函数。在分库分表的场景中,我们可以将订单号 和 用户信息组合成一个字符串,然后使用哈希函数对这个字符串进行计算,得到一个固定长 度的哈希值。
http://t.csdnimg.cn/EwRpX 分库分表的两种方式(垂直拆分&水平拆分)
面试常见问题:
- 1. 分库还是分表?还是分库分表?考虑的要素?
- 2. 选择哪个字段作为分片键?选择单个分片键还是复合分片键?
考虑因素:均匀性(让数据均匀分布在不同节点和分区内)、查询性能、扩展性、数据特点(根据业务数据特点)。
- 单个分片键:如果业务需求简单且数据量不大,可以选择单个字段作为分片键。这样可以简化系统设计和实现,并且易于管理和维护。
- 复合分片键:如果业务需求复杂或者数据量较大,可以考虑使用多个字段组合作为分片键。复合分片键可以更精确地控制数据的分布,以满足不同查询需求,并且可以避免单个字段带来的热点问题。
(五) 车票查询-Redis vs ES
.why采用 Redis?而不是ElasticSearch。
-
Redis
ElasticSearch(有弹性的)
最关键
搜索条件(例如出发地,到达地,乘客类型...)大部分是前端筛选,没有触发后端请求。
实时性
redis以内存为基础,读取速度快;
分布式(在分布式和集中式架构之下都可以用)
以磁盘为基础,读取速度慢;
分布式(数据同步)导致延迟;
并发性
·并发读写差:单线程,但是可以通过多路复用技术支持高并发的读写操作
·并发查询强:键值对查询
·并发读写强:多线程
·并发查询差:复杂全文搜索(大规模查询,性能差)
部署成本
成本小,容易扩展
大
·多路复用技术:
-
同时监视多个输入输出通道,实现多个连接和数据流的处理(有电话来,立即接听,并且可以实时切换不同电话之间的通话)