Prechádzať zdrojové kódy

修改面经项目部分

seamew 1 rok pred
rodič
commit
d28a0e2515

+ 32 - 0
面经/2023年暑期实习/阿里一面.md

@@ -20,3 +20,35 @@
 18. mysql如何提升查询性能
 19. 加入索引的讲究 --- 数据检索的条件字段,聚合函数的聚合字段,排序字段,防止回表,关键字段
 20. 联合索引a,b的顺序
+
+## 2023/5/5
+
+设置IP黑名单类
+
+如果一个IP,3分钟之内访问超过5次加入黑名单
+
+自动解锁,如果IP在黑名单里面超过5分钟没有再次访问,则移除黑名单
+
+需求:高并发
+
+```java
+ConcurrentHashMap ips; 
+HashSet blacks;
+boolean check(string ipaddr) {
+    // 判断是否大于5 和 时间大小
+}
+void add(string ipaddr) {
+    synchronized(ipaddr.intern()) {
+        // 加入IPS,这一步骤加锁
+    }
+    // 判断是否超过5分钟
+    if (check(ipaddr)) {
+        blacks.insert(i)
+    }
+    // 另外启动一个线程,执行remove
+}
+void remove(string ipaddr) {
+    // 遍历每个blacks去除
+}
+```
+

+ 17 - 0
面经/问答/JVM.md

@@ -88,8 +88,23 @@ jvm内存区域大体上可以分为线程私有和线程共有两大部分
 
 1. 类加载检查
 2. 分配内存
+
+指针碰撞 : 
+
+- 适用场合 :堆内存规整(即没有内存碎片)的情况下。
+- 原理 :用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
+- 使用该分配方式的 GC 收集器:Serial, ParNew
+
+空闲列表 : 
+
+- 适用场合 : 堆内存不规整的情况下。
+- 原理 :虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
+- 使用该分配方式的 GC 收集器:CMS
+
 3. 初始化零值
+
 4. 设置对象头
+
 5. 执行init方法
 
 ## 类加载过程
@@ -111,5 +126,7 @@ jvm内存区域大体上可以分为线程私有和线程共有两大部分
 
 不会被清除,因为强引用对象本身就是GC ROOT,除非该方法已经从栈中弹出,或者不在使用才会被清除
 
+## 新生代老年代比例
 
+![1](assets/061921034534396.png)
 

BIN
面经/问答/assets/061921034534396.png


+ 16 - 1
面经/问答/计网.md

@@ -22,7 +22,7 @@ TCP 是**面向连接的、可靠的、基于字节流**的传输层通信协议
    1. 拥塞窗口 `cwnd = ssthresh + 3` ( 3 的意思是确认有 3 个数据包被收到了);
    2. 重传丢失的数据包;
    3. 如果再收到重复的 ACK,那么 cwnd 增加 1;
-   4. 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;
+   4. 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值(这里是快速重传时候的ssthresh ),原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;
 
 ## TCP如何保证可靠
 
@@ -85,6 +85,9 @@ TCP 是**面向连接的、可靠的、基于字节流**的传输层通信协议
 
 ### 服务端方法:
 
+* `socket`,新建一个socket对象
+  * 如果没有调用bind方法会,客户端对服务端发起了连接建立,服务端会回 RST 报文
+
 * `bind`,将 socket 绑定在指定的 IP 地址和端口;
   * **服务端如果只 bind 了 IP 地址和端口,而没有调用 listen 的话,然后客户端对服务端发起了连接建立,服务端会回 RST 报文。**
 * `listen`,进行监听,服务器端处于listen状态
@@ -228,3 +231,15 @@ TCP 的 Keepalive 这东西其实就是 **TCP 的保活机制**
 
 - 如果对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 **TCP 保活时间会被重置**,等待下一个 TCP 保活时间的到来。
 - 如果对端主机崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,**TCP 会报告该 TCP 连接已经死亡**。
+
+## 如何解决TCP的粘包问题?
+
+什么是粘包问题?
+
+> 粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息。
+
+一般有三种方式分包的方式:
+
+- 固定长度的消息:这种是最简单方法,即每个用户消息都是固定长度的,比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息。
+- 特殊字符作为边界:我们可以在两个用户消息之间插入一个特殊的字符串,这样接收方在接收数据时,读到了这个特殊字符,就把认为已经读完一个完整的消息。
+- 自定义消息结构:我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。

+ 115 - 6
面经/项目/中煤项目.md

@@ -1,16 +1,123 @@
 # kafka模块
 
-煤矿大数据平台作为智能矿山基础服务平台的一部分,承担智能矿山服务平台中煤矿数据的采集、传输、存储、分发、计算等任务,致力于达到打通煤矿数据壁垒、整合数据资源、规范煤矿数据等目标。我主要负责数据采集和该模块监控这个两部分的功能。主要应用的组件是kafka,为了与下层数据传感器或者叫收集器解耦,也为了缓解系统IO压力,我们引入了kafka模块做数据采集的主要组件,为了保证顺序消费,因为采集的数据将按照时间顺序存入到hbase中。在生产者那一段主要是使用对象池技术进行缓存kafka producer。在消费者那一端则采用线程池对消费的数据进行处理,主要采取策略模式,按照选择的不同策略进行计算,例如区间或者压强的简单计算,或者是数值替换,也支持三方jar包,然后通过futuer收集数据。最后存储到hbase中。为了保证消息的等幂性,消费者端我们采取redis进行缓存数据,缓存超时时间默认为3分钟,将ACK设置为1保证消息顺序消费,如果消息发送失败,则默认重试一次,因为这个系统更多的是在意吞吐量,对于消息是否丢失不是很在意。
+煤矿大数据平台作为智能矿山基础服务平台的一部分,承担智能矿山服务平台中煤矿数据的采集、传输、存储、分发、计算等任务,致力于达到打通煤矿数据壁垒、整合数据资源、规范煤矿数据等目标。我主要负责数据采集和管道监控这个两部分的功能。主要应用的组件是kafka,为了与下层数据收集器解耦,同时也为了缓解系统IO压力,引入了kafka模块做数据采集的主要组件。
+
+该模块主要采集传感器生成的实时数据,为了保证数据的有序,所以要保证数据的顺序消费,因为采集的数据将按照时间顺序存入到hbase中。在生产者那一端可以从mysql或者kafka管道中收集数据实时数据,为了保证顺序消费,综合考虑将重试次数设置为0,因为这个系统更多的是在意吞吐量,对于消息是否丢失不是很在意。在消费者那一端则采用线程池对消费的数据进行多线程处理,主要采取策略模式,按照选择的不同策略进行计算,例如区间或者压强的简单计算,或者是数值替换,也支持三方jar包,然后通过futuer收集数据。最后存储到hbase中。为了保证消息的等幂性,消费者端我们采取redis进行缓存分区偏移量,缓存超时时间默认为3分钟,手动提交ACK。等方式保证消息顺序消费。
+
+我完成的第二个模块就是kafka监控模块,主要监控kafka集群的状态,与kafka-egale类似。主要操作就是调用kafka提供的kafka-admin-client接口和JMX进行操作,这里进行了相应的优化,主要包括复用同一个连接,使用工厂模式,生产一个admin-client,这里主要加了饿汉式单例来保证线程安全,利用redis缓存topic的速率。最后对于该模块,采用热启动的方式优化,例如自动建表,提前获取相关admin-client链接等
+
+```java
+import redis.clients.jedis.Jedis;
+import org.apache.kafka.clients.consumer.*;
+import org.apache.kafka.common.TopicPartition;
+
+import java.time.Duration;
+import java.util.*;
+
+public class KafkaConsumerExample {
+    private static final String LOCK_PREFIX = "KAFKA_LOCK_";
+    private static final int LOCK_EXPIRE_TIME = 10000; //锁过期时间
+    private static final String OFFSET_SET_NAME = "KAFKA_OFFSETS"; //已处理记录的偏移量集合名称
 
-我完成的第二个模块就是kafka监控模块,主要监控kafka集群的状态,与kafka-egale类似。主要操作就是调用kafka提供的kafka-admin-client接口和JMX进行操作,这里进行了相应的优化,主要包括复用同一个连接,多个用户同时调用接口,都会通过工厂模式,单例的产生一个admin-client,这里主要加了两段锁和volatile关键字保证线程安全,利用redis缓存topic的速率。最后对于该模块,采用热启动的方式优化,例如自动建表,提前获取相关admin-client链接等
+    private Consumer<String, String> consumer;
+    private Jedis jedis;
 
+    public KafkaConsumerExample(Properties props, String redisHost, int redisPort) {
+        consumer = new KafkaConsumer<>(props);
+        jedis = new Jedis(redisHost, redisPort);
+    }
 
+    public void consume() {
+        Map<TopicPartition, Long> lastProcessedOffsets = readLastProcessedOffsets(); //读取上次处理的偏移量
 
-我参与了一个煤矿大数据平台的开发项目,旨在为智能矿山服务平台提供支持并优化业务流程。在项目中,我负责设计和实现了数据采集和处理模块。我使用了Kafka作为数据采集工具,通过Kafka监控模块对数据进行实时监控和处理,利用Redis进行缓存以提高数据查询效率。
+        while (true) {
+            ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100)); //批量拉取消息
+            //遍历每个分区的消息
+            for (TopicPartition partition : records.partitions()) {
+                List<ConsumerRecord<String, String>> partitionRecords = records.records(partition);
+
+                //获取分区的最新偏移量,并检查是否已经处理过该记录
+                long lastOffset = partitionRecords.get(partitionRecords.size() - 1).offset();
+                long lastProcessedOffset = lastProcessedOffsets.getOrDefault(partition, -1L);
+
+                if (lastProcessedOffset >= lastOffset) { //如果已经处理过,则跳过该分区
+                    continue;
+                }
+
+                //尝试获取 Redis 锁
+                String lockName = LOCK_PREFIX + partition.topic() + "_" + partition.partition();
+                boolean locked = false;
+
+                try {
+                    while (!locked) {
+                        Long result = jedis.setnx(lockName, "1"); //使用 SETNX 命令尝试获取锁
+
+                        if (result != null && result == 1) { //如果成功获取锁,则设置超时时间并跳出循环
+                            jedis.expire(lockName, LOCK_EXPIRE_TIME);
+                            locked = true;
+                        } else { //否则等待一段时间后重试
+                            Thread.sleep(50);
+                        }
+                    }
+
+                    //处理消息
+                    for (ConsumerRecord<String, String> record : partitionRecords) {
+                        if (record.offset() <= lastProcessedOffset) { //如果已经处理过,则跳过该记录
+                            continue;
+                        }
+
+                        // TODO: 处理消息的业务逻辑
+
+                        lastProcessedOffsets.put(partition, record.offset()); //将偏移量记录到最新处理偏移量表中
+                    }
+                    consumer.commitSync(Collections.singletonMap(partition, new OffsetAndMetadata(lastProcessedOffset))); //手动提交偏移量
+                } catch (Exception e) {
+                    e.printStackTrace();
+                } finally {
+                    if (locked) { //释放 Redis 锁
+                        jedis.del(lockName);
+                    }
+                }
+            }
+
+            if (!lastProcessedOffsets.isEmpty()) { //保存已处理的偏移量至 Redis
+                saveLastProcessedOffsets(lastProcessedOffsets);
+            }
+        }
+    }
+
+    private Map<TopicPartition, Long> readLastProcessedOffsets() {
+        Set<String> offsetKeys = jedis.smembers(OFFSET_SET_NAME);
+        Map<TopicPartition, Long> lastProcessedOffsets = new HashMap<>();
+
+        for (String key : offsetKeys) {
+            String[] parts = key.split("_");
+            String topic = parts[0];
+            int partition = Integer.parseInt(parts[1]);
+            long offset = Long.parseLong(jedis.get(key));
+
+            lastProcessedOffsets.put(new TopicPartition(topic, partition), offset);
+        }
+
+        return lastProcessedOffsets;
+    }
+
+    private void saveLastProcessedOffsets(Map<TopicPartition, Long> lastProcessedOffsets) {
+        jedis.multi();
+
+        for (Map.Entry<TopicPartition, Long> entry : lastProcessedOffsets.entrySet()) {
+            String key = OFFSET_SET_NAME + "_" + entry.getKey().topic() + "_" + entry.getKey().partition();
+            jedis.set(key, String.valueOf(entry.getValue()));
+            jedis.sadd(OFFSET_SET_NAME, key);
+        }
+
+        jedis.exec();
+    }
+}
+
+```
 
-为了保证项目的性能和稳定性,我针对Kafka监控模块进行了优化,包括对消费者组进行调优、调整分区和副本数量、设置合理的消息大小和缓冲区大小等。这些优化措施显著提升了系统的数据处理速度和稳定性。
 
-通过我在数据采集和处理模块的设计和实现,项目成功地实现了高效、可靠的数据采集和处理流程,为智能矿山服务平台提供了稳定的数据支持,并在业务流程优化方面取得了显著的成果。项目上线后,数据处理速度提升了30%,用户体验得到了明显的改善。此外,通过合理的资源管理和团队协作,项目按计划完成,并且得到了用户和业务部门的高度认可。
 
 ## kafka监控
 
@@ -48,4 +155,6 @@ ack = 1
 * 策略模式
 * 责任链模式
 
-流量分析项目是六所网络安全部门的一个子项目,它主要功能是识别工控设备异常流量,达到防范攻击和攻击溯源的功能,我主要负责的是工控协议流量解析部分,负责12种工控协议的解析。协议解析主要参照各个协议的官网进行翻译,主要通过策略模式进行协议选择,和通过责任链模式进行协议判断,然后将该jar包通过SPI注入到服务方的服务中去
+流量分析项目是六所网络安全部门的一个子项目,它主要功能是识别工控设备异常流量,达到防范攻击和攻击溯源的功能,我主要负责的是工控协议流量解析部分,负责12种工控协议的解析。协议解析主要参照各个协议的官网进行翻译,主要通过策略模式进行协议选择,选择协议之后通过责任链模式进行协议判断,主要实现了对方提供的接口,输出的是解析成功的json格式的对象,通过SPI机制生成一个三方jar包。
+
+该项目的主要难点就是协议需要参照协议官网进行一个字节一个字节的分析解析,并对其错误情况进行判断。