赞
踩
JVM由三部分组成:类加载子系统、执行引擎、运行时数据区。
类加载子系统:可以根据指定的全限定名来载入类或接口。
执行引擎:负责执行那些被载入类的方法中的指令。
运行时数据区:包含五部分的内容:栈、堆、本地方法栈(为Native方法提供服务)、方法区(元空间)、程序计数器(用来保存线程执行的位置)。
栈:存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包含局部变量和对象的引用
堆:存储对象实例的以及数组
线程私有:栈、本地方法栈、程序计数器
线程共享:堆、方法区
类加载机制总共包括个阶段:加载,验证,准备,解析,初始化
加载:通过类加载器,使用字节码文件创建类对象。
验证:主要是验证当前的字节流包含的信息是否符合当前的虚拟机环境。
准备:为类变量(static修饰的变量)分配内存,并赋值.
解析:主要是将常量池的符号引用变成直接引用。
初始化:执行类构造器方法的过程。
public class ThreadTEST { public static void main(String[] args) { Thread.currentThread().setName("main thread"); MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); myThread1.start(); myThread2.start(); } } class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + i); } } }
public class RunnableTest { public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); //开启线程 thread.start(); for(int i = 0; i <5;i++){ System.out.println(Thread.currentThread().getName() + i); } } } class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + i); } } }
public class CallableTest { public static void main(String[] args) { //执行Callable 方式,需要FutureTask 实现实现,用于接收运算结果 FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable()); new Thread(futureTask).start(); //接收线程运算后的结果 try { Integer sum = futureTask.get(); System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class MyCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { sum += i; } return sum; } }
注:Callable和Runnable的区别?
1.Runnable重写的方法是run(),Callable重写的方法是call()。
2.Runnable没有返回值,Callable可以通过FutureTask接收返回值。
//线程池实现 public class ThreadPoolExecutorTest { public static void main(String[] args) { //创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); ThreadPool threadPool = new ThreadPool(); for(int i =0;i<5;i++){ //为线程池分配任务 executorService.submit(threadPool); } //关闭线程池 executorService.shutdown(); } } class ThreadPool implements Runnable { @Override public void run() { for(int i = 0 ;i<10;i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } }
反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。
获取Class对象的三种方式:
JDK原生动态代理
JDK原生动态代理是Java原生支持的,不需要任何外部依赖,它是基于接口进行代理。
CGLIB动态代理
CGLIB通过继承的方式进行代理(让需要代理的类成为Enhancer的父类),无论目标对象有没有实现接口都可以代理。
引用计数法
给对象添加一个引用计数器,每当有一个地方引用,计数器就加1。当引用失效,计数器就减1。任何时候计数器为0的对象就是不可能再被使用的。
可达性分析算法
通过一系列的称为”GC Roots“的对象作为起点,从这些节点开始向下搜索,当一个对象到GC Roots没有任何引用链相连的话,则证明此对象时不可用的。
在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递。
串行垃圾回收器(Serial)、并行垃圾回收器(Parallel)、并发清除回收器(CMS)、G1回收器。
final:修饰声明属性、方法、类
finally:是异常处理的一部分,表示总是执行
finalize:当垃圾回收器将要回收对象所占内存之前调用finalize()方法来完成最后的处理
同步代码块通过monitorenter和monitorexit执行来进行加锁。当线程执行到monitorenter的时候要先获得锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。每个对象自身维护着一个被加锁次数的计数器,当计数器不为0时,只有获得锁的线程才能再次获得锁
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
可重入锁有synchronized、ReentrantLock。
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
解决办法:
1.破坏不可剥夺:线程在申请不到资源时主动释放掉已有的资源。
2.破坏请求与保持:可以一次性给线程申请所有的资源。
3.破坏循环等待:将资源线性排列,申请时先申请序号小的,再申请序号大的。
判断死锁:
JDK 提供了jps命令来检测某个java进程中心线程的情况。
方法:1.使用jps命令查看java进程号。 2. 使用jstack命令连接上进程号
ThreadLocal叫做线程变量,该变量是当前线程独有的变量,ThreadLocal在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。
当一个线程有多个threadlocal时,ThreadLocalMap集合可以用来管理多个 ThreadLocal ThreadLocalMap底层是数组,数组的元素是 Entry,Entry 的 key 就是 ThreadLocal 的引用
Entry的key是弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。所以key会在垃圾回收的时候被回收掉, 而key对应的value则不会被回收, 这样会导致一种现象:key为null,value有值。key为空的话value是无效数据,久而久之,value累加就会导致内存泄漏。
避免:
在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收
进程是资源分配的基本单位,线程是程序执行的基本单位。进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间。一个进程里面可以包含多个线程。
ReentrantLock是可重入锁,底层是通过AQS实现的。
ReentrantLock共有三个核心内部类:Sync、NonfairSync、FairSync。其中Sync继承了AQS
AQS中有三个核心属性:head(指向队列头部)、tail(指向队列尾部)、state(表示状态,修改状态又是通过CAS来实现的)
CAS是比较和替换,是基于乐观锁的思想实现的。
CAS 应用:原子类(AtomicInteger)中的incrementAndGet()方法就是基于CAS实现的。
volatile关键字是一个轻量级的同步机制。volatile 关键字可以保证可见性和有序性,不能保证原子性。
核心参数:
工作原理:
join方法可以用来插队,有线程在cpu运行时,另一个线程使用join方法把在运行的线程退回等待状态,自己进入cpu运行。
ArrayList
LinkedList
1. 数据结构
1.7数据结构为:数组+链表。
1.8数据结构为: 数组 + 链表+红黑树。
2.put过程
1.7:计算key的hash值,根据hash值找到数组的下标。如果当前的位置上有值,则遍历当前位置上所有对象,如果key完全相同的,就覆盖这个值。如果key都不相同,就将要当前值封装成entry ,加入当前链表的头结点处(头插),如果当前位置没有值,就直接封装成entry加入当前位置。
1.8:根据key计算hashcode,然后计算再数组中的下标,如果当前位置上没有值,就直接插入,如果当前位置上有值,就进行遍历,如果存在一个key完全相同,就进行覆盖,如果不存在就通过尾插法插入到链表或者红黑树。如果链表的长度大于8并且数组长度大于64,就自动转为红黑树。
3.扩容机制:默认长度为16,负载因子为0.75,每次扩容会变为原来的2倍,就是2的N次方
因为在链表模式下,当发生过多的哈希碰撞的时候,链表会越来越长,查询速度会变慢
如果不同的输入得到了同一个哈希值,就发生了"哈希碰撞"。
解决:
ConcurrentHashMap是一个线程安全的集合,它的底层是数组+链表/红黑树构成的。
对于CopyOnWriteArrayList来说,写操作需要加锁。它并不会直接操作原数组,而是先复制一个数组再进行操作,写操作执行结束后在将复制后的数组赋值给原数组。CopyOnWriteArrayList在写操作的同时允许读操作,大大提高了读操作的性能。
包括:Error和Exception
Exception包括:编译时异常和运行时异常
编译时异常:
运行时异常:
IOC:控制反转。控制:对象的创建的控制权限;反转:将对象的控制权限交给spring。之前我们创建对象时用new,现在直接从spring容器中取,维护对象之间的依赖关系,降低对象之间的耦合度。
AOP:面向切面编程。在不改变原有业务逻辑的情况下,将公共逻辑封装成切面,跟业务逻辑代码分离。可以减少系统的重复代码和降低模块之间的耦合。
SpringBoot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factory中注册的各种AutoConfiguration类,满足@Conditional注解的条件时,就实例化该AutoConfiguration类中定义的Bean,并注入Spring容器,即可完成Springboot的自动装配。
@Autowied注解,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用
BeanFactory 是所有Spring Bean容器的顶级接口,它为Spring容器定义了一套规范,并提供了getBean方法从容器中获取指定的Bean实例。
FactoryBean 是一个工厂Bean,它可以动态去生成某一类型的Bean的一个实例。其中getObject()方法就是用来实现动态构建Bean的一个过程。
自定义注解定义为:@interface。通常使用另外两个元注解定义:
Modle层(模型,数据访问层)、Cotroller层(控制,逻辑控制层)、View层(视图,页面显示层)
starter配置,约定大于配置,spring boot将日常企业应用研发中的各种场景都抽取出来,做成一个个的starter(启动器),starter中整合了该场景下各种可能用带的依赖,用户只需要在Maven中引入starter依赖,spring boot就能自动扫描到要加载的信息并启动响应的默认配置
Spring boot是默认启动事务的,只需要在(service层中添加)类或者方法上添加 @Transactional注解即可
静态工厂方式实例化:通过静态工厂创建并返回Bean。其实质是将Bean对应的类交给我们自己的静态工厂管理。Spring只是帮我们调用了静态工厂创建实例的方法。
实例工厂方式实例化:通过实例工厂创建并返回Bean,其实质就是把创建实例的工厂类和调用工厂类的方法的过程也交由Spring管理。
/**
接口内的抽象方法
**/
public abstract User selectUser(@Param("id") int userId);
映射文件的sql语句
<!--
映射文件的sql语句
-->
<select id="selectUser" resultType="User">
select * from user where userId = #{id};
</select>
/**
接口内的抽象方法
**/
public abstract List<User> selectUser1(User user);
<!--
映射文件的sql语句
-->
<select id="selectUser1" resultType="User">
select * from user where userId = #{UserId} or sex = #{sex}
</select>
/**
接口内的抽象方法
**/
public abstract List<User> selectUserByMap(Map<String,Object> map);
<!--
映射文件的sql语句
-->
<select id="selectUserByMap" resultType="User">
select * from user where userId = #{id} or sex = #{sex}
</select>
Map<String,Object> map = new HashMap<>();
map.put("id",37);
map.put("sex","男");
一级缓存:作用域默认是SqlSession。Mybatis默认开启一级缓存。 在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;第二次以后则直接去一级缓存中取。当执行的SQL查询中间发生了增删改的操作,mybatis会把SqlSession的缓存清空。
二级缓存:作用域是nameSpace。Mybatis需要手动设置启动二级缓存。一个会话,查询一条数据,这个数据会被放在当前会话的一级缓存中;如果会话被关闭了,一级缓存汇总的数据会被保存到二级缓存。新的会话查询信息就会参照二级缓存。
1.like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。
2、or语句前后没有同时使用索引。
当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效
3、如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引
4、在索引列上使用 IS NULL 或 IS NOT NULL操作。
5、在索引字段上使用<>,!=。
1.原子性:事务最小的执行单位,不允许分割。事务的原子性确保动作要么全部执行,要么全部不执行。
2.一致性:执行事务的前后,数据保持一致。例如转账的业务中,无论事务是否成功,转账者和收款人的总额应该是不变的。
3.隔离性:并发访问数据库时,一个用户的事务不应该被其他事务所影响,各并发事务之间数据库是独立的。
4.持久性:一个事务被提交后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有影响。
1.读取未提交(READ-UNCOMMITTED):最低的隔离级别,允许读取尚未提交的数据变更,可能造成脏读、不可重复读、幻读。
2.读取已提交(READ-COMMITTED):允许读取并发事务已经提交的数据,可以避免脏读,但是可能造成不可重复、幻读。
3.可重复读(REPEATABLE-READ):mysql默认隔离级别。对同一字段多次读取的结果都是一致的,除非本身事务修改,可以避免脏读和不可重复读,但是可能造成幻读。
4.可串行化(SERIALIZABLE):最高的隔离级别,完全服从ACID的隔离级别,所以的事务依次执行,可以避免脏读、不可重复读、幻读。
1.行级锁
行锁是指加锁的时候锁住的是表的某一行或多行记录,多个事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问;行级锁并不是直接锁记录,而是锁索引。
特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高;
2.表级锁
表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;
特点: 粒度大,加锁简单,容易冲突;
3.共享锁
共享锁又称读锁,简称S锁。当一个事务对数据加上读锁之后,其他事务只能对该数据加读锁,而无法对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加写锁。 加了共享锁之后,无法再加排它锁,这也就可以避免读取数据的时候会被其它事务修改,从而导致重复读问题。
4.排他锁
排他锁又称写锁,简称X锁;当一个事务对数据加上写锁之后,其他事务将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。加了排他锁之后,其它事务就无法再对数进行读取和修改,所以也就避免了脏写和脏读的问题。
5.记录锁
记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录。
6.间隙锁
间隙锁属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。
7.临键锁
临键锁也属于行锁的一种,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住。
扩展:什么是关系型数据库?什么是非关系型数据库?
答:关系型数据库,是指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,以便于用户理解,关系型数据库这一系列的行和列被称为表,一组表组成了数据库。
非关系型数据库,NoSQL,泛指非关系型的数据库。区别于关系数据库,它们不保证关系数据的ACID特性。
MVCC 中文全程叫多版本并发控制,是数据库引擎实现中常用的处理读写冲突的手段,目的在于提高数据库高并发场景下的吞吐性能。
MVCC底层核心原理:隐藏字段(事务id trx_id 和 回滚指针 roll_pointer)、Undo Log版本链 、 ReadView
MVCC只在读已提交(每次Select都会获取一次ReadView)和可重复读(只在第一次Select时获取一次ReadView)两个隔离级别下工作
索引的数据结构会被存储在磁盘中,每次查询都需要到磁盘中访问,对于红黑树,树的高度可能会非常的高,会进行很多次的磁盘IO,效率会非常低,B+树的高度一般为2-4,也就是说在最坏的条件下,也最多进行2到4次磁盘IO,这在实际中性能时非常不错的
如果表多个列并且没有主键,则 count(1) 的执行效率优于 count(*)
如果表只有一个字段,则 select count(*)最优
MySQL索引是一种帮助快速查找数据的数据结构,可以把它理解为书的目录,通过索引能够快速找到数据所在位置。场景的索引数据结构有:Hash表(通过hash算法快速定位数据,但不适合范围查询,因为需要每个key都进行一次hash)、二叉树(查找和修改效率都比较高),但是在InnoDB引擎中使用的索引是B+Tree,相较于二叉树,B+Tree这种多叉树,更加矮宽,更适合存储在磁盘中。使用索引增加了数据查找的效率
因为在主从复制过程中,是**基于binlog(二进制日志)**实现的。在mysql5.0之前,binlog只支持statement格式(记录的是修改SQL语句),而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有问题的,可能会出现从机执行的顺序与主机不一致。
解决方式:1) 隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。2)将binglog的格式修改为row格式,此时是基于行的复制。
普通索引:就是各类索引中最为普通的索引。其特点是允许出现相同的索引内容,允许空(null)值
唯一索引:就是不可以出现相同的索引内容,但是可以为空(null)值
1.查询过程:
mysql主从复制是指将主服务器的数据复制到一个或多个从服务器上。
作用:数据备份、提高性能、读写分离
步骤:
当前读:就是它读取的是 记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进⾏加锁。
eg: update、insert 、delete、select lock in share mode( 共享锁),select for update
快照读:不加锁的select 操作就是快照读。
快照读的前提是隔离级别不能为:未提交读和串行化级别,因为未提交读总是读取最新的数据⾏,⽽不是符合当前事务版本的数据⾏。⽽串⾏化则会对所有读取的⾏都加锁
from->where->group by->having->select->order by->limit
1. SETNX + EXPIRE
这个方案中,setnx和expire两个命令分开了,「不是原子操作」。如果执行完setnx加锁,正要执行expire设置过期时间时,进程crash或者要重启维护了,那么这个锁就“长生不老”了,「别的线程永远获取不到锁啦」。
2. SETNX + value值是(系统时间+过期时间)
这个方案的优点是,巧妙移除expire单独设置过期时间的操作,把「过期时间放到setnx的value值」里面来。解决了方案一发生异常。
3.使用Lua脚本(包含SETNX + EXPIRE两条指令)
实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令)
4.SET的扩展命令(SET EX PX NX)
除了使用,使用Lua脚本,保证SETNX + EXPIRE两条指令的原子性,我们还可以巧用Redis的SET指令扩展参数!(SET key value[EX seconds][PX milliseconds][NX|XX]),它也是原子性的!
SET key value[EX seconds][PX milliseconds][NX|XX]
NX :表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁,才能获取。
EX seconds :设定key的过期时间,时间单位是秒。
PX milliseconds: 设定key的过期时间,单位为毫秒
XX: 仅当key存在时设置值
但是呢,这个方案还是可能存在问题:「锁被别的线程误删」。假设线程a执行完后,去释放锁。但是它不知道当前的锁可能是线程b持有的(线程a去释放锁时,有可能过期时间已经到了,此时线程b进来占有了锁)。那线程a就把线程b的锁释放掉了,但是线程b临界区业务代码可能都还没执行完呢。
5.SET EX PX NX + 校验唯一随机值
既然锁可能被别的线程误删,那我们给value值设置一个标记当前线程唯一的随机数,在删除的时候,校验一下,不就OK了嘛。
参考链接地址:Redis实现分布式锁的7种方案,及正确使用姿势!
1.String
字符串对象的值底层都是由简单动态字符串实现的
get <key> 查询对应键值
set <key> <value> 添加键值对
append <key> <value> 将给定的<value>追加到原值的末尾
strlen <key> 获取值的长度
senx <key> <value> 只有在key 不存在时设置key的值
incr <key> 将key中存储的数字值增1, 只能对数字值操作,如果为空,新增值为1
decr <key> 将key中存储的数字值减1, 只能对数字之操作,如果为空,新增值为-1
incrby/decrby <key> 将key中存储的数字值增减,自定义步长
mset <key1> <value1> <key2> <value2> 同时设置一个或多个key-value对
mget <key1> <key2> <key3> 同时获取一个或多个value
msetnx <key1> <value1> <key2> <value2> 同时设置一个或多个key-value对,当且仅当所有给定的key都不存在
getrange <key> <起始位置> <结束位置> 获得值的范围,类似java中的substring
setrange <key> <起始位置> <value> 用<value>覆盖<key>所存储的字符串值,从<起始位置>开始
setex <key> <过期时间> <value> 设置键值的同时,设置过去时间,单位秒
getset <key> <value> 以新换旧,设置了新值的同时获取旧值
2.list
底层数据结构是双向链表
常用操作命令
lpush/rpush <key> <value1> <value2> 从左边/右边插入一个或多个值。
lpop/rpop <key> 从左边/右边吐出一个值。值在键在,值光键亡。
rpoplpush <key1> <key2> 从<key1>列表右边吐出一个值,插到<key2>列表左边
lrange <key> <start> <stop> 按照索引下标获得元素(从左到右)
lindex <key> <index> 按照索引下标获得元素(从左到右)
llen <key> 获得列表长度
linsert <key> before <value> <newvalue> 在<value>的后面插入<newvalue> 插入值
lrem <key> <n> <value> 从左边删除n个value(从左到右)
3.set
底层数据结构是hash+整数数组
4.zset
底层数据结构是ziplist+跳表
5.hash
底层数据结构是ziplist+hash
先更新数据库再删除缓存,若有错误需要重试
缓存穿透:指大量请求访问一个本身不存在的数据,使得请求直达存储层,导致负载过大。
处理手段:使用过滤器进行拦截,若请求的数据不存在则直接返回空值。
缓存击穿:指一份热点数据缓存失效,突然涌入大量的访问请求,使得请求直达存储层,导致负载过大。
处理手段:1. 永不过期:对热点数据不设置过期时间 2.加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值
缓存雪崩:大量数据同时过期,缓存层无法提供服务,使得请求直达存储层,导致负载过大。
处理手段:1. 永不过期:对热点数据不设置过期时间 2.避免数据同时过期,设置随机过期时间
对于键值对读写是单线程的,而对于持久化、异步删除是依赖其他线程来执行的。事实上他底层并不是单线程的
select / poll / epoll 这是三个多路复用接口
select/poll
select 实现多路复用的方式与poll的方式类似:将已连接的 Socket 都放到一个FD(文件描述符)集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 进行标记, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。
epoll
epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述符,减少了内核和用户空间大量的数据拷,提高了效率。
epoll 内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中
1.实现异步处理
有时候我们会遇到这样的场景,用户在客户端提交了一个请求,后端处理这个请求的业务相对比较复杂,如果这个请求使用的是同步调用,客户端就会出现发送请求后过了很久才相应的情况,这对用户体验来说是十分致命的.如果说用户并不关心请求是否处理,对于一些耗时的非事务性的业务处理,我们可以使用mq异步请求的方式,将处理信息放入队列,由后端监听队列自行处理,在将消息存入队列后直接返回客户端相应,加快响应速度.
2.实现分布式系统之间的应用解耦
在分布式系统中,经常会出现一个服务会有多个消费端调用,而且可能每个消费方需要接入的逻辑不一致问题。将消息写入消息队列,需要消息的系统自己从消息队列中订阅,实现解耦。
3.流量削峰
例如秒杀活动,可能在短时间内会有很大请求同时到后端,如果后端对每个请求都执行业务操作,例如查询数据库和写数据库,会造成服务器压力过大,同时,在同一时间进行大量数据库操作,可能会出现数据异常,我们可以使用mq实现缓冲,将所有请求先放入消息队列中,服务端每次处理业务先从消息队列获取.
通道的建立-------三次握手:
(1)在建立通道时,客户端首先要向服务端发送一个SYN同步信号。
(2)服务端在接收到这个信号之后会向客户端发出SYN同步信号和ACK确认信号。
(3)当服务端的ACK和SYN到达客户端后,客户端与服务端之间的这个“通道”就会被建立起来。
通道的关闭——四次挥手:
(1)在数据传输完毕之后,客户端会向服务端发出一个FIN终止信号。
(2)服务端在收到这个信号之后会向客户端发出一个ACK确认信号。
(3)如果服务端此后也没有数据发给客户端时服务端会向客户端发送一个FIN终止信号。
(4)客户端在收到这个信号之后会回复一个确认信号,在服务端接收到这个信号之后,服务端与客户端的通道也就关闭
粘包是指发送方发送的若干包数据到接收方接收时粘成一包
原因:
解决:
在每次使用tcp协议发送数据流时,在开头标记一个长度信息,并固定该报文长度,当服务端接收数据,判断客户端发送数据流长度,并只接收该长度字节数据,就可以实现拆包,完美解决tcp粘包问题.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。