2022-6-27抖音 客户端一面凉经

1 自我介绍

面试官:你先开始好吗?ok,我先做一下自我介绍我叫魏博台,是。,抖音,安卓边基础技术的客户端开发同学,你今天来面试的是我们的客户端研发实习生。,你边也可以简单做自我介绍,
答:我叫XXXX嘛。我目前是天津大学电子信息专业在读,上是计算机专硕,下半年研二了。刚刚的话我本科的话是电子商务专业的,本科。也学了很多计算机知识,本身的话也是管理和计算机交叉的。学科,从本科开始,我就关注java web方向,也做了一些网站。相关的开发,学习了一些框架技术,到后面也在不断的深化方面知识,其实我发现。那后台开发其实也是非常复杂的岗位吧,可以说他也需要你大量的实践,同时还有大量理论实习,你才能在行业中得心应手,开发出优雅。的程序

项目经历

面试官:ok。。行那那能比如说简单介绍一下说。之前所经历的对项目里面我看还现在还比较多,就比如说,你最。额,熟悉的项目,说你在项目里面主要承担的角色,以及有哪些。
答:觉得非常有挑战的技术难点,有两个比较有挑战性的。您有是,您在看我的简历是吗?有有是20年到21年做的。B站的社区项目,还有就是,今年刚做的秒杀系统,两个都比较要挑战,您想听哪个呢。

面试官:就那个秒杀项目吧
答:它其实是非常复杂的项目。他我也是我是跟着b站做的,其实b站做的项目只是很简单的。部分只是粗略的一部分,真正的秒杀系统,它设计的方面非常复杂的。既要保证他的高性能,高可用,同时他还涉及到很多复杂的技术。像,我粗略说一下。典型的那种分布式所。其实相对来说刚开始学的话就比较复杂了。
答:具体复杂在哪。我得想一下。
怎么说呢你让我说怎么复杂呢,因为没了解之前肯定觉得整个都是复杂。但是了解了之后的话就觉得还好嘛。我要不单纯说。。。。(被打断)

面试官:就他为什么需要使用到分布式锁。我用或者不用,他们之间会有差异?
答:分布式锁典型的应用场景。redis的缓存穿透(这里说错了,所以后面自动纠正了)缓存击穿,缓存击穿首先缓存击穿的话,指。比如说我有,同时有成千上万的请求访问缓存,但是缓存突然在。一刻过期了,但是时候他就会,有些高并发请求都会打到数据库,数据库接受不了压力。就会崩掉,我们这时候就需要分布式所 来。处理过期问题分布是锁吗。他首先,他是缓存中的数据,肯定是过期了,肯定最起码有一条请求。要打到数据库,来删除缓存,或者是更新缓存,这时候。就使用分布式锁,分布式锁它的特点,只有当数据不存在的时候,我才能创建。才能查询数据库,或者说创建锁,才能创建锁,查询数据库。如果说当锁存在的时候,我后面。我就进入自选状态,或者说返回失败,返回用户。失败状态嘛,这时候就用了分布式锁,分布式锁。得考虑两个问题,是你解锁和设置锁必须是分别保证原子性的过程。
原子性过程就要用到set命令,redis的set命令,可以同时兼顾setnx执行且可以设置他的过期时间,因为是一条命令嘛,当中,它条命令执行是原子性的,还有它的解锁命令,解锁过程,他其实包括了查询redis锁,比较锁,再删除锁三个步骤。这时候,其实可以使用lua脚本将几条命令封装到脚本中,脚本也是原子性执行的过程。

面试官:lua脚本是指什么?
答:lua为脚本,而由于脚本是redis自带的一种脚本机制。你可以用java程序或者用其他第三方的语言知识写lua,我们也可以在redis内部服务器,预先设置好相应的脚本,进行加把在第三方调用就可以了,ok,他是怎么去保证。说原子型的保证原子性。对他脚本本身也是一些指令的集合。那他如果是指令的话,就程序指令的话,他怎么去保证他整个程序的原子性。
答:redis应该可以使用事物。因为我方面没有了解过,但是我想的话,redis使用事务能够保证原子性吧。

面试官:如何理解redis中的事务是串行执行的?
redis事务的串行执行是借助watch指令实现的,watch指令本质上是乐观锁,可以从下面这个代码中看出其具体的应用场景,理论上在redis分布式锁的解锁过程中可以使用事务替代掉lua脚本保证执行过程的原子性同时不受到并发性的影响。
redis的事务可以查看如下链接:https://zhuanlan.zhihu.com/p/146865185
至于watch指令的原理可以参考下面:

public function index()
{
    $redis = new \Redis();
    $redis->connect("39.105.156.191",6379);
    $redis->auth("5s8156w91bibx");
    $store = 5; //商品秒杀总数
    $redis->watch("sales"); // 监听sales
    $sales = $redis->get("sales"); // 销量
    if ($sales < $store){
        // 使用redis 事务
        $redis->multi();
        $redis->incr("sales");// 增加销量
        // TODO 添加用户信息 商品信息到队列 , 队列中产生订单 修改库存等
        sleep(1);
        $res = $redis->exec();
        if($res){
            return '秒杀成功';
        }
        return '秒杀结束';

    }else{
        return '秒杀结束';
    }

}

面试官:我们是回到分布式锁继续讲?
答:分布式锁的话。他其实还有关键点。分布式锁,所有的我要请求设置所的线程。他每线程都必须对应他的占位符k,所有线程都能拿到这个不变的k,拿到这个k的线程必须给value设置为与自己唯一绑定的value值,一般可以是request id。
否则的话,如果锁不是一一绑定,就会等等,我想一下问题。会造成锁的误删?因为值都是一样的嘛。他的values对应的都是一样的,造成所的是误删,肯定是。每线程都对应独一无二的他的值吧?

面试官:你知道乐观锁和悲观锁,他们之间的区别是?
乐观锁的话。您是说加到里面,不是数据库里面吗?都可以都可以。数据库里面的悲观锁的话是mysql内部的一种实现机制。一般是像一般的update,insert等语句都自带for update排他锁。或者说你如果在select语句中使用for update,或者说lock in shar mode关键字。他也会帮你带排他锁或者共享锁。乐观锁的话就相当于是一种使用版本号机制来决定。版本号机制来实现的,他们最主要目的解决并发产生的问题。乐观锁显著的最常用的一种解决方案那种。版本号在不对的时候我先不对之前查询版本号。我成为oldversion版本好,在真正执行不对的语句的时候,我在where条件那里面。判断一下他版本是否还是旧版本,如果是旧版本则执行成功,否则就会执行失败。但是。乐观锁,其实它也是会有一些性能问题的,比如说我1000个线程更新同一条记录,最终只有一个会成功,所以这个时候就会用到可重入锁,但是用到可重入锁的代价还是会很高,可以用到发布-订阅机制,即持有锁的线程释放锁的时候主动通知其他进入阻塞队列的线程,收到消息的线程才会进入第二次锁的争夺。
面试官:那么redis的分布式锁也是一种分布式锁嘛?
答:其实乐观锁也是一种分布式锁,但是它是应用在数据库层面的。setnx指令用在redis缓存方面,他们的应用场景不一样。ok,那意思额redis的分布式锁是乐观锁。

面试官:那你知道说CAS里面它有比较著名的问题叫ABA问题
答:不好意思,我看过但是我忘记了

面试官:好吧,好的,可以下来你再里额了解一下好谢谢我看你。是有使用过表现的,我再多问一点,就我刚听到你说你是对于b站的系统做了一些。额,就课程去做了一次升升级嘛对吧?主要升级是升级在哪些方面。
答:主要是在使用mq进行流量削峰的时候会碰到消息丢失和重复消费的问题和垃圾消息问题及其解决方案…

面试官:消息的公平性如何保证,即保证下单是按照用户请求先来后到的顺序处理?
答:这就涉及到公平的范围问题,如果说公平的范围只保证在mysql数据库扣减库这一环节实现,那么就可以不用管。毕竟最后肯定是能够保证用户这100个商品被用户抢到。首先他,我在里面做了redis预减库存,我有1000万条,我最终到达后端的肯定是只有100条。就在单机,在单机状态下是100条,如果说在集群状态下是。会有超出100条,但肯定不会说1000万条,我说的应该是对的吧。你认同吗?你们不认同的话,我可以解释一下,就在单机状态下我做redis预减库存。

面试官:你确定最后真的只有100个请求会进入到消息队列中嘛?
答:你是说到服务器那边的。链接是只有100条是对不是我是说到买收口服务器。只有100条,ok,,我们就不用。管些嘛,因为我们就我公平的范围如果说只是限制在。用户到达服务到达买搜口服务器的顺序的话,那么其实就不用管。最终那用户100个还是能抢到的,那你不应该是先确认。1000万条进球里面,有哪100条才能去预检。种的话,那他设计量他太复杂了。很难了,很难实现。太复杂了。那就。额,不去实现了不是主要是因为我问了我同学我同学在拼多多那边,他就说他们那边的就没有管些东西,他们都是在。只保证在数卖搜索数据库段保证公平性问题,其他的就不用管。

面试官:那你不应该确认这1000w条里面有哪一百条才能得到商品数据嘛?
答:这样的话那设计量太复杂了啊,
面试官:难道因为太复杂了就不设计了吗
答:主要是因为我问过我同学,他在pdd工作,他就是这么跟我说的,只保证在mysql服务器端的公平性就好了

面试官:你有测试过吗,你开发的系统能承担多大的qps
答:16GB内存, 4核的情况下有2000多吧,
面试官:用什么测的
答:JMeter,开始,我还测了一下,在使用验证码的前后,他其实qps在进一步生成进一步上升,他是在。因为验证码的话,它能够将些波峰均匀的分布到几秒之内。而不是说一秒,或者说更短的时间之内(说错了,给自己挖坑)。
面试官:qps概念是?
答:每一次每秒能够达到多少查询请求。
面试官:那你说说为什么就增加验证码流程,就能提高qps?
答:原本1s的1000w个请求会打到服务器,但是现在因为增加了验证码的流程,有的用户可能几秒内才能验证完成,那么就能够大大减少服务器的压力,服务器原来可能会瘫痪掉,但是现在能正常工作了啊,必然qps会提升啊
面试官:qps跟服务器相关还是客户端相关
答:跟mysql服务器直接相关
面试官:假设有无限的流量进入系统,那么即使加了验证码,仍然有无限的流量进入mysql服务器,这个时候你的mysql服务器的qps还会因此而改变吗?或者假设即使经过了rabbitmq流量削峰,那么到达后端的请求还是无限的,如果服务端不做升级的话,其上限还是2000。那么这个时候验证码和qps有关系吗
答:确实没关系
面试官:我知道你想表达的意思是说加了验证码后,使得流量更加均匀到达后端
现在项目问题已经问的比较多了,那么现在问一些基础相关的问题哈

Java

面试官:HashMap
答:答了一些基本的实现的数据结构,数组+链表+红黑树。
面试官:红黑树和AVL的查询复杂度
答:都是log(n)
面试官:为什么用红黑树而不用AVL树
答:红黑树更适合用于频繁修改的场景,二叉平衡树经常为了做到平衡而需要修改
面试官:为什么红黑树更适合用于频繁修改?
答:红黑树这点,当时没有细纠,不好意思,真正的原因其实是因为红黑树允许最大的路径长度不达到最短路径长度的两倍,但是AVL树要求根节点到叶子节点的最长路径长度和最短路径长度只差不超过1,所以红黑树插入一个结点时,修改不会那么频繁
面试官:那你机制HashMap的resize扩容机制嘛?
答:先申请原来长度的两倍的数组,然后对旧数组上的元素全部进行rehash操作。而且hashmap在jdk1.8版本之后的扩容机制有一个非常大的好处,那就是不一定要对所有的数据进行迁移,这是因为hash桶的个数刚开始就是2^k次方个,然后扩容的时候只需要乘以二倍了
面试官:为什么hashmap桶的初始值大小必须是2^k个而不是是质数呢?
答:这是因为采用质数的性能很低,当进行rehash的时候,基本上所有的元素的位置基本都会移动,而且重新取模的操作需要用到素有的比特位,相对来说很耗时;但是如果一开始桶的大小就是2^k个,扩容的时候只需要乘以2,然后进行rehash的时候,需要参与取模运算的hashcode的位数只是由原来的最后的k位变成最后的k+1位,而不需要用到所有的位数
面试官:hashcode在resize的时候会变化嘛
答:肯定不会,hashcode是作为取模函数的输入而存在的,hashcode对新数组的长度取模后的值才是元素在新数组中最终放置的位置
面试官:resize的流程是什么样的?
答:
面试官:如果一个数的hashcode值是7存放在长度为8的桶里面,怎么认为他能够迁移呢?
答:如果hashcode为7肯定不会迁移,只有当hashcode为7和15同时在这个桶的链上时,才会对15进行迁移。
面试官:那你怎么知道哪个元素放在哪个桶里
答:这就涉及到二进制位偏移了
面试官:HashMap是线程安全的吗?
答:不是,必须要用ConcurrentHashMap
面试官:HashMap在多线程环境下会产生什么问题
答:那么如果多线程操作同一个map中的key,会产生脏数据问题
面试官:就只有这个嘛?
答:(这里没有答出来)还会产生死循环问题。下面这篇文章关于hashMap死循环问题讲解的非常好。
总的来说,开始时
https://gupaoedu-tom.blog.csdn.net/article/details/124449573?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-124449573-blog-40797085.pc_relevant_multi_platform_whitelistv1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-124449573-blog-40797085.pc_relevant_multi_platform_whitelistv1&utm_relevant_index=2

面试官:hashmap为什么在链表长度大于8,且数组长度大于64时将链表转换为红黑树
答:
面试官:concurrentHashMap如何保证线程安全性的
答:https://blog.csdn.net/gupaoedu_tom/article/details/124449788

面试官:为什么需要jvm垃圾收集机制
答:因为java运行时可能无时无刻都在new一个对象,都放到堆里,但是内存空间是有限的,可能会撑爆,所以对那些没有被引用的对象,可以使用垃圾收集器清理
面试官:cpp为什么不需要垃圾回收?
答:因为cpp需要程序员手动释放这些对象
面试官:java的垃圾回收过程?
答:沿着GcRoots链,判断实例的可达性,如果不可达则回收,否则就回收
面试官:哪些对象可以当作GC ROOT ?
答:(1)局部变量 ( Local variables )(2)活动的线程 ( Active threads )(3)静态字段 ( Static fields )(4)Java Native Interface 引用 ( JNI references )
面试官:什么是gc roots?
答:的标记算法我们可以了解为一个可达性算法,所以所有的可达性算法都会有起点,那么这个起点就是GC Root。它是一组指向堆内存中对象的活着的引用的
面试官:gc root存在哪儿?
因为static变量,存放在静态存储区;普通成员变量,存放在栈区;new出来的实例,存放在堆区;static final变量,属于不可改变值的常量,存放在常量区。可以存放在虚拟机栈中,本地方法栈中,也可以在静态存储区,
面试官:垃圾回收器有很多种,谈谈g1垃圾回收器
答:
面试官:除了g1垃圾回收器,还了解其他的垃圾回收器吗?
答:serial及其old版本,parallel及其old版本,一般涉及到old版本的回收器都使用的是标记清楚算法,在老年代中使用,在新生代中一般使用标记复制算法,此外还有cms,这个注重缩短用户的等待时间,强调用户体验,适用于C/S架构。

面试官:CMS的原理了解吗?
答:https://mp.weixin.qq.com/s?__biz=MzU4NzA3MTc5Mg==&mid=2247484764&idx=1&sn=67b0e576a4e7660bec0d3e9ac947954d&chksm=fdf0ed03ca876415f3f0c05f8670497f39a934742012204f18d4c0a79d3d492a1be53859f442&token=1837416809&lang=zh_CN&scene=21#wechat_redirect

计算机网络

1 DNS的解析流程:
答:有两种方式:迭代和递归,目前流行的是迭代方式,…
2 根域名服务器一共有多少个?
答:不好意思,我不清楚,只知道大多数根服务器结点部署在美国,
注:DNS有很多的门门道道,尤其是为什么只有13个根,以及后来的任播技术,可以看下面这篇文章:
https://zhuanlan.zhihu.com/p/361668909
3 tcp和udp你了解嘛,他们有什么区别?
答:巴拉巴拉一大堆
4 DNS的解析用的是UDP还是TCP协议?
答:UDP啊(其实还真不一定,因为涉及到DOH和DOT技术,为了防止DNS劫持问题)
强烈建议查看以下面经:
https://www.zhihu.com/question/310145373/answer/583869215
https://draveness.me/whys-the-design-dns-udp-tcp/
强烈看这篇博客:
https://blog.csdn.net/qq_57981456/article/details/124590062
5 为什么是使用UDP协议呢?
答:首先因为UDP的代价小,不需要像tcp那样建立连接,如果是tcp连接,全球几十亿的设备发出的tcp连接请求是非常耗时的,所以使用udp的响应速度更高;另一个原因是也不需要关注什么安全性,因为这些dns服务器的结点ip本来就是公开的,任何人都能请求解析ip地址。
6 毕竟UDP协议不安全啊,如果UDP的数据报丢失了怎么办?
答:可以再发送啊。
7 那我怎么知道包到达了吗,难道不是因为网络拥塞的问题?
可以在发送方设置超时重传机制。
8 DNS的可靠性怎么保证?
答:字节的做法,因为UDP本身是不可靠的,所以需要在应用层实现服务的可靠性,比如说可以在应用层使用随机数算法或者二进制指数退避算法来解决网络的拥堵问题!
9

8 你知道arp协议嘛
答:查询本地网关的ip地址对应的mac地址
9 为什么有了ip还需要mac
答:因为mac地址唯一标识了一台设备,不论设备移动到哪个场景,都不会变换;但是ip地址存在NAT私有网络复用的情况,即一个ip地址可能唯一标识的是一个网络而不是一台主机,这在局域网中能得到体现,比如当主机从一个局域网换到了另一个局域网时,根据DHCP协议新网络会为这台主机新分配一个ip地址;
mac地址就像身份证号,但是ip地址就如同手机号,手机号可以有多个,不同的场景下使用不同的手机号,一个用来私人打电话,另一个是工作用的,ip地址工作时,就好像你的电话分机一样,外面的打电话的时候要先拨通总机,再转给具体的分机,分机打外面的电话时也需要通过总机进行中转,总机就相当于外网ip,分机就相当于内网ip

算法 leetcode - 103

[LeetCode 103 ]二叉树之字形(Z字型)遍历
面试官查看的时候:问了方法名为什么叫getZip,其实应该改为getZigzag的,我记错了zip单词的意思,zip是拉链,而zigzag是之字形的意思。

import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;



public class Main {
    public static void main(String[] args) {
        // Scanner input=new Scanner(System.in);
        // String str=input.next();
        Node node = new Node(-1);
        getZip(root);
        System.out.println("hello world");
    }

    public static List<Integer> getZ(Node root) {
        if (root == null) {
            return new ArrayList<Integer>();
        }
        Deque<Node> deque = new LinkedList<Node>();
        List<Integer> res = new ArrayList<Integer>();
        deque.offerLast(root);
        int level = 0;
        while (!deque.isEmpty()) {
            int size = deque.size();
            for (int i = 0; i < size; i++) {
                if (level % 2 == 1) {
                    // 奇数层,链尾出队,头入队
                    Node cur = deque.pollLast();
                    res.add(cur.val);
                    if (cur.right != null) {
                        deque.offerFirst(cur.right);
                    }
                    if (cur.left != null) {
                        deque.offerFirst(cur.left);
                    }
                   
                } else {
                    // 偶数层,左 -> 右,链尾入队,链头出队
                    Node cur = deque.pollFirst();
                    res.add(cur.val);
                    if (cur.left != null) {
                        deque.offerLast(cur.left);
                    }
                    if (cur.right != null) {
                        deque.offerLast(cur.right);
                    }

                }     
            }
            level++;
        }
        return res;
    }
}

反问环节

1 为什么这次就一直将java往深了问?
两个原因:首先您简历里面写了java基础扎实,所以肯定会问;此外,我们部门是基础架构部门,主要是写java和Kotln,另外Kotln也兼容java,所以我们肯定会问的。

2 你们部门是不是字节的核心部门?
答:算是吧,我们主要做比如说安卓底层的一些优化呀,或者说是我们整体的研发效能的提升

3 我表现的如何呢?
整体来说还是有一些需要提升的地方吧,这个不代表最终结果,只是个人整体的一个观感上的回答

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值