之前有几个站点是用Wordpress搭建的,准备迁移服务器后,需要安装Wordpress, 这次想用Docker来迁移。于是就在Docker中安装Wordpress. 看到Compose的文档里有一篇Quickstart: Docker Compose and WordPress, 于是使用Compose安装。

按照文档安装,报如下错误。

1
2
wordpress_mysql | 2016-09-22T09:34:33.723755Z 0 [ERROR] InnoDB: mmap(137428992 bytes) failed; errno 12
wordpress_mysql | 2016-09-22T09:34:33.723792Z 0 [ERROR] InnoDB: Cannot allocate memory for the buffer pool

发现是内存不够,因为已经安装了一个MySQL, 并且服务器内存只有512M, 于是内存吃紧, 只好放弃这种安装方法,直接连接现有的MySQL容器。

docker pull wordpress拉取最新版本的Wordpress后,执行命令后台启动Wordpress

1
docker rund --name wordpress --link mysql:db -p 8000:80 -e WORDPRESS_DB_USER=root -e WORDPRESS_DB_NAME=wordpress -e WORDPRESS_DB_PASSWORD=password wordpress:latest

又发现CPU吃紧, 查看进程后发现loop0进程很占CPU,原来用的是Apache服务器,这就显得浪费了,我已经安装的Nginx了,而Wordpress镜像里还安装Apache。

得想想其它办法才好。当然如果内存足够,这样安装也没什么问题。

联系作者

phpmyadmin用来查看MySQL非常方便,决定给测试服务器上安装一个,这样开发人员就可以通过浏览器访问数据库,很方便。

查看官方的说明文档,一直没弄懂要如何连接本地安装的MySQL, 好在测试环境的数据库是通过docker容器安装的,方便连接。

执行docker pull phpmyadmin/phpmyadmin拉取官方的镜像后,假设已经存在一个在运行的MySQL容器,名字叫mysql, 执行

1
docker run --name myadmin -d --link mysql:db -p 8000:80 -e PMA_USER=root -e PMA_PASSWORD=mysql123456 phpmyadmin/phpmyadmin

其中–link是连接已经运行的mysql容器, db只是别名. 而PMA_USER和PMA_PASSWORD是连接mysql的用户名和密码, 这样开发人员在使用phpmyadmin时,就可以不用输入用户名和密码了,之后访问服务器的8000端口就可以访问MySQL数据库了,很方便。

参考资料:

联系作者

在使用requests库时,遇到中文文件名问题,现象如下

如果是英文文件名,body中的内容为

1
Content-Disposition: form-data; name="document"; filename="my.doc"

如果是中文文件名,body中的内容为

1
Content-Disposition: form-data; name="document"; filename*=utf-8''03%E7%BD%91%E7%AB%99%E5%A4%87%E6%A1%88%E4%BF%A1%E6%81%AF%E7%9C%9F%E5%AE%9E%E6%80%A7%E6%8F%90%E4%BE%9B%E7%94%B5%E5%AD%90%E7%89%88%E6%89%AB%E6%8F%8F%E4%BB%B6.doc

查看requests的源码,从request.post一路跟踪,进入session.request, 进入 session.prepare_request, 进入PreparedRequest.prepare, 进入进入PreparedRequest.prepare_body, 进入PreparedRequest._encode_files, 进入RequestField.make_multipart, 进入RequestField._render_parts, 进入RequestField._render_part,最后进入RequestField.format_header_param方法, 内容如下

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
def format_header_param(name, value):
"""
Helper function to format and quote a single header parameter.

Particularly useful for header parameters which might contain
non-ASCII values, like file names. This follows RFC 2231, as
suggested by RFC 2388 Section 4.4.

:param name:
The name of the parameter, a string expected to be ASCII only.
:param value:
The value of the parameter, provided as a unicode string.
"""

if not any(ch in value for ch in '"\\\r\n'):
result = '%s="%s"' % (name, value)
try:
result.encode('ascii')
except UnicodeEncodeError:
pass
else:
return result
if not six.PY3: # Python 2:
value = value.encode('utf-8')
value = email.utils.encode_rfc2231(value, 'utf-8')
value = '%s*=%s' % (name, value)
return value

这里明确说明文件名只能是ascii, 于是只好将文件名urlencode一下,使用urllib将文件对象的名字修改后,例如假设document是一个文件对象, 可以进行如下修改

1
2
f = {'document': (urllib.quote(document.name.encode('utf-8')), document, document.content_type)}
response = requests.post(UPLOAD_DOCUMENT_URL + "?access_token=%s" % access_token, files=f, data=params)

参考资料:

联系作者

MySQL提供了count命令来统计表中的记录数, 使用起来非常方便。但加上where条件的count命令有时会很慢,此时需要优化。

最近在项目中就遇到这个问题。看下面两个SQL的查询结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> SELECT COUNT(*) FROM user WHERE user.is_active = 1;
+-----------+

| COUNT(id) |
+-----------+

| 239563 |
+-----------+

1 row in set (0.21 sec)

mysql> select count(*) from sku_pro where is_agent=1;
+-----------+

| count(id) |
+-----------+

| 1253535 |
+-----------+

1 row in set (0.11 sec)

看到这个结果,肯定会很吃惊。user表的数据比sku_pro表的数据少很多,但执行时间却比它长,匪夷所思。查看两个表结构,两个表都是MyISAM引擎,看到的差别是user表61个字段,而sku_pro表16个字段。

在网上找各种资料,没有找到问题的答案。于是请教同学,同学提示说看数据大小,如果数据大的,读到内存需要花费更多的IO,这样会更慢一些。但发现两个表数据大小差不多。

在查看show table status的结果时,发现user表的RowFormat是Dynamic, 而sku_pro表的是Fixed, 于是查看user表结构,发现很多字段是varchar字段,于是猜测在Dynamic时,要查找到字段的值,需要计算便宜量,这样速度更慢。在MySQL Optimization: Faster Selects with MyISAM fixed row format一文中有提到这个问题。

但是如果不想改变RowFormat又该怎么做?在StackoverFlow上提了这个问题, 很快得到解答,只需要创建索引即可,需要注意索引里有加上id字段,

1
CREATE INDEX is_active ON user (is_active,id);

值得注意的是,这里不是创建

1
CREATE INDEX is_active ON user (is_active);

在解答中有提到,这个索引会被忽略。

参考资料:

联系作者

最近在添加微信支付的公众号支付功能,遇到一些问题,记录下来。

您没有JSAPI支付权限

这个是因为在商户后台的支付配置里,没有设置支付目录

支付签名失效

这里要注意app支付的签名字段和公众号支付的签名字是不一样

app签名字段

查看app支付的业务流程,可以看到

1
2
步骤3:统一下单接口返回正常的prepay_id,再按签名规范重新生成签名后,将数据传输给APP。
参与签名的字段名为appId,partnerId,prepayId,nonceStr,timeStamp,package。注意:package的值格式为Sign=WXPay

公众号支付jsapi

公众号支付的业务流程里,没有看到像APP支付那样的步骤,没有写明签名字段,查看H5调起支付API, 看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId""wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp"" 1395712654", //时间戳,自1970年以来的秒数
"nonceStr""e61463f8efa94090b1f366cccfbbb444", //随机串
"package""prepay_id=u802345jgfjsdfgsdg888",
"signType""MD5", //微信签名方式:
"paySign""70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}

还是没有弄清楚paySign字段的生成方式。

但是在公众平台的微信支付文档里, 又看到发起微信支付请求

1
2
3
4
5
6
7
8
9
10
11
12
wx.chooseWXPay({
timestamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
nonceStr: '', // 支付签名随机串,不长于 32 位
package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
paySign: '', // 支付签名
success: function (res) {
// 支付成功后的回调函数
}
});

备注:prepay_id 通过微信支付统一下单接口拿到,paySign 采用统一的微信支付 Sign 签名生成方法,注意这里 appId 也要参与签名,appId 与 config 中传入的 appId 一致,即最后参与签名的参数有appId, timeStamp, nonceStr, package, signType。

在这里提到了生成paySign需要的字段。但纳闷的是为何发起微信支付请求会有两种方式,难道是新旧方式?

需要注意的是,在getBrandWCPayRequest方式中,时间戳字段是timeStamp,而在chooseWXPay中,时间戳字段是timestamp.

在APP支付中,package字段的值是Sign=WXPay, 而在公众号支付中,package字段的值是 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***

联系作者

最近遇到一个DateTimeField的问题,记录下来

在Post的model里设置

1
update_time = models.DateTimeField(auto_now=True)

希望每次更新文章时,这个update_time能自动更新为当前时间。

发现了三种情况

使用queryset的update方法

这里使用update方法更新,Post.object.filter(pk=id).update(), 发现不会更新update_time

使用save方法

1
2
post = Post.object.filter(pk=id)[:1][0]
post.save()

此时会更新update_time

使用save方法,指定update_fields

1
2
post = Post.object.filter(pk=id)[:1][0]
post.save(update_fields=['title'])

此时指定了更新字段,不更新update_time

联系作者

有时候想了解MySQL的版本号,运行状态等,此时status命令派上了用场

在mysql客户端里输入status命令,可以查看MySQL服务器的运行状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mysql> status
--------------
mysql Ver 14.14 Distrib 5.7.10, for osx10.11 (x86_64) using EditLine wrapper

Connection id: 282
Current database: blog
Current user: root@localhost
SSL: Not in use
Current pager: less
Using outfile: ''
Using delimiter: ;
Server version: 5.7.10 Homebrew
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8
Db characterset: utf8
Client characterset: utf8
Conn. characterset: utf8
UNIX socket: /tmp/mysql.sock
Uptime: 29 days 18 hours 32 min 59 sec

Threads: 1 Questions: 8074 Slow queries: 0 Opens: 1592 Flush tables: 1 Open tables: 354 Queries per second avg: 0.003

而当你想查看MySQL的表运行状态时,可以执行show table status

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

mysql> show table status;
+-----------------------+--------+---------+------------+------+----------------+-------------+------------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+--------------------+----------+----------------+---------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time | Update_time | Check_time | Collation | Checksum | Create_options | Comment |
+-----------------------+--------+---------+------------+------+----------------+-------------+------------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+--------------------+----------+----------------+---------+
| wp_commentmeta | MyISAM | 10 | Dynamic | 25 | 207 | 5184 | 281474976710655 | 10240 | 0 | 827 | 2016-03-21 16:04:14 | 2016-03-21 16:04:14 | NULL | utf8_general_ci | NULL | | |
| wp_comments | MyISAM | 10 | Dynamic | 129 | 558 | 72008 | 281474976710655 | 20480 | 0 | 1005 | 2016-03-21 16:20:21 | 2016-03-21 16:20:21 | 2016-03-21 16:20:21 | utf8_general_ci | NULL | | |
| wp_links | MyISAM | 10 | Dynamic | 13 | 97 | 1272 | 281474976710655 | 3072 | 0 | 25 | 2016-03-21 16:04:14 | 2016-03-21 16:04:15 | NULL | utf8_general_ci | NULL | | |
| wp_options | MyISAM | 10 | Dynamic | 247 | 1451 | 364504 | 281474976710655 | 22528 | 6084 | 95575 | 2016-03-21 16:20:22 | 2016-06-21 17:46:41 | NULL | utf8_general_ci | NULL | | |
| wp_postmeta | MyISAM | 10 | Dynamic | 952 | 116 | 111032 | 281474976710655 | 47104 | 76 | 2312 | 2016-03-21 16:04:14 | 2016-05-30 14:39:33 | NULL | utf8_general_ci | NULL | | |
| wp_posts | MyISAM | 10 | Dynamic | 782 | 4397 | 3439132 | 281474976710655 | 1702912 | 0 | 1212 | 2016-03-21 16:20:21 | 2016-06-21 17:46:38 | 2016-03-21 16:20:22 | utf8_general_ci | NULL | | |
| wp_term_relationships | MyISAM | 10 | Fixed | 620 | 21 | 13020 | 5910974510923775 | 31744 | 0 | NULL | 2016-03-21 16:04:15 | 2016-05-30 14:00:52 | NULL | utf8_general_ci | NULL | | |
| wp_term_taxonomy | MyISAM | 10 | Dynamic | 368 | 39 | 14696 | 281474976710655 | 17408 | 0 | 421 | 2016-03-21 16:04:15 | 2016-05-30 14:00:53 | NULL | utf8_general_ci | NULL | | |
| wp_termmeta | InnoDB | 10 | Dynamic | 0 | 0 | 16384 | 0 | 32768 | 0 | 1 | 2016-07-04 11:14:31 | NULL | NULL | utf8mb4_unicode_ci | NULL | | |
| wp_terms | MyISAM | 10 | Dynamic | 367 | 45 | 16880 | 281474976710655 | 35840 | 0 | 420 | 2016-03-21 16:04:15 | 2016-03-21 16:04:16 | NULL | utf8_general_ci | NULL | | |
| wp_usermeta | MyISAM | 10 | Dynamic | 28 | 79 | 3092 | 281474976710655 | 10240 | 864 | 31 | 2016-03-21 16:04:15 | 2016-06-21 17:46:33 | NULL | utf8_general_ci | NULL | | |
| wp_users | MyISAM | 10 | Dynamic | 1 | 96 | 96 | 281474976710655 | 4096 | 0 | 2 | 2016-03-21 16:20:21 | 2016-03-21 16:20:21 | NULL | utf8_general_ci | NULL | | |
| wp_wp_rp_tags | MyISAM | 10 | Dynamic | 173 | 24 | 4200 | 281474976710655 | 8192 | 0 | NULL | 2016-03-21 16:04:15 | 2016-03-21 16:04:16 | NULL | latin1_swedish_ci | NULL | | |
+-----------------------+--------+---------+------------+------+----------------+-------------+------------------+--------------+-----------+----------------+---------------------+---------------------+---------------------+--------------------+----------+----------------+---------+

其中,Engine是使用的引擎,Row_format指的是行模式(Dynamic说明存在varchar字段, Fixed说明表的所有列长度都是固定的),Rows指的是表记录数,Data_length指的是表数据大小。

联系作者

因为启动docker服务器需要root权限,所以连接到docker服务器需要输入sudo, 即便设置了sudo不需要输入密码, 还是需要输入sudo, 可以通过增加docker组的方式来避免输入sudo.

增加一个docker组

sudo groupadd docker

将用户test加入docker组,用户test需要重新登录才能生效

sudo gpasswd -a test docker

重启docker服务

sudo service docker restart

此后执行docker命令就不需要加上sudo

参考资料:

联系作者

准备慢慢的将香港VPS上的服务迁移到新的服务器,趁这个机会,学习使用Docker技术。首先安装MySQL

搜索MySQL镜像

使用docker search命令,docker search mysql

1
2
3
4
5
INDEX       NAME                                 DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
docker.io docker.io/mysql MySQL is a widely used, open-source relati... 3021 [OK]
docker.io docker.io/mysql/mysql-server Optimized MySQL Server Docker images. Crea... 194 [OK]
docker.io docker.io/centurylink/mysql Image containing mysql. Optimized to be li... 46 [OK]
docker.io docker.io/sameersbn/mysql 36 [OK]

下载MySQL镜像

使用docker pull命令,docker pull docker.io/mysql

启动MySQL镜像

使用docker run命令,执行docker run docker.io/mysql
提示

1
2
error: database is uninitialized and password option is not specified
You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD

参考How to connect to MySQL running on Docker from the host machine,执行docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d docker.io/mysql启动

访问MySQL容器

1
2
sudo docker exec -it mysql bash
mysql -uroot -ppassword

参考资料

联系作者