==:判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。(基本数据类型比较值,引用数据类型比较内存地址)
equals():判断两个对象是否相等,不能用于基本数据类型变量。
若类没有覆盖equals()方法,通过equals()比较时等价于“==”
若类覆盖了equals()方法,则会使用覆盖的equals()方法来判断
hashCode():获取哈希码,使用C/C++实现的,该方法通常用内存地址转换为整数之后返回。
重写equals时必须重写hashCode是因为:
(HashSet如何检查重复?)
如果两个对象相等, 则hashcode一定也是相同的。但是两个对象有相同的hashcode值,他们也不一定是相等的。以HashSet举例,HashSet会先计算对象的hashcode来判断对象的加入位置,同时也会与其他已经加入的对象的hashcode作比较。如果hashcode不相等,则HashSet会假设对象没有重复出现。如果有相同的hashcode,则会调用**equals()检查hashcode相同的对象是否真的相同。如果两者真的相同,HashSet不会让其加入成功。换句话说,如果不重写hashCode()**方法,则HashSet在判断时永远不会认为两个封装类的hashcode相同(因为他们的内存地址不会相同)
提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
在Java编译期间,所有的泛型信息都会被擦掉,这就是类型擦除
泛型的三种使用方式:
泛型通配符
- ?:表示不确定的Java类型
- T(type):表示具体的一个Java类型
- K V(key value):分别表示Java键值中的key、value
- E(element):代表元素
valueOf()
方法xxxValue()
方法Byte
、Short
、Integer
、Long
这4种包装呗默认创建了**[-128, 127]的缓存数据,Character
创建了[0, 127]**的缓存数据,Boolean
直接返回True
Or False
。
Float
和Double
没有实现常量池技术。
超出范围和new
一个对象,都是新建一个对象。调用valueOf()
时会调用常量池。
包装类对象之间值的比较,全部使用equals方法比较
String
类中使用final关键字修饰字符数组来保存字符串,private final char value[]
,所以是不可变的;StringBuilder
与StringBuffer
都继承自AbstractStringBuilder
,使用字符数组保存字符串但是没有final
关键字修饰,所以是可变的。String
中对象是不可变的,可以理解为常量,是线程安全的;StringBuffer
对公共方法加了同步锁或者对调用方法加了同步锁,所以是线程安全的;StringBuilder
没有对方法加同步锁,所以是非线程安全的。String
进行改变的时候,都会产生一个新的String
对象,然后指针指向新对象;StringBuffer
每次对本身进行操作;StringBuilder
相比使用StringBuffer
能获得10%左右的性能提升,但要冒多线程不安全的风险。String
StringBuilder
StringBuffer
可以在运行时分析类以及执行类中的方法。通过反射可以获取任意一个类的所有属性和方法,并调用他们。
获取Class对象的4种方式:
知道具体类的情况
Class clz = TargetObject.class;
Class.forName()
传入类的路径
Class clz = Class.forName("xx.xxx.TargetObject");
通过对象实例instance.getClass()
TargetObject o = new TargetObject();
Class clz = o.getClass();
类加载器xxxClassLoader.loadClass()
传入类的路径
Class clz = ClassLoader.LoadClass("xx.xxx.TargetObject");
通过类加载器获取的Class对象不会进行初始化,即初始化步骤、静态块和静态对象不会得到执行。
List
:存储的元素是有序的、可重复的Set
:无序的、不可重复的Map
:使用键值对(key-value)存储,key是无序的、不可重复的,value是无序的、可重复的JDK1.8之后,底层采用数组+链表/红黑树
来实现。数组是HashMap的主体,链表/红黑树则是解决哈希冲突而存在的。
链表转红黑树的条件:
put过程:
(n - 1) & hash
判断元素存放位置。treefyBin()
方法,根据HashMap数组长度决定是否转换为红黑树。只有数组长度大于等于64才会进行转换红黑树操作,以减少搜索时间。否则,只是执行resize()
方法对数组扩容。扰动函数:防止一些实现比较差的hashCode()方法,使用扰动函数后可以减少碰撞。
两个关键参数:
loadFactor太大导致查找元素效率低,太小导致数组利用率低,存放数据会很分散。默认值0.75f
是一个比较好的临界值。
默认容量为16,当数量达到16 * 0.75 = 12时,就需要进行扩容。
扩容是变为原来对两倍。
HashMap长度为什么是2的幂次方?
为了使HashMap存取高效,尽量减少碰撞,要尽量把数据分配均匀。在计算数组下标的时候,一定会涉及到%
取余操作。当取余操作中除数是2的幂次时,等价于其除数减一的与(&)操作。即hash % length == hash & (length - 1)
。二进制位操作&,比%运算效率要高,这就解释了为什么HashMap长度是2的幂次方。
HashMap多线程下不安全/导致死锁问题
在多线程环境下,扩容/rehash过程中容易出现死循环。
假设有两个线程对同一HashMap进行操作。且都执行到
transfer()
,线程1执行到next = e.next;
就被挂起,线程2执行完成,此时线程1指向rehash后的链表,会有顺序反转的情况。当调度回线程1之后,执行到next = e.next;
就会出现环形链表,导致死锁。
e = next
,导致e指向7,执行next = e.next
导致next指向3get(11)
便会出现死锁。HashMap
是非线程安全的,HashTable
是线程安全的,内部方法都经sunchronized
修饰。(现在已经废弃,使用ConcurrentHashMap
来保证线程安全)HashMap
效率高于HashTable
。HashMap
可以存储null的key和value,但null作为键只能有一个;HashTable
不允许有null的key和value,否则会抛出异常。HashMap
初始大小为16,每次扩容变为原来的2n。HashTable
初始大小为11,每次扩容变为原来的2n + 1。HashMap
在解决哈希冲突时,会在 ①链表长度大于阈值(默认为8)②数组长度大于等于64 时,转化为红黑树,以减少搜索时间。HashTable
没有这样的机制。JDK1.7中采用分段锁机制,划分成很多个Segment
,每一个Segment
是一个类似于HashMap
的结构,所以每一个Segment
的内部都可以扩容,但Segment
个数一旦初始化就不能改变。默认为16个,即默认最多支持16个线程并发。
JDK1.8中采用数组+链表/红黑树的底层结构。当冲突链表达到一定长度时,链表会转换成红黑树。
扩容会变为原来的2倍,使用头插法进行链表迁移。
ConcurrentHashMap
JDK1.7使用分段数组+链表,JDK1.8使用数组+链表/红黑树ConcurrentHashMap
使用分段锁,多线程访问容器里的不同数据段的数据,就不会存在锁竞争。JDK1.8使用synchronized
和CAS操作
。而HashTable
使用synchronized
来控制同一把锁,来保证线程安全,效率十分低下。CAS操作:Compare and Swap,涉及三个参数(内存地址V,旧的预期值A,要修改的新值B),更新一个变量的时候,只有当变量的预期值A和内存地址V中的实际值相同时,才会将内存地址V对应的值修改为B。可用“多线程自增操作”举例。
底层是数组队列,相当于动态数组,它的容量能动态增长。
扩容机制:
需要扩容时,将新容量设置为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
如果新容量小于最小所需容量,则将最小所需容量作为新容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
如果最小所需容量小于最大容量,则新容量为Integer.MAX_VALUE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 最小所需容量小于0则溢出
最后调用Arrays.copyOf()
进行扩容
添加大量元素前最好先使用
ensureCapacity
方法,以减少增量重新分配的次数,提高效率。
是一个实现了List
接口和Deque
接口的双端链表。底层接口是链表,使它支持高校的插入和删除操作,实现了Deque
接口,使它也具有了队列的特性。linkedList
不是线程安全的,如果想使LinkedList
变成线程安全的,可以调用Collections
中的synchronizedList
方法。
ArrayList
底层是**Object
数组**LinkedList
底层是双向链表ArrayList
数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。LinkedList
链表存储,所以插入和删除元素的时间复杂度不受元素位置影响。ArrayList
支持随机元素访问,而LinkedList
不支持。ArrayList
的空间浪费主要体现在list列表结尾会预留一定的容量空间。LinkedList
的空间浪费体现在每一个元素都需要一定额外空间存放直接前驱和直接后继的指针。synchronized
关键字解决的是多个线程之间访问资源的同步性,synchronized
关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
使用:
synchronized void method() {
// 业务代码
}
synchronized
方法占用的锁是当前类的锁,而访问非静态synchronized
方法占用的是当前实例对象锁。synchronized static void method() {
// 业务代码
}
synchronized(this|object)
表示进入同步代码库前要获得给定对象的锁。synchronized(类.class)
表示进入同步代码前要获得当前class的锁。synchronized(this) {
// 业务代码
}
总结:
synchronized
关键字加到static
静态方法和synchronized(class)
代码块上都是给Class类上锁。synchronized
关键字加到实例方法上是给实例对象加锁。级别从低到高依次是:
锁是可以升级的,但不能降级。
偏向锁:
针对于一个线程而言,线程获得锁之后就不会再有解锁等操作,节省很多开销。
当一个线程访问同步块并获取锁时,会在锁对象的对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程进入和退出同步块时不需要进行CAS操作来加锁和解锁。
当其他线程尝试金正偏向锁时,持有偏向锁的线程才会释放锁,会升级成轻量级锁。
轻量级锁:
出现两个线程来竞争锁,偏向锁失效,升级成轻量级锁。
线程在执行同步块之前,JVM会现在当前线程的栈帧中创建锁记录空间,并尝试使用CAS替换锁记录指针。如果成功,当前线程获得锁;如果失败,表示其他线程竞争锁,当前线程使用自旋锁来获取锁。
解锁时,会使用CAS操作将当前线程锁记录还原,如果成功,表示没有竞争发生;如果失败,表示当前锁存在竞争,升级成重量级锁。
比较
锁 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步代码方法的性能相差无几 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问的同步场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 始终得不到锁竞争的线程,使用自旋锁会消耗CPU | 追求响应时间,同步执行速度非常快 |
重量级锁 | 线程竞争不使用自旋,不消耗CPU | 线程堵塞,响应时间慢 | 追求吞吐量,同步块执行时间长 |
线程私有的:
线程共享的:
Java内存可以简单分为堆内存(heap)和栈内存(stack)。局部变量表主要存放各种基本数据类型和对象引用。
会出现两种错误:StackOverFlowError
(不允许动态扩展且超过最大深度)和OutOfMemoryError
(允许动态扩展但无法申请足够内存)。
Java栈中保存的主要内存是栈帧,每一次函数调用都会压入栈帧,调用结束后弹出栈帧。
虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。
存放对象实例。是垃圾收集器管理的主要区域。基本都采用分代垃圾收集算法。
通常分为3个区域:
对象在内存不同区域:
类加载检查
遇到new
指令时,首先在常量池中定位到这个类的符号引用,检查该类是否已被加载过、解析和初始化。没有则执行相应的类加载过程。
分配内存
在类加载检查通过后,虚拟机为新生对象分配内存。
初始化零值
这一步保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,访问到的是零值。
设置对象头
虚拟机对对象进行必要的设置,信息包括该对象是哪个类的实例、如何找到元数据信息、哈希值、GC分代年龄等信息,存放在对象头中。
执行init方法
按照开发人员的需求进行初始化,如执行构造方法。
引用计数法
给对象添加一个引用计数器,有地方引用就+1;引用失效就-1.当计数器为0表明对象不再被使用。无法解决对象之间相互循环引用的问题。
可达性分析算法
基本思想:通过一系列成为GC Roots的对象(栈、方法区的引用对象、同步锁持有对象)为起点,从这些节点开始向下搜索,节点走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明此对象不可用。
标记-清除算法
分为标记和清除两个阶段:首先标记出所有不需要回收的对象,再清除掉没被标记的对象。
存在两个问题:①效率问题 ②空间问题(产生大量不连续碎片)
标记-复制算法
解决了效率问题
将内存分为大小相同的两块,每次使用其中一块,当该块内存使用完,就将存活对象复制到另一块区,再把使用的空间一次清除。
标记-整理算法
针对老年代提出的标记算法,标记存活对象,然后将所有存活对象向一端移动,直接请去掉边界以外的内存。
分代收集算法
根据不同的对象存活周期将内存分为几块,根据各个年代的特点选择合适的垃圾收集算法。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
这种风格将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选、排序、聚合等。
stream of element -----> filter -----> sorted -----> map -----> collect
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x, y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
数据源:流的来源,可以是集合、数组、I/O channel、生成器Generator等。
聚合操作:类似SQL语句一样的操作,比如filter、map、reduce、find、match、sorted等。
Pipelining:中间操作都会返回流对象本身。这样的多个操作串成一个管道,如同流式风格。这样可以对操作进行优化,如延迟执行和短路*。
内部迭代:以前对集合遍历都是通过Iterator或者For-Each方式,显示的在集合外部迭代,这是外部迭代。Stream提供了内部迭代方式,通过访问者模式实现。
常用方法:
collect(Collectos.xx)
实现归约操作,将流转换成集合和聚合元素。(parameters) -> expression
(parameters) -> { statements; }
特征:
final
,要么不会被后续代码修改(隐式final含义)。索引是一种用于快速查询和检索数据的数据结构。
常见的索引有:B树、B+树、Hash
优点:
缺点:
不使用Hash作为索引:
不使用B树作为索引:
聚集索引:
聚集索引即索引结构和数据一起存放的索引。主键索引属于聚集索引。
优点:查询速度快,定位到索引的节点,相当于定位到了数据。
缺点:①依赖于有序数据 ② 更新代价大(修改索引列数据时对应的索引也要修改)。
非聚集索引:
非聚集索引即索引结构和数据分开存放的索引。二级索引属于非聚集索引。
优点:更新代价小。
缺点:①依赖于有序数据 ②会有回表(二次查询),第一次查到索引对应的主键,第二次再根据主键去查找数据。
一个或一组sql语句组成当执行单元,这个执行单元要么全部执行,要么全部不执行。
属性ACID
并发问题
对于两个事务t1、t2
隔离级别
二进制日志binlog
记录所有数据库表结构变更、表数据修改。
事务日志
undo log
事务开始之前,在操作任何数据之前,首先将需要操作的数据备份到一个地方。
为了实现事务持久性。
redo log
将事务中操作的任何数据的最新版本备份到一个地方。
为了实现事务原子性。
读写锁X/S
有以下规定:
存在问题:在行/表锁的情况下,事务T要对表A加X锁,需要检测是否有其他事务对表A或者表A中任意一行加了锁,对每一行都需要检测,十分耗时。
意向锁IX/IS
IX/IS都是表锁,表明想要在表中的某行数据加X/S锁。
有以下规定:
查询优化:
where
后的字段适合作为索引提高查询效率。insert
和update
耗时增加。表结构优化:
垂直拆分
主键和常用字段放在一张表中,主键和其他字段放在另一张表。
可以减少IO次数,但是查询所有数据需要join
查询。
水平拆分
根据某一列的值把数据放到多个独立的表中。
减少查询读取的数据量,提高IO速度,但会增加查询的复杂度。
逆规范化
增加冗余列,避免联合查询。
增加派生列,保存中间计算结果,避免重复计算。
重新组表,将经常联合查询的表组成一个表,减少联合查询。
Redis是一个数据库,与传统数据库不同的是,Redis的数据是存在内存中的。由于它是内存数据库,读写速度非常快,因此被广泛用于缓存方向。
Redis用处:
Redis提供了多种数据类型来支持不同的业务场景,还支持事务、持久化、Lua脚本、多种集群方案。
string
一般用在需要计数的场景,如用户访问次数、热点文章点赞转发量等。
list
易于元素的插入/删除,但随机访问困难。
一般用在发布与订阅或者消息队列、慢查询。
hash
类似HashMap。
适合用于存储对象。
set
类似HashSet。
用于存放不能重复的数据,以及交集、并集、差集运算。
sorted set
相比set
增加了一个权重参数score
,使集合中的元素按score
进行有序排序。
适用于排序场景中,如礼物排行榜、弹幕消息等。
bitmap
存储连续的二进制数字(0/1),一个bit位来表示某个元素对应的值或者状态。
适用于需要保存状态信息并需要进一步对这些场景进行分析的场景。如用户签到情况、活跃用户情况、用户行为统计。
惰性删除
只会在取出key的时候进行过期检查,对CPU友好,但可能会造成太多key没有被删除。
定期删除
每隔一段时间抽取一批key执行过期删除key操作,对内存友好。Redis底层会通过限制删除操作执行的时长和频率来减少对CPU时间的负担。
Redis采用定期删除+惰性删除。
6种数据淘汰策略:
针对设置了过期时间的key:
针对所有key:
其他:
快照持久化(RDB)
通过创建快照来获得存储在内存中的数据在某个时间点上的副本。
创建快照后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(主从结构,提高性能),并且快照留在原地以便重启服务器时使用。
AOF持久化
实时性更好,每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入到AOF文件中。
3种AOF方式:
概念:
缓存在同一时间大面积失效,后面的请求都直接落到数据库上,造成数据库短时间内承受大量请求。
解决办法:
针对Redis服务不可用:
针对热点缓存失效:
概念:
大量请求的key根本不存在于缓存中,导致请求直接到了数据库上。
解决办法:
缓存无效key
如果缓存和数据库都查不到某个key的数据,就写一个到Redis中并设置过期时间。
布隆过滤器
借助布隆过滤器,可以非常方便的判断一个给定数据是否存在于海量数据中。
布隆过滤器:判定某个元素不存在,该元素一定不存在;判定某个元素存在,该元素有可能不存在。
Cache Aside Pattern(旁路缓存模式)
若删除cache失败,可以采用 ①缓存失效时间变短 ②增加cache更新重试机制。
Read/Write Through Pattern(读写穿透)
写(Write Through):
读(Read Through):
Write Behind Pattern(异步缓存写入)
只更新缓存,不直接更新DB,使用异步批量方式更新DB。
适合数据经常变化但对数据一致性要求没那么高的场景,如浏览量、点击量。
作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见场景:Windows任务管理器、回收站,项目中读取配置文件的类,数据库连接池
作用:实现了创建者和调用者分离。实例化对象不使用new
,用工厂方法代替。
常见场景:日志记录器、数据库访问等。
3种类型:
作用:新建对象都是对原型对象的克隆,即深拷贝。
常见场景:类初始化较为耗费资源、一个对象有多个修改者。
作用:将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示,在用户不知道对象构造过程和细节的情况下直接创建复杂对象。
常见场景:需要生成的对象具有复杂的内部结构。
作用:使原本不兼容的接口能够一起工作,常用对象适配器,即关联关系实现。
常见场景:修改一个正常运行的系统接口。
作用:在对象间的一对多的依赖关系中,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
常见场景:一个对象的改变导致其他若干对象也会发生改变。
作用:为其他对象提供一种代理以控制对这个对象的访问。
常见场景:远程代理、Cache代理等。
作用:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换,防止if...else
所带来的复杂和难以维护。
常见场景:让类动态的选择使用的一种或几种行为。
应用层
通过应用进程间的交互来完成特定网络应用。
协议:HTTP、SMTP、DNS
运输层
负责向两台主机进程之间的通信提供通用的数据传输服务。
向应用层提供服务。
协议:TCP、UDP
网络层
选择合适的网间路由和交换节点,确保数据及时传送。
数据链路层
负责相邻的两台主机之间的数据帧传送。
物理层
相邻计算机节点之间的比特流传送。
SYN
标志的数据包。SYN/ACK
标志的数据包。ACK
标志的数据包。原因:
三次握手的目的是建立可靠的通信信道,使通信双方确认对方的发送与接收是正常的。
FIN
,用来关闭客户端到服务器的数据传送。FIN
,发回一个ACK
,确认序号为收到的序号+1。FIN
给客户端。ACK
报文确认,将确认序号设置为收到的序号+1。任何一方在数据传送结束后发出连接释放的同志,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭TCP连接。
UDP在传送数据前不需要先建立连接,目的主机收到UPD报文后不需要给出任何确认,是一种无连接的不可靠交付。一般用于即时通信。
TCP提供面向连接的可靠服务,在传送数据前必须建立连接,传送结束后释放连接。不提供广播或多播服务。为了保证可靠传输,需要额外开销。一般用于文件传输、发送和接收邮件、远程登录等。
自动重传请求通过使用确认和超时两个机制,在不可靠服务的基础上实现可靠信息传输。
基本原理:每发完一个分组就停止发送,等待对方确认(回复ACK),超时后还没有收到确认,会自动重传。直到收到确认后再发下一个分组。
接收方如果收到重复分组,就丢弃该分组,同时发送确认。
优点:简单。
缺点:信道利用率低。
异常情况:
确认丢失
确认消息在传输过程中丢失。
当A向B发送消息M时,B收到后发回确认,但在传输过程中丢失,而A不知道。在超时计时过后,A重传M,B再次收到后采取以下措施:①丢弃这个重复的消息 ②向A发送确认消息。
确认迟到:
确认消息在传输过程中迟到。
当A向B发送消息M时,B收到并发送确认。在超时时间内没有收到确认,A重传消息M,B接收到后继续发送确认消息。此时A收到了B第二次发送的确认消息。过了一会收到了B第一次发送的确认消息。A采取以下措施:①A收到重复确认后直接丢弃 ②B收到重复消息M后,直接丢弃。
连续ARQ协议可提高信道利用率。发送方维持了一个发送窗口,位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明这个分组为止的所有分组都已经正确收到。
优点:信道利用率高,即使确认丢失,也不必重传。
缺点:不能像发送方反映出已经正确接收到的所有分组信息。当中间第N个包丢失后,N后所有的包都需要重传。
拥塞控制防止过多数据注入到网络中。
TCP发送方要维持一个**拥塞窗口(cwnd)**的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接口窗口中较小的一个。
4种算法:
使用协议:
类别 | 原因 | |
---|---|---|
1XX | Informational(信息性状态码) | 接受的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求} |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
HTTP/1.0默认使用短链接,也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就终端连接。
HTTP/1.1起,默认使用长连接,用以保持连接特性。长连接在响应头中加入代码Connection:keep-alive
.使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问时,会继续使用这一条已经建立的连接。这个连接不是一直存在的,有一个保持时间,在服务器上进行设定。
HTTP是一种无状态(stateless)协议,为了保存用户状态,使用Session机制。
Session主要作用时通过服务端记录用户状态。典型场景是购物车。服务端给特定的用户创建特定的Session就可以标识这个用户并跟踪,一般情况下服务器会在一定时间内保存这个Session,超过时间限制,就会销毁这个Session。
服务端Session保存常用方法是内存和数据库。通过在Cookie中附加一个Session ID来跟踪用户。
Cookie一般用来保存用户信息,保存在客户端。
使用Token实现,第一次登陆时存放Token在Cookie中,下次登陆时根据Token查找用户。重新登陆将Token重写。
Session通过服务端记录用户状态,保存在服务器。
所以说,HTTP安全性没有HTTPS高,但是更为节省服务器资源。
RPC是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
RPC让分布式或者微服务系统中不同服务之间的调用像本地调用一样简单。
RPC只是一种设计,是概念性的东西,一般包括传输协议和序列化协议,而HTTP是一个协议。
Web Service是一套RPC规范,一般属于基于HTTP的、XML文本的、跨平台(平台中立)的,功能完善、体系成熟、支持事务、支持安全机制,广泛应用在金融、电信领域。
C:Consistency(一致性):所有节点访问同一份最新的数据副本。
A:Availability(可用性):非故障的节点在合理的时间内返回合理的响应(不是错误或者超时的响应)。
P:Partition Tolerance(分区容错性):分布式系统出现网络分区的时候,仍然能够对外提供服务。
分区容错性P是必须要实现的,在此基础上只能满足可用性A或者一致性C。
如果系统发生“网络分区”,才需要考虑选择CP还是AP。
BA:Basically Available(基本可用):允许损失部分可用性,不等价于系统不可用。允许响应时间和系统功能上的损失。
S:Soft-state(软状态):允许系统中的数据存在中间状态,允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
E:Eventually Consistent(最终一致性):强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。
一致性的3种级别:
- 强一致性:系统写入了什么,读出来就是什么。
- 弱一致性:不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只会尽量保证某个时刻达到数据一致的状态。
- 最终一致性:弱一致性的升级版,系统保证会在一定时间内达到数据一致的状态。
核心思想:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需保持系统整体“主要可用”。
BASE理论本质上是对CAP中AP方案的一个补充。