大家好,我是小林。
今天分享一位同学面试微信时候的校招面经,同学的技术栈是Java 后端开发。
不愧是微信,问的都不算很八股,基本都是围绕项目来展开技术来问的,对候选者的技术深度比较看重。
涉及的面很广,分布式协议、网络安全、操作系统底层、项目设计都考察了一番。
大二大三做科研,大四开始专心做开发,研究生跟的老师也是做工程的。
我最开始是有一门课,就是本科的时候有一门课就是Java程序设计,就开始接触了一些基础的语法。
但是后来又觉得光上课其实也学不到什么,然后我就开始自己在网上看资料,一开始就跟大家路径很一致,就是先去 b 站看视频,然后先过了一遍整个视频之后大概对 Java 有一个整体的了解,我就去开始尝试去做项目,做一些小的demo。
然后来因为着急做毕设,我就开始学Springs,还有那个就是 SSM 和 SpringBoot那一套嘛,学完之后我就去把我的 毕设给搭起来了。嗯,然后毕设当时是做了一个,因为我们老师给了我们一些数据,然后我就拿它去搭了一个可视化的平台,搭数据可视化的平台实际上用到 Java 的东西不算特别多,主要还是去做可视化那一部分,做前端的东西多了一点。
然后后面就老师说他有一个项目,我就进组去帮忙做嘛,然后做的时候就就去在这个过程中是边做边学,然后跟那个行长沟通,然后就根据他的需求去设计一些方案去做。
然后来我又去做了那个,又是那个很烂大街的项目谷粒商城,但是确实也是学浪蛮多,就是学 spring boot and spring cloud 的那一套微服务的东西。
然后学完之后我觉得就是自己去做这种整体的系统设计的能力还不是很足,于是我就跟另外一个同学我们一起做了那个仿视频网站的项目,但是就是每一步不是去看直接看教程或者是看别人的方案。我们是先自己去思考这些点要怎么去做,然后再去广泛的搜一些方案,然后讨论,接着再去写。
所以在这个项目中我觉得我还是有比较大的提升,因为有更多的自己的思考在里面,而不是直接去模仿别人去做。那其他的可能就是一些玩具项目,相对来说就可能去参考别人的代码的那种情况比较多,所以我还是比较喜欢我这个项目的。
用来做通信,把channel信道给搭起来。
它主要就是一个他在新建的时候一个 boss group,一个 worker group,然后他就用了那个 reactor 的模型,一个网络处理他的 IO 的模型。首先他就是客户端先connect,就由 BOSS group 来收取这些 connect 的信息,接着它就把对应的工作任务给分给 worker 去做,然后 worker 它其实也是一个线程池,做完之后他就把结果给返回到客户端,然后后面就是在在那个项目里面。我也是用 Netty 去简单做了一个聊天室。
我在项目里是自己去实现这个粘包和半包的。我没有用 Netty 本身的方法,因为毕竟是去做这个 RPC 框架的,实现这个还是比较重要的。就是粘包和拆包它是什么问题?粘包就可能是因为我们是用数,用字节流来传输的,所以他可能会,比如说两个包粘在一起了,就是粘包,或者说他只发了半个包,就是半包。
那我们设计就是自己设计了一套protocol,里面就包括了一个 magic number,然后一个它的长度,还有一个就是嗯它具体的数据,然后我们那个接收缓冲区,就那个指针就会先读出来它的魔数,看一下是不是我们约定好了这个魔数,如果不是就直接丢掉了。然后如果是的话就去读它的这个就是长度,接着再去读具体的数据,如果读到一个完整数据包,我们就把它强转成我们对应的那个invocation,然后就是我们的包装类,然后如果没有读到完整数据,就指针回退到原来的位置,然后等待它剩下那半个包发过来。
(一开始答错了,答成把Protocol里的data部分拆出来)
面试官:还不是,就相当于 HTTP 协议里面,他如果有一个很大的报文,那他会怎么做?就实际发送的时候。
答:这个就是它会根据它有一个长度,MTU的那个长度,如果超过这个长度,它就会把那些包给拆开。
追问:那这个默认长度是多少?
答:默认的长度这个我不太清楚。
追问:没事,那假设我告诉你现在默认长度是1500,OK,然后你扣除一些其他一些报文的一些报头部分,那我们业务数据有多少?业务数据的长度?业务数据的长度。
答:业务数据的长度就是 减去 IP 头减去 TCP 头,然后剩下就是业务数据的长度。这个我也不太清楚它具体多长。
追问:没有了解过 TCPIP 的包头的一些具体构造是吧?
答:构造我是知道的,我只是不知道它具体有多长。
追问:就可能要就算,可能慢慢算能算出来是吧?
答:可以的。
z a b 这个协议我是没有了解过,在我这个项目里面我就是拿它来做,就是做一个注册中心,把它具体的那个 port 和具体的 service 注册在这个 Zookeeper上面。因为我是自己去实现路由层的这一块的,所以我的只是去注册了一个监听事件,然后把 zookeeper 上面如果产生了更新数据的话,我就把这个节点给拉下来,然后更新我的路由层里面的这个缓存数据。
追问:就说你现在这个监听是怎么实现的
答:监听就是用一个 Listener的,然后就去就是它有一个 ZK client,就是在 Java 里面是有一个客户端,然后可以写一个 update 事件,如果有产生 update 事件,它就会感应到这事件,就发到我们的 listener 那里去做具体的handle。然后具体怎么感应啊?我觉得我猜一下,我不是很确定这一块就是zookeeper,他可能会,嗯,发一下自己最新的这个数据,如果他产生更新的话,他就发一下到他注册的这一块,这是一个推送的方式,要么就是我们自己轮询的去拉,就这两种方式。我猜。
推拉结合是很经典的。
推的话,就是相当于说我们会每次发送一条动态的时候,把这条动态给送到所有的粉丝的信箱里面去,然后粉丝在上线的时候,他就去看查看他的这个信箱
拉的话就是粉丝他自主的去那个他关注了 up 主那里,把他的动态给拉过来。
这个是,但是这种就会有个问题,像是微博大 v 的那种场景,假如说他要给他所有的粉丝给发消息的话,就是会有很大的这个存储的压力,会耗很多空间,因为他要发很多条消息给他所有的这个粉丝的信箱里面去。
嗯,那所以说微博它就采用了一种推拉结合的方式去识别一下哪一些是活跃粉丝,哪一些是非活跃粉丝。然后具体而言,我不知道微博的算法是怎么样,但是我觉得他可以从两个方面来看,一个就是他最近一次的上线时间,如果他很久没上线的话,比如说他一个星期以上没有上线了,那我就认为他是一个非活跃粉丝,那就不发给他了。那如果说而另外一个方法就是看这个他查看的次数,当然这个就要去记录一下这个粉丝查看的历史记录了,可能就会其实也不是很复杂,就是一个计数器而已,然后就去判断他是不是一个活跃粉丝,如果是僵尸粉的话,就让僵尸粉想要看的时候自己来拉就好了。
【开始答不好了】
追问:那你刚刚你有说到就是说储存压力,对吧?嗯,就相当于说他可能推的时候会有很多很大的储存压力。对对对,那什么时候应该用拉呢?
答:每一个 up 主他的粉丝量不能太多,如果他太多的话用推就会不合适,然后让每个粉丝自己来拉,就会这个存储压力就非常小
追问:你觉得推跟拉的优势是什么?
答:拉的优势就是实现很简单,就是你想看的时候自己去获取就好了。那推的优势我觉得就是可以保证他这个每个粉丝他不用自主的去,自主地去原始的那个信箱拉数据,他只要上线就可以看到那个小红点。
一致性的方法其实就是有一个强一致性和最终一致性。然后像我简历里面之前写的那个使用Seata,它就是用强一致性来执行的,就是说有一个两阶段提交,需要我讲两阶段提交吗?(不用)这是一种实现方法。
嗯,然后像RabbitMQ,它其实就是借助消息队列来实现一个弱一致性。最终一致性。嗯,就是像我们这个场景,就是你发送一个信息过来,然后我会把它发到一个延时队列里面去,就是说这个队列它是会计时的,比如说我们下一个订单你还没有付款,我这个定这个队列就开始计时,如果超过时间还没有付款的话就会放到一个死信队列里面去,然后这个死信队列就会最终去恢复对应我们一开始扣减的库存,这就是实现它最终一致性的方法。
分布式 ID 它其实有很多种方案,首先就是基于数据库的,是他自增的ID。然后还有像数据库本身有一个号段,但这两种方式都是很容易被其他人比如说猜到我们的业务具体数据量是有多少的,所以一般是不用这种比较简单实现的方案的。
然后还有一些其他的,像 Snowflake 是 Twitter 推出的,应该是就是它会有一个 worker ID,一个时间戳,还有一个随机数,就是来设置这个 worker ID,就是对应的机器生成的机器来设置这个ID。
然后还有一种方法就是 UUID 了,但 UID 它就是无序性更强,而且它很长,不适合存在我们的数据库里面。
然后像我们这个项目里面使用的就是美团的leaf,然后美团 leaf它也有两种模式,一种是号段模式,然后另外一种它也是基于 Snowflake 的,然后我们具体是用 Snowflake 的这种方案,因为为什么呢?号段它其实虽然说美团它是做了一个双号段的 buffer 模式,但仍然是会存在刚才我们说的那种被别人猜到业务量的问题,所以还是选择了 Snowflake 这种方式。然后 Snowflake 美团它是使用了一个弱依赖于 zookeeper 的方式来解决 Snowflake 原先会存在的一个时钟不一致的问题。(一时没想起时钟回退这个词)就大概是这几种方案。
没讲好,想讲多机并发的,但是后面扯到用MQ,把自己给绕晕了,发现MQ不是用来解决抢分布式锁的竞争问题的
看论文学的,Simple的那篇
我感觉现在大部分的这一种,这种类似于选举的这种方案,还有维护多机的,像Redis,还有 MySQL 多机,他们其实用的基本上都是这种选举还有 vote 的方法。
选举的时候不可用
Paxos没有选举,是对单值一致性投票。(我不确定,瞎说的)
追问:有没有不可用的时间?没了解过
XSS就是脚本攻击,跨站脚本攻击,然后 CSRF 是跨站请求攻击。
然后像跨端请求攻击,它主要就是会因为我们基本一般的那种实现认证的方式都是带个cookie,然后他可能就会拿着我们这个 cookie 然后去做一些事情。然后或者是他会导向外站的一个钓鱼网站之类的,然后这个解决方式就有两种,一种是去它的 header 设置一个referer,就是它是从哪一个来源来的,然后第二个就是禁止它导向外链。像我们现在经常设置的就是如果你要导向其它的链接,我就会给你一个提示,相当于把这个风险给转嫁了,如果你自愿过去,那就是你的问题。
然后 XSS 攻击是就是跨站脚本攻击,可能就是在我们的 HTML 里面插入一些,插入一些病毒或者是什么,就是改掉我们原始的 HTML 文件。但这个通常是出现于像是用户论坛或者是评价这种用户可以自己发数据上去的地方。那解决这个方法也是首先是要去过滤一下用户他发的信息,去识别一些敏感的字,比如说像 Javascript 这样子的敏感的关键词。还有去做一个就是纯前端渲染的过程,那它存的时候去后端去做校验,然后再在前端只是去拿数据,然后把它渲染出来而已。
知道。SQL注入就是...(面试官打断了:那就不用了,下一个)
我思考一下, select 的话,它是维护了一个 1024 的数组,那它这个数组,嗯,可以是它的优势,也可以是它的劣势。劣势就在于它不能。连更多的 f d,那优势的话,我觉得如果你要限制你可以连 FD 的数量的话,用 select 也是可以的。
但是如果是不想要限制这个连接数量的话,就可以用poll。
然后像epoll,他对他们的改进就是用了红黑树加链表的方式。我觉得在大部分情况下 epoll 都是相对来说更好的,因为,嗯,因为它更快,也是用 O(1) 的时间就拿过去,而且也不用去做这个,这是个大规模的复制。
Fork 我知道,它是创建出一个子进程出来。
方法栈还有程序计数器。
不会,这个就是写时复制,像 Redis 里面也是类似于这样做的,它是 Fork 一个子进程出来,然后可能它们虚拟地址不一样,然后这个实际的物理地址是映射到一样的。
AQS就是一个 abstract q synchronizer,然后它其实它的核心思想就是有一个可以理解为一个计数器,就是有一个信号量来控制访问这个资源的数量,然后具体就是怎么去拿到这个AQS,就是先通过 CAS 去看一下能不能拿到这个锁。
哦哦等待队列,它是一个双向链表,可以拿锁的时候就去唤醒这个列表里面的任务,让他们去抢锁。
互斥锁、信号量、条件变量、readwrite lock,自旋锁 (忘记自旋锁了,面试官提示:就你刚才说的AQS里的,无锁;哦CAS,自旋锁)
我想着另辟蹊径用数组做,结果不如直接用链表。思路没问题但是超时了。
bababala
C++和自研的框架
历史好文: