Java基础整理

基础

原码反码补码的相互转换

数据范围

二叉树

Map

HashMap
HashMap的底层数据结构
数组和链表组合构成的数据结构,
HashMap的存取原理
数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry在Java8中叫Node。
他本身所有的位置都为null,在put插入的时候会根据key的hash去计算一个index值。
HashMap是通过key的hashCode去寻找index的,那index一样就形成链表了
get的时候,他就是根据key去hash然后计算出index,
在Java7和Java8的区别
java8之前是头插法,就是说新来的值会取代原有的值,原有的值就顺推到链表中去,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率
在java8之后,都是所用尾部插入了。
为啥改为尾部插入
使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

Java7在多线程操作HashMap时可能引起死循环,原因是扩容转移后前后链表顺序倒置,在转移过程中修改了原来链表中节点的引用关系。

Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
Java8就可以把HashMap用在多线程
put/get方法都没有加同步锁,多线程情况最容易出现的就是:无法保证上一秒put的值,下一秒get的时候还是原值,所以线程安全还是无法保证。
为啥重写equals方法的时候需要重写hashCode方法,能用HashMap举个例子么
在java中,所有的对象都是继承于Object类。Ojbect类中有两个方法equals、hashCode,这两个方法都是用来比较两个对象是否相等的。
在未重写equals方法我们是继承了object的equals方法,那里的 equals是比较两个对象的内存地址,显然我们new了2个对象内存地址肯定不一样
对于值对象,==比较的是两个对象的值
对于引用对象,比较的是两个对象的地址

HashMap是通过key的hashCode去寻找index的,那index一样就形成链表了
get的时候,他就是根据key去hash然后计算出index,
equals方法进行了重写,建议一定要对hashCode方法重写,以保证相同的对象返回相同的hash值,不同的对象返回不同的hash值
默认初始化大小是多少?为啥是这么多?为啥大小都是2的幂?
16,创建HashMap的时候,阿里巴巴规范插件会提醒我们最好赋初值,而且最好是2的幂,为了位运算的方便,位与运算比算数计算的效率高了很多,之所以选择16,是为了服务将Key映射到index的算法
因为在使用是2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。
只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。这是为了实现均匀分布
HashMap的扩容方式?负载因子是多少?为什是这么多
扩容:创建一个新的Entry空数组,长度是原数组的2倍。
ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
是因为长度扩大以后,Hash的规则也随之改变。
怎么处理HashMap在线程安全的场景
一般在多线程的场景,我都会使用好几种不同的方式去代替:
使用Collections.synchronizedMap(Map)创建线程安全的map集合;
Hashtable
ConcurrentHashMap
不过出于线程并发度的原因,我都会舍弃前两者使用最后的ConcurrentHashMap,他的性能和效率明显高于前两者。
Collections.synchronizedMap是怎么实现线程安全的
在SynchronizedMap内部维护了一个普通对象Map,还有排斥锁mutex,在调用这个方法的时候就需要传入一个Map,可以看到有两个构造器,如果你传入了mutex参数,则将对象排斥锁赋值为传入的对象。

如果没有,则将对象排斥锁赋值为this,即调用synchronizedMap的对象,就是上面的Map。

创建出synchronizedMap之后,再操作map的时候,就会对方法上锁
Hashmap中的链表大小超过八个时会自动转化为红黑树,当删除小于六时重新变为链表,为啥呢?
在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。
ConcurrentHashMap
ConcurrentHashMap的并发度不够,性能很低,怎么处理
在开发过程中都是使用ConcurrentHashMap,他的并发的相比前两者好很多。
ConcurrentHashMap的数据结构,以及为啥并发度这么高
1.7数据结构
HashMap 底层是基于 数组 + 链表 组成的,不过在 jdk1.7 和 1.8 中具体实现稍有不同。
1.7中的数据结构:
是由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表。
Segment 是 ConcurrentHashMap 的一个内部类,
HashEntry跟HashMap差不多的,但是不同点是,他使用volatile去修饰了他的数据Value还有下一个节点next。
volatile的特性是
保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)

禁止进行指令重排序。(实现有序性)

volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
并发度高
原理上来说,ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。

不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。

每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。

就是说如果容量大小是16他的并发度就是16,可以同时允许16个线程操作16个Segment而且还是线程安全的。
put
他先定位到Segment,然后再进行put操作。
首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。

尝试自旋获取锁。

如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。
get
get 逻辑比较简单,只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。

由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁。
1.7虽然可以支持每个Segment并发访问,但是还是存在一些问题
基本上还是数组加链表的方式,我们去查询的时候,还得遍历链表,会导致效率很低,这个跟jdk1.7的HashMap是存在的一样问题,所以他在jdk1.8完全优化了。
jdk1.8数据结构
其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

跟HashMap很像,也把之前的HashEntry改成了Node,但是作用不变,把值和next采用了volatile去修饰,保证了可见性,并且也引入了红黑树,在链表大于一定值的时候会转换(默认是8)。

值的存取操作么?以及是怎么保证线程安全的

put
ConcurrentHashMap在进行put操作的还是比较复杂的,大致可以分为以下步骤:

根据 key 计算出 hashcode 。

判断是否需要进行初始化。

即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。

如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。

如果都不满足,则利用 synchronized 锁写入数据。

如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
CAS是什么?自旋又是什么
CAS 是乐观锁的一种实现方式,是一种轻量级锁,JUC 中很多工具类的实现就是基于 CAS 的。
CAS 操作的流程如下图所示,线程在读取数据时不进行加锁,在准备写回数据时,比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。

这是一种乐观策略,认为并发操作并不总会发生。
CAS就一定能保证数据没被别的线程修改过么
并不是的,比如很经典的ABA问题,CAS就无法判断了。
ABA
来了一个线程把值改回了B,又来了一个线程把值又改回了A,对于这个时候判断的线程,就发现他的值还是A,所以他就不知道这个值到底有没有被人改过,其实很多场景如果只追求最后结果正确,这是没关系的。

但是实际过程中还是需要记录修改过程的,比如资金修改什么的,你每次修改的都应该有记录,方便回溯。
怎么解决ABA问题?
用版本号去保证就好了,就比如说,我在修改前去查询他原来的值的时候再带一个版本号,每次判断就连值和版本号一起判断,判断成功就给版本号加1。
时间戳也可以,查询的时候把时间戳一起查出来,对的上才修改并且更新值的时候一起修改更新时间,这样也能保证
CAS性能很高,但是我知道synchronized性能可不咋地,为啥jdk1.8升级之后反而多了synchronized?
synchronized之前一直都是重量级的锁,但是后来java官方是对他进行过升级的,他现在采用的是锁升级的方式去做的。

针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。

所以是一步步升级上去的,最初也是通过很多轻量级的方式锁定的。

get

根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。

如果是红黑树那就按照树的方式获取值。

就不满足那就按照链表的方式遍历获取值。
Hashtable
跟HashMap相比Hashtable是线程安全的,适合在多线程的情况下使用,但是效率可不太乐观。
我看过他的源码,他在对数据操作的时候都会上锁,所以效率比较低下。
Hashtable 跟HashMap不一样点
Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。
实现方式不同:Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类。
初始化容量不同:HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
扩容机制不同:当现有容量大于总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1。
迭代器不同:HashMap 中的 Iterator 迭代器是 fail-fast 的,而 Hashtable 的 Enumerator 不是 fail-fast 的。
当其他线程改变了HashMap 的结构,如:增加、删除元素,将会抛出ConcurrentModificationException 异常,而 Hashtable 则不会
为啥 Hashtable 是不允许 KEY 和 VALUE 为 null, 而 HashMap 则可以
Hashtable在我们put 空值的时候会直接抛空指针异常,但是HashMap却做了特殊处理。
因为Hashtable使用的是安全失败机制(fail-safe),这种机制会使你此次读到的数据不一定是最新的数据。

如果你使用null值,就会使得其无法判断对应的key是不存在还是为空,因为你无法再调用一次contain(key)来对key是否存在进行判断,ConcurrentHashMap同理。
fail-fast是啥?
快速失败(fail—fast)是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
fail-fast原理:
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。

集合在被遍历期间如果内容发生变化,就会改变modCount的值。

每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。

因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。
fail-fast场景:
ava.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)算是一种安全机制吧。

Tip:安全失败(fail—safe)大家也可以了解下,java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

List

ArrayList
ArrayList是一个什么东西,可以用来干嘛

JVM

数据类型:

基本类型和引用类型。
基本类型包括,数值类型,boolean类型,和returnAddress类型。
数值类型包括,整型,浮点型,和char类型。
boolean类型同样只有true和false。
returnAddress类型是一个指针,指向jvm指令的操作码,在Java中没有与之对应的类型。
boolean类型的操作会被转化为int类型的操作进行,boolean数组会当成byte数组去操作。1表示true,0表示false。
引用类型包括三种,类类型,数组类型,和接口类型。
它们的值是动态创建的类实例,数组,或实现接口的类实例。

数组有component类型和element类型,component类型就是数组去掉最外层维度后剩下的类型,可能还是一个数组类型(对于多维数组)。

element类型就是数组里面存储的最小数据的类型,它必须是一个基本类型,类类型,或接口类型。

对于一维数组的话,component类型和element类型是相同的。

引用类型还有一个特殊值,就是null,表示没有引用任何对象。

运行时公有数据区

jvm有一个堆,在所有jvm线程间共享,堆是一个运行时数据区域,所有为类实例和数组分配的内存都来自于它。
堆在jvm启动时创建,堆中对象不用显式释放,gc会帮我们释放并回收内存。
方法区
jvm有一个方法区,在所有jvm线程间共享,它存储每一个类的结构。

像运行时常量池,字段和方法数据,方法和构造函数的代码,还有特殊的方法用于类和实例的初始化,以及接口的初始化。

方法区在jvm启动时创建,虽然方法区在逻辑上是堆的一部分。

但简单实现时可以选择不进行gc和压缩,本规范没有强制要求方法区的位置,也没有要求管理已编译代码的策略。
运行时常量池
运行时常量池就是类或接口的字节码文件里的常量池的运行时表示形式,它包含几种常量。

如在编译时就已经知道的数字字面量值,和必须在运行时解析的方法和字段的引用,运行时常量池的功能类似于传统语言的符号表,不过它包含的数据会更加宽泛。

运行时常量池分配在jvm的方法区,类或接口的运行时常量池在类或接口被jvm创建时才会构建。

运行时私有数据区

pc寄存器
jvm支持一次运行多个线程,每个线程都有自己的pc寄存器,任何时候一个线程只能运行一个方法的代码。

如果方法不是native的,pc寄存器包含当前正在被执行的jvm指令地址,如果方法是native的,pc寄存器的值是未定义的。
jvm栈
每一个jvm线程都有一个私有的jvm栈,随着线程的创建而创建,栈中存储的是帧。

jvm栈和传统语言如C的栈相似,保存局部变量和部分计算结果,参与方法的调用和返回。jvm栈主要用于帧的出栈和入栈,除此之外没有其它操作,

帧可能是在堆上分配的,所以jvm栈使用的内存不必是连续的。
native方法栈
native方法不是用Java语言写的,为了支持它需要使用传统栈,如C语言栈。不过jvm不能加载native方法,所以也不需要提供native方法需要的栈。

每次当一个方法被调用时一个新的帧会被创建。当方法调用完成时,与之对应的帧会被销毁,无论是正常完成还是抛异常结束。

所以帧是方法调用的具体体现形式,或称方法调用是以帧的形式进行的。帧用来存储数据和部分计算结果,和执行动态链接,方法返回值,分发异常。

帧分配在创建帧的线程的jvm栈上,每一个帧都有自己的本地变量数组,自己的操作数据栈,和一个对当前方法所在类的运行时常量池的引用。

本地变量数组和操作数栈的大小在编译时就确定了,它们随着和帧关联的方法编译后的代码一起被提供,因此帧这种数据结构的大小只依赖于jvm的实现,这些结构所需的内存可以在方法调用时同时被分配。

在一个线程执行的任何时刻,都只会有一个帧是处于激活的。这个帧被称为当前帧,与之对应的方法被称为当前方法,方法所在的类被称为当前类,此时用到的本地变量数组和操作数栈也都是当前帧的。

一个帧将不在继续是当前帧,如果它的方法调用了另一个方法,或者它的方法结束了。

当一个方法被调用,一个新的帧被创建,当执行控制由原来的方法传递到新的方法时,这个新的帧变为当前帧。

当方法返回时,当前帧把方法执行的结果传回到上一帧,当上一帧被激活的同时当前帧会被丢弃。
本地变量数组
每一帧都包含一个变量数组,就是都熟知的本地变量存储的地方。这个本地变量数组的长度在编译时确定,随着编译后的方法代码一起提供。

通常一个本地变量(的位置)能够存储一个类型的值,但是longdouble类型却需要两个本地变量(的位置)才能存一个值。

本地变量按索引寻址,第一个本地变量的索引是0longdouble需要消耗两个连续的索引,但却是按照较小的这个索引寻址的。不能按照较大的那个索引去读数据,但是可以写入,当然这样将使本地变量内容错乱。

在方法被调用时,jvm使用本地变量来接收传递进来的参数值。在类(静态)方法调用时,所有参数被传入从索引0开始的连贯的本地变量数组里。

在实例(非静态)方法调用时,索引0处总是传入正在其上执行方法调用的那个对象的引用,(就是Java中的this了),所有参数被传入从1开始的连贯的本地变量数组里。
操作数栈
每个帧包含一个后进先出的栈,用于存储正在执行的jvm指令的操作数,就是都熟知的操作数栈,这个栈的最大深度在编译时就已确定,随着编译后的方法代码一起提供。

当帧被创建时,操作数栈是空的,jvm提供一些指令用于加载常量值,本地变量值,字段值到操作数栈上,另一些jvm指令采用操作数栈上的操作数进行操作,并把结果放回到操作数栈上。

操作数栈也用于准备将要传递给方法调用的参数和接收方法调用返回的结果。

longdouble类型的值占用两个单位的栈深度,其它类型的值占用一个单位的栈深度。
动态链接
每一个帧都包含了对当前方法所属类型的运行时常量池的引用。目的是为了支持方法代码的动态链接。class文件中描述一个方法引用被调用的方法和被访问的变量的代码,是采用符号引用的形式实现的。

符号引用的形式可以粗略的认为是字符串的形式,就是用字符串标明需要调用哪个类的哪个方法或访问哪个字段或变量。就像符号引用这个名字一样,这些仅仅是符号,是拿不到具体值的,所以必须要进行转换。

动态链接就是把这些符号方法引用转换为具体的方法引用,在必要时加载类来解析尚未明确的符号,把符号变量的访问转换为这些变量运行时所在存储结构的适合的偏移量(索引)。这样的方式又称为后期绑定。
方法调用
一个方法调用正常完成(即没有抛异常)时,会根据所返回的值的类型执行一个适合的return指令,当前帧会去恢复调用者的状态,包括它的本地变量和操作数栈,使调用者的程序计数器适合的递增来跳过刚刚的那个方法调用指令。

返回值会被放到调用者帧的操作数栈上,然后继续执行调用者方法的帧。

一个方法在调用时抛出了异常,且这个异常没有在这个方法内被捕获处理,将会导致这个方法调用的突然结束,这种情况下永远不会向方法的调用者返回一个值。

特殊方法

站在jvm的级别,每一个用Java写的构造函数都以一个实例初始化方法出现,且都是特殊的名字,就是<init>,这个名字是编译器提供的。

实例初始化方法只能在jvm内部使用invokespecial这个指令调用,且只能在尚未初始化的类实例上调用。

一个类或接口最多可以有一个类或接口初始化方法,通过调用这个方法被初始化。类或接口的初始化方法也有特殊的名字,就是<clinit>,该方法没有参数,且返回值是void。

方法名称也是由编译器提供的,从Java7开始,在字节码中这个方法必须被标记为静态的才行。

这个初始化方法是被jvm隐式调用的,它们绝对不会直接被用任何jvm指令调用,仅作为类初始化进程的一部分被间接的调用。

Java类库

jvm必须为Java类库的实现提供足够的支持。一些类库中的类如果没有jvm协助是无法实现的。

反射,就是在运行时获取某个类的类型相关信息,如它的字段信息,方法信息,构造函数信息,父类信息,实现的接口信息。

这些信息都必须是把一个类加载完之后才可以知道的,只有jvm才可以加载类。如java.lang.reflect这个包下的类和Class这个类。

在Java中加载一个类或接口用类加载器,即ClassLoader,背后还是委托给jvm来实现的。

链接和初始化一个类或接口。

安全,如java.security包下的类,还有其它类像SecurityManager。

多线程,如线程这个类Thread。

弱引用,像java.lang.ref包下的类。

公有设计,私有实现

以上内容只是jvm的一个“相对宽泛”的规范,它并不是实现方案,也不是实现细节。

实现者可以根据自身的需要来实现jvm,如运行在后端服务器上的jvm和运行在移动设备上的jvm肯定侧重点有所不同。

从事Java的人都知道,事实上jvm是有较多的实现版本。

由于jvm是处在Java语言和操作系统之间的,所以它要向上提供对Java的支持,向下与操作系统良好交互。

线程

并发与多线程

说一说自己对于 synchronized 关键字的了解
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
是怎么使用 synchronized 关键字,在项目中用到了吗
synchronized关键字最主要的三种使用方式:

修饰实例方法: 作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法: 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!

框架

SpringCloud

集群/分布式/微服务/SOA
集群:同一个业务,部署在多个服务器上(不同的服务器运行同样的代码,干同一件事)

计算机集群简称集群是一种计算机系统,它通过一组松散集成的计算机软件和/或硬件连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。集群系统中的单个计算机通常称为节点,通常通过局域网连接,但也有其它的可能连接方式。集群计算机通常用来改进单个计算机的计算速度和/或可靠性。一般情况下集群计算机比单个计算机,比如工作站或超级计算机性能价格比要高得多

技术特点:

通过多台计算机完成同一个工作,达到更高的效率。

两机或多机内容、工作过程等完全一样。如果一台死机,另一台可以起作用。
分布式:一个业务分拆多个子业务,部署在不同的服务器上(不同的服务器,运行不同的代码,为了同一个目的)

分布式系统是一组计算机,通过网络相互连接传递消息与通信后并协调它们的行为而形成的系统。组件之间彼此进行交互以实现一个共同的目标

特点:

功能拆分,模块之间独立

好处:

模块之间独立,各做各的事,便于扩展,复用性高

数据库

WEB

网络

  1. 七层协议

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IDqO5gUr-1585537838465)(D:\文档图片\网络七层协议.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4Zc0TIj-1585537838466)(D:\文档图片\网络七层协议1.png)]

连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。集群系统中的单个计算机通常称为节点,通常通过局域网连接,但也有其它的可能连接方式。集群计算机通常用来改进单个计算机的计算速度和/或可靠性。一般情况下集群计算机比单个计算机,比如工作站或超级计算机性能价格比要高得多

技术特点:

通过多台计算机完成同一个工作,达到更高的效率。

两机或多机内容、工作过程等完全一样。如果一台死机,另一台可以起作用。
分布式:一个业务分拆多个子业务,部署在不同的服务器上(不同的服务器,运行不同的代码,为了同一个目的)

分布式系统是一组计算机,通过网络相互连接传递消息与通信后并协调它们的行为而形成的系统。组件之间彼此进行交互以实现一个共同的目标

特点:

功能拆分,模块之间独立

好处:

模块之间独立,各做各的事,便于扩展,复用性高

数据库

WEB

网络

  1. 七层协议

    [外链图片转存中…(img-IDqO5gUr-1585537838465)]

[外链图片转存中…(img-T4Zc0TIj-1585537838466)]

Linux

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值