分类目录归档:开发日常

游戏开发记录

gevent 版本的数据库链接池 django backend实现(greenlet)

1) 数据库连接池
连接池基于sqlalchemy的QueuePool, 自己实现了一个基于gevent 的queue的GreenletQueuePool, 协程安全并且get和put操作的时候不会hung住导致获取链接超时
版本约束:
        gevent == 1.0.2 -> 1.2a1
        sqlalchemy == 0.9.1 -> 1.1.3
        django-mysqlpool == 0.2.1(注意这个必须用这个版本, 低版本(pip install 安装的)不支持django的多数据库!!!重要哦)
        (下载地址: https://github.com/smartfile/django-mysqlpool)
        gevent-queuepool == 1.0.0(下载地址: https://github.com/viphxin/gevent-queuepool)

2)配置:
        django settings.py
        'common':{#通用库
                    'ENGINE': 'django.db.backends.mysql',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
                    #'ENGINE': 'django_mysqlpool.backends.mysqlpool',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
                    'NAME': 'common_game',  # Or path to database file if using sqlite3.
                    'USER': '******',  # Not used with sqlite3.
                    'PASSWORD': '********',  # Not used with sqlite3.
                    'HOST': mysql_db,  # Set to empty string for localhost. Not used with sqlite3.
                    'PORT': '3308',  # Set to empty string for default. Not used with sqlite3.
                    'AUTOCOMMIT':True,
                    'CONN_MAX_AGE':0,
                    'OPTIONS':{"init_command": "SET storage_engine=INNODB;SET sql_mode='STRICT_TRANS_TABLES'",  # INNODB MYISAM,前者支持事务和行锁
                               "use_unicode": True , 'charset': 'utf8'}
                },

        # -----------fix sqlalchemy 支持线程池(queuepool 并发会超时)
        #from common import sqlalchemy_patch
        import gevent_queuepool
        gevent_queuepool.load()
        # -----------fix sqlalchemy 支持线程池
        MYSQLPOOL_BACKEND = 'GreenletQueuePool' #StaticPool(一个进程一个链接),QueuePool, GreenletQueuePool
        MYSQLPOOL_ARGUMENTS = {
            'use_threadlocal': False,
            'pool_size': 5,#这个>=1 最大缓存链接数
            'max_overflow': 10,#这个>=1 最大可超过的连接数, 也就是说连接数最大可到pool_size + max_overflow, 但是max_overflow用完之后就会关闭不会返回pool
            'timeout': 5,#获取链接超时时间
            'recycle': 600,#600秒后自动重新获取新的数据库链接, 防止mysql server主动断开
        }

3)主动将数据库连接放回pool(重要):
from django_mysqlpool import auto_close_db
@auto_close_db
def api_n:
    pass

Gitlab7.9通知邮箱的配置

新搭建的gitlab ce服务器需要支持邮件通知功能,记录下过程。

公司内部自建的邮件服务器,使用Centos默认安装的Postfix+ExtMail搭建。
在此基础上配置gitlab的通知邮件。

1. 复制配置文件

1
2
cd /home/git/gitlab
sudo -u git -H cp config/initializers/smtp_settings.rb.sample config/initializers/smtp_setting.rb

2. 编辑配置文件

1
sudo -u git -H vim config/initializers/smtp_setting.rb

以下是完整的配置信息,真实信息已被修改

if Rails.env.production?
Gitlab::Application.config.action_mailer.delivery_method = :smtp

ActionMailer::Base.smtp_settings = {
#smtp服务器地址
address: “mail.company.com”,
#smtp服务端口
port: 25,
#内部邮件系统配置的邮箱
user_name: “gitlab@company.com”,
#邮箱的密码
password: “password”,
#邮件服务器的域名
domain: “company.com”,
authentication: :login,
enable_starttls_auto: false,
openssl_verify_mode: ‘none’ # See ActionMailer documentation for other possible options
}

end

3. 修改gitlab配置文件

1
2
cd /home/git/gitlab
sudo -u git -H vim config/gitlab.yml

 ## Email settings
# Email address used in the “From” field in mails sent by GitLab
#刚才配置的公司邮箱
email_from: gitlab@company.com
#发信人的名称,接收邮件时,显示在”发信人”里
email_display_name: GitLab CE

4. 重启gitlab服务

service gitlab restart

6. 贴几个网上查到的163邮箱和腾讯邮箱的配置案例(没有验证过)

163邮箱

if Rails.env.production?
Gitlab::Application.config.action_mailer.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
address: “smtp.163.com”,
port: 25,
user_name: “username”,
password: “123456”,
domain: “163.com”,
authentication: :plain,
enable_starttls_auto: true
}
end

腾讯邮箱

if Rails.env.production?
Gitlab::Application.config.action_mailer.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
address: “smtp.exmail.qq.com”,
port: 25,
user_name: “username@company.com”,
password: “123456”,
domain: “smtp.qq.com”,
authentication: :plain,
enable_starttls_auto: true,
}
end

7. 其他说明

  • 第二步中的配置使用于多数开源项目的邮件通知配置,至少公司的redmine的配置和这样一样;
  • 此方法仅在gitlab 7.X版本上做过测试,不保证其他版本可用;

转载: http://www.zrick.net/2015/04/08/1428493100/

docker部署中遇到的mysql坑

前几天在用docker部署游戏服务器组,方便运维和开服合服操作,我自己制作的docker镜像包括 1) mysql镜像 2) redis镜像 3)认证服务器镜像 4) 网关服务器镜像 5) 游戏逻辑服务器镜像,镜像做完把它们部署到外网测试服务器(这里表达一下对docker开发人员的感谢, 真的是解放了我的双手,整个外网测试服务器组在几分钟内部署调试完毕,这在以前是不能想象的), 服务器部署完毕在使用过程中没有发现任何问题。过了几天因为有新的需求需要在本地调试服务器代码,以前我是直接在本机环境开n多进程调试的,其实写2个脚本脚本start server 和stop server也停放便的,由于前几天刚刚做完docker镜像,自己按耐不住对docker的冲动,啪啪啪,干脆直接在本地测试也用docker得了,好,说干就干,啪啪啪几个命令敲完,本地测试环境搭建完毕,开始进行开发了测试,现在问题来了,在开发调试阶段突然发现整个服务器组响应很慢很慢,这对于我这个性子比较急的coder来说当然是不能忍受的,开始啪啪啪查问题,首先要定位问题,刚开始我怀疑是服务部署的问题(经排查没有问题),然后又怀疑是网络环境的问题(经验证容器网络环境也没有任何问题)……..(中间省略n久的查错和排错过程), 最好通过开启游戏逻辑服务器的sql打印才发现是访问数据库慢,难道是数据库容器有问题,可是外网测试环境也是用的相同的容器呀!!!!!,真是日了狗了,后来在网上查到(一下引用自网络):

mysql优化之–skip-name-resolve

mysql优化之–skip-name-resolve

同一IDC ,IDC内部有DNS服务器,对各服务器的IP做了反向解析,
但未对内网IP做反向解析,所以使用skip-name-resolve以后用内网地址向mysqlslap请求响应快了一半

附录: 7.5.6. MySQL如何使用DNS

涉及参数 –skip-name-resolve ,–skip-host-cache ,–skip-networking

当新的客户连接mysqld时,mysqld创建一个新的线程来处理请求。该线程先检查是否主机名在主机名缓存中。如果不在,线程试图解析主机名:

·         如果操作系统支持线程安全gethostbyaddr_r ()和gethostbyname_r()调用,线程使用它们来执行主机名解析。

·         如果操作系统不支持线程安全调用,线程锁定一个互斥体并调用gethostbyaddr()和gethostbyname()。在这种情况下,在第1个线程解锁互斥体前,没有其它线程可以解析不在主机名缓存中的主机名。

你可以用–skip-name-resolve选项启动mysqld来禁用DNS主机名查找。然而,在这种情况下,你只可以使用MySQL中的授权表中的IP号。

如果你有一个很慢的DNS和许多主机,你可以通过用–skip-name-resolve禁用DNS查找或增加HOST_CACHE_SIZE定义(默认值:128)并重新编译mysqld来提高性能。

你可以用–skip-host-cache选项启动服务器来禁用主机名缓存。要想清除主机名缓存,执行FLUSH HOSTS语句或执行mysqladmin flush-hosts命令。

如果你想要完全禁止TCP/IP连接,用–skip-networking选项启动mysqld。

附录.抓包数据 待补全

连接mysql时,都会向DNS做反向地址查询
只有等超时失败后,mysql才会响应客户端
等待解析的mysql进程都是 login状态

访问mysql之所以会慢是因为mysql服务器反向地址查询,docker容器启动的时候如果本机没有配置nameserver 那么默认会用 8.8.8.8 和8.8.4.4这两个域名解析服务器相信大家一看就知道问题了吧。

接觉得办法是在运行docker的时候加–dns 127.0.1.1(这里代表自己指定的dns server)。

sudo docker run -p 3308:3306 –dns 10.202.72.118 –dns 10.202.72.116 -d centos_mysql:1.0.0 sh /root/run.sh

geohash:用字符串实现附近地点搜索

geohash是一种地址编码,它能把二维的经纬度编码成一维的字符串。比如,北海公园的编码是wx4g0ec1。

geohash有以下几个特点:

首先,geohash用一个字符串表示经度和纬度两个坐标。某些情况下无法在两列上同时应用索引(例如MySQL 4之前的版本,Google App Engine的数据层等),利用geohash,只需在一列上应用索引即可。

其次,geohash表示的并不是一个点,而是一个矩形区域。比如编码wx4g0ec19,它表示的是一个矩形区域。使用者可以发布地址编码,既能表明自己位于北海公园附近,又不至于暴露自己的精确坐标,有助于隐私保护。

第三,编码的前缀可以表示更大的区域。例如wx4g0ec1,它的前缀wx4g0e表示包含编码wx4g0ec1在内的更大范围。这个特性可以用于附近地点搜索。首先根据用户当前坐标计算geohash(例如wx4g0ec1)然后取其前缀进行查询(SELECT * FROM place WHERE geohash LIKE ‘wx4g0e%’),即可查询附近的所有地点。

geohash的算法

下面以(39.92324, 116.3906)为例,介绍一下geohash的编码算法。首先将纬度范围(-90, 90)平分成两个区间(-90, 0)、(0, 90),如果目标纬度位于前一个区间,则编码为0,否则编码为1。由于39.92324属于(0, 90),所以取编码为1。然后再将(0, 90)分成 (0, 45), (45, 90)两个区间,而39.92324位于(0, 45),所以编码为0。以此类推,直到精度符合要求为止,得到纬度编码为1011 1000 1100 0111 1001。

纬度范围 划分区间0 划分区间1 39.92324所属区间
(-90, 90) (-90, 0.0) (0.0, 90) 1
(0.0, 90) (0.0, 45.0) (45.0, 90) 0
(0.0, 45.0) (0.0, 22.5) (22.5, 45.0) 1
(22.5, 45.0) (22.5, 33.75) (33.75, 45.0) 1
(33.75, 45.0) (33.75, 39.375) (39.375, 45.0) 1
(39.375, 45.0) (39.375, 42.1875) (42.1875, 45.0) 0
(39.375, 42.1875) (39.375, 40.7812) (40.7812, 42.1875) 0
(39.375, 40.7812) (39.375, 40.0781) (40.0781, 40.7812) 0
(39.375, 40.0781) (39.375, 39.7265) (39.7265, 40.0781) 1
(39.7265, 40.0781) (39.7265, 39.9023) (39.9023, 40.0781) 1
(39.9023, 40.0781) (39.9023, 39.9902) (39.9902, 40.0781) 0
(39.9023, 39.9902) (39.9023, 39.9462) (39.9462, 39.9902) 0
(39.9023, 39.9462) (39.9023, 39.9243) (39.9243, 39.9462) 0
(39.9023, 39.9243) (39.9023, 39.9133) (39.9133, 39.9243) 1
(39.9133, 39.9243) (39.9133, 39.9188) (39.9188, 39.9243) 1
(39.9188, 39.9243) (39.9188, 39.9215) (39.9215, 39.9243) 1

经度也用同样的算法,对(-180, 180)依次细分,得到116.3906的编码为1101 0010 1100 0100 0100。

经度范围 划分区间0 划分区间1 116.3906所属区间
(-180, 180) (-180, 0.0) (0.0, 180) 1
(0.0, 180) (0.0, 90.0) (90.0, 180) 1
(90.0, 180) (90.0, 135.0) (135.0, 180) 0
(90.0, 135.0) (90.0, 112.5) (112.5, 135.0) 1
(112.5, 135.0) (112.5, 123.75) (123.75, 135.0) 0
(112.5, 123.75) (112.5, 118.125) (118.125, 123.75) 0
(112.5, 118.125) (112.5, 115.312) (115.312, 118.125) 1
(115.312, 118.125) (115.312, 116.718) (116.718, 118.125) 0
(115.312, 116.718) (115.312, 116.015) (116.015, 116.718) 1
(116.015, 116.718) (116.015, 116.367) (116.367, 116.718) 1
(116.367, 116.718) (116.367, 116.542) (116.542, 116.718) 0
(116.367, 116.542) (116.367, 116.455) (116.455, 116.542) 0
(116.367, 116.455) (116.367, 116.411) (116.411, 116.455) 0
(116.367, 116.411) (116.367, 116.389) (116.389, 116.411) 1
(116.389, 116.411) (116.389, 116.400) (116.400, 116.411) 0
(116.389, 116.400) (116.389, 116.394) (116.394, 116.400) 0

接下来将经度和纬度的编码合并,奇数位是纬度,偶数位是经度,得到编码 11100 11101 00100 01111 00000 01101 01011 00001。

最后,用0-9、b-z(去掉a, i, l, o)这32个字母进行base32编码,得到(39.92324, 116.3906)的编码为wx4g0ec1。

十进制 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
base32 0 1 2 3 4 5 6 7 8 9 b c d e f g
十进制 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
base32 h j k m n p q r s t u v w x y z

解码算法与编码算法相反,先进行base32解码,然后分离出经纬度,最后根据二进制编码对经纬度范围进行细分即可,这里不再赘述。不过由于geohash表示的是区间,编码越长越精确,但不可能解码出完全一致的地址。

geohash的应用:附近地址搜索

geohash的最大用途就是附近地址搜索了。不过,从geohash的编码算法中可以看出它的一个缺点:位于格子边界两侧的两点,虽然十分接近,但编码会完全不同。实际应用中,可以同时搜索当前格子周围的8个格子,即可解决这个问题。

以geohash的python库为例,相关的geohash操作如下:

>>> import geohash
>>> geohash.encode(39.92324, 116.3906, 5)  # 编码,5表示编码长度
'wx4g0'
>>> geohash.expand('wx4g0')                # 求wx4g0格子及周围8个格子的编码
['wx4ep', 'wx4g1', 'wx4er', 'wx4g2', 'wx4g3', 'wx4dz', 'wx4fb', 'wx4fc', 'wx4g0']

最后,我们来看看本文开头提出的两个问题:速度慢,缓存命中率低。使用geohash查询附近地点,用的是字符串前缀匹配:

SELECT * FROM place WHERE geohash LIKE 'wx4g0%';

而前缀匹配可以利用geohash列上的索引,因此查询速度不会太慢。另外,即使用户坐标发生微小的变化,也能编码成相同的geohash,这就保证了每次执行相同的SQL语句,使得缓存命中率大大提高。

注: 利用geohash将二维的经纬度编码成以为的字符串,将编码后的字符串取前缀存入数据库,利用数据库索引查询实现附近搜索, 有一个为题就是没有办法精确到具体的范围搜索, 如果需要精确的范围搜索,可以考虑pg和redis(3.2+支持geo)

参考:http://blog.jobbole.com/80633/

利用二进制运算实现业务的多级审核

大家在做项目的过程中肯定会碰到多级审核的情况,多级审核如果审核级数可以定下来还好,但是偏偏事与愿违,需求多变,为了减轻我们编码人员的负担,一种基于二进制运算灵活配置实现的多级审核方案呼之欲出,现在我就来介绍一下:

继续阅读利用二进制运算实现业务的多级审核