Quellcode durchsuchen

修改了面经文档

seamew vor 1 Jahr
Ursprung
Commit
f76534f6b5

BIN
后端/项目/开源之夏/assets/image-20230604104941343.png


BIN
后端/项目/开源之夏/assets/image-20230604112340116.png


+ 429 - 0
后端/项目/开源之夏/申请书.md

@@ -0,0 +1,429 @@
+> [TOC]
+
+# 1、项目背景
+
+项目名称:增加跨工作流的参数传递功能
+
+项目主导师: 鲍亮
+
+申请人:孙浩博
+
+申请日期:2023/6/4
+
+邮箱:sunhaobo@stu.xidian.edu.cn
+
+## 1.1、项目基本背景
+
+为开源项目dolphinscheduler设计两个任务,并且为上述两个任务编写详细的设计文档以及使用文档
+
+1. 工作流可以选择自己的输出参数,作为工作流的输出参数,输出给下游任务使用。
+
+场景1: shellA -> subprocessB -> shellC
+
+shellA 查出所有学生信息 (a,b,c) 并将 users 输出给下游任务subprocessB
+
+subprocessB 是一个子工作流, 负责计算所有学生个数 userCount,并将userCount 作为工作流的输出传递给下游
+
+shellC 负责将 userCount 输出到控制台
+
+2. 依赖任务也可以选择将被依赖任务的输出参数继承过来使用。增加依赖参数继承功能
+
+需要在依赖任务上增加一个字段:是否继承被依赖任务的参数
+
+场景2:
+
+工作流A : taska1 -> taska2, taska2 输出了参数 count=10
+
+工作流B : taskb1 -> dependentb2 -> taskb3
+
+dependentb2 依赖了taska2 就可以将count继承过来作为dependentb2的输出参数,给taskb3使用
+
+
+## 1.2、开源项目仓库
+
+- https://github.com/apache/dolphinscheduler
+
+# 2、方法可行性
+
+## 2.1、vue相关
+
+本人有vue开发相关经验,曾从事实验室项目(煤矿大数据平台--大屏模块)的开发,熟练运用前端知识,完成项目开发。主要包括,使用flex和grid完成自适应布局;基于websocket的实时数据展示;基于 echarts封装常见组件如柱状图、饼状图、折线图;对接三方监控,了解chrome播放策略等。
+
+## 2.2、java开发经验
+
+丰富的linux使用经验
+在中电六所实习的经验,从事软件项目开发
+实验室项目开发经验,从事煤炭大数据后端模块的开发
+个人博客编写习惯,编写个人博客并在github开源,[博客地址](https://www.seamew.top)
+
+# 3、项目实现细节--(主要分析start-process-instance接口)
+
+dolphinscheduler项目主要实现为master和worker节点的交互,以下内容分析执行start-process-instance接口的实现细节
+
+![image-20230604104941343](assets/image-20230604104941343.png)
+
+## 3.1、master执行流程分析
+
+首先调用execService的execProcessInstance方法
+
+```java
+        Map<String, Object> result = execService.execProcessInstance(loginUser, projectCode, processDefinitionCode,
+                scheduleTime, execType, failureStrategy,
+                startNodeList, taskDependType, warningType, warningGroupId, runMode, processInstancePriority,
+                workerGroup, tenantCode, environmentCode, timeout, startParamMap, expectedParallelismNumber, dryRun,
+                testFlag,
+                complementDependentMode, version, allLevelDependent);
+        return returnDataList(result);
+```
+
+execProcessInstance方法实现细节
+
+```java
+        /**
+         * 主要调用creat command方法将command存储到DB中
+         */
+        int create =
+                this.createCommand(triggerCode, commandType, processDefinition.getCode(), taskDependType,
+                        failureStrategy,
+                        startNodeList,
+                        cronTime, warningType, loginUser.getId(), warningGroupId, runMode, processInstancePriority,
+                        workerGroup, tenantCode,
+                        environmentCode, startParams, expectedParallelismNumber, dryRun, testFlag,
+                        complementDependentMode, allLevelDependent);
+
+
+    private int createCommand() {
+        // 忽略大部分代码
+        } else {
+            command.setCommandParam(JSONUtils.toJsonString(cmdParam));
+        	// 实际上执行createCommand方法
+            int count = commandService.createCommand(command);
+            if (count > 0) {
+                triggerRelationService.saveTriggerToDb(ApiTriggerType.COMMAND, triggerCode, command.getId());
+            }
+            return count;
+        }
+    }
+
+    public int createCommand(Command command) {
+        int result = 0;
+        if (command == null) {
+            return result;
+        }
+        // add command timezone
+        Schedule schedule = scheduleMapper.queryByProcessDefinitionCode(command.getProcessDefinitionCode());
+        if (schedule != null) {
+            Map<String, String> commandParams =
+                    StringUtils.isNotBlank(command.getCommandParam()) ? JSONUtils.toMap(command.getCommandParam())
+                            : new HashMap<>();
+            commandParams.put(Constants.SCHEDULE_TIMEZONE, schedule.getTimezoneId());
+            command.setCommandParam(JSONUtils.toJsonString(commandParams));
+        }
+        command.setId(null);
+        // 这一步将command存储到DB中
+        result = commandMapper.insert(command);
+        return result;
+    }
+
+```
+
+## 3.2、master分配任务细节
+
+主要是MasterSchedulerBootstrap线程线性扫描DB解析command,然后使用分片算法分发给worker
+
+run方法解析
+
+```java
+    @Override
+    public void run() {
+        // 省略大部分代码
+        // 这一步直接扫描数据库获取command,注意这里同时也会删除数据库习惯记录
+        List<Command> commands = findCommands();
+        commands.parallelStream()
+            .forEach(command -> {
+                try {
+                    WorkflowExecuteRunnable workflowExecuteRunnable =
+                        workflowExecuteRunnableFactory.createWorkflowExecuteRunnable(command);
+                    ProcessInstance processInstance = workflowExecuteRunnable.getProcessInstance();
+                    if (processInstanceExecCacheManager.contains(processInstance.getId())) {
+                        log.error(
+                            "The workflow instance is already been cached, this case shouldn't be happened");
+                    }
+                    // 这一步是启动一个监听任务,监听该实例执行情况
+                    processInstanceExecCacheManager.cache(processInstance.getId(), workflowExecuteRunnable);
+                    // 通过事件的方式去传播任务
+                    workflowEventQueue.addEvent(
+                        new WorkflowEvent(WorkflowEventType.START_WORKFLOW, processInstance.getId()));
+    }
+```
+
+WorkflowEventLooper类用来处理MasterSchedulerBootstrap方法添加的事件
+
+run方法解析
+
+```java
+    public void run() {
+        // 省略大部分代码
+        WorkflowEvent workflowEvent;
+        while (RUNNING_FLAG.get()) {
+            try {
+                // 获取workflowEventQueue队列的事件
+                workflowEvent = workflowEventQueue.poolEvent();
+            try (
+                WorkflowEventHandler workflowEventHandler =
+                        workflowEventHandlerMap.get(workflowEvent.getWorkflowEventType());
+                // 这里通过策略模式处理事件
+                workflowEventHandler.handleWorkflowEvent(workflowEvent);
+    }
+                
+    @Override
+    public void handleWorkflowEvent(final WorkflowEvent workflowEvent) throws WorkflowEventHandleError {
+	    // 省略大部分代码
+        // 核心调用call方法去处理任务
+        CompletableFuture.supplyAsync(workflowExecuteRunnable::call, workflowExecuteThreadPool)
+    }
+                
+    @Override
+    public WorkflowSubmitStatus call() {
+        // 省略大部分代码
+        try {
+            if (workflowRunnableStatus == WorkflowRunnableStatus.CREATED) {
+                // 新建甘特图
+                buildFlowDag();
+                workflowRunnableStatus = WorkflowRunnableStatus.INITIALIZE_DAG;
+                log.info("workflowStatue changed to :{}", workflowRunnableStatus);
+            }
+            if (workflowRunnableStatus == WorkflowRunnableStatus.INITIALIZE_DAG) {
+                // 初始化任务队列
+                initTaskQueue();
+                workflowRunnableStatus = WorkflowRunnableStatus.INITIALIZE_QUEUE;
+                log.info("workflowStatue changed to :{}", workflowRunnableStatus);
+            }
+            if (workflowRunnableStatus == WorkflowRunnableStatus.INITIALIZE_QUEUE) {
+                // 提交任务
+                submitPostNode(null);
+                workflowRunnableStatus = WorkflowRunnableStatus.STARTED;
+                log.info("workflowStatue changed to :{}", workflowRunnableStatus);
+            }
+            return WorkflowSubmitStatus.SUCCESS;
+        }
+    }
+```
+
+现在为止我们分析到了提交任务阶段,submitPostNode方法提交任务之后实际会执行submitStandByTask方法提交
+
+```java
+    private void submitPostNode(String parentNodeCode) throws StateEventHandleException {
+        // 省略大部分代码
+        submitStandByTask();
+        updateProcessInstanceState();
+    }
+
+    public void submitStandByTask() throws StateEventHandleException {
+        // 省略大部分代码
+        TaskInstance task;
+        // 循环peek处理任务队列
+        while ((task = readyToSubmitTaskQueue.peek()) != null) {
+            DependResult dependResult = getDependResultForTask(task);
+            if (DependResult.SUCCESS == dependResult) {
+                log.info("The dependResult of task {} is success, so ready to submit to execute", task.getName());
+                // 实际处理任务
+                if (!executeTask(task)) {
+        }
+    }
+    private boolean executeTask(TaskInstance taskInstance) {
+        // 省略大部分代码
+        // 核心代码,分发任务
+        taskExecuteRunnable.dispatch();
+    }
+```
+
+任务分发之后,GlobalTaskDispatchWaitingQueueLooper类会处理分发的任务
+
+```java
+   @Override
+    public void run() {
+        // 省略大部分代码
+        DefaultTaskExecuteRunnable defaultTaskExecuteRunnable;
+        while (RUNNING_FLAG.get()) {
+            try {
+                // 获取任务
+                defaultTaskExecuteRunnable = globalTaskDispatchWaitingQueue.takeNeedToDispatchTaskExecuteRunnable();
+            }
+            try {
+                final TaskDispatcher taskDispatcher = taskDispatchFactory
+                        .getTaskDispatcher(defaultTaskExecuteRunnable.getTaskInstance().getTaskType());
+                // 分发任务
+                taskDispatcher.dispatchTask(defaultTaskExecuteRunnable);
+    }
+                @Override
+    public void dispatchTask(TaskExecuteRunnable taskExecuteRunnable) throws TaskDispatchException {
+        // 省略大部分代码
+        // 实际分发任务
+        doDispatch(taskExecuteRunnable);
+        // 添加分发任务监听事件
+        addDispatchEvent(taskExecuteRunnable);
+    }
+            
+    protected void doDispatch(TaskExecuteRunnable taskExecuteRunnable) throws TaskDispatchException {
+        // 省略大部分代码
+        // 核心代码,通过RPC方式分发任务给task处理线程
+        Message message = masterRpcClient.sendSyncCommand(Host.of(taskExecutionContext.getHost()),
+                                                          taskDispatchRequest.convert2Command());
+    }
+```
+
+## 3.3、worker线程处理task
+
+NettyServerHandler类用来接收master传递的RPC消息
+
+```java
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) {
+        // 接受消息
+        processReceived(ctx.channel(), (Message) msg);
+    }
+
+    private void processReceived(final Channel channel, final Message msg) {
+        // 省略大部分代码
+        final Pair<NettyRequestProcessor, ExecutorService> pair = processors.get(messageType);
+        if (pair != null) {
+            Runnable r = () -> {
+                try {
+                    // 通过策略模式分发消息
+                    pair.getLeft().process(channel, msg);
+    }
+```
+
+WorkerTaskDispatchProcessor类来分发消息
+
+```java
+    @Counted(value = "ds.task.execution.count", description = "task execute total count")
+    @Timed(value = "ds.task.execution.duration", percentiles = {0.5, 0.75, 0.95, 0.99}, histogram = true)
+    @Override
+    public void process(Channel channel, Message message) {
+            // 省略大部分代码
+            WorkerDelayTaskExecuteRunnable workerTaskExecuteRunnable
+            // 推送任务
+            if (!workerManager.offer(workerTaskExecuteRunnable)) {
+                log.error("submit task: {} to wait queue error, current queue size: {} is full",
+                        taskExecutionContext.getTaskName(), workerManager.getWaitSubmitQueueSize());
+                sendDispatchRejectResult(channel, message, taskExecutionContext);
+            } 
+        }
+    }
+```
+
+WorkerManagerThread类实际处理消息
+
+```java
+    @Override
+    public void run() {
+        // 省略大部分代码
+        Thread.currentThread().setName("Worker-Execute-Manager-Thread");
+        while (!ServerLifeCycleManager.isStopped()) {
+            try {
+                if (!ServerLifeCycleManager.isRunning()) {
+                    Thread.sleep(Constants.SLEEP_TIME_MILLIS);
+                }
+                if (this.getThreadPoolQueueSize() <= workerExecThreads) {
+                    // 获取任务
+                    final WorkerDelayTaskExecuteRunnable workerDelayTaskExecuteRunnable = waitSubmitQueue.take();
+                    // 提交任务
+                    workerExecService.submit(workerDelayTaskExecuteRunnable);
+                } 
+            } 
+        }
+    }
+```
+
+封装的WorkerTaskExecuteRunnable类的run方法
+
+```java
+    @Override
+    public void run() {
+        // 省略大部分代码
+        // 处理任务
+        executeTask(taskCallBack);
+    }
+    @Override
+    public void executeTask(TaskCallBack taskCallBack) throws TaskException {
+        if (task == null) {
+            throw new IllegalArgumentException("The task plugin instance is not initialized");
+        }
+        // 通过策略模式调用任务插件处理任务
+        task.handle(taskCallBack);
+    }
+```
+
+## 3.4、处理任务1
+
+根据上述的执行流程分析我们知道任务是以process为调度单元,任务通过任务ID来确定先后顺序
+
+![image-20230604112340116](assets/image-20230604112340116.png)
+
+```java
+    @Override
+    public int compareTo(Delayed o) {
+        if (o == null) {
+            return 1;
+        }
+        return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
+    }
+```
+
+我们可以将任务队列的任务改为submit/future模式,通过添加params参数确定返回值和入参,通过future.get方法来阻塞等待任务将返回值输入到下一个任务中,可以将执行结果缓存到map中,类似于
+
+```java
+    @Override
+    public Object executeTask(TaskCallBack taskCallBack) throws TaskException {
+        if (task == null) {
+            throw new IllegalArgumentException("The task plugin instance is not initialized");
+        }
+        // 通过策略模式调用任务插件处理任务
+        return task.handle(taskCallBack);
+    }
+
+```
+
+WorkerManagerThread类中有一个map缓存结果
+
+```java
+ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
+    @Override
+    public void run() {
+        // 省略大部分代码
+                if (this.getThreadPoolQueueSize() <= workerExecThreads) {
+                    // 获取任务
+                    final WorkerDelayTaskExecuteRunnable workerDelayTaskExecuteRunnable = waitSubmitQueue.take();
+                    // 判断是否需要入参
+                    if (需要入参) {
+                        改造workerDelayTaskExecuteRunnable
+                    }
+                    // 提交任务
+                    future f = workerExecService.submit(workerDelayTaskExecuteRunnable);
+                    f.put(ID, f.get())
+                } 
+            } 
+        }
+    }
+```
+
+# 4、规划
+
+> 有丰富的后端开发经验,可以保证较快的进度,
+> 身边的同学也同时参加了开源之夏,可以相互帮助进步
+> 也是第一次参与开源项目,有兴趣长期投入事件参与开源事业,为他添砖加瓦
+
+## 4.1、项目研发第一阶段(6月-7月):
+
+* 6月份大致完成任务1
+* 7月份完成任务2
+
+## 4.2、项目研发第二阶段(7月-8月):
+
+* 完善任务1,2
+* 编写前端UI
+* 编写文档
+* 总结
+

+ 6 - 14
算法/多线程/三个线程打印ABC.md

@@ -1,3 +1,7 @@
+## 三个线程轮流打印ABC,打印N次
+
+### synchronized锁同步
+
 ```java
 public void print(String str, int target) throws InterruptedException {
     for (int i = 0; i < n; i++) {
@@ -15,22 +19,10 @@ public void print(String str, int target) throws InterruptedException {
         }
     }
 }
-
-public void print(String str, int target) throws InterruptedException {
-    for (int i = 0; i < n; ) {
-        synchronized (obj) {
-            while (state % 3 != target) {
-                obj.wait();
-            }
-            state++;
-            i++;
-            System.out.print(str);
-            obj.notifyAll();
-        }
-    }
-}
 ```
 
+### lock锁同步
+
 ```java
 public void print(String str, int target) throws InterruptedException {
     for (int i = 0; i < n; ) {

+ 8 - 0
面经/工作/秋招.md

@@ -0,0 +1,8 @@
+| 公司     | 网站                                                    | 投递时间 | 进度 |
+| -------- | ------------------------------------------------------- | -------- | ---- |
+| 百度     | https://talent.baidu.com/jobs/center                    | 7月10号  | 初筛 |
+| 科大讯飞 | https://campus.iflytek.com/official-pc/delivery         | 7月6号   | 初筛 |
+| oppo     | https://careers.oppo.com/university/oppo/center/history | 7月3号   | 初筛 |
+| 米哈游   | https://campus.mihoyo.com/#/campus/applyRecord          | 7月5号   | 初筛 |
+| 大疆     | https://we.dji.com/zh-CN/user                           | 7月7号   | 初筛 |
+

+ 6 - 4
面经/问答/并发编程.md

@@ -8,7 +8,7 @@
 
 同样,`notify()`方法也必须在同步块中使用。如果一个线程在未获得锁的情况下调用`notify()`方法,那么它将无法通知任何等待的线程。因为它没有获取锁,所以它不能访问共享数据或执行必要的同步操作来确保正确的通知。
 
-因此,使用`wait()`和`notify()`方法时,必须在同步q块中使用它们,以确保线程之间的安全性并避免出现竞态条件。
+因此,使用`wait()`和`notify()`方法时,必须在同步块中使用它们,以确保线程之间的安全性并避免出现竞态条件。
 
 ## volatile 关键字
 
@@ -79,7 +79,7 @@ CAS是Compare and Swap的缩写,即比较并交换。它是一种无锁算法
 
 * AQS
 
-AQS抽象队列同步器。它是Java并发包中锁和同步工具的核心实现上面的所说的LOCK锁就是基于AQS实现的
+AQS抽象队列同步器。它是Java并发包中锁和同步工具的核心实现上面的所说的LOCK锁就是基于AQS实现的
 
 AQS提供了一种通用的框架,用于实现线程间的协作和同步操作。它的核心思想是使用一个先进先出的等待队列来管理线程状态,同时支持独占模式和共享模式两种同步方式。
 
@@ -99,7 +99,7 @@ AQS提供了一种通用的框架,用于实现线程间的协作和同步操
 
 * LockSupport不需要获取锁对象,因此避免了可能出现的死锁问题。
 
-* LockSupport可以响应中断,而Object.wait()/notify()方法无法响应中断
+* notify只能随机释放一个线程,并不能指定某个特定线程,notifyAll是释放锁对象中的所有线程。而unpark方法可以唤醒指定的线程
 
 * LockSupport可以先执行unpark()方法,然后再执行park()方法,而Object.notify()方法必须在对应的wait()方法之前执行。
 
@@ -160,6 +160,8 @@ AQS提供了一种通用的框架,用于实现线程间的协作和同步操
 
 ### 如何设定线程池的大小?(CPU 核心数 - N)
 
+Runtime.getRuntime().availableProcessors()
+
 * **CPU 密集型任务(N+1)**
 
 * **I/O 密集型任务(2N)**
@@ -210,7 +212,7 @@ public void run() {
                 ran = false;
                 setException(ex);
             }
-            if (ran)
+            if (ran)·
                 // 改变状态
                 set(result);
         }

+ 8 - 1
面经/问答/操作系统.md

@@ -244,7 +244,7 @@ DMA(Direct Memory Access)是一种硬件实现的技术,能够直接将数
 
 ## PageCache 有什么作用?
 
-**零拷贝使用了 PageCache 技术**,***PageCache\***其实本质上就是一种**磁盘高速缓存**,通过 DMA 把磁盘里的数据搬运到内存里,这样就可以用读内存替换读磁盘。
+**零拷贝使用了 PageCache 技术**,**PageCache其实本质上就是一种磁盘高速缓存**,通过 DMA 把磁盘里的数据搬运到内存里,这样就可以用读内存替换读磁盘。
 
 **PageCache 来缓存最近被访问的数据**,当空间不足时淘汰最久未被访问的缓存。所以,读磁盘数据的时候,优先在 PageCache 找,如果数据存在则可以直接返回;如果没有,则从磁盘中读取,然后缓存 PageCache 中。
 
@@ -265,6 +265,13 @@ DMA(Direct Memory Access)是一种硬件实现的技术,能够直接将数
 
 **在高并发的场景下,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术**。
 
+异步I/O:
+
+它把读操作分为两部分:
+
+- 前半部分,内核向磁盘发起读请求,但是可以**不等待数据就位就可以返回**,于是进程此时可以处理其他任务;
+- 后半部分,当内核将磁盘中的数据拷贝到进程缓冲区后,进程将接收到内核的**通知**,再去处理数据;
+
 直接 I/O 应用场景常见的两种:
 
 - 应用程序已经实现了磁盘数据的缓存,那么可以不需要 PageCache 再次缓存,减少额外的性能损耗。在 MySQL 数据库中,可以通过参数设置开启直接 I/O,默认是不开启;

+ 19 - 1
面经/项目/中煤项目.md

@@ -157,4 +157,22 @@ ack = 1
 
 流量分析项目是六所网络安全部门的一个子项目,它主要功能是识别工控设备异常流量,达到防范攻击和攻击溯源的功能,我主要负责的是工控协议流量解析部分,负责12种工控协议的解析。协议解析主要参照各个协议的官网进行翻译,主要通过策略模式进行协议选择,选择协议之后通过责任链模式进行协议判断,主要实现了对方提供的接口,输出的是解析成功的json格式的对象,通过SPI机制生成一个三方jar包。
 
-该项目的主要难点就是协议需要参照协议官网进行一个字节一个字节的分析解析,并对其错误情况进行判断。
+该项目的主要难点就是协议需要参照协议官网进行一个字节一个字节的分析解析,并对其错误情况进行判断。
+
+
+
+## 开场白
+
+面试官您好,我叫孙浩博,本科和硕士就读于西安电子科技大学,专业是计算机科学。在实验室在学校我主要参与中煤大数据煤矿项目,该项目主要承担煤矿数据的采集、传输、存储、分发、计算等任务。我主要负责其中的采集模块和kafka监控模块,在电子六所实习我主要从事软件开发实习生,主要跟进流量分析项目。
+
+在平常的工作和学习中,我会写博客来记录自己的经验和总结,以便能够更好地总结和交流。个人博客地址在我的简历上面
+
+
+
+good morning. I am glad to be here for this interview. First let me introduce myself. My name is Sun Haobo, 23 , come from xidian University where I major in Computer Science
+
+During my time at university, I actively participated in the CMDB (Coal Mine Big Data) project, which primarily focused on various tasks related to coal mine data, including data collection, transmission, storage, distribution, and computation. My primary responsibilities were in the data collection module and the Kafka monitoring module.
+
+During my internship at NCSE. I worked as a software development intern and actively contributed to the development of a traffic analysis project. Specifically, my responsibilities focused on the development of the parsing module.
+
+As part of my regular work and studies, I maintain a personal blog to document my experiences, enabling me to effectively summarize and share my knowledge. The link to my personal blog can be found on my resume.