1.1. 前言
由于系统历史问题,在系统中有着许多的定时任务。小到每5分钟一次,大到每天一次。由于这种定时任务的无限增多,当达到时间的最小公倍数的时候就会许多个任务同时跑起来,这样就会让MySQL发生抖动。就会发现时不时的MySQL性能会变差。
1.2. 原因
1、可能是定时任务的SQL太复杂,这一点可以从慢SQL中看出。
2、发生这样的原因是每个定时任务并发的对数据库进行操作。
1.3. 解决思路
1、排查优化定时任务的SQL
2、将并发任务改成串行任务去执行。
这边我们讨论如何让定时任务串行执行。
1.4. zookeeper的引入
要让并行的任务串行起来,这我就想到了MySQL的mutex。在对资源进行操作的时候需要对资源加上一把锁,如果有其他 线程/进程 需要对同一资源进行操作的时候需要等待上一个 线程/进程 释放 mutex。
但是,我们不可能去实现或使用MySQL中的mutex,这是我们就需要使用到 “分布式锁”,这边我更喜欢将他叫做 “全局资源锁”。因此我们就需要引入zookeeper,用他来实现我们的”全局资源锁”的功能。当定时任务执行的时候都需要获取这个”全局资源锁”,当定时任务执行完之后再释放”全局资源锁”,从而让其他并发的定时任务去争抢”全局资源锁”,并运行定时任务。
1.5. kazoo的使用
kazoo 是zookeeper的Python API,我使用它的理由是十分方便,比使用zookeeper java API(zkClient/dubbo)方便太多了。
1.6. zookeeper环境
这边我就不说需要如何搭建一个zookeeper集群了有需要自行去 Google。或到zookeeper官网上学习。
我这边使用的是zookeeper单机伪集群
主要配置如下
1
2
3
|
server.1 = 192.168.1.233:2887:3887
server.2 = 192.168.1.233:2888:3888
server.3 = 192.168.1.233:2889:3889
|
数据库环境
1
2
3
4
5
6
7
8
9
10
11
12
|
USE test;
CREATE TABLE `orders` (
`order_id` int(11) DEFAULT NULL,
`num` int(11) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO orders VALUES
(1, 11111, 10),
(2, 22222, 10),
(3, 33333, 10),
(4, 44444, 10),
(5, 55555, 10);
|
1.7. 模拟步骤
1、编写lock_mysql_1.py文件对mysql进行SELECT操作,并sleep 30秒后释放锁。
2、编写lock_mysql_2.py文件对mysql进行SELECT操作,不用sleep。
3、运行lock_mysql_1.py,后马上运行op_mysql_2.py。
4、观察lock_mysql_2.py被阻塞(30秒)后执行效果。
lock_mysql_1.py中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Program: kazoo lock
# Author : HH
# Date : 2016-04-07
from kazoo.client import KazooClient
from kazoo.recipe.lock import Lock
import mysql.connector
import time
import logging
import sys
reload(sys)
sys.setdefaultencoding(‘utf-8’)
logging.basicConfig()
if __name__ == ‘__main__’:
# zookeeper 链接参数
zk_config = {
‘hosts’ : ‘192.168.1.233:2181,192.168.1.233:2182,192.168.1.233:2183’,
‘timeout’ : 5000,
‘read_only’ : False,
}
# MySQL链接参数
mysql_conf = {
‘host’ : ‘localhost’,
‘port’ : ‘3306’,
‘database’ : ‘test’,
‘user’ : ‘root’,
‘password’ : ‘root’
}
# 创建zookeeper客户端
zk = KazooClient(**zk_config)
zk.start()
# 设置zookeeper锁节点并返回锁
lock = zk.Lock(“/lock_mysql”, “lock_1”)
with lock:
”‘加锁并对数据库进行操作’”
# 查询数据SQL
sql = ”‘SELECT * FROM orders’”
conn = mysql.connector.connect(**mysql_conf)
cursor = conn.cursor()
cursor.execute(sql)
for x in cursor:
print x
# 睡眠30秒
time.sleep(30)
|
lock_mysql_2.py中的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Program: kazoo lock
# Author : HH
# Date : 2016-04-07
from kazoo.client import KazooClient
from kazoo.recipe.lock import Lock
import mysql.connector
import time
import logging
import sys
reload(sys)
sys.setdefaultencoding(‘utf-8’)
logging.basicConfig()
if __name__ == ‘__main__’:
# zookeeper 链接参数
zk_config = {
‘hosts’ : ‘192.168.1.233:2181,192.168.1.233:2182,192.168.1.233:2183’,
‘timeout’ : 5000,
‘read_only’ : False,
}
# MySQL链接参数
mysql_conf = {
‘host’ : ‘localhost’,
‘port’ : ‘3306’,
‘database’ : ‘test’,
‘user’ : ‘root’,
‘password’ : ‘root’
}
# 创建zookeeper客户端
zk = KazooClient(**zk_config)
zk.start()
# 设置zookeeper锁节点并返回锁
lock = zk.Lock(“/lock_mysql”, “lock_2”)
with lock:
”‘加锁并对数据库进行操作’”
# 查询数据SQL
sql = ”‘SELECT * FROM orders’”
conn = mysql.connector.connect(**mysql_conf)
cursor = conn.cursor()
cursor.execute(sql)
for x in cursor:
print x
|
在session1中执行 lock_mysql_1.py (睡眠30秒)
1
|
time python lock_mysql_1.py
|
在session2中执行lock_mysql_2.py 观察呗阻塞现象(没有睡眠当时也被阻塞了)
1
|
time python lock_mysql_2.py
|
1.8. 补充
你也可以使用手动掉用acquire(获得锁)和release(释放锁)方法来实现。不过没有特殊情况我的编程风格不会这么干。
1.9. 总结
其实这只是变向的方法解决定时任务的问题。最重要的还是要和业务沟通,或其他技术实现定时任务内容(如定时任务是统计一些信息,是否可以使用storm等技术来代替)。从而减少对定时任务的使用。
文章转载来自:ttlsa.com