首页 文章 精选 留言 我的

精选列表

搜索[手游社交组件],共10010篇文章
优秀的个人博客,低调大师

分享自己的页运维架构

简单画了个图: 首先,后端程序及客户端都是分成三个版本:内部测试版,线上测试版,线上稳定版。线上测试版是小范围更新,经过一天测试没问题,然后再推到线上稳定版,更新其他服,一般游戏也都是按这个流程来更新的。 运维管理后台,记录了区服信息,提供各种简单API接口给各脚本使用。 然后批量维护脚本,create_list.py是根据运维管理后台提供的API,根据输入的参数(平台,区服范围)生成一份cqbyupdate.py需要使用的iplist文件,然后cqbyupdate.py根据这份ip文件执行相应的操作。 saltstack,是用于全服修改一些配置使用,例如批量修改zabbix的配置,批量修改nginx的配置 等等。 rsync,用于数据同步,例如给游戏服拉取最新版本。 游戏服最关键的只有一个control.py脚本,该脚本集成了管理单个游戏区服的所有操作,根据传进去的版本参数及动作参数执行对应的操作。 整套架构的优点是全服维护可用cqbyupdate.py脚本操作,如果临时游戏服上想做些什么更新,可用单服脚本control.py操作,比较灵活;缺点是对中心机依赖比较高,万一中心机岩了,就麻烦大了,所以搞了一台备份中心机。这套架构已经上线开服3000+ control.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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 #!/usr/bin/python #coding=utf-8 import subprocess import shutil import os import sys reload (sys) sys.setdefaultencoding( 'utf-8' ) import optparse import ConfigParser import time import jinja2 import urllib2 import json import socket try : import fcntl except : pass import struct import MySQLdb def get_ip_address(ifname): s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) return socket.inet_ntoa(fcntl.ioctl( s.fileno(), 0x8915 , #SIOCGIFADDR struct.pack( '256s' ,ifname[: 15 ]) )[ 20 : 24 ]) class Cqby: def __init__( self ,version,platform,platformid, id ): self .version = version self .platform = platform self .platformid = platformid self . id = id #工作目录: self .workdir = '/data/init' #定义游戏程序目录: self .gamedir = '/data/game/game%s' % self . id try : os.makedirs( '/data/game' ) except : print "目录已存在" #当前游戏聊天监控目录: self .chatdir = '/data/game/chat%s' % self . id #定义游戏端口: if int ( self . id )> 50000 : self .gameport = str ( self . id ) else : self .gameport = 20000 + int ( self . id ) self .gameport = str ( self .gameport) try : self .localip = get_ip_address( 'eth0' ) except : self .localip = get_ip_address( 'em1' ) #定义数据库名称: self .dbname = 'game%s' % self . id #定义管理员使用的数据库帐号密码: self .admindbuser = 'root' self .admindbpass = '123456' #定义备份目录: self .backup = '/data/backup' try : os.makedirs( self .backup) except : print "目录已经存在" #建立日志目录: self .gamelogdir = '/data/gamelogs/chuanqi/%s/S%s' % ( self .platform, self . id ) if not os.path.isdir( self .gamelogdir): os.makedirs( self .gamelogdir) subprocess.call( 'chownwww:www-R/data/gamelogs' ,shell = True ) #程序配置文件模板: self .binConfigDir = '%s/bin' % self .gamedir self .binConfigFiles = [ 'socket.jinja2' ] self .confConfigDir = '%s/conf' % self .gamedir self .confConfigFiles = [ 'jade.cfg.jinja2' ] self .independentConfigDir = '%s/conf/independent' % self .gamedir self .independentConfigFiles = [ 'auth.properties.jinja2' , 'debug.properties.jinja2' , 'fcm.properties.jinja2' , 'gm.properties.jinja2' , 'net.properties.jinja2' , 'server.properties.jinja2' , 'whiteList.properties.jinja2' , 'onlineLimit.properties.jinja2' , ] self .miscConfigDir = '%s/conf/config/common' % self .gamedir self .miscConfigFiles = [ 'misc.properties.jinja2' , ] #数据库权限: baselist = [ '127.0.0.1' ,] payIPListAll = { '37wan' :[], 'liebao' :[], '2345' :[], 'yilewan' :[], 'renrenwang' :[], '6711' :[], '1360' :[], 'duowan' :[], 'baidu' :[], 'lianyun' :[], 'tencent' :[] } try : self .platformPayList = payIPListAll[ self .platform] except : self .platformPayList = payIPListAll[ 'lianyun' ] self .payList = baselist + self .platformPayList self .mergelist = self .__getMerge() def __getMerge( self ): '''获取合服列表''' i = 0 while True : try : if i> = 3 : print "请求超时!!!!!!" sys.exit( 2 ) url = 'http://yw.admin.xxx.com/yunwei/api/getmergetarget/%s/%s/' % ( self .platform, self . id ) request = urllib2.urlopen(url) response = request.read().split( ',' ) except Exception,e: print "请求合服信息失败:" + str (e) print "正在重试。。。" i = i + 1 else : break return response def createDatabase( self ): '''创建数据库''' try : print "正在创建数据库:%s" % self .dbname cmd = '''/usr/local/mysql/bin/mysql-u'%s'-p'%s'-e"createdatabase%sDEFAULTCHARACTERSETutf8COLLATEutf8_general_ci"''' % ( self .admindbuser, self .admindbpass, self .dbname) ret = subprocess.call(cmd,shell = True ) print "执行状态:%s" % ret if ret: print "创建数据库失败,请确认!" sys.exit( 2 ) except Exception,e: print "捕捉到异常:" ,e sys.exit( 2 ) def updateDB( self ,filename): '''导入数据库文件''' try : print "正在导入SQL文件:%s" % filename cmd = '''/usr/local/mysql/bin/mysql-u'%s'-p'%s'%s<%s''' % ( self .admindbuser, self .admindbpass, self .dbname,filename) ret = subprocess.call(cmd,shell = True ) print "执行状态:%s" % ret except Exception,e: print "捕捉到异常:" ,e sys.exit( 2 ) def dumpDatabase( self ): '''备份数据库''' try : print "正在备份数据库:%s" % self .dbname curTime = time.strftime( '%Y%m%d%H%M%S' ,time.localtime(time.time())) cmd = '''/usr/local/mysql/bin/mysqldump-u'%s'-p'%s'%s>%s''' % ( self .admindbuser, self .admindbpass, self .dbname,' % s / % s - % s.sql' % ( self .backup,curTime, self .dbname)) ret = subprocess.call(cmd,shell = True ) print "执行状态:%s" % ret except Exception,e: print "捕捉到异常:" ,e def dropDatabase( self ): '''删除数据库''' try : print "正在删除数据库:%s" % self .dbname cmd = '''/usr/local/mysql/bin/mysql-u'%s'-p'%s'-e"dropdatabase%s"''' % ( self .admindbuser, self .admindbpass, self .dbname) ret = subprocess.call(cmd,shell = True ) print "执行状态:%s" % ret except Exception,e: print "捕捉到异常:" ,e def createGameDir( self ): '''创建游戏目录''' try : print "正在检测目录是否存在:%s" % self .gamedir if os.path.isdir( self .gamedir): print "目录已存在,请检查参数!" sys.exit( 2 ) else : print "正在复制程序文件至:%s" % self .gamedir shutil.copytree( '%s/%s/server' % ( self .workdir, self .version), self .gamedir) except Exception,e: print "捕捉到异常:" ,e sys.exit( 2 ) def dropGameDir( self ): '''清理游戏目录''' try : print "正在删除游戏目录:%s" % self .gamedir if os.path.isdir( self .gamedir): shutil.rmtree( self .gamedir) except Exception,e: print "遇到错误:" ,e def dropGameLogDir( self ): '''清理游戏日志目录''' try : print "正在删除日志目录:%s" % self .gamelogdir if os.path.isdir( self .gamelogdir): shutil.rmtree( self .gamelogdir) except Exception,e: print "遇到错误:" ,e def createConfig( self ,configdir,configlist): '''创建程序配置''' try : print "正在生成配置文件:%s" % configdir url = 'http://yw.admin.xxx.com/yunwei/api/getmem/%s/%s' % ( self .platform, self . id ) response = urllib2.urlopen(url) mem = response.read() env = jinja2.Environment(loader = jinja2.FileSystemLoader(configdir)) for gateconfig in configlist: print gateconfig template = env.get_template(gateconfig) f = open ( '%s/%s' % (configdir,gateconfig.rstrip( '.jinja2' )), 'w' ) f.write( template.render( version = self .version, platformid = self .platformid, platform = self .platform, gameid = self . id , gameport = self .gameport, gamedir = self .gamedir, dbuser = 'game' , dbpass = 'game123456' , dbname = self .dbname, paylist = self .platformPayList, mem = mem, mergelist = self .mergelist, ) ) f.close() except Exception,e: print "生成配置文件遇到错误:" ,e sys.exit( 2 ) def updateconfig( self ): self .createConfig( self .binConfigDir, self .binConfigFiles) os.chmod( '%s/bin/socket' % self .gamedir, 0755 ) self .createConfig( self .confConfigDir, self .confConfigFiles) self .createConfig( self .independentConfigDir, self .independentConfigFiles) #self.createConfig(self.miscConfigDir,self.miscConfigFiles) def updategame( self ): print "正在更新游戏程序。。。" cmd = '''rsync-avzP--exclude="socket"--exclude="log"--exclude="onlineLimit.properties"--exclude="jade.cfg"--exclude="auth.properties"--exclude="debug.properties"--exclude="fcm.properties"--exclude="gm.properties"--exclude="net.properties"--exclude="server.properties"--exclude="whiteList.properties"%s/%s/server/%s/''' % ( self .workdir, self .version, self .gamedir) print cmd result = subprocess.call(cmd,shell = True ) return result def start( self ): print "给JSVC添加执行权限:" os.chmod( '%s/bin/jsvc' % self .gamedir, 0755 ) print "正在启动服务:" cmd = '''cd%s/bin;./socketstart''' % self .gamedir result = subprocess.call(cmd,shell = True ) return result def stop( self ): print "正在关闭服务:" cmd = '''cd%s/bin;./socketstop''' % self .gamedir result = subprocess.call(cmd,shell = True ) return result def clearnow( self ): self .dumpDatabase() self .updateDB( '%s/%s/server/sql/database.sql' % ( self .workdir, self .version)) self .dropGameLogDir() def clear( self ): try : conn = MySQLdb.connect(user = self .admindbuser,passwd = self .admindbpass,host = 'localhost' ,db = self .dbname,unix_socket = '/tmp/mysql.sock' ) cursor = conn.cursor(cursorclass = MySQLdb.cursors.DictCursor) sql = '''select*fromPlayer''' sum = cursor.execute(sql) cursor.close() conn.close() print "数据库Player表有:%s" % sum if int ( sum )> 30 : print "Player表记录总数大于30!请确认后再执行清档操作!!!" sys.exit( 2 ) else : print "Player表记录总数小于30,可以执行清档操作!" self .stop() self .clearnow() self .start() except Exception,e: print "连接数据库错误:%s" % e sys.exit( 2 ) def create( self ): '''一键搭服''' self .createDatabase() self .updateDB( '%s/%s/server/sql/database.sql' % ( self .workdir, self .version)) self .mysqlgrant() self .createGameDir() self .updateconfig() self .createchat() self .nginxlogs() def drop( self ): self .dumpDatabase() self .dropDatabase() self .dropGameDir() self .dropGameLogDir() self .dropchat() def onekey( self ): '''一键更新''' self .stop() time.sleep( 10 ) self .updategame() self .start() def mysqlgrant( self ): '''添加数据库授权''' print "正在添加数据库授权:" for ip in self .payList: print "正在添加%s权限" % ip cmd = '''/usr/local/mysql/bin/mysql-u'%s'-p'%s'-e"grantallprivilegeson*.*togame@'%s'Identifiedby'cqbygame'"''' % ( self .admindbuser, self .admindbpass,ip) subprocess.call(cmd,shell = True ) cmd = '''/usr/local/mysql/bin/mysql-u'%s'-p'%s'-e"grantselecton*.*todb@'119.131.244.178'identifiedby'lizhenjie';"''' % ( self .admindbuser, self .admindbpass) subprocess.call(cmd,shell = True ) if __name__ = = "__main__" : active_list = [ 'create' , 'drop' , 'updateconfig' , 'start' , 'stop' , 'clear' , 'updategame' , 'updateDB' , 'onekey' , 'mysqlgrant' , 'clearnow' ] gamever_list = [ 'test' , '37dev' , '37stable' ] usage = '''usage:%prog-pplatform %prog-vversion-iid-aaction %prog-vversion-iid-aupdateDB-ssqlfile ''' parser = optparse.OptionParser( usage = usage, version = "%prog2.0" ) setplat_opts = optparse.OptionGroup( parser, '设置服务器平台标识' , '一台硬件服务器设置一次即可。' ) setplat_opts.add_option( '-p' , '--platform' , dest = "platform" , help = "平台名称" ) parser.add_option_group(setplat_opts) tools_opts = optparse.OptionGroup( parser, '服务器日常功能' , ) tools_opts.add_option( '-v' , '--ver' , dest = "ver" , help = "版本目录" , type = "choice" , choices = gamever_list, default = gamever_list[ 1 ] ) tools_opts.add_option( '-i' , '--id' , dest = 'id' , help = "服务器ID" ) tools_opts.add_option( '-a' , '--action' , dest = 'action' , help = "执行动作" , type = "choice" , choices = active_list ) tools_opts.add_option( '-s' , '--sql' , dest = 'sql' , help = "SQL文件(可选,配合updateDB使用)" ) parser.add_option_group(tools_opts) options,args = parser.parse_args() err_msg = '参数不对,请输--help查看详细说明!' ini = 'platform.ini' if options.platform: apiurl = 'http://yw.admin.xxx.com/yunwei/api/getplatforminfo/' ini = 'platform.ini' result = urllib2.urlopen(apiurl) response = json.loads(result.read()) for code, id in response.items(): if options.platform = = code: platformid = id print "正在设置服务器标识为:%s-%s" % (platformid,options.platform) cfd = open (ini, 'w' ) conf = ConfigParser.ConfigParser() conf.add_section( 'platforminfo' ) conf. set ( 'platforminfo' , 'name' ,options.platform) conf. set ( 'platforminfo' , 'id' ,platformid) conf.write(cfd) cfd.close() break sys.exit( 0 ) if options. id and options.ver and options.action: cf = ConfigParser.ConfigParser() cf.read(ini) platform = cf.get( 'platforminfo' , 'name' ) platformid = cf.get( 'platforminfo' , 'id' ) cqby = Cqby(options.ver,platform,platformid,options. id ) run_function = getattr (cqby,options.action) if options.action in [ 'updateDB' ,]: run_function( '%s/server/sql/%s' % (options.ver,options.sql)) else : run_function() else : parser.error(err_msg) cqbyupdate.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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 #!/usr/bin/python #coding:utf-8 import threading import Queue import subprocess import optparse import logging import logging.config import datetime import os import sys reload (sys) sys.setdefaultencoding( 'utf-8' ) #test: import time #logging.basicConfig(level=logging.DEBUG,format='(%(threadName)-10s)%(message)s',) logging.config.fileConfig( "logger.conf" ) logger = logging.getLogger( "root" ) logger2 = logging.getLogger( "file" ) queue = Queue.Queue() Failed_List = [] class Ahdts(threading.Thread): def __init__( self ,queue): super (Ahdts, self ).__init__() self .queue = queue self .workdir = '/data/init' #建立日志目录: log_path = 'updatelog' today = datetime.date.today() self .log_path_today = '%s/%s' % (log_path,today) if not os.path.isdir( self .log_path_today): try : os.makedirs( self .log_path_today) except Exception,e: print e sys.exit( 2 ) def run( self ): while True : global action global sqlfile item = self .queue.get() value = item.strip().split( ',' ) platform = value[ 0 ] id = value[ 1 ] ip = value[ 2 ] port = value[ 3 ] opentime = value[ 4 ] logging.debug( "%10s%6s%15s%15s%10sThreadingStart!" % (platform, id ,ip,action,ver)) if action = = 'rsync' : cmd = '''cd%s;./rsync''' % self .workdir elif action = = 'ntp' : cmd = '''cd%s;./TimeClient.py''' % self .workdir elif action in [ 'updateDB' ,]: cmd = '''cd%s;./control.py-i%s-a%s-v%s-s%s''' % ( self .workdir, id ,action,ver,sqlfile) elif action = = 'platform' : cmd = '''cd%s;./control.py-p%s''' % ( self .workdir,platform) else : cmd = '''cd%s;./control.py-i%s-a%s-v%s''' % ( self .workdir, id ,action,ver) sshcmd = '''sshroot@%s-n"%s"''' % (ip,cmd) with open ( '%s/%s-%s-%s-%s.log' % ( self .log_path_today,platform, id ,ver,action), 'a' )aslogfile: exitcode = subprocess.call(sshcmd,shell = True ,stdout = logfile,stderr = subprocess.STDOUT) if exitcode = = 0 : logger2.debug( '%10s%6s%15s%15s%10s%s' % (platform, id ,ip,action,ver,cmd)) rettxt = '%10s%6s%15s%15s%10sThreadingEnd!ExitCode:%s' % (platform, id ,ip,action,ver,exitcode) if exitcode: Failed_List.append(rettxt) logging.debug(rettxt) self .queue.task_done() if __name__ = = "__main__" : action_list = [ 'rsync' , 'create' , 'drop' , 'start' , 'stop' , 'clear' , 'updateconfig' , 'updategame' , 'updateDB' , 'onekey' ] gamever_list = [ 'test' , '37dev' , '37stable' ] usage = '''usage:%prog--file<file.ini>--action<action> Forexample:%prog-fgame-test.ini-acreate %prog-fgame-test.ini-aonekey %prog-fgame-test.ini-aupdateDB-stest.sql ''' parser = optparse.OptionParser( usage = usage, version = "%prog1.4" ) parser.add_option( '-f' , '--file' ,dest = "file" , help = "IP文件列表" ) parser.add_option( '-a' , '--action' ,dest = "action" , help = "执行动作" , type = "choice" ,choices = action_list) parser.add_option( '-v' , '--ver' ,dest = 'ver' , help = "版本目录标识" , type = "choice" ,choices = gamever_list) parser.add_option( '-s' , '--sql' ,dest = 'sql' , help = "待更新的SQL文件" ) options,args = parser.parse_args() err_msg = '参数不对,请输--help查看详细说明!' if options.action and options.ver and options. file : with open (options. file )as file : content = file .readlines() action = options.action ver = options.ver sqlfile = options.sql maxThreadNum = 200 if len (content)< 100 : maxThreadNum = len (content) for i in range (maxThreadNum): t = Ahdts(queue) t.setDaemon( True ) t.start() logging.debug( "%10s%6s%15s%15s%10s" % ( 'PlatForm' , 'ID' , 'IP' , 'Action' , 'Version' )) iplist = [] for i in content: ii = i.strip().split( ',' ) ip = ii[ 2 ] if action in [ 'rsync' , 'platform' ] and ip in iplist: continue queue.put(i) iplist.append(ip) queue.join() #打印执行失败列表: print '=' * 20 + '执行失败列表' + '=' * 20 if Failed_List: for i in Failed_List: print i else : print "None" print '=' * 52 logging.debug( "Done" ) else : print err_msg 批量维护脚本其实就是ssh远程过去游戏服执行control.py脚本,后面看能不能改成用socket的方式去连接,把socket的东西练练手,整套东西感觉还是比较简单。 本文转自运维笔记博客51CTO博客,原文链接http://blog.51cto.com/lihuipeng/1617958如需转载请自行联系原作者 lihuipeng

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册