背景
公司業務處於上升期,但是服務端卻 low 的像個 demo,於是乎重構服務端開始了;
關於測試,測試這個行業在大多數網際網路公司比較失敗,做測試的應該都有這種感覺。但是我感覺我很幸運,因為 CTO 很重視測試,他的口頭禪就是不可測試的程式都是不可靠的,所以我們公司的所有程式都會有配套的測試工具。這篇文章中的工具就是專門測試服務端而模擬的客戶端 TSP。
業務需求
重構服務端,達到接入百萬客戶端級別
實現原理
- 服務端監聽 3 個埠,這三個埠全部是裝置發起
- tsp 模擬客戶端連線服務端邏輯,從而達到裝置、使用者上線
- 採用程序池巢狀執行緒池方式,達到高併發的目的
- 程式碼中統計發包時間平均值、收包時間平均值、收包位元組大小平均值、收包錯誤率;
- 服務端採集硬體配置、cpu 負載、記憶體負載、磁碟 io 等等
具體實現
1.構建主流程,完成基於 socket 請求的收發包
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#
from socket import socket,AF_INET,SOCK_STREAM
import struct
from log import log
log=log()
class socket_main:
def __init__(self,who,conn_data):
self.conn_data = conn_data
self.who = who
self.__conn(conn_data)
def __conn(self,addr):
try:
log.debug('{} _conn data:{}'.format(self.who,addr))
self.tcpCliSock = socket(AF_INET, SOCK_STREAM)
self.tcpCliSock.connect(addr)
self.tcpCliSock.setblocking(1)
self.tcpCliSock.settimeout(1)
# self.tcpCliSock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,8192)
log.debug('{} socket data:{}'.format(self.who,'success'))
except BaseException as msg:
log.error('{} socket conn_result:{}'.format(self.who,msg))
def send(self,data):
try:
try:
result = self.tcpCliSock.send(data)
except BaseException as msg:
# self.__conn(self.conn_data)
# result = self.tcpCliSock.send(data)
log.error(msg)
raise msg
log.debug('{} socket send_len:{}'.format(self.who, result))
return result
except BaseException as msg:
raise log.error('{} socket send_error:{}'.format(self.who,msg))
def recv(self,size,code):
result =''
try:
try:
result = self.tcpCliSock.recv(int(size))
while len(result)<int(size):
result +=self.tcpCliSock.recv(int(size)-len(result))
except:
pass
if not result:
log.error('{} socket recv_result ERROR:result is null'.format(self.who))
log.debug('{} socket recv_result:{}'.format(self.who, len(result)))
data_struct = struct.unpack(code, result)
log.debug('{} socket recv_result_unpack:{}'.format(self.who, data_struct))
return data_struct
except BaseException as msg:
log.error('{} socket recv_error:{}'.format(self.who,msg))
raise msg
def only_recv(self,size):
try:
result = self.tcpCliSock.recv(int(size))
while len(result) < int(size):
result += self.tcpCliSock.recv(int(size) - len(result))
log.debug('{} socket only_recv_result_len:{}'.format(self.who, len(result)))
log.debug('only_recv:{}'.format(result))
return result
except BaseException as msg:
log.error('{} socket recv_error:{}'.format(self.who,msg))
raise msg
def close(self):
try:
log.debug('{} socket has been closed'.format(self.who))
self.tcpCliSock.close()
except BaseException as msg:
log.debug('{} socket close_error:{}'.format(self.who,msg))
- 建立程序池、執行緒池套餐
def main():
print '業務程式碼'
class MyThread(threading.Thread):
def __init__(self, func, args, name=''):
threading.Thread.__init__(self)
self.name = name
self.func = func
self.args = args
def run(self):
apply(self.func, self.args)
def main_thread():
global sn
threads = []
nloops = xrange(thread_num)# thread_num併發執行緒數
for i in nloops:
mac, mac_real, sn = getMacSn()
t = MyThread(main, (mac,mac_real,sn))
threads.append(t)
for i in nloops:
threads[i].start()
for i in nloops:
threads[i].join()
if __name__=='__main__':
result = ''
pool = multiprocessing.Pool(processes=proc)# processes程序池數量
log.info("main process(%d) running..." % os.getpid())
for i in xrange(proc_num):# proc_num 併發程序數量
result = pool.apply_async(main_thread)
pool.close()
pool.join()
if not result.successful():
log.error('主程序異常:{}'.format(result.successful()))
else:
log.info('goodbye:主程序({})執行完畢'.format(os.getpid()))
- 透過在業務程式碼中計算統計引數,然後放在記憶體中累計計算,輸出到指定級別日誌中,用到的主要方法是透過設定全域性變數進行統一計算:
class globalMap:
# 拼裝成字典構造全域性變數 借鑑map 包含變數的增刪改查
map = {}
def set_map(self, key, value):
if(isinstance(value,dict)):
value = json.dumps(value)
self.map[key] = value
log.debug(key + ":" + str(value))
def set(self, **keys):
try:
for key_, value_ in keys.items():
self.map[key_] = str(value_)
log.debug(key_+":"+str(value_))
except BaseException as msg:
log.error(msg)
raise msg
def del_map(self, key):
try:
del self.map[key]
return self.map
except KeyError:
log.error("key:'" + str(key) + "' 不存在")
def get(self,*args):
try:
dic = {}
for key in args:
if len(args)==1:
dic = self.map[key]
log.debug(key+":"+str(dic))
elif len(args)==1 and args[0]=='all':
dic = self.map
else:
dic[key]=self.map[key]
return dic
except KeyError:
log.warning("key:'" + str(key) + "' 不存在")
return 'Null_'
總結
多程序的方式和多執行緒進行對比,雖然 Python 全域性鎖的限制導致執行緒有點瑕疵,但是跟程序比起併發,還是能甩程序幾條街的。透過這種方式進行高密度業務邏輯操作,可以很輕鬆找到服務端瓶頸。
宣告:看到過的好文章,轉載分享,若有侵權,請及時聯絡,速刪。