面试题
synchronized和lock的区别
1.作用位置不同
synchronized可以给方法,代码块加锁,lock只能给代码块加锁
2.锁的华侨锁和释放机制不同:
synchronized无需手动获取和释放锁,发生异常会自动解锁,不会出现死锁
lock需要自己加锁和释放锁,如lock()和unlock(),如果忘记使用unlock()则会出现死锁
所以一般我们会在finally里面使用unlock()
synchronized修饰成员方法时,锁对象就是当前对象
synchronized修饰静态方法时,默认的锁对象,是当前类的class对象,比如User.class
66,TCP和UDP的区别
首先,两者都是传输层协议
其次,
tcp提供可靠的传输协议,传输前需要建立连接,面向字节流,传输慢
udp无法保证传输的可靠性,无需创建连接,以报文的方式传输,效率高
TCP三次握手:
-
客户端给服务端发送一个随机数x
-
服务端给客户端发送x+1和一个随机数y
-
客户端给服务端发送x+1和y+1
TCP四次挥手:(双工通信模式)
-
客户端-->服务端,FIN=1,seq=u
-
服务端-->客户端,ACK=1 ack=u+1 seq=v
-
服务端-->客户端,FIN=1 ack=ack+1 seq=w
-
客户端-->服务端,ACK=1 seq=x ack=w+1
7层网络模型
7.应用层 HTTP,FTP,SMTP,DNS.Telenet,RIP,TFTP
6.表示层
5.会话层
4.传输层 TCP,UDP
3.网络层 IP,ICMP,OSPF,BGP,IGMP,ARP,RARP
2.数据链路层 SLIP,CSLIP,PPP,MTU,ARP,RARP
1.物理层 ISO2110,IEEE802
如何防止死锁:
1. 尽量采用tryLock(timeout)方法,可以设置超时时间,这样超时之后,就可以主动退出,防止死锁(关键)
-
减少同步代码嵌套操作
-
降低锁的使用粒度,不要几个功能共用一把锁
反射:
一种在程序运行时,动态获取当前类对象的所有属性和方法的能力,可以动态执行方法,给属性赋值等操作的能力
Class代表的就是所有的字节码对象的抽象,利用反射实现动态效果
对Spring的认识:
核心的IOC容器技术(控制反转,依赖注入),帮助我们自动管理依赖的对象,不需要我们自己创建和管理依赖对象,从而实现了层与层之间的解耦,所以重点是解耦。
核心的AOP技术(面向切面编程),方便我们将一些非核心业务逻辑抽离,从而实现核心业务和非核心业务的解耦,比如添加一个商品信息,那么核心业务就是做添加商品信息记录这个操作,非核心业务比如事务管理,日志,性能检测,读写分离的实现等。
Spring 的bean作用域有哪些?
1.默认是singleton,单例模式
2.prototype,
3.request
4.session
5.global-session
Spring的bean是线程安全的吗?
Spring的bean模式是单例,后端的程序,处于一个多线程的工作环境
bean是无状态的,从这点来说是安全的
所谓无状态就是没有存储数据,即没有通过数据的状态来作为下一步操作的判断依据。
事务的传播特性以及Spring支持的特性有哪些?
PROPAGATION_REQUIRED: 支持当前事务,如果当前没有事务,新建一个事务。这是最常见的选择
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_REQUIRES_NEW
PROPAGATION_NOT_SUPPORTED
PROPAGATION_NEVER
乐观锁,悲观锁:
悲观锁是利用数据库本身的锁机制来实现的,会锁记录
实现方式是:select * from t_table where id = 1 for update
乐观锁是一种不锁记录的方式,采用CAS(compare and set/swap)模式,减一采用version字段来作为判断的依据。
每次对数据更新的操作,都会对version+1,这样提交更新操作是,如果version的值已经被更改,则更新失败
乐观锁如果不选择version字段,选择其他字段,比如业务字段store,那么可能出现ABA问题(中间经历过其它状态)
MyBatis缓存机制,一级缓存、二级缓存
缓存,主要作用是提高查询性能,减少跟数据库交互次数,从而减轻了数据库承受的压力
适用于读多写少的场景,如果数据变化频率非常高,则不适用。
MyBatis一级缓存:
默认生效,作用域在于sqlSession,如果中间有对数据的更新操作,则将清空一级缓存
MyBatis二级缓存:作用域SqlSessionFactory,作用域比一级缓存更大
默认开启,
开启需要设置:1.<setting name ="cacheEnabled" value="true"/>
2.在Mapper.xml中,配置二级缓存(也支持在接口配置)
在标签<mapper>下面添加<cache/>标签即可
默认二级缓存的特点:
所有的select语句将会被缓存
所有的更新语句将会刷新缓存
缓存将采用LRU(least recently used最近最少使用)算法来回收
缓存会存储1024个对象的引用,
回收算法建议采用LRU,还提供了FIFO,SOFT(软引用),WEAR(弱引用)等其他算法
配置例子:
<cache evicition="LRU" readonly="true" size="1024"></cache>
MyBatis的二级缓存默认采用的是Map来实现,可以集成第三方缓存来保存MyBatis的二级缓存,如EhCache和Redis,推荐Redis
MyBatis分页方式:
逻辑分页:使用MyBatis自带的RowBunds分页,它会一次查出多条数据,然后再检索分页中的数据,具体一次查多少条数据,受封装JDBC配置的fetch-size决定
物理分页:从数据库查询指定条数的数据,分页插件PageHelper实现就是物理分页
MyBatis分页插件(拦截器)原理:
分页插件的原理就是使用MyBatis提供的插件接口,实现自定义插件,在插件的拦截方法内,拦截待执行的SQL,然后根据设置的dialect和设置的分页参数,重写SQL,生成带分页语句的SQL,执行重写后的sql,从而实现分页
发起请求到响应要经历什么?
1.浏览器检查协议,域名,请求参数的格式输入是否正确
2.检查是否有缓存,有就直接使用缓存
3.到DNS服务器进行域名解析,获取IP地址
4.通过3次握手协议和服务器建立连接
5.通过4次挥手和服务器断开连接
synchronized底层原理:
synchronized是由一对monitorenter和monitorexit指令来实现同步的,在JDK6之前monitor的实现是依赖于操作系统内部的互斥锁来实现的,所以需要进行用户态和内核态的切换,所以此时的同步操作是一个重量级的操作,性能很低
但是JDK1.6带来了新的变化,提供了3中monitor的实现方式,分别是偏向锁,轻量级锁,重量级锁,即锁会先从偏向锁再根据情况逐渐升级到轻量级锁和重量级锁。
这就是锁升级
在锁对象的对象头里面有一个threadid字段,默认情况下为空,当第一次有线程访问时,则将该threadid设置为当前的线程id,我们称之为获取偏向锁,当线程执行结束,则重新将threadid设置为空(用户态)
之后如果线程再次进入的时候,会先判断threadID与该线程的id是否一致,如果一致,则可以获取该对象,如果不一致,则发生锁升级,从偏向锁升级为轻量级锁(用户态)
轻量级锁的工作模式是通过自旋循环的方式来获取锁,看对方线程是否已经释放了锁,如果执行一定次数后,还是没有获取到锁,则发生锁升级,从轻量级锁升级为重量级锁(内核态)
偏向锁、轻量极锁实际上是一个缓冲的过程,不用马上就从用户态进入内核态,减少锁带来的性能消耗
synchronized如何保证可见性?
就是当获取到锁之后,每次读取都从主内存读取,当释放锁的时候,都会将本地内存的信息写到主内存,从而实现可见性
synchronized和volatile的区别?
-
作用位置不同synchronized是修饰方法、代码块,volatile是修饰变量
-
作用不同,synchronized,可以保证变量修改的可见性以及原子性,可能会造成线程的阻塞
-
volatile仅能实现变量修改的可见性,但无法保证原子性,不会造成线程的阻塞
volatile的作用:
是一个轻量级的线程同步机制,特征之一:保证变量在线程之间的可见性。
所谓的可见性是指当一个线程修改了变量的值之后,其他线程可以感知该变化。
为什么会有可见性问题?
因为硬件速度不匹配问题,CPU的速度要明显快于主内存
为了解决速度不匹配问题,在CPU和主内存之间有多级缓存。
这个时候会发生,一个线程修改了数据,数据还没有及时刷到主内存,那么其他线程读取到的数据依然还是旧的,这就是可见性问题发生的根源。
通过给变量设置volatile关键字修饰,可以保证变量在线程修改之后,会刷新到共享内存,这样其他线程就可以读取到最新的内容。
volatile保证了在多个线程之间是可见的,但不能保证原子性操作。
当变量被声明为volatile之后,线程每次都会从主内存读取,而不会去读取自己的工作内存,这样实现了线程之间的可见性。
前后端分离,接口文档,:URL, 请求方式,请求参数,返回参数,示例
一般采用swagger生成接口文档
服务拆分:
-
抽取公共的基础服务(短信,邮件,文件管理)
-
以业务为边界,拆分服务
-
要管理这么一堆服务,需要注册中心(Eureka,zookeeper)
-
服务之间需要通信,通信方式:
4.1 同步调用
Dubbo,RPC的方式,底层采用Netty来实现,基于TCP建立的长连接
注意:BIO,NIO只是一种网络通信模式
BIO( Blocking I/O ):为每个连接建立一个线程
NIO( Non-blocking I/O):一个线程服务多个连接,有弊端,当连接数太多,性能下降
Netty:封装NIO,在它的基础上,添加注册处理组,BossGroup,WorkerGroup
编程思想:Reactor(反应堆)思想,NIO,Netty是一种实现
SpringCloud ,restful http的方式,短连接的方式
性能角度: Dubbo>SpringCloud
4.2异步通知
MQ调用方无需等待执行放处理的结果
5.服务之间的共享资源,如何保证数据的安全性
单体架构:单机部署的时候,采用JDK提供的synchronized就可以实现
分布式架构:多机部署的时候(多个JVM),上面的方式就不能满足,只能控制一个JVM
分布式锁的方案:保证锁是共享的第三方资源,1.上锁,2.解锁
数据库的方式:t_lock ,id lock(0)
上锁:将数值修改为1
解锁:将数值修改为0
数据库:创建一张表,t_lock,其中包含字段,lock(1),默认为0,有客户获得锁,将其置为1
Redis:setnx: 不存在就设置成功,否则,设置失败(最终可用性,AP模式,可用性要高些)
上锁:成功执行setnx key value
解锁:delete key
避免死锁:需要设置过期时间expire key timeout
注意:需要将两个操作变成一个原子操作
4.0之前:Redis+lua脚本 ,lua脚本帮助我们扩充Redis指令
4.0之后:直接使用线程的指令,在设置数据的时候,同时设置时间
避免无锁:释放锁的时候,检查这把锁是不是我的
zookeeper:采用树状节点的方式来保存我们的服务的注册信息(znode)
以节点作为锁,创建成功,表示获取锁成功
上锁:创建节点lock
解锁:删除结点lock
避免死锁:节点,为临时节点类型,客户端如果挂掉,会自动删除
节点分:持久性、非持久性(临时节点)
存在“羊群效应”:一旦锁失效后,假设有上百个客户端都会去争抢这个锁
解决方法:时序节点(节点有序号)
Eureka:
EurekaServer的注册表存储结构,采用内存的方式来管理服务信息。map结构
服务注册:新增一个key-value的映射关系
服务发现:获取一个key-value的映射集合
ConcurrentHashMap:一个线程安全且性能兼顾的map(HashTable:线程安全,但性能一般)
-
解决服务之间的session共享问题:
有状态:服务器依然要存储用户的信息生成cookie(user_token:uuid),从而来帮助我们确定存储在Redis中的用户凭证信息
此处的UUID等同于之前的sessionid,有状态的方式需要消耗一定的内存空间来存储用户信息
无状态:服务器不需要保存用户的状态,但是需要耗费一定的CPU资源来计算
基于一种算法的方式:JWT(JSON WEB Token)
token:用户本身信息+时效信息
依赖于解析结果,如果发生异常,说明凭证已经失效,如果没有异常,说明状态正常。
cookie之所以能够成功,有一个非常重要的前提:所有的子系统都是同父域的情况,无法实现跨域
如何理解CAP
C:一致性,每一次请求得到的数据是一样的,每个节点保存的数据是一致的
A:可用性,每次请求都可以得到正确的响应,但是数据不一定是最新的
P:分区容错性,
分区:是一种现象,由于网络的不稳定性,造成部分节点之间的通信出现了问题,于是就产生了分区
容错性:是一种目标,就是发生分区之后,服务的节点是否能对外提供服务
QPS:query每秒可以处理的查询请求量
TPS:transaction事务,每秒可以处理的事务请求数量
响应时间,处理一次请求消耗的时间
吞吐量:单位时间可以处理的请求数量
并发量:同一时刻有多少请求访问服务器
PV:page view当前页面被访问的次数
UV:user view当前被多少用户访问
日活:一天活跃用户
调优,通过压力测试工具,以上性能指标有没有变化
如何应对高并发?
1.垂直升级
单机硬件升级
摩尔定律
2.水平扩展升级
单机的硬件发展速度跟不上业务的发展速度
所以我们要求采用水平扩展升级的方案,这样在理论上,只有增加机器就可以让处理能力无限扩展
3.整体的基数架构图
三大法宝:限流,缓存,熔断
如何实现分布式系统的数据一致性问题(分布式事务)
之前我们控制事务的实现非常简单
1编写一个service方法,里面可以做多个dao操作,然后再seervice方法上实现事务的控制
,使用SpringAOP实现了事务的控制
2,局域数据库本身的事务机制,从而保证数据的原子性
现在结构发生了变化,所以原先的解决方案没用了
采用分布式事务,实现类似上述达到的效果
1.刚性事务,依然要么都成功,要么都失败
2PC,TCC
2.柔性事务,追求的是数据的最终一致性,允许出现中间状态
基于MQ方式来实现
生产者 --> MQ --> 消息消费者(各种服务)
关键点:MQ如何保证消息可达(不丢失)
对BASE理论的理解
BA(basiclly available)基本可以
S (soft state)柔性状态
E (Eventual Consistency)最终一致性
BASE理论,通过牺牲强一致性,保证最终一致性,来获取高可用性
谈谈Dubbo
Dubbo的底层网络通信是基于Netty网络框架,而Netty是对NIO的封装
NIO可以多路复用,效率高
SpringCloud是一系列主流框架的集合,基于SpringBoot进行开发
屏蔽了复杂的配置和实现原理,给用户提供的一套简单易懂,容易不是,容易维护的分布式系统开发工具集
解决的是非业务的共性问题
集成了:服务注册与服务发现(注册中心):Eureka Server
负载均衡:Ribbon
熔断: Hystrix
配置中心: config server
网关:Zuul,SpringCloudGatway
消息总线:
链路监控:zipkin server
做到独立部署,可方便进行水平扩展,独立访问等特点
Spring Cloud VS Dubbo
1.Dubbo,RPC,性能比http性能好,并发能力强
2.springcloud,http协议,性能差些
3.springcloud主打微服务架构全家桶,组件齐全,功能齐全
4.融合,springcloud融合其他组件,如springcloud alibaba
适合缓存的数据:
1.高频访问数据
2.变化频率不高
2.非敏感数据
Redis基本数据类型:
string:用户信息,分布式锁,验证码,(点赞,点踩,数字)
hash:可以快速定位,需要存储信息,且这个信息需要频繁被修改时,就可以采用这个结构
list:队列和栈,双向列表(秒杀,保存待抢购的商品列表)
set:无序唯一(唯一,秒杀,保存抢购到商品的幸运用户,保证每个人每件商品只能抢购一件)
zset:可排序特性,分数(数值,可排序,排行榜)
Redis内存回收:
1.惰性回收,执行get指令,刚好这个key已经过期了,那么这个时候会自动删除
2.采用定时回收的方式 10/s
3.具体回收算法:
TTL:过期时间
LRU:最近最少使用,采样方式
Redis持久化策略
1.RDB,产出二进制文件,默认开启
save 900 60
触发持久化,并非实时的方式,适合做全量备份
2.AOF,产出日志文件,默认不开启
触发时机:每秒*(推荐),每次操作
如何实现缓存的高可用?
单点情况,放生故障对系统造成很大影响
实现:
主从方式,同时有哨兵这个服务来监控主从节点的健康状况
当主节点宕机,此时哨兵服务具体要做什么呢?
1.哨兵要选择一台从机器作为新主机,这里面有一个关键指标,就是偏移量,这个是来看主机跟从机数据同步的状态,执行指令:slaveof no master
2.其他的从机要重新建立跟新主机的关系,slaveof newip newport
3.客户端是连接哨兵服务的,所以哨兵服务会给客户端返回新的主机地址
哨兵服务监控主机的情况,主机保存从机的信息,所以哨兵也会监控从机的状况
RedisCluster:
redis3.0之后的版本提供的特性
特点:
1.没有中间层,客户端直接更RedisCluster的节点进行连接
2.每个主节点都支持读写操作,分担了写的压力,后期继续增加新主机,然后做hash迁移,新主机就可以分担压力
3.因为每个节点负责的区域是不同的,所以往里面保存数据的时候,要根据key来做crc16算法,从而得到一个值,从而确定存储哪个节点
4.为了保证每个节点的高可用,所以每个节点可以做主从
消息中间件应用场景:
适合的场景:不需要实时性的
1.解耦
2.异步化
3.限流削峰:前后两个交互的系统处理的速度不匹配,为了保护处理慢的系统,从而引入消息中间件,来实现削峰限流处理
方案1 ,异步写的方案,保护数据库,缓解瞬间写的压力
方案二:比如秒杀系统,会产生很多订单消息,此时也可以通过MQ来保护订单系统,节省服务器成本
OCP原则:对修改关闭,对扩展开放
如何保证消息的可达或不丢失
1.确认机制+补偿发送
异步confirm模式,客户端通过设置comfirm监听器,获取MQ服务器的异步响应。如果消息成功传递到MQ服务器,则服务器会传递回ACK=true,否则ack=false
消息队列做持久化,宕机后重启不会丢失未处理的消息
消费端的手工确认模式开启,只有消费端手动确认后,才表示这个信息已经被正确处理
避免消息重复处理依然失败的情况,设置重复处理3尺,如果3次还有问题,则错误信息记录到日志表,然后人工介入处理,
避免堵塞后面的消息处理
有可能MQ服务器没有收到消费端发送的确认消息,会重复处理,所以要保证接口的幂等性,不会被重复消费
延迟队列?
需要延迟处理的消息,我们将使用延迟队列来达到这个效果
消息超过了有效期,没有人处理,就称为死信
限流降级:
Nginx
计数器
滑动时间窗口
令牌桶、漏桶算法
Hystrix
防刷验证码
流量削峰
秒杀下单接口地址隐藏
秒杀系统高阶优化:
Redis缓存高可用集群,Redis单机大概支持几万并发
独立秒杀服务
前端优化:
页面静态化
页面资源优化
js/css压缩,减少流量
JS/css组合,减少连接数
CDN优化
Redis缓存雪崩:
大量缓存在同一时间失效,导致数据库访问量瞬间上升,导致无法正常提供服务
解决办法:
设置随机缓存失效时间
Redis集群 部署,不同热点的key放到不同的节点上去
不设置缓存失效时间(暴力解法)
定时刷缓存
缓存穿透:
使用不存在的参数发起请求,Redis查不到这样的数据,然后就会把请求打到数据库上
解决办法:
如果请求穿透Redis,无论数据库查询到什么结果都缓存到Redis,下次请求就不会穿透Redis
可以拉黑IP
校验参数合法性
使用布隆过滤器
缓存击穿:
当某一个非常热点的key失效的时候把大量请求打到数据库,导致服务失效
解决办法:
缓存永远不过期
分布式锁,互斥锁:请求数据库的时候上锁,只能由一个用户能操作这个数据库,然后把查询结果缓存到Redis,其他没有抢到的线程先睡几毫秒,可以使用zookeeper实现这个分布式锁
分布式锁
1.基于数据库
2.基于缓存(Redis等)
3.基于Zookeeper
基于数据库缺点:
-
强依赖于数据库,数据库是一个单点,一旦数据库挂掉,会导致业务系统不可用
-
锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在数据库中,其他线程无法再获得锁
-
这把锁只能是非阻塞的,因为数据的insert操作,一旦插入失败就会直接报错,没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作
-
这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁,因为数据库中数据已经存在
解决方案:
-
做主从数据库,数据之间双向同步,一旦主库挂掉迅速切换到从库
-
做一个定时任务,每隔一段时间把数据库中超时的数据清理一遍
-
搞个while循环,直到insert成功再返回成功
基于Redis缺点:
在主从结构中存在明显的竞态:
客户端A从master获取到锁,
在master将锁同步到slave之前,master宕掉
slave节点被晋级为master节点
客户端B获取了同一个资源被客户端A已经获取到的威朗我一个锁。安全失效。
Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Znode
Znode分四种类型:
-
持久节点(Persistent),默认类型,创建节点的客户端和zookeeper断开连接后,该节点依旧存在
-
持久节点顺序节点(Persistent_sequential),根据创建的时间顺序给该节点名称进行编号
-
临时节点(Ephemeral),创建连接的客户端与zookeeper断开链接后删除节点
-
临时顺序节点(Ephemeral_sequential)
Zookeeper分布式锁原理:
Zookeeper分布式锁应用了临时顺序节点
详细步骤:
获取锁:
首先在Zookeeper中创建一个持久节点ParentLock,在第一个客户端想要获取锁时,需要在Parentlock这个结点节点下面创建一个临时顺序节点Lock1
之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获取锁。
这时候,如果有一个客户端Client2前来获取锁,则在ParentLock下再创建一个临时顺序节点Lock2,
client2查找parentLock下面的所有临时顺序节点并排序,判断Lock2是不是顺序最靠前的节点,发现节点Lock2并不是最小的。于是,client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听lock1节点是否存在,这意味着client2抢锁失败,进入等待状态。
如果又有client3前来获取锁,就会在parentlock下新建临时顺序节点lock3,发现自己不是顺序最靠前的节点,于是就向排序比它前的lock2注册watcher。如果后面还有新的客户端,就在前面的临时节点注册一个watcher,形成一种链。
释放锁:
任务完成,客户端显示释放
任务执行过程中,客户端崩溃,相关联的节点会自动删除
前面的节点删除,监听它的客户端就会立刻收到通知,这时后面的节点会再次查询parentlock下面的所有节点,确认自己创建的节点是不是目前最小的节点,如果是最小的就获得锁,否则继续等待。
缺点:
性能上可能没有缓存服务那么高,因为每次在创建锁和释放锁的过程中,都要动态创建、销毁临时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同步到所有的Follower机器上。
可能带来并发问题,只是不常见。由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就会获取到分布式锁了,就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端有多种重试策略,多次重试之后还不行的话才删除临时节点。
Redis速度快的原因:
-
基于内存,时间花费主要集中在IO上,所以读取速度很快
-
单线程模型,保证每个操作的原子性,不用进行线程之间的切换和竞争
-
使用非阻塞IO,IO多路复用,使用了单线程来轮训描述符,将数据库的开、关、读、写都转换成了事件,减少了线程的上下文切换和竞争
-
全程使用hash数据结构,还有特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如跳表,使用有序的数据结构加快读取的速度
-
Redis采用自己实现的事件分离器,效率高,内部采用非阻塞的执行方式,吞吐能力比较大
IO多路复用技术详解:
同步阻塞IO(Blocking IO)
同步非阻塞IO(Non-blocking IO)
IO多路复用(IO Multiplexing)(异步阻塞IO)(最常使用)( Reactor反应堆设计模式 )
异步IO(Asynchronous IO)( Proactor设计模式 )
IO多路复用模型是建立在内核提供的多路分离函数select基础之上,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。
使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞的模型中,必须通过多线程的方式才能达到这个目的。
dubbo心跳机制
网络层的可用性:使用了TCP中的keepAlive机制
应用层的连接可用性:心跳机制
就是客户端会开启一个定时任务,定时对已经建立连接的对端应用发送请求,服务端需要特殊处理该请求,返回响应。如果心跳持续多次没有收到响应,客户端会认为连接不可用,主动断开连接。
Netty提供一个经典的时间轮询定时器实现心跳机制
Dubbo采用双向心跳设计,即服务端会向客户端发送心跳,客户端也会向服务端发送心跳,接收方更新lastread字段,发送方更新lastwrite字段,超过心跳间隙的时间,便发送心跳请求给对端
长连接和短连接
HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开,等待在同域名下继续用这个通道传输数据;相反就是短连接。