一个机房对应多个机架,一个机架对应多个服务器,一个机房对应多个机房网络,一个机房网络相当于一个IP段。在添加服务器的时候会为该服务器指定相应的内网IP和外网IP,查询IP的时候根据服务器的资产编号和IP类型(内网和外网)去查,要把所有符合条件的IP都查出来供服务器选择,下面就讲三种方法统计IP:
第一种:
根据资产编号——》服务器——》机架——》机房——》机房网络列表
然后遍历机房网络列表,把查出的IP合并到一起。
'''用字符串拼接,最原始的方法''' d1 = datetime.datetime.now() nocnetSet = NOCNetInfo.objects.filter(nocinfo = nocinfo) ips = [] if nocnetSet: nocnetinfo = list(nocnetSet) for nocnet in nocnetinfo: ipstart = nocnet.ip_start ipend = nocnet.ip_end ip_st = ipstart.split('.') ip_et = ipend.split('.') start = int(ip_st[3]) end = int(ip_et[3]) while True: ip_st[3] = str(start) ipAddress1 = ip_st[0] +'.'+ ip_st[1] +'.'+ ip_st[2] +'.'+ ip_st[3] ip_t = IPModel.objects.filter(ipAddress = ipAddress1, ipType = ipType, noc_id = nocinfo.id)#IPType。“0”为内网、“1”为外网 if ip_t: ips.append(list(ip_t).pop(0)) start += 1 if start == end+1:break; print "ips: " + str(len(ips)) print ips d2 = datetime.datetime.now() d = d2 - d1 print d
第二种:
跟第一种类似,资产编号——》服务器——》机架——》机房——》机房网络列表
然后遍历机房网络列表,把查出的IP合并到一起。
不同的是不同的机房网络查出来的IP合并到一起的方法。这里采用系统自带的list方法:extend
'''用list的extend方法实现''' d1 = datetime.datetime.now() nocnetinfos = nocinfo.nocnetinfo_set.all() ips = [] for nocnetinfo in nocnetinfos: ippools = IPModel.objects.filter(Q(ipAddress__gte=nocnetinfo.ip_start) & Q(ipAddress__lte=nocnetinfo.ip_end) & Q(ipType__exact=ipType)) ips.extend(ippools) print "ips: " + str(len(ips)) d2 = datetime.datetime.now() d = d2 - d1 print d
第三种:
前面也跟第一种类似,
资产编号——》服务器——》机架——》机房——》机房网络列表
然后遍历机房网络列表,把查出的IP合并到一起。
主要还是合并IP的方法的区别,这里采用多线程实现,每个机房网络开一个线程。
'''用多线程实现''' d1 = datetime.datetime.now() nocnetinfos = nocinfo.nocnetinfo_set.all() ips_2 = [] lock = threading.Lock() for nocnetinfo in nocnetinfos: thread_id = Counter(ips_2, nocnetinfo, ipType, lock, "thread-" + str(nocnetinfo.id)) thread_id.start() thread_id.join() print "ips_2: " + str(len(ips_2)) d2 = datetime.datetime.now() d = d2 - d1 print d
线程类Counter:
import threading ''' 线程类 ''' class Counter(threading.Thread): def __init__(self, ips_2, nocnetinfo, ipType, lock, threadName): '''''@summary: 初始化对象。 @param lock: 琐对象。 @param threadName: 线程名称。 ''' super(Counter, self).__init__(name=threadName) #注意:一定要显式的调用父类的初始化函数。 self.ips_2 = ips_2 self.nocnetinfo = nocnetinfo self.ipType = ipType self.lock = lock def run(self): '''''@summary: 重写父类run方法,在线程启动后执行该方法内的代码。 ''' self.lock.acquire() ippools = IPModel.objects.filter(Q(ipAddress__gte=self.nocnetinfo.ip_start) & Q(ipAddress__lte=self.nocnetinfo.ip_end) & Q(ipType__exact=self.ipType)) self.ips_2.extend(ippools) self.lock.release()
这里面有两点需要注意:
1.给函数传参数ips_2,因为ips_2是list类型,所以在多线程类Counter中对ips_2的修改会直接影响到主线程中的ips_2,在这里也正是我所需要的
2.启动每一个线程后要调用该线程的join方法,起到阻塞主线程的作用,不然在子线程没有执行完的情况下就会去执行主线程,这样会得到错误的结果,并不是我所希望的。
简单做了测试:
在查询200条数据的情况下,第一种要5000毫秒左右,第二种在500毫秒左右,第三种利用多线程反而比第二种慢了一点,大约在700毫秒左右(应该是数据量太少,没有体现出多线程的优势)。
在查询5000条数据的情况下,第一种大约要10000毫秒左右,第二种要5000毫秒左右,第三种大约也是5000毫秒
总的来说还是很慢,满足不了需求,后来经过研究,django的查询集是惰性的,如果只是得到查询集,不去操作数据库,速度将会很快,最其码能够提高十倍。
但在python代码中执行extend操作时就会去操作数据库,所以速度给降下来了,要想提高速度,只有改变实现方式,现在的思路是:
针对不同的机房网络,写出对应的查询条件,然后把查询条件拼接到一起,然后再执行查询,这样只是得到了查询集,没有去操作数据库,所以速度很快。
实现代码:
'''拼接查询条件,利用查询集的缓存''' d1 = datetime.datetime.now() nocnetinfos = nocinfo.nocnetinfo_set.all() search = '' for nocnetinfo in nocnetinfos: ip_start = IPModel.objects.get(ipAddress__exact=nocnetinfo.ip_start) ip_end = IPModel.objects.get(ipAddress__exact=nocnetinfo.ip_end) search += 'Q(id__gte=' + str(ip_start.id) + ') & Q(id__lte=' + str(ip_end.id) + ') & Q(ipType__exact=' + str(ipType) + ') | ' search = search[0:-2] # search = 'Q(log_type__exact=log_type) & Q(relate_id__exact=relate_id) | Q(sequence__startswith=sequence)' print search ips_3 = IPModel.objects.filter(eval(search)) # print "ips_3: " + str(len(ips_3)) print "ips_3: " d2 = datetime.datetime.now() d = d2 - d1 print d
里面有三点需要注意:
1.以前查询IP用>=192.168.1.1,<= 192.168.1.255这样的形式是不行的,这样的话192.168.1.3是不在这个范围的,实际上这个是我们想得到的数据。所以现在换成了用主键id
2.不能执行print len(ips_3)这行代码,因为执行这行代码时会去操作数据库,速度就降下来了。
不能调用len函数获得查询集中数据中个数,可以调用查询管理器的count方法,用法如下:
count()¶
返回数据库中匹配查询(QuerySet)的对象数量。 count() 不会抛出任何异常。
例如:
# Returns the total number of entries in the database.
Entry.objects.count()
# Returns the number of entries whose headline contains 'Lennon'
Entry.objects.filter(headline__contains='Lennon').count()
count() 会在后端执行 SELECT COUNT(*) 操作,所以你应该尽量使用 count() 而不是对返回的查询结果使用 len() 。
根据你所使用的数据库(例如 PostgreSQL 和 MySQL),count() 可能会返回长整型,而不是普通的 Python 整数。这确实是一个很古怪的举措,没有什么实际意义。
3.查询条件拼接好后实际上是一个字符串,直接传给
IPModel.objects.filter(search)
这样是不行的,要对search用eval函数解析一下,如下所示:
IPModel.objects.filter(eval(search))
这种情况测试了一下:在5000条数据的情况下500毫秒就全查出来了。哈哈,这才是我需要的。
关于查询集的惰性用法:
查询集是惰性的
查询集 是惰性的,即创建一个 查询集 不会触动数据库。你可以不停地创建 过滤器,但 Django 不会真正运行查询,除非 查询集 被 执行 了。来看以下的
例子:
上例中表面看执行了三个数据库查询,但实质只有最后一行代码( print q )执行了 一次数据库查询。通常,只到你“要求”返回结果时, 查询集 才会从数据库中抓取
数据, 查询集 被 执行 了。更多关于执行的精确时间的内容参见 When
QuerySets are evaluated 。
关于查询集什么时候真正去操作数据库:
从内部讲,QuerySet 可以被构造,过滤,切片,做为参数传递,这些行为都不会对数据库进行操作。只要你查询的时候才真正的操作数据库。
下面的 QuerySet 行为会导致执行查询的操作:
-
循环(Iteration):QuerySet 是可迭代的,在你遍历对象时就会执行数据库操作。例如,打印出所有博文的大标题:
for e in Entry.objects.all(): print e.headline
-
切片(Slicing):在 查询限定(Limiting QuerySets) 中提到,
a QuerySet 是可以用 Python 的数组切片语法完成切片。一般来说对一个 QuerySet 切片就返回另一个 QuerySet (新
QuerySet 不会被执行)。不过如果你在切片时使用了 "step" 参数,Django 仍会执行数据库操作。 -
序列化/缓存化(Pickling/Caching): 详情请查看 pickling
QuerySets。 这一节所强调的一点是查询结果是从数据库中读取的。 -
repr(). 调用 QuerySet 的 repr() 方法时,查询就会被运行。这对于
Python 命令行来说非常方便,你可以使用 API 立即看到查询结果。 -
len(). 调用 QuerySet 的 len() 方法,查询就会被运行。这正如你所料,会返回查询结果列表的长度。
注意:如果你想得到集合中记录的数量,就不要使用 QuerySet 的 len() 方法。因为直接在数据库层面使用
SQL 的 SELECT COUNT(*) 会更加高效,Django 提供了 count() 方法就是这个原因。详情参阅下面的 count() 方法。 -
list(). 对 QuerySet 应用 list() 方法,就会运行查询。例如:
entry_list = list(Entry.objects.all())
要注意地是:使用这个方法会占用大量内存,因为 Django 将列表内容都载入到内存中。做为对比,遍历 QuerySet 是从数据库读取数据,仅在使用某个对象时才将其载入到内容中。
关于数据何时被放到缓存中的:
缓存和查询集
每个 查询集 都包含一个缓存,用于最小化数据库使用。为了写出最有效率的代码, 理解缓存的工作方式是非常重要的。
当一个 查询集 新建时,其缓存是空的。当 查询集 第一次被执行时,数据库就 会使用一次, Django 会把查询结果保存在 查询集 的缓存中并返回结果。以后的
查询就会重复使用缓存。
你要小心缓存的行为,如果不正确使用 查询集 ,那么有可能上当。例如下列语句会 创建两个 查询集 并执行它们,然后把它们抛在一边:
这意味着两个同样的查询被执行了两次,浪费啊。同时,两条语句的条目还可能不一致, 因为在两次查询之间可能会有条目增减。
要避免以上问题,只要保存 查询集 并且重复使用就可以了:
为了只得到查询集,不去真正操作数据库,经常会用到Q对象进行复杂的查询,Q对象用法如下:
使用 Q 对象进行复杂查询
在 filter() 等语句中的关键字查询参数都是“逻辑与”的关系。如果要进行更复杂的 查询(如条件之间为“逻辑或”的关系),那么可以使用 Q 对象。
Q 对象( django.db.models.Q )是用于封装一堆关键字查询参数的对象。这些 关键字查询参数在上文“字段查找”中已描述过。
例如,下面的 Q 对象封装了一个单一的 LIKE 查询:
Q 对象可以用 & 和 | 操作符联结,联结之后会产生一个新的 Q 对象。
下例产生了一个新的 Q 对象,这个对象表示两个 "question__startswith" 查询的 OR 的联结:
上例相当于下面的 SQL WHERE 子句:
可以用 & 和 | 操作符及圆括号来组合任意复杂的 Q 对象,还可以使用 ~ 符号来表示“逻辑非”。例如:
每个有关键字参数的查询函数(如 filter() 、``exclude()`` 和 get() )可以 接受一个或多个 Q 对象作为位置参数(不是命名参数)。如果你提供了多个 Q 对象,则参数之间是“逻辑与”的关系。例如:
... 相当于以下 SQL:
在查询函数中可以混合使用 Q 对象和关键字参数。查询函数中的所有参数(包括 Q 对象和关键字参数)都是“逻辑与”的关系。如果混合使用这两种参数, Q 对象应当放在关键字参数之前。例如:
... 是合法的查询,相当于上一个例子,但:
... 是不合法的。