db系统遇到的问题

1. linux服务器cpu使用率异常高

  1. 现象:内网服务器cpu使用率较高,达到80%多,启动game服务器非常慢。
  2. top命令查看内网服务器的cpu使用率,发现排第一的是redis进程;其次是mysql进程;再次是一堆py进程(在内网linux服务器上,启动了25个左右的py脚本,每个py脚本就是一个db服务,对应负责1个game服务器的数据业务:CRUD。
  3. 观察py进程,分析哪个进程占用的cpu最多。检查该py进程对应的game服务器,数据或者业务上是否有什么异常。
  4. 发现该服务器对应的redis缓存数据有点异常,设置的缓存数量是20,但该服务器缓存了130条player数据。
  5. 所以是代码bug,导致缓存数量突破上限。随后复查代码,修正bug。

2. cpu使用率异常高

  1. 还是cpu使用率异常,但这次未发现明显异常的game服务器。
  2. 当game服务器发送请求到redis队列中,game服务器需要轮询来判断请求执行的状态(执行失败、执行成功等)。
  3. 在一般情况下,py脚本处理的速度较快,不会发生拥挤情况。但当多个服务器同时进行世界地图存盘操作时。(项目是slg类型,世界地图数据比较大,世界地图被划分为100个格子,以格子序号来分批进行存储。)
  4. 此时redis处理数据存储,本身就已经很耗性能了。而此时game服务器发送过来的查询请求,进一步增加了redis进程的负载,导致拥塞情况的加剧。(就好像你在写代码,本身已经在消耗脑力了。此时策划坐在你旁边,不断的问你,好了没有?好了没有?好了没有?)
  5. 解决方案:后续计划将请求队列的数据结构由sortset修改为list,因为list支持阻塞方式(blpop)查询请求是否执行完毕,而不需要dbsdk一直轮询来判断请求是否执行完毕。
  6. 之前设计上: 命令队列和结果队列,都是使用sortset来存放。
  7. 关于blpop,后面又发生了一个小插曲。(早期的py-redis库中,blpop实现又bug,可能队列中有数据,但是blpop会无法取出数据,从而导致直接卡死。后续解决方案是升级到最新的py-redis库,修复卡死。)

3. 命令被覆盖

  1. mgr和game都引用了dbsdk,在启动初期的数据库表和zcfg校验环节,有一些请求会出现覆盖的现象。
  2. 例如:show tables命令,进入request队列是被封装为下列结构:{请求唯一oid,请求}。由于request队列是sortset类型,val值相同(都是show tables)的话,先放进去的show tables会被后到来的直接覆盖掉。导致游戏上层没有收到该命令的执行结果,无法正常启动。
  3. 例如,game服务器发送请求{1,show tables},此时py脚本还未取出请求。mgr服务器立马发来请求{2,show tables}。此时{1,show tables}被覆盖了。导致game服务器会一直等待{1,show tables}的执行结果,但却再也等不到了。

4. rapidjson转换接口使用不正确

  1. cache队列中,save_time字段的最后面多了个\u000,导致py脚本在定时将redis cache队列中的数据,同步到mysql时,组装的sql语句执行失败。

5. 考虑内存不足的处理方案

  1. select * from t_map,在game启动时加载世界地图,当游戏后期数据量大的时候,python或game服务器内存不够,就可能导致加载失败,需要设计方案处理内存不足的情况:
  • 批次载入:先count(*)一下,查询出t_map表一共有多少行数据。比如count=1000,那么将1000行数据分10次载入,每次载100行。
  • 可能每次载100行数据所组成的数据包,其大小仍旧超过网络框架代码的限定值,那么此时就必须完善分包传输机制了。

6. redis监控

redis-cli monitor:查看redis进程当前正在执行的命令

7.请求执行的唯一性

  1. 一条命令请求,应该只能被成功执行一次。
  • 之前请求队列的数据结构是sortset,由于代码bug,导致:执行失败的命令,没有从请求队列中删除,导致该命令会被循重复执行。
  • 现在请求队列是list类型,py脚本在Update中blpop弹出请求。此时会面临一个问题:当弹出的请求执行失败时(比如mysql断开连接了),我们要怎么办?因为请求已经从redis队列中弹出,我们没法再发起第2次执行行为。

8.命令队列、结构队列,修改为list结构,避免一直轮询,效率低下

  • 用list的阻塞接口 blpop
  • 复杂命令可以用json封装一下,然后rpush到list中。取出的时候,再用json解析。

9.BUG

  • api接口支持一次执行多条sql语句
  • 支持多线程进行阻塞redis读取,进一步降低cpu使用率
  • 当select空表时,返回的结果集中不要填充内容
  • Update & Insert:effectRows、laseOId、异常的errMsg
  • 列类型要正确区分:null和其它数据类型(目前:sql组成的临时表被select时,如果第一行为null,则列类型被设置为null,导致后面行的数据不能正确读取)

10.hiredis阻塞

Redis作为一个高性能的内存数据库,在实际应用中可以很好的解决cache,数据共享等问题。但客户端采用hiredis的时候,需要注意几点:

  1. 对于block方式的操作:在block操作模式下,每个命令都是单独发向Redis的,且也会等待每个的结果返回。即,通过redisCommand调用,即完成了向Redis发送命令,也等待Redis返回结果。换言之,是一个阻塞的过程。如果在是并发量比较高的情况下,此种方式,会使得客户端效率较差。即便是将Redis部署在本地,通过local进行访问,也会有0.02毫秒级别的等待过程。如果客户端有很多次的访问操作,每次都会阻塞一小段时间,会使得客户端本身的处理速度降下来。为了降低这种开销,可以采用pipeline的方式。也即一次向Redis发送多个命令。将这多个命令发送完毕后,再进行统一处理。当然,这样的代价是客户端的发送缓冲区会变大,接收缓冲区也会变大。但处理的吞吐量会增加。

  2. 对于non-block方式的操作:在这种模式下,默认的调用redisCommand就会立即返回,相当于block模式下显式的调用AppendCommand。不过在这种模式下,要显示的调用获取回应的replay,然后将reply释放掉,以免造成内存泄漏。

上面说的,都是同步的操作过程。操作过程和客户端的程序是在一个进程里完成的。另外还可以采用异步的方式。

11.编码解析问题

因为横跨游戏层、dbsdk层、redis、python、mysql,所以命令请求的传输方式使用json格式来传输。但是json不支持直接存储二进制数据。所以用base64算法加密二进制数据,转换为字符串,然后json.dump封装到json中,在dbsdk、redis、python之间传输。

12.nohup.out文件过大

  1. 清空文件内容。这个不需要关闭服务,但是如果项目多,nohup.out 文件多 ,不好定位。
echo '' > nohup.out

13.LoadPlayer时,player_oid为0

  1. 起因:JH502账号无法登陆
  2. 原因:LoadPlayer时,db返回的player_oid为0
  3. 怀疑有两种情况:
    1. redis缓存数据不对。
    2. 写入到mysql表中的blob数据,已经出错了。
  4. 先验证3.1,把redis缓存数据手动删除掉。然后登陆账号,发现可以正常登陆。那么说明mysql表中数据时正确的,redis缓存数据出错了。
  5. redis缓存数据是在python脚本中插入的,且删除缓存以后再登陆,走的是Select操作。那么开始排查Select操作的代码。
  6. 然后发现在select出结果集rows时,会对row中的bytes数据,进行base64.encode()操作。注意这个函数的返回值仍然是bytes类型。此时再传递到写redis的接口时,接口内部会对row再进行一次base64.encode()。导致blob数据player_game_data被两次base64了。
  7. 那么在dbsdk中,就无法再解析出正确的数据。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值