表示快速失败,在集合遍历过程中,一旦发现容器中的数据被修改了,会 立刻抛出ConcurrentModificationException 异常,从而导致遍历失败
java.util 包下的集合类都是快速失败机制的, 常见的的使用 fail-fast 方式遍历的容器有 HashMap 和 ArrayList 等。
修改代码示例
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ModifyListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
// 修改元素
iterator.set(element + "-Modified");
}
System.out.println("Modified List: " + list);
}
}
删除代码示例
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ModifySetExample {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
// 修改元素
iterator.remove();
}
System.out.println("Modified Set: " + set);
}
}
表示失败安全,也就是在这种机制下,出现集合元素的修改,不会抛出 ConcurrentModificationException。
原因是采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先 复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中 对原集合所作的修改并不能被迭代器检测到
比如这种情况(贴下面这个图) , 定义了一个 CopyOnWriteArrayList,在对这个集 合遍历过程中,对集合元素做修改后,不会抛出异常,但同时也不会打印出增加的元素。
(但同时也不会打印出增加的元素。) (但同时也不会打印出增加的元素。) (但同时也不会打印出增加的元素。)这句话的意思是:对于 CopyOnWriteArrayList,迭代器是不可变的,也就是说一旦创建迭代器,它将保留创建时刻集合的快照,后续对集合的修改不会反映在迭代器上。
java.util.concurrent 包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。 常见的的使用 fail-safe 方式遍历的容器有 ConcerrentHashMap 和 CopyOnWriteArrayList 等。
在微服务架构下,由于数据库和应用服务的拆分,导致原本一个事务单元中的多个 DML 操作, 变成了跨进程或者跨数据库的多个事务单元的多个 DML 操作, 而传统的数据库事务无法解决这类的问题,所以就引出了分布式事务的概念。
市面上有很多针对这些理论模型实现的分布式事务框架,我们可以在应用中集成这 些框架来实现分布式事务。 而 Seata 就是其中一种,它是阿里开源的分布式事务解决方案,提供了高性能且简单易 用的分布式事务服务。
分布式事务本质上要解决的就是跨网络节点的多个事务的数据一致性问题,业内常 见的解决方法有两种
基于 CAP 定理我们可以知道,强一致性方案对于应用的性能和可用性会有影响,所以 对于数据一致性要求不高的场景,就会采用最终一致性算法。
是一种基于本地事务+二阶段协议来实现的最终数据一致性方案,也是 Seata 默认的解决方案
TCC 事务是 Try、Confirm、Cancel 三个词语的缩写,简单理解就是 把一个完整的业务逻辑拆分成三个阶段,然后通过事务管理器在业务逻辑层面根据 每个分支事务的执行情况分别调用该业务的 Confirm 或者 Cacel 方法。
Saga 模式是 SEATA 提供的长事务解决方案,在 Saga 模式中,业务 流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功 的参与者。
XA 可以认为是一种强一致性的事务解决方法,它利用事务资源(数据库、 消息服务等)对 XA 协议的支持,以 XA 协议的机制来管理分支事务的一种 事务 模式。
在实际应用中,如果我们需要把磁盘中的某个文件内容发送到远程服务器上,那么它必须要经过几个拷贝的过程,如图
在这个过程中我们可以发现,数据从磁盘到最终发送出去,要经历 4 次拷贝,而在这四 次拷贝过程中,有两次拷贝是浪费的,分别是:
除此之外,由于用户空间和内核空间的切换会带来 CPU 的上线文切换,对于 CPU 性能 也会造成性能影响。
而零拷贝,就是把这两次多于的拷贝省略掉,应用程序可以直接把磁盘中的数据从内核 中直接传输给 Socket,而不需要再经过应用程序所在的用户空间,如下图所示。
所以,所谓零拷贝,并不是完全没有数据赋值,只是相对于用户空间来说,不再需要进 行数据拷贝。对于前面说的整个流程来说,零拷贝只是减少了不必要的拷贝次数而已。
它的原理是:将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。使用这种 方式可以获取很大的 I/O 提升,省去了用户空间到内核空间复制的开销。
幻读是指在同一个事务中,前后两次查询相同的范围时,得到的结果不一致(我们来看 这个图)
所以,幻读会带来数据一致性问题。
Mysql 有四种事务隔离级别,这四种隔离级别代表当存在多个事务并发冲突时,可能出 现的脏读、不可重复读、幻读的问题。 其中 InnoDB 在 RR(默认) 的隔离级别下,解决了幻读的问题。
InnoDB 引入了间隙锁和 next-key Lock 机制来解决幻读问题,为了更清晰的说明这两 种锁,我举一个例子:
假设现在存在这样(图片)这样一个 B+ Tree 的索引结构,这个结构中有四个索引元 素分别是:1、4、7、10。
当我们通过主键索引查询一条记录,并且对这条记录通过 for update 加锁(请看这个 图片)
这个时候,会产生一个记录锁,也就是行锁,锁定 id=1 这个索引(请看这个图片)。
被锁定的记录在锁释放之前,其他事务无法对这条记录做任何操作。 前面我说过对幻读的定义: 幻读是指在同一个事务中,前后两次查询相同的范围时, 得到的结果不一致! 注意,这里强调的是范围查询, 也就是说,InnoDB 引擎要解决幻读问题,必须要保证一个点,就是如果一个事务通过 这样一条语句(如图)进行锁定时。
另外一个事务再执行这样一条(显示图片)insert 语句,需要被阻塞,直到前面获得锁 的事务释放。
所以,在 InnoDB 中设计了一种间隙锁,它的主要功能是锁定一段范围内的索引记录(如 图)
当对查询范围 id>4 and id <7 加锁的时候,会针对 B+树中(4,7)这个开区间范围 的索引加间隙锁。 意味着在这种情况下,其他事务对这个区间的数据进行插入、更新、删除都会被锁住。
但是,还有另外一种情况,比如像这样(图片)
这条查询语句是针对 id>4 这个条件加锁,那么它需要锁定多个索引区间,所以在这种 情况下 InnoDB 引入了 next-key Lock 机制。 next-key Lock 相当于间隙锁和记录锁的合集,记录锁锁定存在的记录行,间隙锁锁住 记录行之间的间隙,而 next-key Lock 锁住的是两者之和。(如图所示)
每个数据行上的非唯一索引列上都会存在一把 next-key lock,当某个事务持有该数据 行的 next-key lock 时,会锁住一段左开右闭区间的数据。 因此,当通过 id>4 这样一种范围查询加锁时,会加 next-key Lock,锁定的区间范围 是:(4, 7] , (7,10],(10,+∞]
间隙锁和 next-key Lock 的区别在于加锁的范围,间隙锁只锁定两个索引之间的引用间 隙,而 next-key Lock 会锁定多个索引区间,它包含记录锁和间隙锁。
CPU 是整个电脑的核心计算资源,对于一个应用进程来说,CPU 的最小执行单元 是线程。
导致 CPU 飙高的原因有几个方面
CPU 上下文切换过多
在 Java 中,文件 IO、网络 IO、锁等待、线程阻塞等操作都会造成线程阻塞从而触发 上下文切换
CPU 资源过度消耗
在程序中创建了大量的线程,或者有线程一直占用 CPU 资源无法被释放,比如死循环!
访问量较大
最后有可能定位的结果是程序正常,只是在 CPU 飙高的那一刻,用户访问量较大, 导致系统资源不够。
CPU 利用率过高之后,导致应用中的线程无法获得 CPU 的调度,从而影响程序的执行 效率!
最后有可能定位的结果是程序正常,只是在 CPU 飙高的那一刻,用户访问量较大, 导致系统资源不够。
不管是线程池内部还是外部,要想知道线程是否 执行结束,我们必须要获取线程执行结束后的状态,而线程本身没有返回值,所以 只能通过阻塞-唤醒的方式来实现,future.get 和 CountDownLatch 都是这样一个 原理。
开放定址法,也称为线性探测法,就是从发生冲突的那个位置开始,按照 一定的次序从 hash 表中找到一个空闲的位置,然后把发生冲突的元素存 入到这个空闲位置中。ThreadLocal 就用到了线性探测法来解决 hash 冲 突的。
在 hash 表索引 1 的位置存了一个 key=name,当再次添加 key=hobby 时,hash 计算得到的索引也是 1,这个就是 hash 冲突。而开放定址法, 就是按顺序向前找到一个空闲的位置来存储冲突的 key。
链式寻址法,这是一种非常常见的方法,简单理解就是把存在 hash 冲突 的 key,以单向链表的方式来存储,比如 HashMap 就是采用链式寻址法 来实现的。
存在冲突的 key 直接以单向链表的方式进行存储。
再 hash 法,就是当通过某个 hash 函数计算的 key 存在冲突时,再用另 外一个 hash 函数对这个 key 做 hash,一直运算直到不再产生冲突。这种 方式会增加计算时间,性能影响较大
建立公共溢出区, 就是把 hash 表分为基本表和溢出表两个部分,凡事存 在冲突的元素,一律放入到溢出表中。
阻塞队列中能够容纳的元素个数,通常情况下是有界的,比如我们实例化一 个 ArrayBlockingList,可以在构造方法中传入一个整形的数字,表示这个基于数 组的阻塞队列中能够容纳的元素个数。这种就是有界队列。
而无界队列,就是没有设置固定大小的队列,不过它并不是像我们理解的那种元素 没有任何限制,而是它的元素存储量很大,像 LinkedBlockingQueue,它的默认 队列长度是 Integer.Max_Value,所以我们感知不到它的长度限制。
无界队列存在比较大的潜在风险,如果在并发量较大的情况下,线程池中可以几乎 无限制的添加任务,容易导致内存溢出的问题!
默认提供了重试的容错机制,也就是说,如果基 于 Dubbo 进行服务间通信出现异常,服务消费者会对服务提供者集群中其他的节点发 起重试,确保这次请求成功,默认的额外重试次数是 2 次。
ConcurrentHashMap 在 JDK1.8 中的存储结构,它是由数组、 单向链表、红黑树组成。
当我们初始化一个ConcurrentHashMap实例时,默认会初始化一个长度为16的数组。 由于 ConcurrentHashMap 它的核心仍然是 hash 表,所以必然会存在 hash 冲突问题。 ConcurrentHashMap 采用链式寻址法来解决 hash 冲突。
当 hash 冲突比较多的时候,会造成链表长度较长,这种情况会使得 ConcurrentHashMap 中数据元素的查询复杂度变成 O(n)。因此在 JDK1.8 中,引入了 红黑树的机制。
当数组长度大于 64 并且链表长度大于等于 8 的时候,单项链表就会转换为红黑树。 另外,随着 ConcurrentHashMap 的动态扩容,一旦链表长度小于 8,红黑树会退化 成单向链表。
并发安全的HashMap。并发安全的主要实现是通过对指定的 Node 节点加锁,来保证数据更新的安全性(如图 所示)。
- 在 JDK1.8 中,ConcurrentHashMap 锁的粒度是数组中的某一个节点,而在 JDK1.7,锁定的是 Segment,锁的范围要更大,因此性能上会更低。
- 引入红黑树,降低了数据查询的时间复杂度,红黑树的时间复杂度是 $O(log_n)$。
- 当数组长度不够时,ConcurrentHashMap 需要对数组进行扩 容,在扩容的实现上,ConcurrentHashMap 引入了多线程并发扩容的机制, 简单来说就是多个线程对原始数组进行分片后,每个线程负责一个分片的数据 迁移,从而提升了扩容过程中数据迁移的效率。
这个是 B 树的存储结构,从 B 树上可以看到每个节点会存储数据。
这个是 B+树,B+树的所有数据是存储在叶子节点,并且叶子节点的数据 是用双向链表关联的。
B+树,其实是在 B 树的基础上做的增强,最大的区别有两个:
原因是 AVL 树的高度要比 B 树的高度要 高,而高度就意味着磁盘 IO 的数量。所以为了减少磁盘 IO 的次数,文件系统或者 数据库才会采用 B 树或者 B+树。
CAS 是 Java 中 Unsafe 类里面的方法,它的全称是CompareAndSwap,比较并交换 的意思。它的主要功能是能够保证在多线程环境下,对于共享变量的修改的原子性。
CAS 主要用在并发场景中,比较典型的使用场景有两个。
四元组,简单理解就是在 TCP 协议中,去确定一个客户端连接的组成要素,它包括源 IP 地址、目标 IP 地址、源端口号、目标端口号。
目前对于大部分企业来说,仍然是 处在第二代微服务架构下。
在第二代微服务架构中,负责业务开发的小伙伴不仅仅需要关注业务逻辑,还需要花大 量精力去处理微服务中的一些基础性配置工作,虽然 Spring Cloud 已经尽可能去完成 了这些事情,但对于开发人员来说,学习 Spring Cloud,以及针对 Spring Cloud 的 配置和维护,仍然存在较大的挑战。另外呢,也增加了整个微服务的复杂性。
实际上,在我看来,“微服务中所有的这些服务注册、容错、重试、安全等工作,都是 为了保证服务之间通信的可靠性”。
于是,就有了第三代微服务架构,Service Mesh。
原本模块化到微服务框架里的微服务基础能力,被进一步的从一个 SDK 中演 进成了一个独立的代理进程-SideCar
SideCar 的主要职责就是负责各个微服务之间的通信,承载了原本第二代微服务架构中 的服务发现、调用容错、服务治理等功能。使得微服务基础能力和业务逻辑迭代彻底解 耦。
之所以我们称 Service Mesh 为服务网格,是因为在大规模微服务架构中,每个服务的 通信都是由 SideCar 来代理的,各个服务之间的通信拓扑图,看起来就像一个网格形状
一份数据,同时保存在数据库和 Redis 里面,当数据发生变化的时候,需要同时更新 Redis 和 Mysql,由于更新是有先 后顺序的,并且它不像 Mysql 中的多表事务操作,可以满足 ACID 特性。所以就会出 现数据一致性问题。
在这种情况下,能够选择的方法只有几种。
异步重试
Canal监听
因为这里是基于最终一致性来实现的,如果业务场景不能接受数据的短期不一致性,那 就不能使用这个方案来做。
简单来说就是自动把第三方组件的 Bean 装载到 Spring IOC 器里面,不需 要开发人员再去写 Bean 的装配配置。
在 Spring Boot 应用里面,只需要在启动类加上@SpringBootApplication 注解就可 以实现自动装配。
@SpringBootApplication 是一个复合注解,真正实现自动装配的注解是 @EnableAutoConfiguration。
自动装配的实现主要依靠三个核心关键技术。
死锁,简单来说就是两个或者两个以上的线程在执行的过程中,争夺同一个共 享资源造成的相互等待的现象。
导致死锁之后,只能通过人工干预来解决,比如重启服务,或者杀掉某个线程。
provider、register、consumer
首先Dubbo 它的关注点主要在于服务的调用,流量分发、流量监控 和熔断。而 Spring Cloud 诞生于微服务架构时代,考虑的是微服务治理, 另外由于依托了 Spirng、Spirng Boot 的优势之上,两个框架在开始目标就不一致, Dubbo 定位服务治理、Spirng Cloud 是一个生态。
两者最大的区别,Dubbo 底层是使用 Netty 这样的 NIO 框架,是基于 TCP 协议传 输的,配合以 Hession 序列化完成 RPC 通信。而 SpringCloud 是基于 Http 协议 + Rest 接口调用远程过程的通信,相对来说,Http 请求会有更大的报文,占的带宽也 会更多。但是 REST 相比 RPC 更为灵活,服务提供方和调用方的依赖只依靠一纸契约, 不存在代码级别的强依赖。
三级缓存是用来存储代理 Bean,当调用 getBean()方法时,发现目标 Bean 需要通过 代理工厂来创建,此时会将创建好的实例保存到三级缓存,最终也会将赋值好的 Bean 同步到一级缓存中
set key value nx
对于 redis 的分布式锁而言,它有以下缺点:
对于 zk 分布式锁而言:
举个例子 对于 31 个节点的树来说 ,一个 5 阶 B+Tree 的高度是 3 一个红黑树的 最小高度是 5,树的高度基本决定了磁盘的 IO 次数 ,所以使用 B+Tree 性能要高 很多
B+Tree 有个特点是相邻的数据在物理上也是相邻的,因为 B+Tree 的 node 的大小设
为一个页,而一个节点上存有多个相邻的关键字和分支信息,每个节点只需要一次 IO 就能完全载入,相当于一次 IO
载入了多个相邻的关键字和分支,而红黑树不具有这个 特性,红黑树中大小相邻的数据,在物理结构上可能距离相差很大。由于程序的局部性
原理,如果我们在索引中采用了预加载的技术,每次磁盘访问的时候除了将访问到的页
加载到磁盘,我们还可以基于局部性原理加载,几页相邻的数据到内存中,而这个加载 是不需要消耗多余磁盘 IO 时间的。
通过一个入口来管理多个服务器中的docker容器
为了避免这类问题出现,我们可以直接实例化 ThreadPoolExecutor,然后自己设置参 数的值,从而确保线程池的可控性
最后redis本身的数据结构也做了很多的优化,比如压缩表、跳跃表等方式来降低时间复杂度,同时提供了不同时间复杂度的数据类型。
压缩表:哈希表压缩。当哈希表中删除了一些键值对后,Redis可能会定期对哈希表进行稀疏压缩,以释放废弃的内存。这有助于确保哈希表在删除键值对后不会浪费太多内存。
跳跃表:
利用MQ中的延时消息功能,消息发送到broker上以后不会立即消费,而是根据消息设置的延时时间去投递,我们只需要把订单和超时时间发送到MQ即可。
Mysql 中的 RR 事务隔离级别,在特定的情况下会出现幻读的问题。
来看这样一种情况,在事务 1 里面通过 update 语句触发当前读的情况下,就 会导致在该事务中的前后两次查询的数据行数不一致,从而出现幻读的现象。
导致幻读的根本原因是,update 触发的当前读操作,绕过了快照读,从而导致 MVCC 机制在当前场景下失效。 最终读取到了事务 2 中已经提交的数据。
为了避免出现这类的情况,我们可以通过 for update 语句加锁。
什么情况下会导致oom
内存泄漏
申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄漏了
内存溢出
申请的内存超过了jvm所能提供的内存大小
常见的oom异常
排查步骤
幂等
简单来说,就是一个接口,使用相同的参数重复执行的情况下,对数据造成的改变只发 生一次。
比如支付操作,如果支付接口被重复调了 N 次,那资金的扣减只发生一次,这就是幂 等。
解决方案
如果这些点有任何一个 0,则被检查的元素一定不在;如果都是 1,则被检查的元素很 可能存在。
类的加载机制
类的加载机制,就是我们自己写的java源文件到运行必须要经过编译和类加载两个阶段。
编译:Java—>class
类加载:将class装载到jvm内存中,装载完成后就会得到一个class对象,我们就可以使用new关键字来实例化这个对象。
而类的加载过程,需要涉及到类加载器。
JVM 在运行的时候,会产生 3 个类加载器,这三个类加载器组成了一个层级关系 每个类加载器分别去加载不同作用范围的 jar 包
除了系统自己提供的类加载器以外,还可以通过 ClassLoader 类实现自定义加载器,去 满足一些特殊场景的需求。
双亲委派模型
就是按照类加载器的层级关系,逐层进行委派。比如当需要执行一个String.class文件的时候,首先会把这个class的查询和加载委派给父加载器去执行,如果父加载器都无法加载,再尝试自己来加载这个class。
好处
Mybatis 里面设计的二级缓存是用来提升数据的检索效率,避免每次数据的访 问都需要去查询数据库。
一级缓存的实现原理
在 SqlSession 里面持有一个 Executor,每个 Executor 中有一个 LocalCache 对象。 当用户发起查询的时候,Mybatis 会根据执行语句在 Local Cache 里面查询,如果没命 中,再去查询数据库并写入到 LocalCache,否则直接返回。 所以,以及缓存的生命周期是 SqlSessiion,而且在多个 Sqlsession 或者分布式环境下, 可能会导致数据库写操作出现脏数据。
二级缓存的实现原理
使用 CachingExecutor 装饰了 Executor,所以在进入一级缓存的查询流程之前,会先 通过 CachingExecutor 进行二级缓存的查询。
开启二级缓存以后,会被多个 SqlSession 共享,所以它是一个全局缓存。因此它的查 询流程是先查二级缓存,再查一级缓存,最后再查数据库。 另外,MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据 的共享,同时缓存粒度也能够到 namespace 级别,并且还可以通过 Cache 接口实 现类不同的组合,对 Cache 的可控性也更强。
解决办法
为了避免追加的方式导致 AOF 文件过大的问题,Redis 提供了 AOF 重写机制,也就是说当 AOF 文件的大小达到某个阈值的时候,就会把这个文件里面相同的指令进 行压缩。
举个例子,JVM 垃圾回收线程就是一个典型的守护线程,它存在的意义是不断的处理 用户线程运行过程中产生的内存垃圾。
一旦用户线程全部结束了,那垃圾回收线程也就没有存在的意义了。
所以它它适合用在一些后台的通用服务场景里面。
双向链表的优势
AQS采用双向链表的原因
存储在双向链表中的线程,有可能这个线程出现异常不再需要竞争锁,所以需要把 这些异常节点从链表中删除,而删除操作需要找到这个节点的前驱结点,如果不采 用双向链表,就必须要从头节点开始遍历,时间复杂度就变成了 O(n)。
新加入到链表中的线程,在进入到阻塞状态之前,需要判断前驱节点的状态,只有 前驱节点是 Sign 状态的时候才会让当前线程阻塞,所以这里也会涉及到前驱节点 的查找,采用双向链表能够更好的提升查找效率
线程在加入到链表中后,会通过自旋的方式去尝试竞争锁来提升性能,在自旋竞争 锁的时候为了保证锁竞争的公平性,需要先判断当前线程所在节点的前驱节点是否 是头节点。这个判断也需要获取当前节点的前驱节点,同样采用双向链表能提高查 找效率。
select id,name from user where id>1000000 limit10
这种方式其实就是先对数据做过滤,然后再 limit,可以有效提升查询效率
select id,name from user order by id limit 1000000,10
需要注意 id 是索引列,通过索引排序后再 limit,同样减少了计算次数
主从数据同步涉及到网络数据传输,由于网络通信的延迟以及从库数据处理的效率问 题,就会导致主从数据同步延迟的情况。
解决办法
缺点
索引类型
有一张 200W 数据量的会员表,每个会员会有长短不一的到期时间,现在想在快到 期之前发送邮件通知提醒续费,如何实现
- 200w的数据量
- 如何快速筛选快过期的会员
解决办法
还是取决于 JDK 动态代理的底层实现。
JDK 动态代理会在程序运行期间动态生成一个代理类$Proxy0,这个动态生成的代理类 会继承 java.lang.reflect.Proxy 类,同时还会实现被代理类的接口 IHelloService。 在 Java 中,是不支持多重继承的。而每个动态代理类都会继承 Proxy 类(这也是 JDK 动态代理的实现规范),所以就导致 JDK 里面的动态代理只能代理接口,而不能代理 实现类。
Redis 里面的内存淘汰策略,是指内存的使用率达到 maxmemory 上限的时候的一种 内存释放的行为。
Redlock实现
简单来说,就是利用多个的主节点,在超过半数以上的主节点获取锁成功,才算成功;否则算失败,回滚–删除之前在所有节点上获取的锁。
Dubbo 服务消费端会使用 Zookeeper 里面的 Watch 来针对 Zookeeper Server 端的/providers 节点注册监听,一旦这个节点下的子节点发生变化,Zookeeper Server 就会发送一个事件通知 Dubbo Client 端。Dubbo Client 端收到事件以后,就会把本地缓存的这个服务地址删除,这样后续就不 会把请求发送到失败的节点上,完成服务下线感知。
Zookeeper 提供了一个 Watch 机制,可以让客户端感知到 Zookeeper Server 上存储的数据变化,这样一种机制可以让 Zookeeper 实现很多的场景,比如配置中心、 注册中心等。
Watch 机制采用了 Push 的方式来实现,也就是说客户端和 Zookeeper Server 会建立 一个长连接,一旦监听的指定节点发生了变化,就会通过这个长连接把变化的事件推送 给客户端。
连接池的核心思想,就是应用程序在启动的时候提前初始化一部分连接保存 到连接池里面,当应用需要使用连接的时候,直接从连接池获取一个已经建立好的链接。 连接池的设计,避免了每次连接的建立和释放带来的开销
滑动窗口限流,本质上也是一种计数器,只是通过以时间为维度的可滑动 窗口设计,来减少了临界值带来的并发超过阈值的问题
每次进行数据统计的时候,只需要统计这个窗口内每个时间刻度的访问量就可以了。
Spring Cloud里面的熔断框架Hystrix ,以及Spring Cloud Alibaba里面的Sentinel 都采用了滑动窗口来做数据统计
TCP 协议,是一种可靠的,基于字节流的,面向连接的传输层协议
偏向锁
就是直接把当前锁偏向于某个线程,简单来说就是通过CAS 修改偏向锁标记,这种锁适合同一个线程多次去申请同一个锁资源并且没有其他线程竞争的场景。
轻量级锁也可以称为自旋锁
基于自适应自旋的机制,通过多次自旋重试去竞争锁。自旋锁优点在于它避免避免了用户态到内核态的切换带来的性能开销。
id 这个属性表示一个Bean 的唯一标志符号,所以Spring 在启动的时候会去验证id 的唯一性,一旦发现重复就会报错
这个错误发生Spring 对XML 文件进行解析转化为BeanDefinition 的阶段。
但是在两个不同的Spring 配置文件里面,可以存在id 相同的两个bean。IOC 容器在加载Bean 的时候,默认会多个相同id的bean 进行覆盖。
在Spring3.x 版本以后,这个问题发生了变化我们知道Spring3.x 里面提供@Configuration 注解去声明一个配置类,然后使用
@Bean 注解实现Bean 的声明,这种方式完全取代了XMl。在这种情况下,如果我们在同一个配置类里面声明多个相同名字的bean,在Spring IOC 容器中只会注册第一个声明的Bean 的实例。后续重复名字的Bean 就不会再注册了。
像这样一段代码,在Spring IOC 容器里面,只会保存UserService01 这个实例,后续相同名字的实例不会再加载。
如果使用@Autowired 注解根据类型实现依赖注入,因为IOC 容器只有UserService01 的实例,所以启动的时候会提示找不到UserService02 这个实例。
如果使用@Resource 注解根据名词实现依赖注入,在IOC 容器里面得到的实例对象是UserService01,于是Spring 把UserService01 这个实例赋值给UserService02,就会提示类型不匹配错误。
这个错误,是在Spring IOC 容器里面的Bean 初始化之后的依赖注入阶段发生的。
Kafka 为了保证 Parititon 的可靠性,提供了 Paritition 的副本机制。生产者发送过来的消息,会先存到 Leader Partition 里面,然后再把消息复制到 Follower Partition,这样设计的好处就是一旦 Leader Partition 所在的节点挂了,可以重新从剩余的 Partition 副本里面选举出新的 Leader。然后消费者可以继续从新的 Leader Partition 里面获取未消费的数据。
在 Partition 多副本设计的方案里面,有两个很关键的需求。
这两个需求都需要涉及到网络通信,Kafka 为了避免网络通信延迟带来的性能问题, 以及尽可能的保证新选举出来的 Leader Partition 里面的数据是最新的,所以设计了ISR这样一个方案。
它是一个集合列表,里面保存的是和 Leader Parition 节 点数据最接近的 Follower Partition。如果某个 Follower Partition 里面的数据落后 Leader 太多,就会被剔除 ISR 列表。简单来说,ISR 列表里面的节点,同步的数据一定是最新的,所以后续的 Leader 选举, 只需要从 ISR 列表里面筛选就行了。
好处
首先,之所以需要序列化,核心目的是为了解决网络通信之间的对象传输问题。也就是说如何把当前jvm进程里面的一个对象,跨网络传输到另一个jvm进程里面。
而序列化就是把内存中的对象转化为字节流,用来实现存储或者传输。那么反序列化就是根据从文件或者网络上获取到的对象的字节流,根据字节流里面保存的对象信息重新构建一个对象。
CompletableFuture 是 JDK1.8 里面引入的一个基于事件驱动的异步回调类。简单来说,就是当使用异步线程去执行一个任务的时候,我们希望在任务结束以后触发 一个后续的动作。而 CompletableFuture 就可以实现这个功能。
举个简单的例子:
先查询到订单以后,再针对这个订单发起 支付,支付成功以后再发送邮件通知。
CompletableFuture 提供了 5 种不同的方式,把多个异步任务组成一个具有先后关系 的处理链,然后基于事件驱动任务链的执行。
thenCombine:把两个任务组合在一起,当两个任务都执行结 束以后触发事件回调
thenCompose:把两个任务组合在一起,这两个任务串行执行, 也就是第一个任务执行完以后自动触发执行第二个任务。
thenAccept:第一个任务执行结束后触发第二个任务, 并且第一个任务的执行结果作为第二个任务的参数,这个方法是纯粹接受上一个任务的 结果,不返回新的计算值。
thenApply:和 thenAccept 一样,但是它有返回值
thenRun:就是第一个任务执行完成后触发执行一个实现了 Runnable 接口的任务。
Spring 里面的事务,本质上就是数据库层面的事务。
在 Spring 里面并没有提供事务,它只是提供了对数据库事务管理的封装。 通过声明式的事务配置,使得开发人员可以从一些复杂的事务处理中得到解脱, 我们不再需要关心连接的获取、连接的关闭、事务提交、事务回滚这些操作。 更加聚焦在业务开发层面
Spring 事务和分布式事务在使用 上并没有直接的关联性。
但是我们可以使用一些主流的事务解决框架,比如 Seata,集成到 Spring 生态里面, 去解决分布式事务的问题。
Cookie
它是客户端浏览器用来保存服务端数据的一种机制。当通过浏览器进行网页访问的时候,服务器可以把某一些状态数据以 key-value 的方式 写入到 Cookie 里面存储到客户端浏览器。然后客户端下一次再访问服务器的时候,就可以携带这些状态数据发送到服务器端,服 务端可以根据 Cookie 里面携带的内容来识别使用者。
Session
Session 表示一个会话,它是属于服务器端的容器对象,默认情况下,针对每一个浏览器的请求。Servlet 容器都会分配一个 Session。Session 本质上是一个 ConcurrentHashMap,可以存储当前会话产生的一些状态数 据。
我们都知道,Http 协议本身是一个无状态协议,也就是服务器并不知道客户端发送过 来的多次请求是属于同一个用户。所以 Session 是用来弥补 Http 无状态的不足,简单来说,服务器端可以利用 session 来存储客户端在同一个会话里面的多次请求记录。
一般的解决办法就是自定义消息分区路由的算法,然后把指定的 key 都发送到同一个 Partition 里面。 接着指定一个消费者专门来消费某个分区的数据,这样就能保证消息的顺序消费了。
秒杀系统的核心:
一个 Java 对象在 JVM 内存中的布局由三个部分组成, 分别是对象头、实例数据、对齐填充。而对象头里面有 4 个 bit 位来存储 GC 年龄。
而 4 个 bit 位能够存储的最大数值是 15,所以从这个角度来说,JVM 分代年龄之所以 设置成 15 次是因为它最大能够存储的数值就是 15。
ArrayList 是一个数组结构的存储容器,默认情况下,数组的长度是 10. 当然我们也可以在构建 ArrayList 对象的时候自己指定初始长度。 随着在程序里面不断的往 ArrayList 中添加数据,当添加的数据达到 10 个的时候, ArrayList 就没有多余容量可以存储后续的数据。 这个时候 ArrayList 会自动触发扩容。 扩容的具体流程很简单, 1. 首先,创建一个新的数组,这个新数组的长度是原来数组长度的 1.5 倍。 2. 然后使用 Arrays.copyOf 方法把老数组里面的数据拷贝到新的数组里面。 扩容完成后再把当前要添加的元素加入到新的数组里面,从而完成动态扩容的过程。
Eureka 是一个服务注册中心,在 Eureka 的设计里面,为了保证 Eureka 的高可用性, 提供了集群的部署方式。
Eureka 的集群部署采用的是两两相互注册的方式来实现,也就是说每个 Eureka Server 节点都需要发现集群中的其他节点并建立连接,然后通过心跳的方式来维持这个连接的状态。
在 Eureka Server 集群中,不存在所谓主从节点,任何一个节点都可以接收 或者写入数据。 一旦集群中的任意一个节点接收到了数据的变更,就直接同步到其他节点上。
这种无中心化节点的数据同步,需要考虑到一个数据同步死循环的问题,也就是需要区 分 Eureka Server 收到的数据 是属于客户端传递来的数据还是集群中其他节点发过来的同步数据。 Eureka 使用了一个时间戳的标记来实现类似于数据的版本号来解决这个问题。
另外,从 Eureka 的数据同步方案来看,Eureka 集群采用的是 AP 模型,也就是只提供 高可用保障,而不提供数据强一致性保障。 之所以采用 AP,我认为注册中心它只是维护服务之间的通信地址,数据是否一致对于 服务之间的通信影响并不大。 而注册中心对 Eureka 的高可用性要求会比较高,不能出现因为 Eureka 的故障导致服 务之间无法通信的问题。
其实微服务架构本身就是一种分布式架构,它强调的是对部署在各个计算机上的应用服 务的粒度。
拆分的好处是使得程序的扩展性更强,开发迭代效率更高。 对于一些大型的互联网项目来说,微服务能够在不影响用户使用的情况下非常方便的实 现产品功能的创新和上线。
简单来说,就是在一个大的数组上,定义一个固定长度的滑动窗口,然后这个 窗口在数组上进行滑动。
在窗口滑动的过程中,左边会出一个元素,右边会进一个元素, 最后,我们根据当前 窗口内记录的数据进行计算。 从而达到数据统计的目的。
ConcurrentHashMap 的 size()方法是非线程安全的。
size()方法是一个非同步方法。put()方法和 size()方法并没有实现同步锁。
这里用到了 CAS 的方式解决了并发更新问题,因此从 size()方法本身来看,它的整个计算过程是线程安全的。但是站在 ConcurrentHashMap 全局角度来看,put()方法和 size()方法之间的数据是 不一致的,因此也就不是线程安全的。
之所以不像 HashTable 那样,直接在方法级别加同步锁。在我看来有两个考虑点。
Nacos 是采用长轮训的方式向 Nacos Server 端发起配置更新查询的功能。 所谓长轮训,就是客户端发起一次轮训请求到服务端,当服务端配置没有任何 变更的时候,这个连接一直打开。 直到服务端有配置或者连接超时后返回。
Nacos Client 端需要获取服务端变更的配置,前提是要有一个比较, 也就是拿客户端本地的配置信息和服务端的配置信息进行比较。 一旦发现和服务端的配置有差异,就表示服务端配置有更新,于是把更新的配置拉到本 地。
在这个过程中,有可能因为客户端配置比较多,导致比较的时间较长,使得配置同步较 慢的问题。于是 Nacos 针对这个场景,做了两个方面的优化。
AMQP 的具体工作机制是,生产者把消息发送到 RabbitMQ Broker 上的 Exchange 交换机上。 Exchange 交换机把收到的消息根据路由规则发给绑定的队列(Queue)。 最后再把消息投递给订阅了这个队列的消费者,从而完成消息的异步通讯。
其中,Exchange 是一个消息交换机,它里面定义了消息路由的规则,也就是这个消息 路由到那个队列。
然后 Queue 表示消息的载体,每个消息可以根据路由规则路由到一个或者多个队列里 面。
而关于消息的路由机制,核心的组件是 Exchange。它负责接收生产者的消息然后把消息路由到消息队列,而消息的路由规则由 ExchangeType 和 Binding 决定。
Binding 表示建立 Queue 和 Exchange 之间的绑定关系,每一个绑定关系会 存在一个 BindingKey。
生产者发送消息的时候,需要声明一个 routingKey(路由键),Exchange 拿到 routingKey 之后, 根据 RoutingKey 和路由表里面的 BindingKey 进行匹配,而匹配的规则是通过 ExchangeType 来决定的。
在 RabbitMQ 中,有三种类型的 Exchange:direct ,fanout 和 topic。
首先,在 RabbitMQ 的整个消息传递过程中,有三种情况会存在丢失。
生产者发送消息的角度
RabbitMQ 提供了一个 Confirm(消息确认)机制, 生产者发送消息到 Server 端以后,如果消息处理成功,Server 端会返回一个 ack 消息。 客户端可以根据消息的处理结果来决定是否要做消息的重新发送,从而确保消息一定到 达 RabbitMQ Server 上。
RabbitMQ Server角度
开启消息的持久化机制。
消费端的角度
自动确认机制修改成手动确认。这种方式可能会造成重复消费问题,所以这里需要考虑到幂等性的设计。
Reactor 其实是在 NIO 多路复用的基础上提出的一个高性能 IO 设计模式。核心思想是把响应 IO 事件和业务处理进行分离,通过一个或者多个线程来处理 IO 事件。然后把就绪的事件分发给业务线程进行异步处理。
核心目的是为了让 hash 值的散列 度更高, 尽可能减少 hash 表的 hash 冲突,从而提升数据查找的性能
最终目的是为了解决 幻读的问题。
CPU 利用率过高或者 CPU 利用率过低,都会影响程序的处理效率。
loadFactor 的默认值是 0.75,capacity 的默认值是 16,也就是元素个数达到 12 的时 候触发扩容。 扩容后的大小是原来的 2 倍。
默认情况下,线程池只会回收非核心线程,非核心线程是为了解决任务过多的时候临时增加的,所以当任务处理完成后, 工作线程处于空闲状态的时候,就需要回收。
因为所有工作线程都是从阻塞队列中去获取要执行的任务,所以只要在一定时间内, 阻塞队列没有任何可以处理的任务,那这个线程就可以结束了。 这个功能是通过阻塞队列里面的 poll 方法来完成的。这个方法提供了超时时间和超时 时间单位这两个参数 当超过指定时间没有获取到任务的时候,poll 方法返回 null,从而终止当前线程完成线 程回收。
如果希望核心线程也要回收,可以 设置 allowCoreThreadTimeOut 这个属性为 true,一般情况下我们不会去回收核心线 程。因为线程池本身就是实现线程的复用,而且这些核心线程在没有任务要处理的时候是处 于阻塞状态 并没有占用 CPU 资源。
缓存击穿,表示请求因为某些原因全部打到了数据库,缓存并没有起到流量缓冲的作用。 我认为有 2 种情况会导致缓存击穿。
解决办法
IO 多路复用机制,核心思想是让单个线程去监视多个连接,一旦某个连接就绪,也就 是触发了读/写事件。 就通知应用程序,去获取这个就绪的连接进行读写操作。 也就是在应用程序里面可以使用单个线程同时处理多个客户端连接, 在对系统资源消耗较少的情况下提升服务端的链接处理数量。 在 IO 多路复用机制的实现原理中, 客户端请求到服务端后,此时客户端在传输数据过程中(如图), 为了避免 Server 端在 read 客户端数据过程中阻塞,服务端会把该请求注册到 Selector 复路器上, 服务端此时不需要等待,只需要启动一个线程, 通过 selector.select()阻塞轮询复路器上就绪的 channel 即可, 也就是说,如果某个客户端连接数据传输完成, 那么 select()方法会返回就绪的 channel,然后执行相关的处理就可以了。
NIO 里面提供的 NIO transferTo 和 transfFrom 方法,也就是常说的零拷贝实现。 它能够利用现代操作系统底层机制,避免不必要拷贝和上下文切换,因此在性能上表现 比较好。
基于阻塞队列的特性,使得阻塞队列中如果没有任务的时候,这些工作线程就会阻塞等 待。直到又有新的任务进来,这些工作线程再次被唤醒。
对于阻塞队列的消费过程
SynchronousQueue
SimpleDateFormat 不是线程安全的
SimpleDateFormat 类内部有一个 Calendar 对象引用, 它用来储存和这个 SimpleDateFormat 相关的日期信息。多个线程对于同一个 Calendar 的操作,会出现数据脏读现象导致一些不可预料的错误。
解决办法
从定位上来说,
因此,这两个协议在定位层面就完全不同。
其次,从原理上来说,
最后,从应用层面来说
在 TCC 这个事务解决方案里面,除了悬挂问题以外,还有空回滚、幂等性需 要考虑。
什么是TCC
所谓 TCC,其实就是(Try-Confirm-Cancel),也就是把一个事务拆分成两个阶段, 类似于传统的 XA 事务模型。
很显然,这是一个最终一致性的实现方案,因此当 Try 执行成功,就必须确保 Confirm 执行成功。 当 Try 执行失败,就必须确保 Cancel 实现资源释放。
悬挂问题
指的是 TCC 执行 Try 接口出现网络超时时候,使得 TCC 触 发 Cancel 接口回滚,但可能在回滚之后,这个超时的 Try 接口才被真正执行,也就导致 Cancel 接口比 Try 接口先执行。从而造成 Try 接口预留的资源一直无法释放,这种情况就是悬挂。
解决办法
只需要保证 Cancel 接口执行完以后,Try 接口不允许在执行就 可以了。所以,我们可以在 Try 接口里面,先判断 Cancel 接口有没有执行过,如果已经执行过, 就不再执行。
是否执行过的这个判断,可以在事务控制表里面插入一条事务控制记录来标记这个事务 的回滚状态。然后在 Try 接口中只需要读取这个状态来判断就行了。
令牌桶是一种能够处理突发流量的限流算法,系统以恒定速率向令牌桶 里面添加令牌,然后每个请求都需要从令牌桶去获取令牌才能访问,如果获取不到,就会触发限流。
为了避免在多线程并发场景下的歧义问题。当一个线程从 ConcurrentHashMap 获取某个 key,如果返回的结果是 null 的时候。 这个线程无法确认,这个 null 表示的是确实不存在这个 key,还是说存在 key,但是 value 为空。 这种不确定性会造成线程安全性问题,而 ConcurrentHashMap 本身又是一个线程安 全的集合。
多线程、克隆、反序列化、反射,都有可能会造成单例的破坏。 而我认为,通过枚举的方式实现单例,是能够解决所有可能被破坏的情况
MyISAm 和 InnoDB 的区别有 4 个,
我认为应该使用 Char 类型,原因是: char 类型是固定长度的字符串,varchar 是可变长度字符串。 而 MD5 是一个固定长度的字符,不管数据怎么修改,长度不变,这个点很符合 char 类型。 另外,由于是固定长度,所以在数据变更的时候,不需要去调整存储空间大小,在效率 上会比 varchar 好。
@Conditional 注解的作用是为 Bean 的装载提供了一个条件判断。只有满足条件的情况下,Spring 才会把当前 Bean 装载到 IOC 容器中。 这个条件的实现逻辑,我们可以实现 Condition 接口并重写 matches 方法自己去实现。 所以@Conditional 注解增加了 Bean 装载的灵活性。
在 Spring Boot 里面,对@Conditional 注解做了更进一步的扩展,比如增加了 @ConditionalOnClass、@ConditionalOnBean
等注解,使得我们在使用的过程中不再需要去写条件的逻辑。
Redis 主从复制包括全量复制和增量复制。
全量复制
全量复制是发生在初始化阶段,从节点会主动向主节点发起一个同步请求,主节点收到 请求后会 会生成一份当前数据的快照发送给从节点,从节点收到数据进行加载后完成全量复制。 增量复制是发生在每次 Master 数据发生变化的过程中,会把变化的数据同步给所有的 从节点。
增量复制
增量复制是通过维护 Offset 这个复制偏移量来实现的
策略模式和观察者模式属于行为型模式。
策略模式主要是用在根据上下文动态控制类的行为的场景,
观察者模式主要用在一对多的对象依赖关系的中,实现某一个对象状态变更之后的感知 的场景
Java 反射的优点: 增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作 提高代码的复用率,比如动态代理,就是用到了反射来实现 可以在运行时轻松获取任意一个类的方法、属性,并且还能通过反射进行动态调用 Java 反射的缺点: 反射会涉及到动态类型的解析,所以 JVM 无法对这些代码进行优化,导致性能要 比非反射调用更低。 使用反射以后,代码的可读性会下降 反射可以绕过一些限制访问的属性或者方法,可能会导致破坏了代码本身的抽象性。
对称加密
指加密和解密使用同一密钥,优点是运算速度较快,常见的对称加密算 法有:DES、AES 等
非对称加密
指的是加密和解密使用不同的密钥,也称为公钥和私钥。公钥与私钥是成 对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。反之,如果采用私 钥加密,只能使用对应的公钥才能解密,RSA 是比较常见的非对称加密算法。
在 Kafka 中,除了 Partition 分区副本的 Leader 选举以外,还有 Kafka 集群本身 的 Leader 选举,这两个不是同一个东西
Kafka 首先会选择一个具有最新数据的副本作为新的 Leader,也就是 ISR 集合中的副 本。其中,ISR(In-Sync Replica)是指与 Leader 同步的副本集合,它们的数据同步状态 与 Leader 最接近, 并且它们与 Leader 副本的网络通信延迟最小。 如果 ISR 集合中没有可用的副本,Kafka 会从所有副本中选择一个具有最新数据的副本 作为新的 Leader。 在这种情况下选举出来的 Leader,由于和原来老的 Leader 节点的数据存在较大的延 迟,会造成数据丢失的情况
CORS
它的工作原理很简单。 如果一个网站需要访问另一个网站的资源,浏览器会先发送一个 OPTIONS 请求, 根据服务器返回的 Access-Control-Allow-Origin 头信息,决定是否允许跨域访问。 所以,我们只需要在服务器端配置 Access-Control-Allow-Origin 属性,并配置允许 哪些域名支持跨域请求即可。
在 Spring 中,只有有状态的单例 Bean 才会存在线程安全 问题。
处理有状态单例 Bean 的线程安全问题有以下三种方法:
1、将 Bean 的作用域由 “singleton” 单例 改为 “prototype” 多例。
2、在 Bean 对象中避免定义可变的成员变量,当然,这样做不太现实,就当我没说。
3、在类中定义 ThreadLocal 的成员变量,并将需要的可变成员变量保存在 ThreadLocal 中,ThreadLocal 本身就具备线程隔离的特性,这就相当于为每个线 程提供了一个独立的变量副本,每个线程只需要操作自己的线程副本变量,从而解决线 程安全问题
文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文
文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作 导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释: cwy_init/init_123..._达梦数据库导入导出
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure
文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf