导言
是的,诸位没有看错,这篇文章的要叙述的并不是我吊打面试官,而是一段我被面试官吊打的陈年往事,这段苦楚的记忆在我脑海中长久不衰,也是一个我心里曾屡次不肯面临的现实,各位看官可以准备好一小把瓜子,听我将这则旧事缓缓道来~
写这篇文章的缘由是由于年前有不少小伙伴主张我写个
2022
年终总结,但我自己比较排挤写总结这类的,不过在脑海里回想近几年的技术生涯时,忽然想起了这起风趣的作业,因而将其稍做同享,期望诸君认真读完本文之后,也可以给咱们带来一些考虑!
其实我与诸位一样,几年前的我排挤、甚至讨厌学习,究竟常识从脑子过一遍后,一点也留不住的感觉我并不喜爱。但也是由于这次阅历,才让我改过自新,从一个不爱考虑的“小码农”,变成了现在的“码农Plus
”,好戏开场!
由于近期作业较忙的原因,其实这篇文章,是从上一年终究一天,也便是
2022
年12
月30
日开端,一向写到现在才完结的文章,标题也被我从原本的「回忆三年前」改成了「回忆四年前」,哈哈哈~
一、“被吊打作业”的来龙去脉
在正式谈及这次“被吊打作业”之前,首要来聊一聊此次作业的来龙去脉,作业的起因源自于我太过自傲,刚结业的那段时刻,本故事的主人公,也便是我,通过一些特别手法,成功入职了一家从事教育软件开发的小企业。当然,你要问我什么样的特别手法能让刚结业的我,无需面试就进入了一家软件企业,那便是大名鼎鼎的面试秘法——走后门。
由于我的一位亲戚,在这家企业担任等级不算低的“高管”,因而我靠常人不能及的手法成功入职,没错!俺是一个妥妥的联系户,也正式由于这个原因,所以入职后的作业使命并不算重,饭点前、下班前,冲在榜首个的永久是我,究竟实力摆在这,不嚣张点简直对不住我的身份,哈哈哈~
总归在入职榜首份作业的韶光中,我大致算整个研制部门中最轻松的那个,大致与沸点摸鱼区那群家伙的作业量相似[狗头],由于作业轻松,所以给了我不少
摸鱼学习时刻,也正是通过这些时刻,我在忙完作业之余的时刻内,自身也额定学习了不少Java
技术。
其时我的技术大致是什么水平呢?这儿我从招聘软件上将我开端的简历信息摘过来了,如下:
2019
年的时分,我其时的简历是这样的,凡是我开端听到过的干流技术,底子上都去做了相关学习,并且一半以上在开发中都用过,由于其时的研制模式归于低代码定制开发,因而核心渠道的功用代码中,涉及到的新技术也蛮多,所以开端我有入神之自傲,自以为技术到达了 很牛逼 的程度。
正是由于其时这份简历,给予了自己极大的自傲,再加上成也萧何败也萧何,由于联系户的原因,我入职额定轻松,但也正由于是联系户,所以极大程度上约束了自己的生长空间,也便是抹不开面子去提涨薪,因而终究我做出了一个决议:“大丈夫生于天地之间,岂能郁郁久居人下”!
没错,其时的我毅然决然的“提桶跑路”了!提出辞去职务之后,在诸多的劝止中,头也不回的卷铺盖走人,没有其他原因,完全归咎于个人对自己技术的自傲!其时跑回了老家玩了一段时刻后,想着男儿膝下有黄金,是时分该有一番作为了!接着我去到了间隔老家最近的省会城市,从此踏上了额定自傲的面试之旅(上份作业不在老家的省份,这也是离任原因之一,玩心重,朋友都不在身边~)
从这段回忆中,咱们应该可以感受出我其时的心态,用一个词去描述特别恰当,也便是“年少轻狂”,沉浸在自己的认知中,换其时的心思,假如非要找一个字来描述的话,那便是“我技术很屌”!哈哈哈,现在想起来感觉有些许天真,但开端的我确实便是这个心态,因自以为的技术飞速提升,造就了其时心里十分膨胀的我。
二、“被吊打作业”的正戏开场
2019
年国庆后的某个下午,阳光明媚,昂首望去,天空万里无云,也正是在这个时刻点上,一位身着白色T恤的帅小伙,正在卖力的蹬动双腿:
别看了,蹬车的不是这双腿,而是我那长达一米四的大长腿~!其时盛行骑同享单车,在我约好面试后,就按约好的时刻骑车赶往面试现场,通过近半小时的不懈努力,我成功在约好时刻前赶到了,首要接待我的是一位人事小姐姐,在走完面试前的一些流程后,随即就喊来了一个技术老哥。
担任榜首轮面试我的老哥,在简略看完我的简历后体现的很有兴趣,大约同我聊了有四五十分钟的时刻,当然,这并不是本次的主题内容,所以按下快进键:
- 先简略问了一些关于
Java
根底的内容,如面向对象、集合、多线程、特性…. - 接着问了最近做的两个项目,全体的事务内容、核心技术、个人担任的技术作业….
- 然后又问了一些
JVM
相关的常识,如内存区域、废物回收、类加载机制、即时编译….. - 接着又聊了一些常用开源框架,如
Spring
事务原理、MVC
作业流程、AOP
的应用场景… - 然后又谈了一些关于散布式体系的技术,如散布式体系的一些处理计划、常用的中间件技术….
- 终究探讨了一下
MySQL
的常识,也便是索引、事务、锁的原理,以及SQL
优化、功用优化….
榜首轮技术面试中,在我的记忆中答复的还算不错,包含其时担任一面的技术老哥似乎也挺满意,在聊完技术过了一瞬间之后,他就带着我的简历去了人事部,紧接着又是开端的那位人事小姐姐,出来领着我往一个房间走去……
由于其时房间内还在说话,所以我在门口稍等了一小会儿,但没过多久,我就被喊了进去,进门后映入我眼帘的,是一位梳着成功人士发型、穿戴黑衬衫、打着小领带、并且面容较为帅气的男人,从面相上看应该在27、28
岁左右,其时给我的榜首形象并不是油腻,而是一位很注重形象、并且风貌气度不凡的老哥,他!便是本文的主角人物!
后来我从人事经理口中得知,他便是这个分公司的技术总担任人,也可以被称之为
CTO
、技术总监啥的,当然,在这儿我形象最深入的并非是他的长相和气质,而是那张超大规模的真皮沙发!究竟我进门听到的榜首句,便是他朝我说:“别紧张,过来这儿坐”!我其时坐在真皮沙发上的榜首感觉便是:真软!
三、“被吊打作业”的来龙去脉
- 前奏:先是简略的攀谈,通过一番问寒问暖之后,总算正戏上演了!
- 技术总监:看了一下你的简历还不错呀,跟我聊聊你最近做过的这个项目吧。
- 我:叭啦叭啦叭…..一顿介绍。
- 技术总监:说说你在这个项目中,首要担任哪块开发呢?
- 我:个人参与了该项意图不少核心功用开发,如整个渠道的用户模块,办理、身份、权限等…..
- 技术总监:OK,那咱们聊一聊登录注册吧,这个是你担任的对嘛?
- 我:是的。
3.1、榜首问:登录、注册的事务规划
技术总监:你先跟我说说,你们这个项意图注册、登录界面是怎样样的呢?
注册、登录界面都迥然不同,与一些干流网站相似,注册界面会要求用户输入「手机号/邮箱、昵称、暗码、二次承认暗码、验证码」等底子信息,等这些根底信息填写完结后,用户就可以点击按钮注册账号了。而关于登录流程的规划,相较来说就更为简略,只需求用户输入「邮箱/昵称/手机号」中的任一信息,然后填写暗码点击登录即可。
技术总监:那假如用户登录时,忘记了暗码怎样办呢?
关于这点是无需忧虑的,由于在登录页面上,供给了找回暗码的入口,前面注册时必需求填写邮箱或手机号的,用户可以通过「手机验证码或邮箱验证码」的方法找回暗码。
技术总监:嗯呢,那用户登录一次之后,第2次登录时还需求从头输入暗码吗?
这个要看详细状况来差异,由于在登录的时分,供给了一个「记住暗码」的选项,假如用户登录时勾选了该选项,这时在浏览器宣布的「登录恳求」中,除开底子的用户登录信息外,还会额定传递一个标识。
在后端判别用户输入的「用户名、暗码」正确后,假如恳求中存在该标识,则会生成一个Cookie
信息,将「用户名、暗码」保存在Cookie
中回来,客户端在收到该信息后,会主动把Cookie
存储在浏览器的本地缓存中,所以当用户第2次登录时,防止了再次重复输入「用户名、暗码」的作业。
技术总监:那你以为这种方法存在什么问题么?
有两个隐患,一方面由于保存的「用户名、暗码」存在浏览器的本地记载中,所以假如在本地找到了对应的Cookie
记载,用户暗码是有被盗取的风险。一起,假如并非用户本人操作电脑时,其他人通过「开发者东西」把input
元素的类型从password
类型修正成text
这种,仍旧有或许形成用户暗码泄露。
技术总监:可以的,逻辑思维能力还不错。
哈哈哈,没有的,首要是关于这块事务比较了解,并且登录注册的事务不算太复杂(这个时分我还没有意识到问题的严重性)。
技术总监:再问问你登录的规划哈。
技术总监:用户注册时填好了一部分信息,但由于有事走开了,终究电脑没电关机,用户重启电脑后,再次翻开注册界面,需求重填信息吗?
在咱们其时的项目中,假如呈现这种状况,由于电脑现已重启了,因而用户前次填写的信息会丢失,需求用户从头从头填写注册信息。
技术总监:嗯呢,那你有没有好的方法处理这个问题呢?
「静心苦思:不对劲,这小子很不对劲,这种问题怎样也问?让我想想该怎样答复。」
在用户填写数据的时分,前端可以通过「光标移出作业」来获取用户其时填写的数据,接着将其保存在本地的Cookie
中,假如用户点击了「注册」按钮后,则主动去删去Cookie
中的信息,究竟提交注册后这些保存的信息就失去了作用。
但假如用户写到一半,电脑忽然没电关机了,重启后再次翻开注册页面,那这儿又可以在「页面加载作业」中,从本地Cookie
中将原本保存的数据读出来,然后赋值给对应的文本框即可,然后防止用户重复填写数据。
技术总监:很棒呀,这个主意很不错!
技术总监:那假定有两个用户在一起注册,并且输入的用户名相同,一起提交注册会呈现什么状况呢?
首要这种状况呈现的几率比中彩票都小,一起就算呈现了也不要紧,由于不同地段的网速必定有差距,所以两个注册恳求到达服务器的机遇也不同,一起在规划数据库的用户表时,对用户名加了仅有索引,所以两个用户一起注册时,就算输入的用户名相同,也只会有一个注册成功,并不会呈现用户名重复的状况。
技术总监:你们项目除开通过注册账号登录外,还有没有什么其他方法呢?
还有第三方联合登录的完结,首要便是QQ、微信这两种社交账号的联合登录,是通过腾讯自身供给的API
来完结的,假如用户挑选这种第三方登录,会直接去调用腾讯的登录API
。用户扫码登录成功后,会触发咱们渠道登录成功的回调接口,为其主动在渠道注册一个账号,终究完结第三方账号的联合登录。
技术总监:好的,那咱们再聊点其他的。
3.2、第二问:注册时的灵敏词检测
技术总监:你在做注册事务的时分,有没有考虑过,如若用户填写的「昵称/用户名」包含灵敏信息怎样办呢?比方填写的昵称存在传达色情、违反政策规矩、存在侮辱性意义等状况。
「缄默沉静下来考虑了几十秒,心里OS
:WC
,我还真没想过这块问题」
关于这块问题,其时在开发时并未考虑完全,由于这个渠道归于定制化开发的,所以用户注册量也不算太大,因而在规划时也没往这块多想。
技术总监:不要紧,那假定现在我让你去处理这个问题,你会怎样下手呢?
「其时的我,由于做的都是一些简略的CRUD
/增修改查项目,所以被问的时分,脑袋有些断线,心思维的是:明明我都说没做过了,你偏偏还得往这块问,这纯属是在存心刁难我胖虎啊!」
但没方法,究竟人家都问了,所以其时硬着头皮随意扯,其时的答复大致是这样的:首要我会在数据库里规划一张表,或许在后端里边创立一个Map、Set
这类的容器,专门用来存储「违规灵敏词」,当用户注册时,在填写好「昵称」后,前端采用Ajax
异步恳求的方法,将用户输入的「昵称」发给后端进行灵敏词检测。
后端收到前端发送的Ajax
恳求后,拿着用户的昵称去和「违规灵敏词」进行匹配,假如用户输入的昵称中包含灵敏词,那就让前端显示一下「昵称违规,请从头输入」,反之则通过验证,答应用户正常注册。
技术总监:思路不错嘛,那假如用户的昵称有七个字,但其间有两个字组成的词语归于灵敏词,请问怎样检测出来呢?
首要必定需求先把用户输入的昵称分隔,然后再进行灵敏词检测,但由于个人未处理过该问题,所以现在不清楚详细的做法(其实详细计划是可以凭借ElasticSearch
对用户输入的昵称做分词处理,然后再对分词后的成果进行灵敏词检测,或许可以通过DFA
算法的方法进行灵敏词检测)。
技术总监:不要紧,已然你没详细做过,那咱们先越过这个论题。
「我心中长呼一口气,总算越过这个该死的问题了,再问下去都遭不住了!但没想到,我以为的完毕却仅仅仅仅开端!」
技术总监:假如有人通过机器手法,如爬虫技术对渠道进行账号的批量注册怎样办?
这点不用忧虑啊,由于前面说过的,在注册时用户必需求填写「手机号、或邮箱地址」,然后后端会先向对应的手机号或邮箱发送「验证码」,用户必需求输入正确的「验证码」之后,才干持续注册的,而手机号也好、邮箱也罢,底子上同一个人不会有太多个,所以通过「验证码」的方法,可以有用阻挠机器批量注册。
也包含这个渠道其实还支撑第三方账号注册,也便是通过QQ
、微信的方法方便注册,这种账号和「手机号、邮箱」相似,都具有必定的稀缺性,同一个人不会有太多的账号,所以依据这类稀缺性账号完结注册功用,都能有用的防止机器批量注册。
技术总监:好的,那一个手机号或许邮箱答应注册多个账号么?
这个是不可的,由于在后端有做仅有性判别,一个「手机号、邮箱」注册一次之后,就无法再使用它进行二次注册了。
技术总监:嗯呢,好的。
3.3、第三问:爬虫歹意调用短信接口做轰炸
技术总监:你有触摸过、或许听说过短信轰炸嘛?
这个之前触摸过,比方当你在网上和一个人起了争执,并且对方通过一些手法得到了你的手机号,他就可以拿着你的手机号,放到一些轰炸渠道上去,然后这个渠道就会频繁的给你发送一些废物短信,以此来完结轰炸、打扰的作用。
技术总监:你说的很对,所谓的短信轰炸便是这么回事,但我想问你个事啊。
技术总监:你前面说过:用户在注册时不是可以挑选手机号注册么?
技术总监:假定有人通过逆向剖析,调试出了你们「发送短信验证码」的接口,接着用爬虫技术批量调用该接口轰炸别人怎样办?
「心里OS
:他*的,早知道说没听过短信轰炸了,我是真嘴欠啊,但后边仔细一想,如同不用忧虑这个问题!」
咳咳,关于这个问题嘛,其实也不用忧虑,由于当用户点击了「发送验证码」的按钮之后,首要会弹出来一个「滑块验证码」,只要当用户通过「滑块验证」之后,才会颁布一个调用接口的「数字签名」,假如不具有这个签名,直接调用「发短信验证码」的接口时就会回来「权限缺乏」的提示。
而作为一个正常人,通过「滑块验证」天然不成问题,所以当一个“人类用户”注册时,必定是先拿到签名再调用「发短信」接口,假如呈现未带着「数字签名」的恳求,天然无法通过调用前的校验,因而通过这种滑块验证码的方法,就能有用防止爬虫的暴力调用问题。
其实开端身为一个
CRUD
仔的我,在被问到这个问题之前,一向并不了解为什么要在发送短信之前,增加「滑块验证码」这步反人类操作,究竟一个简略的滑块,就连三岁小孩都能通过,因而开端在开发程序时,思来想去都不能了解这步操作!
技术总监:嗯呢,那假如对方通过Selenium
这种主动化技术,通过了你们渠道的「滑块验证」,又或许说对方又调试出了「数字签名」的生成接口,然后得到了签名,仍旧可以正常调用「短信」接口怎样办?
当我听到这个发问的时分,我很想答复一句:我!不!知!道!我仅仅一个天天摸鱼的螺丝仔,这不是纯属刁难人么!但不懂两个字决不能从我口中说出,因而其时随意间就扯了起来!
这个其时没有考虑到,究竟前面跟您说过的,这个渠道归于定制化程序,上线后边临的用户量并不算大,因而也没有考虑规划反爬虫机制,但您所说的这个问题也很好处理,关于一些较为“宝贵”的接口资源,比方现在所说到的短信接口,由于每条宣布的短信都需求付费,所以通常状况下都会做调用约束,比方约束十分钟内只答应调用三次这类的。
技术总监:拿你所说的十分钟调用三次为例,假如一个人在第九分钟调用了三次,接着又在第十一分钟调用了三次,这样做是不是打破了调用约束呢?你以为是否有更好的计划代替呢?
其时答复的是:听您这么说,确实是存在必定的漏洞,然后让调用约束被打破,但这块没有去详细了解和触摸过,所以并不清楚是否有更好的计划处理此问题。
关于这个问题,其时确实没有触摸过,现在想来,他想听到的答案应该是“高并发状况下的限流计划”,而我答复的限流算法,归于最底子的计数器限流计划,除此之外还有时刻窗口限流、令牌桶限流、漏桶限流这三种计划,下面临这几种常见的限流计划翻开聊聊。
一起,关于「发短信」这类“宝贵性”接口,也应该做好接口的安全性规划,比方做好接口的防篡改、防重放,以及通过数字签名完结接口调用的高鉴权等方法。
3.3.1、计数器限流计划
计数器计划归于限流算法中最简略、并且完结难度最低的算法,比方以前面的事例来说,规矩了「短信」接口的调用频率,不答应在十分钟内超出三次。
这时完结起来就很简略,在「短信」接口的类中,创立一个Map<String,AtomicInteger>
类型的容器即可,其间Key
存储用户ID
,而Value
则存储一个原子计数器,每当一个用户调用一次短信接口后,就将容器中对应的计数器加一,一起敞开一个定时使命,每十分钟对计数器做归零重置。
当然,上述这种做法在用户量较大的状况下,显着会对程序形成较大的功用损耗,假定有
100W
用户,那就需求维护100W
个计数器,这会使得内存占用率直线飙升,一起还需求创立100W
个定时器,来别离维护每个用户的调用计数器。
更好一些的做法是凭借中间件完结,比方依据Redis
缓存中间件来完结,将用户ID
规划成Key
,而Value
则是计数器,并且创立每个Key
时将过期时刻指定为10s
,这样就能充分使用资源,不会形成太大的资源与功用开销,伪逻辑如下:
@Autowired
private StringRedisTemplate redis;
@RequestMapping("/sendSmsVerification")
public ResultVO sendSmsVerification(String sign, String userId){
// 用 SMS_ 拼接用户ID作为Key
String userIdSMS = "SMS_" + userId;
// 先通过前面生成的Key去Redis中进行查询
String value = redis.opsForValue().get(userIdSMS);
// 假如现在现已到达了调用次数约束
if ("3".equals(value)) {
return new ResultVO(200, "短信调用次数已达上限,请在十分钟后重试...");
}
// 假如该用户的Key在Redis中不存在,阐明是榜首次调用短信接口
if ("".equals(value)) {
// 初次调用短信接口时,则在Redis中创立一个计数器
redis.opsForValue().set(lockKey, 1, 10, TimeUnit.SECONDS);
}
// 假如该用户的Key在Redis中存在,阐明并非榜首次调用短信接口
else {
// 此刻则通过Redis的incr指令,把对应的计数器加一
redis.opsForValue().increment(key);
}
// 省略其他事务代码......
}
这段限流代码并不算特别复杂,全体下来无非仍是前面说的那几步:
- ①先通过用户
ID
拼接得到Key
,然后去Redis
中进行查询。 - ②假如查询出的成果为
3
,阐明现在已到达了调用约束,则直接回来调用已达上限。 - ③假如查询出的成果为空,则阐明用户是榜首次调用短信接口,此刻则在
Redis
中创立计数器。 - ④假如查询出的
value
和上面两条都不匹配,则对Redis
中的计数器加一。
这种计数器限流算法完结起来尤为简略,但前面也聊过它所存在的问题:临界问题,假如在两个时刻单位的临界处调用,比方在第9:59
秒调用了三次,接着又在第10:01
秒调用了三次,那仍旧会发生“超出调用上限”的状况,究竟以十分钟作为单位,第9、10
分钟归于一个时刻单位内,这时就超出了调用上限,调用次数到达6
次。
3.3.2、时刻窗口限流计划
时刻窗口限流计划被提出的首要意图,便是为了处理传统的计数器计划存在的临界问题,它的演化前身为TCP
协议的滑动窗口,假如关于TCP
协议较为了解的小伙伴,听到这个词汇相信必定不生疏,如若对这块内容并不了解的小伙伴也不要紧,可参考之前文章中聊过的《TCP粘包、半包问题-滑动窗口》。
限流计划中的时刻窗算法,首要可被分为固定窗口限流、滑动窗口限流两种计划,而前面聊到的计数器计划,实际上便是一种特别的固定窗口限流计划,在前面的比方中,时刻窗口巨细为
10min
,速率约束为3
次,这种计划存在显着的临界约束问题。
下面要点聊一聊滑动时刻窗口,这种计划是处理临界问题而被提出的,但关于滑动窗口的概念有些不好了解,所以先上一副逻辑图,如下:
在上图中,整个用虚红线圈出来的代表一个时刻窗口,以上述比方来说,一个窗口的巨细为600s/10min
,并且每个窗口被分为了三个单位,每个单位巨细是200s
,这也就意味着每过200s
,窗口会向后滑动一个单位,这个动作也可以被称之为向后滑动一格,现在的窗口散布如下:
- 榜首格:
0~200s
- 第二格:
201~400s
- 第三格:
401~600s
差异出来的每个格子,都具有各自独立的计数器,比方在第138s
时发生了一次接口调用,此刻榜首格的计数器就会+1
,仍是以之前的比方来说:
第
9:59
秒调用了三次,接着又在第10:01
秒调用了三次。
将这儿的分钟转换为详细秒数,也便是在第599s
调用了三次,第601s
调用了三次,此刻来看,每其时刻曩昔200s
,窗口就会向后滑动一格,这也就意味着整个窗口会变成图中的下面的样子,此刻的窗口散布为:
- 榜首格:
201~400s
- 第二格:
401~600s
- 第三格:
601~800s
当第599s
调用了三次「短信」接口后,第二格的计数器会累加到3
,此刻再当第601s
测验调用「短信」接口时,就会检测出已到达调用上限,此刻就会回绝用户的调用,以此来处理传统计数器计划的临界问题。
Why?Why?Why?
有些小伙伴或许到这儿就有些晕了,第601s
是怎样检测出调用超额的呐?由于现在的时刻窗口规模是201~800s
,而将整个时刻窗口内的计数器求和,就会得到调用总次数为3
,因而成功检测出了第601s
的调用上限。
当呈现调用到达上限时,必须跟着时刻推移、窗口不断向后滑动,这样整个窗口的计数器总和才会下降,因而用户才干持续调用,通过这种方法就能操控一个时刻段的绝对限流。
但滑动窗口限流计划就不存在临界问题吗?答案是No
,仍旧存在,Why
?来看下图:
看上图中给出的事例,由于现在的时刻窗口巨细是600s
,而199s~203s
显着处于同一个时刻窗口规模内,但跟着窗口向后滑动,这儿仍旧会呈现临界问题,也便是在一个窗口规模内,相同会呈现打破调用次数上限的状况,那这种状况下又该怎样处理呢?其实答案很简略,把一个窗口的格子单位调小即可。
比方直接将每一格的单位巨细从
200s
调整为1s
,此刻每过一秒钟,窗口就会向后滑动一格,等到100s
秒过后,窗口会向后滑动100
格,此刻窗口的区间规模是101~700s
,这就将199~203s
这个规模包含了进去,因而上述状况天然就不会呈现!
通过上述剖析由此可以得出一条准则:当滑动窗口的格子差异的单位越小,整个窗口中的格子数量会越多,滑动窗口的向后移动就越平滑,限流的统计就会越准确。
3.3.3、令牌桶限流计划
前面简略聊完了时刻窗口限流计划后,接着再来聊一聊大名鼎鼎的令牌桶限流计划,令牌桶算法是一种相似于“池化”思维的产物,算法的大体进程如下:
- ①初始化令牌桶并设置最大令牌数,当桶内的令牌到达阈值时,新增加的令牌会被回绝或丢掉。
- ②依据限流巨细,启动一条线程,并依照必定速率向令牌桶中不断增加新的令牌。
- ③任何处于「限流规模」内的恳求,都需求先获取到一个可用令牌,然后才会被处理。
- ④当一个恳求获取到可用令牌后,才会真实履行事务逻辑,履行完结后会将此令牌从桶内移除。
- ⑤令牌桶除开有最大令牌数外,也会有最小令牌数,当桶内令牌数小于最小阈值时,处理完恳求并不会移除令牌,而是会将令牌还给令牌桶。
关于令牌桶限流算法,了解起来并没有前面的滑动时刻窗口复杂,但仅有要注意的是:当桶内的令牌被一个恳求获取后,此刻并不会立马从桶内移除,该令牌会仍旧停留在桶内,只不过该令牌的状况会从可用状况变为不可用状况,也便是其他恳求无法再获取该令牌,真实移除令牌的作业,会在事务逻辑履行完结之后才触发。
3.3.4、漏桶限流计划
漏桶限流和令牌桶限流都归于桶类型的算法,但漏桶算法更相似于MQ
音讯行列,其算法的履行示意图如下:
想要了解漏桶算法,咱们先来看看日常日子中的漏斗,比方现在我要用漏斗来给摩托车加油:
倒油时,咱们可以用瓶子,也可以用桶子,也可以用加油枪…..,这也就意味着:漏斗上方的进油速率并不固定,但不论上方的进油速率怎样,下方的漏斗出口,其速率确实固定的,不论上方进油多快,都不能影响下方的出油速率。
了解了日常日子中的漏斗后,接着再来看看前面的漏桶限流算法,恳求会从漏桶上方进入,而服务端则只会依照固定速率去处理恳求。此刻考虑一个问题:当恳求进入的速率大于恳求处理的速率,会发生什么状况呢?
此刻仍旧回到用漏斗给摩托车加油的比方中,假如漏斗上方的倒油速度比较快,而由于漏斗的结构原因,下方的出口跟不上进油速度,此刻漏斗中的油量会直线上升,直到超出漏斗的最大容量时,再进入漏斗的汽油会溢出。
而限流中的漏桶算法相同如此,恳求进入的速率大于恳求处理的速率时,多出来的恳求会被放入桶中等候,当桶内阻塞等候的恳求超过最大约束后,后续进入的恳求会被丢掉或回绝。
从上述的讲解中,诸位应该可以显着感受到漏桶算法的特色,即:宽进严出,该算法中不会约束恳求进入的速率,但会约束恳求处理的速率,一些对安稳性要求较高的体系,就可以采用该算法对体系进行限流。当然,假如了解MQ
的小伙伴也能感受出:漏桶算法和MQ
的削峰填谷有着异曲同工之妙,当体系峰值流量较高时,会将恳求写入到MQ
中,然后再由详细的事务服务,依照固定的速率拉取MQ
中的音讯进行处理。
3.3.5、高并发限流算法小结
在前面共计提到了计数器、滑动窗口、令牌桶、漏桶这四种惯例的限流计划,但要记住:并不存在一种适用于任何场景的限流算法,依据事务的需求不同,体系的重视面不同,应当采用不同的限流计划,没有所谓的最好!终究简略说一些成熟的限流完结:
-
Guava
中的RateLimiter
东西类:依据令牌桶完结的限流组件,并且对其进行了预热拓宽。 -
Sentinel
中的匀速排队限流策略:依据漏桶思维的限流策略,内部采用行列进行完结。 -
Nginx
的limit_req_zone
限流模块:依据漏桶思维的限流模块,完结网关层的限流操控。 ........
3.4、第四问:API接口的幂等性问题
技术总监:接下来咱们再聊聊其他方面的可以吧?
技术总监:以现在的技术来说,任何用户在使用网络时,难免会存在推迟是不是?
对的,这点我深有体会,尤其是在春节回老家的时分,由于山区的网络覆盖并不全面,所以在拜访一个网站时,加载的速度会特其他慢。
技术总监:嗯呢,已然你也说了这个问题,那我再问你一个问题。
技术总监:假如一个用户在注册时,网络比较卡顿,所以提交注册后迟迟没有反应,因而他又接连点击了屡次「注册」按钮,此刻会发生什么状况呢?
「我沉思顷刻答复道」:假如没有做任何约束,理论上会向服务端宣布屡次恳求,假如数据库的表结构规划不合理,那么还会呈现同一用户的注册信息,在用户表中被刺进屡次。
技术总监:说的不错,那请问你们其时是怎样处理呢?
咱们其时处理计划比较简略,首要在前端做了必定约束,也便是当用户初次点击了「注册」按钮后,「注册」按钮就会变成灰色,也便是用户再次点击时,并不会再次发送Post
恳求向后端提交表单数据。
技术总监:那假如用户看点击注册按钮后迟迟没反应,按F5
改写或浏览器的撤退键,接着再次点了「注册」按钮怎样办?
「心里一颤,没想过啊!硬着头皮解释道」:关于此问题,我在做登录注册时并未考虑周全,未对这个问题进行考虑。
但其完结在想来,处理的思维也比较简略,除开在原本将按钮变灰的根底上,再加上一个「重定向页面」即可,比方信息提交后就跳转下述这个界面:
这样做的好处在于:重定向操作发生后,当用户再次改写网页,或许通过浏览器的回退键,回到原本的界面时,之前表单中填写的信息并不会保存。这样做的好处在于:用户想要再次点击注册按钮,就只能再次从头输入信息。
在用户网络比较卡顿的状况下,做了上述规划后,就只会呈现两种状况:
- ①用户前次点击「注册」按钮提交的
Post
恳求发送失利,服务端并未处理前次的注册恳求。 - ②用户前次点击「注册」按钮提交的
Post
恳求发送成功,在用户再次填写信息的进程中,服务端将前次的注册恳求成功处理,用户再次提交注册时,体系会直接提示去通过手机号登录。
总归参加了这个「重定向页面」后,都能保障在短时刻内,用户无法再次重复提交参数相同的注册恳求。
技术总监:那假如有人通过PostMan
之类的东西,模仿注册参数屡次调用注册接口呢?
这个实际上也不需求忧虑的,由于在数据库的表规划中,咱们对「邮箱/昵称/手机号」这些特别字段也加了仅有索引,就算特别状况下形成重复恳求呈现,由于表结构中有仅有性字段,所以关于相同注册参数的恳求,在用户表中仍旧只会成功刺进一条数据。
技术总监:这种计划也可以,但你还有没有什么其他更好的计划呢?
其时项目是这么做的,所以并未再去对其他计划进行研究。
技术总监:没事,你等面试完毕之后可以再研究一下。
3.4.1、接口幂等性规划的最佳完结
尽管其时并未答复出更好的计划,但后续自己也去了解过「接口幂等性与防重规划」,这儿做简略总结。
发生幂等问题的底子原因
总的来说,在软件体系中呈现幂等问题的原因无非四个:
- ①用户重复提交:一般是指用户填写好表单信息后,由于呼应较慢,然后屡次点击提交按钮。
- ②不合法调用:指第三方通过逆向手法调试到了接口地址,然后通过爬虫或接口东西屡次调用。
- ③失利重试:指散布式项目中,被调用方呈现超时或异常时,触发了调用方的重试补偿机制。
- ④重复音讯:通常指引入
MQ
的项目,关于同一个音讯,生产者屡次发送,或消费者重复消费。
会呈现幂等问题的操作
作为开发者的咱们都知道,任何一个软件,不论事务多么复杂,其背面的本质仍旧是增修改查,关于删、查操作而言,天然具有幂等性,因而需求考虑幂等性规划的就只要增、改这两种,Why
?
由于查询、删去操作,就算呈现屡次也并不影响全体数据的一致性,比方查询“张三”的年纪,同一时刻内不论查多少次,得到的成果都是相同的。而删操作相同如此,如删去姓名为“张三”的用户数据,就算同一时刻内呈现了十个这样的恳求,终究成果都是“张三”这条数据不见了。
多个层面处理幂等问题的计划
- 前端:
- ①按钮变灰/或变为
Load
状况:防止用户点击屡次按钮,形成多个重复恳求呈现。 - ②重定向页面:防止用户通过改写/回退的方法,形成多个重复恳求呈现。
- ①按钮变灰/或变为
- 后端:
- ①仅有
Key
计划:先依据事务参数,从中选出或核算出一个大局仅有Key
:- 仅有
Key
的核算计划:- 选用恳求参数中的某个特别值,如手机号、订单号…作为
Key
。 - 通过
Hash
函数来对一切参数进行哈希核算,得到一个Key
。 - 非注册的场景,可以使用其时用户
ID
+方针方法名作为Key
。 - …..(这儿只要能得到一个与事务相关的仅有
Key
即可)。
- 选用恳求参数中的某个特别值,如手机号、订单号…作为
- 得到仅有
Key
之后,通过set nx px
指令向Redis
刺进数据:- 成功:代表前面没有重复的恳求,其时恳求可以履行。
- 失利:代表前面有相同恳求现已刺进过了,其时恳求需求被丢掉。
- 仅有
- ②防重表计划:使用事务的仅有
ID
,如订单号作为仅有索引,操作之前先刺进防重表。 - ③状况机计划:在表上多加一个状况字段,关于
update
操作加上状况判别,如订单表:- 将「待付款」改为「待发货」:
update ...,status = 2 where status = 1;
- 这样就算呈现多个修正恳求,由于榜首个恳求改成功后,状况变为
2
,其他恳求都会失利。
- 将「待付款」改为「待发货」:
- ④
Token
计划:内容较多,后边聊。
- ①仅有
- 数据库:
- 乐观锁计划:额定规划一个
version
版别字段,但这种计划只适用于update
操作。 - 仅有索引:关于数据的要害字段加上仅有索引,如手机号,防止重复数据屡次刺进。
- 乐观锁计划:额定规划一个
上面依据不同的层面,给出了多种幂等问题的处理计划,但有些计划只适用于特别的场景,如状况机、乐观锁、防重表等计划,假如要规划一套处理幂等问题的通用计划,挑选如下:
- 甲、前端重定向页面防重 + 后端仅有
Key
去重 + 数据库仅有索引兜底。 - 乙、前端按钮变灰防重 + 后端
Token
去重 + 数据库仅有索引兜底。
通过上述这两套组合计划,任选其一都可以打造出一套处理幂等问题的通用策略,但其间仅有没翻开讲解的则是Token
计划,这种方法到底是怎样完结的呢?下面翻开聊一聊,示意图如下:
- ①当用户进入一个表单时,前端通过
Ajax
异步调用后端供给的Token
获取接口。 - ②后端生成一个大局仅有性的
Token
放入Redis
中,可以是UUID、SnowflakeID....
。 - ③后端将生成的
Token
回来给前端,前端先将其保存在一个变量或Cookie
中。 - ④用户填写好表单数据后,在
Post
恳求的头部带着Token
值,接着与表单数据一起发给后端。 - ⑤后端先获取头部的
Token
值,并测验去Redis
中删去该Token
,即del [token_value]
。 - ⑥后端依据删去指令的履行成果,进行下一步判别:
- 假如成功删去:表示现在恳求是榜首次调用接口,答应履行详细的事务逻辑。
- 假如删去失利:表示该
Token
之前现已删过了,其时恳求归于重复恳求,应当被丢掉。
上述便是前面所说的Token
计划,整个进程会呈现两个恳求,榜首个恳求是异步获取Token
,第二个恳求则是详细的事务恳求,终究会依据事务恳求上带着的Token
值,以此作为重复恳求的判别条件,然后防止一起处理多个重复的恳求。
3.5、第五问:用户账号的兼并问题
技术总监:你之前说过,你们项目注册时,可以选用「邮箱/手机号/第三方账号」进行登录是吧?
对的,用户可以通过这三种方法来注册并登录渠道。
技术总监:那一个用户通过手机号注册后,能否绑定第三方账号呢?
这个是支撑的,在用户的个人中心里,用户可以挑选绑定第三方账号,绑定第三方账号后,后续用户也可以直接通过第三方账号登录。
技术总监:那假定用户先通过微信进行第三方登录,按你们渠道的规矩,会主动为其注册一个账号。
技术总监:接着该用户又用手机号注册了,此刻同一个人在你们渠道,是不是有了两个账号?
是的,通过微信登录时,假如之前这个微信没有绑定过渠道账号,会为其主动创立一个账号。用户通过手机号进行注册,相同又会生成一个账号。
技术总监:嗯呢,那我想问一下,假如这个用户有一次通过手机号登录,接着想要绑定那个微信,这样可以吗?
我听到这个问题,榜首反应是想答复:“可以”!
但转念一想发现了端倪,假如能绑定同一个微信,岂不是一个微信对应两个渠道账号了?假定该用户下次挑选通过微信扫码登录,扫码成功之后,到底要登入哪个账号呢?
「我理清思路答复道」:这是不可以的,由于这样绑定之后,一个微信号会与两个渠道账号发生映射联系,下次用户挑选用该微信号登录时,就会呈现问题,无法确定要登入哪个账号。
技术总监:已然你能想明白这个问题,那我想问问你有没有什么好的处理计划呢?
「我听到这个问题后,陷入了缄默沉静…..」
3.5.1、站在现在的视点再次看待此问题
其实这个问题自身并不是技术问题,而是一个事务问题,所以想要处理此问题,就无法完全依靠程序自己完结,此刻必须介入人工进行处理,而这个问题在现在的各大渠道都有处理计划,大体归为下述五类:
- ①挑选第三方登录时,需求用户通过手机号先创立一个渠道账号。
- ②兼并多账号的权力交给用户自己。
- ③当用户测验绑定一个「已绑微信」时,提示用户找办理员申诉。
- ④答应同一个第三方账号对应多个渠道账号,扫码登录时,挑选登录哪个账号的权力交给用户。
- ⑤用户想要绑定一个「已绑微信」时,提示用户先去解除该微信与其他账号的绑定联系。
榜首种做法在各大银行的手机
APP
中比较常见,当你挑选通过第三方账号登录手机银行时,假如是榜首次登录,微信登录成功后会跳转注册界面,要求你先通过手机号创立一个账号,接着银行APP
会主动将其时「手机号、微信」发生绑定联系,后续可以两者中的任一方法登录。
第二种做法我在简书见过,当多个账号之间存在冲突时,将兼并账号的权力交给用户自己,当用户挑选保存某个账号时,其他账号都会被毁掉,包含其他账号在渠道上的一切数据也会完全丢失。
第三种做法我在一些小的自建站见过,其实这是触发了渠道的「不知道操作」的补偿机制,由于用户在测验绑定一个「已绑微信」,这种操作在程序后台无法识别,所以直接给出一致的提示,即:“请联系办理员进行申诉”,申诉后会由渠道办理员,介入修正后台数据库进行处理。
第四种做法在游戏的用户办理中比较常见,以广为人知的「王者荣耀」举例阐明,在登录界面可以挑选通过微信登录游戏,而微信登录成功之后,会呈现下述这个界面:
在这类游戏中,玩家可以自行挑选分区,同一个微信账号支撑在多个分区创立账号,这也就意味着一个第三方账号,可以与多个渠道账号存在关联联系,当用户下次通过该微信账号登录时,用户可以自行挑选详细的分区(详细要登录的渠道账号)。
第五种做法归于最常见的做法,明确规矩一个第三方账号,只能与一个渠道账号存在绑定联系,当一个账号测验绑定第三方账号时,假如检测到对应的第三方账号存在其他的绑定联系,就直接提示用户:“该第三方账号已被其他账号绑定,请手动解除绑定后重试”!
3.6、第六问:登录的夺命五连问
技术总监:用户登录成功之后,第二天再次翻开网站需求从头登录吗?
假如用户登录成功之后,第二天再次翻开网站无需再次登录,但「免登录」存在时效约束,一般状况下为7
天,也便是间隔用户前次登录的时刻超出七天后,用户再次拜访网站就需求再次登录。
完结的大体原理:通过
JWT
完结,用户登录成功之后,后端往Redis
中存储一个时效七天的refresh Token(Key=userID,value=refreshToken)
,接着会向前端颁布一个时效较短的access Token
,前端会将其存储浏览器本地,在后续每次客户端拜访其时网站时,都会带着这个access Token
完结鉴权。
颁布给前端的
access Token
时效为何比refresh Token
要短呢?
有些事务对权限比较灵敏,为了Token
防止被盗用,access Token
天然是有用期越短越安全。
时效较短的
access Token
过期了怎样办?
当一个客户端带着过期的access Token
来恳求时,服务端可以通过该Token
解析出时刻戳和用户信息,效验时刻戳没有问题后,接着通过用户信息中的userID
去查Redis
,假如可以查询到对应的refresh Token
,此刻就可以从头签发一个access Token
回来给前端,前端将之前的Token
替换成新的后,再次恳求服务端资源。
这个进程会不断循环,循环往复之,直至服务端Redis
中的refresh Token
过期为止(过期后需求用户从头登录)。
技术总监:用户登录成功之后,其他的子体系怎样得知该用户登录了?
由于不同的子体系都有权限操控,一个用户在主站登录成功之后,服务端会向客户端颁布Token
,客户端可以通过该Token
在主站域名下“活泼”,但当客户端测验拜访其他不同域名的子体系时,由于浏览器的本地数据(缓存、Cookies
等)是按域名差异存储的,所以拜访其他子体系时并不会带着前面主站颁布的Token
,终究客户端的拜访会遭到回绝。
现现在事务线更加复杂,因而都会引入散布式概念拆分出不同的子体系,并且不同的事务子体系会采用不同的域名布置,所以想要确保「用户一次登录,全线都能拜访」的功用,就需求完结单点登录功用。在咱们项目中,其时通过OAuth2.0
整合JWT
完结了SSO
认证服务,然后终究完结了单点登录的功用。
简略概述
OAuth2.0 + JWT + SSO
完结单点登录的原理,如下图:
条件:
①当用户在拜访恣意子体系没有带着Token(Ticket)
时,都会被重定向到独立布置的SSO
认证中心。
②假如对应的用户在SSO
服务中找不到登录凭据,终究会跳转登录页面,要求用户进行登录操作。
一次完好的单点登录进程:
- ①用户未带着
Ticket
拜访A
体系的某个页面,被重定向到SSO
服务。 - ②用户未带着登录凭据拜访
SSO
认证中心,被重定向到登录页面。 - ③用户完结登录操作,在
SSO
域的Cookie
中植入各种凭据,并再带着Code
重定向到A
体系的回调接口。 - ④用户带着
Code
拜访A
体系,A
向SSO
恳求验证Code
,有用则为A
域颁布Ticket
,偏重定向到原网页。 - ⑤用户带着
Ticket
拜访A
体系的原网页,A
向SSO
恳求校验Ticket
,有用则履行详细的事务逻辑。 - ⑥用户拜访
B
体系的某个页面(此刻无法带着A
域的Ticket
),被重定向到SSO
服务。 - ⑦用户带着
SSO-Cookie
拜访SSO
,该用户的登录凭据校验成功,带着Code
重定向到B
体系的回调。 - ⑧用户带着
Code
拜访B
体系,B
向SSO
恳求验证Code
,有用则为B
域颁布Ticket
,偏重定向到原网页。 - ⑨用户带着
Ticket
拜访B
体系的原网页,B
向SSO
恳求校验Ticket
,有用则履行详细的事务逻辑。
为什么可以通过
Code
换Ticket
呢?使用OAuth2.0
的四种授权方法之一:授权码来完结。
为什么要用Code
换Ticket
呢?Code
是一次性的,下降Ticket
被盗用的风险。
技术总监:用户仿制一个登录后才干拜访的链接,然后粘贴到另一个页面上会怎样?
这要分状况,假如用户仿制链接之后,粘贴在同一个浏览器的其他页面,此刻该用户是可以正常拜访的。但假如用户仿制链接粘贴到其他浏览器上,在其他浏览器未登录过的状况下,本次拜访都会遭到回绝。
这是由于后端都对用户做了权限操控,假如未登录账号的客户端,在咱们渠道归于游客等级,而登录了账号的客户端,则归于正常用户的等级,不同的用户等级对应不同人物,不同人物则又对应不同权限,以此来完结权限的精准操控。
这儿背面的完结原理就不过多啰嗦了,其时的项目是采用
Shrio
权限框架完结的,一切的权限、人物、用户的映射联系,都存储在数据库的五张权限表之中(有兴趣的可以自行去了解)。
技术总监:用户点击登录之后把其时页面关了会发生什么?
「思索顷刻后不自傲道」:额….,应该会登录成功吧。
技术总监:确定会登录成功么?
「陷入缄默沉静…..」(心里:我擦,这纯属刁难人啊,那个吃饱没事干的人,会点了登录就关网页!?!!)
站在现在的视点考虑:
定论:是否会登录成功要分实际状况来决议,看用户封闭的是其时网页,仍是其时的浏览器。
用户封闭了其时网页,成果是会登录成功。用户封闭了其时浏览器,成果是不用定登录成功。
原理剖析:
封闭其时网页:由于用户点击登录按钮之后,登录(账号、暗码)的恳求现已发往了服务器,所以服务端处理完登录恳求后,终究会回来一个Token
或登录凭据,此刻由于浏览器进程还在,这也就意味着浏览器自带的网络进程并未消失,所以登录效验成功之后的操作,如在Cookie
中植入Token
、各类凭据等操作仍旧能正常完结,所以理论上会登录成功。
封闭其时浏览器:这种状况下,用户点击登录按钮后,仍旧会向服务器宣布登录恳求,但由于浏览器现已被封闭了,所以相应的网络进程也会消失,终究就会呈现一种特别现象:「当服务端处理完登录恳求后,向客户端回来呼应成果时,由于客户端的网络进程现已毁掉,所以浏览器无法接纳呼应成果,也就天然无法在Cookie
中植入各种登录凭据,终究成果就不用定登录成功」。
疑问解答:
为什么封闭浏览器之后无法接纳服务端的呼应成果?
由于HTTP/HTTPS
协议的底层是TCP
协议,TCP
是一种双向通讯的网络协议,当通讯的一端呈现毛病时,两头之间的网络数据就无法正常传输,期间TCP
的发送方会屡次从头发送数据包,但由于接纳端的网络进程已毁掉,所以无法收到呼应成果。
为什么封闭浏览器的成果是不用定登录成功?
由于存在不安稳要素,究竟大多数进程在退出时,采纳的方法都是高雅封闭,也便是会先处理完现在正在履行的使命后,才会真实将一切后台进程退出(也便是咱们封闭一个程序之后,电脑管家都会提示你XX
软件还有残留进程可整理的原因)。
假如封闭浏览器之后,网络进程没有立马毁掉,在这期间或许会正常收到服务端的呼应成果,终究就会登录成功。
但假如服务端的呼应时刻比较慢,或许用户安装了电脑管家之类的程序,在进程退出后或许会主动整理残留进程,这种状况下就会完全毁掉网络进程,此刻成果便是登录失利。
技术总监:用户点击登录之后把网线拔了,你以为成果是怎样样的?
「其时的心境:……………………………….」
「其时的心里:我去你大爷的,你*&#…~-/!,前面的点登录按钮后关页面就够离谱了,你现在又整一个拔网线…,你怎样不问我用户点击登录之后,地球就爆炸了会怎样呢???」
「我的答复」:不知道!(其时到这儿心态都被问出一点问题了)
以现在的常识储备进行理性考虑:
定论:详细要看用户拔网线的机遇,成果仍旧或许是登录成功或登录失利。
假如用户在呼应成果回来之后拔了网线,成果是登录成功。但如若呼应回来之前拔了网线,成果是失利。
原理剖析:
这个问题其实和上一个问题相似,但实际状况又存在很大差异,由于不论什么时分拔网线,本质上浏览器的网络进程都不会消失,问题在于网络传输链路出了问题。
关于接纳到呼应成果之后才拔网线的状况,了解起来也比较简略,究竟呼应成果都拿到了,剩下的作业天然也能进行,终究成果就必然是登录成功。但此刻要点要阐明的另一种状况,也便是:为什么在呼应成果回来之前,拔掉网线的成果是登录失利?
想要明白这个问题,本质上与核算机网络的根底脱不了干系,众所周知的一点,现现在的互联网是由一个个局域网组成的(不了解的小伙伴回去看《计网根底:漫谈核算机网络》),由于IP
归于宝贵性资源,所以并不是每台网络设备都具有公网IP
,而恰恰远间隔的网络通讯需求公网IP
,此刻又该怎样办呢?那也便是多台网络设备同享一个公网IP
,这些同享一个公网IP
的多台机器,会组成一个小的局域网(假如了解比较困难,可以这样了解:插同一个路由器网线、连同一个路由器WiFi
的设备,都可以看成是一个局域网内的设备)。
有了上述常识的简略储备后,接着再回到问题自身进行探讨,当用户的浏览器宣布登录恳求,并且服务端将用户的登录恳求处理完结后,经一系列处理睬发生一个数据报文,该报文的方针地址便是宣布登录恳求的那台机器(实际上是那台机器地点的局域网的公网IP
),接着呼应报文会先来到机器地点的局域网,但此刻问题来了!
呼应报文现已抵达了局域网,不过此刻用户的电脑网线被拔,也便是对应设备会退出这个局域网,那么局域网的路由器在“派送数据报文”时,就无法找到详细的派送方针,但此刻用户电脑上的浏览器网络进程仍旧存在,仅仅由于传输链路呈现毛病,所以无法接纳到呼应成果,终究导致登录失利。
这种状况就相当于买快递,原本你写的是收货地址A
,当快递送到A
小区的菜鸟驿站时,成果你搬迁搬去了B
小区,这时A
小区的驿站派送员,就无法依据收货地址将快递送货上门。
当然,还有一种特别状况,也便是用户把网线拔了之后,又立马插上去了,这时理论上仍是会登录成功的,由于HTTP
底层的TCP
协议,是一种可靠性传输协议,在传输失利的状况下会有重发机制。
3.7、第七问:令人窒息的多IP并发操作
技术总监:一个账号在多台电脑上一起点击登录按钮,终究会呈现什么状况呢?
「吸收前面的经验,听到这个问题的我,榜首反应便是这儿面绝对有诈!」
「通过一番考虑后,答复道」:应该都会登录成功。
技术总监:哦?也便是你们的项目中,并未约束多IP
登录,或许做同端互斥对吗?
「我仔细想了想,如同确实没做,于是答复道」:在咱们的项目中确实没做这些。
技术总监:那假定一个账号在两个IP
上登录了,一起修正昵称会发生什么变化?
有一个IP
上修正的昵称,会被另外一个IP
上的昵称代替掉。由于就算两个IP
一起修正、一起提交,终究到数据库履行update
句子时,都会被串行化,由于两个事务并发修正同一行数据时,需求先获取行锁资源,这也就意味着这两个修正操作终究都有前后之分,前一个IP
修正的昵称总会被后一个IP
修正的昵称覆盖掉。
技术总监:嗯呢,那在不约束多IP
登录的状况下,你有什么好的方法成果这个问题吗?
「仔细推导一番后,答复道」:可以参加一个中间状况,也便是在用户表中多规划一个状况字段,0
代表正常状况,1
代表审核状况,当用户的信息发生变化后,对应的用户记载都会被改成「审核中状况」,而履行句子时只答应修正正常状况的用户记载,伪SQL
如下:
-- 之前的SQL句子
update zz_user set user_name = "竹子爱熊猫", ... where user_id = 888;
-- 优化后的SQL句子
update
zz_user
set
user_name = "竹子爱熊猫", status = 1, ...
where
user_id = 888 and status = 0;
通过这样的手法,在榜首个IP
修正成功之后,第二个IP
就无法满足SQL
句子的履行条件,终究就无法真实修正用户数据。
技术总监:很不错的思路,确实可以处理我所提出的问题。
技术总监:假如现在有一个签到领积分的功用,两个不同IP
的同一账号一起签到,会不会领到双倍积分?
假如没有做任何约束方法,这种状况下应该会领到双倍积分,但条件是两个IP
是以绝对手法进行一起操作的才行,也便是服务端中同一时刻内,两条线程并行处理两个IP
的签到恳求。
技术总监:嗯呢,那假如你项目中有订单功用,一个IP
删去订单,一个IP
结算订单,两个操作同一时刻内进行,成果是什么呢?
会呈现问题,导致一个账号上的订单数据错乱。
技术总监:那你以为该怎样处理此问题呢?
其时的我听到这个问题,心里的榜首主意:得加锁,但又转念一想,似乎发现了不对劲,由于加锁只能让并行操作串行化,但终究两个事务操作总会履行的,这儿加锁之后只会呈现两种状况:
①删去订单的恳求先获取锁,先删掉了订单,结算订单的恳求无法履行结算事务(由于订单都没了)。
②结算订单的恳求先拿到锁,用户付钱结算了订单之后,删去订单的恳求获取到了锁,然后把用户现已付钱的订单删了(这显着更不合理,用户估量能举起四十米的大刀…)。
「由于其时的我没做过并发处理,就只懂一些简略的多线程理论,于是又陷入了缄默沉静…..」
站在现在的视点出发,再次看待此问题,处理计划为:状况机!啥意思呢?其实和之前「并发修正昵称」的计划差不多,单独的靠加锁无法处理此问题,问题并不在这上面,这相同是个事务逻辑的问题,应该在订单表上面也规划一个
status
状况字段。
订单表的状况字段,可选状况如下:
-
0
:待结算(待支付)。 -
1
:待发货。 -
2
:待收货。 -
3
:已签收。 .....
-
9
:已毁掉。
有了上述这个状况机字段后,再回过头来看「删去订单、结算订单」这两个事务操作,本质上都是履行update
操作,删去是将状况改为9
,结算是将状况改为1
,所以SQL
句子只需求新增一个条件即可:
-- 删去订单(只答应删去待结算、已签收的订单)
update zz_order set status = 9,... where status = 0 or status = 3;
-- 结算订单(只答应结算待支付的订单)
update zz_order set status = 1,... where status = 0;
也便是直接通过状况字段约束其他并发操作,不论「删去订单、结算订单」谁先履行,另一个操作都无法持续履行。有人或许会疑问:有了状况机之后,就不需求加锁了吗?
其实这种状况下,加不加锁就无所谓了,由于
MySQL-InnoDB
自身有行锁机制,多个事务并发修正同一条数据,都会被串行化履行,因而在后端加锁,仅仅将恳求串行化的作业提前罢了,这反而会影响全体的功用。
「其实到这儿还并未完毕,后来这位面试官还与我聊了许多,但由于时刻较为长远,就只能回忆起一些形象比较深入的发问了~」
四、这段难忘阅历给我带来的感悟
或许看到这儿,咱们很感兴趣的一点是:后来的我怎样样了?其实这次面试之后,其时的我不气馁是不或许的,甚至那时的我被冲击的有些严重,自以为不可一世的我,成果死在了“最简略的登录注册”上….
完毕了这次面试后,我并未再持续投递简历,但人总得吃饭是不?于是乎,我又使出了另一种大名鼎鼎的面试秘法 —— 朋友内推,在第二天以满意的薪酬,成功入职了另一家公司~
当然,其实后来这家外企的人事也在后边一周的周一,给我打来了入职邀约的电话,但由于我现已入职了朋友公司,所以用「暂时有事,不方便曩昔入职」的理由回绝了(我原本以为自己必定凉了,究竟三四天都没有给我通知,但后边转念一想,究竟这是外企的分公司,或许是入职批阅流程比较长)。
不过令人出乎意料的是:在当天的下午,该外企的人事总监又打来了一个电话约请我入职,说他们技术总监比较看好我,感觉我很具有培育价值….,并且这回的入职邀约中,或许以为我前次回绝是薪资不满意,还额定在我报价的根底上加了1.5K
的工资(这对其时的我来说,尽管不算特别多,但每个月多出1.5K
也是一笔不菲的收入),不过终究仍是由于多方面的原因回绝了,哈哈(其实早两天打给我说不定真的会曩昔~)。
尽管这次面试带给我的冲击很大,但从中我的收成也不小,其实总的来说也算自取其祸,究竟其时的我确实很自豪,而这位
CTO
则用我其时以为“最简略的事务”,将我虐的遍体鳞伤,从这段阅历中我想明白了一个道理:谦善戒骄才是真实的大佬应有的美德。
当然,说了这么多的进程,终究也来聊聊这段阅历带给我的感悟,扪心自问,其实这位面试官也是人生中的一位“贵人”,从他身上我看到了许多之前并不知晓的道理。
4.1、千万不要诉苦自己仅仅个CRUD的螺丝仔
在现在的开发环境中,许多人都会诉苦作业:“天天都是担任事务的增修改查,这种日子什么时分是个头啊,不想一向再做CRUD
的螺丝仔了”!拥有这种心态的人不在少数,谁的心里都有个梦,起初的我也并不破例,「架构师、CTO
、技术总监、技术专家…..」,面临这一个个高大上的职位,曾经的我也神往过,时常幻想着什么时分我也能成为这样的人啊,这头衔说出去就倍有面子…..
但等到了这些职位的时分,你会发现每天的作业仍是和事务打交道,泡泡茶畅谈未来技术?用技术在项目中指点江山?沉沦在技术中做架构选型?其实这些都不存在,每天其实仍旧在围绕着事务兜兜转转,「高职技术人」和「普通开发者」之间,仅有差异便是把敲简略的事务代码这项作业,换成了其他更为艰难的使命。
当然,话再说回主题,已然现在无法在项目中用到各种新技术,现在的
CRUD
无法给自己带来技术生长,那咱们要做的便是:在有限的空间内做到“无限”的开展,其实就算最普通的事务也能玩出不同把戏,事务的增修改查想要做好也并不简略,比方怎样才干让代码更整洁、能否让程序拓宽性更好?怎样才干让代码跑的更快…..,动才干改变,诉苦再多也改变不了自身。
4.2、紧记谦善戒骄,人外有人天外有天
这个道理应该是本次阅历中,带给我感悟最深的一条,作为技术人觉得自己牛可以,但千万不要自豪,也不要在面试中、搭档攀谈中、群聊讨论中…..表露出来,由于永久会有人比你更厉害,不要为了虚荣心去故意“攀比”,不然终究倒霉的仍是自己,举个很常见的事例:
假如当过面试官的小伙伴应该都遇到过一种状况,也便是候选者在面试时有些故意装逼,这样的候选者在面试时,往往都会遭到面试官的无情打压,最简略的做法便是连环炮问法,从根底入门问源码原理,从
API
调用问到操作体系完结…..直到终究被问到哑口无言。
拥有自傲是功德,但千万不要自傲过头,紧记谦善戒骄,由于人外有人天外有天。比方我,原以为自己的技术已达巅峰造极,但通过这次面试后,发现自己了解的一些东西都是浮于外表的假象,看似驾轻就熟,实际上仅仅上层特性的搬砖工,学习和学会,压根是两码事!
4.3、再牛的技术也永久是为事务所服务
在IT
开发行业,其实有不少人抱着做纯技术开发的念想,至少我遇到过的不在少数,不想去重复做单纯的事务开发,但也请紧记:技术驱动事务,但技术也永久是为事务供给服务。
当然,想做纯技术开发也并非不可,但国内这样的人很少,或许说国内这样的岗位比较少,除开少数中间件开发、开源技术研制、根底渠道开发等作业外,大多数岗位都需求和事务打交道,所以在学习新技术时也万万不要忘了事务,等你吃透某一行业的事务时,或许给你带来的好处会胜过技术的收益。
4.4、终究的结语
到这儿,本篇内容也就完毕啦,其实我开端写技术文章的初衷,也便是单纯的想同享技术常识,由于我自身在学习的途中,遇到了许多不如意的作业,例如:
- 看到标题特别合适自己,但点进去发现内容底子不是自己想要的….
- 找到一篇优质文章后,仔细阅览收益颇多,但想要寻找后续时,发现压根没有…..
- 阅览一些经典的技术书本时,总会碰到一些很难了解,或许很拗口的段落….
- 有时发现某篇文章,名字特别吸引人,成果点进去发现是引流广告….
- ……
正是由于上面的一些原因,促使我动了写技术文章的想法,迄今为止,个人发布的一切文章,底子上都已连载形式呈现,力求完好、优质、易懂且内容对得住标题!从2019
年开端动笔,到现在转瞬已逝三个年头,但却只完结了大约4
个专栏:
- 《JVM成神路》
- 《深入并发编程》
- 《程序员的网络编程》
- 《全解MySQL数据库》
四个专栏看起来并不多,更文总共才几十篇,但累计码字已有近百万,使用作业之余的空闲韶光,写出这些连载专栏,协助了不少小伙伴生长,也收成了许多小伙伴的称誉。本想着在《数据库》、《网络》两个专栏更完后,回头去写《中间件》或《散布式》专栏:
究竟这两个专栏,是早已欠给咱们要还的债,但正在《全解MySQL数据库》专栏接近尾声,计划朝着新专栏动笔时,船长大佬找到了我:
考虑一再,我说在年后有主意写,但在年前会先更完之前断掉的《网络》专栏,但在此之后,自己脑海里一向在想,要写什么体裁的小册合适呢?写技术类型吧,自以为水平不够,思索一段时刻后,结合现在大环境下的氛围,终究决心写实用型体裁的内容,现在规划如下(或许会调整):
估计大约会在四月份左右上线,名字应该叫《求职的神机妙算》,全体内容倾向于代码之外的实用软技术,定价也不会太高,估量在小几十上下。为此,在终究的尾巴上,也提前给自己做个小小的宣传,有主意的小伙伴到时分可以重视一下(但假如想着分钱不花,主打的便是一个陪伴,也是完全OK的,哈哈哈~)