diff --git "a/MD/Java\345\237\272\347\241\200-IO.md" "b/MD/Java\345\237\272\347\241\200-IO.md" index d5399bb..b5c1ee2 100644 --- "a/MD/Java\345\237\272\347\241\200-IO.md" +++ "b/MD/Java\345\237\272\347\241\200-IO.md" @@ -1,3 +1,7 @@ +同步/异步关注的是消息通信机制 (synchronous communication/ asynchronous communication) 。所谓同步,就是在发出一个调用时,在没有得到结果之前, 该调用就不返回。异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果 + +阻塞/非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 + ## BIO Block-IO:InputStream和OutputStream,Reader和Writer。属于同步阻塞模型 @@ -38,4 +42,4 @@ AIO得到结果的方式: * 基于回调:实现CompletionHandler接口,调用时触发回调函数 * 返回Future:通过isDone()查看是否准备好,通过get()等待返回数据 -但要实现真正的异步非阻塞IO,需要操作系统支持,Windows支持而Linux不完善 \ No newline at end of file +但要实现真正的异步非阻塞IO,需要操作系统支持,Windows支持而Linux不完善 diff --git "a/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" "b/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" index 0d0e436..92e8d12 100644 --- "a/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" +++ "b/MD/Java\345\237\272\347\241\200-JVM\345\216\237\347\220\206.md" @@ -128,6 +128,32 @@ G1将堆划分为多个大小固定的独立区域,根据每次允许的收集 参数:`-XX:+UseG1GC` +### G1的原理,相比CMS的优势 +G1的工作原理: +内存布局: +G1将堆内存划分为多个大小相等的区域(Region),每个区域可以是Eden、Survivor、老年代的一部分或整个Humongous对象(大对象)。这样的划分使得收集可以更细粒度地进行。 +并发标记-整理: +G1使用多阶段的并发标记过程来识别存活对象。首先进行初步标记,然后进行根搜索标记,最后进行重新标记,这些步骤尽可能与应用程序线程并发执行。 +基于目标的收集: +G1根据用户设定的暂停时间目标来决定收集哪些区域。它优先回收垃圾最多(即回收效益最高)的区域,以尽快满足内存需求,这就是“Garbage-First”命名的由来。 +混合收集周期: +G1执行混合垃圾收集,不仅回收年轻代,也回收一部分老年代区域,这样可以更均匀地分散垃圾回收的压力,减少老年代增长导致的Full GC风险。 +空间整合: +G1在回收过程中会进行空间整理,减少内存碎片,这是与CMS的一个重要区别,后者使用标记-清除算法,容易产生碎片。 + +相比CMS的优势: +更好的内存碎片管理: +G1通过复制和整理算法减少了内存碎片,提高了内存使用的效率,而CMS使用标记-清除算法后易造成碎片化。 +可预测的暂停时间: +G1允许用户设定暂停时间目标,通过动态调整收集策略来尽量满足这一目标,使得应用的响应时间更加可预测,适合对延迟敏感的服务。 +自动内存管理: +G1自动管理年轻代和老年代的大小,不需要手动配置比例,降低了调优难度。 +更大的堆支持: +G1设计之初就考虑了大容量堆的管理,能够有效管理数十GB甚至上百GB的堆内存,而CMS在大堆上的表现可能不佳。 +整体性能和吞吐量: +在很多场景下,G1能够提供与CMS相当甚至更好的吞吐量,同时保持较低的暂停时间。 + + ## Stop The World Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互 @@ -181,4 +207,18 @@ STW总会发生,不管是新生代还是老年代,比如CMS在初始标记 案例:https://www.wangtianyi.top/blog/2018/07/27/jvmdiao-you-ru-men-er-shi-zhan-diao-you-parallelshou-ji-qi/ -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ + +## java中的==和equals有什么区别 +==: + +1. ==操作符专门用来比较变量的值是否相同。如果比较的对象是基本数据类型,则比较数值是否相等;如果比较的是引用数据类型,则比较的是对象的内存地址是否相等。 +2. 因为Java只有值传递,所以对于==来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。引用类型对象变量其实是一个引用,它们的值是指向对象所在的内存地址,而不是对象本身。 + +equals: + +1. equals方法常用来比较对象的内容是否相同。 +2. equals()方法存在于Object类中,而Object类是所有类的直接或间接父类。 +3. 未重写equals方法的类:Object中的equals方法实际使用的也是==操作符,比较的是他们的内存地址是否同一地址。 +4. 重写了equals方法的类:实现该类自己的equals方法比较逻辑(一般是比较对象的内容是否相同)。比如: +5. String:比较字符串内容,内容相同这相同; +6. Integer:比较对应的基本数据类型int的值是否相同(==操作符)。 diff --git "a/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" "b/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" index 7b3ab1b..a06a026 100644 --- "a/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" +++ "b/MD/Java\345\237\272\347\241\200-\345\244\232\347\272\277\347\250\213.md" @@ -83,7 +83,16 @@ Synchronized是独享锁 2. 禁止 JVM 进行的指令重排序。 ### ThreadLocal -使用`ThreadLocal userInfo = new ThreadLocal()`的方式,让每个线程内部都会维护一个ThreadLocalMap,里边包含若干了 Entry(K-V 键值对),每次存取都会先获取到当前线程ID,然后得到该线程对象中的Map,然后与Map交互。 +1. 定义:ThreadLocal变量每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。 +2. 原理:Thread线程类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,即每个线程都有一个属于自己的ThreadLocalMap。ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。并发多线程场景下,每个线程Thread,在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而可以实现了线程隔离。 +3. Entry的Key为什么要设计成弱引用:如果Key使用强引用:当ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用的话,如果没有手动删除,ThreadLocal就不会被回收,会出现Entry的内存泄漏问题。如果Key使用弱引用:当ThreadLocal的对象被回收了,因为ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value则在下一次ThreadLocalMap调用set,get,remove的时候会被清除。 + + +1. 强引用:我们平时new了一个对象就是强引用,例如 Object obj = new Object();即使在内存不足的情况下,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。 +2. 软引用:如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。 +3. 弱引用:具有弱引用的对象拥有更短暂的生命周期。如果一个对象只有弱引用存在了,则下次GC将会回收掉该对象(不管当前内存空间足够与否)。 +4. 虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。 + ### 线程池 #### 起源 @@ -103,6 +112,19 @@ new Thread弊端: * threadFactory:线程工厂,来创建线程 * rejectHandler:当拒绝任务提交时的策略(抛异常、用调用者所在的线程执行任务、丢弃队列中第一个任务执行当前任务、直接丢弃任务) +#### 线程池几个核心参数在CPU密集型、IO密集型业务场景的配置 +核心线程数(Core Pool Size) +CPU 密集型:CPU 密集型任务主要依赖处理器资源,因此推荐将核心线程数设置为等于 CPU 核心数或稍高于 CPU 核心数(例如 CPU核数 + 1),以充分利用硬件资源,减少上下文切换的开销。 +IO 密集型:IO 密集型任务在等待 IO 操作完成时不会占用 CPU,这时可以设置更多的核心线程,通常可以是 2 * CPU核数 + 1 或者更高,以便在等待 IO 期间能有更多线程准备处理后续的计算任务。 + +线程保持存活时间(Keep-Alive Time) +CPU 密集型:通常设置较短的保持存活时间,因为CPU密集型任务一旦完成,线程可能不需要再次激活,过多的空闲线程会浪费资源。 +IO 密集型:可以设置较长的保持存活时间,允许线程在等待下一次 IO 操作时保持一段时间的空闲状态,以备快速响应新的请求。 + +工作队列(Work Queue) +CPU 密集型:可以选择较小的有界阻塞队列,比如 SynchronousQueue,这可以防止过多的线程创建,保持核心线程始终忙碌。 +IO 密集型:可以使用较大的有界队列,如 LinkedBlockingQueue,允许更多的任务在等待,减少线程的创建和销毁。 + #### 创建线程的逻辑 以下任务提交逻辑来自ThreadPoolExecutor.execute方法: diff --git "a/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" "b/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" index b8a5047..deee610 100644 --- "a/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" +++ "b/MD/Java\345\237\272\347\241\200-\351\233\206\345\220\210.md" @@ -15,9 +15,17 @@ 4. 为什么容量是2的倍数?在根据hashcode查找数组中元素时,取模性能远远低于与性能,且和2^n-1进行与操作能保证各种不同的hashcode对应的元素也能均匀分布在数组中 ### HashMap rehash过程: +1.7: 1. 空间不够用了,所以需要分配一个大一点的空间,然后保存在里面的内容需要重新计算 hash -2. 如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小 -3. 在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了 +2. 在准备好新的数组后,map会遍历数组的每个“桶”,然后遍历桶中的每个Entity,重新计算其hash值(也有可能不计算),找到新数组中的对应位置,以头插法插入新的链表 +3. 因为是头插法,因此新旧链表的元素位置会发生转置现象。元素迁移的过程中在多线程情境下有可能会触发死循环(无限进行链表反转),因此HashMap线程不安全 + +1.8: +1. 底层结构为:数组+单链表/红黑树。因此如果某个桶中的链表长度大于等于8了,则会判断当前的hashmap的容量是否大于64,如果小于64,则会进行扩容;如果大于64,则将链表转为红黑树 +2. java1.8+在扩容时,不需要重新计算元素的hash进行元素迁移。而是用原先位置key的hash值与旧数组的长度(oldCap)进行"与"操作。 +3. 如果结果是0,那么当前元素的桶位置不变。 +4. 如果结果为1,那么桶的位置就是原位置+原数组 长度 +5. 值得注意的是:为了防止java1.7之前元素迁移头插法在多线程是会造成死循环,java1.8+后使用尾插法 ### ConcurrentHashMap原理 [http://www.jasongj.com/java/concurrenthashmap/](http://www.jasongj.com/java/concurrenthashmap/) diff --git "a/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" "b/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" index bcf8221..a509eca 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-CAP\347\220\206\350\256\272.md" @@ -9,6 +9,55 @@ CAP理论就是说在分布式系统中,最多只能实现上面的两点。 虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回 no error 和 no timeout。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。 -BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 +BASE理论的核心思想是:BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)的缩写,它接受在短时间内可能存在的不一致性,但保证最终达到一致性。与ACID(原子性、一致性、隔离性、持久性)相比,BASE更注重系统的可用性和可伸缩性,牺牲了严格的即时一致性。 -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ +## 强一致如何实现 +什么是强一致性:在分布式存储系统中,强一致性是指系统保证任意时刻数据是一致的,即无论在任何节点上进行的操作,都能立即在所有节点上访问到最新的数据。 + +分布式共识算法。其中,Paxos 和 Raft 是两个著名的共识算法,它们可以用于实现分布式系统的强一致性。这些算法通过选举和投票来确保各个节点的操作顺序一致,从而达到强一致性的要求。 +1. Paxos 算法通过多个阶段的提议、投票和决议过程来确保一个值被所有参与者接受。算法包括提议者(Proposer)、接受者(Acceptor)和学习者(Learner)的角色,并且使用一系列编号递增的消息来进行通信,确保在任何时候只有一个提议会被接受。 +2. Raft 使用了明确的领导者(Leader)角色,简化了状态机复制的过程。领导者负责处理所有的客户端请求并维护集群状态的一致性。它通过日志复制、心跳机制以及选举流程确保在任何时刻都有一个有效的领导者,并保持集群内的日志一致性。 +3. Raft 可以看作是对 Paxos 算法的一种工程化改进,它保留了 Paxos 对于分布式一致性问题的核心解决方案,同时通过对算法进行模块化和清晰化设计,极大地降低了开发者理解和实施分布式共识协议的难度 + + +## 数据一致性解决方案 +强一致性(Strong Consistency) +通过事务(如ACID事务)确保每次操作都看到最新的数据状态。这通常需要两阶段提交(2PC)或其他复杂的事务协议,但可能导致性能下降。 + +最终一致性(Eventual Consistency) +允许短暂的数据不一致,但保证在一段时间后所有副本间达到一致。这种模型在分布式系统中广泛使用,如Cassandra和Amazon DynamoDB。 + +分布式事务 +如两阶段提交(2PC)、三阶段提交(3PC)和多阶段提交(MPC)等协议,用于跨节点的事务协调。 + +复制策略 +同步复制:更新在所有副本之间同步完成,保证强一致性,但可能影响性能。 +异步复制:更新在主节点上完成,副本随后更新,牺牲一致性以换取性能。 + +PAXOS/Raft共识算法 +用于在分布式系统中选举领导者并达成一致性决策。 + +幂等性设计 +确保多次执行相同操作不会改变系统状态,有助于防止因重试或网络问题导致的数据不一致 + +## 高可用多活面试题 +解释什么是“多活”架构,并描述其在高可用性中的作用。 +多活架构是指在一个分布式系统中,多个数据中心或节点同时处于活动状态,每个节点都能独立处理请求,从而提高系统的可用性和容灾能力。当某个节点出现故障时,其他节点仍能继续提供服务。 + +描述一下典型的多活架构部署策略,例如如何处理数据同步和流量路由。 +多活架构通常采用数据同步机制(如主从复制、分布式数据库)保持数据一致性,同时使用智能DNS、负载均衡器或路由策略将流量分散到各个活节点。在故障发生时,可以快速切换流量路由,确保服务不中断。 + +如何在多活架构中实现故障检测和自动切换? +通过心跳检测、健康检查、Zookeeper或Consul等服务发现机制监控各个节点的状态,一旦检测到故障,立即触发流量切换策略,将流量导向其他正常运行的节点。 + +谈谈多活架构下的数据一致性挑战及解决方案 +数据一致性是多活架构中的主要挑战。可以采用异步复制、分布式事务(如2PC、Saga)、最终一致性等方法来平衡数据一致性和高可用性。 + +如何设计一个高可用多活系统?请简述关键要素。 +关键要素包括:负载均衡(如DNS轮询、硬件负载均衡器)、数据复制与一致性(如主从复制、分布式数据库)、故障检测与切换机制(如健康检查、心跳检测)、网络连通性保障(如SDN、专线连接)、自动化运维工具(如自动化部署、监控报警)。 + +多活架构面临的主要挑战有哪些?如何解决? +主要挑战包括数据一致性、网络延迟、故障检测与恢复、运维复杂度等。解决方案涉及采用合适的数据同步技术、优化网络架构(如使用CDN、专线)、实施自动化运维工具、以及建立完善的监控和告警体系。 + +在多活架构中,如何处理跨数据中心的事务? +可以采用分布式事务协调服务(如Seata、LRA),或者通过Saga模式、TCC(Try-Confirm-Cancel)模式来处理跨数据中心的长事务,确保事务的原子性和一致性。 diff --git "a/MD/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201.md" "b/MD/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201.md" index 243b80e..25a8611 100644 --- "a/MD/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201.md" +++ "b/MD/\345\210\206\345\270\203\345\274\217-\351\231\220\346\265\201.md" @@ -54,4 +54,13 @@ public BaseResponse getUserByFeignBatch(@RequestBody UserReqVO userRe 使用RateLimiter 有几个值得注意的地方: -允许先消费,后付款,意思就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取,但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌。 \ No newline at end of file +允许先消费,后付款,意思就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取,但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌。 + + +# 其他算法 +1. 固定窗口算法又叫计数器算法,是一种简单方便的限流算法。主要通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。虽然我们限制了 QPS 为 2,但是当遇到时间窗口的临界突变时,如 1s 中的后 500 ms 和第 2s 的前 500ms 时,虽然是加起来是 1s 时间,却可以被请求 4 次。 +2. 滑动窗口算法在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。有效解决了窗口切换时可能会产生两倍于阈值流量请求的问题。 + +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4b39534226cf4981a4c4632f0cab335e~tplv-k3u1fbpfcp-zoom-in-crop-mark:1512:0:0:0.awebp) + + diff --git "a/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" "b/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" index f9ea66d..c2cebbb 100644 --- "a/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" +++ "b/MD/\346\225\260\346\215\256\345\272\223-MySQL.md" @@ -69,7 +69,14 @@ inner join( 4. 持久性(Durability):一个事务一旦提交,对数据库的修改应该永久保存。 不同事务隔离级别的问题: -[https://www.jianshu.com/p/4e3edbedb9a8](https://www.jianshu.com/p/4e3edbedb9a8) +1. 读未提交(Read Uncommitted):在这个级别下,一个事务可以读取到另一个事务尚未提交的数据变更,即脏读(Dirty Read)现象可能发生。这种隔离级别允许最大的并行处理能力,但并发事务间的数据一致性最弱。 +2. 读已提交(Read Committed):在这种级别下,一个事务只能看到其他事务已经提交的数据,解决了脏读的问题。但是,在同一个事务内,前后两次相同的查询可能会返回不同的结果,这被称为不可重复读(Non-repeatable Read)。 +3. 可重复读(Repeatable Read):这是MySQL InnoDB存储引擎默认的事务隔离级别。在这个级别下,事务在整个生命周期内可以看到第一次执行查询时的快照数据,即使其他事务在此期间提交了对这些数据的修改,也不会影响本事务内查询的结果,因此消除了不可重复读的问题。但是,它不能完全避免幻读(Phantom Read),即在同一事务内,前后两次相同的范围查询可能因为其他事务插入新的行而返回不同数量的结果。 + +为什么可重复读不能完全避免幻读: +1. 幻读的定义: 幻读是指在一个事务内,同一个查询语句多次执行时,由于其他事务提交了新的数据插入或删除操作,导致前后两次查询结果集不一致的情况。具体表现为:即使事务A在开始时已经获取了一个数据范围的快照,当它再次扫描这个范围时,发现出现了之前未读取到的新行,这些新行如同“幻影”一般突然出现。 +2. 可重复读与幻读的关系: 在可重复读隔离级别下,对于已存在的记录,MVCC确保事务能够看到第一次查询时的数据状态,因此不会发生不可重复读。但是,当有新的行被插入到符合事务查询条件的范围内时,MVCC本身并不能阻止这种现象的发生,因为新插入的行对于当前事务是可见的(取决于插入事务的提交时间点和当前事务的Read View)。 +3. 为了减轻幻读的影响,InnoDB在执行某些范围查询时会采用间隙锁(Gap Locks)或者Next-Key Locks来锁定索引区间,防止其他事务在这个范围内插入新的记录,但并非所有的范围查询都会自动加上这样的锁,而且这仅限于使用基于索引的操作,非索引字段的范围查询无法通过间隙锁完全避免幻读。 ## 锁表、锁行 1. InnoDB 支持表锁和行锁,使用索引作为检索条件修改数据时采用行锁,否则采用表锁 @@ -77,16 +84,34 @@ inner join( 3. 行锁相对于表锁来说,优势在于高并发场景下表现更突出,毕竟锁的粒度小 4. 当表的大部分数据需要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁 -[https://segmentfault.com/a/1190000012773157](https://segmentfault.com/a/1190000012773157) - ### 悲观锁乐观锁、如何写对应的SQL -悲观锁:select for update -乐观锁:先查询一次数据,然后使用查询出来的数据+1进行更新数据,如果失败则循环 - -[https://www.jianshu.com/p/f5ff017db62a](https://www.jianshu.com/p/f5ff017db62a) +悲观锁:在悲观锁机制下,事务认为在它执行期间数据一定会被其他事务修改,因此在读取数据时就立即对其加锁,阻止其他事务对同一数据进行操作,直到当前事务结束并释放锁。SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE +乐观锁:乐观锁并不在读取数据时立即加锁,而是在更新数据时检查自上次读取以来数据是否已被其他事务修改过。这通常通过在表中添加一个版本号字段或者时间戳字段来实现。 + +### 锁的种类 +按功能分类: +1. 共享锁:Shared Locks,简称S锁。在事务要读取一条记录时,需要先获取该记录的S锁。 +2. 独占锁:排他锁,英文名:Exclusive Locks,简称X锁。在事务要改动一条记录时,需要先获取该记录的X锁。 + +按粒度分类: +1. 表锁是对一整张表加锁,虽然可分为读锁和写锁,但毕竟是锁住整张表,会导致并发能力下降,一般是做ddl处理时使用。另外表级别还有AUTO-INC锁:在执行插入语句时就在表级别加一个AUTO-INC锁,然后为每条待插入记录的AUTO_INCREMENT修饰的列分配递增的值,在该语句执行结束后,再把AUTO-INC锁释放掉。这样一个事务在持有AUTO-INC锁的过程中,其他事务的插入语句都要被阻塞,可以保证一个语句中分配的递增值是连续的 +2. 行锁,也称为记录锁,顾名思义就是在记录上加的锁。锁住数据行,这种加锁方法比较复杂,但是由于只锁住有限的数据,对于其它数据不加限制,所以并发能力强,MySQL一般都是用行锁来处理并发事务。下面是行锁的类型: + - Record Locks:记录锁,当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可以继续获取X型记录锁;当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不可以继续获取X型记录锁; + - Gap Locks:是为了防止插入幻影记录而提出的,不允许其他事务往这条记录前面的间隙插入新记录。在REPEATABLE READ隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用MVCC方案解决,也可以采用加锁方案解决,但是在使用加锁方案解决时有个大问题,就是事务在第一次执行读取操作时,那些幻影记录尚不存在,我们无法给这些幻影记录加上记录锁 + - Next-Key Locks:记录锁和一个gap锁的合体。锁住某条记录,又想阻止其他事务在该记录前面的间隙插入新记录 + - Insert Intention Locks:插入意向锁。一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的gap锁,如果有的话,插入操作需要等待,直到拥有gap锁的那个事务提交 + - 隐式锁:当事务需要加锁的时,如果这个锁不可能发生冲突,InnoDB会跳过加锁环节,这种机制称为隐式锁。隐式锁是InnoDB实现的一种延迟加锁机制,其特点是只有在可能发生冲突时才加锁,从而减少了锁的数量,提高了系统整体性能 + +### MVCC +MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种用于数据库管理系统中的事务处理机制,它在不使用加锁或者只对数据进行有限锁定的情况下,为并发事务提供了一种相对无冲突的访问方式。MVCC有效地避免了脏读、不可重复读等问题,并极大地提高了数据库系统的并发性能 + +原理: +1. 隐式字段:每行记录除了实际的数据列外,还包含额外的系统信息,例如DB_TRX_ID(最近修改该行事务ID)、DB_ROLL_PTR(回滚指针指向undo日志),以及DB_ROW_ID(行ID)等。这些隐藏字段用于跟踪数据的不同版本。 +2. 事务版本号:每个事务都有一个唯一的事务ID,在事务开始时分配。这个ID用来区分不同事务的操作时间点。 +3. Undo日志:当事务对某一行进行修改时,旧版本的数据会被存放在Undo日志中,形成历史版本链。这样当其他事务需要读取旧版本数据时,可以从Undo日志中获取。 +4. Read View:MVCC通过Read View来决定事务可见的行版本。每个读操作都会创建或复用一个Read View,根据Read View的规则判断当前事务能否看到某个版本的数据:如果被读取行的事务ID小于Read View的最低事务ID,则可见;如果大于等于Read View的最高事务ID且未提交,则不可见;否则,检查该行是否在Read View创建之后被其他已提交事务修改过。 +5. 非锁定读:在MVCC中,存在两种类型的读操作:快照读(Snapshot Read)和当前读(Current Read)。快照读就是基于MVCC实现的,它不会阻塞其他事务,并总是返回某一特定时间点的数据视图。而当前读则会获取最新的数据并加锁以确保一致性。 -### MVVC -https://tech.meituan.com/2014/08/20/innodb-lock.html ### 索引 #### 原理 @@ -94,22 +119,19 @@ https://tech.meituan.com/2014/08/20/innodb-lock.html 非聚集索引:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,所以一个表中可以拥有多个非聚集索引。叶子结点包含索引字段值及指向数据页数据行的逻辑指针 #### 索引原理 -使用B+树来构建索引,为什么不用二叉树?因为红黑树在磁盘上的查询性能远不如B+树 - -红黑树最多只有两个子节点,所以高度会非常高,导致遍历查询的次数会多,又因为红黑树在数组中存储的方式,导致逻辑上很近的父节点与子节点可能在物理上很远,导致无法使用磁盘预读的局部性原理,需要很多次IO才能找到磁盘上的数据 - -但B+树一个节点中可以存储很多个索引的key,且将大小设置为一个页,一次磁盘IO就能读取很多个key,且叶子节点之间还加上了下个叶子节点的指针,遍历索引也会很快。 - -B+树的高度如何计算? -1. 单个叶子节点(页)中的记录数 =16K/1K=16。(这里假设一行记录的数据大小为1k) -2. 假设主键 ID 为 bigint 类型,长度为 8 字节,而指针大小在 InnoDB 源码中设置为 6 字节,这样一共 14 字节 -3. 我们一个页中能存放多少这样的单元,其实就代表有多少指针,即 16384/14=1170。 -4. 那么可以算出一棵高度为 2 的 B+ 树,能存放 1170*16=18720 条这样的数据记录。 - -B与B+区别: +为什么使用B+树来构建索引? 1. b+树的中间节点不保存数据,所以磁盘页能容纳更多节点元素; 2. b+树查询必须查找到叶子节点,b树只要匹配到即可不用管元素位置,因此b+树查找更稳定 3. 对于范围查找来说,b+树只需遍历叶子节点链表即可,b树却需要重复地中序遍历 +4. 红黑树最多只有两个子节点,所以高度会非常高,导致遍历查询的次数会多 +5. 红黑树在数组中存储的方式,导致逻辑上很近的父节点与子节点可能在物理上很远,导致无法使用磁盘预读的局部性原理,需要很多次IO才能找到磁盘上的数据。B+树一个节点中可以存储很多个索引的key,且将大小设置为一个页,一次磁盘IO就能读取很多个key +6. 叶子节点之间还加上了下个叶子节点的指针,遍历索引也会很快。 + +B+树的高度如何计算? +1. 假设B+树的高度为2的话,即有一个根结点和若干个叶子结点。这棵B+树的存放总记录数为=根结点指针数 * 单个叶子节点记录行数 +2. 假设一行记录的数据大小为1k,那么单个叶子节点可以存的记录数 =16k/1k =16. +3. 非叶子节点内存放多少指针呢?我们假设主键ID为bigint类型,长度为8字节(int类型的话,一个int就是32位,4字节),而指针大小是固定的在InnoDB源码中设置为6字节,假设n指主键个数即key的个数,n*8 + (n + 1) * 6 = 16K=16*1024B , 算出n约为 1170,意味着根节点会有1170个key与1171个指针 +4. 一棵高度为2的B+树,能存放1171* 16 = 18736条这样的数据记录。同理一棵高度为3的B+树,能存放1171 * 1171 * 16 = 21939856,也就是说,可以存放两千万左右的记录。B+树高度一般为1-3层,已经满足千万级别的数据存储。 #### 优化 如何选择合适的列建立索引? @@ -124,7 +146,41 @@ B与B+区别: 2. count(1)包括了忽略所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL 3. count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。 -count(*),自动会优化指定到那一个字段。所以没必要去count(1),用count(*),sql会帮你完成优化的 因此:count(1)和count(*)基本没有差别! +count(\*),自动会优化指定到那一个字段。所以没必要去count(1),用count(\*),sql会帮你完成优化的 因此:count(1)和count(\*)基本没有差别! + +### explain详解 +1. id:select 查询序列号。id相同,执行顺序由上至下;id不同,id值越大优先级越高,越先被执行。 +2. select_type:查询数据的操作类型,其值如下: + - simple:简单查询,不包含子查询或 union + - primary:包含复杂的子查询,最外层查询标记为该值 + - subquery:在 select 或 where 包含子查询,被标记为该值 + - derived:在 from 列表中包含的子查询被标记为该值,MySQL 会递归执行这些子查询,把结果放在临时表 + - union:若第二个 select 出现在 union 之后,则被标记为该值。若 union 包含在 from 的子查询中,外层 select 被标记为 derived + - union result:从 union 表获取结果的 select +3. table:显示该行数据是关于哪张表 +4. partitions:匹配的分区 +5. type:表的连接类型,其值,性能由高到底排列如下: + - system:表只有一行记录,相当于系统表 + - const:通过索引一次就找到,只匹配一行数据 + - eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常用于主键或唯一索引扫描 + - ref:非唯一性索引扫描,返回匹配某个单独值的所有行。用于=、< 或 > 操作符带索引的列 + - range:只检索给定范围的行,使用一个索引来选择行。一般使用between、>、<情况 + - index:只遍历索引树 + - ALL:全表扫描,性能最差 +注:前5种情况都是理想情况的索引使用情况。通常优化至少到range级别,最好能优化到 ref +6. possible_keys:显示 MySQL 理论上使用的索引,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。如果该值为 NULL,说明没有使用索引,可以建立索引提高性能 +7. key:显示 MySQL 实际使用的索引。如果为 NULL,则没有使用索引查询 +8. key_len:表示索引中使用的字节数,通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好 显示的是索引字段的最大长度,并非实际使用长度 +9. ref:显示该表的索引字段关联了哪张表的哪个字段 +10. rows:根据表统计信息及选用情况,大致估算出找到所需的记录或所需读取的行数,数值越小越好 +11. filtered:返回结果的行数占读取行数的百分比,值越大越好 +12. extra:包含不合适在其他列中显示但十分重要的额外信息,常见的值如下: + - using filesort:说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。出现该值,应该优化 SQL + - using temporary:使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。出现该值,应该优化 SQL + - using index:表示相应的 select 操作使用了覆盖索引,避免了访问表的数据行,效率不错 + - using where:where 子句用于限制哪一行 + - using join buffer:使用连接缓存 + - distinct:发现第一个匹配后,停止为当前的行组合搜索更多的行 ## 分区分库分表 分区:把一张表的数据分成N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的,通过将不同数据按一定规则放到不同的区块中提升表的查询效率。 @@ -141,4 +197,23 @@ count(*),自动会优化指定到那一个字段。所以没必要去count(1), 2. 跨库跨表的join问题。在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上,我们无法join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次查询能够完成的业务,可能需要多次查询才能完成。 3. 额外的数据管理负担和数据运算压力。额外的数据管理负担,最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算。 + +## MySQL数据库主从延迟同步方案 +什么事主从延迟:为了完成主从复制,从库需要通过 I/O 线程获取主库中 dump 线程读取的 binlog 内容并写入到自己的中继日志 relay log 中,从库的 SQL 线程再读取中继日志,重做中继日志中的日志,相当于再执行一遍 SQL,更新自己的数据库,以达到数据的一致性。主从延迟,就是同一个事务,从库执行完成的时间与主库执行完成的时间之差 + +1. 「异步复制」:MySQL 默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给客户端,并不关心从库是否已经接收并处理。这样就会有一个问题,一旦主库宕机,此时主库上已经提交的事务可能因为网络原因并没有传到从库上,如果此时执行故障转移,强行将从提升为主,可能导致新主上的数据不完整。 +2. 「全同步复制」:指当主库执行完一个事务,并且所有的从库都执行了该事务,主库才提交事务并返回结果给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。 +3. 「半同步复制」:是介于全同步复制与全异步复制之间的一种,主库只需要等待至少一个从库接收到并写到 Relay Log 文件即可,主库不需要等待所有从库给主库返回 ACK。主库收到这个 ACK 以后,才能给客户端返回 “事务完成” 的确认。这样会出现一个问题,就是实际上主库已经将该事务 commit 到了存储引擎层,应用已经可以看到数据发生了变化,只是在等待返回而已。如果此时主库宕机,可能从库还没写入 Relay Log,就会发生主从库数据不一致。为了解决上述问题,MySQL 5.7 引入了增强半同步复制。针对上面这个图,“Waiting Slave dump” 被调整到了 “Storage Commit” 之前,即主库写数据到 binlog 后,就开始等待从库的应答 ACK,直到至少一个从库写入 Relay Log 后,并将数据落盘,然后返回给主库 ACK,通知主库可以执行 commit 操作,然后主库再将事务提交到事务引擎层,应用此时才可以看到数据发生了变化。 +4. 一主多从:如果从库承担了大量查询请求,那么从库上的查询操作将耗费大量的 CPU 资源,从而影响了同步速度,造成主从延迟。那么我们可以多接几个从库,让这些从库来共同分担读的压力。 +5. 强制走主库:如果某些操作对数据的实时性要求比较苛刻,需要反映实时最新的数据,比如说涉及金钱的金融类系统、在线实时系统、又或者是写入之后马上又读的业务,这时我们就得放弃读写分离,让此类的读请求也走主库,这就不存延迟问题了。 +6. 并行复制:MySQL在5.6版本中使用并行复制来解决主从延迟问题。所谓并行复制,指的就是从库开启多个SQL线程,并行读取relay log中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。MySQL 5.7对并行复制做了进一步改进,开始支持真正的并行复制功能。MySQL5.7的并行复制是基于组提交的并行复制,官方称为enhanced multi-threaded slave(简称MTS),延迟问题已经得到了极大的改善。其核心思想是:一个组提交的事务都是可以并行回放(配合binary log group commit);slave机器的relay log中 last_committed相同的事务(sequence_num不同)可以并发执行。 + +强制走主库之后,如何在处理大数据量的时候保证实时写查: +1. 表结构与索引优化:确保表结构设计合理,尽量避免数据冗余和更新异常。根据查询条件创建合适的索引,特别是对于高频查询字段,可以显著提高读取速度。 +2. 分区表(Partitioning):对于非常大的表,可以考虑使用分区表功能,将大表物理分割成多个小表,根据时间、范围或其他业务逻辑进行划分,这样不仅能提高查询效率,还可以通过并行处理提高写入速度。 +3. 分库分表:当单表数据量过大时,采用水平拆分或垂直拆分策略将数据分布到多个数据库或表中,进一步提升读写性能。 +4. 查询优化:避免全表扫描,尽可能使用覆盖索引。编写高效的SQL语句,减少不必要的计算和排序操作。 +5. 缓存机制:在数据库层面,可以考虑使用内存表或者缓存技术(如Redis)来临时存储最近写入的数据,以便快速响应实时查询请求。 +6. 实时分析型数据库:对于大数据量且实时性要求极高的场景,可以考虑引入实时分析型数据库系统,例如ClickHouse、Druid等,它们设计之初就为实时写入和查询提供了高效的支持。 + 欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git "a/MD/\346\225\260\346\215\256\345\272\223-Redis.md" "b/MD/\346\225\260\346\215\256\345\272\223-Redis.md" index f386827..8b86d69 100644 --- "a/MD/\346\225\260\346\215\256\345\272\223-Redis.md" +++ "b/MD/\346\225\260\346\215\256\345\272\223-Redis.md" @@ -37,6 +37,19 @@ volatile-lru:当内存不足以容纳新写入数据时,在设置了过期 volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除 +Redis 是单线程吗? +1. Redis 单线程指的是「接收客户端请求->解析请求 ->进行数据读写等操作->发生数据给客户端」这个过程是由一个线程(主线程)来完成的但是,Redis 程序并不是单线程的,Redis 在启动的时候,是会启动后台线程(BIO)的: +2. Redis 在 2.6 版本,会启动 2 个后台线程,分别处理关闭文件、AOF 刷盘这两个任务; +3. Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。例如执行 unlink key / flushdb async / flushall async 等命令,会把这些删除操作交给后台线程来执行,好处是不会导致 Redis 主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用 del 命令删除,因为 del 是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key。 + +Redis 采用单线程为什么还这么快? +1. Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构 +2. Redis 采用单线程模型可以避免了多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且也不会导致死锁问题 +3. Redis 采用了I/O 多路复用机制处理大量的客户端 Socket 请求,IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。 + +Redis 6.0 之后为什么引入了多线程? +1. 随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 I/O 的处理上。所以为了提高网络请求处理的并行度 +2. 对于读写命令,Redis 仍然使用单线程来处理,Redis 官方表示,Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上。 ## 集群模式 来源: @@ -109,8 +122,22 @@ redis主从或哨兵模式的每个实例都是全量存储所有数据,浪费 ## 持久化 RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。 -AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。 +1. save:save 是需要占用主线程资源的,会阻塞主线程。 +2. bgsave:使用的是子进程写,不会占用主线程的资源(fork会阻塞主线程,但是copyonwrite过程是纳秒级别相对较快,aof是毫秒级别)。redis使用了操作系统的写时复制技术(COPY -ON-WRITE COW)bgsave子进程是由主进程fork生成的,可以共享主进程的内存,如果主进程有写入命令时候,主进程会把写入的那块内存页先复制一份给fork使用,这样就不影响主进程的写操作,可以继续修改原来的数据,避免了对正常业务的影响。 + + +AOF 持久化记录服务器执行的所有写操作命令,aof日志的重写是由后台程序bgrewriteaof子进程完成的,这也是为了避免阻塞主线程,导致数据性能下降。Redis 还可以在后台对 AOF 文件进行重写(rewrite重写机制就是把这条键值对的多次操作压缩成一次操作,这样就大大减少了aof日志文件的大小。),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。 +1. always 同步回写:每条命令执行完成后,回立即将日志写回磁盘。always回写回占用主线程资源,每次回写都是一个慢速的罗盘操作,基本上可以避免数据的丢失,会对主线程产生影响,如果你对redis数据的准确性要求非常高,而写入和读取占比不高的话,可以采用这种策略。 +2. everysec 每秒回写:每个命令执行完后回先将日志写入aof文件的内存缓冲区,每隔一秒把缓冲区中的命令写入磁盘。每秒回写采用一秒回写一次的策略,避免了同步回写的性能开销,虽然减少了对系统性能的影响,但是如果1秒内产生了大量的写操作,在aof缓冲区积压了很多日志,这时候还没来得及写入aof日志文件就发生了宕机,就会造成数据的丢失。 +3. no 操作系统控制的回写:每个写命令执行完后只是先把日志写入aof文件的内存缓冲区,何时回写磁盘由操作系统决定。采用操作系统控制的回写,在写完aof缓冲区后就可以继续执行命令,但是如果系统发生宕机了,也同样会造成数据的丢失。 + Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。 -但实际上持久化会对Redis的性能造成非常严重的影响,如果一定需要保存数据,那么数据就不应该依靠缓存来保存,建议使用其他方式如数据库。所以Redis的持久化意义不大。 +## 渐进式rehash +以下是哈希表渐进式 rehash 的详细步骤: +1. 为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。 +2. 在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示 rehash 工作正式开始。 +3. 在 rehash 进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对 rehash 到 ht[1] , 当 rehash 工作完成之后, 程序将 rehashidx 属性的值增一。 +4. 随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被 rehash 至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示 rehash 操作已完成。 +渐进式 rehash 的好处在于它采取分而治之的方式, 将 rehash 键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式 rehash 而带来的庞大计算量。 欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" index 0c780e4..e1f12d9 100644 --- "a/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" +++ "b/MD/\351\200\232\347\224\250\345\237\272\347\241\200-\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" @@ -60,4 +60,28 @@ HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的 3. 密钥交换 - 使用RSA非对称公钥加密算法(客户端生成一个对称密钥,然后用SSL证书里带的服务器公钥将该对称密钥加密。随后发送到服务端,服务端用服务器私钥解密,到此,握手阶段完成。)或者DH交换算法在客户端与服务端双方确定将要使用的密钥。这个密钥是双方都同意的一个简单,对称的密钥。这个过程是基于非对称加密方式和服务器的公钥/私钥的。 4. 加密通信 - 在服务器和客户端加密实际信息是用到对称加密算法,用哪个算法在Hello阶段已经确定。对称加密算法是对于加密和解密都很简单的密钥。这个密钥是基于第三步在客户端与服务端已经商议好的。与需要公钥/私钥的非对称加密算法相反。 -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ +## socket线程安全? +总结来说,虽然系统级别的socket操作本身具备一定程度的并发安全性,但在多线程程序中对socket进行读写时仍需要开发者采取适当的同步策略以保证线程安全。 + +1. 系统调用级别:在操作系统层面,对同一个socket文件描述符(fd)进行读写操作的系统调用如send、recv、write和read等,通常是线程安全的。这意味着多个线程可以同时调用这些函数而不会导致内核数据结构的混乱。 +2. 应用程序级别:然而,在应用程序中直接对同一个socket进行并发读写时,并非完全线程安全。不同的线程如果不加控制地同时对同一socket进行读写,可能会遇到以下问题: + - 数据包边界混淆:TCP是流式传输协议,没有消息边界的概念,两个线程如果并发发送或接收,可能导致接收到的数据边界与发送时不一致。 + - 竞争条件:当多个线程尝试修改socket的状态或缓冲区时,可能会产生未定义的行为,例如,一个线程正在发送数据时,另一个线程关闭了socket,这可能导致不可预测的结果。 + - 同步问题:如果没有适当的锁或其他同步机制来保护共享状态,那么不同线程之间对于何时开始和结束I/O操作的协调就可能出现问题。 +3. 最佳实践:为了在多线程环境下正确且安全地使用socket,通常建议采取以下措施: + - 互斥访问:使用线程同步机制,如互斥锁(mutex),确保任何时候只有一个线程在执行读写操作。 + - 生产者-消费者模型:设计成一个线程专门负责接收数据并放入队列,另一个线程从队列取出数据并发送,通过队列实现线程间的通信和同步。 + - 事件驱动编程:在某些场合下,如使用异步I/O(如epoll/kqueue等)、IOCP(Windows平台)或者事件循环机制,可以避免直接的并发访问,从而在单个线程内处理多个socket连接,减少线程间同步开销。 + +## java socket跟socket有什么区别 +1. 抽象层次: + - 一般意义上的Socket:通常指的是计算机网络编程中的一个抽象概念,是操作系统提供的一种用于进程间通信的机制,通过它可以在网络中不同主机上的进程之间进行双向的数据传输。它是TCP/IP协议栈的一部分,在TCP或UDP等传输层协议之上提供接口。 + - Java Socket:在Java编程语言中,java.net.Socket和java.net.ServerSocket类是对底层操作系统Socket接口的封装,提供了面向对象的方式来创建、管理和使用网络连接。程序员可以通过Java Socket API实现跨网络的客户端-服务器通信。 +2. API形式: + - 通用Socket:在不同的编程语言或环境中,对Socket的操作可能需要直接调用系统API函数(如C语言中的socket()、bind()、listen()、accept()、send()、recv()等)。 + - Java Socket:Java为开发者提供了一套高级且易于使用的API,如Socket socket = new Socket(host, port)来建立到指定主机和端口的连接,以及socket.getInputStream()和socket.getOutputStream()来获取输入输出流以读写数据。 +3. 平台无关性: + - 通用Socket:直接操作系统提供的Socket API时,代码往往具有一定的平台相关性,即在不同的操作系统上可能需要采用不同的API及调用方式。 + - Java Socket:由于Java语言的跨平台特性,其封装的Socket API能够在支持Java的所有平台上运行,使得基于Java Socket编写的网络程序具有良好的可移植性。 + +综上所述,Java Socket是在Java编程环境下对通用Socket概念的具体实现和抽象,为开发者提供了一个更加便捷、安全且平台无关的方式来处理网络通信问题。 diff --git a/README.md b/README.md index 4b91026..c8e9559 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,3 @@ -本项目将永久持续维护,有兴趣的伙伴建议微信扫码入群,我将在一段时间内免费在群内为大家提供(本人前快手高级开发,8年Java工作经验,大厂小厂C端B端都呆过): -1. Java相关面试问题咨询 -2. 国内大厂内推机会 -3. 一对一语音简历优化、模拟面试 - - - ----------------------- - 本项目是本人参加BAT等其他公司电话、现场面试之后总结出来的针对Java面试的知识点或真题,每个点或题目都是在面试中被问过的。 除开知识点,一定要准备好以下套路: @@ -53,11 +44,14 @@ * [数组-对撞指针-最大蓄水](MD/算法-数组-对撞指针-最大蓄水.md) * [数组-滑动窗口-最小连续子数组](MD/算法-数组-滑动窗口-最小连续子数组.md) * [数组-归并排序-合并有序数组](MD/算法-数组-归并排序-合并有序数组.md) +* [数组-顺时针打印矩形](https://www.nowcoder.com/practice/9b4c81a02cd34f76be2659fa0d54342a) +* [数组-24点游戏](https://leetcode.cn/problems/24-game/description/) * [链表-链表反转-链表相加](MD/算法-链表-反转链表-链表相加.md) * [链表-双指针-删除倒数第n个](MD/算法-链表-双指针-删除倒数第n个.md) * [链表-双指针-重排链表](https://leetcode.cn/problems/reorder-list/description/) * [二叉树-递归-二叉树反转](MD/算法-二叉树-递归-二叉树反转.md) * [二叉树-递归-多叉树中最长的连续序列](MD/算法-二叉树-多叉树中最长的连续序列.md) +* [二叉树-trie树](https://leetcode.cn/problems/implement-trie-prefix-tree/description/) * [动态规划-连续子数组最大和](MD/算法-动态规划-连续子数组最大和.md) * [数据结构-LRU淘汰算法](MD/算法-数据结构-LRU淘汰算法.md) * [其他-二十进制相加](MD/算法-其他-二十进制相加.md) @@ -72,5 +66,3 @@ ### 智力题 * [概率p输出1,概率1-p输出0,等概率输出0和1](https://blog.csdn.net/qq_29108585/article/details/60765640) * [判断点是否在多边形内部](https://www.cnblogs.com/muyefeiwu/p/11260366.html) - -欢迎光临[我的博客](http://www.wangtianyi.top/?utm_source=github&utm_medium=github),发现更多技术资源~ diff --git a/images/pay_wx.png b/images/pay_wx.png new file mode 100644 index 0000000..0956481 Binary files /dev/null and b/images/pay_wx.png differ diff --git a/images/wxg.jpg b/images/wxg.jpg deleted file mode 100644 index f37bd3e..0000000 Binary files a/images/wxg.jpg and /dev/null differ diff --git a/images/wxg.png b/images/wxg.png new file mode 100644 index 0000000..6f41830 Binary files /dev/null and b/images/wxg.png differ diff --git a/images/wxg2.jpg b/images/wxg2.jpg new file mode 100644 index 0000000..97a90a7 Binary files /dev/null and b/images/wxg2.jpg differ diff --git a/images/wxg2.png b/images/wxg2.png new file mode 100644 index 0000000..199aed7 Binary files /dev/null and b/images/wxg2.png differ