- django原生支持是不支持 以連接池方式連接數據庫的
概述
在使用 Django 進行 Web 開發時, 我們避免不了與數據庫打交道。 當並發量低的時候, 不會有任何問題。 但一旦並發量達到一定數量, 就會導致 數據庫的連接數會被瞬時占滿。
這將導致一個嚴重的后果 --其他應用, 或者 Django 本身的其他服務都無法訪問數據庫。 這是不可容忍的!
造成這樣的結果的原因之一是 Django 底層與數據庫的連接方式並不是連接池。
它會為每一次的數據庫訪問建立連接。 這樣當並發量上來以后, 數據庫的連接數就不夠用了。
所以本篇博客介紹一種修改 Django 底層與數據庫的連接方式為連接池的形式, 避免出現這種災難性的后果。 (本文數據庫采用 MySQL)
安裝 djorm-ext-pool
可以使用命令行的方式安裝
pip install djorm-ext-pool
也可以直接使用 Pycharm 進行搜索后安裝。
創建 APP
創建一個名為 djorm_pool 的 APP, 在 init.py 文件中寫如下語句(原封不動的復制進去就可以, 此 APP 中只有一個 init.py 文件就可以):
# -*- coding: utf-8 -*-
import logging
from functools import partial
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
from sqlalchemy import exc
from sqlalchemy import event
from sqlalchemy.pool import manage
from sqlalchemy.pool import Pool
from sqlalchemy.event import listens_for
POOL_PESSIMISTIC_MODE = getattr(settings, "DJORM_POOL_PESSIMISTIC", False)
POOL_SETTINGS = getattr(settings, 'DJORM_POOL_OPTIONS', {})
POOL_SETTINGS.setdefault("recycle", 3600)
logger = logging.getLogger('djorm.pool')
@event.listens_for(Pool, "checkout")
def _on_checkout(dbapi_connection, connection_record, connection_proxy):
logger.debug("connection retrieved from pool")
if POOL_PESSIMISTIC_MODE:
cursor = dbapi_connection.cursor()
try:
cursor.execute("SELECT 1")
except:
# raise DisconnectionError - pool will try
# connecting again up to three times before raising.
raise exc.DisconnectionError()
finally:
cursor.close()
@event.listens_for(Pool, "checkin")
def _on_checkin(*args, **kwargs):
logger.debug("connection returned to pool")
@event.listens_for(Pool, "connect")
def _on_connect(*args, **kwargs):
logger.debug("connection created")
def patch_mysql():
class hashabledict(dict):
def __hash__(self):
return hash(tuple(sorted(self.items())))
class hashablelist(list):
def __hash__(self):
return hash(tuple(sorted(self)))
class ManagerProxy(object):
def __init__(self, manager):
self.manager = manager
def __getattr__(self, key):
return getattr(self.manager, key)
def connect(self, *args, **kwargs):
if 'conv' in kwargs:
conv = kwargs['conv']
if isinstance(conv, dict):
items = []
for k, v in conv.items():
if isinstance(v, list):
v = hashablelist(v)
items.append((k, v))
kwargs['conv'] = hashabledict(items)
if 'ssl' in kwargs:
ssl = kwargs['ssl']
if isinstance(ssl, dict):
items = []
for k, v in ssl.items():
if isinstance(v, list):
v = hashablelist(v)
items.append((k, v))
kwargs['ssl'] = hashabledict(items)
return self.manager.connect(*args, **kwargs)
try:
from django.db.backends.mysql import base as mysql_base
except (ImproperlyConfigured, ImportError) as e:
return
if not hasattr(mysql_base, "_Database"):
mysql_base._Database = mysql_base.Database
mysql_base.Database = ManagerProxy(manage(mysql_base._Database, **POOL_SETTINGS))
def patch_postgresql():
try:
from django.db.backends.postgresql_psycopg2 import base as pgsql_base
except (ImproperlyConfigured, ImportError) as e:
return
if not hasattr(pgsql_base, "_Database"):
pgsql_base._Database = pgsql_base.Database
pgsql_base.Database = manage(pgsql_base._Database, **POOL_SETTINGS)
def patch_sqlite3():
try:
from django.db.backends.sqlite3 import base as sqlite3_base
except (ImproperlyConfigured, ImportError) as e:
return
if not hasattr(sqlite3_base, "_Database"):
sqlite3_base._Database = sqlite3_base.Database
sqlite3_base.Database = manage(sqlite3_base._Database, **POOL_SETTINGS)
def patch_all():
patch_mysql()
patch_postgresql()
patch_sqlite3()
patch_all()
配置 settings.py
打開 settings.py 文件, 按照如下要求進行配置:
INSTALLED_APPS = [
...
'djorm_pool', # 將剛創建的項目添加進 INSTALLED_APPS
]
DJORM_POOL_OPTION = {
'pool_size': 20, # 本連接池的最大連接個數
'max_overflow': 0,
'recycle': 3600
}
DJORM_POOL_PESSIMISTIC = True
這樣 settings.py 就配置好了。
修改 MySQL 配置文件
將 MySQL 的配置文件 mysqld.cnf 按如下參數進行配置:
#
# The MySQL database server configuration file.
#
# You can copy this to one of:
# - "/etc/mysql/my.cnf" to set global options,
# - "~/.my.cnf" to set user-specific options.
#
# One can use all long options that the program supports.
# Run program with --help to get a list of available options and with
# --print-defaults to see which it would actually understand and use.
#
# For explanations see
# http://dev.mysql.com/doc/mysql/en/server-system-variables.html
# This will be passed to all mysql clients
# It has been reported that passwords should be enclosed with ticks/quotes
# escpecially if they contain "#" chars...
# Remember to edit /etc/mysql/debian.cnf when changing the socket location.
# Here is entries for some specific programs
# The following values assume you have at least 32M ram
[mysqld_safe]
socket = /var/run/mysqld/mysqld.sock
nice = 0
[mysqld]
#
# * Basic Settings
#
user = mysql
pid-file = /var/run/mysqld/mysqld.pid
socket = /var/run/mysqld/mysqld.sock
port = 3306
basedir = /usr
datadir = /var/lib/mysql
tmpdir = /tmp
lc-messages-dir = /usr/share/mysql
skip-external-locking
open_files_limit = 10240
back_log = 600
external-locking = FALSE
#
# Instead of skip-networking the default is now to listen only on
# localhost which is more compatible and is not less secure.
#bind-address = 127.0.0.1
#
# * Fine Tuning
#
key_buffer_size = 32M
max_allowed_packet = 32M
thread_stack = 192K
thread_cache_size = 300
# This replaces the startup script and checks MyISAM tables if needed
# the first time they are touched
myisam-recover-options = BACKUP
max_connections = 3000
max_connect_errors = 6000
#thread_concurrency = 10
#
# * Query Cache Configuration
#
query_cache_limit = 4M
query_cache_size = 64M
query_cache_min_res_unit = 2k
#default-storage-engine = MyISAM
#
# * Logging and Replication
#
# Both location gets rotated by the cronjob.
# Be aware that this log type is a performance killer.
# As of 5.1 you can enable the log at runtime!
#general_log_file = /var/log/mysql/mysql.log
#general_log = 1
#
# Error log - should be very few entries.
#
log_error = /var/log/mysql/error.log
#
# Here you can see queries with especially long duration
#log_slow_queries = /var/log/mysql/mysql-slow.log
#long_query_time = 2
#log-queries-not-using-indexes
#
# The following can be used as easy to replay backup logs or for replication.
# note: if you are setting up a replication slave, see README.Debian about
# other settings you may need to change.
#server-id = 1
#log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 10
max_binlog_size = 100M
#binlog_do_db = include_database_name
#binlog_ignore_db = include_database_name
#
# * InnoDB
#
# InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
# Read the manual for more InnoDB related options. There are many!
#
# * Security Features
#
# Read the manual, too, if you want chroot!
# chroot = /var/lib/mysql/
#
# For generating SSL certificates I recommend the OpenSSL GUI "tinyca".
#
# ssl-ca=/etc/mysql/cacert.pem
# ssl-cert=/etc/mysql/server-cert.pem
# ssl-key=/etc/mysql/server-key.pem
transaction_isolation = READ-COMMITTED
# 設定默認的事務隔離級別.可用的級別如下:
# READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE
# 1.READ UNCOMMITTED-讀未提交2.READ COMMITTE-讀已提交3.REPEATABLE READ -可重復讀4.SERIALIZABLE -串行
tmp_table_size = 256M
# tmp_table_size 的默認大小是 32M。如果一張臨時表超出該大小,MySQL產生一個 The table tbl_name is full 形式的錯誤,如果你做很多高級 GROUP BY 查詢,增加 tmp_table_size 值。如果超過該值,則會將臨時表寫入磁盤。
max_heap_table_size = 256M
long_query_time = 2
#log-bin = /data/3306/mysql-bin
binlog_cache_size = 4M
max_binlog_cache_size = 8M
max_binlog_size = 512M
expire_logs_days = 7
key_buffer_size = 2048M
#批定用於索引的緩沖區大小,增加它可以得到更好的索引處理性能,對於內存在4GB左右的服務器來說,該參數可設置為256MB或384MB。
read_buffer_size = 1M
# MySql讀入緩沖區大小。對表進行順序掃描的請求將分配一個讀入緩沖區,MySql會為它分配一段內存緩沖區。read_buffer_size變量控制這一緩沖區的大小。如果對表的順序掃描請求非常頻繁,並且你認為頻繁掃描進行得太慢,可以通過增加該變量值以及內存緩沖區大小提高其性能。和sort_buffer_size一樣,該參數對應的分配內存也是每個連接獨享。
read_rnd_buffer_size = 16M
# MySql的隨機讀(查詢操作)緩沖區大小。當按任意順序讀取行時(例如,按照排序順序),將分配一個隨機讀緩存區。進行排序查詢時,MySql會首先掃描一遍該緩沖,以避免磁盤搜索,提高查詢速度,如果需要排序大量數據,可適當調高該值。但MySql會為每個客戶連接發放該緩沖空間,所以應盡量適當設置該值,以避免內存開銷過大。
bulk_insert_buffer_size = 64M
#批量插入數據緩存大小,可以有效提高插入效率,默認為8M
myisam_sort_buffer_size = 128M
# MyISAM表發生變化時重新排序所需的緩沖
myisam_max_sort_file_size = 10G
# MySQL重建索引時所允許的最大臨時文件的大小 (當 REPAIR, ALTER TABLE 或者 LOAD DATA INFILE).
# 如果文件大小比此值更大,索引會通過鍵值緩沖創建(更慢)
myisam_repair_threads = 1
# 如果一個表擁有超過一個索引, MyISAM 可以通過並行排序使用超過一個線程去修復他們.
# 這對於擁有多個CPU以及大量內存情況的用戶,是一個很好的選擇.
#自動檢查和修復沒有適當關閉的 MyISAM 表
lower_case_table_names = 1
#這個參數用來設置 InnoDB 存儲的數據目錄信息和其它內部數據結構的內存池大小,類似於Oracle的library cache。這不是一個強制參數,可以被突破。
innodb_buffer_pool_size = 256M
# 這對Innodb表來說非常重要。Innodb相比MyISAM表對緩沖更為敏感。MyISAM可以在默認的 key_buffer_size 設置下運行的可以,然而Innodb在默認的 innodb_buffer_pool_size 設置下卻跟蝸牛似的。由於Innodb把數據和索引都緩存起來,無需留給操作系統太多的內存,因此如果只需要用Innodb的話則可以設置它高達 70-80% 的可用內存。一些應用於 key_buffer 的規則有 — 如果你的數據量不大,並且不會暴增,那么無需把 innodb_buffer_pool_size 設置的太大了
#innodb_data_file_path = ibdata1:1024M:autoextend
#表空間文件 重要數據
innodb_thread_concurrency = 8
#服務器有幾個CPU就設置為幾,建議用默認設置,一般為8.
innodb_flush_log_at_trx_commit = 2
# 如果將此參數設置為1,將在每次提交事務后將日志寫入磁盤。為提供性能,可以設置為0或2,但要承擔在發生故障時丟失數據的風險。設置為0表示事務日志寫入日志文件,而日志文件每秒刷新到磁盤一次。設置為2表示事務日志將在提交時寫入日志,但日志文件每次刷新到磁盤一次。
innodb_log_buffer_size = 16M
#此參數確定些日志文件所用的內存大小,以M為單位。緩沖區更大能提高性能,但意外的故障將會丟失數據.MySQL開發人員建議設置為1-8M之間
innodb_log_file_size = 128M
#此參數確定數據日志文件的大小,以M為單位,更大的設置可以提高性能,但也會增加恢復故障數據庫所需的時間
innodb_log_files_in_group = 3
#為提高性能,MySQL可以以循環方式將日志文件寫到多個文件。推薦設置為3M
innodb_max_dirty_pages_pct = 90
#推薦閱讀 http://www.taobaodba.com/html/221_innodb_max_dirty_pages_pct_checkpoint.html
# Buffer_Pool中Dirty_Page所占的數量,直接影響InnoDB的關閉時間。參數innodb_max_dirty_pages_pct 可以直接控制了Dirty_Page在Buffer_Pool中所占的比率,而且幸運的是innodb_max_dirty_pages_pct是可以動態改變的。所以,在關閉InnoDB之前先將innodb_max_dirty_pages_pct調小,強制數據塊Flush一段時間,則能夠大大縮短 MySQL關閉的時間。
innodb_lock_wait_timeout = 120
# InnoDB 有其內置的死鎖檢測機制,能導致未完成的事務回滾。但是,如果結合InnoDB使用MyISAM的lock tables 語句或第三方事務引擎,則InnoDB無法識別死鎖。為消除這種可能性,可以將innodb_lock_wait_timeout設置為一個整數值,指示 MySQL在允許其他事務修改那些最終受事務回滾的數據之前要等待多長時間(秒數)
innodb_file_per_table = 0
我們可以采用高並發的方式測試, 博主采用的是每秒寫入 20 條數據。 經過一晚上的測試(大概 10 個小時), 數據庫中已存入幾萬條數據, 但是連接數一直保持在 13 個。
大功告成!