Shell's Home

FIN-WAIT-1的问题一例

Feb 26, 2015 - 1 minute read - Comments

这是一个早应该知道的事情。但是还是被整了半天。 引子 tcp关闭时有多少个状态? 当当,别数了,应该是6个,不算CLOSED。分别是FIN-WAIT-1/FIN-WAIT-2,CLOSING, TIME_WAIT, CLOSE_WAIT,LAST_ACK。如果不能瞬间想起一个方块来,说明tcp状态不算熟。 问题 今天的故事来自今天BI同事提出的一个问题。在线上,他发现这么一个现象。在一组系统中,客户端全都FIN-WAIT-1了,但是服务器还是ESTAB。 我的第一反应很简单,这明显差了一个FIN包的距离。而且鉴于两者在同一网络中,而且重复出现。建议他首先排查中间的防火墙设备和防火墙设置。 会找上我的问题,当然没这么简单。中间没有任何防火墙或软件防火墙设定。 分析 下一步呢?有点没方向了。抓包分析。发现FIN端向ESTAB端不停的发起ACK,但是看起来和FIN没什么太大关系。 偶然,同事注意到所有出现现象的链接都有写缓冲区数据。这是一个不常见的现象。写缓冲区一般会有点数据,但是应该很快就被消费,而不会长期堆积,更不会长期维持同样的数字。这是写缓冲区满。结合刚刚的ACK,其实本质是对端停止消费数据。 这是一个TCP的边角。当读缓冲区满的时候,tcp协议栈会声明window=0。当读缓冲区恢复的时候,读方会用ack with window来重新宣告可用缓冲区。但是在tcp里,ack是不重传的。所以这个ack会丢失。因此写方有责任定期请求确认读方window,来确定整个过程不会卡死。这就是刚刚看到的不停ACK的现象。 而这里就有个非常重要的可能性——FIN包的处理方式。为此我阅读了源码。源码告诉我们,FIN包被接收到的时候,并不是即时处理的。实际上,在ESTAB状态收到的FIN,正常path下会进入tcp_data_queue。这个函数会将包堆积到队列中,并根据当前seq来处理包。主要包括三种seq,当前包,过去包,未来包。只有在以下两种情况下,fin包才会被处理: 当前收到一个fin包。 当前收到一个包,完成处理后out of order队列中有数据,因而进入tcp_ofo_queue。而队列中有fin包。 而不幸的是,当前包处理流程第一步就是判断tcp_receive_window。如果没空间了,会进入out_of_window过程。后者会快速的触发一个ack返回,然后就把包给丢了。 我猜对了开头,可是没有猜到结局。 结论 通过python的快速复现,验证了这个结论。建立一对连接,其中一个不接收任何数据,而其中一个发送足够长的数据。当读缓冲区满后,再去close,出现一端FIN-WAIT-1,一端ESTAB的现象。 因此,结论如下: 当写缓冲区满之后,收到的fin包会被丢弃,而发送端并不会重发。而只要写缓冲还有剩余空间,哪怕一个字节,都可以正常处理。 内核参数 根据文档,可能有几个内核参数与此有关。 net.ipv4.tcp_max_orphans net.ipv4.tcp_orphan_retries 测试表明,net.ipv4.tcp_max_orphans可以抑制这个现象。当减低这个数值后,再进入FIN-WAIT-1状态的连接会自动消失。ss -natp不能看到连接。有趣的是,如果进程尚未关闭的话,可以在/proc/[pid]/fd下面看到fd仍然存在,而且还可以读出数据。 抓包表明,连接实际是被一个RST干掉的。阅读源码,在tcp_close的最后部分,可以看到tcp_too_many_orphans被调用了。如果超过限制,就会发出reset,并且关闭连接。 而tcp_orphan_retirs,根据我的理解和测试,和这事没有关系。这主要是指这么一种现象:当对端机器poweroff(而不是shutdown)的情况下,你所发出的报文会丢失。因此理所当然的,写者的写缓冲区会很快充满。此时会发起连接探测,以确定对方是不是掉线了。套在close的这个case上,就是一边是FIN-WAIT-1,另一边死不响应。需要通过多次探测来宣告对方死亡。因此,如果对方机器死机导致不响应你的FIN,才是用tcp_orphan_retirs的场合。

p2p vpn的部署方法

Feb 9, 2015 - 1 minute read - Comments

p2p vpn的基本概念 p2p vpn这个概念的提出,是因为openvpn在数据传输上的一个特性——虚拟链路都是从拨入端到服务器的。例如vpn网关在北美,上海电信的两个人要通讯,数据就要从北美绕一圈。这个特性在多节点打通上无疑很扯,于是催生了很多p2p vpn。他们的基本理念是——尽力从端到端,不成再绕。而且为了解决端到端,顺便得解决NAT问题——也就是带有STUN打洞。 tun模式的三层转发表 先说明一点,大部分p2p vpn都是tun模式。这也很正常,tunnel用的么。但是大家在配置openvpn的时候,不知是否注意过iroute这个指令。为什么会有iroute指令的存在? tun模式是三层模式,相信大家都有数。也就是说,报文传递的时候只带有三层地址,openvpn也凭借三层地址来找到要转发给谁。这里和普通的网络就显示出区别了——普通网络使用ARP协议来自管理转发规则,而openvpn则是凭借内部写好的转发表。 例如vpn gateway的虚地址是192.168.100.1,节点1是100.5,节点2是100.10。那么节点2发送报文给节点1时,报文大约长这个样子。 192.168.100.10 -> 192.168.100.5 在普通网络中,第一步会查路由表,确定是eth0(虚拟网络是tun0)。然后在上面广播ARP请求,获得MAC地址。最后填写MAC地址,发送报文。但是在tun虚拟网络中,仔细看你的路由表,是不是整个虚网络都被交给了一个叫做100.4之类的奇怪gateway转发?甚至如果你没有打开client-to-client,整个虚网络只有一台可见,这台还是交给这个gateway转发的。 这是因为你到这个奇怪的IP之间还是走ARP过程,但是这个奇怪的IP收到你的报文后,就可以是纯三层过程了。你可以把这个IP视为本地openvpn的化身。openvpn会把你的报文发送到openvpn gateway,然后openvpn gateway再转发给正确的机器。也就是说,openvpn gateway必须知道某个目标IP需要转给哪个节点,物理地址多少,对吧? 作为纯虚网络,知道节点的IP很容易——毕竟是openvpn gateway管理的地址分配过程。但是作为tunnel和多地址打通,这里就有点困难了。例如节点1还有个网段是192.168.80.0/24,节点2还有个网段是192.168.60.0/24,那么如下一个报文从节点2中出去,你让openvpn gateway怎么办? 192.168.60.15 -> 192.168.80.15 你也许会说,我当然有配节点1的网关转给openvpn的拨入端。问题是,这个动作,openvpn的拨入端尚且不知,何况openvpn gateway?于是我们派生了route/iroute这两个指令。 route/iroute表示这个地址段归属于这个节点所有,区别在于route同时修改路由表,而iroute不修改路由表。配合push,可以由服务器端下发指令修改客户端路由表。 p2p vpn也有类似的问题。甚至,由于没有统一配置端,因此连每个节点的虚IP都不能很容易的得到。在配置中必须注意这点。 n2n n2n的模式比较简单,也比较有局限性。基本分为两个端,supernode和edge。supernode类似于hub,把所有edge拉到一起。edge都是对等的。 supernode: supernode -l port edge: edge -a [address] -c [name] -k [password] -l [supernode:port] -u [userid] -g [groupid] 解释一下。开一个supernode,不用做任何设定。反正supernode也不会持有edge别的信息。edge要设定supernode的ip和端口,然后提交name和password。name和password相同的,就进入同一个虚拟网络。然后自己的虚拟ip是address。最后的userid和groupid必须是数字。主要是因为开tun需要root权限,因此可以在获得完权限后退化成普通用户,以防权限太高。 这里好玩的就是,理论上address可以天南海北,完全不用管路由怎么走。甚至172.16.0.1可以和10.0.0.1通讯(我没实际确认)。因为大家都是看彼此的IP是否经过注册,而不是计算路由表。 但是这里就有个缺点,我看到man文档中只提到address,没提到可以提交一个网段。所以无论我怎么设定,使用三层方案做隧道的时候,n2n是转不过去的。因为他不知道这个网段归哪个节点管。 所以,n2n的p2p模式很便利,但是没法打tunnel(至少我不知道怎么玩)。 tinc tinc和n2n一样,也是一种p2p vpn。不过好处在于,tinc允许你在一个节点上配置多个网络,因此可以打tunnel。 在配置之前,我先约定两个词。“配置名”和“节点名”。一个配置是接入同一个网络的多个节点,还有他们如何拓扑。节点名就是一个节点的名字。所以,和配置有关的有以下两个。 /etc/tinc/nets.boot: 这里写上想自动启用的配置的名字 /etc/tinc/[configname]: 配置的根路径,以下路径全是相对路径。 配置 下面就是某个配置中的一堆文件。注意这些配置都是配置自己节点的属性信息。 tinc.conf: Name = [nodename] Device =

openvpn的几种基本模式

Feb 4, 2015 - 1 minute read - Comments

vpn的原始模式 vpn的最简模型,相当于在两台机器上插一块虚拟网卡,然后中间连一根虚拟网线连通。因此vpn才得名vpn(virtual private network)。 其复杂之处在于,这块虚拟网卡如何配置网络,和别的网卡是什么关系。再加上多个节点间如何通讯。种种都够新手喝一壶。 虽然openvpn在科学上网上是废了,但是在不出国的网络上用来保护通讯,还是非常好用的。 tap模式 tap模式的特点是二层打通。典型场景是从外部打一条隧道到本地网络。进来的机器就像本地的机器一样参与通讯,你分毫看不出这些机器是在远程。 优点: 配置简单。 不需要在所有机器上配置或者动网关。 缺点: tap在部分设备上不支持(例如移动设备)。 wlan加入网桥后不一定可以工作。 广播包会散发到虚拟网络中,可能极大消耗流量。 特别解说一下wlan。部分AP对一个客户只接受一个MAC地址,因此无法做网桥。这应该是wifi网络的常规问题了。解决方法是换AP,或者做mac-nat。 操作方法: 你需要先在当前网络中,为vpn预留一些地址。这些地址应该足够拨入用户使用,不应和dhcp撞车,不应有其他人使用。 而后,建立一个br,将当前工作的eth迁移过去。(具体细节就不说了,每个系统小有差别)再建立一个tap vpn,在启动脚本中指定加入这个br。 example 假定内网地址为172.19.0.0/24,其中保留172.19.0.16-172.19.0.31供vpn使用。 服务器配置: port [port num] proto udp ; 参考我上一篇[vpn不要走tcp协议](http://shell909090.org/blog/archives/2722) dev tap ca ca.crt cert server.crt key server.key server-bridge 172.19.0.16 255.255.255.0 172.19.0.17 172.19.0.31 ; 或者可以采用这句 ; server 172.19.0.16 255.255.255.240 ; 注意掩码实际上等于/28,做掩码运算后,这段地址和上面的保留地址重合 script-security 2 up vpn-start ; 建议使用绝对路径,避免版本坑 down vpn-stop vpn-start: brctl add br0 $dev vpn-stop: brctl del br0 $dev 客户端配置:

vpn不要走tcp协议

Feb 2, 2015 - 1 minute read - Comments

和大家唠叨一件小事。 vpn不要走tcp协议。 我原本以为这是个常识。因为当网络发生丢包时,vpn的那个tcp会等到超时,然后重发。但是vpn里面封装的tcp链路多半也超时了,也要重发。所以一旦发生丢包,整个链路上就会突然一下子变拥堵了。 那么能不能调整链路超时重发机制呢?这个比较困难。链路的超时重发是靠RTT(round-trip time)来工作的,因此VPN的RTT一定小于里面所封装tcp的RTT。如果你的vpn工作在一根很好的链路上,而tcp链路的对端要通过一个延迟非常高的网络。那么vpn的重发对tcp链路的影响并不大。但是我看到的大部分情况下,vpn是穿越互联网(而且大多是跨国或者跨洲网络)的,而vpn的双端都落在内部网络里。因此vpn构成延迟主体。这时候,vpn的RTT和里面所封装tcp的RTT几乎相差无几,两者会几乎同时超时。 什么时候会发生丢包呢?最常见的理由是带宽跑满。当路由器来不及处理数据的时候,就只有丢包了。例如某出国网络丢包三成,这就是拥塞爆了。当然我们也不否认其他理由,例如无线,天生就是有丢包的。即便没有上述理由,有的时候就是运气不好,无理由的发生一下丢包,也完全不需要奇怪。 所以,在大部分网络上,丢包是常态。而丢包后,内外两层tcp同时超时会引起严重的重传问题。所以tcp协议不是特别适合做vpn。 结果某vpn居然被配置到了tcp模式,而且还不能改。。。郁闷啊。。。

Charlie and Dave

Jan 27, 2015 - 1 minute read - Comments

公司希望弄一套双授权的安全系统,老大提供了一套算法,求大家review。如果这个方案确实可行,那么我们会做完然后开源出来给大家用。 Author and License Author: 韩拓 保留所有权利 All Rights Reserved 以下内容不以cc-by-sa3.0发布。(因为根本不是我的创作) 场景 Alice希望登录到Bob上执行操作。 两者的基本控制协议为ssh。 假定 攻击者名叫Mallory。 如果Alice的私钥泄漏,管理者必须有权停止Alice到Bob的访问而不需要更换所有Bob的公钥。 除去Alice和Bob外,参与通讯过程的所有机器(即下文中的Charlie和Dave)中可能随机被攻破一台。 服务都在内网,但是如果网关和被攻破的机器是同类系统,Mallory即可具有内网监听和伪造数据报文的权限。 Alice不会利用获得的Bob的权限故意执行危害性指令(但是可能被诱骗)。 Alice和Bob不会被攻破。 方案 假定有两台机器,Charlie和Dave,Dave和网关不得是同类系统。根据假定4,两台机器不会同时被攻破。 Alice通过SSL和Dave建立连接,上报自己的用户名,需要访问的设备和帐号,并提交一个临时生成的ssh pubkey(username, account, host, pubkey)。 Dave根据预先设的IP-username-sslkey验证用户身份为Alice,并且根据ACL确认其具有访问权限。 如果通过验证,那么Dave用自己的key,通过SSL联系Bob上的某个程序,将Alice的pubkey提交到Bob的合适帐号上(account, pubkey)。 Bob通过sslkey验证提交者确系Dave,将pubkey临时加入account中。 Bob完成此事后,通过Dave向Alice返回成功。 Alice通过SSL和Charlie联系,上报自己的(username, account, host)。 Charlie根据预设的IP-username-sslkey验证用户身份为Alice,并且根据ACL确认其具有访问权限。 如果通过验证,那么Charlie用自己的key,通过SSL联系Bob上某个程序,为Alice开通到Bob的22端口的tcp盲转发。 Alice利用开启的tcp通道,和自己的临时ssh private key验证登录Bob。 在Alice连接Bob上的程序后,删除alice留在Bob上的临时pubkey。 验证 假定Charlie被攻破。 方案1-5没有影响。 Charlie拥有能够在任意一台机器上开启盲转发的权限。 但是Charlie并不能影响Dave去添加pubkey。 假定Dave被攻破。 Dave拥有在任意一台机器上添加pubkey的权限。 但是Dave并不具有打开到任意一台机器ssh端口的权限。

无题

Jan 5, 2015 - 1 minute read - Comments

路过南京,突然想起前几年在狮子桥吃的鸭血粉丝汤。什么味道其实已经忘了,只是在干完了活后等火车回上海。看时间,与其在火车站无聊还不如在狮子桥吃点东西。 其实这不是我头一回吃这家。刚毕业的时候,没钱,但是又想出去玩。于是就一个人去了南京。那时南京倒是还有个朋友,可是我放假的时候人家也放假,所以还是一个人在南京到处跑。有天晚上,就是在狮子桥吃的晚饭。同样的店,同样的座位。 我已经想不起来两次看到的景观有什么细微的差别。大概来说,无非就是几个消防栓什么的。但是几年间,他们居然没有什么太大变化。 我又想起了外婆家,我长大的地方。从大体上看,好像也没什么太多的变化。但是细微处还是变了。我结婚搬了出去,外公也走了,留外婆一人独住。于是,很多东西都开始凑合了起来。原本电饭锅坏了大概是会去换一个的,现在也懒得换了。冰箱里也塞满了隔夜菜,有些甚至会隔月。 有形的东西总是会坏的,无形的东西总是会被忘记的。无论什么,消失才是大多数的宿命。和宇宙比起来,这地球只能算是沧海一粟。和时间比起来,人的生命只不过是白驹过隙。就在这小小的地球上,有多少匹白驹曾经越过溪水呢?你记住了几匹?又有几匹,曾经看到过我看的东西。狮子桥的消防栓,我的博客,火车站的长椅。他们总也有坏掉的一天,也有被人忘记得一天。记得他们的人,也有离去的一天,写下他们的文字,总有丢失的一天。直到有一天,他们的形体早已经消亡,痕迹再也不能从地球上找到。甚至记得他们痕迹的人的痕迹都已随风而逝。这时候,他们就像其他的白驹一样,成为了一个数字。

北海道之行的感想

Dec 24, 2014 - 1 minute read - Comments

礼貌的老奶奶 这次去日本,让我最受震动的是一位老奶奶。 大家知道我们的日语很差。我没学过,老婆只会简单的日语。所以我们在大通那里问路的时候,预期是很麻烦的。结果旁边有位老奶奶,直接给领到地方。最后走的时候,她的方向和我们相反的。虽然我日语不算太好,好歹听懂了最后一句是“请路上小心,务必保重”。 您太客气了吧。如果在中国碰到这种情况,别说我们的日语这幅德行。就算语言沟通无碍,最多也是指路,或者顺路领过去。更何况我们这种沟通情况,外加您一不顺路,二年龄又大。着实让我们觉得汗颜。 后来听同事们说,在日本问路,不管远近,只要愿意搭理你的,一律是领到地方。当然我们碰到的数量很小,所以参考意义不大。 我觉得我们纯粹是占了外国人的便宜。 唉,礼仪之帮。。。 根本不看车 原来twitter上有个笑话。从日本来中国,过马路的时候一定会被撞死。我测试了一下,原来不是笑话。 在日本过马路的时候,压根不用看车,哪怕你乱穿马路。我在街上过马路,正好碰到行人和司机都是绿灯。我示意司机先走,结果司机在那里等我。我再示意,他再等。我看看我等不起。举手谢谢,然后先走了。 这种情况占了大多数。只有少数司机,你示意他先走,他举手表示感谢,然后先走的。大多数司机根本在那里傻等着。 回国第一天,绿灯过街的时候看到一辆右转。我看了一眼,脑子里压根就没浮起来我还得让他。结果差点撞上。 这似乎和法律都没什么关系了。中国的法律是同时绿灯的,车辆让行人。行人和车辆碰撞,车辆全责。如果大部分人还是没什么顾忌。我估计要么罚到的概率太低,都有侥幸心理。要么赔偿数目过低,大部分人也不在乎。 精美而细致的商品 日本商品的一大特点就是精美而细致。便利店买个牛丼,两层装就不说了。大陆全家卖的红烧牛肉饭也是两层的。上面还粘帖了一个油料包,一个辛辣味调料包,可以自由添加或者不添加。最狠的是,里面带的筷子还有一个方便的撕开口,和一个很不错的一次性牙签。大陆全家的筷子带撕开口不错,但是却不好用,很难撕开,而且没牙签。 卖个碳酸饮料,带巨人或者海贼王宣传。去餐厅吃饭,路上一堆天体的海报。路过地铁,里面有动漫在周边更是平常到不能再平常。 简单来说这种做法就是附加值高。一个商品,本身只有100,却附加上100的附加值,还更容易卖。对于一个资源缺乏的国度来说,大批量的将资源加工成产品然后输出是不合算的。显然加工的深度越深,产生的利润越高。 中国目前根本没到这个地步,估计是因为中国的商品还没有到“难卖”的地步,或者说中国卖东西和“营销”没什么太大关系。我们总是能够找到某些办法,把产品搞出去。至于要赚钱,好像也不是靠老老实实的做产品,或者做一些营销能够搞定的。以至于我们始终宣传和重视“营销”,脑子里想的东西却和“营销”没有什么太大关系。 过度包装和垃圾分类 日本商品的一大毛病就是过度包装,而且是让人看不下去的精致包装。当然,这和“精美而细致的商品”是一脉相承的。 跑到店里,要一个挂坠,说要送人。挂坠本身带一个塑料壳,店员又给你拿一个纸袋子包好,贴个胶带封上,然后装在塑料袋里。合着一个挂坠套三层,包装比东西还重。 但是同时,日本又有着高效的垃圾分类-回收利用系统。导游介绍日本的垃圾回收率高达九成(我不知道他哪里来的数字)。按照这个来算的话,就算过度包装,人均垃圾产量依然比中国低的多。 从我看到的资料上看,至少在昭和时代早期(差不多是中日战争时期),就开始宣传资源回收了。想想也是,日本这种缺乏资源的国度要对外开战的话,节约自己的每一分资源是非常有必要的。 大雪时没有雪的街道 在札幌,我们碰到了大雪。鹅毛大雪整个落下来,一晚上能落上数十公分厚(最大降雪是130公分)。但是第二天早上七点我起床看外面,整个街道上干干净净,没有积雪,甚至连雪水融化后打湿街面都看不到。 这意味着扫雪的人至少要在早上六点就开始工作了。 当然,还是有不少街道是有雪的,尤其是还在施工的街区前面。所以这让我怀疑扫雪是有临街商铺自行负责的。否则一个城市的环卫系统很难调度这么庞大的资源,来扫除整个城市的积雪。 而且在街道上有很多箱子,里面放着一包包不知道是沙子还是盐的东西,写着可以免费使用。按照标签,是用于街区防滑的。 札幌大通公园的辐射计数器 在我的照片里有,0.033uSv/h。按照这个计算,一年的辐射量是300uSv左右。按照建议,一年最大接受1mSv的辐射。可见辐射量不算太大。 自从311海啸之后,日本人似乎对辐射量特别敏感。我专门看了两期海啸和辐射有关的节目。日本人是认认真真,自己买仪器,在不同位置不同高度测量辐射计量的。我很好奇,如果让他们来冬天的华北大地逛一圈的话。。。

北海道旅游——札幌附近地区攻略

Dec 16, 2014 - 1 minute read - Comments

札幌 白色恋人巧克力工坊 在宫の沢站,从大通坐东西线,340就可达。 工坊庭院非常漂亮,整点有音乐表演。一楼进去是商店,可以在那里买到全系列的产品。小火车在往里走,不过我们去的时候人挺多,很遗憾没坐上。 工坊在9:30, 11:30, 13:30, 15:30提供亲手制作巧克力饼干的课程,必须赶准时间否则就得等下一批。参观博物馆是600一人,我们报的课程是1250的,报名者参观打折到500。 课程并不复杂,戴好装备走进去,大约一刻钟到半小时的操作后,出来等烘陪结果。大约20分钟后再进去裱花,然后就可以带走了。很多小孩进去体验的,大人还可以在外面看着。 带小孩进去的需要注意安全,最好有大人陪同。 博物馆里有很多老式留音机,碰到半点(准确时间不确定)还会有表演。 建议一早冲过去,踩准点,否则容易碰到白白等着。做饼干的空余,出来在餐厅喝个茶,吃点糕点,看看风景。一般还能看到音乐表演。逛到楼下直接买点东西就走。 36片的巧克力饼干折扣价在2400日元上下,目前汇率折合人民币120上下。 蟹将军 在ささきの那里,离开狸小路非常近。从大通可以走到。 全蟹宴大概4300-4500,按现在汇率,200多点的样子。店的定位非常高档,所以绝对超值。 整个店都是要脱鞋,全地板铺塌塌米。上了一堆螃蟹,最后还有一道泡饭,是穿着和服的女服务员帮着处理的。手势超级优雅,感觉碉堡了。 狸小路 在大通南边一点,是一条非常热闹的步行街。 去的话可以先学学小钢珠(パチンコ)的玩法。其实没啥好学的,就是扔进去,完了按按钮。但是老婆楞是没看懂,没上去玩。主要是,我们完全没想到这种简单的规则还能当游戏玩。。。 狸小路5那里有个免税店,店员的中文相当不错,价格也基本是最低了(至少白色恋人巧克力是这样)。我在那里基本扫齐了所有货。 狸小路上有一家饺子の王將,两盘饺子加一个杏仁豆腐才733。换成人民币大约36,和上海基本持平。关键是,好吃到爆啊!!!顿时觉得上海的饺子店弱爆了。 居酒屋 我们总计吃了两次居酒屋。 首先,大部分居酒屋都有服务费。例如毛巾,前菜什么的。一般是一个人500上下。我们第一次不知道,进去点了1500的东西,被收了2500。老婆日语太差,还不敢问。后来就明白了,大部分这种店都是这样的。只要是居酒屋类的,都会收取一定的服务费。蟹将军不是很确定,可能也是收了服务费的。 但是,居酒屋的点菜超级困难。根据大熊酱的说法,日语二级进去也未必能全身而退。我老婆日语还不一定比我强,去了就要英文菜单了。 居酒屋基本就是吃个意思。论性价比还是饺子の王將,或者吉野家之类的比较好。可是真去日本的,基本不会选择这些。 小樽 小樽没啥好说的,总共才去了半天。 nikka的威士忌酒厂没啥好看的,倒是商店里面的商品还算不错。有很漂亮的小瓶威士忌,当纪念品很好。 情书拍摄地很坑。 商店街那里看个人喜欢。玻璃制品和八音盒都很漂亮,让人担心自己的钱包。有空的话可以多逛一下,买点海鲜吃。 滑雪 坑爹。第一天跑过去,大雪关门。第三天跑过去,还有半个小时关门。 所以,这次滑雪行程没有滑到雪。 不要玩一些太少人玩的东西啊,只有一个人玩的结果就是很可能投票失败。。。 洞爷湖 我们住的酒店叫做洞爷湖万世屋。我开始还不知道。出门一看,越后屋。我了个去。尼玛万事屋不是因为空知英秋根本住过这间吧。 不管了,扫货。整个店里面,大部分动漫都是银魂和天体的周边(偷偷说,酒店去餐厅路上还能看到天体的海报)。 登别 地狱谷有鬼灯的画报。好像还有周边,但是没空逛。

上下文切换技术

Nov 20, 2014 - 1 minute read - Comments

上下文切换技术 简述 在进一步之前,让我们先回顾一下各种上下文切换技术。 不过首先说明一点术语。当我们说“上下文”的时候,指的是程序在执行中的一个状态。通常我们会用调用栈来表示这个状态——栈记载了每个调用层级执行到哪里,还有执行时的环境情况等所有有关的信息。 当我们说“上下文切换”的时候,表达的是一种从一个上下文切换到另一个上下文执行的技术。而“调度”指的是决定哪个上下文可以获得接下去的CPU时间的方法。 进程 进程是一种古老而典型的上下文系统,每个进程有独立的地址空间,资源句柄,他们互相之间不发生干扰。 每个进程在内核中会有一个数据结构进行描述,我们称其为进程描述符。这些描述符包含了系统管理进程所需的信息,并且放在一个叫做任务队列的队列里面。 很显然,当新建进程时,我们需要分配新的进程描述符,并且分配新的地址空间(和父地址空间的映射保持一致,但是两者同时进入COW状态)。这些过程需要一定的开销。 进程状态 忽略去linux内核复杂的状态转移表,我们实际上可以把进程状态归结为三个最主要的状态:就绪态,运行态,睡眠态。这就是任何一本系统书上都有的三态转换图。 就绪和执行可以互相转换,基本这就是调度的过程。而当执行态程序需要等待某些条件(最典型就是IO)时,就会陷入睡眠态。而条件达成后,一般会自动进入就绪。 阻塞 当进程需要在某个文件句柄上做IO,这个fd又没有数据给他的时候,就会发生阻塞。具体来说,就是记录XX进程阻塞在了XX fd上,然后将进程标记为睡眠态,并调度出去。当fd上有数据时(例如对端发送的数据到达),就会唤醒阻塞在fd上的进程。进程会随后进入就绪队列,等待合适的时间被调度。 阻塞后的唤醒也是一个很有意思的话题。当多个上下文阻塞在一个fd上(虽然不多见,但是后面可以看到一个例子),而且fd就绪时,应该唤醒多少个上下文呢?传统上应当唤醒所有上下文,因为如果仅唤醒一个,而这个上下文又不能消费所有数据时,就会使得其他上下文处于无谓的死锁中。 但是有个著名的例子——accept,也是使用读就绪来表示收到的。如果试图用多个线程来accept会发生什么?当有新连接时,所有上下文都会就绪,但是只有第一个可以实际获得fd,其他的被调度后又立刻阻塞。这就是惊群问题thundering herd problem。 现代linux内核已经解决了这个问题,方法惊人的简单——accept方法加锁。(inet_connection_sock.c:inet_csk_wait_for_connect) 线程 线程是一种轻量进程,实际上在linux内核中,两者几乎没有差别,除了一点——线程并不产生新的地址空间和资源描述符表,而是复用父进程的。 但是无论如何,线程的调度和进程一样,必须陷入内核态。 传统网络服务模型 进程模型 为每个客户分配一个进程。优点是业务隔离,在一个进程中出现的错误不至于影响整个系统,甚至其他进程。Oracle传统上就是进程模型。 缺点是进程的分配和释放有非常高的成本。因此Oracle需要连接池来保持连接减少新建和释放,同时尽量复用连接而不是随意的新建连接。 线程模型 为每客户分配一个线程。优点是更轻量,建立和释放速度更快,而且多个上下文间的通讯速度非常快。 缺点是一个线程出现问题容易将整个系统搞崩溃。 一个例子 py_http_fork_thread.py 在这个例子中,线程模式和进程模式可以轻易的互换。 如何工作的 父进程监听服务端口 在有新连接建立的时候,父进程执行fork,产生一个子进程副本 如果子进程需要的话,可以exec(例如CGI) 父进程执行(理论上应当先执行子进程,因为exec执行的快可以避免COW)到accept后,发生阻塞 上下文调度,内核调度器选择下一个上下文,如无意外,应当就是刚刚派生的子进程 子进程进程进入读取处理状态,阻塞在read调用上,所有上下文均进入睡眠态 随着SYN或者数据报文到来,CPU会唤醒对应fd上阻塞的上下文(wait_queue),切换到就绪态,并加入调度队列 上下文继续执行到下一个阻塞调用,或者因为时间片耗尽被挂起 关于更多细节,可以看这里。这篇文章里还介绍了epoll的工作细节。 评价 同步模型,编写自然,每个上下文可以当作其他上下文不存在一样的操作,每次读取数据可以当作必然能读取到。 进程模型自然的隔离了连接。即使程序复杂且易崩溃,也只影响一个连接而不是在整个系统。 生成和释放开销很大(效率测试的进程fork和线程模式开销测试),需要考虑复用。 进程模式的多客户通讯比较麻烦,尤其在共享大量数据的时候。 C10K问题 描述 当同时连接数在10K左右时,传统模型就不再适用。实际上在效率测试报告的线程切换开销一节可以看到,超过1K后性能就差的一塌糊涂了。 更细节描述,可以看这里。 进程模型的问题 在C10K的时候,启动和关闭这么多进程是不可接受的开销。事实上单纯的进程fork模型在C1K时就应当抛弃了。 Apache的prefork模型,是使用预先分配(pre)的进程池。这些进程是被复用的。但即便是复用,本文所描述的很多问题仍不可避免。 线程模式的问题 从任何测试都可以表明,线程模式比进程模式更耐久一些,性能更好。但是在面对C10K还是力不从心的。问题是,线程模式的问题出在哪里呢? 内存? 有些人可能认为线程模型的失败首先在于内存。如果你这么认为,一定是因为你查阅了非常老的资料,并且没仔细思考过。 你可能看到资料说,一个线程栈会消耗8M内存(linux默认值,ulimit可以看到),512个线程栈就会消耗4G内存,而10K个线程就是80G。所以首先要考虑调整栈深度,并考虑爆栈问题。 听起来很有道理,问题是——linux的栈是通过缺页来分配内存的(How does stack allocation work in Linux?),不是所有栈地址空间都分配了内存。因此,8M是*最大*消耗,实际的内存消耗只会略大于实际需要的内存(内部损耗,每个在4k以内)。但是内存一旦被分配,就很难回收(除非线程结束),这是线程模式的缺陷。 这个问题提出的前提是,32位下地址空间有限。虽然10K个线程不一定会耗尽内存,但是512个线程一定会耗尽地址空间。然而这个问题对于目前已经成为主流的64位系统来说根本不存在。 内核陷入开销? 所谓内核陷入开销,就是指CPU从非特权转向特权,并且做输入检查的一些开销。这些开销在不同的系统上差异很大。

上下文切换测试总结报告

Nov 18, 2014 - 2 minute read - Comments

效率测试 测试环境 Intel® Pentium® CPU G2030 @ 3.00GHz 8G内存 debian jessie Linux 3.16-2-amd64 2014年10月27日 附注一下,该CPU有2核心,无HT,1ns3个时钟周期。 测试方法 测试代码如下: time -f "%e,%S,%c,%r,%s,%K,%P" ./perf_fork 数据的意义分别为: 总时间,内核CPU时间,context switch次数,读/写次数,内存耗用,CPU使用百分比。 数据处理方法如下: import numpy as np p = lambda s: [float(line.strip().split(',')[0]) for line in s.splitlines()] q = lambda s: [float(line.strip().split(',')[1]) for line in s.splitlines()] np.array(p(s)).mean() np.array(p(s)).var() np.array(q(s)).mean() np.array(q(s)).var() 基础开销测试 函数调用开销 使用s_call来测试性能,循环1G次。 2.35,0.00,17,0,0,0,99% 2.34,0.00,13,0,0,0,99% 2.34,0.00,10,0,0,0,100% 2.35,0.00,10,0,0,0,99% 2.34,0.00,14,0,0,0,99% 2.34,0.00,6,0,0,0,99% 统计结果如下: time mean = 2.34 time var = 0.000022 每次call的开销为2.34ns,约7个指令周期。当然,这些并没有考虑调用压栈和数据返回。