Redis與服務接口相結合的高併發python
Xadserver接口與redis結合實現高併發須要知足如下三個條件:redis
事前須要關注的三個概念:xadserver併發數,redis支持的最大鏈接數,鏈接池的最大鏈接數(以後若是須要動態設置redis的最大鏈接數而又不想重啓redis影響線上的服務,能夠經過config set maxclients 65535 命令實時設置):json
127.0.0.1:6379> config get maxclients併發
1) "maxclients"app
2) "2000"python2.7
先來查詢一下,咱們當前redis支持的最大鏈接數:高併發
實驗1測試
爲了說明鏈接池最大鏈接數與併發數的關係,咱們 先作如下這個試驗;ui
# coding=utf-8
from gevent import monkey
import requests
monkey.patch_all()
import gevent
import redis
import time
import redis
Pool = redis.ConnectionPool(host='127.0.0.1', port=6379, max_connections=10, db=2)
pr = redis.Redis(connection_pool=Pool, decode_responses=True)
# print pr.get('__h5_campaign_info__122671')
import sys
def getFunc(key):
"""取key"""
v = pr.get('__h5_campaign_info__122671')
print v
def call_gevent(count):
"""調用gevent 模擬高併發"""
begin_time = time.time()
run_gevent_list = []
num = 1
for i in range(count):
print('--------------%d--Test-------------' % i)
mykey = 'test' + str(num)
run_gevent_list.append(gevent.spawn(getFunc, mykey))
num = num + 1
gevent.joinall(run_gevent_list)
end = time.time()
print('測試併發量' + str(count))
print('單次測試時間(平均)s:', (end - begin_time) / count)
print('累計測試時間 s:', end - begin_time)
if __name__ == '__main__':
# 併發請求數量
test_count = 20 # 改變併發量查看測試效果。。我這裏取7000,10000,20000進行測試。記得將rdis的最大鏈接數改成30000並重啓redis。
while 1:
call_gevent(count=test_count)spa
如代碼所示,咱們設置的鏈接池最大鏈接數是10,而併發數咱們設置爲20,執行該併發條件下的返回結果,發現出現以下異常;
Traceback (most recent call last):
File "src/gevent/greenlet.py", line 716, in gevent._greenlet.Greenlet.run
File "/Users/liquid/PycharmProjects/aliyun_sls/redis高併發.py", line 19, in getFunc
v = pr.get('__h5_campaign_info__122671')
File "/Users/liquid/PycharmProjects/aliyun_sls/venv/lib/python2.7/site-packages/redis/client.py", line 1207, in get
return self.execute_command('GET', name)
File "/Users/liquid/PycharmProjects/aliyun_sls/venv/lib/python2.7/site-packages/redis/client.py", line 752, in execute_command
connection = pool.get_connection(command_name, **options)
File "/Users/liquid/PycharmProjects/aliyun_sls/venv/lib/python2.7/site-packages/redis/connection.py", line 970, in get_connection
connection = self.make_connection()
File "/Users/liquid/PycharmProjects/aliyun_sls/venv/lib/python2.7/site-packages/redis/connection.py", line 986, in make_connection
raise ConnectionError("Too many connections")
ConnectionError: Too many connections
2019-04-16T06:55:17Z <Greenlet "Greenlet-2" at 0x102051050: getFunc('test13')> failed with ConnectionError
查看redis的當前鏈接數爲11(去除本就存在的1個鏈接,可知redis當前鏈接數爲10,而更多的併發數並無分配到資源)
127.0.0.1:6379> info clients
# Clients
connected_clients:11
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0
實驗2
以後咱們設置redis鏈接池的最大鏈接數爲20,再試一次;
測試併發量20
('\xe5\x8d\x95\xe6\xac\xa1\xe6\xb5\x8b\xe8\xaf\x95\xe6\x97\xb6\xe9\x97\xb4\xef\xbc\x88\xe5\xb9\xb3\xe5\x9d\x87\xef\xbc\x89s:', 7.859468460083007e-05)
('\xe7\xb4\xaf\xe8\xae\xa1\xe6\xb5\x8b\xe8\xaf\x95\xe6\x97\xb6\xe9\x97\xb4 s:', 0.0015718936920166016)
能夠正常處理併發的請求,並不會報錯
以後再去查看redis的當前鏈接數爲21(去除本就存在的1個鏈接,可知redis當前鏈接數爲20每個併發的請求都分配到了的redis鏈接池中的資源),
127.0.0.1:6379> info clients
# Clients
connected_clients:21
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0
實驗3
此時咱們修改咱們鏈接池的最大鏈接數爲30,再次執行代碼,代碼依然沒有報錯
測試併發量20
('\xe5\x8d\x95\xe6\xac\xa1\xe6\xb5\x8b\xe8\xaf\x95\xe6\x97\xb6\xe9\x97\xb4\xef\xbc\x88\xe5\xb9\xb3\xe5\x9d\x87\xef\xbc\x89s:', 7.630586624145508e-05)
('\xe7\xb4\xaf\xe8\xae\xa1\xe6\xb5\x8b\xe8\xaf\x95\xe6\x97\xb6\xe9\x97\xb4 s:', 0.0015261173248291016)
結論:
在使用redis的鏈接池訪問redis裏的資源時,鏈接池數必須大於等於併發數(兩者同時小於redis可支持的最大鏈接數),不然多出來的併發數將會由於分配不到redis的資源而收到報錯信息(參考地址:https://redis.io/topics/clients)
上述三個實驗並無與咱們的接口服務結合在一塊兒,下面將結合接口服務再次實驗
實驗4:
# coding=utf-8
from gevent import monkey
import requests
monkey.patch_all()
import gevent
import redis
import time
def getFunc(key):
"""取key"""
v = requests.get('http://127.0.0.1:91/sss')
print v
def call_gevent(count):
"""調用gevent 模擬高併發"""
begin_time = time.time()
run_gevent_list = []
num = 1
for i in range(count):
print('--------------%d--Test-------------' % i)
mykey = 'test' + str(num)
run_gevent_list.append(gevent.spawn(getFunc, mykey))
num = num + 1
gevent.joinall(run_gevent_list)
end = time.time()
print('測試併發量' + str(count))
print('單次測試時間(平均)s:', (end - begin_time) / count)
print('累計測試時間 s:', end - begin_time)
if __name__ == '__main__':
# 併發請求數量
test_count = 100 # 改變併發量查看測試效果。。我這裏取7000,10000,20000進行測試。記得將rdis的最大鏈接數改成30000並重啓redis。
while 1:
call_gevent(count=test_count)
接口代碼以下:
Pool = redis.ConnectionPool(host='127.0.0.1', port=6379, max_connections=50, db=2)
# 從池子中拿一個連接
pr = redis.Redis(connection_pool=Pool, decode_responses=True)
@app.route("/sss", methods=["GET", "POST"])
def test_concurrent():
try:
pr.get('__h5_campaign_info__111127')
return json.dumps({'code':1})
except:
traceback.print_exc()
return json.dumps({'code':0})
本地機器上,該接口最多支持2600的併發(uwsgi啓動兩個進程處理請求),因此等下模擬請求時,數量並不會太多(100的併發量作測試)
此時併發量爲100,而咱們設置的redis鏈接池爲50,按照預期,應該是100個請求分攤到鏈接池的50個資源上,多餘的請求資源等待前50個資源的釋放(事實上鍊接池會在初始化時申請一部分資源,使用完後歸還鏈接池,從而達到減小鏈接redis與註銷鏈接開銷的目的),接下來看接口的響應如何
有一半的請求響應以下(預期中的):
127.0.0.1 - - [16/Apr/2019 15:20:07] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:20:07] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:20:07] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:20:07] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:20:07] "GET /sss HTTP/1.1" 200 -
另外一半的請求響應(預期外的):
Traceback (most recent call last):
File "run.py", line 533, in test_concurrent
pr.get('__h5_campaign_info__111127')
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/client.py", line 880, in get
return self.execute_command('GET', name)
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/client.py", line 570, in execute_command
connection = pool.get_connection(command_name, **options)
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 897, in get_connection
connection = self.make_connection()
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 904, in make_connection
raise ConnectionError("Too many connections")
ConnectionError: Too many connections
此時查詢redis中的客戶端鏈接數爲51(去除本就存在的一個鏈接),數量和鏈接池申請的資源相匹配
127.0.0.1:6379> info clients
# Clients
connected_clients:51
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0
實驗5:
此時修改鏈接池的最大鏈接數爲100,併發量依然控制在100
所有請求的響應爲:
127.0.0.1 - - [16/Apr/2019 15:30:58] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:30:58] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:30:58] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:30:58] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:30:58] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:30:58] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:30:58] "GET /sss HTTP/1.1" 200 -
此時查詢redis的客戶端鏈接數(去除本就存在的一個鏈接),數量和鏈接池申請的資源徹底匹配
127.0.0.1:6379> info clients
# Clients
connected_clients:101
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0
實驗6
鏈接池的最大鏈接數設置爲200,併發量控制在100
所有請求的響應爲:
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:35:49] "GET /sss HTTP/1.1" 200 –
此時查詢redis的客戶端鏈接數(去除本就存在的一個鏈接),數量和併發數徹底匹配
127.0.0.1:6379> info clients
# Clients
connected_clients:101
client_recent_max_input_buffer:2
client_recent_max_output_buffer:0
blocked_clients:0
這6個實驗所有都是基於redis可支持的最大鏈接數大於鏈接池的最大鏈接數以及併發數
實驗7:
此時咱們修改redis可支持的最大鏈接數爲20,再次實驗(修改命令以下):
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "2000"
127.0.0.1:6379> config set maxclients 20
OK
127.0.0.1:6379> config get maxclients
1) "maxclients"
2) "20"
此時修改鏈接池的數量爲30,而咱們的併發也控制在30,再次實驗:
返回結果有20個以下:
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 -
127.0.0.1 - - [16/Apr/2019 15:42:32] "GET /sss HTTP/1.1" 200 –
剩餘10個請求的返回結果以下:
Traceback (most recent call last):
File "run.py", line 533, in test_concurrent
pr.get('__h5_campaign_info__111127')
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/client.py", line 880, in get
return self.execute_command('GET', name)
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/client.py", line 578, in execute_command
connection.send_command(*args)
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 563, in send_command
self.send_packed_command(self.pack_command(*args))
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 538, in send_packed_command
self.connect()
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 446, in connect
self.on_connect()
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 520, in on_connect
if nativestr(self.read_response()) != 'OK':
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 577, in read_response
response = self._parser.read_response()
File "/Users/liquid/PycharmProjects/Env/lib/python2.7/site-packages/redis/connection.py", line 255, in read_response
raise error
ConnectionError: max number of clients reached
此時可分配資源內的20個請求能夠正常返回,而資源外的10個請求則按照redis拒絕請求的信息返回
綜合以上7個實驗,咱們得出如下結論:
1, redis可支持的最大鏈接數必須大於等於鏈接池設置的最大鏈接數或者併發數的任意一個數值
2, redis鏈接池申請資源的數量必須大於等於併發數,不然多餘的併發請求將會由於分配不到資源而出現異常
3, 考慮到redis不停建立鏈接和銷燬鏈接的系統開銷會影響咱們的接口質量,因此咱們在xadserver項目中使用redis鏈接池申請到足夠的資源供併發請求分配調用
仍需確認的點:
1,修改機器的文件描述符以及redis配置的最大鏈接數超過咱們的併發數,redis是否能夠按照預期接受並處理請求
2,接口內調用redis實時獲取數據,對接口的響應速度影響有多大(須要徹底模擬線上環境測試)