ftp和ssh账户访问限制

2011年5月27日 roboter 没有评论

服务器:ubuntu

ftp服务:vsftpd

ftp只允许用户访问自己的主目录:

  1. vi /etc/vsftpd.conf
  2. 将chroot_list_enable=YES,chroot_list_file=/etc/vsftpd.chroot_list两行注释打开
  3. 设置用户的配置文件,添加一行:user_config_dir=/etc/local_root
  4. vi /etc/vsftpd.chroot_list
  5. 写入要限制的用户,如:user1
  6. cd  /etc/local_root
  7. vi user1
  8. 写入user1用户的主目录:local_root=/home/user1
  9. 重新启动vsftpd即可

禁止ssh用户登陆,方案:采用白名单,只有设定的用户才能ssh登录,其他用户统统取消

  1. vi /etc/ssh/sshd_config
  2. 添加:AllowUsers username
  3. 重新启动ssh
分类: linux 标签:

django admin 定制

2011年2月28日 roboter 没有评论

新做了一个项目,需要有后台管理功能,很自然,想到了django的admin功能。

项目的要求:

  1. 基于django的用户系统进行开发,不能修改django的源代码
  2. 权限管理,不同的用户显示不同的数据行,字段也各不相同。

首先打算在django中的Permission加入特定的权限,但是考察下来感觉很怪,django的Permission是表级的权限管理,而用户的是逻辑权限,两种权限混在一起,很难管理,所以django的Permission就不能使用了,自定义一个权限表来管理。

不能修改django的代码,用户的信息只能另外创建表来管理。但是admin后台界面通过django的user来管理比较方便,因此需要定制user的显示。google了一下,发现也很简单,先注销django中的useradmin,再把自己的useradmin注册上去就可以了。

class CustomUserAdmin(UserAdmin):

……

admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin)

在user的list界面中,需要显示额外的字段,如city_name,则在CustomUserAdmin中添加下列代码:

def city_name(self,obj):
cityname = get_city_by_user(obj)
return cityname
city_name.short_description = u’城市’
city_name.verbose_name = u’城市’
city_name.allow_tags  = True

第二个需求则是需要依据不同的用户显示不同的数据,行和列都不相同。考察了django的admin代码,发现数据的获取都放在ChangeList这个类中,而ModelAdmin类有一个接口get_changelist,可以重载这个接口,加入自定义的ChangeList类,从而完成数据的过滤。changelist中的关键接口get_results,具体代码如下:

def get_results(self, request):
user = request.user
qs = self.query_set

#依据用户删除字段,
self.list_display.remove(’field_name1′)
self.list_display.remove(’field_name2′)
#依据用户过滤数据
qs = qs.filter(user=user)
self.query_set = qs
return super(xxxChangeList,self).get_results(request)

另外就是需要修改一些template文件,django的template文件很有层次,在http://www.slideshare.net/lincolnloop/customizing-the-django-admin中的描写如下:

Templates can be overridden:
● Across an entire project
admin/change_form.html
● Across an application
admin/<my_app>/change_form.html
● For an individual model
admin/<my_app>/<my_model>/change_form.html

查看django的ModelAdmin的render_change_form函数,也体现了这个层次

return render_to_response(form_template or [
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_form.html" % app_label,
"admin/change_form.html"
], context, context_instance=context_instance)

如果需要修改某个app的某个model的change界面,则文件位置在admin/app/model/change_form.html。模板中需要{% extends “admin/change_form.html” %}

ModelAdmin的保存接口如下:

def save_model(self, request, obj, form, change):
“”"
Given a model instance save it to the database.
“”"
obj.save()

保存动作就可以在重载的save_model接口中进行。

django代码可读性比较好,带着问题来读admin代码,还是很容易找到解决办法。

分类: django 标签:

uwsgi使用

2011年1月19日 roboter 没有评论

使用了gevent一段时间之后,应用进程占用的内存稳步增长,一不小心就好几百M了,gevent应该存在一些内存泄漏,同时还有一些其他问题,放到生产环境中很不适合。于是查找了一些其他的wsgi server,看了国外的一些评测,选定了uwsgi,安装之后,和gevent进行了一些测试比较,两者相差不多,在响应速度上似乎uwsgi要胜出一点。

下载源码之后,直接make,得到uwsgi,就可以直接用了。

首先设置django应用的配置文件,django-uwsgi.py文件格式如下:

import os
os.environ['DJANGO_SETTINGS_MODULE'] = ‘yourapp.settings’

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

uwsgi启动命令:

/var/www/uwsgi –socket 127.0.0.1:8098 –pythonpath /var/www/yourapp -w django-uwsgi -p 10 -M -d /var/log/nginx/uwsgi-yourapp.log –uid www-data –l 10000

nginx中的配置非常简单,nginx从0.8.40之后,默认支持了uwsgi协议。

location / {
    uwsgi_pass 127.0.0.1:8098;
    include uwsgi_params;
}

uwsgi中的-p 10表示启动10个进程,-l 10000 表示处理了10000次请求之后,会自动重新启动该进程。

uwsgi的重启也非常优雅,和nginx一样,发一个信号过去,uwsgi的主控制程序会等候应用进程处理完当前事务之后,再进行重启。uwsig的说明在http://projects.unbit.it/uwsgi/wiki/uWSGIReload

重启命令:

sudo killall -s HUP /var/www/uwsgi

和nginx的信号一样

分类: django, uwsgi 标签:

postgresql 使用记录

2011年1月14日 roboter 没有评论

在一个网站中,django_session表很容易就突破了200万条记录,后来定期清理过期的session,也会保持在100多万条记录。数据库是mysql,后果是django_session表不定期损坏,每次都得修复或者干脆truncate。所以打算弄弄postgresql。

os 是ubuntu 10.04,使用sudo apt-get install postgres,将会安装8.4版本的postgres。安装完之后,执行psql,就报了一个错:

FATAL:  Ident authentication failed for user

无法验证用户

google了一下,需要使用postgres帐号进行管理,su postgres即可。

在网上有一个中文文档,非常有帮助,http://man.chinaunix.net/database/postgresql_8.0_CN/.

创建用户:在shell下直接执行createuser 命令,或者在psql下执行CREATE USER name;

更改用户密码:alter user username1 with password ‘new password’;

导出数据:

pg_dump -b dbame | gzip > dbname.gz

恢复数据:

createdb dbname
gunzip -c dbname.gz | psql dbname

更改数据库所有者:

alter database dbname owner to usename;

在/etc/postgres/目录下,修改pg_hba.conf,加上用户访问数据库的权限

登录命令:

psql -U usename -W -d dbname

分类: postgresql 标签:

gevent试用比较

2010年12月30日 roboter 没有评论

以前一直使用django推荐的fastcgi,用来启动python 应用进程,应用进程的内存占用很多,一个进程三四十M,启动100个就差不多占用了4个G。而整个linux系统就只有4个G的内存。在压力测试中,200个并发导致内存耗尽,linux系统停止响应,最后把应用进程数降到60个,整个系统就很稳定了。

听说gevent占用内存很少,速度又快,所以尝试一下。从http://blog.tinbrain.net/blog/2010/sep/30/django-and-gevent/了解了在django中使用gevent的方法,依样画葫芦,nginx的配置如下:

1 server {
2     listen 83;
3     server_name 192.168.1.101;
4     # nginx docs recommend try_files over "if"
5     location    /   {
6         try_files $uri @proxy;
7     }
8     location @proxy {
9         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
10         proxy_set_header Host $http_host;
11         proxy_pass http://unix:/var/www/flashgame/server.sock;
12     }
13 }

python应用进程的启动脚本如下:

import sys, os

from gevent import wsgi
from gevent import socket
from gevent import monkey

sys.stdout = sys.stderr

# Just in case
monkey.patch_all()

import pwd

# Get this so we can chown/chgrp the socket and let nginx read it
pe = pwd.getpwnam(’www-data’)

SOCK = ‘/var/www/flashgame/server.sock’

sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
    os.remove(SOCK)
except OSError:
    pass

sock.bind(SOCK)
os.chown(SOCK, pe.pw_uid, pe.pw_gid)
os.chmod(SOCK,0770)
sock.listen(256)

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
print application
# Set up for Django
sys.path.insert(0,’/var/www/’)
sys.path.insert(0,’/var/www/flashgame/’)
os.environ['DJANGO_SETTINGS_MODULE'] = ‘flashgame.settings’
#print ’sys.path.insert’

#wsgi.WSGIServer(sock, application, spawn=None).serve_forever()
wsgi.WSGIServer(sock, application).serve_forever()

注意,wsgi.WSGIServer函数中不能使用spawn=None,否则无法读取memcached中的内容。默认的spawn会创建新的greenlet处理请求。

在linux虚拟机上,83端口是gevent,80端口是facstcgi,资源有限,两者同时启动,fastcgi启动12个进程,只进行10个并发的测试。对比效果如下:

内存占用:gevent始终比1个应用进程内存用量稍多,而fastcgi需要使用10个应用进程的内存量。

CPU使用:两者差不多,在虚拟机上测试,不够精确。

响应时间:gevent基本上是fastcgi的两倍,10个并发中,gevent耗时8.786 seconds,fastcgi耗时14.478 seconds

测试方法:

ab -n 100 -c 10 http://192.168.1.101/

ab -n 100 -c 10 http://192.168.1.101:83/

详细结果:

gevent的结果如下:
Server Software:        nginx/0.8.53
Server Hostname:        192.168.1.101
Server Port:            83
Document Path:          /zhuangti/p8/
Document Length:        7238 bytes
Concurrency Level:      10
Time taken for tests:   8.786 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      753100 bytes
HTML transferred:       723800 bytes
Requests per second:    11.38 [#/sec] (mean)
Time per request:       878.601 [ms] (mean)
Time per request:       87.860 [ms] (mean, across all concurrent requests)
Transfer rate:          83.71 [Kbytes/sec] received
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       3
Processing:   698  847 233.1    743    1619
Waiting:      697  847 233.1    743    1619
Total:        699  848 233.2    743    1620
Percentage of the requests served within a certain time (ms)
  50%    743
  66%    756
  75%    778
  80%    978
  90%   1289
  95%   1327
  98%   1550
  99%   1620
100%   1620 (longest request)

fastcgi的结果如下:
Server Software:        nginx
Server Hostname:        192.168.1.101
Server Port:            80
Document Path:          /zhuangti/p8/
Document Length:        7238 bytes
Concurrency Level:      10
Time taken for tests:   14.478 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      750200 bytes
HTML transferred:       723800 bytes
Requests per second:    6.91 [#/sec] (mean)
Time per request:       1447.802 [ms] (mean)
Time per request:       144.780 [ms] (mean, across all concurrent requests)
Transfer rate:          50.60 [Kbytes/sec] received
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   1.2      0       4
Processing:   165 1399 1835.1    726    7485
Waiting:      164 1398 1834.7    726    7484
Total:        165 1400 1836.2    727    7489
Percentage of the requests served within a certain time (ms)
  50%    727
  66%   1122
  75%   1444
  80%   1583
  90%   5549
  95%   6561
  98%   7320
  99%   7489
100%   7489 (longest request)

 

update:

0.13.1版本的gevent存在内存泄漏,网站运行几天之后,应用进程占用的内存稳步上升,很快就到了200多M,换用了0.14.0开发版本的进行一下试验:

hg clone https://bitbucket.org/denis/gevent

开发版本需要libevent-2.0以上,安装完成之后,gevent将http的get请求全部变成post请求,这个样子网站是无法接受的,先换回fastcgi。

分类: django, gevent 标签:

django debug toolbar

2010年12月20日 roboter 没有评论

 

django debug toolbar有两个分支,有一个有profile功能,所以就选定他了

https://github.com/dcramer/django-debug-toolbar

下了一个zip包,安装过程中发现错误,记录如下:

提示找不到profile包,在linux下安装

sudo apt-get install python-profiler

安装profile包

然后继续出错:

http://192.168.1.102:90/?djDebugStatic=djDebugToolbar.compressed.js

找不到js文件

因为安装程序有误,有些文件没有拷贝到相应目录。解决方法:将压缩包中的media目录拷贝到django debug toolbar的安装目录django_debug_toolbar-0.2-py2.6.egg/debug_toolbar下,

然后界面出来了,但是只要点击某一项,就会弹出新窗口,很讨厌,查看了js代码,需要修改djDebugToolbar.js文件,把其中的注释“//return false”打开,这样才能避免打开新窗口。

分类: django, python 标签:

django升级之后的文件名UnicodeEncodeError

2010年10月5日 roboter 没有评论

系统原为python2.5和django1.1

最近把django升级为1.2.3,然后问题来了,处理一个中文名文件的时候,报错:

File "/usr/lib/python2.5/site-packages/django/db/models/fields/files.py", line 77, in _get_size
   return self.storage.size(self.name)
File "/usr/lib/python2.5/site-packages/django/core/files/storage.py", line 216, in size
   return os.path.getsize(self.path(name))
File "/usr/lib/python2.5/posixpath.py", line 139, in getsize
   return os.stat(filename).st_size
UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 30-33: ordinal not in range(128)

 

系统的locale早就设置为zh_CN.utf-8,django1.1都没有问题的。先google一下,发现了也有相同的问题,

https://groups.google.com/group/django-users/browse_thread/thread/87034bee2e887886/066bf517e426acbb?lnk=gst&q=os.stat+UnicodeEncodeError#066bf517e426acbb

另外也发现了一篇好文章:python默认对待unicode的方式,介绍了python编解码的方法。

python主要涉及到两个编码问题:
1. 文件系统使用编码方式. 这个值由 sys.getfilesystemencoding() 取得
2. python的unicode函数使用的默认解码方式. 这个值由 sys.getdefaultencoding() 取得.

最后的解决方式是修改python库中的site.py文件,在setencoding函数中强制设置为utf-8的编解码方式。

分类: django, linux, python 标签:

mysql状态查询

2010年5月17日 roboter 没有评论

一般在mysql下输入show global status,即可看见所有相关状态,有200多项,一项一项列出来,看起来很不方便。

在网上找到了mysqlreport 这个工具,可以分类显示相关状态,很清晰。

解包之后,执行如下命令即可。

./mysqlreport –user root –password ******

相关文章:通过show status 来优化MySQL数据库

在网上发现了mysqlreport各个项目的详细介绍,查不到最早的出处了,就从nick处转载过来,感谢nick。

MySQLReport 报告说明

2010年05月9日

Report Header

MySQL 5.0.3 uptime 0 0:34:26 Fri Sep 1 19:46:02 2006
MySQL Server 的版本、自上次启动后已经过多少时间、目前 Server 的日期与时间

key report
MySQL Server的Buffer分为Global Buffer和Thread Buffer

计算 Server 至少需使用的总内存数量的方式为:
min_memory_needed = global_buffer + (thread_buffers * max_connection)

MyISAM Storage Engine 将每个 table 分成三个档案储存在硬盘之中.

FRM: 储存这个数据表的结构
MYD: Row Data,也就是你存在 example 数据表里的数据
MYI: 此数据表的索引

Buffer used 380.00k of 512.00M %Used: 0.07
Current 59.32M %Usage: 11.59
Write ratio 0.93
Read ratio 0.00

注意:新版本的key report变成了

Write hit      99.98%
Read hit       99.92%

Write hit      99.98%

Read hit       99.92%

表示命中率,这样更好理解。

1:Buffer used指出 MySQL “曾经” 耗用过的最大内存数量,因此目前 “正在使用” 的内存数量有可能少于(甚至大于)这个数字。MySQL 称此数值为 “High Water Mark”
如果你的 MySQL 已经使用了 80~90% 以上的 Key Buffer,你就应该要调高 key_buffer_size.

2:Current表示 Key_blocks_unused 这个系统变量来决定目前 MySQL “正在使用” 的 Share Key Buffer 大小

索引(Indexes, Keys)主要是在内存内(RAM-Based)进行操作的,索引之所以如此有用有部份原因就归功于它们主要是在 RAM 里面运作,因此拥有极高的存取效能,不像储存在硬盘中的数据存取速度非常慢。然而,不可否认的是 MySQL 终究还是必须从硬盘中将索引读入 RAM 或是将储存在 RAM 中的索引写回硬盘之中

3:Write ratio 标示着 MySQL 将索引写入硬盘与 MySQL 将索引写入 RAM 的比值(Write Ratio = MySQL 将索引写入硬盘的次数 / MySQL 将索引写入 RAM 的次数)。具有接近于1 的Write Ratio 并不是一件很罕见的事,就像 MySQL 官方手册中所说的,如果你的 MySQL 最主要的活动是 Update、Insert 等等,那么 Write Ratio 将会很接近于1

4:Read Ratio 比 Write Ratio 来得重要一些,它标示了 MySQL 从硬盘读取索引与从 RAM 读取索引的比值(Read Ratio = MySQL 从硬盘读取索引的次数 / MySQL 从 RAM 读取索引的次数)。Read Ratio 的值应该要是 0.00 或 0.01,若大于这个值则表示 Server 有问题需要进一步的调查,通常此问题的成因是 Share Key Buffer 设得太小造成 MySQL 需要不断地从硬盘中读取所需要的索引信息,而这个动作是十分没有效率的并且完全抵消了使用索引可以带来的好处。
Questions Report
Questions是第二重要的信息,因为它可以告诉你 MySQL 到底都在忙些什么事情。Questions 包含了 SQL queries 以及 MySQL protocol communications。大部份的人都只在意 Server 每秒可以处理多少查询(Queries Per Second, QPS),但若以整个 Server 的观点来考虑,QPS 其实是非常不精确的数值,它无法有效的告诉您 Server 的整体运作状况

Total 98.06k 47.46/s
DMS 81.23k 39.32/s %Total: 82.84
QC Hits 16.58k 8.02/s 16.91
COM_QUIT 200 0.10/s 0.20
Com_ 131 0.06/s 0.13
-Unknown 82 0.04/s 0.08
Slow 0 0.00/s 0.00 %DMS: 0.00
DMS 81.23k 39.32/s 82.84
SELECT 64.44k 31.19/s 65.72 79.33
INSERT 16.75k 8.11/s 17.08 20.61
UPDATE 41 0.02/s 0.04 0.05
REPLACE 0 0.00/s 0.00 0.00
DELETE 0 0.00/s 0.00 0.00
Com_ 131 0.06/s 0.13
change_db 119 0.06/s 0.12
show_fields 9 0.00/s 0.01
show_status 2 0.00/s 0.00

1:Total 表示
纯的记载 MySQL 总共响应过多少查询,第二个字段则记录响应的频率(QPS)

2:Distribution of Total Queries (DTQ):
所有的 Questions 可以大致区分为五个不同的类别:
1.Data Manipulation Statements (DMS)
2.query cache hits (QC Hits)
3.COM_QUIT
4.all other Com_ commands
5.Unknown

理想的情况下,你会希望 MySQL 把大部份的时间都花在 DMS 与 QC Hits 这两个类别,因为这两个类别才是真正在 “完成正事” 的类别

3:Data manipulation statements(DMS) 包含了:ELECT, INSERT, REPLACE, UPDATE, 与 DELETE(技术上来说,其实不只这几个类别但mysqlreport 只会用到这几类)。基本上,你可以将 DMS 想成是 MySQL 真正有在做些 “有用的事” 的情况,因此你会希望 DMS 是 MySQL 最忙着处理的事情。

4:QC Hits 是 MySQL 不需要实际执行 Query 而只要直接从 Query Cache 中即可找到所需数据的次数。拥有高比例的 QC Hits 是让人梦寐以求的事,因为从 Query Cache 直接存取所需要的数据是十分快速且有效率的。然而大部份的 MySQL Server 因为各种原因,而无法具有非常有效率的 Query Cache

5:COM_ 这个类别代表着所有 MySQL 所执行过的指令,通常与 MySQL protocol 相关。在正常的情况下,你会希望这个类别所占的比例越低越好,因为当这个数值很高的时候就表示 MySQL 正忙碌于无关紧要的事情上

Slow
表示它记录了 MySQL 总共执行了多少次 Slow Query。Slow Query 就是指执行所需时间超过某个时间区间的 Query。一般来说 Slow Query 占 Total Questions 的比例应该要低于 0.05,Slow Query 的次数(第一个字段)本身不是很重要,真正需要注意的是

Slow Query 占 Total Questions 的比例,若这比例偏高就代表 Server 有些问题需要解决
DMS 81.23k 39.32/s 82.84
SELECT 64.44k 31.19/s 65.72 79.33
INSERT 16.75k 8.11/s 17.08 20.61
UPDATE 41 0.02/s 0.04 0.05
REPLACE 0 0.00/s 0.00 0.00
DELETE 0 0.00/s 0.00 0.00

DMS 的子分类项目可以告诉我们,这台 MySQL Server 是属于哪一个类型的 MySQL Server,例如它是着重在 SELECT 操作或是 INSERT 操作,大部份的 MySQL Server 都是着重在 SELECT 操作。知道某台 Server 是属于哪一个类型的 MySQL Server 有助于我们思考报表中的其它信息,例如一台着重在 SELECT 操作的 MySQL Server 的 Write Ratio 应该会非常的接近 1,并有着较高的 Lock 时间。同时它也隐含了一个意义,就是也许你可以考虑使用 InnoDB Storage Engine,因为 MySQL 预设采用的 MyISAM Storage Engine 所提供的 Lock 层级只有

Table Lock(只能针对整个数据表锁定),而 InnoDB 则提供 Row Lock 层级的锁定机制(可只针对特定的 ROW 进行锁定,减少等待时间)。若是着重在 SELECT 操作的 Server,它的 Read Ratio 应该会接近于零,并有着非常低的 Table Lock 时间。

SELECT and Sort Report

Scan 38 0.02/s %SELECT: 0.06
Range 14 0.01/s 0.02
Full join 3 0.00/s 0.00
Range check 0 0.00/s 0.00
Full rng join 0 0.00/s 0.00
Sort scan 14 0.01/s
Sort range 26 0.01/s
Sort mrg pass 0 0.00/s

大致上来说,你只要注意Scan 与 Full Join。Scan 指的是有多少 SELECT statements 造成 MySQL 需要进行 Full Table Scan。Full Join 的意思与 Scan 差不多,但它是适用在多个 Tables 相互 Join 在一起的情况。这二种情况的执行效能都非常的差,因此原则上你会希望这两个数值越低越好。

Query Cache Report

Memory usage 17.81M of 32.00M %Used: 55.66
Block Fragmnt 13.05%
Hits 16.58k 8.02/s
Inserts 48.50k 23.48/s
Prunes 33.46k 16.20/s
Insrt:Prune 1.45:1 7.28/s
Hit:Insert 0.34:1

Query Cache Report 只有在 MySQL 有支持 Query Cache,以及 Query Cache 功能有开启的情况下才会有这段信息出现。

(1)Memory usage 表示此项目指出 Query Cache 的使用状况

(2)Block Fragmnt 表示块碎片,通常它会界于 10%~20% 之间

(3)Hits, Inserts, Prunes

Table Locks Report

Waited 1.01k 0.49/s %Total: 1.24
Immediate 80.04k 38.74/s

这个部份包含了两项信息:第一项是 Waited,代表 MySQL 需要等待以取得 table lock 的次数。第二项是 Immediate,表示 MySQL 不需要等待即可立刻取得 table lock 的次数

Tables Report

Open 107 of 1024 %Cache: 10.45
Opened 118 0.06/s

Tables Report 同样包含了二项信息:第一是 Open,显示目前正开启的 table 数量、总共可开启的最大数量,以及 Table Cache 的使用状况。第二是 Opend,表示截至目前为止 MySQL 总共开启过的 Table 数量,以及除上 Uptime 后的比值。这里有两件事值得注意:首先是Table Cache 的使用状况,100% 的 Table Cache 使用率并不是一件坏事但你可以试着调大 Table Cache 以增进效能。第二是 MySQL 开启Table 的平均速率,若这个值很高则表示您的 table_cache 设得太小了,需要调大一些。一般来说,MySQL 开启 Table 的平均速率最好是小于 1/s。但大于这个数值也不一定就是坏事,有些调校良好且运作的十分有效率的 MySQL Server 其值为 7/s 并使用了 100% 的 Table Cache

Connections Report

Max used 77 of 600 %Max: 12.83
Total 202 0.10/s

Connections Report 所代表的意义与 Tables Report 相似,请各位以此类推。比较需要注意的是:若你发现 Connections 的使用率接近100%,也许你会想调大 max_connections 的值以允许 MySQL 的 Client 建立更多联机。然而,这通常是一种错误。我们常常可以发现很多网络上的数据会教我们要调大 max_connections,但却从来没有给一个明确的理由。事实上,max_connections 的默认值(100),就算是对于负载十分沉重但有良好调校过的 Server 都已十分足够。MySQL 对于单一联机的数据处理通常只需要零点几秒的时间即可完成,就算是最大只能使用 100 个联机也够让你用上很长一段时间。若是您的 Server 有着非常高的最大联机数(max connections)或是单一联机需要很长时间才可完成,那么问题八成不是 max_connections 的值不够大而是在别的地方,例如 slow queries、索引设计不良、甚至是过于缓慢的 DNS 解析。在您将 max_connections 的值调到 100 以上之前,您应该要先确定真的是因为 Server 过于忙碌而需要调高此数值,而不是其它地方出了问题。每秒平均联机数有可能会很高,事实上,若这个值很高而且 Server 的运作十分顺畅,那么这通常会是一个好现象,无需

担心。大部份 Server 的每秒平均联机数应该都会低于 5/s。

Created Temp Report

Disk table 10 0.00/s
Table 26 0.01/s
File 3 0.00/s

MySQL 可以建立暂时性的数据表,它可建立在硬盘中、档案里、或是 RAM 之中,而 Created Temp Report 则提供了相关的数据供您参考。这些数据大多是相对而言,没有一定的标准,但将暂时性的数据表建立在硬盘中是十分没有效率的,因此 Disk table 的值最好是三者中最小的一个。当暂时性的数据表被建立在硬盘中,表示此数据表没有办法被放进 RAM 里面(因为 tmp_table_size 的值设得不够大)。

Threads, Aborted, Bytes Reports

Running 55 of 77
Cache 0 %Hit: 0.5
Created 201 0.10/s
Slow 0 0.00/s

这几个部份大多没什么好解释的,只有一个项目值得特别说明:第 66 行的最后一个字段(%Hit)。每一个连接到 MySQL 的联机都是由不同的Thread 来处理,当 MySQL 启动时会预先建立一些 Threads 并保留在 Thread Cache 中,如此一来 MySQL 就不用一直忙着建立与删除Threads。但当每秒最大联机数大于 MySQL 的 Thread Cache 时,MySQL 就会进入 Thread Thrash 的状态:它不断地建立新的 Threads 以满足不断增加的联机的需求。当 Thread Thrash 发生时,%Hit 的数值就会降低。在本范例中 %Hit 的值为 0.05%,这是非常不好的,因为它表示几乎每一个新进来的联机都会造成 MySQL 建立新的 Thread。我们可以看到在此范例中造成此现象的原凶就在第 66 行的第一个字段,我们可以发现 Thread Cache 的值为 0,因此 thread_cache_size 的值需要调大

mysqlreport中的innodb部分详解

__ InnoDB Buffer Pool __________________________________________________
Usage 7.97M of 8.00M %Used: 99.61
Read hit 100.00%

Pages
Free 2 %Total: 0.39
Data 499 97.46 %Drty: 0.00
Misc 11 2.15
Latched 0 0.00

Reads 101.06M 8.5/s
From file 373 0.0/s 0.00
Ahead Rnd 19 0.0/s
Ahead Sql 13 0.0/s

Writes 860.88k 0.1/s
Flushes 254.62k 0.0/s
Wait Free 0 0/s

__ InnoDB Lock _________________________________________________________
Waits 424 0.0/s
Current 0
Time acquiring
Total 254266 ms
Average 599 ms
Max 39559 ms

__ InnoDB Data, Pages, Rows ____________________________________________
Data
Reads 502 0.0/s
Writes 344.09k 0.0/s
fsync 158.03k 0.0/s
Pending
Reads 0
Writes 0
fsync 0

Pages
Created 699 0.0/s
Read 523 0.0/s
Written 254.62k 0.0/s

Rows
Deleted 4.59k 0.0/s
Inserted 74.16k 0.0/s
Read 94.67M 8.0/s
Updated 40.61k 0.0/s

第一区块, 展示了 mysql innodb 的缓存统计信息.
其中, innodb跟myisam的缓存机制有较大区别, innodb不仅缓存索引,还缓存一些表数据.而myisam只缓存索引.

Usage 7.97M of 8.00M %Used: 99.61
Read hit 100.00%

Usage 表示, 总的缓存中, 当前已占用 7.97M, 使用率达 99.61%. 这种情况, 是存在瓶颈的, 需要适当增加缓存总量.
Read hit 表示缓存命中率 100%, 这个数值是比较理想的, 一般情况下, 都应该大于 99.98%.

Pages
Free 2 %Total: 0.39
Data 499 97.46 %Drty: 0.00
Misc 11 2.15
Latched 0 0.00

innodb的存储是按页分的, 每页的容量默认是 16K. (详见 http://www.mysqlperformanceblog.com/2006/06/04/innodb-page-size/)

这里的,Free指的是缓存中的总页数, 剩余的页, 占总的 0.39%.
Data是指缓存中, 存储索引数据的页的数量.其中, 最后一项 %Dtry 表示脏数据的百分比.所谓的脏数据是指, 对缓存数据更新后, 没有同步到硬盘的数据.
misc 和 latched 就是之前说的其他信息的一些缓存.

Reads 101.06M 8.5/s
From file 373 0.0/s 0.00
Ahead Rnd 19 0.0/s
Ahead Sql 13 0.0/s

Reads代表从缓存里, 总共读取了多少M的数据.
From file, 表示从硬盘文件中读取到缓存里的页数量.
Ahead Rnd, 表示随机预读的次数.
Ahead Sql, 表示全表扫描时, sql预读的次数.

Writes 860.88k 0.1/s
Flushes 254.62k 0.0/s
Wait Free 0 0/s

Writes , 表示写入缓存的总大小.
Flushes , 表示缓存数据更新到硬盘的大小.
Waint Free, 表示, 等待可写入数据的页的次数.
这里为什么会等待, 是因为, 数据写入到缓存页时, 必须保证, 这个页被创建好, 或者这个页之前的数据已经被同步到硬盘上.

Waits 424 0.0/s
Current 0
Time acquiring
Total 254266 ms
Average 599 ms
Max 39559 ms

Waits , 表示执行线程等待锁的释放的次数.
Current, 表示当前所有的执行线程, 正在等待锁的数量.
Time acquiring 里的 Total , 指的是, 等待锁所消耗的总时间.
Average, 是指, 平均消耗的时间.
Max, 是指, 单次等待锁, 所消耗最多的时间.

Data
Reads 502 0.0/s
Writes 344.09k 0.0/s
fsync 158.03k 0.0/s
Pending
Reads 0
Writes 0
fsync 0

Data里的Reads, 表示数据读取的次数.
Writes, 表示数据写入的数据量大小.
fsync, 表示缓存同步到硬盘的数据量大小.

Pending里的Reads, Writes, fsync , 表示当前进行读写, 同步的次数.

Pages
Created 699 0.0/s
Read 523 0.0/s
Written 254.62k 0.0/s

Pages 里的created, 表示, 总共创建过 699 个页.
Read, 表示被读取的页的数量.
Written, 表示写入到页里的,总大小.

Rows
Deleted 4.59k 0.0/s
Inserted 74.16k 0.0/s
Read 94.67M 8.0/s
Updated 40.61k 0.0/s

Rows里的四个操作,分别代表删除, 插入, 查询, 更新的数据量大小.

性能关注点分析
首先是Usage, 如果占比达 90 – 95%以上, 可能需要增加预设缓存大小.
调整的参数是 innodb_buffer_pool_size

其次是 Read Hit, 即缓存命中率, 如果该值远远小于100%, 就要调查缓存的有效性了.
比如, 缓存写次数是否大于查询次数. (Buffer里的reads和writes)

最后,需要关注的是, 数据的读写比例. 这里有个要注意的地方是, mysql如果启动到现在不到24小时或一个较长的运行周期, 这个读写比例值可能是不准的.

一般的应用,我感觉读写比例在 8/2 差不多. 如果读远远大于写, 那么你可以测下 MyISAM 引擎的性能, 看看是否适合你.

分类: 网站 标签:

django打包成客户端软件

2009年11月23日 roboter 没有评论

用django做了一个网站,希望断网的时候,也能够在本地使用。google的gears功能很弱,需要一大堆js来操作sqlite数据库,感觉很麻烦。因此还是需要自己弄一个客户端软件。

py2exe可以提供打包功能,将python打包成一个可执行的exe文件。google了一下“django py2exe” ,已经有了很多详细的文档。这个老外的博客比较详细,按照这个流程下来就基本搞定了。

首先确定要打的包:

  1. django
  2. cherrypy,是多线程的http服务器,替换django的http测试服务器
  3. 自己的app
  4. python的email

编写一个用于启动的python文件,从原作者处拷贝过来,稍微做了一些修改,命名为startdesktop.py

import os
import sys

os.environ[ 'DJANGO_SETTINGS_MODULE' ] = "settings"

import settings
from cherrypy import wsgiserver
import cherrypy
import webbrowser
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import AdminMediaHandler

if __name__ == "__main__":
  print ‘To exit DemoSite close this window.’

  # Set up site-wide config first so we get a log if errors occur.
  cherrypy.config.update({
    ‘environment’: ‘production’,
    ‘log.error_file’: ’site.log’,
    ‘log.screen’: True,
  })

  # run CherryPy and open browser
  try:
    full_media_path = os.path.dirname( os.path.abspath( sys.argv[ 0 ])) + settings.ADMIN_MEDIA_PREFIX

    cherrypy.tree.graft(
      AdminMediaHandler(
        WSGIHandler(),
        media_dir=full_media_path
      ),
      ‘/’
    )
    cherrypy.server.socket_port = settings.WEB_SERVER_PORT
    cherrypy.server.start()
    cherrypy.engine.start_with_callback( webbrowser.open, ( settings.START_URL,), )   
    cherrypy.engine.block()
  except KeyboardInterrupt:
    cherrypy.server.stop()

再然后就是py2exe的setup.py文件的编写了,基本上照抄下来,原作者使用的django版本很老,有一些模块已经没有了,同时又增加了很多新模块。同时目前的网站代码还是比较多,要手工一个个写是在太麻烦,就写一个函数搜一下,自动把当前的app文件添加进去。同时用正则表达式查找导入的django模块,发现一个就自动添加一个。修改后的setup.py文件如下:

# -*- coding: utf-8 -*-
from distutils.core import setup
import py2exe
import os

def add_path_tree( base_path, path, skip_dirs=[ '.svn', '.git' ]):
  path = os.path.join( base_path, path )
  partial_data_files = []
  for root, dirs, files in os.walk( os.path.join( path )):
    sample_list = []
    for skip_dir in skip_dirs:
      if skip_dir in dirs:
        dirs.remove( skip_dir )
    if files:
      for filename in files:
        sample_list.append( os.path.join( root, filename ))
    if sample_list:
      partial_data_files.append((
        root.replace(
          base_path + os.sep if base_path else ”,
          ”,
          1
        ),
        sample_list
      ))
  return partial_data_files

def add_python_file(includes,curpath,prefix=”):
    files = os.listdir(curpath)
    for f in files:
        fpath = os.path.join(curpath, f)
        if os.path.isdir(fpath):
            if f == ‘.svn’:
                continue
            add_python_file(includes,fpath,prefix+’%s.’ % f)
        else:
            name,ext = os.path.splitext(f)
            if ext == ‘.py’:
                includes.append(’%s%s’ % (prefix,name))
                add_django_import(includes,fpath)

import re
pat = r’from +django\.(.*?) +import +(.*?)( +as +|\n)’
def add_django_import(includes,filepath):
    #print filepath
    f = open(filepath,’r')
    data = f.read()
    f.close()
    models = re.findall(pat,data)
    for m in models:
        prefix = ‘django.%s.’ % m[0]
        pys = m[1].split(’,')
        for p in pys:
            p = p.lower().strip()
            dmodel = ‘%s%s’ % (prefix,p)
            add_django_file(includes,dmodel)
        prefix = ‘django.%s’ % m[0]
        add_django_file(includes,prefix)

def add_django_file(includes,dmodel):
    if dmodel in includes:
        return
    item = dmodel.replace(’.',’/')
    mpath = os.path.join(django_path,item)
    if os.path.exists(mpath):
        print ‘1′,dmodel
        includes.append(dmodel)
        #如果是django的目录,则继续寻找
        mpath = os.path.join(mpath,’__init__.py’)
        #print mpath
        if os.path.exists(mpath):
            add_django_import(includes,mpath)
    else:
        mpath = os.path.join(dja
ngo_path,’%s.py’ % item)
        if os.path.exists(mpath):
            #如果是django的文件,则继续寻找
            print ‘2′,dmodel
            #print mpath
            includes.append(dmodel)
            add_django_import(includes,mpath)

py2exe_options = {
  ‘py2exe’: {
    ‘compressed’: 1,
    ‘optimize’: 2,
    ‘ascii’: 1,
    ‘bundle_files’: 1,
    ‘dist_dir’: ‘../setup-client’,
    ‘packages’: [ 'encodings' ],
    ‘excludes’ : [
      'pywin',
      'pywin.debugger',
      'pywin.debugger.dbgcon',
      'pywin.dialogs',
      'pywin.dialogs.list',
      'Tkconstants',
      'Tkinter',
      'tcl',
    ],
    ‘dll_excludes’: [ 'w9xpopen.exe', 'MSVCR71.dll' ],
    ‘includes’: [
    'django.template.loaders.filesystem',
    'django.template.loaders.app_directories',
      'django.middleware.common',
        'django.contrib.auth.backends',
        'django.contrib.sessions.middleware',     
        'django.middleware.doc',     
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.sessions.backends.db',
        'django.contrib.sites',
        'django.contrib.admin',
        'django.core.cache.backends',
        'django.db.backends.sqlite3.base',
        'django.db.backends.sqlite3.introspection',
        'django.db.backends.sqlite3.creation',
        'django.db.backends.sqlite3.client',
        'django.template.defaulttags',
        'django.template.defaultfilters',
        'django.template.loader_tags',
        'django.conf.urls.defaults',
        'django.contrib.admin.views.main',
        'django.core.context_processors',
        'django.contrib.auth.management',
        'django.contrib.auth.management.commands',
        'django.contrib.auth.handlers',     
        'django.contrib.auth',
        'django.contrib.auth.middleware',
        'django.views.static',
        'django.contrib.admin.templatetags.adminmedia',
        'django.contrib.admin.templatetags.admin_list',
        'django.contrib.admin.templatetags.admin_modify',
        'django.contrib.admin.templatetags.log',
        'django.conf.urls.shortcut',
        'django.views.defaults',
        'django.core.cache.backends.locmem',
        'django.templatetags.i18n',
        'django.views.i18n',
      # also used by django?
      'email.mime.audio',
      'email.mime.base',
      'email.mime.image',
      'email.mime.message',
      'email.mime.multipart',
      'email.mime.nonmultipart',
      'email.mime.text',
      'email.charset',
      'email.encoders',
      'email.errors',
      'email.feedparser',
      'email.generator',
      'email.header',
      'email.iterators',
      'email.message',
      'email.parser',
      'email.utils',
      'email.base64mime',
      'email.quoprimime',
      'traceback',
    ],
  }
}

# Take the first value from the environment variable PYTHON_PATH
python_path = os.environ[ 'PYTHONPATH' ].split( ‘;’ )[ 0 ]

django_path = os.path.normpath( python_path + ‘/lib/site-packages/’)

django_admin_path = os.path.normpath( python_path + ‘/lib/site-packages/django/contrib/admin’ )
py2exe_data_files = []

lc_path = os.path.normpath( python_path + ‘/lib/site-packages/django/conf/locale’)

# django admin files
py2exe_data_files += add_path_tree( django_admin_path, ‘templates’ )
py2exe_data_files += add_path_tree( django_admin_path, ‘media’ )
# project files
py2exe_data_files += add_path_tree( ”, ‘newmedia’ )
py2exe_data_files += add_path_tree( ”, ‘db’ )

exedict = py2exe_options['py2exe']
includes = exedict['includes']
curpath = os.path.dirname(__file__)
add_python_file(includes,curpath)

django_auth_path = os.path.normpath( python_path + ‘/lib/site-packages/django/contrib/auth’ )
add_python_file(includes,django_auth_path,prefix=’django.contrib.auth.’)
add_python_file(includes,django_admin_path,prefix=’django.contrib.admin.’)

print includes

setup(
  options=py2exe_options,
  data_files=py2exe_data_files,
  zipfile = None,
  console=[ 'startdesktop.py' ],
)

最后再执行setup.py py2exe就开始打包了,最后得到的exe为6M多,有点大。

在这个过程中,还会碰到其他问题:

  1. template文件太多,不能暴露出去,这个时候可以预先装载template文件到一个py文件中,再编写一个load_template_source函数,放到settings中的TEMPLATE_LOADERS,用于模板查找就可以了。
  2. admin界面不能
    使用,因为django使用imp来查找app的admin.py文件,而imp不能搜索.zip文件。可以使用woobiz的方法解决,这个暂时还没有试验。
分类: django 标签:

网站的页面优化

2009年11月2日 roboter 没有评论

网站上线之后,添加到了Google的网站管理员工具。发现google从网站内部提取出了一些奇怪的关键字,比如padding,style,class,这些都是html的标签属性,不应该可见的,难道页面有问题,导致google分析页面出现问题?正好要调整一下页面,所以就一起来弄了。

首先整个网站的结构需要进行调整,原先的html页面内写了太多的style了,有太多的padding了,现在把风格统统调整到一个单独的css文件中。原先在张沈鹏的http://kanrs.com/网站上见过相关介绍,这两天网站好像无法访问了。从http://blog.html.it/layoutgala/获得比较简单的结构,采用css就可以调整各个版块的布局,很方便。

<div id="container">
  <div id="header">Header</div>
  <div id="wrapper">
    <div id="content">Content</div>
  </div>
  <div id="navigation">Navigation</div>
  <div id="extra">Extra stuff</div>
  <div id="footer">Footer</div>
</div>

采用这个模板的一个好处是可以把页面正文放在前面,让搜索引擎能够尽快找到真正的内容。原先的结构是把navigation菜单内容放到了前面,结果baidu认为navigation菜单内容比较重要,显示出来了,google比较智能,能够识别真正的内容。

另外一个定宽的css模板推荐使用Variable Grid System,可以自动生成所需要的css模板。我们的网站不需要定宽,就不采用这个960 Grid了。

结构调整之后,就开始移除padding,style等标签属性了。这个问题不大,慢慢来弄就好了。另外发现在html页面中直接展示了“>”字符,这是一个闭合符号,应该用“&gt;”替代的,不知道这是否是导致google解析有误的地方?这些地方也要修改。

firefox的插件web developer真的不错,可以检查很多内容。在网页信息中,让它显示了一下排版信息,结果看到了很多粉红色的方块,拿douban的网页来看,基本上没有粉红色方块。我猜应该是dom的层次太多,导致了粉红色。开始对div进行调整,没有必要的就去掉,能够合并的就合并,div层次减少之后,再来看看,没有粉红色了。

web developer还可以检验html页面是否符合xhtml规范,html规范等,选择工具,validator HTML,则会跳到Markup Validation Service,可以进行完整的检验,利用这个工具,发现了一个未闭合的标签。还发现一些属性没有加上引号。修改之后,错误减少了很多,剩下的错误主要是图片标签没有加alt属性了。

另外也看了一些seo的书籍,查看了网站管理员/站长 帮助,调整了页面的title,keyword,description等等。感觉这次调整差不多了,先更新到网站上看看效果。

分类: 网站 标签: