final
- 构造函数中对象的final字段赋值,与随后将该对象引用赋值,两者不能重排序
- 读取某个对象,与读取对象final的字段,两者不能重排序
paxos协议可能难以理解,但是并不复杂,简单来说主要基于以下几个原则:
prepare阶段:Proposer使用序号s向过半数的Accepor发送prepare请求,Acceptor收到请求后检查序号是否符合要求,一旦符合要求Acceptor则会响应请求表示通过。如果之前没有通过提案,响应中不包含任何提案信息,如果之前通过某些提案,则相应中包含最大序号的提案。
accept阶段:如果Proposer收到了半数以上Acceptor的响应则发出accept请求,请求内容包含之前prepare请求的序号,以及本次提案的值,如果prepare阶段半数以上Acceptor响应的提案全部为空,则本次accept请求的值为Proposer要提交提案的值,如果prepare阶段过半数响应中有非空提案,则本次accept请求的值为提案集合中序号最大提案的值。Acceptor收到请求后仍然只需要检查序号是否符合要求,只要序号符号要求就会通过提案响应请求。
commit阶段:如果Proposer收到了来自半数以上Acceptor的accept请求响应,则发出commit请求给每个Acceptor,通知所有Acceptor提交之前通过的提案。
dubbo是阿里巴巴开发的RPC框架,虽然阿里集团内部使用dubbo并不多,但是近年来dubbo被很多其他互联网厂商广泛使用,这也许就是所谓墙内开花墙外香吧。虽然它并不完美,(如果完美可能就不会有HSF了吧),但是它有足够的理由让大家认可。
Dubbo的默认协议即Dubbo协议,它使用单TCP链接的Hessian序列化协议,Hessian虽然性能不及ProtocolBuffer、Thrift,但其不需要中间解释文件,相对灵活,对于一般公司来说已经足够。至于单链接也许会存在争议,有人可能会说单链接存在带宽问题,其实如果不是某个请求发送大文件,业务之间不会相互影响,而且性能略好,但是如果网络环境较差再加上业务量陡增的话就悲催了。
对于RPC框架的服务端来说,主要功能无非有两个,即:数据传输 + 协议。
广义上Dubbo和Netty同属于RPC框架的范畴,两者对比来看:
epoll提供了3个系统调用
ET:边缘触发、LT:水平触发
最初epoll只提供了ET模式,因为ET模式对于用户来说难以操作,才妥协推出了性能稍低LT模式,那就看一下两者的区别;
ET模式下,只有某个描述符的状态变为可用后(不可读变为可读/不可写变为可写),调用epoll_wait才会返回该描述符。LT模式下,只要当某个描述符处于某种状态时(可读/可写),每次调用epoll_wait都会返回该描述符;
当用户使用ET模式时,需要在两次epoll_wait调用之间将活跃的描述符一次性将数据读完,或者一次性将数据发完/填满,或者需要在用户代码中保存句柄状态。
从性能因素考虑,ET模式效率会高一些,因为活跃的描述符会存放在数组当中,LT模式下每当用户IO操作完成之后,要检查描述符的状态来确定是否删除数组中的描述符;而ET模式只要用户epoll_wait之后,扫过数组就会删除描述符;
selector
init():初始化NIO.selector
SimpleIoProcessorPool -> IoProcessor
bindInternal():调用startupAcceptor(),执行Accept.run();
selector
init();打开selector;
Processor -> Runnable
add():将新session加入到Queue,随后检查如果第一次被调用的话则启动startupProcessor()方法执行Processor.run();
NioSocketSession -> NioSession -> AbstractIoSession
异步Io是Mina等网络的框架的重要功能,它大大降低了用户操作网络IO的复杂度,这其中由于TCP的协议特点,通过TCP发送数据的异步实现更是尤为重要,本文就简述一下Mina写异步的代码实现。
调用session.write() -> 调用session中的filterChain.fireFilterWrite(WriteRequest) -> 从TailFilter一直到HeadFilter,在HeadFilter中调用processor.write方法 -> AbstractPollingIoProcessor.write:将session列入flushingSession当中,随后的IO线程将发送flushingSession中session的WriteRequestQueue所有WriteRequest。
因为是异步操作,所以Future是必不可少的,Mina提供了WriteFuture作为WriteRequest发送成功后的通知。WriteFuture被组合在WriteRequest当中,并在调用session.write()时返回。
DefaultWriteFuture -> DefaultIoFuture
WriteFuture阻塞:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public IoFuture await() throws InterruptedException {
synchronized (lock) {
while (!ready) {
waiters++;
try {
// Wait for a notify, or if no notify is called,
// assume that we have a deadlock and exit the
// loop to check for a potential deadlock.
lock.wait(DEAD_LOCK_CHECK_INTERVAL);
} finally {
waiters--;
if (!ready) {
checkDeadLock();
}
}
}
}
return this;
}
当IO线程将WriteRequest发送完成时,调用filterChain的fireMessageSent(),唤醒等待的线程。
WriteFuture释放:1
2
3
4
5
6
7
8
9
10
11
12
13
14public void setValue(Object newValue) {
synchronized (lock) {
// Allow only once.
if (ready) {
return;
}
result = newValue;
ready = true;
if (waiters > 0) {
lock.notifyAll();
}
}
notifyListeners();
}
Mina的责任链数据结构主要是一个双向链表,链表项为Entry的实现EntryImpl,DefaultIoFilterChain中保存了链表的head和tail。
EntryImpl是IoFilter的包装,还提供了前后节点的链接。需要关注的是NextFilter类,由于责任链的两端都可能触发事件,不同事件在链中的传递方向不同,所以IoFilter的实现需要根据不同的事件来判断下一个需要接收事件的Entry是在哪个方向,而Nextfilter正是将这部分逻辑从filter中分离出来,Nextfilter中包含了双向链表中两个方向所有可能发生的事件类型,这样IoFilter只需要根据不同事件调用NextFilter中不同方法即可。
IoFilter是责任链模型中的每个节点主要代码需要实现的接口,包含messageReceive、messageSent等方法。
这篇文章主要总结一些个人认为TCP中比较重要又容易引起混淆的理论
因为tcp协议是可靠的协议,接收端需要告诉发送端目前它收到了哪些包以及哪些包需要重传,ack就是用来是用来对tcp数据包的确认。简单来说,接收方每接受一个数据包就应该响应一个ack,但是大多数tcp实现认为这样做没必要,它们的做法是累计确认,而且最多只累积确认两个数据包。也就是说,假设A向B发送了10个数据包,而且数据包都是正常按序到达,B接收数据包过程中只需要间隔响应5个ack就可以了。
至于延迟ack的意义,除了上文提到的可以提高ack的传输效率,还可以进行“捎带”操作。
延迟ack似乎也只是在正常情况下才会使用,那什么情况下接收方不会累计确认而会立即响应ack呢?看一下ack的发送时机:
TCP状态