本文为【场景题合集】初版,后续还会进行优化更新,欢迎大家关注交流~
hello hello~ ,这里是绝命Coding------老白~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
💥个人主页 :绝命Coding-CSDN博客
💥 所属专栏 :后端技术分享
这里将会不定期更新有关后端、前端的内容,希望大家多多点赞关注收藏💖
更多历史精彩文章(篇幅过多,不一一列出):(简历相关)
求职经验分享(1):一份合格的简历应该如何写?-CSDN博客(推荐)
求职经验分享(2):简历如何优化以及如何应对面试【后端篇】-CSDN博客
(项目亮点相关)
大厂面试官赞不绝口的后端技术亮点【后端项目亮点合集(1):Redis篇】-CSDN博客
大厂面试官赞不绝口的后端技术亮点【后端项目亮点合集(2)】-CSDN博客
(八股文)
大厂面试官问我:Redis处理点赞,如果瞬时涌入大量用户点赞(千万级),应当如何进行处理?【后端八股文一:Redis点赞八股文合集】_java中redis如何实现点赞-CSDN博客大厂面试官问我:布隆过滤器有不能扩容和删除的缺陷,有没有可以替代的数据结构呢?【后端八股文二:布隆过滤器八股文合集】_布隆过滤器不能扩容-CSDN博客
.........
场景题
10亿的数据,找中位数
(1)先通过外排序进行排序再寻找中位数
先将这10G的数据等分成5份存储到硬盘中,然后依次读入一份到内存里面,进行排序,然后将这5份数据进行归并得到最后的排序结果,然后找出中位数第5G大
(2)堆排序(转换为求前5G大的元素)
我们知道利用堆排序处理海量数据的topK是非常合适不过了,因为它不用将所有的元素都进行排序,只需要比较和根节点的大小关系就可以了,同时也不需要一次性将所有的数据都加载到内存;对于海量数据而言,要求前k小/大的数,我们只需要构建一个k个大小的堆,然后将读入的数依次和根节点比较就行了(当然这里的前提是内存需要存的下k个数)
(3)分组
- 每个数4字节,2G能存5亿个。
- 分组,每个组内按照最大基数,分布桶。
- 然后每组都从0号桶开始,拿出数据,当累加到25亿的时候,所有分组的那个桶里都可能存了中位数。因此把问题转换到从这些桶内找中位数。可以继续这个方法。直到只剩一个数。
10亿的数据,1g内存,如何统计
(1)外部排序:
将数据分批读入内存,对每批数据进行排序。
将排序好的数据块写入磁盘。
最后合并所有已排序的数据块,进行统计分析。
(2)两次扫描法:
第一次扫描数据,统计每个值出现的频率,并将结果写入磁盘。
第二次扫描数据,读取磁盘上的频率统计结果,进行最终统计。
(3)分治法:
将数据划分为多个小块,每个小块使用有限内存进行统计。
然后对各个小块的统计结果进行合并。
(4)MapReduce:
将数据分散到多台机器上进行统计。
每台机器统计自己部分的数据,然后汇总结果。
大数据情况下求去最大的top K个
正常排序不可行的原因:在32位的机器上,每个float类型占4个字节,一亿个就要占400MB的存储空间,对于一些可用内存小于400MB的就不可行,显然没办法一次性读入内存进行排序,即使机器可以,方法也不高效
- 局部淘汰,使用冒泡排序
- 每一轮排序能获得最大的那一个,所以经过K轮就可以获得topK
- 时间复杂度 O(KN),空间复杂度 O(K)
- 使用堆来实现
- 用小顶堆 ,堆顶是最小值。那么新来一个数据,如果小于堆顶,那么就直接抛弃。否则插入堆中
- 时间复杂度O(NlogK),空间复杂度O(K)
如果是找最大的10个,就用10个堆
- 归并
- 把大数据分成N份,每一份内求topK。然后用快排的思想,找到正好为K的那个元素,那么左边的就是答案了。
4.快速排序
找个基准数,如果这个基准数位置大于N,就继续从左边找,否则从右边找
给一个文件,很大,几个G,我们内存只有256MB,每个文件每行都有一个数字,如何做排序呢?
(1)分块读取:
将文件分成多个小的数据块,每个块的大小不超过 256 MB。
将这些数据块逐个读入内存。
(2)内部排序:
对每个小的数据块进行内部排序,可以使用常见的排序算法,如快速排序、归并排序等。
(3)归并排序:
将排序好的小块数据,逐个读入内存进行归并排序。
归并操作时,可以利用磁盘作为临时存储空间,减少内存占用。
1亿行的数字排序
(1)分治
将1亿行分成1000个文件。每个文件内排序,然后每个文件维护一个指针,每次读一个,比较,选最小的加入,该文件的指针+1。
(2)快速排序
(3)归并排序
大文件中返回频数最高的100个词
此处1G文件远远大于1M内存,分治法,先hash映射 把大文件分成很多个小文件 ,
具体操作如下:读文件中,对于每个词x,取hash(x)%5000,然后按照该值存到5000个小文件(记为f0,f1,...,f4999)中,这样每个文件大概是200k左右(每个相同的词一定被映射到了同一文件中)
(2)对于每个文件fi,都用hash_map做词和出现频率的统计,取出频率大的前100个词(怎么取?topK问题,建立一个100个节点的最小堆),把这100个词和出现频率再单独存入一个文件
(3)根据上述处理,我们又得到了5000个文件,归并文件取出top100
/
另外,对于十万个单词中找出重复次数最高的十个单词的问题,可以使用哈希表 来统计每个单词出现的次数,并维护一个大小为10 的最小堆,遍历哈希表,对于每个单词的出现次数,如果大于堆顶元素,则将堆顶元素替换为当前单词,并对堆进行调整。最终堆中的元素就是重复次数最高的十个单词。
数据类,两个方法插入数据,复杂度O(lgN),查找,复杂度是O(1)。如何实现
- 最大堆和最小堆,维护两个堆堆大小平衡,差距不超过1。最大堆存较小的一半,最小堆存较大的一半。取中位数的时候,取两个堆的堆顶。
你知道网站短链和长链吧?要你设计一个长短链解析系统,且你要考虑有的短链属于热点链接,访问量很多,你怎么设计?
原理:
302 的 HTTP 响应,在响应中包含了长链接地址。浏览器收到响应后,转而去请求长链接地址。
链系统设计主要得解决如下两个问题:
- 如何根据长链生成唯一短链?
- 如何保存短链与长链的映射关系?
我们有 2 个思路生成一个唯一短链,分别是:
- 使用哈希算法生成唯一值
- 使用分布式唯一 ID 生成作为锻炼 ID
索引优化
如果使用关系型数据库的话,对于短链字段需要创建唯一索引,从而加快查询速度。
增加缓存
并发量小的时候,我们都是直接访问数据库。但当并发量再次升高时,需要加上缓存抗住热点数据的访问。
读写分离
短链服务肯定是读远大于写的,因此对于短链服务,可以做好读写分离。
分库分表
如果是商用的短链服务,那么数据量上亿是很正常的,更不用说常年累月积累下的量了。这时候可以一开始就做好分库分表操作,避免后期再大动干戈。
防止恶意攻击
开放到公网的服务,什么事情都可能发生,其中一个可能的点就是被恶意攻击,不断循环调用。
一开始我们可以做一下简单地限流操作,例如:
-
没有授权的用户,根据 IP 进行判断,1 分钟最多只能请求 10 次。
-
没有授权的用户,所有用户 1 分钟最多只能请求 4000 次,防止更换 IP 进行攻击。
简单地说,就是要不断提高攻击的成本,使得最坏情况下系统依然可以正常提供服务。
一个非常大的文件,多行,每行有多个字符串。怎么找出出现频率最高的字符串,以及出现次数。只用一台机器,内存有限。
(1)分块读取:
将文件划分成多个小的数据块,每个块的大小不超过可用内存。
逐个读取数据块到内存中进行处理。
(2)内存统计:
对于每个数据块,使用哈希表(字典)统计每个字符串出现的频率。
由于内存有限,只保留当前块中出现频率最高的前 K 个字符串及其出现次数。
(3)外部归并:
将每个数据块处理后得到的前 K 个高频字符串,写入临时文件。
对这些临时文件进行外部归并排序,得到全局出现频率最高的字符串及其出现次数。
有千万级数据,如何判断一个整数是否存在
对于千万级的数据,如果要判断一个整数是否存在,可以考虑以下几种方法:
- 哈希表 :哈希表(如 Java 中的 HashSet 或 HashMap)可以在 O(1) 的时间复杂度内判断一个元素是否存在。但是,哈希表需要大量的内存空间来存储数据和哈希表本身。如果内存空间有限,那么这可能不是一个好的选择。
- 布隆过滤器 :布隆过滤器是一种空间效率极高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是它存在一定的误识别率和删除困难。
- 位图 :如果整数的范围不大,例如都是非负整数,那么可以使用位图(BitSet)来判断一个整数是否存在。位图的每一位代表一个整数,如果整数存在,那么对应的位就置为 1。位图需要的内存空间远小于哈希表,但是如果整数的范围非常大,那么位图可能就不适用了。
- 数据库 :如果数据已经存储在数据库中,那么可以直接使用 SQL 查询来判断一个整数是否存在。这种方法的效率取决于数据库的性能和数据的索引情况。
- 二分查找 :如果数据是有序的,那么可以使用二分查找来判断一个整数是否存在。二分查找的时间复杂度是
O(log n)
,但是它需要数据是有序的。
千万级数据用布隆过滤器初始化的时候 redis 太慢了,有没有什么好方法
对于千万级的数据,如果直接使用Redis来初始化布隆过滤器确实可能会比较慢。这是因为Redis的操作都是网络IO操作,相比于内存操作,网络IO的速度要慢很多。以下是一些可能的优化方法:
- 批量操作 :Redis支持批量操作,可以一次性发送多个命令,然后一次性接收所有的结果。这样可以减少网络IO的次数,提高效率。
- 管道操作 :Redis还支持管道操作,可以一次性发送多个命令,不需要等待上一个命令的结果就可以发送下一个命令。这样可以进一步减少网络IO的次数。
- 多线程或多进程 :可以使用多线程或多进程来并行初始化布隆过滤器。每个线程或进程负责一部分数据,这样可以充分利用多核CPU,提高效率。
- 使用更快的存储 :如果条件允许,可以考虑使用更快的存储,例如SSD硬盘,或者直接在内存中初始化布隆过滤器。
- 预热数据 :如果数据不经常变动,可以考虑预先计算好布隆过滤器的状态,然后保存在文件或数据库中。需要使用时,直接加载预先计算好的状态,这样可以避免实时初始化布隆过滤器
秒杀系统
(1)秒杀开始的时候,会有大量用户同时参与进来,因此秒杀系统一定要满足 高并发和 高性能 。
(2)为了保证秒杀整个流程的顺利进行,整个秒杀系统必须要满足 高可用
(3)除此之外,由于商品的库存有限,在面对大量订单的情况下,一定不能超卖,我们还需要保证 一致性
挑战:
1、突增的服务器及网络需求
通常情况下,双 11 的服务器使用是平时的 3-5 倍,网络带宽是平时 N倍。
2、业务高并发,服务负载重
实际情况,在高并发的实际场景下,服务器处于高负载的状态,网络带宽被挤满,在这个时候平均响应时间会被大大增加。随着用户数量的增加,数据库连接进程增加,需要处理的上下文切换也越多,服务器造成负载压力越来越重。
3、业务耦合度高,引起系统"雪崩"
更可怕的问题是,当系统上某个应用因为延迟而变得不可用,用户的点击越频繁,恶性循环最终导致"雪崩",因为其中一台服务器挂了,导致流量分散到其他正常工作的机器上,再导致正常的机器也挂,然后恶性循环,将整个系统拖垮。
秒杀架构设计思路:
将请求拦截在系统上游 ,降低下游压力:秒杀系统特点是并发量极大 ,但实际秒杀成功的请求数量却很少,所以如果不在前端拦截很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时。
充分利用缓存(redis):利用缓存可极大提高系统读写速度。
消息中间件(ActiveMQ、Kafka等):消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程,后台业务根据自己的处理能力,从消息队列中主动的拉取请求消息进行业务处理。
前端设计方案
页面静态化:将活动页面上的所有可以静态的元素全部静态化 ,并尽量减少动态元素。通过CDN来抗峰值。
禁止重复提交:用户提交之后按钮置灰,禁止重复提交
用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流
后端设计方案
服务端控制器层(网关层)
限制uid(UserID)访问频率:我们上面拦截了浏览器访问的请求,但针对某些恶意攻击或其它插件,在服务端控制层需要针对同一个访问uid,限制访问频率。
服务层
上面只拦截了一部分访问请求,当秒杀的用户量很大时,即使每个用户只有一个请求,到服务层的请求数量还是很大。比如我们有100W用户同时抢100台手机,服务层并发请求压力至少为100W。
采用消息队列缓存请求 :既然服务层知道库存只有100台手机,那完全没有必要把100W个请求都传递到数据库啊,那么可以先把这些请求都写到消息队列缓存一下,数据库层订阅消息减库存,减库存成功的请求返回秒杀成功,失败的返回秒杀结束。
利用缓存应对读请求 :比如双11秒杀抢购,是典型的读多写少业务,大部分请求是查询请求,所以可以利用缓存分担数据库压力。
利用缓存应对写请求 :缓存也是可以应对写请求的,比如我们就可以把数据库中的库存数据转移到Redis缓存中,所有减库存 操作都在Redis中进行,然后再通过后台进程把Redis中的用户秒杀请求同步到数据库中。
数据库层
数据库层是最脆弱的一层,一般在应用设计时在上游就需要把请求拦截掉,数据库层只承担"能力范围内"的访问请求。所以,上面通过在服务层引入队列和缓存,让最底层的数据库高枕无忧。
比如:利用消息中间件和缓存实现简单的秒杀系统
Redis是一个分布式缓存系统,支持多种数据结构,我们可以利用Redis轻松实现一个强大的秒杀系统。
我们可以采用Redis 最简单的key-value数据结构,用一个原子类型的变量值(AtomicInteger)作为key,把用户id作为value,库存数量便是原子变量的最大值。对于每个用户的秒杀,我们使用 RPUSH key value插入秒杀请求, 当插入的秒杀请求数达到上限时,停止所有后续插入。
然后我们可以在台启动多个工作线程,使用 LPOP key 读取秒杀成功者的用户id,然后再操作数据库做最终的下订单减库存操作。
当然,上面Redis也可以替换成消息中间件如ActiveMQ、Kafka等,也可以将缓存和消息中间件 组合起来,缓存系统负责接收记录用户请求,消息中间件负责将缓存中的请求同步到数据库。
秒杀架构设计总结:
限流: 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。
削峰:对于秒杀系统瞬时会有大量用户涌入,所以在抢购一开始会有很高的瞬间峰值。高峰值流量是压垮系统很重要的原因,所以如何把瞬间的高流量变成一段时间平稳的流量也是设计秒杀系统很重要的思路。实现削峰的常用的方法有利用缓存和消息中间件等技术。
异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,其实异步处理就是削峰的一种实现方式。
内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,由于数据库读写属于磁盘IO,性能很低,如果能够把部分数据或业务逻辑转移到内存缓存,效率会有极大地提升。
可拓展:当然如果我们想支持更多用户,更大的并发,最好就将系统设计成弹性可拓展的,如果流量来了,拓展机器就好了。像淘宝、京东等双十一活动时会增加大量机器应对交易高峰
/
一些概念
热点隔离 。秒杀系统设计的第一个原则就是将这种热点数据隔离出来,不要让1%的请求影响到另外的99%,隔离出来后也更方便对这1%的请求做针对性优化。针对秒杀我们做了多个层次的隔离:
业务隔离 把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名,从技术上来说,卖家报名后对我们来说就是已知热点 ,当真正开始时我们可以提前做好预热。
系统隔离 系统隔离更多是运行时的隔离,可以通过分组部署的方式和另外99%分开。秒杀还申请了单独的域名,目的也是让请求落到不同的集群中。
数据隔离 秒杀所调用的数据大部分都是热数据,比如会启用单独cache集群或MySQL数据库来放热点数据,目前也是不想0.01%的数据影响另外99.99%。
当然实现隔离很有多办法,如可以按照用户来区分,给不同用户分配不同cookie,在接入层路由到不同服务接口中;还有在接入层可以对URL的不同Path来设置限流策略等。服务层通过调用不同的服务接口;数据层可以给数据打上特殊的标来区分。目的都是把已经识别出来的热点和普通请求区分开来。
动静分离
前面介绍在系统层面上的原则是要做隔离,接下去就是要把热点数据进行动静分离,这也是解决大流量系统的一个重要原则。如何给系统做动静分离的静态化改造我以前写过一篇《高访问量系统的静态化架构设计》详细介绍了淘宝商品系统的静态化设计思路,感兴趣的可以在《程序员》杂志上找一下。我们的大秒系统是从商品详情系统发展而来,所以本身已经实现了动静分离
除此之外还有如下特点:
把整个页面Cache在用户浏览器
如果强制刷新整个页面,也会请求到CDN
实际有效请求只是"刷新抢宝"按钮
这样把90%的静态数据缓存在用户端或者CDN上,当真正秒杀时用户只需要点击特殊的按钮"刷新抢宝"即可,而不需要刷新整个页面,这样只向服务端请求很少的有效数据,而不需要重复请求大量静态数据。秒杀的动态数据和普通的详情页面的动态数据相比更少,性能也比普通的详情提升3倍以上。所以"刷新抢宝"这种设计思路很好地解决了不刷新页面就能请求到服务端最新的动态数据。
基于时间分片削峰
熟悉淘宝秒杀的都知道,第一版的秒杀系统本身并没有答题功能,后面才增加了秒杀答题,当然秒杀答题一个很重要的目的是为了防止秒杀器,2023年09月04日秒杀非常火的时候,秒杀器也比较猖獗,而没有达到全民参与和营销的目的,所以增加的答题来限制秒杀器。增加答题后,下单的时间基本控制在2s后,秒杀器的下单比例也下降到5%以下。
其实增加答题还有一个重要的功能,就是把峰值的下单请求给拉长了,从以前的1s之内延长到2~10s左右,请求峰值基于时间分片了,这个时间的分片对服务端处理并发非常重要,会减轻很大压力,另外由于请求的先后,靠后的请求自然也没有库存了,也根本到不了最后的下单步骤,所以真正的并发写就非常有限了。其实这种设计思路目前也非常普遍,如支付宝的"咻一咻"已及微信的摇一摇。
除了在前端通过答题在用户端进行流量削峰外,在服务端一般通过锁或者队列来控制瞬间请求。
先做数据的动静分离
将90%的数据缓存在客户端浏览器
将动态请求的读数据Cache在Web端
对读数据不做强一致性校验
对写数据进行基于时间的合理分片
对写请求做限流保护
对写数据进行强一致性校验
如何设计一个排队系统
是面向单个店还是面向整个区域(确定了是区域),然后说可以用队列的形式存储每类桌子的排队情况,再开一个叫号的队列,把排到的号子移到叫号队列中进行广播。
数据库分表分库后,如何保证所有数据库的id唯一且自增?
(1)(多一个表,每次先来这里获取)
- 这个就是说你的系统里每次得到一个 id,都是往一个库的一个表里插入一条没什么业务含义的数据,然后获取一个数据库自增的一个 id。拿到这个 id 之后再往对应的分库分表里去写入。
(2)(分段,0到1000,1001到2000这种) - 可以通过设置数据库 sequence 或者表的自增字段步长来进行水平伸缩。比如说,现在有 8 个服务节点,每个服务节点使用一个 sequence 功能来产生 ID,每个 sequence 的起始 ID 不同,并且依次递增,步长都是 8。
给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url
方法一:
一、采用Bloom filter,假设布隆过滤器的错误率为0.01,则位数组大小m约为输入元素个数n的13倍,此时需要的哈希函数k约为8个。
元素个数:n = 5G
位数组大小:m = 5G * 13 = 65G = 650亿 即需要650亿个bit位才能达到错误率0.01
而我们拥有的内存可容纳bit位个数:4G * 8bit = 32G bit = 320亿,按此实现错误率大于0.01。
方法二:
采用分治的思想
首先遍历文件 a,对遍历到的 URL 求 hash(URL) % 1000
,根据计算结果把遍历到的 URL 存储到 a0, a1, a2, ..., a999,这样每个大小约为 300MB。使用同样的方法遍历文件 b,把文件 b 中的 URL 分别存储到文件 b0, b1, b2, ..., b999 中。这样处理过后,所有可能相同的 URL 都在对应的小文件中,即 a0 对应 b0, ..., a999 对应 b999,不对应的小文件不可能有相同的 URL。那么接下来,我们只需要求出这 1000 对小文件中相同的 URL 就好了。
接着遍历 ai( i∈[0,999]
),把 URL 存储到一个 HashSet 集合中。然后遍历 bi 中每个 URL,看在 HashSet 集合中是否存在,若存在,说明这就是共同的 URL,可以把这个 URL 保存到一个单独的文件中。
100万数据,四个节点同时操作怎么做
- 分段,每个节点按照id分到25w个数据。
三门问题
一共三个门,一个门后面有汽车,其他两个是空的。你先选了一个,主持人帮你在另外两个里排除了一个,这时候你有一次换的机会,你换不换,为什么,概率是多少?
换。看下面解析,总结来说就是你一开始选的是1/3,而另外的2/3,此时主持人帮你排除了一个,那么剩下的那一个就占了2/3的概率。比手头的1/3高。
A、B两个服务器隔很远,带宽100M,要传输的数据特别大,有什么方法提高效率?
-
数据压缩
-
分块传输:将大数据分成多个较小的块进行传输,而不是一次性传输整个大数据。这样可以减少传输中的延迟,并且在网络出现问题时,可以更容易地从中断处恢复传输。
-
并行传输:将大数据分成多个部分,并使用多个线程或多个连接同时进行传输。通过并行传输可以充分利用带宽,提高传输速度。
-
使用断点续传机制:在传输大数据时,可能会遇到网络中断或传输失败的情况。为了提高效率,可以使用断点续传机制,即在传输中断后,可以从中断处继续传输,而不需要重新传输整个数据。
两个1亿行的文件求交集。
分治+hash
将a,b两个文件通过hash分成1000个文件。每次读入相同hash的a,b子文件,求交集。
设计朋友圈,查朋友圈的接口怎么设计
参数:用户id,可见的好友id列表
- 从缓存中取上一次进入朋友圈时,好友最迟的动态时间
- 取所有好友,获取动态时间>=上次的
- 将不可见的做删除
- 按照时间排序返回
100万单词,找出其中拼错的单词,并输出正确的单词,并分析时间复杂度和空间复杂度
设计一个会议室预订系统,说一下如何预定,预定的流程,如何判断是否冲突,如何设计库表?如何解决对同一时间段统一会议室的预定(分布式锁,以会议室id作为键)?如何查找本人已预订的会议室信息?
大文件排序
对于远高于内存的文件排序。
外归并排序:
对文件分割,然后分别排序
排好序的文件依次读取一个缓冲区的大小,然后进行排序,输出到输出缓冲区,然后保存到结果文件。
如果是数字,可以用位图排序,但是要求比较苛刻:
数字不重复
知道最大值
相对密集,因为没出现的数字也会占用空间
比较适合电话号之类的。
大文本全排序,强调数据是double类型
一个主播,有很多人给她打赏,频繁的加钱,update语句有什么问题
(加了排他锁会阻塞,使用MQ异步)
情景题:如果一个外卖配送单子要发布,现在有200个骑手都想要接这一单,如何保证只有一个骑手接到单子?
这个可以用redis的lpush rpop来实现。
/
Redis的分布式锁
场景题:美团首页每天会从10000个商家里面推荐50个商家置顶,每个商家有一个权值,你如何来推荐?第二天怎么更新推荐的商家?
这个可以用堆排,第二天更新推荐的可以在要更新之前的时候在redis做计算操作,然后放数据库做个同步就行了。
微信抢红包问题
悲观锁,乐观锁,存储过程放在mysql数据库中。
场景题:1000个任务,分给10个人做,你怎么分配,先在纸上写个最简单的版本,然后优化。
全局队列,把1000任务放在一个队列里面,然后每个人都是取,完成任务。
分为10个队列,每个人分别到自己对应的队列中去取务。
场景题:保证发送消息的有序性,消息处理的有序性。
给消息加一个header,识别一下header的syn就行
如何把一个文件快速下发到100w个服务器
边下发,边复制
10亿个数,找出最大的10个。
建议一个大小为10的小根堆。
有几台机器存储着几亿淘宝搜索日志,你只有一台2g的电脑,怎么选出搜索热度最高的十个搜索关键词?
分bucket,每个bucket找出频次最高的10个,总体用分治算法做。
分布式集群中如何保证线程安全?
分布式锁,意在让分布式多线程的环境针对一个共享资源一次性只会有一个线程获取到锁里的资源。分布式锁的实现一般就是redis和zk居多。redis设置一下expire time就行。
10万个数,输出从小到大?
先划分成多个小文件,送进内存排序,然后再采用多路归并排序。
12、有十万个单词,找出重复次数最高十个?
map<String,Integer> 字符串,频次,然后堆排。
快速将一个文件发到大量服务器
多线程和并行处理
多个传输
CDN(内容分发网络)
三个节点,拥有本地缓存,如何保证数据一致性;
设计订单表,有oid,uid,price字段,每天100w数据,业务诉求是根据oid查pirce,
输入1返回0,输入0返回1,有哪些方法,方法越多越好
多级评论的层级显示的数据库设计与实现
数据库设计
在以评论为主的树形显示情况下,可以将评论分为评论表和回复表。评论挂在主题下面,而回复挂在评论下面
评论表:
id 主键
topic_id 主题id
topic_type 主题类型
content 评论内容
from_uid 评论用户id
回复表:
id 主键
comment_id 评论id
reply_id 回复目标id
reply_type 回复类型
content 回复内容
from_uid 回复用户
to_uid 目标用户id
(reply_type:表示回复的类型,因为回复可以是针对评论的回复(comment),也可以是针对回复的回复(reply), 通过这个字段来区分两种情景。)
设计短链接
https://cloud.tencent.com/developer/article/1762482
我们其实就是要将一个长长的链接映射为只有 4 到 7 个字母的字符串。这里我用了 MySQL来存储,存放 short_key 和 original_url 的记录。
主要的列为:
- id: 逻辑主键,BIGINT
- short_key: 短链中的字符串,域名部分一般不需要加进去,加入唯一索引 unique
- original_url: 原长网址,限 256 字符
- 另外,基于业务需要,可以加入业务标识 biz、过期时间 expire_time 等。
在生成 key 的时候,一种最简单的实现方式是使用随机字符串,因为是随机码,所以可能会出现失败,通常就需要重试。随着记录越来越多,就越容易发生 key 重复的情况,这种方案显然不适合数据量大的场景。
我们不容易保证我们随机生成的 key 不重复,但是我们容易实现的就是 id 不重复,我们只要想个办法把 id 和 key 一一对应起来就可以了。
1、加入随机码
2、加入缓存
3、数据库大小写
/
URL短链由固定短域名+唯一字符串组成;
唯一字符串怎么生成?
1)假设保存在数据库有一个表long_short_url,有id,long_url,short_url 三个字段,需求是根据long_url查询对应的short_url,给long_url创建索引,short_url可以通过hash(long_url),但可能会有hash冲突,怎么解决呢?
最简单的话可以给short_url创建唯一索引,如果hash冲突了必定插入不成功,可以对原来的long_url进行加工,在后面拼接上自定义的字符串,再进行hash,然后再插入。也可以不对short_url创建唯一索引,那么需要先查询字符串是否存在,如果不存在,才插入;
2)第二种方法,唯一字符串用分布式id生成,落地实现有uuid、mysql自增主键、redis incr、雪花算法等;
用数据库保存长短链关系合适吗?
看业务,
1)如果短链的存活时间不长 ,并且数据量不大,可以用redis生成 ,并且可以用redis的lua脚本直接判断key是否存在,如果不存在才进行插入;
2)如果短链是长期有效的,并且数据量较大,比如对于每个用户,都需要不同的短链,那么用redis实现就成本太大了,这时候可以用MySQL数据库;
/
这个应该是比较公认的方案了:
分布式ID生成器产生ID
ID转62进制字符串
记录数据库,根据业务要求确定过期时间,可以保留部分永久链接
主要难点在于分布式ID生成。鉴于短链一般没有严格递增的需求,可以使用预先分发一个号段,然后生成的方式。
看了下新浪微博的短链接,8位,理论上可以保存超过200万亿对关系,具体怎么存储的还有待研究。
8核16g计算机统计图书馆内所有书中单词数?
抢红包,100个人同时抢红包,但只能有20人抢到
红包系统其实很像秒杀系统,只不过同一个秒杀的总量不大,但是全局的并发量非常大,比如春晚可能几百万人同时抢红包。
主要技术难点也类似,主要在数据库,减库存的时候会抢锁。另外由于业务需求不同,没办法异步,也不能超卖,事务更加严格。
不能采用的方式:
乐观锁 :手慢会失败,DB 面临更大压力,所以不能采用。
直接用缓存顶,涉及到钱,一旦缓存挂掉就完了。
建议的方式:
接入层垂直切分,根据红包ID,发红包、抢红包、拆红包、查详情详情等等都在同一台机器上处理,互不影响,分而治之。
请求进行排队 ,到数据库的时候是串行 的,就不涉及抢锁的问题了。
为了防止队列太长过载导致队列被降级,直接打到数据库上,所以数据库前面再加上一个缓存 ,用CAS自增控制并发,太高的并发直接返回失败 。
红包冷热数据分离,按时间分表。
分布式ID
主要关键在于是否需要严格递增,严格递增的话效率必然大降。
不需要递增的话比较简单:
一种方式是预先分片,比如十台机器,每台先分一千个ID,一号机从0开始,二号从1000开始等等。缺点是大致上可以被人看出来业务量。
另一种方式是类似雪花算法,每个机器有个id,然后基于时间算一个id,再加上一个递增id。比如如下美团的方案。缺点是机器的时间戳不能回拨,回拨的话会出现问题。
如果要求严格递增,我没找到现成的很好的方案,大概只能单机生成,不能分布式了,然后都去单机上取号。效率的话,类似Redis的数据库大概能到每秒十几二十几万的速度。
/
(1)uuid,无需全局同步就可以保证唯一性,缺点就是比较长
原因:本机生成不耗费资源,目的是用于分布式环境中唯一生成标志码,是由32个16进制数组成,主要包括三部分:
(1)当前日期和时间,UUID的第一个部分是当前日期和时间
(2)时钟序列
(3)全局唯一的IEEE机器识别号(如果有网卡,从网卡获得,没有网卡以其他方式获得)
(2)数据库的自增,每个机器请求时向数据库请求一个id,优点数据库会保证递增且唯一,缺点是需要使用数据库,并且有一定的开销。
(3)Redis的incr命令: 使用Redis的incr命令实现全局自增的计数器。每个机器向Redis请求递增的计数值,并将该计数值作为id。Redis会保证incr命令的原子性,保证了id的唯一性。缺点是需要使用Redis,并且Redis的性能可能会成为瓶颈。
(4)雪花算法: Snowflake算法是一种在分布式系统中生成唯一id的算法。它使用一个64位的整数,其中包含了时间戳、机器id、序列号 等信息。每个机器都有一个唯一的机器id,保证了id的唯一性。Snowflake算法的优点是生成的id比较短且有序。缺点是需要保证机器id的唯一性,并且需要有一个时钟的同步。
微博推送
(1)推模式:推模式就是,用户A关注了用户 B,用户 B 每发送一个动态,后台遍历用户B的粉丝,往他们粉丝的 feed 里面推送一条动态。
(2)拉模式:推模式相反,拉模式则是,用户每次刷新 feed 第一页,都去遍历关注的人,把最新的动态拉取回来。
另外冷热数据分离,用户关系在缓存里面可以设置一个过期时间,比如七天。七天没上线的可能就很少用这个 APP。
10000个小任务给线程池来执行,处理的时间很长,怎么优化
-
任务分批: 将任务分成多个批次,每次从任务列表中取出一部分任务交给线程池处理。这样可以避免一次性将大量任务提交给线程池,减少系统资源竞争。
-
线程池大小调优: 根据系统资源和任务的性质,合理设置线程池的大小。如果任务是 CPU 密集型的,可以设置较少的线程数;如果任务是 I/O 密集型的,可以适当增加线程数。避免线程数过多导致上下文切换频繁,影响性能。
秒杀系统
设计难点:并发量大,应用、数据库都承受不了。另外难控制超卖。
设计要点:
将请求尽量拦截在系统上游,html尽量静态化,部署到cdn 上面。按钮及时设置为不可用 ,禁止用户重复提交请求 。
设置页面缓存,针对同一个页面和uid 一段时间内返回缓存页面。
数据用缓存 抗,不直接落到数据库 。
读数据的时候不做强一致性校验 ,写 数据的时候再做。
在每台物理机上也缓存商品信息等等变动不大 的相的数据
像商品中的标题和描述这些本身不变的会在秒杀开始之前全量推送 到秒杀机器上并一直缓存直到秒杀结束。
像库存这种动态数据 会采用被动失效 的方式缓存一定时间(一般是数秒),失效后再去Tair缓存拉取最新的数据。
如果允许的话,用异步的模式,等缓存都落库之后再返回结果 。
如果允许的话,增加答题教研等验证措施。
其他业务和技术保障措施:
业务隔离。把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名 ,从技术上来说,卖家报名后对我们来说就是已知热点 ,当真正开始时我们可以提前做好预热。
系统隔离。系统隔离更多是运行时的隔离,可以通过分组部署的方式和另外 99% 分开。秒杀还申请了单独的域名 ,目的也是让请求落到不同的集群中。
数据隔离。秒杀所调用的数据大部分都是热数据,比如会启用单独 cache 集群 或 MySQL 数据库来放热点数据,目前也是不想0.01%的数据影响另外99.99%。
另外需要复习缓存穿透、雪崩等等问题,主要的流量都落在了缓存数据库上,需要针对缓存数据库的高可用作保障。
场景题答题小建议:
架构设计题目远不止这些,我觉得主要从以下几个方面准备:
先了解常用算法,针对解决各种问题能用哪些算法,比如大文件排序用外排序,大量数据中的命中判断用位图/布隆过滤器等等。
注意扩展性、多考虑极端情况,多问自己几个为什么。比如说起单机的限流算法想想分布式的怎么做。
实在不知道怎么弄的叙述自己的思考过程,着重展示自己考虑周全、思维缜密。
如何设计登陆黑名单
设计登录黑名单的主要目的是 防止恶意用户或者机器人进行暴力破解或者恶意登录 。以下是一种可能的设计方案:
- 记录登录失败次数 :对每个用户或者
IP
地址,记录其登录失败的次数。如果在一定时间内登录失败次数超过某个阈值,那么就将该用户或者IP
地址加入黑名单。 - 设置黑名单有效期 :黑名单不应该永久有效,否则可能会误伤正常用户。可以设置一个有效期,例如 24 小时。超过有效期后,自动将用户或者 IP 地址从黑名单中移除。
- 使用布隆过滤器 :为了高效地判断一个用户或者 IP 地址是否在黑名单中,可以使用布隆过滤器。布隆过滤器可以在近似 O(1) 的时间复杂度内判断一个元素是否存在。
- 黑名单同步 :如果系统是分布式的,那么需要考虑黑名单的同步问题。可以使用消息队列或者Redis等工具来同步黑名单。
- 提供解封接口 :对于误伤的正常用户,应该提供一个解封的接口或者方式。例如,可以通过验证邮箱或者手机短信来解封。
- 记录黑名单日志 :对于被加入黑名单的用户或者 IP 地址,应该记录详细的日志,包括被加入黑名单的时间,原因,以及登录失败的详细信息。这对于后期的审计和分析都是非常有帮助的。
新开发了一个功能,网站已经有一定的用户量,如何在不影响用户正常使用的情况下发行新功能,如何平滑更新
使用docker部署,利用镜像
如果一篇文章访问量激增,云服务器承受不住了,怎么办
租服务器,部署,改nginx,也要考虑平滑启动nginx -s reload
如果网站被大量爬虫,被恶意攻击,防范爬虫
首先会参考一些知名网站的反爬机制,还可以限制同一个ip的请求次数
如果博客开放了评论功能,如何防范恶意脚本注入
想正则过滤掉script、alert部分,但是面试官说直接转成字符串文本更好,因为并不是所有script都是恶意的
用户表单数据提交之后,服务端怎么对数据进行验证
扣减库存后,下单失败怎么办
用户下单时减库存
优点 :实时减库存,避免付款时因库存不足减库存的问题
缺点:恶意买家大量下单,将库存用完,但是不付款,真正想买的人买不到
下单时不会立即减库存,而是等到支付时才会减库存。
优点 :防止恶意买家大量下单用光库存,避免下单减库存的缺点
缺点:下单页面显示的库存数可能不是最新的库存数,而库存数用完后,下单页面的库存数没有刷新,出现下单数超过库存数,若支付的订单数超过库存数,则会出现支付失败。
下单页面显示最新的库存,下单后保留这个库存一段时间(比如10分钟),超过保留时间后,库存释放。若保留时间过后再支付,如果没有库存,则支付失败。
优点 :结合下单减库存的优点,实时减库存,且缓解恶意买家大量下单的问题,保留时间内未支付,则释放库存。
缺点:保留时间内,恶意买家大量下单将库存用完。并发量很高的时候,依然会出现下单数超过库存数。
高并发下库存超卖的问题:
方案一:
SQL语句直接更新库存,而不是先查询出来,然后赋值
UPDATE [库存表] SET 库存数 - 1
方案二
SQL语句更新库存时,如果扣减库存后,库存数为负数,直接抛异常,利用事务的原子性进行自动回滚。
方案三
利用SQL语句更新库存,防止库存为负数
UPDATE [库存表] SET 库存数 - 1 WHERE 库存数 - 1 > 0
如果影响条数大于1,则表示扣减库存成功,否则不更新库存,并退款。
(1)悲观锁
实现方式:查询时添加更新锁。
实现原理:使当前线程持有数据库记录行更新锁,其它线程被挂起,直到当前线程执行完释放锁,其他线程才能获取行更新锁,这样就防止了超发现象。
select stock from t_stock where id=1 for update
(2)乐观锁
实现方式:添加版本号。
实现原理:先获取当前版本号,执行完业务流程进行库存更新时,判断当前线程持有的版本号是否与数据库中的版本号相同,如一致则更新库存并增加版本号。乐观锁是一种不会阻塞其它线程并发的机制,它不会使用数据库的锁进行实现
缺陷:库存充足,并发的用户无法下单
解决方法:
乐观锁重入机制:
1)使用时间戳执行重入,比如在100毫秒内重复执行。当因为版本号更行失败后,会尝试重新下单,但是会进行时间戳判断,如果在100毫秒内,就继续,否则就判定失败。
2)限制重复次数执行重入
3)redis使用lua脚本实现
-- 获取当前库存
local stock = tonumber(redis.call('hget', productId, 'stock'))-- 如果购买量小于库存,返回0
if stock < quantity then return 0 end-- 减少库存
stock = stock - 1-- 保存当前库存
redis.call('hset', productId, 'stock', tostring(stock))-- 返回成功
return 1
(4)在秒杀的情况下,肯定不能如此高频率的去读写数据库,会严重造成性能问题的
必须使用缓存,将需要秒杀的商品放入缓存中,并使用锁来处理其并发情况。当接到用户秒杀提交订单的情况下,先将商品数量递减(加锁/解锁)后再进行其他方面的处理,处理失败在将数据递增1(加锁/解锁),否则表示交易成功。
当商品数量递减到0时,表示商品秒杀完毕,拒绝其他用户的请求。
3、首先,多用户并发修改同一条记录时,肯定是后提交的用户将覆盖掉前者提交的结果了。
这个直接可以使用加锁机制去解决,乐观锁或者悲观锁。
乐观锁,就是在数据库设计一个版本号的字段,每次修改都使其+1,这样在提交时比对提交前的版本号就知道是不是并发提交了,但是有个缺点就是只能是应用中控制,如果有跨应用修改同一条数据乐观锁就没办法了,这个时候可以考虑悲观锁。
悲观锁,就是直接在数据库层面将数据锁死,类似于oralce中使用select xxxxx from xxxx where xx=xx for update,这样其他线程将无法提交数据。
除了加锁的方式也可以使用接收锁定的方式,思路是在数据库中设计一个状态标识位,用户在对数据进行修改前,将状态标识位标识为正在编辑的状态,这样其他用户要编辑此条记录时系统将发现有其他用户正在编辑,则拒绝其编辑的请求,类似于你在操作系统中某文件正在执行,然后你要修改该文件时,系统会提醒你该文件不可编辑或删除。
不建议在数据库层面加锁,建议通过服务端的内存锁(锁主键)。当某个用户要修改某个id的数据时,把要修改的id存入memcache,若其他用户触发修改此id的数据时,读到memcache有这个id的值时,就阻止那个用户修改。
5、实际应用中,并不是让mysql去直面大并发读写,会借助"外力",比如缓存、利用主从库实现读写分离、分表、使用队列写入等方法来降低并发读写。
订单过期时间
加行锁
更新完毕之后,我们立即查询一下库存的数量是否大于等于0即可。如果为负数的时候,我们直接抛出异常即可
校验库存和扣减库存的时候统一加锁,让其成为原子性的操作,并发的时候只有获取锁的时候才会去读库库存并且扣减库存操作。当扣减结束之后,释放锁,确保库存不会扣成负数
秒杀场景下如何扣减库存
5.1 采用下单减库存
因秒杀场景下,大部分用户都是想直接购买商品的,可以直接用下单减库存。
大量用户和恶意用户都是同时进行的,区别是正常用户会直接购买商品,恶意用户虽然在竞争抢购的名额,但是获取到的资格和普通用户一样,所以下单减库存在秒杀场景下,恶意用户下单并不能造成之前说的缺点。
而且下单直接扣减库存,这个方案更简单,在第一步就扣减库存了。
5.2 Redis 缓存
查询缓存要比查询数据库快,所以将库存数放在缓存 中,直接在缓存中扣减库存。
5.3 限流
秒杀场景中,对请求做了很多限流操作,比如前端页面的限流和后端令牌桶限流,真正到扣减库存那一步时,请求数很少了。所以限流常用在秒杀方案中,感觉可以再写一篇限流的文章了~
接口慢的排查思路
先明确一个问题,是只有一个接口 变慢,还是只有一个服务的接口 变慢,还是系统里多个服务的接口都变慢?
如果系统中有多个服务的接口都变慢 ,那可能是系统共用的资源不足导致的,比如数据库连接数太多、数据库有大量慢查询、一些共同依赖的下游服务性能问题等,可以查看系统中调用量激增的服务,它的调用量是否导致数据库的并发达到了瓶颈,它是不是导致共同调用的下游服务出现了性能问题,数据库中是不是有大量这个服务引起的慢查询等,做针对性的优化
-
如果数据库并发达到瓶颈,可以考虑用读写分离、分库分表、加读缓存等方式来解决
-
如果下游接口出现性能问题,需要通知下游服务做优化,同时要加降级开关
-
如果数据库中有大量慢查询,需要改sql或加索引等
如果是只有一个服务的接口变慢,那就要针对这个服务做分析,查看它的cpu占用率和gc频率是不是异常,做针对性的优化(cpu占用率飙升的排查解决思路,单独写一篇文章来聊)。也可能是这个服务单独依赖的下游服务性能出现了问题
如果是只有一个接口变慢,那就要针对这个接口做分析。可能是这个接口单独依赖的下游服务性能出了问题。也可能是它本身的代码写的有问题,需要优化
- 比如在循环里获取远程数据,可以改成只调用一次,批量获取数据
- 比如在链路中多次调用同一个远程接口获取相同数据,可以第一次调用之后就把数据缓存起来,后续直接从缓存中获取
- 比如为了得到一个复杂的model,需要调多个接口获取信息,但当前接口只需要其中部分比较简单的数据,那么需要根据实际情况,重新写一个简单model的获取方法
- 比如如果多个比较耗时的操作是串行执行的,但它们又没有依赖关系,就可以把串行改成并行,可以用countDownLatch
- 还有如果实在无法再缩短请求处理耗时的话,也可以考虑从产品逻辑上进行优化,比如把原来的同步请求改为异步的,前端再去轮询请求处理结果
淘宝店铺中的热销榜单如何设计
在一个搜广推的项目里,如果发现自前半个月以来,调用耗时由 500ms -> 700ms,请你分析一下可能的原因
(主要从 IO 限制,上下游对接、缓存命中、流量高峰等因素考虑)
-
网络延迟增加:可能是由于网络环境发生变化,导致请求的往返时间增加。这可能是由于网络拥塞、网络设备故障或网络传输路径变化等原因引起的。
-
服务器负载增加:前半个月以来,服务器的负载可能有所增加,导致请求的处理时间延长。这可能是由于流量增加、并发连接数增多或处理逻辑复杂度增加等原因引起的。
-
数据量增加:如果搜广推项目中的数据量在前半个月内有较大增长,查询或计算的数据量可能增加了,导致调用的耗时增加。
-
第三方服务延迟:如果搜广推项目依赖于其他的第三方服务,那么这些服务的响应时间变长可能会影响到整体调用耗时。可能是由于第三方服务的性能问题、网络问题或第三方服务的配置变化等原因导致的。
-
代码或配置变更:在前半个月内,可能对搜广推项目的代码或配置进行了更改,引入了性能问题或不良影响。可能是由于引入了低效的算法、不合理的配置参数、数据库索引缺失等原因导致的。
抢红包设计
![[a0cc369abc3d4a3b85ccab3a0796ff81.png]]
可以明显的看到打开了红包不一定可以抢到。这样做的好处是:
- 符合现实生活逻辑,有仪式感
- 防止误领,发现不对劲可以马上退出
- 流程拆的长一些,平摊高并发下的压力
预拆包:我在发红包的时候,就已经把所有的东西都计算好了放在redis里面了。
实时计算:在抢红包的时候进行现场计算。
我选择的时把红包提前拆好,虽然提前拆好需要占用Redis的一些存储,但是这样可以让抢红包的速度到达最快。
发红包的时候需要指定红包的金额和个数。然后通过二倍均值法计算后放入Redis。这里我们采用的数据结构是List类型,因为List抢走一个红包和红包库存呢减一是一个原子性操作 。而且List类型的数据结构就决定了获取库存的时间复杂度是O(1),是可行的。到时候按照顺序弹出即可
拆红包
用户点击红包的时候查看库存,如果库存为0的话直接显示红包已经抢完了,并且把详细信息显示。如果还有库存的话,就可以进入抢红包的环节了。
抢红包
用户点击抢红包,此时再次判断库存是否充足 ,如果充足的话判断用户是否抢过红包,如果两个条件都满足 的话,就抢红包成功,从Redis里面弹出一个红包给用户,更新对应的Redis(红包总金额要扣除了),然后使用消息队列异步的调用服务,将用户的金额进行相应的加上。
高并发场景下可能会遇到的问题
超卖问题
因为查看是否还有库存和扣减库(抢红包)存两个操作不是原子性的,因此我们可以使用Redis+Lua实现原子性操作。
Lua实现抢红包的流程:
查询用户是否抢过红包
查询是否还有红包
有的话就扣减红包Redis弹出一个
没有的话就返回
使用 EVAL 命令每次请求都需要传输 Lua 脚本 ,若 Lua 脚本过长,不仅会消耗网络带宽,而且也会对 Redis 的性能造成一定的影响。
思路是先将 Lua 脚本先缓存起来 , 返回给客户端 Lua 脚本的 sha1 摘要。 客户端存储脚本的 sha1 摘要 ,每次请求执行 EVALSHA 命令即可。
如何设计一个延迟队列
两个队列,一个是暂时不可见的,一个是已经可见的然后不断的将任务送进去就好。追问轮询用什么数据结构:堆(时间稳定且每次只需要找堆顶即可)
设计电影院订票
MySQL+redis set + 过期机制(涉及到定了之后的规定时间内支付,即超时未支付取消订单)===>MySQL + 后台任务/延迟队列即可
支付场景
https://zhuanlan.zhihu.com/p/516126246
1、同一笔订单,如果保证用户不重复付款?
这一步会生成**预支付交易会话标识,**即交易会话的唯一ID。
- **out_trade_no:**商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
- **notify_url:**支付回调通知URL,该地址必须为直接可访问的URL,不允许携带查询串
- **total:**订单总金额,单位为分
拿商户系统的唯一订单号 ,去微信支付生成了一个唯一的**交易会话ID,后续支付扣款时,都会带上这个交易会话ID,**从而保证"同一笔订单,用户支付时,即使遇到了支付系统故障,不管用户尝试了多少次,最后都只扣款一次"。
最常用的做法是利用数据库。比如把幂等令牌所在的数据库表的列作为唯一性索引。这样,当你试图存储两个含有同样令牌的请求时,必定有一个会报错。
创建了**交易会话ID,**就意味着微信支付认可这是一笔合法的交易(不是第三方诱骗用户支付的交易),那么,微信支付如何保证这比交易,是合法的商户创建的呢?
这个交易会话ID ,是为了保证系统的幂等性,也称为"幂等令牌" (Idempotency Key)。
微信支付,采用的是"SHA256 with RSA" 签名算法。大致流程如下:
![[v2-26668f1c973557bd96b0f006a6fb10f3_720w.jpg]]
1、请求方拿微信支付颁发的私钥(私钥保存在商户服务端,不能泄露),生成签名, 放入http header中。
2、微信支付拿公钥对签名进行解密。只有特定私钥生成的数据,公钥才能解密成功,而私钥只有商户持有,这样就保证了创建支付订单的请求,肯定来自被授权的商户。
2、用户在微信支付成功之后,如何通知商户?
此类通知,一般通过异步回调方式。那么:
- 回调时,如果商户系统故障了怎么办?
- 商户系统如何保证请求调用方就是微信呢?如果第三方伪造了请求,就会导致用户没有支付,商户系统却认为支付成功了。
第三步:用户支付成功后,微信支付会通过API,异步通知商户服务端。
- 异步回调时,如果商户系统故障,导致用户支付成功状态信息没有存入数据库,怎么办?
- 商户系统如何保证请求调用方就是微信呢?如果第三方伪造了请求,就会导致用户没有支付,商户系统却认为支付成功了。
问题一 解决方案:
解决方案:
商户在没有接收到微信支付结果通知的情况下需要主动调用查询订单API查询支付结果。
即如果回调失败了,接收方可以通过api主动去查询支付状态。
问题二解决方案:
和步骤一 一样,也是通过签名方法来实现。只不过,这次是微信支付服务端对请求进行签名,商户服务端进行验签。
双重验证机制
用户可以启用短信验证和支付密码验证两种安全方式
后期新的八股文合集文章会继续分享 ,感兴趣的小伙伴可以点个关注~
更多精彩内容以及免费资料请关注公众号:绝命Coding