编程导航B站话题讨论

B站

267 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

02 盘点2025遇到的各大厂面试真题总结

# 写在前面的话 已写**Java后端完整版学习路线**(仔细讲解每个技术栈怎么学,学到什么程度可以投实习/面试,怎么选择简历项目等内容)+ **Redis核心面试考点深度梳理** + **MySQL八股深度梳理** 3篇长文,如有需要欢迎大佬们自取,后续还会更新MySQL、RabbitMQ、SSM等其他部分的面试高频八股梳理,欢迎关注。 [01 非科班转码拿下大厂Offer,花费一天整理的Java后端完整版学习路线](https://www.codefather.cn/post/2005544965425446914) [03 一文吃透 Redis 核心考点,面试真题深度梳理](https://www.codefather.cn/post/2006203053207732225) [04 MySQL面试八股看这一篇就够了——深度梳理MySQL面试问题](https://www.codefather.cn/post/2008053544355131393) # 百某度篇 ## 1、JVM OOM的排查与内存泄露情况 **JVM OOM的可能原因:** <p align="left">JVM的OOM错误根据发生的内存区域不同,原因也各不相同。Java 8之后,内存区域主要分为以下几部分,对应的OOM原因如下:</p> **1. Java堆空间(Heap Space)OOM:** - **原因**:对象实例占用的内存总量达到了堆内存的最大值(通过-Xmx设定)。 - **具体场景**: - **内存泄漏**:这是最核心的原因。对象已经不再使用,但由于错误的引用,垃圾收集器无法回收它们,导致这些无用对象持续堆积,最终耗尽内存; - **内存设置过小**:-Xmx设置的值相对于应用程序的正常需求来说太小了; - **数据量激增**:某一时刻数据量异常庞大(如大文件导入、高并发请求),瞬间创建了大量对象,超出了堆的承载能力。 **2.元空间(Metaspace)OOM:** - **原因**:加载的类、方法等元数据信息占用的本地内存(Native Memory) `-XX:MaxMetaspaceSize`; - **具体场景**: - **动态类生成**:大量使用了CGLIB动态代理(如Spring AOP)等技术,运行时生成了大量的新类; - **热部署/热加载**:应用频繁重启或热部署,旧的类加载器未被回收,导致其加载的类元数据也无法卸载,从而造成堆积; - **Tomcat部署过多应用**:同一个Tomcat实例下部署了多个应用,每个应用都有自己的类库; - **元空间大小设置不合理**:默认不限制(仅受本地内存大小限制),但如果设置了最大值且设置过小,则容易触发。 **3.GC Overhead Limit Exceeded:** - **原因**:这是一种“保护机制”式的OOM。JVM花费了超过98%的时间进行垃圾收集,但只回收了不到2%的堆内存。这意味着GC效率极低,几乎在做无用功。 - **本质**:这通常是堆内存OOM的一个前兆,根本原因还是堆内存快满了,而且充满了难以回收的对象(很可能是内存泄漏)。 **4.无法创建本地线程:** - **原因**:应用程序创建的线程数超过了操作系统或JVM进程的限制。 - **具体场景**: - **线程池配置不当**:创建了无限数量的线程(如没有上限的cachedThreadPool); - **应用确实需要极多线程**:如超高并发场景; - **操作系统限制**:设置的用户最大进程/线程数过低; - **内存不足**:每个线程都需要为其栈分配一定的内存(通过-Xss设置),如果本地内存(栈所需内存属于本地内存)不足,也无法创建新线程。 ## 2、 内存泄漏的常见场景 常见的内存泄露情况包括: 1. **静态集合**,业务代码不断向其中添加对象,但是从不移除(因为静态集合被其所属的类(Class对象)直接引用,而类本身又被加载它的类加载器(ClassLoader)引用。类加载器作为GC Roots的一部分,是JVM垃圾回收机制绝不会回收的对象。因此,这条强大的引用链保护了静态集合不会被回收) 2. **使用了本地缓存**,但没有设置合理的过期时间或大小限制; 3. **资源未关闭**,数据库连接、文件流等使用完之后没有关闭; 4. **生命周期过长的对象持有短生命周期对象的引用**,例如,一个全局的单例对象持有了一个HttpRequest对象的引用。 ## 3、 JVM OOM排查思路: 1. **确认OOM类型**:第一时间查看错误日志的第一行,确认是哪种OutOfMemory。这直接决定了后续的排查方向。例如,是堆空间还是元空间。 2. **立即保留现场**:在启动脚本中预先加入以下JVM参数,以便在发生OOM时自动生成dump文件: `-XX:+HeapDumpOnOutOfMemoryError # 发生OOM时记录dump文件 -XX:HeapDumpPath=/path/to/save/dump.hprof dump文件的保存路径 -XX:OnOutOfMemoryError="your_script.sh" # 可选,用于发生OOM后执行一些命令,如发告警 如果之前没配置,但进程还在,尝试用jmap命令立即dump当前内存状态` 注意:在生产环境执行jmap可能会引发STW(Stop-The-World),需谨慎。 3. **分析现场信息**: 查看GC日志:如果配置了GC日志(`-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps`),分析GC频率、耗时、内存回收效果。如果看到老年代使用率持续上升且Full GC后回收效果很差,基本断定是内存泄漏。 使用工具分析Heap Dump文件: Eclipse MAT (Memory Analyzer Tool):这是最强大、最常用的离线堆转储文件分析工具; JVisualVM:JDK自带的工具,功能较MAT弱一些,但方便快捷; 4. **复现问题**: 如果问题无法在线上立即解决,需要尝试在预发布或测试环境复现。可以使用压力测试工具模拟线上流量,进行问题复现。 ## 4、内存泄漏专项排查 内存泄漏的排查是OOM问题中最复杂和最常见的。其核心思路是:找出哪些本该被回收的对象,仍然被意外地引用着(GC Roots),从而无法被GC回收。 使用Eclipse MAT进行分析: 1. 打开Heap Dump文件:使用MAT加载.hprof文件。 2. 寻找嫌疑对象:查看概览:打开后MAT通常会给出一个泄漏嫌疑报告。它会直接指出占用内存最大的对象和线程,这是第一线索。使用直方图(Histogram):查看各个类的实例数量(Objects)和总大小(Shallow + Retained Heap)。重点关注那些数量异常多或者占用总空间巨大的类。与正常情况对比尤其有效。 3. 定位GC Root:找到某个疑似泄漏的对象实例后,排除弱引用等,只显示强引用。这条引用链就是阻止该对象被回收的“罪魁祸首”! 4. 分析常见泄漏模式: 即问题2中介绍的内存泄露的常见场景 ## 5、上线需要配置哪些JVM参数 **核心基础参数(必须配置)** 这些参数定义了JVM最基本的行为,通常必须设置。 1. **堆内存大小(`-Xms, -Xmx`)** `-Xms 4g -Xmx 4g`:强烈建议将初始堆(-Xms)和最大堆(-Xmx)设置为相同值。这样可以避免运行时动态调整堆大小带来的性能开销,同时防止在扩容时因内存不足而发生的停顿。 3. **元空间大小**(`-XX:MetaspaceSize, -XX:MaxMetaspaceSize`) `-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m`:设置元空间初始大小和最大值。元空间使用本地内存,默认只受限于系统内存,设置上限可以防止应用因类加载器泄漏等问题耗尽系统内存。 3. **垃圾收集器** JDK 8及以前:`-XX:+UseConcMarkSweepGC(CMS)`或`-XX:+UseG1GC(G1)` JDK 9及以后:默认就是G1。对于新项目,G1是绝大多数场景的最佳选择。 `-XX:+UseG1GC`:显式指定使用G1垃圾收集器。 4. **GC日志(必须配置)** 这是排查线上问题最重要的依据。 - `-Xloggc:/path/to/your/logs/gc-%t.log`:指定GC日志输出路径,%t会增加时间戳,避免覆盖。 - `-XX:+PrintGCDetails`:打印详细的GC信息。 - `-XX:+PrintGCDateStamps`:在GC日志中增加日期时间戳,便于定位。 - `-XX:+UseGCLogFileRotation`:开启GC日志滚动。 - `-XX:NumberOfGCLogFiles=5`:保留的GC日志文件数量。 - `-XX:GCLogFileSize=10M`:每个GC日志文件的大小。 5. **OOM时自动转储堆快照(必须配置)** `-XX:+HeapDumpOnOutOfMemoryError`:在发生OOM时自动生成Heap Dump文件。 `-XX:HeapDumpPath=/path/to/your/logs/java_heapdump.hprof`:指定Heap Dump文件的存放路径。 6. **字符集(选择性)** `-Dfile.encoding=UTF-8`:确保应用使用统一的UTF-8编码,避免乱码问题。 ## 6、 内存与GC调优参数 在核心参数基础上,根据具体应用特性(如响应优先还是吞吐优先、内存大小等)进行微调。以下以G1垃圾收集器为例: **G1相关调优** `-XX:MaxGCPauseMillis=200`:设置期望的最大GC停顿时间目标(毫秒)。G1会尽力实现,但不保证。默认200ms,对于响应敏感的应用可以设为100甚至50。 `-XX:InitiatingHeapOccupancyPercent=45`:设置触发Mixed GC的堆占用率阈值(老年代占整个堆的比例)。默认45%,如果GC频繁可以适当降低此值,让G1更早开始回收。 `-XX:ConcGCThreads= / -XX:ParallelGCThreads=`:可以手动设置并发和并行GC的线程数,通常不需要调整。 **其他内存区域** `-Xss256k`:设置每个线程的栈大小。默认1M,对于微服务等线程数多的应用,可以适当减小(如256k/512k)以创建更多线程,但要保证不出现StackOverflowError。 `-XX:MaxDirectMemorySize=1g`:设置堆外内存(Direct Buffer)的大小上限,防止Netty等框架耗尽堆外内存。 ## 7、 hashMap有哪些线程安全问题 **死循环(JDK 1.7及之前版本特有)** <p align="left">这是最著名、最严重的问题,主要发生在JDK 1.7及更早版本中。原因是这些版本的HashMap在扩容时采用“头插法”来转移节点。</p> **触发场景**:多个线程同时对一个HashMap进行put操作,触发了扩容。在扩容过程中,链表中的节点会倒序转移。如果有两个线程A和B同时执行转移操作,线程A在执行到一半时被挂起,线程B完成转移。当线程A恢复后继续执行,就可能形成一个循环链表。当下次有线程对这个链表进行查询(get)或插入时,就有可能进入这个无限循环。 **注意**:在JDK 1.8中,HashMap的链表转移改为了“尾插法”,并且引入了红黑树优化。“尾插法”理论上避免了循环链表的问题,但并不意味着HashMap在1.8中就变得线程安全了,它仍然存在其他严重的并发问题。 **数据覆盖(所有版本都存在)** <p align="left">这是最常见的并发问题,发生在put操作中。</p> **触发场景**:两个线程A和B同时对一个HashMap执行put操作,并且计算出的哈希值对应的是同一个桶(bucket)。 **问题根源**:假设线程A和B都执行到步骤1,发现桶是空的。于是它们都认为自己可以插入新节点。线程A先将新节点插入到桶中,但随后线程B也将其新节点插入到同一个位置,覆盖了线程A插入的数据。最终,线程A放入的数据就丢失了。 **数据不一致/脏读(所有版本都存在)** <p align="left">即使没有发生数据覆盖,由于没有内存可见性(Volatile)保证,一个线程的修改可能不会立即对其他线程可见。</p> **触发场景**:线程A成功put了一个值,但线程B在执行get时,可能由于CPU缓存没有及时刷新,读取到的是旧的、未更新的值,而不是线程A刚刚放入的新值。 ## 8、 怎么去解决HashMap的线程安全问题 <p align="left">为了解决线程安全问题,有几种方案:</p> **1. 使用Collections.synchronizedMap** <p align="left">它会返回一个同步包装器,所有方法(get,put,size等)都被synchronized关键字保护,相当于给整个对象加了一把大锁。</p> **优点**:实现简单,代码修改成本低(只需包装一下)。 **缺点**:性能是瓶颈,因为所有操作都需要竞争同一把锁,并发度低。 **2. 使用ConcurrentHashMap(推荐)** <p align="left">这是JUC(java.util.concurrent)包中提供的专门用于高并发的Map实现。</p> <p align="left">JDK 1.7采用分段锁(Segment)机制,将整个数据结构分成多个段(Segment),每个段一把锁。写操作只锁住对应的段,允许多个线程同时访问不同的段,提高了并发度。</p> <p align="left">JDK 1.8及之后做了巨大优化,放弃了分段锁,改用synchronized + CAS(Compare-And-Swap) + volatile的实现方式。锁的粒度更细,变成了对单个数组元素(桶的头节点)加锁,并发性能得到了极大的提升。</p> **优点**:高并发、高性能、线程安全。是当前多线程环境下的首选。 **实现原理具体介绍:** **1. put操作流程** **计算哈希**:计算key的哈希值((h ^ (h >>> 16)) & HASH\_BITS),spread后的哈希值。 **判断表是否为空**:如果数组未初始化,先调用initTable()进行初始化(使用CAS控制并发初始化)。 **定位桶**:根据哈希值找到对应的数组下标i。 **插入节点**: 1. **情况一(桶为空)**:如果table[i]为null,直接使用CAS操作将新节点放入该位置。成功则结束,失败则意味着发生了竞争,重试。 1. **情况二(正在扩容)**:如果发现该桶的头节点是ForwardingNode(一个特殊的节点,哈希值为MOVED),则说明当前哈希表正在扩容。当前线程会协助进行数据迁移。 1. **情况三(桶非空)**:对桶的头节点f加synchronized锁。 1. 如果是**链表**,则遍历链表,找到key相同的节点则更新value,否则在链表尾部插入新节点。 1. 如果是**红黑树**,则调用红黑树的插入方法。 判断是否需要**树化**:插入链表后,如果链表长度达到8,会尝试调用treeifyBin方法。该方法会进一步判断当前数组容量是否达到64,如果达到则进行树化;否则只是触发扩容。 **计数**:调用addCount()方法增加元素个数,并检查是否需要进行扩容。 **2. get操作** get操作是**完全无锁的**,这也是它高性能的原因。 计算哈希,定位到数组下标。 如果头节点就是要找的key,直接返回。 如果头节点的哈希值小于0,说明该节点是特殊节点(可能是TreeBin或ForwardingNode),则调用这些节点对应的find方法来查找。 如果是链表,则遍历链表查找。 之所以不需要加锁,是因为Node的val和next属性都使用了volatile修饰,保证了多线程下的可见性。虽然get可能读到正在扩容或迁移过程中的中间状态,但设计的find方法能保证在并发环境下也能正确地找到数据。 **3. 扩容机制** 这是最复杂的设计之一。它通过一个volatile变量sizeCtl来控制整个扩容过程。 - **负数**:表示正在初始化或扩容。-1表示正在初始化,-N表示有N-1个线程正在协助扩容。 - **正数**:扩容的阈值(容量\*负载因子)。 - **0**:默认初始值。 扩容触发时机:在put后调用addCount时,如果发现元素数量超过阈值,就会触发transfer方法进行扩容。 **扩容特点:** - **多线程协助**:扩容时,原有数组的每个桶上会放置一个ForwardingNode标记。其他线程在进行put或remove操作时,如果遇到这个标记,就会先暂停自己的操作,来帮助进行当前桶的数据迁移。迁移完成后,再继续自己的操作。这大大加快了扩容速度。 - **步长(Stride)**:每个协助线程会领取一个“步长”的任务(比如一次迁移 16 个桶),完成后再继续领取,直到所有桶都迁移完毕。 ### 3. 使用Hashtable(已过时,不推荐) Hashtable是一个古老的线程安全类,它通过在所有方法上添加synchronized关键字来实现线程安全。 **缺点**:和Collections.synchronizedMap一样,性能极差,因为它也是全局锁。这是一个设计上就被淘汰的类,在新代码中不应再使用。 ## 9、 java实现的锁有哪些 **(1)由JVM直接提供的内置锁-**synchronized**关键字** **使用方式**: 同步代码块:手动指定锁对象(可以是任何对象实例或Class对象)。 同步实例方法:锁是当前对象实例(this)。 同步静态方法:锁是当前类的Class对象(如MyClass.class)。 **特点**: **互斥性**:同一时刻只有一个线程可以进入临界区。 **可重入**:同一个线程可以多次获取同一把锁(锁计数器+1)。 **不公平**:默认是非公平锁,不保证等待时间最长的线程优先获取锁。 **不可中断**:在等待获取锁的过程中,线程无法被中断。 **自动释放**:线程执行完同步代码或发生异常时,JVM会自动释放锁。 synchronized关键字的核心思想是:每一个Java对象都可以关联一个“监视器锁”(Monitor),也称为“内部锁”或“互斥锁”。当线程进入synchronized修饰的代码块时,会自动尝试获取与之关联的对象的Monitor锁。如果获取成功,则该线程成为该Monitor的所有者,可以执行代码块;其他线程则会被阻塞,直到锁被释放。 **(2)由JDK java.util.concurrent.locks包提供的显式锁** ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/qCFhJQj52ZCOgq4D.webp) **(3)使用场景总结** | **场景** | **推荐选择** | **理由** | | :------------------------------------: | :-----------------------------------------------: | :----------------------------------------------------: | | 通用的互斥场景 | synchronized | 代码简洁,由JVM自动优化和管理,性能优异,不易出错。 | | 需要高级功能(如可中断、超时、公平锁) | ReentrantLock | 提供synchronized不具备的灵活性。 | | 读多写少 | <p>ReentrantReadWriteLock</p><p>或StampedLock</p> | 显著提升读并发性能。StampedLock性能更优,但API更复杂。 | | 纯粹的计数器、状态标志更新 | 原子变量(如AtomicInteger) | 基于CAS,无锁操作,性能最高。 | | 控制同时访问的线程数 | Semaphore(信号量) | 是一种更广义的“共享锁”。 | ## 10、 重载和重写方法本质的区别 ### 重载 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。 如果多个方法有相同的名字、不同的参数,便产生了重载。 编译器通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。 Java 允许重载任何方法,而不只是构造器方法。 综上:重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。 ### 重写 重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。 方法名、参数列表必须相同,子类方法返回值类型应比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。 如果父类方法访问修饰符为private/final/static则子类就不能重写该方法,但是被static修饰的方法能够被再次声明。 构造方法无法被重写 综上:重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/bnFnkXXCyObTHPty.webp) ## 11、 静态方法为什么不能调用非静态成员 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化之后才存在,需要通过类的实例对象去访问。 在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作。 ## 12、 线程池怎么创建&默认线程池为啥一般不用 方式一:通过ThreadPoolExecutor构造函数来创建(推荐)。 方式二:通过Executor框架的工具类Executors来创建。 **通过Executors工具类可以创建多种类型的线程池,包括:** FixedThreadPool:固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 SingleThreadExecutor:只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 CachedThreadPool: 可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 ScheduledThreadPool:给定的延迟后运行任务或者定期执行任务的线程池。 **Executors返回线程池对象的弊端如下:** FixedThreadPool和SingleThreadExecutor:使用的是阻塞队列LinkedBlockingQueue,任务队列最大长度为Integer.MAX\_VALUE,可以看作是无界的,可能堆积大量的请求,从而导致OOM。 CachedThreadPool:使用的是同步队列SynchronousQueue,允许创建的线程数量为Integer.MAX\_VALUE,如果任务数量过多且执行速度较慢,可能会创建大量的线程,从而导致OOM。 ScheduledThreadPool和SingleThreadScheduledExecutor:使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为Integer.MAX\_VALUE,可能堆积大量的请求,从而导致OOM。 # 美某团篇 ## 1、接口和抽象类的区别和各自的应用场景 **接口和抽象类的共同点** 实例化:接口和抽象类都不能直接实例化,只能被实现(接口)或继承(抽象类)后才能创建具体的对象; 抽象方法:接口和抽象类都可以包含抽象方法。抽象方法没有方法体,必须在子类或实现类中实现。 **接口和抽象类的区别** **设计目的**:接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。 **继承和实现**:一个类只能继承一个类(包括抽象类),因为Java不支持多继承。但一个类可以实现多个接口。 **成员变量**:接口中的成员变量只能是public static final类型的,不能被修改且必须有初始值。抽象类的成员变量可以有任何修饰符(private, protected, public),可以在子类中被重新定义或赋值。 **方法**:Java 8之前,接口中的方法默认是public abstract,也就是只能有方法声明。自Java 8起,可以在接口中定义默认方法和静态方法。自Java 9起,接口可以包含private方法。 抽象类可以包含抽象方法和非抽象方法。抽象方法没有方法体,必须在子类中实现。非抽象方法有具体实现,可以直接在抽象类中使用或在子类中重写。 Java 8引入的default方法用于提供接口方法的默认实现,可以在实现类中被覆盖。这样就可以在不修改实现类的情况下向现有接口添加新功能,从而增强接口的扩展性和向后兼容性。 ## 2、进程之间的通讯方式 - **匿名管道:**用于**具有亲缘关系**的进程之间的通信,比如父子进程、兄弟进程,遵循**先进先出**原则,数据一旦被读出,便不在管道中存在,**不可反复读取**;容量有限,如果管道满了,写操作会阻塞;如果管道空了,读操作会阻塞; - **命名管道:突破了亲缘关系的限制**,任何无关进程都可以通过访问该路径进行通信,其他和匿名管道一样; - **消息队列:支持无关进程间通信**。消息有类型,读进程**可以根据类型有选择地接收消息**,而不必须是FIFO顺序。内核中的消息队列有最大长度和最大消息数的限制。 - **共享内存:**允许多个进程共享同一块物理内存区域。进程通过系统调用创建一块共享内存区,并**将其映射到自己的虚拟地址空间**。之后,进程就可以像访问普通内存一样读写这块区域。**速度极快**:因为数据不需要在内核和用户空间之间来回拷贝。**需要同步机制**(如信号量或互斥锁):因为多个进程同时读写同一块内存,会产生竞争,必须由程序员自己控制同步。 - **信号量:本质不是用来传递数据的**,而是一个**计数器**,用于实现进程间的**同步与互斥**(防止多个进程同时访问一个共享资源)。它主要用于控制多个进程对共享资源(如共享内存)的访问。信号量的值表示可用资源的数量。通常与共享内存等方式配合使用,解决其同步问题。 - **信号:异步通信机制**,用于通知接收进程某个事件已经发生。进程可以发送信号给自身或其他进程,内核也可以在特定事件(如除零错误、用户按下Ctrl+C)发生时发送信号给进程。特点是**开销小,但能携带的信息量极少**(只有一个信号编号)。用于处理**异常、中断或简单的进程控制**(如终止、暂停、继续)。 - **套接字socket:**不仅可以用于同一台主机的进程间通信,更主要用于**不同主机**(网络)上的进程间通信。套接字是一种通信端点,通过IP地址、端口号和协议类型来定义。它支持多种协议,最常见的是TCP(面向连接、可靠)和UDP(无连接、不可靠)。适用范围最广(跨网络)。功能强大但设置相对复杂。 ## 3、分布式场景,全局限流器是怎么添加的?(实习相关) 需要对“账号池”这个资源进行全局性的速率控制。关键在于: 1. **全局性**:限流状态必须被所有发起请求的JVM实例共享(比如您部署了多个服务实例)。 1. **高效率**:限流器的判断必须非常快,不能成为性能瓶颈。 1. **准确性**:在分布式环境下,限流要尽可能准确,避免在时间窗口切换时产生超出限额的请求(毛刺现象)。 首先,可以直接回答我们方案里这个全局限流器是通过现成的限流中间件(类似阿里的Sentinel)来实现的。 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/zT8yaNDK2mA5lig3.webp) 其次,可以介绍下redis + Lua脚本的方式: ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/kq7djTXW3nqEvmSI.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/J0iroDYa7xPonn4D.webp) ## 4 Java线程安全的List ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/l0Z2vHEUKVbHY9Iw.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/8IGDij08NiXGEau7.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/UKx1Y9RbtwxkXnKI.webp) ## 5 Lua脚本是不是一定能够保证原子性 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/59QyfcUhTd19rdMJ.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/WnNpk6MPNL2enjFk.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/SqNXkA4Mn4bxtjhk.webp) ## 6 MySQL可重复读为什么还会有幻读问题? ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/kx5bQHKHnisY8KSd.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/2GaHvYHv8iUMk5Ji.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/A3fqm6evyOELdd3R.webp) ## 7 RabbitMQ整体的系统架构 **生产者**:消息的发送方,创建消息并发布到RabbitMQ服务器(Broker)上的一个交换机; **消费者**:消息的接收方,可以订阅一个或多个队列,当队列有消息时,Broker会将消息推送给消费者,消费者处理这些消息; **交换机**:消息的路由中心,生产者将消息发送到交换机,而不是直接发送到队列,交换机会根据路由规则决定把消息投递到哪个消息队列上; - 如果是Direct交换机,是精确匹配的路由键,会把消息投递到绑定键与之完全匹配的队列,点对点的精确发送; - 如果是Fanout:广播模式,将消息投递到所有绑定到该交换机上的队列,忽略路由键; - 如果是Topic:模式匹配路由键,使用通配符(\*匹配一个词,#匹配零或多个词)进行匹配。常用于基于模式的多播。 - Headers:通过匹配消息的Headers属性(而非路由键)来路由消息。不常用。 **队列**:消息的**存储缓冲区**,本质上是一个FIFO(先进先出)的数据结构。职责是存储被交换机路由过来的消息,直到消息被消费者安全地处理(ACK)或丢弃。队列可以持久化(确保Broker重启后仍存在),也可以是独占的(只对声明它的连接可见,连接关闭后自动删除)或自动删除(当最后一个消费者断开连接后自动删除)。 **Broker**:指RabbitMQ服务器本身,是以上所有组件的运行容器。接收客户端连接、管理消息的路由和分发、实现AMQP协议、提供权限管理和插件系统等。 ## 8 RabbitMQ消息堆积怎么处理 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/8EXMkWROZwdX9ykc.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/pTvJGBrdDnQzk0aO.webp) ## 9 优惠劵场景,RabbitMQ保证一致性问题 本地消息表的核心思想是**将分布式事务拆分为两个本地事务**,通过数据库的事务特性和重试机制保证最终一致性: 第一个本地事务:在业务数据库中同时完成订单状态更新和消息记录 第二个本地事务:通过定时任务将记录的消息可靠地发送到MQ **具体实现流程:** **第一阶段:业务处理与消息记录** **开启数据库事务**:当优惠券被抢后,系统开始处理优惠券状态更新 **更新订单状态**:在同一个数据库事务中,将优惠券状态从"待发放"改为"已发放" **写入本地消息表**:在同一事务中,向专门设计的消息表插入一条记录,包含: 消息ID(唯一标识)、消息内容(JSON格式的优惠券数据)、消息状态(初始为"待发送")、创建时间、重试次数(初始为0) **提交事务**:只有当优惠券更新和消息记录都成功后才提交事务 **第二阶段:消息发送与状态更新** **定时任务扫描**:系统有一个独立的定时任务,定期(如每5秒)扫描本地消息表中状态为"待发送"的记录 **发送MQ消息**:对于每条待发送记录:尝试将消息内容发送到消息队列。如果发送成功:更新该消息记录状态为"已发送";如果发送失败:增加重试次数、记录错误日志、保持状态为"待发送" **重试机制**:对于发送失败的消息,定时任务会在下次扫描时重新尝试发送,直到:发送成功或达到最大重试次数(如5次),此时可将状态改为"发送失败"并报警。 ## 10 redis怎么去添加分布式锁,应该注意什么 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/JyS8I0DH1SonR8ZR.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/jPXOu4XNyXmOHmJx.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/a7jDpPKvMcrjUzyx.webp) ## 11 Java 17的新特性,G1之后的新垃圾回收器 **G1之后的新一代垃圾回收器** (G1)自Java 9起成为默认回收器,它的设计目标是在延迟和吞吐量之间取得良好平衡。但在它之后,为了应对**超大内存**(多TB级别)和**极致低延迟**(停顿时间<10ms)的需求,Oracle推出了更为先进的回收器:**ZGC**。 核心目标是:**尽可能缩短垃圾回收引起的停顿时间(STW),并且停顿时间不会随着堆大小的增加而显著增长**。 **关键技术与特点**: - **并发处理**:ZGC的几乎所有工作(标记、转移、重定位)都是**并发**执行的,仅在最初有一个极短的STW进行根扫描。 - **染色指针**:它在指针的64位地址中存储了额外的元数据,用于标记对象的状态(可达、已转发等)。这使得垃圾回收器在并发阶段能直接通过指针判断对象状态,而无需访问对象本身。 - **负载屏障(Load Barrier)**:当应用程序线程从堆中加载引用时,会触发一个“负载屏障”。这个屏障检查指针上的元数据,如果需要,会在应用程序线程继续执行之前完成一些轻量的回收工作(如对象重定位)。 **其他值得一提的GC** - **Serial GC**:依然是单线程、小内存场景(如客户端程序、嵌入式设备)的最高效选择。 - **Parallel GC**:又名吞吐量收集器,在Java 8及之前是默认的,适合需要最大化计算吞吐量、对停顿不敏感的后台处理应用。 ## 13 数据冷热库分库,如果冷库数据突然变热怎么办? ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/ClSalqSC9yUvvmay.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/qqTuCrYmfxxmk5S5.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/IDljs5jrnQm2PL60.webp) ## 14 索引下推 索引下推的核心思想是:将原本在Server层进行的部分数据过滤操作,“下推”到存储引擎的索引层面来完成。 这样做的最大好处是减少了存储引擎必须回表查询的次数和返回给Server层的数据行数,从而显著提升查询性能,尤其是对于包含模糊查询或范围查询的复合索引场景。 为什么需要索引下推?—— 通过一个例子对比 假设我们有一张user表,并建立了一个复合索引idx\_name\_age (name, dep\_id, age)。 现在我们要执行这样一个查询:查找所有姓“张”并且年龄大于15岁的用户。 `SELECT \* FROM user WHERE name LIKE '张%' AND age > 15;` ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/UcM9dcN3rb0SQ355.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/nHNhG6S9L8Z2vgiW.webp) ## 15 RabbitMQ怎么保证消息的顺序性 **方案一:单队列单消费者(FIFO最简单模式)** 这是最直接但也限制最大的方法。 **原理**: - **生产者**:将需要保证顺序的所有消息发送到**同一个队列**中。 - **消费者**:该队列**只能有一个消费者**,并且设置channel.basicQos(1),即每次只取一条消息,处理完一条再取下一条。 **优点**: - 实现简单,充分利用了RabbitMQ队列本身的FIFO(先进先出)特性。 **缺点**: - **无法水平扩展**:单个消费者是性能瓶颈,吞吐量低。 - **单点风险**:如果该消费者宕机,虽然消息不会丢,但处理会完全停止。 - **适用场景**:消息量非常小,对吞吐量要求不高的场景。 **方案二:根据消息ID或业务键进行分组(路由到同队列)** 这是最常用且合理的方案,核心思想是:**将需要保证顺序的消息通过路由键确保它们进入同一个队列,并被同一个消费者顺序处理**。 **原理**: - **消息分组**:对消息进行分组。例如,订单ID为order\_123的所有消息(创建、付款、发货)必须顺序处理;而订单ID为order\_456的消息是另一组,它们之间没有顺序要求。 - **生产端**:使用**一致性哈希交换器**或**自定义路由键**,将同一组的消息总是路由到同一个队列。 **例如**:使用x-modulus-hash这类交换器,以订单ID order\_123作为路由键,计算出的哈希值总会将其路由到队列queue\_2。 - **消费端**:为**每个队列启动一个消费者**(可以是多个队列,即多个消费者实例,每个实例处理不同组的消息)。同样,每个消费者需要设置prefetchCount=1。 **工作流程**: - 订单A(ID=1)的所有消息 → 路由键order\_1→始终发往**队列1**→ 由**消费者1**处理。 - 订单B(ID=2)的所有消息 → 路由键order\_2→ 始终发往**队列2**→ 由**消费者2**处理。 **优点**: - **高性能且可扩展**:不同的组(如不同的订单)可以被不同的消费者并行处理,解决了方案一的瓶颈问题。 - **逻辑清晰**:符合大部分业务场景(如订单、会话跟踪)。 **缺点**: - 需要提前规划好消息的分组逻辑。 - 如果某个组的消息特别多(“热点订单”),对应的队列和消费者可能会有压力,但通常这种情况较少。 ## 16 MySQL持久化机制;redolog日志的作用 MySQL的持久化机制的核心目标是:确保一旦事务提交,它对数据库所作的更改就是永久性的,即使发生系统崩溃、断电等故障,数据也不会丢失。 这个机制主要依赖于以下几个核心组件和技术协同工作: InnoDB缓冲池 (Buffer Pool) 重做日志(Redo Log) 二进制日志(Binlog) 双写缓冲区(Doublewrite Buffer) 日志刷盘机制 **1. 核心组件介绍** **a.InnoDB缓冲池** 这是InnoDB引擎的核心内存区域,用于缓存数据和索引。所有的数据修改(增、删、改)都不是直接写入磁盘的数据文件,而是首先在Buffer Pool中修改对应的“数据页”。这极大地提升了性能。 **脏页(Dirty Page)**:在Buffer Pool中被修改过但与磁盘上数据文件内容不一致的数据页,被称为“脏页”。 **刷脏(Flushing)**:后台有专门的线程负责在特定时机将“脏页”刷新到磁盘的数据文件中,使内存和磁盘的数据保持一致。 **b.重做日志 (Redo Log)** 这是InnoDB引擎特有的日志,是实现**崩溃恢复(Crash Recovery)**的关键。它是一个物理日志,记录的是**物理数据页的修改内容**(例如:“在表空间A、页面B、偏移量C处写入数据D”)。 - **循环写入**:Redo Log文件是固定大小的,通常由两个文件(ib\_logfile0, ib\_logfile1)组成,以循环写入的方式工作。 - **顺序I/O**:写入Redo Log是顺序追加的,速度远快于随机写入数据文件。 - **作用**:当发生崩溃时,InnoDB会重放Redo Log中从最后一个检查点(Checkpoint)开始的所有日志记录,将数据库恢复到崩溃前的状态,从而保证已提交事务的持久性。 **c.二进制日志(Binlog)** 这是MySQL Server层实现的日志,所有存储引擎都可以使用。它是一种逻辑日志,记录的是**逻辑性的SQL语句**(Statement模式)或**行的更改**(Row模式)。 **主要用途**:用于**主从复制(Replication)**和**基于时间点的数据恢复(Point-in-Time Recovery)**。 **追加写入**:Binlog文件会不断增长,不会循环覆盖。 **d.双写缓冲区(Doublewrite Buffer)** 这是 InnoDB 的一个安全特性,用于解决**部分页写入**问题。 **问题**:操作系统磁盘I/O的单位是页(通常是4KB),而InnoDB数据页的大小是16KB。这意味着写一个数据页需要4次磁盘I/O。如果在写入过程中系统崩溃,可能导致只有部分4KB被写入(页数据损坏)。 **解决方案**:在将“脏页”刷到数据文件的实际位置**之前**,InnoDB会先将它们顺序地、批量地写入磁盘上一个叫“双写缓冲区”的连续区域。完成后,再将这些页分散写入到数据文件中各自的真正位置。 **恢复**:如果发生崩溃,InnoDB在恢复过程中可以通过比较双写缓冲区中的数据页和数据文件中的页来发现损坏的页,并用双写缓冲区中完整的副本来修复它。 **2. 持久化流程(以更新操作为例)** **事务执行**:应用程序发出UPDATE语句。 **修改缓冲池**:InnoDB从磁盘(或Buffer Pool)加载对应的数据页到Buffer Pool,并在内存中修改它,使其成为“脏页”。 **写入Redo Log Buffer**:将产生这次修改的**Redo Log 记录**写入内存中的**Redo Log Buffer**。 **事务提交(关键步骤)**:应用程序发出COMMIT。 此时,根据innodb\_flush\_log\_at\_trx\_commit的设置,会有不同的行为: - **= 1 (默认,最安全)**:**强制**将Redo Log Buffer中的**所有内容**刷新(fsync)到磁盘的Redo Log文件。**确保**只要事务提交成功,其对应的Redo Log一定已经物理写入磁盘。这是真正的持久化保证。 - **= 2**:将Redo Log Buffer中的内容**写入**到操作系统内核的页面缓存(Page Cache),但**不立即执行**fsync。如果MySQL进程挂了,但操作系统没挂,数据不会丢;如果机器断电,由于数据还在Page Cache中,可能会丢失约1秒的数据。 - **= 0**:每秒一次地将Redo Log Buffer的内容写入Page Cache并刷盘。**事务提交时不会主动触发写入**。性能最好,但崩溃时最多会丢失1秒钟的事务。 **写入Binlog**:在事务提交的过程中,也会将本次修改以逻辑格式写入Binlog Cache,然后根据sync\_binlog参数的设置,决定何时将Binlog刷盘(sync\_binlog=1表示每次提交事务都刷盘,最安全)。 **两阶段提交(2PC)**:为了确保Redo Log和Binlog的逻辑一致性(即两者要么都记录了这个事务,要么都没记录),MySQL使用了内部的两阶段提交协议。这是另一个复杂但至关重要的机制。 **返回成功**:完成上述步骤后,才向客户端返回“提交成功”。 **后台刷脏**:提交成功后,数据仍在Buffer Pool的“脏页”中。InnoDB的后台线程会在之后某个时间点(根据负载、脏页比例等因素)将“脏页”通过双写缓冲区安全地刷新到磁盘的数据文件中。 **如何保证持久性?** 在内存中的脏页被刷盘之前,一定要先保证其对应的Redo Log已经持久化到磁盘。 这意味着,数据的修改不是立即写数据文件,而是先写日志(Redo Log)。只要Redo Log被安全地写入磁盘,即使数据页还没有刷盘,系统崩溃后依然可以通过Redo Log来重做恢复。而提交事务时强制刷Redo Log(innodb\_flush\_log\_at\_trx\_commit=1)这个动作,就是持久性承诺兑现的时刻。 因此,配置MySQL实现真正的持久化,通常建议: - innodb\_flush\_log\_at\_trx\_commit = 1 - sync\_binlog = 1 - innodb\_doublewrite = ON(默认) 虽然这样配置会牺牲一些写性能(因为每次提交都要等待磁盘I/O),但它提供了最高级别的数据安全保证。在实际生产中,会根据业务对数据安全和性能的要求进行权衡。 **RedoLog日志的作用** **为什么需要Redo Log?** 要理解它的作用,首先要明白它要解决什么问题: **性能问题**:直接写磁盘上的数据文件是**随机I/O**,速度很慢。如果每次事务提交都要把修改的数据页写回磁盘,数据库的性能会极其低下。 **安全性问题**:如果不在事务提交时做点什么,万一此时数据库崩溃,内存中已提交但未写入磁盘的数据就会永远丢失,这违反了事务的“持久性”原则。 InnoDB的解决方案是: **修改数据先在内存中完成**:所有数据的修改都在内存中的**缓冲池(Buffer Pool)**里进行,这样可以极大地提升速度。 **采用“预写日志”策略**:但为了保证持久性,它遵循**WAL (Write-Ahead Logging)**原则,即**在内存中的脏页被刷回磁盘之前,必须先把描述这个修改的日志记录持久化到磁盘**。这个“描述修改的日志”就是**Redo Log**。 **Redo Log 是如何工作的?** 可以把Redo Log想象成一个**高效的“记账本”**。 **记账内容**:它记录的是**物理日志**,内容类似于:“在表空间A、页面号B、偏移量C的位置,将数据更新为D”。这种记录方式非常紧凑且高效。 **顺序写入**:Redo Log文件是**循环写入、顺序追加**的。顺序写的速度远快于随机写数据文件。 **流程比喻**: 当你要修改一条数据时(比如更新金额),InnoDB并不会直接去金库(数据文件)里翻箱倒柜。 它先把“某某账户增加100元”这个操作记录到“记账本”(Redo Log Buffer)上。 当你确认交易完成(事务提交)时,InnoDB会**立刻让账房先生把这条记账内容用钢笔(**fsync**)正式地、永久地誊写到账本上**(将Redo Log刷盘)。 至于真正去金库里搬动金币(将脏页刷回数据文件)这个体力活,可以交给后台伙计稍后闲的时候批量完成。 这样,即使伙计还没来得及去金库搬钱(脏页未刷盘),突然房子塌了(数据库崩溃),重启后只要**查一下记账本(重放 Redo Log)**,就知道哪些交易完成了但还没入库,然后根据记录再去金库把该搬的钱搬好,数据就恢复了。 **Redo Log的两个主要作用** 基于上述原理,Redo Log 具体负责两件事: **1.崩溃恢复(Crash Recovery)** 这是它最核心的职责。当MySQL实例意外宕机重启后,InnoDB会自动执行恢复流程: - 检查最后一个**检查点(Checkpoint)**。 - 从检查点开始,读取Redo Log文件中的记录。 - 将日志记录中描述的所有修改**重新应用(重做)**到数据页上,将数据库恢复到宕机前的状态。 - 这样就保证了所有已提交事务的数据都完好无损。 **2.确保提交事务的持久性** 通过配置参数innodb\_flush\_log\_at\_trx\_commit,你可以控制事务提交时Redo Log的刷盘行为: **= 1 (默认值,最安全)**:每次事务提交时,都**强制**将Redo Log从内存缓冲区(Log Buffer)刷新到磁盘。这确保了即使系统崩溃,已提交的事务也绝不会丢失。 **= 2**:每次事务提交时,只将Redo Log写入操作系统的页面缓存(Page Cache)。如果只是MySQL进程崩溃,操作系统没崩,数据不会丢;但如果机器断电,数据可能丢失。 **= 0**:每秒刷一次日志到磁盘。事务提交时不会主动触发写入。性能最好,但崩溃时最多会丢失1秒钟的事务。 **只要这个参数设置为 1,事务的持久性就得到了绝对保证。** ## 17 千万请求量的文件处理系统怎么去设计架构 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/LCnqII28hq9lqAUT.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/flpDk6CASxcdNK06.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/ImW6YAnmg1GqS0K2.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/4ySKt3AVw0saHVFW.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/GyI1Kv1bVSz9dxbh.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/doOobzz87180cAyJ.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/G6MQHFXP3dUCA6pN.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/BQEc6uTev23TpOjv.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/Z8FxY2t9pUfQq7Jj.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/6tVwcgTwHNIjWjqW.webp) ## 18 分布式熔断(Sentinel) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/3tpxxRAVLz5GYhlK.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/haqey3VTMfY5gmLI.webp) | 熔断策略 | 触发条件 | 统计时长(可配置) | 适用场景 | | -------------------------------- | ------------------------------------------------------------ | ---------------- | ------------------------------------------------------------ | | <p>**慢调用**</p><p>**比例**</p> | 单位统计时长内请求数>minRequestAmount**且**慢调用比例>slowRatioThreshold | 支持 | 依赖服务响应变慢(如数据库慢查询、第三方API延迟增高) | | **异常比例** | 单位统计时长内请求数>minRequestAmount**且**异常比例>count | 支持 | 依赖服务出现错误(如接口抛异常、超时) | | **异常数** | 单位统计时长内异常数>count | 支持 | 需要关注绝对异常数量的场景(注意timeWindow需配置得较大一些) | ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/tdPPciBfQQ8JxiF6.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/i4sEW5NmelGfYg2Z.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/yDaQVjBhsDSo71Rx.webp) ## 19 分布式锁是可重入锁吗 - **分布式锁**:指的是锁的作用范围,它是一种跨进程、跨机器的同步机制,用于在分布式系统中控制对共享资源的访问。 - **可重入锁**:指的是锁的一种行为特性(也称为递归锁)。它允许同一个线程在已经持有锁的情况下,再次成功获取该锁,而不会被阻塞。 | **实现方式** | **是否通常可重入?** | **说明** | | :-------------------------: | :------------------: | :----------------------------------------------------------: | | **数据库(如MySQL)悲观锁** | **通常不可重入** | 基于SELECT ... FOR UPDATE。同一个事务内再次执行此语句会被阻塞,因为MySQL的行锁在事务内不可重入。需要应用层自己模拟(例如记录线程ID和计数)。 | | **Redis(SETNX + Lua)** | **可实现为可重入** | 原生的SETNX命令本身不支持。但成熟的客户端(如**Redisson**)通过**Lua 脚本**实现了可重入锁。它在锁的Value中存储线程标识和重入计数。 | | **ZooKeeper** | **原生支持可重入** | ZooKeeper的序列节点特性可以很自然地实现可重入锁。客户端在创建节点时记录自己的信息,再次尝试获取锁时会检查当前节点是否已经是持有锁的节点。**Curator框架**提供的InterProcessMutex就是可重入的。 | | **Etcd** | **可实现为可重入** | 类似于ZooKeeper,基于租约和KV存储,可以通过在Value中存储持有者信息和计数器来实现可重入。 | ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/N4v9HIO5oNjnwdRs.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/NVVWPGVVuTauVRte.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/pAkpTijsK2F7RhU1.webp) # B某站篇 ## 1 SQL查询,平时正常,但是偶发性的变慢SQL,可能是什么情况 **(1)数据量和统计信息的不稳定性(最常见)** 这是**最可能**的原因。MySQL的查询优化器依赖于对表的统计信息(如行数、索引分布、基数等)来决定使用哪个索引和执行计划。 这些统计信息不是实时更新的。当表中数据发生大量增删改(特别是UPDATE和DELETE)后,统计信息会变得过时。优化器基于过时的信息,可能选择了一个非最优的索引(甚至全表扫描),导致查询突然变慢。 **为什么是间歇性的**:UPDATE和DELETE操作积累到一定程度,或者触发了自动更新统计信息的阈值后,统计信息被更新,新的、可能更差的执行计划被生成,慢SQL就出现了。或者,当表数据量突破某个临界点时,原来的好计划突然就不好了。 之所以又可以自愈: ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/kM1aVsqwsT9H7LWh.webp) **排查方法**: - 在慢SQL出现时,使用EXPLAIN语句分析当前的执行计划。 - 与正常时的EXPLAIN结果进行对比,看是否使用了不同的索引(key字段)或访问方式(type字段)。 - 检查表的统计信息收集设置:SHOW CREATE TABLE your\_table\_name;查看STATS\_PERSISTENT设置。 **(2)缓存失效** MySQL有多层缓存,缓存失效会导致查询需要重新处理。 **InnoDB缓冲池(Buffer Pool)**:这是最重要的缓存,缓存了表数据和索引。如果系统有重启、缓冲池大小设置不合理、或者突然有一个大型查询/报表任务扫走了大量缓存数据,那么你的查询所需的数据就可能被挤出缓存,导致必须从磁盘读取,速度急剧下降。 **查询缓存(Query Cache)**(注意:MySQL 8.0中已移除):如果启用了QC,一旦表有任何修改,所有基于该表的查询缓存都会失效。后续的查询需要重新执行。如果你的表每隔一段时间才被更新一次,那么更新后的第一次查询就会变慢。 **排查方法**: - 监控缓冲池命中率:SHOW STATUS LIKE 'Innodb\_buffer\_pool\_read%’;。理想情况下,Innodb\_buffer\_pool\_read\_requests(总请求数)远大于Innodb\_buffer\_pool\_reads (从磁盘读取的次数),命中率应接近99%。 - 检查缓冲池大小:SHOW VARIABLES LIKE 'innodb\_buffer\_pool\_size’;,确保它设置得合理(通常是系统内存的50%-70%)。 **(3)系统资源竞争** 数据库服务器本身可能正在经历周期性的资源瓶颈。 **周期性任务**:是否存在定时任务(如每日/每周报表生成、ETL作业、批量数据处理)?这些任务会消耗大量的CPU、内存和磁盘I/O资源,与你的常规查询争抢资源,导致其变慢。 **邻居吵闹**:如果数据库是部署在虚拟机或云服务器上,可能存在“邻居吵闹”问题。同一台物理机上的其他虚拟机在某段时间内资源使用激增,影响到了你的数据库性能。 **排查方法**: - 检查慢SQL发生时间点系统的监控指标:CPU使用率、内存使用率、磁盘I/O使用率(特别是await和util指标)、网络流量。看是否有明显的关联性。 - 检查MySQL的进程列表:在变慢时执行SHOW PROCESSLIST;,看看是否有其他大型查询正在运行。 **(4)锁竞争** 你的查询可能正在等待获取锁。 - **行锁/表锁**:你的应用程序可能在某些业务场景下(如定期结算、批量更新)持有锁的时间过长,导致其他查询被阻塞。虽然你的查询本身很快,但等待锁的时间被计入执行时间,从而成为“慢SQL”。 - **元数据锁(Metadata Lock)**:有一个长时间运行的事务(甚至是一个未提交的SELECT)或者正在执行ALTER TABLE等DDL操作,会阻塞其他需要获取元数据锁的查询。 **(5)应用程序层面的周期性模式** - **缓存穿透**:应用层的缓存(如Redis)可能定期失效。失效后,大量请求直接打到数据库,造成数据库瞬时压力过大,其中一些查询响应变慢。 - **特定时间的高流量**:例如,每周一的早上用户活跃度最高,并发请求数增加,数据库负载加重,导致个别查询性能下降。 **如何系统性地排查和解决?** **开启并监控慢查询日志** 1. 确保MySQL的慢查询日志(slow query log)是开启的,并设置一个合理的阈值(如long\_query\_time = 2秒)。 1. 使用工具(如mysqldumpslow, pt-query-digest)定期分析慢日志,找出TOP N的慢SQL及其出现的时间规律。 1. 在问题发生时现场排查 - **第一步**:使用SHOW PROCESSLIST; 查看当前所有连接状态,有没有阻塞、有没有状态不正常的查询。 - **第二步**:对变慢的SQL立即执行EXPLAIN,分析其执行计划。保存下来与正常时对比。 - **第三步**:查看服务器资源状态(top, htop,iostat -x 1)和MySQL状态变量(SHOW GLOBAL STATUS LIKE 'Threads\_running’; 查看并发数,SHOW GLOBAL STATUS LIKE 'Innodb\_row\_lock%’; 查看行锁情况)。 ## 2、分库分表怎么保证一致性,分布式事务具体应该怎么做? ### 分库分表下的一致性挑战 在单库单表时代,我们依赖数据库本身的**ACID事务**(特别是原子性和隔离性)来保证一致性。所有操作在一个数据库事务内,要么全部成功,要么全部失败。 但在分库分表后,数据被分散到不同的数据库(分库)甚至不同的服务器上。一个业务逻辑可能涉及更新多个数据库(例如:订单库和库存库),这就产生了**分布式事务**问题。你无法再用一个本地数据库事务来涵盖所有操作。 核心挑战在于**CAP理论**: **C (一致性)**: 所有节点在同一时间看到的数据是一致的。 **A (可用性)**: 每个请求都能得到响应(不保证是最新数据)。 **P (分区容错性)**: 系统在遇到网络分区时仍然能正常工作。 在分布式系统中,**P(网络分区)是必须接受的**,因此我们只能在C和A之间做权衡。分布式事务的各种方案,其实就是根据业务场景在一致性和可用性之间做出不同的选择。 ### 分布式事务的具体做法(解决方案) 分布式事务的解决方案主要分为两大类:**强一致性**和**最终一致性**。 #### 方案一:强一致性方案 这类方案追求数据的实时一致性,通常性能开销较大,实现复杂。适用于对数据一致性要求极高的金融、支付等场景。 **1.两阶段提交(2PC-Two-PhaseCommit)** 这是一种经典的分布式事务协议,包含一个协调者(Coordinator)和多个参与者(Participant)。 **第一阶段:准备阶段(PreparePhase)** - 协调者向所有参与者发送事务内容,询问是否可以提交。 - 每个参与者执行事务**但不提交**,写入undo和redo日志,然后锁定资源。 - 参与者回复协调者:“可以提交”(Yes)或“不能提交”(No)。 **第二阶段:提交/回滚阶段(Commit/RollbackPhase)** - **如果所有参与者都回复Yes**:协调者向所有参与者发送**Commit**命令。参与者正式提交事务,释放锁资源。 - **如果任何一个参与者回复No或超时**:协调者向所有参与者发送**Rollback**命令。参与者利用undo日志回滚事务,释放锁资源。 **优点**:最大限度地保证了数据的强一致性。 **缺点**: - **同步阻塞**:所有参与者在等待协调者指令时都处于阻塞状态,资源被锁定,性能差。 - **单点问题**:协调者宕机会导致整个事务阻塞或数据不一致。 - **数据不一致**:在第二阶段,如果只有部分参与者收到了Commit请求,会导致数据不一致。 **2. 三阶段提交 (3PC)** 针对2PC的优化,引入了超时机制和预提交阶段,超时机制一定程度解决了单点故障的问题,但是依然无法彻底解决数据不一致问题。 #### #### 方案二:最终一致性方案(主流推荐) 这是互联网公司更常用的模式,它不追求实时强一致,而是允许系统存在一个短暂的“不一致”状态,通过一些补偿措施,最终达到一致。它的**可用性高,性能好**。 **1.TCC (Try-Confirm-Cancel)** TCC是一种业务侵入性较强的补偿型方案,需要业务逻辑本身提供三个接口: **Try 阶段**:尝试执行。完成所有业务的检查,并**预留必要的业务资源**(例如:冻结库存、预扣优惠券、预生成订单状态为“处理中”)。 **Confirm阶段**:确认执行。真正执行业务操作,使用Try阶段预留的资源。**此操作默认会成功**。 **Cancel阶段**:取消执行。释放Try阶段预留的资源(例如:解冻库存、返还优惠券)。 **工作流程**: - 事务协调器依次调用所有服务的 Try 接口。 - 如果所有 Try 都成功,协调器再调用所有服务的 Confirm 接口,事务提交。 - 如果任何一个 Try 失败,协调器则调用所有已完成 Try 服务的 Cancel 接口,事务回滚。 **优点**: 性能比 2PC 好,数据最终一致。 **缺点**: 对代码侵入性强,需要为每个业务逻辑设计 try/confirm/cancel 三个方法。需要处理网络重试、空回滚、幂等、防悬挂等复杂问题。 **代表框架**:阿里的**Seata**(AT模式是TCC的变种,无侵入) **2.本地消息表(异步确保)** 这是利用消息队列实现最终一致性的经典模式,非常常用。 **工作流程**: - **步骤1 & 2**:系统A在执行本地事务时,将需要发送的消息和业务数据**放在同一个数据库事务里**,存入一张“消息表”(状态为“发送中”)。 - **步骤3**:系统A有一个定时任务,轮询消息表中状态为“发送中”的消息,并将其发送给消息队列(如 RocketMQ, Kafka)。 - **步骤4**:消息队列将消息投递给消费者系统B。 - **步骤5**:系统B消费消息,执行本地事务。执行成功后,通知消息队列ack确认。 - **步骤6**:如果系统B消费失败,消息队列会重试投递。系统A的定时任务也会对长时间未确认的消息进行重发。 - **优点**:方案简单,依赖MQ的可靠性,保证了消息最终一定会被消费。 - **缺点**:消息表会耦合在业务数据库中;存在重复消费的可能,要求消费者接口是**幂等**的。 1. **消息事务 (e.g., RocketMQ事务消息)** 这是对本地消息表的优化,由消息队列中间件直接提供事务支持,避免了自建消息表。 **工作流程**: - 生产者发送一个“半消息”到MQ,此时消费者不可见。 - MQ回复“半消息”发送成功。 - 生产者执行本地事务。 - 生产者根据本地事务执行结果(成功/失败),向MQ发送**Commit**或**Rollback**指令。 - 如果MQ收到Commit,则“半消息”变为正式消息,可供消费者消费。如果是Rollback,则丢弃消息。 - **补偿流程**:如果MQ长时间未收到生产者的确认,会回查生产者的本地事务状态,据此决定提交或回滚消息。 **优点**:解决了本地消息表与业务耦合的问题。 **缺点**:需要消息中间件支持此功能(RocketMQ支持)。 1. **Saga 模式** 一种长事务解决方案,将一个大事务拆分为多个本地小事务,每个小事务都有对应的补偿操作。 **工作流程**: - 按顺序执行所有子事务。 - 如果所有子事务都成功,事务完成。 - 如果其中一个子事务失败,则**反向依次调用前面所有已成功子事务的补偿操作**进行回滚。 **优点**:一阶段直接提交本地事务,无锁,性能好。 **缺点**:不保证隔离性,可能出现“脏写”(一个事务覆盖了另一个未完成事务的更改),需要业务层自己处理(例如:版本号控制)。 **模式**: - choreography(协同):每个服务产生事件并监听其他服务的事件,自己决定是否执行补偿。 - orchestration(编排):由一个协调器(orchestrator)集中负责指挥服务的调用和补偿。 ----- **总结与选型建议** | **方案** | **一致性** | **性能** | **复杂度** | **适用场景** | | :------------: | :----------: | :------: | :----------------: | :------------------------------------------: | | **2PC/XA** | **强一致** | 低 | 中 | 传统金融、数据库代理层 | | **TCC** | **最终一致** | 中 | **高**(业务侵入) | 对一致性有较高要求,如资金、积分 | | **本地消息表** | **最终一致** | 高 | 中 | 几乎所有异步场景,如扣库存后发积分 | | **MQ事务消息** | **最终一致** | 高 | 低 | 同本地消息表,是它的升级版 | | **Saga** | **最终一致** | **很高** | 高(补偿逻辑) | 长流程业务,如旅行订票(订机票、酒店、租车) | **给你的建议:** **优先选择最终一致性**:99%的业务场景都可以通过最终一致性方案来解决。它保证了高可用和高性能。 **代码侵入性**:如果不想大幅修改代码,可以选择**Seata AT 模式**或**RocketMQ 事务消息**。 **强一致性场景**:如果必须是强一致(如银行转账核心链路),再考虑**2PC**,但要深知其性能代价。 **幂等性**:在分布式环境下,网络超时、重试是家常便饭,**任何服务的接口都必须设计为幂等的**(即多次调用产生的结果与一次调用相同)。 **人工补偿**:任何系统都不能保证100%可靠,必须要有**对账和人工补偿**机制,用于处理极端的异常情况。 ## 3、分库分表,什么时候垂直分什么时候水平分? ### 垂直分库分表 **核心思想:**“按业务模块拆分”或“按列拆分”。将一张宽表或者一个完整的数据库,拆分成多个不同的、更小的表或数据库。 它主要解决的是:**单库/单表因字段过多导致的业务耦合和性能问题**。 **1. 垂直分表** 基于**列**进行拆分。将一个包含很多字段的大表,根据访问频次和业务关联性,拆分成多个扩展表。 **常见做法:** **冷热分离**:将“常用字段(热数据)”和“不常用字段(冷数据)”分开。 **大字段分离**:将TEXT, BLOB等占用空间大的字段单独拆到一张扩展表中。 **例子**:一张t\_user表有50个字段,其中id, name, email, phone是查询最频繁的,而personal\_info, description等文本字段不常查询。 拆分后: t\_user (核心表): id, name, email, phone, ... t\_user\_ext (扩展表): id, user\_id, personal\_info, description, ... **何时使用:** 表字段非常多,但每次查询只涉及其中一部分。 存在超长文本(TEXT/BLOB)字段,影响了核心表的查询和运维效率(如慢SQL、备份慢)。 **2.垂直分库** 基于**业务模块**进行拆分。将一个大而全的数据库,拆分成多个小而专的数据库。 **例子**:一个电商数据库shop\_db包含了用户、商品、订单、支付等所有数据。 拆分后: user\_db (用户库): 存放用户、会员等级等相关表。 product\_db (商品库): 存放商品、品类、库存等相关表。 order\_db (订单库): 存放订单、购物车等相关表。 pay\_db (支付库): 存放支付、账户等相关表。 **何时使用:** 业务系统模块清晰,耦合度低。 应用程序本身已经按照微服务进行了拆分,数据库自然也需要跟随服务进行隔离。 希望不同的业务团队能独立拥有和管理自己的数据库,减少相互影响。 **垂直分库/分表的优点:** **解耦业务**:降低系统的复杂度和耦合度。 **提升性能**:减少单次I/O的数据量(垂直分表)、减少单库的连接数和处理压力(垂直分库)。 **便于维护**:不同团队可以专注于自己的数据库,升级、运维更灵活。 **垂直分库/分表的缺点:** **无法解决单表数据量过大的问题**。order\_db里的订单表依然会无限增长。 **可能带来跨库Join的问题**。原本一个Join查询就能搞定的事,现在需要业务代码或中间件做多次查询和结果聚合,复杂度增加。 **分布式事务问题开始出现**。例如:下单操作需要同时操作 order\_db 和 pay\_db,需要引入分布式事务方案。 ### 水平分库分表 **核心思想:**“按数据行拆分”。将同一个表中的数据,按照某种规则(分片键)分散到多个**结构相同**的数据库或表中。 它主要解决的是:**单库单表数据量过大、读写性能瓶颈**的问题。 **水平分表**:将一张表的数据分到**同一个数据库的多个表**中。例如:order\_0,order\_1, ...order\_n。 **水平分库**:将一张表的数据分到**多个数据库的多个表**中。例如:db0.order\_0, db1.order\_1, ... dbn.order\_n。(水平分库是水平分表的进阶,通常一起进行) **分片规则(Sharding Key):** **哈希取模**:根据某个字段(如user\_id)的哈希值对分片数量取模,决定数据落在哪个分片。**优点**:数据分布均匀。**缺点**:扩容时需要迁移大量数据(一致性哈希可以缓解)。 **范围分片**:根据某个字段的范围进行分片,如按时间(月/年)、按id区间。**优点**:易于扩容。**缺点**:容易产生数据热点(最新的分片读写频繁)。 **地理分片**:根据用户所在地等地理位置信息分片。 **自定义规则**:根据业务特点自定义复杂规则。 **何时使用:** **单表数据量巨大**,预计未来会超过千万甚至亿级别,导致索引膨胀,查询性能下降。 **数据库的CPU、内存、I/O或连接数**即将达到瓶颈,且无法通过升级硬件解决。 需要解决**写操作的性能瓶颈**(因为读操作还可以通过读写分离缓解)。 **水平分库/分表的优点**: **根本性解决大数据量存储和性能问题**。 **大幅提升系统的扩展性和吞吐量**。 **水平分库/分表的缺点:** **复杂度急剧上升**: - **跨库Join**:几乎无法执行,需要在业务层模拟。 - **跨库聚合排序**:count(), order by, group by变得异常复杂。 - **全局主键ID**:不能再用数据库自增ID,需要分布式ID生成方案(雪花算法等)。 - **分片键的选择至关重要**,选错可能导致数据倾斜和热点。 - **运维复杂度高**:数据迁移、扩容、缩容都非常麻烦。 **总结与决策流程图** **核心原则:能不分则不分,优先选择简单的优化方案(如优化SQL、索引、读写分离、缓存),只有当这些手段都无法满足时,再考虑分库分表。** **如何选择?** | **维度** | **垂直分库/分表** | **水平分库/分表** | | :----------: | :----------------------------------------: | :------------------------: | | **拆分角度** | **业务/字段**维度 | **数据行**维度 | | **目标** | **解耦**,专库专用 | **扩容**,解决性能瓶颈 | | **影响** | 表结构不同 | 表结构相同 | | **适用场景** | 业务清晰,系统耦合度高;字段多且有冷热之分 | 单表数据量巨大,写操作频繁 | ## 4、自己有使用过哪些JAVA的设计模式 **(1)单例设计模式:全局配置管理、工具类** **使用场景**:项目中的全局配置类(如AppConfig)、日志工具类(LoggerUtil),这类组件需要全局唯一实例,避免重复加载资源或创建冗余对象。 **(2)工厂模式** 有简单工厂和抽象工厂,简单工厂就是我们就只有一个工厂类,然后通过if-else的判断来确定创建什么对象,但是这种方式是不符合开闭原则的,每次新增一个对象,都需要去修改工厂。 更常用的其实是抽象工厂,比如支付业务,就可以通过抽象工厂模式去实现不同支付方式的创建逻辑: 我们可以先声明一个统一的支付接口,里面通过一个抽象方法来约束所有支付方法的形参和返回值。然后多种支付方式类,比如微信支付,支付宝支付,银行卡支付等等,都去实现这个接口,每个支付方式类都去重写接口的支付方法实现自己的支付逻辑。然后我们再去声明一个支付工厂接口,每种支付方式的工厂类都去实现统一的支付工厂接口,这些工厂类都有自己的支付方式类的对象创建逻辑。 我们在使用的时候,就可以直接根据需要去new对应的工厂类,通过工厂类调用统一的方法来实现支付方式对象的获取,通过多态的方式,我们不需要去关心每种支付方式的具体实现类,直接通过统一的支付接口进行声明即可。 **(3)策略模式** 比如我的项目里实现答题评分,就使用了策略模式,通过声明一个统一的策略接口,约束评分策略的入参和返回值。然后让多个具体的评分策略类去实现我们的策略接口,在每个类中具体去实现自己的评分逻辑。然后我们通过一个自定义注解,来进行不同类型的应用的区分,因为不同应用类型就对应不同的评分逻辑。所以在调用的时候,就可以根据我们创建应用的类型,来动态获取对应的评分策略,避免了大量if-else的判断。 **(4)观察者模式** 一般是指某一个模块状态发送变化时,会去通知其他多个模块,同步的去改变状态。 比如,电商场景下,我们订单发送变化,可能库存、物流模块都需要相应的去改变状态。具体的实现,比如我们去声明一个被观察者的接口,里面定义几个方法,分别是添加观察者,移除观察者,和通知观察者;再去定义一个观察者的接口,主要是接收通知并更新状态的方法。然后我们可以在具体的被观察者中,通过一个List,去把所有的观察者加进List中,然后如果被观察者某个量发送改变了,我们就可以通过通知观察者的方法,在里面去调用观察者的接收通知并更新状态的方法,实现对观察者状态的同步改变。 具体我使用的场景,其实是利用了现成的RxJava库,来实现对AI流式生成的结果进行实时的事件流的观测,这里的被观察者就是AI流式接口,观察者就是我们接收返回结果的接口,被观察者产生事件流之后会主动调用我们的结果处理接口,对流式接口的结果进行单道题目的拼接。 ## 5、上传文件切片后,服务器端是怎么进行处理的,如果因为网络问题,服务端实际收到了分片,但客户端以为没收到,要怎么处理 客户端在上传分片之前,会先发送一个“初始化的请求”,携带文件元信息,比如文件的唯一标识(如文件名、文件大小、MD5等),文件的总分片数量,以及每个分片的大小;服务器收到之后会去创建一个临时目录,存储所有的分片。 客户端携带文件唯一标识,分片的索引、具体分片数据以及分片的MD5等上传时,服务器会首先去验证这个文件的ID是否存在,然后验证分片的索引是否合法(比如是否是在0到总分片数-1的范围内),通过MD5校验分片数据完整性等,如果验证通过就会把分片保存到临时目录中,并返回成功的响应,当所有的分片上传完,客户端会再去发送一个合并请求,服务器去检查分片是否完整,如果完整,就按照索引顺序去读取文件,完整拼接,并保存到目标目录中,删掉临时目录和分片文件,并返回合并成功的响应。 解决服务端收到但是客户端以为没收到重传的方案:核心是服务器端实现幂等性处理。 **具体实现:** **唯一标识分片**:每个分片必须有唯一标识(文件 ID + 分片索引 + 分片 MD5)。服务器收到分片时,先根据这个标识检查 “该分片是否已正确存储”。 - 若已存储且完整(MD5匹配),直接返回成功响应(无需重复写入磁盘)。 - 若已存储但不完整(如上次上传中断导致文件损坏),则覆盖旧分片,重新存储新数据。 **状态记录持久化**:已接收的分片索引和状态(如 MD5、大小)必须记录在可靠存储中(如数据库或Redis,而非内存),避免服务器重启后状态丢失。 **客户端:主动校验与断点续传** 客户端发现某个分片上传 “疑似失败”(如超时、未收到响应)时,不应直接重试,而应先查询服务器状态: **分片状态查询接口**:服务器提供一个接口,客户端可传入文件ID,获取“已接收的分片索引列表”。 - 例如:客户端上传分片3超时后,调用GET/upload/status?fileId=xxx,服务器返回[0,1,2,3](说明分片3已被接收),客户端则跳过该分片,继续上传下一个。若查询发现分片3未被接收,客户端再重试上传。 # 快某手篇 ## 1 七层网络模型,Http在哪一层,http的协议在哪一层 | **OSI七层模型** | **TCP/IP四层模型** | **五层混合模型** | **功能概述** | **典型协议** | **典型设备** | | ---------------- | ------------------ | ---------------- | ---------------------------------- | ------------------------------ | ------------------------ | | **7.应用层** | **4.应用层** | **5.应用层** | 为应用程序提供网络服务接口 | HTTP, HTTPS, DNS, SSH | 终端设备PC、手机 | | **6.表示层** | | | 数据格式转换、加密解密 | SSL/TLS(初期)、JPEG, ASCII | | | **5.会话层** | | | 建立、管理和终止会话 | RPC, SIP | | | **4.传输层** | **3.传输层** | **4.传输层** | 提供端到端的可靠或不可靠数据传输 | TCP, UDP | 防火墙 | | **3.网络层** | **2.网络层** | **3.网络层** | 寻址和路由选择,实现网络互连 | **IP**, ICMP, RIP, OSPF | **路由器** | | **2.数据链路层** | **1.网络接口层** | **2.数据链路层** | 在物理介质上提供可靠的数据帧传输 | **Ethernet**, PPP, Switch, MAC | **交换机**, 网桥 | | **1.物理层** | | **1.物理层** | 传输原始比特流,定义电气和物理特性 | RJ45, 802.3, 光缆, 双绞线 | **集线器**, 中继器, 网卡 | ## 2 RabbitMQ的channel和Queue的关系 Connection是TCP连接,Channel是连接里的“虚拟连接”或“轻量级线程”,而Queue是消息的存储队列。所有对Queue的操作(声明、绑定、消费、发布消息到该队列等)都必须通过一个Channel来进行。 **Queue(队列)** **是什么**:队列是消息的存储缓冲区。它是一个命名的、有序的、先进先出(FIFO)的数据结构,生产者(Producer)将消息发送到队列,消费者(Consumer)从队列中获取消息。 **作用**:消息的最终目的地,用于存储消息,直到被消费者处理。 **特点**: - 队列是建立在RabbitMQ服务器上的。 - 队列的元数据(名称、属性等)和其中的消息都存储在RabbitMQ服务端。 - 队列需要被**声明**(创建)后才能使用。如果它不存在,声明会创建它;如果已存在且属性匹配,则直接使用。 **Channel(信道)** **是什么**:Channel是在一个AMQP(RabbitMQ使用的协议)TCP连接(Connection)中开辟的**逻辑上的、轻量级的双向数据流通道**。一个Connection可以包含多个Channel。 **作用**:在保持一个TCP连接的基础上,提供多个独立的、隔离的会话通道。**几乎所有具体的AMQP命令(如声明队列、发送消息、消费消息)都是通过Channel对象来执行的。** **特点**: - Channel是建立在客户端(你的应用程序)的。 - 它是“轻量级”的,因为创建和销毁它的开销远比创建和销毁一个TCP连接要小得多。 - 每个Channel都有自己独立的通信流,多个Channel之间是隔离的。 | **特性** | **Connection** | **Channel** | **Queue** | | :----------: | :------------------------------------: | :--------------------------------------------------: | :----------------------------: | | **本质** | 物理的TCP连接 | 逻辑上的虚拟连接 | 消息存储实体 | | **层级** | 位于最底层,是通信的基础设施 | 建立在Connection之上 | 位于RabbitMQ服务器端 | | **资源消耗** | 重量级,开销大 | **轻量级,开销极小** | 服务器资源,与客户端连接数无关 | | **主要作用** | 提供网络联通保障 | **执行AMQP命令(操作Queue等)** | 存储和传递消息 | | **数量关系** | 一个应用通常维护一个或少量的Connection | **一个Connection可包含多个Channel** | 一个队列可以被多个Channel操作 | | **线程安全** | 一般不是线程安全的 | **通常要求Channel非线程共享**(一个线程一个Channel) | 线程安全,由RabbitMQ内部管理 | ## 3 Java虚拟线程 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/ATuXt9y75gSut9iE.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/84EGmo42HNhVEF4S.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/LKhErvJksITojlMv.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/VzAYQyhb7fz5ZW1G.webp) ## 4 SSE技术和HTTP的区别 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/lACZAjJiOFR3Cf6j.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/uJmKiGXEVNGE5mn7.webp) ## 5 SSE要在前端设置打字机效果应该怎么做 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/89n85bmjnkRRJZA4.webp) ## 6 HTTP怎么实现流量控制(滑动窗口算法) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/poHQ6Jhd7wVJc0UL.webp) # 字某节篇 ## 1 http连接,如果中间路由器断开一秒再恢复,原来的连接还存在吗? 这个问题要区分几个层次来看: **HTTP本身的特点** - HTTP/1.0:默认是“短连接”,每个请求-响应完成后,TCP连接就断开了。此时路由器中断一秒,影响不大,因为本来就每次重建TCP连接。 - HTTP/1.1:引入了Keep-Alive长连接,多个请求可以复用一个TCP连接。此时TCP连接能否继续存在,取决于底层TCP状态。 - HTTP/2/HTTP/3:支持多路复用,底层连接(TCP)断开就会导致整个HTTP连接失效。 **TCP连接在路由器断开时的表现** TCP连接的状态由两端的主机维护,路由器只是转发数据包,它不保存连接状态。如果路由器仅仅“断开一秒再恢复”,而且这期间没有数据包需要传输,那么两端主机的TCP连接表里依然是ESTABLISHED状态。 如果这期间恰好有数据传输: - 数据包会丢失,触发TCP重传。 - TCP会等待ACK,如果超时重传时间比较短,连接依然存在。 - 如果丢包过多导致超时重传时间超过上限,最终主机会判定连接断开。 **实践中的情况** 短暂(1秒左右)的路由器掉线:绝大多数情况TCP连接仍然存在,HTTP长连接可以继续使用,只是那一瞬间的请求可能会延迟。 如果有请求正在传输:可能造成请求失败,需要客户端重试,但底层TCP连接仍可能存活。 如果路由器重启(NAT表丢失):NAT环境下(家庭路由器、运营商网关),即使只掉线一秒,NAT映射表可能清空,导致外部连接无法路由,这种情况下连接会彻底断掉。 **结论:** 如果只是“物理层转发中断一秒”但路由器没有重置NAT表,原有的HTTP长连接通常还在。 如果路由器断电或NAT表清空,则连接会失效,需要重新建立。 ## 2 现在有一个用户登陆的日志文件,20G的大小,如果我们的内存只有1G,要选出日志里面用户登陆次数最多的TOP10,应该怎么做? **步骤1:分割大文件** **目标:**将20G的大文件分割成多个小文件,使得每个小文件的大小适合内存处理(例如,每个小文件小于500MB),并确保同一个用户ID的所有记录都分配到同一个文件中。 **方法:** 1. 选择一个哈希函数(如MD5或SHA-1)并确定桶的数量(例如100个桶)。桶的数量应足够多,以确保每个小文件的大小和其中的用户数量都在内存可处理范围内。计算哈希值:hash(user\_id) % N,其中N是桶的数量。 1. 逐行读取大文件,对于每条日志记录,提取用户ID,计算哈希值并取模,根据结果将记录写入对应的桶文件(如bucket\_0.txt, bucket\_1.txt, ..., bucket\_99.txt)。 注意:由于哈希分布可能不均匀,有些桶文件可能较大,但通过选择足够的桶数,即使个别文件较大,其内容统计时所需内存(基于不同用户数)也远小于2G。 **步骤2: 处理每个桶文件,统计用户登陆次数** **目标:**对每个桶文件,统计每个用户的登陆次数(由于同一个用户的所有记录都在一个文件中,这里统计的是全局次数)。 **方法:** 1. 逐个处理每个桶文件。读取一个桶文件到内存中,使用哈希表来统计每个用户ID的出现次数。键是用户ID,值是登陆次数。 1. 统计完成后,将结果写入一个输出文件(如count\_bucket\_0.txt, count\_bucket\_1.txt, ...),每行包含用户ID和对应的登陆次数。 **步骤3: 合并所有统计结果,找出TOP 10** **目标:**从所有输出文件中找出登陆次数最多的10个用户。 **方法:** - 初始化一个最小堆(大小为10)用于维护TOP 10用户。堆中元素是(登陆次数, 用户ID)对,堆顶是当前TOP 10中的最小次数。 - 逐个读取所有输出文件(count\_bucket\_\*.txt),对于每行(用户ID和次数),比较当前次数与堆顶的次数: - 如果堆中元素不足10个,直接将(次数, 用户ID)插入堆。 - 如果堆已满(10个元素),且当前次数大于堆顶次数,则弹出堆顶元素并插入当前元素。 - 处理完所有文件后,堆中的10个元素就是全局登陆次数最多的TOP10用户。 **优化和考虑** 哈希函数选择:选择分布均匀的哈希函数(如MurmurHash)以减少文件大小倾斜。 桶数量调整:如果预计用户ID分布极不均匀(如少数用户有大量记录),可以增加桶数(如1000个)以确保每个桶文件更小。 处理大桶文件:如果在处理每个桶的过程中遇到某个桶文件过大(如超过2G),可以进一步对该桶文件进行哈希分割,但通常桶数足够时不会出现这种情况。 ## 3、GC Roots包含哪些对象 ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/JRigtH8WIQyzkN6n.webp) ![image.png](https://pic.code-nav.cn/post_picture/1734931576698986498/VBQjAr6u2Uccjx9E.webp)

10min速通bilibili三面---秋招面试体验最好的一次

说来惭愧,由于最近项目组事情太多,所以我本来提前十五分钟就准备去面试间,结果突然来了两个需求和我对,硬控了我快二十分钟,导致我赶紧冲到面试间然后打开面试和面试官道歉。幸好小破站的+2非常的善良,笑着和我说没事,一开始的体验就拉满了。 然后便是常规的自我介绍+实习拷打,下面给几个比较有意思的问题: 1、你认为你实习期间做的最有技术深度的工作或者提升是什么? 2、你做的这些事情,你站在自己的角度说说为什么要做,有什么好处和提升?(这个问题还是挺好的,大家可以多去准备准备) 3、你认为实习最大的意义是什么,在哪些方面给了你最大的提升? 然后是八股,就只考了很经典的一道题: 加入有一个100g的都是一行行写字符串的文件,然后你有一个单机内存1g和磁盘空间无限大的机器,要求如何去做才可以找到最多的重复字符串? 这个问题我当时答了两种方法,然后后面去大佬小群问了一下得到了一个更加简单的回答思路。 本质就是因为内存空间不足导致我们要变换处理,然后两个方法--排序再排序、哈希再哈希 ①排序再排序 外部排序 + 顺序扫描。把 100G 文件分成多个小块(比如每块 500MB),逐块读入内存。在内存中对每一块进行排序(比如快速排序/归并排序),然后写回到磁盘。使用多路归并算法,把这些有序子文件合并成一个整体的有序大文件。归并的时候一定要边读边处理(这里额外问了我)。最后因为最终的大文件是有序的,相同的字符串会集中在一起。 ②哈希再哈希 哈希分桶 + 去重,这个也比较经典就不多说了。 我这个问题回答的比较好,所以面试官很高兴,就直接说我肯定有别的offer,那为什么选择bilibili。由于我当时准备的特别充分,也了解了很多所面部门的信息(并且我说了除了bili我其余的都拒面了),所以当时面试官也是很满意,就让我反问了。从我迟到开始面试一直到反问大概就十几分钟,也是让我体验上了速通的一次。 后面反问了大概半个小时,问了很多细节的问题。 最后不到一个小时hr就来和我约hr面了,美滋滋!!! ps:周末加班好困啊.......水一水文章摸摸鱼,大家周末好好休息,加油!

苍穹外卖学习笔记

# 苍穹外卖 ## 项目经历 ### 技术架构 SpringBoot+SpringMVC+Mybatis+MySQL+Redis+OSS+AOP+Swagger ### 项目简介 ​ 苍穹外卖是一个在线外卖平台,为用户提供外卖下单、支付、订单跟踪等功能。平台分为管理端和用户端,管理端供商家进行菜品、订单、员工和套餐等基本信息操作,且具有数据统计功能。用户端(微信小程序)供用户进行菜品浏览、添加购物车、下单、支付、催单等操作。 ### 我负责的部分 - 用户认证与权限管理:使用JWT令牌配置自定义拦截器完成用户认证,实现管理人员、用户、卖家的权限管理 - 缓存机制:使用Redis和Spring Cache缓存菜品、套餐等信息 - 文件上传与存储:使用阿里云OSS存储菜品图片等文件 - 定时任务处理:使用Spring Task框架实现定时任务,例如定时处理过期订单等功能 - 实时通讯:使用WebSocket实现来单提醒、用户催单的功能 - 数据导出与可视化:使用EasyExcel导出订单数据、菜品数据等数据 - 公共字段填充:使用AOP切面自定义注解简化数据库表的公共字段的新增和更改 - 异常处理:为集中处理异常系统,自定义统一的错误码,并封装了全局异常处理器,屏蔽了项目冗余的报错细节,便于接口调用方法理解和统一处理 - 自动接口文档:使用Knife4j+Swagger自动生成后端接口文档,并通过编写ApiOperation等注解补充接口注释,避免了人工编写维护文档的麻烦 ## 项目环境 **项目角色** ​ 产品经理、开发工程师、测试运维工程师 **技术选型**:展示项目使用到的技术框架和中间件 ​ 用户层:node.js/vue.js ​ 网关层:Nginx ​ 应用层:Spring Boot/Spring MVC/Spring Task... ​ 数据库:Mysql/Redis/Mybatis... ​ 工具使用:Maven/Git/Juint/Postman **整体结构** ​ 前端:管理端(Web)用户端(小程序) ​ 后端:后端java应用 **观察前端发送过来的项目代码** ​ 包名: ​ 1. sky-take-out:maven父工程,统一管理依赖版本,聚合其它子模块 ​ 2. sky-common:子模块,存放公共类,例如:工具类、常量类(constant)、异常类 ​ 3. sky-pojo:子模块,存放实体类,VO、DTO Entity:实体,和数据库中列表对应 DTO:数据传输对象,通常用于程序中各层之间传递数据 VO:视图对象,为前端展示数据提供的对象 POJO:普通java对象,只有getter和setter ​ 4.sky-server:子模块,后端服务,存放配置文件、Controller、Service、Mapper 配置文件:gitignore:配让git忽略的通配文件 ### Nginx 好处: ​ 1.提高访问速度 ​ 2.进行负载均衡(将大量的请求按照程序员指定的方式分配给不同的服务器) ​ 3.反向代理(避免暴露后端接口,保证后端服务安全) ​ 4.改写请求(控制浏览器【利用请求头设置缓存有效期】、请求重定向、URI重写) ​ 5.日志记录(访问和错误) ​ 6.访问控制和限制(专门限制IP地址) ​ 7.限流 ​ 8.开启虚拟主机 ![image-20240716153425469.png](https://pic.code-nav.cn/post_picture/1828819762856710146/AqEdNDMFJrJXpsIh.webp) **数据库密码加密**:将明钥改成密钥 ### 前后端开发流程 ![image-20240716164240458.png](https://pic.code-nav.cn/post_picture/1828819762856710146/6XrIMebG591ACZmp.webp) **后端接口测试Swagger技术** ![image-20241009110604650.png](https://pic.code-nav.cn/post_picture/1828819762856710146/DnB9XT9uDr9vuFaH.webp) ![image-20240716213840829.png](https://pic.code-nav.cn/post_picture/1828819762856710146/4EEAqUPxVeYghsYm.webp) ------ ## 商家管理 ### **新增员工** **难点**:拿到现在在操作用户的id 解决方式:在校验令牌的下方使用ThreadLocal获取现在的id ThreadLocal并不是一个线程Thread,而是Thread的局部变量 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外访问不到 **注意点**: 1. Controller层接受前端传过来的数据类型是EmployeeDTO,数据层Mapper接受的数据类型是Employee,中间在Service层要处理一下 ​ 2.利用DigestUtils.md5DigestAsHex进行加密处理默认密码 ​ 3.一般将经常使用的数据封装成静态变量 ### 分页查询 观察文档接口参数(分页查询有专门接受数据的参数) 思考Mapper语句:select * from employee where name like concat('%',#{name},'%) Service层: 1.设置分页参数PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize()); 2.Mapper层执行查询Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO); 3.封装pageBean PageResult pageResult = new PageResult(page.getTotal(), page.getResult()); ### 禁用启用员工账号 @Builder的作用: ``` Employee employee = Employee.builder() .status(status) .id(id) .build(); ``` ### 公共字段自动填充 在update和insert中要添加updateTime和updateUser等,会造成代码冗余的情况 使用Aop面向切面技术 实现思路: ​ 1.自定义注解,用于标识需要自动处理的方法 ​ 2.在定义切面类 ​ 1.切入点 ​ 2.通知 ​ 1.获取到当前拦截到的方法对应的数据库类型 ​ 2.获取当前拦截到的方法的参数 ​ 3.根据不同的操作类型,为对象的属性通过反射来赋值 ​ 3.在数据层添加自定义注解进行标识 ### 新增菜品 难点:文件上传 ​ 业务逻辑(涉及一次对多个表格进行操作) 解决:1.使用阿里云(在配置文件中的editor去除https:\\\ ),阿里云创建的bucket权限是公共读 ​ 2. Service层中涉及多条数据执行,记得加事物开启的标识 ​ 3.在DishMapper中获取主键id useGeneratedKeys="true" keyProperty="id" ​ 4.在DisFlavorMapper.xml中遍历的时候记得用flavor.name ## Redis Redis存储的是key-value结构的数据 ​ key是字符串数据 ​ value有五种常用的数据类型: | string(字符串) | hash(哈希) | list(列表) | set(集合) | sorted set / zset (有序集合) | | -------------- | ----------------------------------- | ------------------------------------------------------ | --------------------------------------------- | ------------------------------------------------------------ | | | 对象(带属性),类型java中的HashMap | 按照插入顺序,可以有重复元素,类似于java中的LinkedList | 无序集合,没有重复元素,类似java里面的HashSet | 集合中每一个元素关联一个分数,根据分数升序排序,没有重复元素 | ![image-20240731194111627.png](https://pic.code-nav.cn/post_picture/1828819762856710146/OKXTGolQZRD6kU1l.webp) ![image-20240731194654167.png](https://pic.code-nav.cn/post_picture/1828819762856710146/mndlXiHeJtAdmcaA.webp) ![屏幕截图 2024-08-03 183100.png](https://pic.code-nav.cn/post_picture/1828819762856710146/IWKE3oaLsdDf0oP4.webp) ![image-20240803183200788.png](https://pic.code-nav.cn/post_picture/1828819762856710146/arA4HHAGi6FfRQoD.webp) ![image-20240803183200788.png](https://pic.code-nav.cn/post_picture/1828819762856710146/0Enzd1XfMA4cjXBe.webp) ![image-20240801162202148.png](https://pic.code-nav.cn/post_picture/1828819762856710146/tXcX8zedKNsRD9WQ.webp) ### 在Springboot中操作Spring Data Redis 1. 导入Spring Data Redis的maven坐标 2. 配置Redis数据源 3. 编写配置类,创建RedisTemplate对象 4. 通过RedisTemplate对象操作Redis ### Spring Cache ![image-20240808202150454.png](https://pic.code-nav.cn/post_picture/1828819762856710146/keNipCwR1WdbqI26.webp) 具体的实现思路 1. 导入Spring Cache和Redis相关maven坐标 2. 在启动类上加上@EableCaching注解,开启缓存注解功能 3. 在用户端接口SetmealController的list方法上加上@Cacheable注解 4. 在管理端接口SetmealController的save、delete、update、startOrStop等方法上加上CacheEvict注解 ## 微信小程序支付流程 ![image-20240811213554343.png](https://pic.code-nav.cn/post_picture/1828819762856710146/RcfUoucri6FZM5JB.webp) ## 内网穿透工具cpolar 获取临时ip网址:cpolar.exe http 8102 ## Spring Task任务调度 1. 导入maven坐标spring-context(已存在) 2. 启动类添加注解@EnableScheduling开启任务调度 3. 自定义定时任务类 ## WebSocket WebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输 ## Apache ECharts Apache ECharts是基于一款javascript的数据可视化图标库,提供直观、生动,可交互,可个性化定制的数据可视化表图。 ## Apache POI Apache POI是一个处理Miscrosoft Office各种文件格式的开源项目。 应用场景: 1. 银行网络系统导出交易明细 2. 各种业务系统导出Excel报表 3. 批量导入业务数据 代码开发实现步骤: 1. 设计Excel模板文件 2. 查询最近30天的运营数据 3. 将查询到的运营数据写入模板文件 4. 通过输出流将Excel文件下载到客户端浏览器 ## 前端vue ### 基于脚手架创建vue项目 #### 基于vue开发前端项目的环境要求 node.js npm Vue CLI #### vue项目创建 创建vue项目:进入控制面板,输入指令(注意项目名称) 方式一:vue create 项目名称 方式二:vue ui(包管理器:npm) #### 项目结构 node_modules:当前项目依赖的js包 assets:静态资源(图片) components:公共组件 App.vue:项目的主组件,页面的入口文件 main.js:整个项目的入口文件 package.json:配置信息,依赖包管理 vue.config.js:vue-cli配置文件 #### 项目启动 打开vscode的控制面板:输入指令npm run serve ### 基础 vue组件:template是结构(html),style是样式(css),script是逻辑(js) 文本插值:{{}} 属性绑定v-bind: 事件绑定v-on: 双向绑定v-model 条件渲染v-if、v-else-if、v-else Axios是一个基于promise的网络请求库,用于浏览器和node.js中【安装命令:npm install axios 导入命令:import axios from ‘axios’ #### 路由Vue-Router 跳转页面主要是跳转路由(路径) 基于Vue CLI 创建带有路由功能的前端项目 【npm install vue-router】 ##### 路由配置 - VueRouter:路由器,根据路由请求在路由视图中动态渲染对应的视图组件 - <router-link>:路由链接组件,浏览器会解析<a> - <router-view>:路由视图组件,用来展示与路由路径匹配的视图组件 跳转方式还有js代码: this.$router.push('/about') ##### 嵌套路由 安装 npm i element-ui -S #### 状态管理vuex state:状态对象,集中**定义**各个组件共享的数据 mutations:修改共享数据的方法,要求是同步函数 【 this.$store.commit('setName', 'Vuex'),只能通过store对象的commit方法调用】 actions:类似mutation,可以包含异步操作,通过调用mutation来改变共享数据【只能通过store对象的dispatch方法调用】 #### TypeScript 包含js 安装:npm install -g typescript 查看版本指令:tsc -v 编译文件:tsc 文件名 运行文件:node 文件名 类型标注的位置: - 标注变量 - 标注参数 - 标注返回值

【LeetCode】每日一题 2024_10_14 鸡蛋掉落(记忆化搜索)

# 前言 每天和你一起刷 LeetCode 每日一题~ # LeetCode 启动! <img src="https://pic.code-nav.cn/post_picture/1647972491353706498/gmKaDLlLv2gnpJoT.webp" alt="" width="100%" /> # 题目:[鸡蛋掉落](https://leetcode.cn/problems/super-egg-drop/description/?envType=daily-question&envId=2024-10-14) <img src="https://pic.code-nav.cn/post_picture/1647972491353706498/OMnWKhLC6wLwVbyj.webp" alt="" width="100%" /> **先聊会儿天** 今日破防: 宿舍的算法哥今天心血来潮,顺手刷刷今天力扣每日一题,几下就秒了,我看了半天,学了老久的题解,看的还是有些云里雾里(拼尽全力无法战胜) 其实也还好,我作为宿舍算法最菜的人,被卷王舍友虐也不是一天两天了 **关于写作方向思考** 如果之后再遇到像今天这种我研究许久也是云里雾里的困难题, 没有信心写好题解,但是每天的力扣每日一题打卡我也想坚持更新,那该写些什么内容呢 今天的打卡内容就探索一下能写些什么东西~ 突然想到闲聊也有接近半个多月没怎么更新了 . . . 我需要一个恢复更新的契机,现在肚子里憋了不少东西想说,长这么大,鸡汤喝了这么多,耳边听到的什么:“种一棵树最好的时间是十年前,其次是现在”,什么增强自己的行动力,知易行难啊。知行合一永远是最难做到的事情 . . . 也许哪一天我突然就又重新开始持续更新了,也许就是今天 . . . 废话结束,也许我们该开始每日一题了 . . . ## 代码与解题思路 今天的题目是昨天的进阶版,昨天给了 2 个鸡蛋,让我们求在一栋有 n 层楼的建筑中扔鸡蛋的最大操作次数 但是今天的题目给了 k 个鸡蛋,这也意味着没法通过数学或者说找规律的形式来取巧了,只能老老实实的用动态规划的思想去做(我的弱项 . . . 所以今天就没有我的核心思路讲解了呜呜) ```go func superEggDrop(k int, n int) int { memo := [][]int{{}} var dfs func(int, int) int dfs = func(i, j int) (res int) { if i == 0 || j == 0 { return } // 记忆化 p := &memo[i][j] if *p != 0 { return *p } defer func() { *p = res }() return dfs(i-1, j) + dfs(i-1, j-1) + 1 } for i := 1; ; i++ { memo = append(memo, make([]int, k+1)) if dfs(i, k) >= n { return i } } } ``` **题目思路与趣闻** 这道题居然是 Google 的经典面试题,推荐这道题的讲解视频:[【复工复产找工作?先来看看这道面试题:双蛋问题】](https://www.bilibili.com/video/BV1KE41137PK/?share_source=copy_web&vd_source=5838aabca6ee756488292563a3936f1d) 在力扣讨论区看到的推荐 # 每天进步一点点,我们明天不见不散~ > 可以和我刷一辈子的每日一题吗? 一题一题,积累起来就是一辈子。<img src="https://pic.code-nav.cn/post_picture/1647972491353706498/fCHWcB3GPP5h9Rns.webp" alt="" width="100%" />

OJ项目部署上线笔记(前端+后端部署)

# **后端部署(yuoj-backend-microservice-master):** ### **1、使用的服务器规格说明:我使用的是2核4g的服务器,2g内存的服务器可能运行不起来,服务器系统使用的是centos 7.9; ssh连接工具是 MobaXterm** ### 2、后端部署可以先观看鱼皮的这个视频(看视频的时候先别操作,请看完下方部署注意事项后再操作): https://www.bilibili.com/video/BV1Cp4y1F7eA/?spm_id_from=333.337.search-card.all.click ### **3、部署注意事项:** ①在源码中的yuoj-backend-gateway模块中的Dockerfile文件中的EXPOSE 8104应该修改为EXPOSE 8101; ### **注:服务器的相关端口一定要开放!!!** ②为防止服务器的内存小导致项目无法成功启动,请将项目中所有的Dockerfile文件中的启动命令里添加上JVM的内存参数设置,如图所示: <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/ucs0ehywTWrUUl2R.png" alt="image.png" width="100%" /> ③由于oj项目的远程代码沙箱是作为一个项目独立运行的,也需要打包上线,所以应该将yuoj-backend-judge-service模块中的RemoteCodeSandbox类里的executeCode方法中的url路径进行修改,将红框中的**localhost修改为服务器的ip地址**(这是偷懒的做法);(应该修改url,让其可以动态的读取application-prod.yml里的值,具体做法可以参考鱼皮视频里提及的InitRabbitMqBean类里的写法); ### **注意:一定要开放服务器的8090端口!!!** <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/D3gp7H6bNmzVQuUn.webp" alt="QQ图片20240830202305.png" width="100%" /> ④使用docker compose命令一次性启动项目中所有模块的时候,可能会出现某个项目无法成功启动,此时我们可以使用docker compose -f docker- compose.service.yml up “模块名” -d 命令进行重新启动(就像鱼皮视频中演示的一样) **建议:使用docker compose -f docker-compose.service.yml up “模块名” -d 命令 一个模块一个模块地启动,防止某个模块因为内存分配不足导致启动失败。** **注:可以使用docker ps -a 命令查看容器是否都处于运行状态** **注:项目部署上线后,如果你在本地修改了某个模块的代码,要在本地重新打包,把jar包重新上传到服务器对应的位置上(即该模块在服务器上对应的目录的target目录里),然后要把该模块的容器停止了,然后删除该容器,然后再把该模块的镜像删除了,最后再使用docker compose命令重新构建容器** **⑤服务器上下载的maven版本要在3.1以上,因为远程代码沙箱项目在服务器上打包的话,需要服务器上的maven的版本在3.1以上。(请自行搜索linux系统上如何下载对应版本的maven)** # **后端部署(yuoj-code-sandbox-master):** ### **1、远程代码沙箱项目也是使用Docker部署的:** ①在服务器的code文件下创建和项目名相同的目录 即/code/yuoj-code-sandbox-master/ (如图) 将yuoj-code-sandbox-master项目中的文件从本地都上传到服务器的/code/yuoj-code-sandbox-master/ 目录下,如图。 而后可以使用mvn命令进行打包(如果服务器上使用mvn命令打包失败,可以在本地打好jar包后,将整个项目重新上传到服务器) 然后在yuoj-code-sandbox-master目录下创建Dockerfile文件(如图) <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/GXZjA2HTFcQmdMhq.png" alt="image.png" width="266px" /> <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/hbqCoiLuJppvYAjh.png" alt="image.png" width="260px" /> **Dockerfile文件里的内容:** ``` # 基础镜像 FROM openjdk:8-jdk-alpine # 将 jar 包添加到工作目录,比如 target/yuoj-backend-user-service-0.0.1-SNAPSHOT.jar ADD target/yuoj-code-sandbox-0.0.1-SNAPSHOT.jar . # 暴露端口 EXPOSE 8090 # 启动命令 ENTRYPOINT ["java","-Xms128m", "-Xmx256m","-jar","/yuoj-code-sandbox-0.0.1-SNAPSHOT.jar"] ``` ②在服务器的code文件夹下创建docker-compose.service.yml文件,如图: <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/ouhePW86awYeFlGZ.png" alt="image.png" width="260px" /> docker-compose.service.yml文件里的内容如下: ``` version: '3' services: yuoj-code-sandbox-master: container_name: yuoj-code-sandbox-master build: context: ./yuoj-code-sandbox-master dockerfile: Dockerfile ports: - "8090:8090" networks: - mynetwork networks: mynetwork: ``` ③在code目录下使用docker compose命令,如下: ``` docker compose -f docker-compose.service.yml up yuoj-code-sandbox-master -d ``` ④使用docker ps -a 命令查看yuoj-code-sandbox-master容器是否启动成功,若不成功请重新执行步骤③ 使用docker ps -a 命令查看所有容器都正常运行则表明部署成功(也可以访问 ip:8101/doc.html 使用聚合文档验证)**注意该ip为你自己服务器的公网ip** # **前端部署(yuoj-frontend-master):** ### **1、在本地进入yuoj-frontend-master\generated\core里,点击OpenAPI.ts,修改OpenAPIConfig里的配置,如图:** **注:如果是使用域名访问,请将BASE里的地址修改为 http://域名 (例如:http://www.oj.com) ; 如果是使用服务器的ip访问,就将BASE里的localhost改为服务器的公网ip地址;此外还需将WITH_CREDENTIALS里的值改为true,如图:** <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/b5kIGuc0Mj4QBG2O.webp" alt="image.png" width="100%" /> ### **2、在终端运行 npm run build命令将项目进行打包,然后生成一个dist文件夹** ### **3、在服务器的code目录下创建一个与前端项目同名的文件夹,即/code/yuoj-frontend-master/ 如图:** <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/EO7u0texTHthYfZC.png" alt="image.png" width="263px" /> ### **4、将刚刚在本地生成的dist文件夹,上传到服务器的yuoj-frontend-master目录下,如图:** <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/SLAJm2xZsqrYgyjv.png" alt="image.png" width="262px" /> ## **此时前后端项目都已经部署到服务器上,但是还需要nginx进行代理转发(请自行搜索Linux系统如何下载nginx)** **注意:一定要开放服务器的80端口** ### **1、找到nginx.conf,修改nginx.conf里server块的配置** **注意:如果nginx是使用yum命令下载的,server块在/etc/nginx/conf.d/下的default.conf文件里** **server块修改如下(填写完server_name那行的ip后,ip后面是有个 ; 的):** <img src="https://pic.code-nav.cn/post_picture/1808869779685212161/TSfhToSF0aVKprMG.webp" alt="image.png" width="100%" /> #### 注意:root后面的目录就是dist文件夹在服务器上的位置, **此外在location / 模块里还需添加try_files $uri $uri/ /index.html;** **2、在正确的目录下使用 ./nginx -s reload 命令(使用该命令前请确保nginx已经启动)重新加载nginx的配置文件(例如nginx是使用yum下载的,应在/usr/sbin目录下使用该命令)** **注意:如果使用./nginx -s reload 命令后报错,则按提示信息修改配置文件里的内容,然后再重新执行./nginx -s reload 命令** # **到此,oj项目前后端都部署完毕,在浏览器使用IP或者域名即可访问。**

绪论

- 一,相关概念 + 1,<font color=green>**数据(DATA)**</font>:各种符号的集合 分为:①数值型数据 实数等 ②非数值型数据 文字图象等 + 2,<font color=green>**数据元素(DATA element)**</font>数据的基本单位,在计算机中通常作为一个<u>整体</u>进行考虑和处理,别名为元素,顶点,记录,结点。 + 3,<font color=green>**数据项(DATA item)**</font>是构成数据元素的不可分割的最小单位 + 小结:数据>数据元素>数据项 + 4,<font color=green>**数据对象(DATA object)**</font>是性质相同的数据元素的集合,是数据的一个子集 ![转存失败,请重新上传图片]() - 二,数据结构 + 1,数据结构是相互之间存在一种或多种特定关系的数据元素的集合,包括:①逻辑结构 ②存储结构 ③数据运算 ![转存失败,请重新上传图片]() + 2,逻辑结构的种类 + 划分方法一: ![转存失败,请重新上传图片]() + 划分方法二: ![转存失败,请重新上传图片]() + 3,存储结构的分类 + 1,顺序存储结构: 将数据元素存放在地址连续的存储单元中,其数据间的逻辑关系与物理关系一致。 + 2,链接存储结构: 将数据元素存放在任意的存储单元中,这组存储单元之间通过链接的方式建立起来。(C语言中用指针实现链式存储结构) + 3,索引存储结构: 在存储数据元素的同时,还建立附加的索引表。 + 4,散列存储结构: 根据元素的关键字直接计算出该元素的存储地址,又称哈希存储结构。 + 4,数据类型(DATA type): + 是指一个值的集合和定义在这个值集上的一组操作的总称。 作用: 1,数据类型决定了数据对象取值范围 2,数据类型决定了数据对象所能进行的操作 + 抽象数据类型(Abstract data type,**ADT**) ![转存失败,请重新上传图片]() 定义格式如下: ```c ADT 抽象数据类型名{ 数据对象: 数据关系: 基本操作: }ADT 抽象数据类型名 ``` 基本操作的定义格式为: ```c 操作名(参数表) 初始条件: 操作结果: ``` + 基本操作的定义格式说明 ![转存失败,请重新上传图片]() + ***概念小结*** ![转存失败,请重新上传图片]() 三,算法效率(时间效率和空间效率) + 1,两种度量方法: + ①事后统计方法: 通过设计好的测试程序和数据,利用计算机计时器对不同算法的运行时间进行比较。 + ②事前分析估算法: 在计算机程序编写前,依据算法所处理的数据规模大小和算法实现所消耗的时间资源、空间资源等,对算法进行估算。 + 2,算法运行时间=一个操作所需时间(假设均为单位时间)×算法中该操作重复执行的次数(语句频度) 算法执行时间约等于语句频度总和 + 3,两个n*n矩阵相乘算法效率分析 ```c for(i=1;i<=n;i++) //n+1 for(j=1;j<=n;j++) //n*(n+1) for(k=1;k<=n;k++) //n*n*(n+1) c[i][j]+=a[i][k]*b[k][j];//n*n*n ``` 算法耗费时间等于每条语句频度之和 T(n)=(n+1)+n*(n+1)+n*n*(n+1)+n*n*n=n^3+3n^2+2n+1 + 4,算法的渐进时间复杂度(O是数量级符号) 简称时间复杂度(T(n)的数量级) + 1,忽略低次幂项和最高次幂系数 + 2,![转存失败,请重新上传图片]() + 3,对于复杂的算法,可以分为几个部分,利用加法和乘法法则计算时间复杂度 + 加法法则 :T(n)=T1(n)+T2(n) 取两个函数中数量级最大的 + 乘法法则 :T(n)=T1(n)*T2(n) 数量级为两个函数的乘积 + 4,时间复杂度T(n)按数量级递增顺序为 O(1)<O(log2n)<O(n)<O(nlog2n)<O(n^2)<O(n^3)<O(2^n)<O(n!) + 5,渐进空间复杂度(S(n)的数量级) + 1,空间复杂度S(n)分析 + 1,算法原地工作:算法所需 Workspace 的空间为常量,O(1) + 2,递归算法的空间复杂度:递归深度n*每次递归调用所需空间 + 3,循环工作算法的空间复杂度:循环中使用的变量所占空间 + 2,空间复杂度S(n)按数量级递增顺序为 O(1)<O(n)<O(n^2)<O(n^3)<O(2^n)<O(n!) 例子: ![转存失败,请重新上传图片]()

chatGPT大火,来看看chatGPT的创始人对于程序员成功的分享

## 《程序员的成功之路》 > - 如果你觉得文字读起来太累:https://www.bilibili.com/video/BV1bs4y1a7B7/?spm_id_from=333.337.search-card.all.click&vd_source=ce628a5bd43df277d141676215ef5ff3 > - 如果你想阅读英文原文:http://blog.samaltman.com/how-to-be-successful ### 1.选择复利增长 复利具有神奇的魔力,现在处处都在强调复利,这其中的奥秘就是指数曲线,因为指数曲线是创造财富的关键。一家中型企业的价值如果按照每年50%的速度增长,那么它的规模可以在短时间内极速扩张。世界上少有企业具有真正的网络效应和高度的可扩展性,但是随着技术进步,这种情况会逐渐改变,这值得我们不断为之努力。 对个体的人生道路来说,我们也应该走成一条指数曲线。也就是说,我们要遵循不断向右增长的人生轨迹。在进行职业规划时,要选择具有复合效应的职业,而大多数职业的发展轨迹都是一条线性直线。 在线性职业领域,工作二十年的效率并不会比工作两年的效率高,像这样的职业不利于个人发展。 我们需要的是一份能保持不断学习的职业。随着职业发展,我们需要产出越来越多的成果。达成这一目标的途径多种多样,比如说资本、技术、品牌、网络效应和做管理。 专注于将你所定义的成功指标增加十倍是有用的,这些指标可以是赚钱、社会地位、世界级影响力或者其他东西。我乐意接受挑战,愿意在各种项目上花时间以解锁下一个项目。但是我希望在每一个项目上都能取得最大成就,创造职业生涯新高度。但是大多数人都被困于线性发展的泥潭,往往捡了芝麻丢了西瓜,我们要学会抓大放小,寻求跳跃式提升。 在我看来,无论是企业还是个人,最大的竞争优势就是要把目标放长远。我们要打开眼界,看出世界上不同体系之间交融互动的方式。复合增长最重要的就是眼光要尽可能放长远,这样的人才能抢占市场先机,获得最大回报。 要相信指数曲线,耐心坚持下去,最后一定会有惊喜。 ### 2.要有绝对自信 自信拥有不可思议的力量,就我认识的人来说,最成功的往往都是那些自信到离谱的人。 我们要尽早树立自信。如果你的判断常常都很准确,能带来很好的结果,那么你一定要加倍自信。 对自己不自信的人很难对未来抱有逆向思维,但是往往逆向思维才能创造出最大的价值。还记得很多年前马斯克带我参观SpaceX工厂,他详细地谈到了制造火箭的一些细节,但是让我印象最深的还是马斯克谈到向火星发射火箭时的表情,离开工厂时我就在想 “啊,这就是自信的样子”。 对大多数创业者来说,激发自己以及团队的士气可以说是最大的挑战之一,如果没有自信,这就成了几乎不可能完成的任务。但往往一个人越有雄心壮志,其受到的打击就会越多。大多数非常成功的人在面对人们的质疑时至少有一次决定是正确的,否则他们面临的挑战会更多。 我们在自信的同时也要保持清醒的自我认知,才能避免盲目自大。我曾经非常讨厌受到批评和质疑,并且总是设法规避这些批评。但现在我开始尝试听取这些意见,我会先设想这些批评是正确的,然后在这个基础上调整我的计划。做决定的过程充满了艰辛和痛苦,但也只有经历了这个过程才能将自信和自欺欺人区分开来。 保持自信与自我认知之间的平衡可以让人免于傲气、避免与他人脱节。 ### 3.学会独立思考 创业很难,因为培养原创性思维很难。这种思维在学校里面是学不到的,实际上学校培养的是一种相反的思维方式,所以只能靠我们自己来培养原创性思维。 我们可以从第一性原理(first principles) 出发,从中想出新的点子,然后与人交流沟通,对这些想法进行改良,之后我们再用轻松快捷的方式进行实际测试。 对创业者来说,失败是家常便饭,但我们一定要抱有必胜的信念,要不断尝试、不断试错,只有这样才能得到幸运之神的眷顾。在这个过程中,最宝贵的经验之一就是,我们要学会在绝境中找到一线生机。我们经历的越多就会越对此深信不疑。要知道勇气来自于多次失败后的坚持不懈。 ### 4.做一个好销售 光有自信是不够的,我们还要具备说服他人的能力。就某种程度来说,所有职业的本质都是销售。你必须向客户、潜在职员、媒体、投资者等宣传兜售你的计划。想要说服他们,首先你的计划要有广阔的发展前景。对于个人而言,你要具备良好的沟通能力、一定的个人魅力以及强大的执行能力。 具备良好的沟通能力十分重要,尤其是书面沟通。在这方面我的建议是:首先要保持思路清晰,然后就是尽量使用简洁明了的语言。 而要做好“销售”最好的方式就是真诚,要对自己推销的产品抱有自信。销售其实无异于其他技能,我们可以通过可以练习提高销售技能。但是出于某些原因(比如人们可能不喜欢销售),很多人认为销售技能不可习得。 做销售的另一个秘诀是:重要的事情要亲力亲为。在刚开始创业时,我非常乐意出差办事,这在很多人看来是不必要的,但是事事亲力亲为却给我带来了三次职业生涯的转折点,如果当时没有选择这样做,我可能会走上另一条道路。 ### 5.要有冒险精神 大多数人往往都高估了风险低估了回报。冒险对我们来说也很重要,因为人不可能永远不犯错,我们需要不断试错,学习并快速适应。 在职业生涯早期,人们往往更愿意冒险,因为那时你没有什么可失去的东西,但却可能得到很多。一旦一个人履行了自己的基本责任义务,就可以大胆冒险了。我篇幅可以先下小的赌注,如果赌输了会输掉1倍,但如果成功了,则可以赚到100倍。之后我们再沿着这个方向下更大的赌注。 但是要注意不能一直待在舒适圈。在YC,我们从谷歌和脸书长期工作的创始人身上看到了这样一个问题:当人们习惯了舒适的生活、稳定的工作和无论做什么都会取得成功的名气时,就很难将这些置之于身后了(人们总是将他们的生活方式与下一年的工资相匹配)。即使他们真的离开了,也非常有可能再回来。与长期利益相比,短期诱惑和便利往往更具吸引力,也更符合人的天性。 但当你摆脱了这些枯燥无味的工作,你可以跟随直觉将时间花在那些有趣的事情上。而想做到这一点,尽可能长时间地过着朴素灵动的生活是一个很好的方法。当然,任何选择背后都有相应的代价。 ### 6.保持专注 专注可以让我们在工作中取得事半功倍的效果。磨刀不误砍柴工,在我认识的人中,那些花时间想明白了未来方向的人最后都得到了不错的结果。由此可见,做正确的事比长时间做事更重要。很多人都将自己的时间花在了无关紧要的事情上面。 一旦你想明白了该做什么,就不要犹豫,快速行动起来去完成优先事项。毕竟成功人士就没有执行力弱的。 ### 7.努力工作 通过运用自己的聪明才智或者勤奋努力,一个人可以达成工作领域里百分之九十的成就,能做到这一点已经很不错了,但是想要尽量做到完美,达成百分之九十九的成就,那就必须要兼顾聪明与勤奋,因为这一阶段你的竞争者往往是两者兼备的人。 有付出才有收获,付出越多收获也就越大。努力工作可能会造成工作与生活失衡,我完全可以理解有的人选择去更好地平衡工作与生活,在工作中不那么拼命,但是拼命工作确实有很多好处,在多数情况下,努力工作会产生叠加效应,越是成功的人就越能成功。 这通常很有趣。生活中最大的乐趣之一就是找到你的目标,并且有所建树,然后你会发现你在这件事上的影响力比你自己本身更重要。一位YC创始人表示:他在离开一家大公司后,尽力发挥了自己的最大影响力,此时他发现自己变得更快乐、更充实。 为发挥出自己最大影响力而努力工作是值得庆祝的。我完全不能理解为什么在美国某些地区努力工作反而成了一件坏事,但我知道世界其他地区肯定不是这样的,那些地区的企业家表现出来的精力和干劲正在快速成为新的社会标杆。 你必须想出一条平衡之策,在努力工作的同时,又不至于透支身体。对此,虽然人们的应对之策不尽相同,但有条几乎不会出错的黄金准则,那就是与相处愉快的人一起从事喜欢的工作。 我认为,那些假装(在你生命中的某个时期)不用把精力放在工作上,就能平步青云的人,其实是在误人子弟。事实上,判断一个人能否笑到最后的关键因素之一就是工作耐力。 另外,我认为刚入职场时就应该要努力工作。努力工作就像利滚利一样,越早开始,获利时间就越长。一般来说,人们身上背负的责任越少,就越容易施展身手。 ### 8.大胆一点 在我看来,与轻松创业相比,人们多半会选择更具挑战性的事业。因为后者往往更激动人心,能带来更大的成就感和满足感。如果你在某个重大问题上取得了进展,就会有源源不断的人前来帮忙。志当存高远,不要害怕去做你真正想做的事情。 如果别人都在创办 meme 公司,而唯独你想创办一家基因编辑公司那就去做吧,不要犹豫。 追随你的好奇心。那些让你感到兴奋的事情,通常也适用于别人。 ### 9.足够坚定 很多人都不知道,只要你足够坚持,世界就会以你的意志为转移。但大多数人甚至都不会去尝试,只单纯认为世界有其自身的运作规律。 人的潜力是巨大的,只要敢想就能做成很多事。但大多数人都会怀疑自我、过早放弃,同时又不够努力,种种原因导致大多数人无法充分发挥自身潜能。 询问自身诉求,你通常不能得偿所愿,而且被拒绝的滋味往往不好受。但若一旦成功,效果就会好得出奇。 那些声称“我将永不言弃,直到梦想成真,不论前方有多少艰难险阻,我也会迎难而上”,并将其付诸行动的人,最后几乎总能获得成功。因为他们坚持了足够久,所以最终迎来了幸运之神的眷顾。 在这方面,爱彼迎 (Airbnb)是我认为的行动标杆。业内流传着许多有关爱彼迎的逸闻趣事,虽然我并不推崇他们的做法(比如透支信用卡、每顿都吃一元店买的麦片、乐此不疲地与强劲的对手进行较量等等) 但是正因为他们足够坚持,最后终于时来运转。 只有保持乐观才能足够坚定,而乐观这种性格特征是可以通过练习逐步提升的,而悲观者是很难成功的。 ### 10.保持强劲的市场竞争力 大多数人都明白,企业竞争力越强,价值就越高。这点至关重要,而且也是显而易见的。这同样也适用于个人。如果你所从事的工作具有可替代性,那么你最终就会被薪资要求更低的人所取代。 增强竞争力的最佳方式就是建立话语权。例如,你可以利用好个人关系,打造强大的个人品牌,或是在不同领域的交叉点建立起自己的个人优势。当然增强竞争力的方式还有很多但不论采取什么方式,关键是你必须要做到这一点。 大多数人会模仿身边人的做法,但这种方式并不可取,如果一味模仿他人,那你还有什么竞争优势可言呢? ### 11.建立人际网络 出色的工作需要团队合作。打造既可密切合作又可轻松相处的优质人际网络是事业成功的必要因素。拥有优秀人才的人际网络的规模会决定你能成功的上限。 建立人际网络的有效方法之一是尽可能多帮助他人。长期以来,这种行为方式给我带来了最佳职场机遇以及四项最佳投资中的三项。我总是惊讶与发生在自己身上的意外之喜,仅仅因为我十年前曾帮助过一位企业创始人。 建立人际网络的另一个途径是拥有好的名声,不亏待每一个一起共事的人。要大方慷慨地与他人分享资源,这会给你带来10倍、100倍的回报。此外,要知人善用,让每个人都能充分施展自己的才华。 我们既要尽力挖掘他人的潜力但是又不能逼得太紧,这容易让人感到精疲力尽。每个人都有各自擅长的领域,因此,我们要多看看自己的优点,不要总盯着缺点,要用优点来定义自身。面对缺点,我们要承认它,想办法弥补它,不要让缺点成为我们前进路上的阻碍。我常能在一些创业者口中听到这样的说法“我不能做A,因为我不擅长B”。这种思维方式让我十分吃惊。这反映出他们缺乏创造力。弥补弱点的最佳方式是聘请互补的团队成员,而不是雇佣那些跟你擅长相同事情的人。 慧眼识珠挖掘未被发掘的人才是建立人际网络的有效途径。通过练习,我们能快速识别那些优质有动力、有创造力的人才。挖掘人才最简单的方式就是多社交,多与他人打交道并且与那些给你留下深刻印象的人保持联系。要记住一点,不要局限于他人过往的工作经验和当前成就,我们需要发掘那些有潜力且能在短时间激发潜能的人。 每当遇到新人,我都会扪心自问“这个人有异于常人的能力吗?”对于渴求人才的人来说,这个问题很值得思考。 建立人际网络的特例是找到你生命中的贵人,特别是在职业生涯早期。毫无疑问,能做到这点的最佳方法就是主动去帮助他人(记住,你必须在日后回报你的贵人!) 最后,我们要结交那些积极向上且志同道合之人。 ### 12.资产决定财富 小时候,我对经济的最大误解就是人们通过高薪发财致富。虽然也有一些特例,比如说娱乐圈的艺人,但从以往的福布斯榜单来看,几乎没有人是靠高薪荣登榜单的。 拥有能迅速增值的东西才能真正发家致富。这些东西可以是商业资产、不动产、自然资源、知识产权等。但无论怎样,你需要实际拥有一些东西,而不是单靠出卖时间赚取工资,出卖时间赢来的财富只会呈慢速线性增长。 让事物迅速增值的最佳方法就是大量制造人们想要的东西。 ### 13.要有内驱力 大多数人主要都是靠外部驱动,他们做事情是为了让别人佩服。这种做法坏处颇多,但以下两点最为突出: 首先这会导致你人云亦云,因循守旧。在工作中,你会过于在意他人的看法,这种在意程度可能已经远远超出了你的意识,并且这会阻碍你从事趣味性工作,即使你正在做这样的工作,也不过是在炒冷饭。 其次,这会让你误判风险等级。从短期影响来看,你会将注意力主要放在和他人的竞争上,以确保不会在竞争游戏中落后。 聪明人似乎更容易受到这种外驱力的影响。了解到了这一点可以帮助你摆脱这种影响,但帮助不大,我们必须要极其谨慎才能不至于掉入模仿他人的陷阱中。 我认识的大多数成功人士都是靠自我驱动。他们做事情是为了让自己心悦诚服,因为他们觉得给世界带来改变是自己的责任。当你赚得盆满钵满并且拥有了较高的社会地位之后,金钱和名誉对你的吸引力开始逐渐消失,这时候内驱力就成为了唯一的动力,推动你向更高的地方攀登。 这就是驱动力重要性的体现。驱动力是我了解他人时最先考察的点。我们很难用一套规则去定义正确的驱动力,但是当你遇到它时立刻就能有所体会。在这件事上,Jessica Livingston和Paul Graham是我认为的行动标杆。在YC创办的最初几年,人人都不看好它的发展,没有人认为YC能够成功。但是杰西卡和保罗很看好YC的发展,他们认为如果YC能够成功,将会对世界大有裨益,他们希望能够借此帮助到其他人,并且坚信这种新模式比现存的模式好。 最终你会发现成功是在自己看重的领域里做出出色的成绩。向着自己热爱的方向越早出发就能走得越远,没有热爱之事的人是很难取得成就的。 以上就是这篇博客的全文了,感谢也恭喜你看到这里,说明你是一个想要改变自己的人,很喜欢最近听到的一个道理,我总结在最后,与大家共勉! > “其实我今天说的这些建议,结论并不重要,因为我相信大家也不会因为今天听我说了一遍,明天你就会去实践,所以最重要的是,你要记得这些事情背后更深层的意义是什么。当你懂的了的事情背后的意义,日积月累,哪天你就可以顿悟,然后你就回去行动,最终带来一些改变 ” - 冴羽

一份很全面的全栈知识文档 包含前端、python和golang,适合零基础入门的同学。

docs.fengfengzhidao.com

Java设计模式详解教程

地址:https://b23.tv/mIUzJlC

HTML+CSS前端入门视频教程(第一季)

地址:https://m.bilibili.com/video/BV1oU4y1278g?is_story_h5=false&p=1&share_from=ugc&share_medium=android&share_plat=android&share_session_id=80e2fd37-85d1-46d6-b5fa-fbf6f779599c&share_source=WEIXIN&share_tag=s_i&timestamp=1662604597&unique_k=g220ubE

下载 APP