2015年(4)
分类: 云计算
2015-11-05 21:03:11
之所以会写这篇文章,是因为之前nova那边遇到了一点小问题,现象如下:
从日志来看,很简单嘛,定时器超时了。但是为什么会超时呢?纯粹的增加一下超时时间行不行?
要想解决这个问题,就不得不说一下OVS的数据库交互机制。OVSDB不是类似MYSQL一样的数据库,
而是一个以JSON格式书写的文件数据库。简单点说吧,就是这个数据库里写的都是一些执行命令,
每次有什么更新或者系统初始化时,都会逐条地去执行数据库里的指令。看看下面的例子:
步骤一:发送订阅消息
method="monitor",
params=[
"Open_vSwitch",
null,
{"Port":{"columns":["fake_bridge","interfaces","name","tag"]},
"Interface":{"columns":["mac","name","ofport_request","type"]},
"Bridge":{"columns":["controller","fail_mode","name","ports"]},
"Controller":{"columns":[]},
"Open_vSwitch":{"columns":["bridges","cur_cfg"]}} //cur_cfg只会在--no-wait选项没有的时候发送
], id=0
cur_cfg只会在no-wait选项为false的时候会订阅。从这里可以看到,增加一个端口的时候,关心所有的bridge,port和interface。
步骤二:得到数据库里的初始值
result={
"Port":{"275537c3-b469-47d1-b3c0-10efd08cb193":
{"new":{"name":"br0","fake_bridge":false,"interfaces":["uuid","34663327-aeca-417d-a91b-8c8f796cb873"],"tag":["set",[]]}}},
"Interface":{"34663327-aeca-417d-a91b-8c8f796cb873":{"new":{"ofport_request":["set",[]],"name":"br0","mac":["set",[]],"type":"internal"}}},
"Bridge":{"8f725ab8-c5f4-4077-a251-d866e7a448d9":
{"new":{"fail_mode":["set",[]],"name":"br0","ports":["uuid","275537c3-b469-47d1-b3c0-10efd08cb193"],"controller":["set",[]]}}},
"Open_vSwitch":{"49c7f033-83c3-4bdf-8c0d-3d13b0c8bcf7":{"new":{"bridges":["uuid","8f725ab8-c5f4-4077-a251-d866e7a448d9"],"cur_cfg":1008}}}
}, id=0
Port和interface的返回值都是一个字典,也就是说,当前有多少端口就会返回多少数据。
Key是port uuid和interface uuid,value就是刚刚订阅的那些列的内容。Bridge也是一个字典,
但是它的内容庞大主要在于bridge的ports这一列所含的数据是所有属于该bridge的port uuid。
步骤三:发送add port transaction消息
method="transact",
params=[
"Open_vSwitch",
{"rows":[{"ports":["uuid","275537c3-b469-47d1-b3c0-10efd08cb193"]}],
"columns":["ports"],
"table":"Bridge",
"until":"==",
"where":[["_uuid","==",["uuid","8f725ab8-c5f4-4077-a251-d866e7a448d9"]]],
"timeout":0,
"op":"wait"
},
{"rows":[{"interfaces":["uuid","34663327-aeca-417d-a91b-8c8f796cb873"]}],
"columns":["interfaces"],
"table":"Port",
"until":"==",
"where":[["_uuid","==",["uuid","275537c3-b469-47d1-b3c0-10efd08cb193"]]],
"timeout":0,
"op":"wait"
},
{"rows":[{"bridges":["uuid","8f725ab8-c5f4-4077-a251-d866e7a448d9"]}],
"columns":["bridges"],
"table":"Open_vSwitch",
"until":"==",
"where":[["_uuid","==",["uuid","49c7f033-83c3-4bdf-8c0d-3d13b0c8bcf7"]]],
"timeout":0,
"op":"wait"
},
{"row":{"ofport_request":100,"name":"p100","mac":"00:00:00:01:01:01","type":"internal"},
"table":"Interface",
"uuid-name":"row84ca2c84_4665_4889_9fda_14cef22ae33e",
"op":"insert"
},
{"row":{"ports":["set",[["uuid","275537c3-b469-47d1-b3c0-10efd08cb193"],["named-uuid","rowe8af9c67_3aa9_4b01_be99_d3cc7aeb8b78"]]]},
"table":"Bridge",
"where":[["_uuid","==",["uuid","8f725ab8-c5f4-4077-a251-d866e7a448d9"]]],
"op":"update"
},
{"row":{"name":"p100","interfaces":["named-uuid","row84ca2c84_4665_4889_9fda_14cef22ae33e"],"tag":100},
"table":"Port",
"uuid-name":"rowe8af9c67_3aa9_4b01_be99_d3cc7aeb8b78",
"op":"insert"
},
{"mutations":[["next_cfg","+=",1]], //next_cfg只会在--no-wait选项没有的时候发送
"table":"Open_vSwitch",
"where":[["_uuid","==",["uuid","49c7f033-83c3-4bdf-8c0d-3d13b0c8bcf7"]]],
"op":"mutate"
},
{"columns":["next_cfg"], //next_cfg只会在--no-wait选项没有的时候发送
"table":"Open_vSwitch",
"where":[["_uuid","==",["uuid","49c7f033-83c3-4bdf-8c0d-3d13b0c8bcf7"]]],
"op":"select"
},
{"comment":"ovs-vsctl: ./ovs-vsctl -v add-port br0 p100 -- set Interface p100 ofport_request=100 type=internal \"mac=\\\"00:00:00:01:01:01\\\"\" -- set Port p100 tag=100",
"op":"comment"}],
id=1
从这里可以看到,所有的port,interface, bridge都被作为前提条件发过去了。
步骤四:接收OVSDB发过来的update消息
method="update",
params=[null,
{"Port":{"4ba835b7-2ed0-4526-b948-dba1674df899":{"new":{"name":"p100","fake_bridge":false,"interfaces":["uuid","b54ccab2-e910-4b90-a13f-de513b7a139f"],"tag":100}}
},
"Bridge":{"8f725ab8-c5f4-4077-a251-d866e7a448d9":{"old":{"ports":["uuid","275537c3-b469-47d1-b3c0-10efd08cb193"]},"new":{"fail_mode":["set",[]],"name":"br0","ports":["set",[["uuid","275537c3-b469-47d1-b3c0-10efd08cb193"],["uuid","4ba835b7-2ed0-4526-b948-dba1674df899"]]],"controller":["set",[]]}}
},
"Interface":{"b54ccab2-e910-4b90-a13f-de513b7a139f":{"new":{"ofport_request":100,"name":"p100","mac":"00:00:00:01:01:01","type":"internal"}}}}
]
只有之前monitor过的表和列才会发送信息
步骤五:接收执行结果
result=[
{},
{},
{},
{"uuid":["uuid","b54ccab2-e910-4b90-a13f-de513b7a139f"]},
{"count":1},
{"uuid":["uuid","4ba835b7-2ed0-4526-b948-dba1674df899"]},
{"count":1},
{"rows":[{"next_cfg":1009}]},
{}], id=1
看到此处的next_cfg了吗?一旦OVSDB执行完毕,next_cfg就已经比上次的值要增加了,但是cur_cfg值还没有变化。
步骤六:接收vswitchd执行完毕的update消息
method="update",
params=[null,
{"Open_vSwitch":{"49c7f033-83c3-4bdf-8c0d-3d13b0c8bcf7":{"old":{"cur_cfg":1008},"new":{"bridges":["uuid","8f725ab8-c5f4-4077-a251-d866e7a448d9"],"cur_cfg":1009}}}}]
此时终于把cur_cfg更新成和next_cfg值一样了,VSCTL可以高高兴兴的退出了。
看了这个流程,估计很多人还是没反应过来,这怎么就反应慢了呢?确实,光看这么一个port不会有任何的效果,
如果再执行一次加port的命令,或者在已经加完100个端口后,再用命令ovs-vsctl –v add-port命令,此时就看得很明白了。
在步骤二,三,四中可以看到黑压压的一片,都是我们的JSON消息里填的portid,interface id等信息。我们知道,
报文每一次加封装和解封装都是需要时间的,内容越多,耗时越长。这个有兴趣的人可以自己打开debug去看日志时间。
也就是说,加端口的时候大部分的时间都花在报文的加解封装上了,端口数越多,时间越长。那么,怎么解决呢?奉上之前的劳动成果:
a. 使bridge表不再包含ports列,而改为port表里增加bridge的列引用。这样一来,bridge表返回的内容就大大减少
b. 监视内容,不再是所有的port,interface和bridge。而是和命令相关的数据。比如,要删一个port tapxxx,
那么就只查询和该port tapxxx相关的数据,其他的数据一概不处理。这样的话,步骤二和步骤三里的数据就大大减少。
也就是说,监视的消息格式里要加上condition选项,只有符合条件的才返回。
这里有一份数据,仅供参考
测试数据是4000个端口,1000个namespace,每个namespace共4个端口
优化方案 |
时间(单位:秒) |
没有做任何优化前 |
2719 |
修改数据库,去掉bridge表的ports列 |
1973 |
优化vswitchd函数bridge_delete_or_reconfigure_ports,使端口不再做循环,改为先获取要删除的端口,然后再去kernel获取数据 |
1759 |
修改monitor规则,使vsctl可以只监视一部分记录 |
480 |
修改vswitchd中的bridge_init函数,不再监视interface表的cfm,bfd |
190 |
编译为release版本,使能参数--disable-coverage --enable-ndebug |
160 |