阿里基础架构后端面经

时间点如下:6.30投递 -> 7.22一面 -> 8.5二面

一点思考

    阿里问的比较基础,原理问的很深,需要看过一些源码并且融会贯通才行,netty/mq/dubbo都问了。面试官比较友善,最后是通过了,但是和hr聊下来,感觉也给不了多少,今年真是沦为鱼肉了。

    今年找工作十分不易,市场候选人很多,能过就是钱给不到位!希望这篇面经能帮到大家~

阿里一面(7.22 11:00)

  1. kafka消息积压优化,为什么不用flink处理?kafka批量消费过程中,出现异常是否会重试?怎么去提交offset?
  2. relay log/ binlog 分别有什么用?
  3. reactor线程模型

    有多种变型,需要了解下,netty底层,一个NioEventLoop对应一个reactor,里面有一个selector,可以注册多个socketChannel,关注读写事件,以及accept事件。一般一个NioEventLoop对应一个线程,负责注册的多个SocketChannel,java.nio.selector底层是select/epoll,单线程实现了无锁话。几个worker线程分别负责就行。mpcs??
    https://zhuanlan.zhihu.com/p/95301195
    https://blog.csdn.net/qq_35217741/article/details/106738556

netty reactor模型、FastThreadLocal、HashedTimeWheel、ByteBuf

netty-grpc好文:https://blog.csdn.net/weixin_31653453/article/details/115389500
https://mp.weixin.qq.com/s/ah9gdutZueCxbqjrWVhiQg
https://www.infoq.cn/article/9Ib3hbKSgQaALj02-90y

  1. tomcat max connection和back log有什么区别,设置了后,是否是最大的句柄?

    max connection应该是backlog,也就是对于突发流量太大,可能会进行丢弃,但是实际的链接应该是由操作系统句柄决定的。

  2. http keep-alive/ tcp keep-alive有什么区别?
  • http keep-alive,服务端和客户端协商好,连接不关闭,可以继续使用。
  • tcp keep-alive,由操作系统自己去维活,超过一段时间回收。
  1. dubbo泛化调用怎么实现的呢?

  2. 哪些应用部署在一起?

    定制应用跟中台应用一起部署,定制应用会调用中台应用提供的基础能力,中台应用单独部署没有意义。

  3. 多机房容灾怎么做的,中间件容灾怎么做的,数据同步怎么做的

  4. netty哈希时间轮是单层还是多层?
    跟dubbo一样,主要是网络io,用的是单层时间轮,但是有个轮次。https://www.cnblogs.com/jfire/p/12243196.html

原生ScheduledThreadPoolexecutor,底层是小根堆。查询队头任务是O(1),添加任务时间复杂度是O(log(n)),poll(也就是移除队头,取出来)的复杂度是O(log(n))
哈希时间轮:新增任务:O(1),删除任务:O(1)
在任务量比较大的情况下,用哈希轮效果才明显。但是精度不是很高。例如10毫秒的间隔。
时间轮上的刻度代表着精度,到底是一秒,还是10毫秒,最终计算时精准度不一样
针对跨度比较大的,延迟比较久的时间轮,例如一个月内,两个月内的,一年的,则可能需要的时间刻度很大。那bucket数就会非常多,占用内存会比较多,而且可能存在很多bucket没有任务。
有两种解决方案,
1.一种是放到同一个哈希轮的bucket上,但是链表可能会比较长。不是O(1),而是O(n),而且需要增加一个round字段。只有轮次为0的,才可以执行。链表也可以根据轮次改造为小根堆,但是插入变为log(n),出队变为log(n)。类似于HashMap,拉链太长时,转化为红黑树了,一样的道理。
2.多层时间轮
以精度为 1 秒,时间范围为 1 天的时间轮为例子,可以设计三级时间轮:秒级时间轮有 60 个槽位,每个槽位的时间为 1 秒;分钟级时间轮有 60 个槽位,每个槽位的时间为 60 秒;小时级时间轮有24个槽位,每个槽位的时间为 60 分钟。当秒级时间轮走完 60 秒后,秒级时间轮的指针再次指向下标为0的槽位,而分钟级时间轮的指针向后移动一个槽位,并且将该槽位上的延迟任务全部取出并且重新计算后放入秒级时间轮。
总共只需要 60 + 60 + 24 = 144 个槽位即可支撑。对比上面提到的单级时间轮需要 86400 个槽位而言,节省了相当的内存。
低层级的走完一轮后,上一层级的,挪一个槽位,且如果有数,直接取出,挪动到下一层级。所以能表示的范围数为最高层级的大小。
使用时间轮实现的延迟队列,能够支持大量任务的高效触发。并且在Kafka的时间轮算法的实现方案中,还引入了DelayQueue,使用DelayQueue来推送时间轮滚动,而延迟任务的添加与删除操作都放在时间轮中,这样的设计大幅提升了整个延迟队列的执行效率。

netty时间轮使用场景:https://cloud.tencent.com/developer/article/1917094

  • 不需要太精准,任务量比较大,例如心跳,请求超时这些。复杂度低,一个线程处理,性能高些,不要有太耗时的操作。比较耗时的任务,建议单独拎出来。搞个scheduled executor

  • 如果时间跨度比较大,可以使用层级时间轮,避免空转,避免精度较低。kafka用的是层级时间轮。比如说, 插入一个延时时间400s的任务, 指针就要执行399次"空推进", 这是一种浪费!有了定时任务,Kafka是如何来推进这些任务的呢?Kafka中的定时器借了JDK中的DelayQueue1 来协助推进时间轮。 具体做法是:

  • 对于每个使用到的TimerTaskList 调用delayQueue.offer加入DelayQueue,超时时间为TimerTaskList对应的expired;
    DelayQueue会根据TimerTaskList 对应的超时时间expiration来排序, 最短expiration 的TimerTaskList会被排在DelayQueue的队头。
    Kafka 中会有一个线程通过调用delayQueue.take来获取DelayQueue中到期的任务列表,这个线程叫作“ExpiredOperationReaper”,可以直译为“过期操作收割机”。

  • 内存级别的,如果要非内存的,要使用分布式延时队列了。否则重启就丢失了。https://www.infoq.cn/article/erdajpj5epir65iczxzi

  • 插入的时间是延迟a秒,(a +currentTicks) % wheelsize,例如,当前a=48s,currrentTicks=10s,wheelsize=50
    时间轮降层级操作。

  • 存在大量过期时间超长的任务,如果这些任务也常驻在redis缓存里,无法得到淘汰,存储成本过高。

  • 死信队列的价值在于,当下游故障时,可以把持续回调下游失败的任务放进死信队列,这样既方便排查异常任务,也保证了任务的流转,当下游恢复后,可以优先消费后续新任务,而不是阻塞在一个故障点。

  1. dubbo3.0有哪些创新
  • 拥抱云原生,多语言,java-go社区互通,部分治理能力交给service mesh去搞,进行打通。
  • 定制新的tri协议
  • 服务发现从接口粒度改为服务粒度

阿里二面(8.5 20:00)

  1. 输出结果
package main


import "fmt"

func Increase() func() int {
    n := 0
    return func() int {
        n++
        return n
    }
}
func main() {
    i1 := Increase()
    i2 := Increase()
    fmt.Println(i1())
    fmt.Println(i2())
}
  1. sql执行顺序
select from where groupby having orderby limit

说错了,select比较靠后,不是放在一开始,https://www.nowcoder.com/questionTerminal/58f48790a3e14914a59559c741e3d640
from--where--group by--having--select--order by

  1. kafka 时间轮算法/nginx红黑树/golang timer 二叉堆 四叉堆,都可以实现超时的功能,有什么区别?哪些场景使用哪个

https://chowdera.com/2022/04/202204150109391653.html

时间轮在多线程场景下,加锁会容易一些。其次是使用delay queue。

  1. 线程的 sleep()方法和 yield()方法有什么区别?线程有哪些状态

① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞( blocked)状态, 而执行yield()方法后转入就绪( ready)状态;

线程状态:7种,

  • blocked,进入synchronized方法,等待锁的,会进入blocked
  • 有时间的,例如wait(time)/park(time)/sleep,会进入time_wating
  • 无时间的,例如wait/park,会进入wait
public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }
  1. 为什么使用kafka,解决积压的思路也是类似于扩充partition,本质原因还是因为一个partition只能由一个consumer消费,积压后即使再新增partition,原来的partition依然积压,没有作用,为什么不用其他mq?动态扩partition?
  • 其实本来就是削峰填谷,积压在所难免。

  • 主要是日志(只写不改),kafka无法查询历史记录,不会修改历史记录。
    看着是新起个临时topic,把老topic的数据写到临时topic,临时topic的queue数量多些,但是下游消费能力跟不上咋办???
    kafka的缺点,如果分区过多(64),也就是一台机器有很多分区,最终会变成随机写
    Kafka 单机超过 64 个队列/分区,Load 会发生明显的飙高现象,队列越多,Load 越高,发送消息响应时间越长;
    使用短轮询方式,实时性取决于轮询间隔时间;
    消费失败不支持重试;
    存储在broker节点上,有耦合了,扩容和缩容比较麻烦
    分区过多,容易造成rebalance,controller选举负担也会比较重。

  • kafka消费模型跟存储模型强耦合在一起,还依赖zk

  • RabbitMQ底层使用的是erlang

  • RocketMq(java)居然跟QMQ很类似,支持延迟消息,消息重试,事务消息,可以查询某条消息?只用一个CommitLog顺序写,不区分Topic,所有的消息都是append到CommitLog的末尾。当consumer的数量大于queue的时候,就会有部分consumer是无法消费的。https://www.itmuch.com/books/rocketmq/design.html。PS: 去哪儿mq,以及滴滴开源的DDMQ,都是依靠于中间增加Proxy层,解决consumer的数量大于queue这个问题。

  • 而RocketMQ所有的消息是保存在同一个物理文件中的,Topic和分区数对RocketMQ也只是逻辑概念上的划分,所以Topic数的增加对RocketMQ的性能不会造成太大的影响。https://www.cnblogs.com/felixzh/p/6198174.html

  • QMQ的设计:所有topic的消息也是放到一个大队列里(message log);然后会有一个中间层topic来存储对应的offset(consume log,文件很小的,offset就是数组下标,从0开始),表示一个topic;一个consume group消费时有个单独的消费文件(pull log,文件很小的,offset就是数组下标,从0开始),每个消费者都会有一个文件,存储的是consume log的offset,这样每增加一个消费者,就会有一个offset文件,积压严重时增加消费者机器即可,过后下线即可,保证根据消费者的个数加大消费粒度,但是没有partition的概念,是无序的。对于延迟消息,用的是两层哈希时间轮,每天一个刻度,最多两年,但是rocketmq使用的是几个level,无法自定义延迟时间,底层每个level对应一个topic?

  • https://github.com/qunarcorp/qmq/blob/master/docs/cn/design.md,https://github.com/qunarcorp/qmq/blob/master/docs/cn/arch.md,https://www.infoq.cn/article/b4VPvP3m8DA-PM7ZqMGZ?from=timeline&isappinstalled=0

  • pulsar:https://www.splunk.com/en_us/blog/it/comparing-pulsar-and-kafka-unified-queuing-and-streaming.html
    https://zhuanlan.zhihu.com/p/47388267
    https://zhuanlan.zhihu.com/p/60288391
    感觉是选型有问题?

不建议topic过多?https://blog.csdn.net/m0_37569884/article/details/121192054

  1. Java里解决包冲突和go里解决依赖冲突有什么区别?

https://tonybai.com/2016/09/13/package-import-in-golang-vs-in-java/?utm_campaign=studygolang.com&utm_medium=studygolang.com&utm_source=studygolang.com

  1. activiti跟jbpmn有什么区别?activiti用了什么设计模式?
#面经##虾皮##阿里巴巴##校招##社招#
全部评论
没写算法吗
2
送花
回复
分享
发布于 2022-08-24 17:32 北京
果然是阿里,好复杂...
1
送花
回复
分享
发布于 2022-08-24 17:05 江苏
滴滴
校招火热招聘中
官网直投
吓我一跳,我以为是应届生,底层太扎实了
1
送花
回复
分享
发布于 2022-08-25 23:55 北京
牛批牛批,把大佬的面经吃透可以学到很多
1
送花
回复
分享
发布于 2022-08-26 11:37 浙江
老哥,我想请教一下,Kafka分区leader副本挂了,选举的时候规则是啥,是根据HW大小还是啥?然后如果不是挂了,是刚开始创建分区的时候,这个流程是咋样的,所有副本的HW都是0。
1
送花
回复
分享
发布于 2022-08-26 22:20 浙江
线程Sleep转入的是超时等待状态
1
送花
回复
分享
发布于 2023-01-08 11:09 北京
楼主知识好渊博,刚看还以为是应届生,吓得我一激灵
点赞
送花
回复
分享
发布于 2022-10-06 16:30 上海
大佬这些底层是怎么学的呀
点赞
送花
回复
分享
发布于 2022-12-23 18:14 四川

相关推荐

头像
不愿透露姓名的神秘牛友
05-08 00:02
已编辑
58同城 产品经理 13*15 硕士211
点赞 评论 收藏
转发
11 93 评论
分享
牛客网
牛客企业服务