mysql批量更新多条记录的同一字段为不同值

mysql更新数据的某个字段,一般这样写:

UPDATE mytable SET myfield = 'value' WHERE other_field = 'other_value';

也可以这样用in指定要更新的记录:

UPDATE mytable SET myfield = 'value' WHERE other_field in ('other_values');

这里注意 ‘other_values’ 是一个逗号(,)分隔的字符串,如:1,2,3

如果更新多条数据而且每条记录要更新的值不同,可能很多人会这样写:

foreach ($values as $id => $myvalue) {
    $sql = "UPDATE mytable SET myfield = $myvalue WHERE id = $id";
    mysql_query($sql);
}

即是循环一条一条的更新记录。一条记录update一次,这样性能很差,也很容易造成阻塞。

那么能不能一条sql语句实现批量更新呢?mysql并没有提供直接的方法来实现批量更新,但是可以用点小技巧来实现。

UPDATE mytable
    SET myfield = CASE id
        WHEN 1 THEN 'myvalue1'
        WHEN 2 THEN 'myvalue2'
        WHEN 3 THEN 'myvalue3'
    END
WHERE other_field ('other_values')

如果where条件查询出记录的id不在CASE范围内,myfield将被设置为空。

如果更新多个值的话,只需要稍加修改:

UPDATE mytable
    SET myfield1 = CASE id
        WHEN 1 THEN 'myvalue11'
        WHEN 2 THEN 'myvalue12'
        WHEN 3 THEN 'myvalue13'
    END,
    myfield2 = CASE id
        WHEN 1 THEN 'myvalue21'
        WHEN 2 THEN 'myvalue22'
        WHEN 3 THEN 'myvalue23'
    END
WHERE id IN (1,2,3)

这里以php为例,构造这两条mysql语句:

  • 更新多条单个字段为不同值, mysql模式
$ids_values = array(
    1 => 11,
    2 => 22,
    3 => 33,
    4 => 44,
    5 => 55,
    6 => 66,
    7 => 77,
    8 => 88,
);
 
$ids = implode(',', array_keys($ids_values ));
$sql = "UPDATE mytable SET myfield = CASE id ";
foreach ($ids_values as $id=> $myvalue) {
    $sql .= sprintf("WHEN %d THEN %d ", $id, $myvalue);
}
$sql .= "END WHERE id IN ($ids)";
echo $sql.";<br/>";

输出

UPDATE mytable SET myfield = CASE id WHEN 1 THEN 11 WHEN 2 THEN 22 WHEN 3 THEN 33 WHEN 4 THEN 44 WHEN 5 THEN 55 WHEN 6 THEN 66 WHEN 7 THEN 77 WHEN 8 THEN 88 END WHERE id IN (1,2,3,4,5,6,7,8);

  • 更新多个字段为不同值, PDO模式
$data = array(array('id' => 1, 'myfield1val' => 11, 'myfield2val' => 111), array('id' => 2, 'myfield1val' => 22, 'myfield2val' => 222));
$where_in_ids = implode(',', array_map(function($v) {return ":id_" . $v['id'];}, $data));
$update_sql = 'UPDATE mytable SET';
$params = array();

$update_sql .= ' myfield1 = CASE id';
foreach($data as $key => $item) {
    $update_sql .= " WHEN :id_" . $key . " THEN :myfield1val_" . $key . " ";
    $params[":id_" . $key] = $item['id'];
    $params[":myfield1val_" . $key] = $item['myfield1val'];
}
$update_sql .= " END";

$update_sql .= ',myfield2 = CASE id';
foreach($data as $key => $item) {
    $update_sql .= " WHEN :id_" . $key . " THEN :myfield2val_" . $key . " ";
    $params[":id_" . $key] = $item['id'];
    $params[":myfield1va2_" . $key] = $item['myfield2val'];
}
$update_sql .= " END";

$update_sql .= " WHERE id IN (" . $where_in_ids . ")";
echo $update_sql.";<br/>";
var_dump($params);

输出

UPDATE mytable SET myfield1 = CASE id WHEN :id_0 THEN :myfield1val_0 WHEN :id_1 THEN :myfield1val_1 END,myfield2 = CASE id WHEN :id_0 THEN :myfield2val_0 WHEN :id_1 THEN :myfield2val_1 END WHERE id IN (:id_1,:id_2);

array (size=6)
 ':id_0' => int 1
 ':myfield1val_0' => int 11
 ':id_1' => int 2
 ':myfield1val_1' => int 22
 ':myfield1va2_0' => int 111
 ':myfield1va2_1' => int 222

另外三种批量更新方式

1. replace into 批量更新

replace into mytable(id, myfield) values (1,'value1'),(2,'value2'),(3,'value3');

2. insert into ...on duplicate key update批量更新

insert into mytable(id, myfield1, myfield2) values (1,'value11','value21'),(2,'value12','value22'),(3,'value13','value23') on duplicate key update myfield1=values(myfield2),values(myfield2)+values(id);

3. 临时表

DROP TABLE IF EXISTS `tmptable`;
create temporary table tmptable(id int(4) primary key,myfield varchar(50));
insert into tmptable values (1,'value1'),(2,'value2'),(3,'value3');
update mytable, tmptable set mytable.myfield = tmptable.myfield where mytable.id = tmptable.id;
  • 【replace into】和【insert into】更新都依赖于主键或唯一值,并都可能造成新增记录的操作的结构隐患
  • 【replace into】操作本质是对重复记录先delete然后insert,如果更新的字段不全缺失的字段将被设置成缺省值
  • 【insert into】则只是update重复的记录,更改的字段只能依循公式值
  • 【临时表】方式需要用户有temporary 表的create 权限
  • 数量较少时【replace into】和【insert into】性能最好,数量大时【临时表】最好,【CASE】则具有通用型也不具结构隐患

SSL/TLS协议和https访问流程

可以先阅读《密码学笔记》了解对称算法和不对称算法,以及《数字签名是什么?》了解数字签名如何保证安全。

http:超文本传输协议;

https:安全套接字超文本传输协议;为了数据的安全传输,HTTPS在HTTP的基础上添加SSL/TLS协议;SSL/TLS依靠证书来验证服务器身份;并为浏览器和服务器之间通信加密。

HTTPS其实是有两部分组成:HTTP + SSL/TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。

用途两种:一种是建立一个信息通道,来保证数据传输的安全;另一种就是确认网站的真实性,凡是使用了 https 的网站,都可以通过点击浏览器地址栏的锁头标志来查看网站认证之后的真实信息,也可以通过 CA 机构颁发的安全签章来查询。

http和https的区别:

  1. https协议需要到CA(Certificate Authority)申请证书,一般证书很少免费,需要交费;
  2. http是超文本传输协议,信息是明文传输;https则是具有安全性的SSL加密传输协议;
  3. http和https使用的是完全不同的连接方式;http使用80端口;https使用443端口;

采用https的服务器必须从CA (Certificate Authority)申请一个用于证明服务器用途类型的证书。该证书只有用于对应的服务器的时候,客户端才信任此主机。

https访问流程是如何确认安全的:

  1. 访问XXX,客户端(浏览器)连接xxx:443并发送带有Cipher的ClientHellod内容
  2. xxx:443收到ClientHellod后与自己支持的SSL/TLS版本协议对比,返回双方都支持的Cipher(接下去就通讯都依靠Cipher规定的算法),同时发送xxx数字证书(包含xxx域名,颁发证书的CA,过期时间,XXX不对称密钥的公钥,经过CA私钥加密证书信息摘要后的【签名】等信息)
  3. 客户端验证证书,生成XXX数字证书的信息摘要并和CA不对称密钥的公钥解密签名后的摘要进行比较确认证书有效,并从此确认了xxx提供的xxx不对称密钥的公钥是有效的
  4. 至此双方通讯安全可以得到保证,客户端发送内容时带上内容摘要的加密签名,服务端获取内容并生成摘要与解密签名后的摘要进行比较从而确认安全
  5. 但不对称算法相当慢所以之后通讯采用对称加密算法,这里就需要先交换一个对称加密的密钥,一般使用Cipher密钥交换协议进行对称密钥交换,客户端生成一个随机值作为对称加密密钥,使用xxx不对称密钥的公钥加密后和对称加密密钥的Cipher算出的MAC值一起发送给xxx:443,xxx:443用不对称密钥的私钥解密后得到对称加密的密钥(会生成对称加密密钥的MAC做验证)
  6. 之后通讯都是一方对内容的摘要进行签名,然后另一方解密签名对比摘要

数字证书:数字证书是一种权威性的电子文档,由权威公正的第三方机构,即CA中心签发的证书。它以数字证书为核心的加密技术可以对网络上传输的信息进行加密和解密、数字签名和签名验证,确保网上传递信息的机密性、完整性。 使用了数字证书,即使您发送的信息在网上被他人截获,甚至您丢失了个人的账户、密码等信息,仍可以保证您的账户、资金安全。 VeriSign、GeoTrust、Thawte 是国际权威数字证书颁发认证机构的“三巨头”,其中,应用最广的为VerSign签发的电子商务数字证书。数字证书的持有人都有一对公钥和私钥,公钥是公开的,而私钥是由证书持有人在自己本地生成并持有,并且必须妥善保管和注意保密。

CA:证书颁发机构,CA中心自己握有CA私钥,并存储CA公钥到客户端(权威的CA一般已经安装到你的浏览器客户端),xxx需要拿自己的xxx公钥去CA申请xxx的数字证书存在服务端,并在在客户端请求时提供。

Cipher:每种Cipher规定了一系列算法,主要就是SSL/TLS版本差异,包含了四部分信息,分别是

  • 密钥交换算法(多为不对称加密算法),用于决定客户端与服务器之间在握手的过程中如何认证,用到的算法包括RSA,Diffie-Hellman,ECDH,PSK等
  • 对称加密算法,用于加密消息流,该名称后通常会带有两个数字,分别表示密钥的长度和初始向量的长度,比如DES 56/56, RC2 56/128, RC4 128/128, AES 128/128, AES 256/256
  • 报文认证信息码(MAC)算法,一般为hash算法,用于创建报文摘要,确保消息的完整性(没有被篡改),算法包括MD5,SHA等。
  • PRF(伪随机数函数),用于生成“master secret”。

比如 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,从其名字可知,它是

  • 基于TLS协议的;
  • 使用ECDHE、RSA作为密钥交换算法;
  • 加密算法是AES(密钥和初始向量的长度都是256);
  • MAC算法(这里就是哈希算法)是SHA。

SSH的SSL/TLS流程应用:

ssl的签名流程同时也用在了SSH协议,相关内容可以阅读《SSH原理与运用(一):远程登录》,但SSH并没有CA中心对公钥进行验证,所以服务器一般会公布自己公钥的指纹。

本文多为自己阅读相关文章和理解后的内容,并未实践抓取通讯流程,难免有不妥之处。

附:
HTTPS 理论详解与实践
Nginx基本配置备忘
HTTPS 简介及使用官方工具 Certbot 配置 Let’s Encrypt SSL 安全证书详细教程
手把手教你在Nginx上使用CertBot
HTTPS那些事(一)HTTPS原理

电子邮件SMTP、POP3、IMAP协议学习

SMTP协议

1. SMTP协议简介

SMTP称为简单邮件传输协议(Simple Mail Transfer Protocal),目标是向用户提供高效、可靠的邮件传输。它的一个重要特点是它能够在传送中接力传送邮件,即邮件可以通过不同网络上的主机接力式传送。通常它工作在两种情况下:一是邮件从客户机传输到服务器;二是从某一个服务器传输到另一个服务器。SMTP是一个请求/响应协议,它监听25号端口,用于接收用户的Mail请求,并与远端Mail服务器建立SMTP连接。

2. SMTP协议工作机制

SMTP通常有两种工作模式。发送SMTP和接收SMTP。具体工作方式为:发送SMTP在接收到用户的邮件请求后,判断此邮件是否为本地邮件,若是直接投送到用户的邮箱,否则向DNS查询远端邮件服务器的MX记录,并建立与远端接收SMTP之间的一个双向传送通道,此后SMTP命令由发送SMTP发出,由接收SMTP接收,而应答则反方向传送。一旦传送通道建立,SMTP发送者发送MAIL命令指明邮件发送者。如果SMTP接收者可以接收邮件则返回OK应答。SMTP发送者再发出RCPT命令确认邮件是否接收到。如果SMTP接收者接收,则返回OK应答;如果不能接收到,则发出拒绝接收应答(但不中止整个邮件操作),双方将如此反复多次。当接收者收到全部邮件后会接收到特别的序列,入伏哦接收者成功处理了邮件,则返回OK应答。

3. SMTP服务器查找

SMTP服务器是基于域名服务DNS中计划收件人的域名来路由电子邮件。一般根据登陆邮箱的后缀域名通过DNS协议查找MX记录。

  1. 查找邮箱后缀域名的MX记录所指向的域名地址
  2. 根据指向域名地址通过dns查找域名地址指向的服务器ip
  3. tcp连接服务器ip:25(默认端口)(用来登陆或者发送)

- 阅读剩余部分 -

MySQL 的隐式类型转换问题

隐式类型转换存在两个巨大的风险:

  1. 类型转换无法命中索引的风险,在高并发、大数据量的情况下,命不中索引带来的后果非常严重。将数据库拖死,继而整个系统崩溃,对于大规模系统损失惨重。
  2. 类型转换导致查询条件改变,造成安全问题。
    例如这个安全问题:假如 password 类型为字符串,查询条件为 int 0 则会匹配上。
    mysql_transform.jpg

MySQL的隐式类型转换原则如下:

  • 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 <=> 对两个 NULL 做比较时会返回 1,这两种情况都不需要做类型转换
  • 两个参数都是字符串,会按照字符串来比较,不做类型转换
  • 两个参数都是整数,按照整数来比较,不做类型转换
  • 十六进制的值和非数字做比较时,会被当做二进制串,和数字做比较时会按下面的规则处理
  • 有一个参数是 TIMESTAMP 或 DATETIME,并且另外一个参数是常量,常量会被转换为 timestamp
  • 有一个参数是 decimal 类型,如果另外一个参数是 decimal 或者整数,会将整数转换为 decimal 后进行比较,如果另外一个参数是浮点数,则会把 decimal 转换为浮点数进行比较
  • 所有其他情况下,两个参数都会被转换为浮点数再进行比较

由于 MySQL 隐式类型转换规则比较复杂,依赖 MySQL 隐式转换很容易出现各种想想不到的问题,而且 MySQL 隐式类型转换本身也是非常耗费 MySQL 服务器性能的,所以建议代码做严格的类型查询。

Redis协议笔记

redis协议相当简单好理解。

redis协议支持类型:

  • 正确 Simple Strings
  • 错误 Errors
  • 整数 Integers
  • 字符块 Bulk Strings
  • 数组 Arrays

Simple Strings(+)
Simple Strings的呈现以+开始,以\r\n结尾,一个只含有'OK'的表示为+OK\r\n。所以简单的字符串不能包含连续的\r\n字符。

Errors(-)
Errors 的呈现以-开始,以\r\n结尾,一条表示Error message错误信息的字符为-Error message\r\n

Integers(:)
Integers 的呈现以:开始,以\r\n结尾,比如:1000\r\n表示数字1000

Bulk Strings($)
Bulk Strings 数据头同时表示下一行数据长度,不包括换行符长度\r\n,$后面则是对应的长度的数据。它还有一个特殊用途,用长度为-1的空字符表示NUll值

Arrays(*)
Arrays 头以 * 开始,后边接一个Integer类型,表示消息体总共有多少行,不包括当前行,*后面是具体的行数,也就是数组长度,之后接着各自的数据类型。

发送方式

客户端将命令用块字符的方式发送给服务端,比如:

SET xyz abcde
*3\r\n
$3\r\n
SET\r\n
$3\r\n
xyz\r\n
$5\r\n
abcde\r\n

成功或者失败:

+OK\r\n
-错误信息\r\n

如果是内建的命令操作,可以直接发送命令给服务器。比如PING, EXISTS mykey等.使用telnet 127.0.0.1 6379连上默认的redis server, 敲入PING就可以返回结果+PONG

php实现redis客户端和服务端 string功能:

  • 服务端
<?php
/**
 * 多进程阻塞式
 */
class Xtgxiso_server
{
    private $socket = false;
    private $process_num = 100;
    public $redis_kv_data = array();
    public $onMessage = null;
 
    function __construct($host="0.0.0.0",$port=1215)
    {
        $this->socket = stream_socket_server("tcp://".$host.":".$port,$errno, $errstr);
        if (!$this->socket) die($errstr."--".$errno);
        echo "listen $host $port \r\n";
        ini_set("memory_limit", "128M");
    }
 
    private function parseRESP(&$conn){
        $line = fgets($conn);
        if($line === '' || $line === false)
        {
            return null;
        }
        $type = $line[0];
        $line = mb_substr($line,1,-2);
        switch ( $type ){
            case "*":
                $count = (int) $line;
                $data = array();
                for ($i = 1; $i <= $count; $i++) {
                    $data[] = $this->parseRESP($conn);
                }
                return $data;
            case "$":
                if ($line == '-1') {
                    return null;
                }
                $length = $line + 2;
                $data = '';
                while ($length > 0) {
                    $block = fread($conn, $length);
                    if ($length !== strlen($block)) {
                        throw new Exception('RECEIVING');
                    }
                    $data .= $block;
                    $length -= mb_strlen($block);
                }
                return mb_substr($data, 0, -2);
        }
        return $line;
    }
 
    private function start_worker_process(){
        $pid = pcntl_fork();
        switch ($pid) {
            case -1:
                echo "fork error : {$i} \r\n";
                exit;
            case 0:
                while ( 1 ) {
                    echo  "waiting...\n";
                    $conn = stream_socket_accept($this->socket, -1);
                    if ( !$conn ){
                        continue;
                    }
                    //"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
                    while(1){
                        $arr = $this->parseRESP($conn);
                        if ( is_array($arr) ) {
                            if ($this->onMessage) {
                                call_user_func($this->onMessage, $conn, $arr);
                            }
                        }else if ( $arr ){
                            if ($this->onMessage) {
                                call_user_func($this->onMessage, $conn, $arr);
                            }
                        }else{
                            fclose($conn);
                            break;
                        }
                    }
                }
            default:
                $this->pids[$pid] = $pid;
                break;
        }
    }
 
    public function run(){
        for($i = 1; $i <= $this->process_num; $i++){
            $this->start_worker_process();
        }
 
        while(1){
            foreach ($this->pids as $i => $pid) {
                if($pid) {
                    $res = pcntl_waitpid($pid, $status,WNOHANG);
 
                    if ( $res == -1 || $res > 0 ){
                        $this->start_worker_process();
                        unset($this->pids[$pid]);
                    }
                }
            }
            sleep(1);
        }
    }
 
}
$server =  new Xtgxiso_server();
$server->onMessage = function($conn,$info) use($server){
    if ( is_array($info) ){
        if ( $info["0"] == "SET" ) {
            $key = $info[1];
            $val = $info[2];
            $server->redis_kv_data[$key] = $val;
            fwrite($conn, "+OK\r\n");
        }else if ( $info["0"] == "GET" ){
            $key = $info[1];
            fwrite($conn, "$".strlen($server->redis_kv_data[$key])."\r\n".$server->redis_kv_data[$key]."\r\n");
        }else{
            fwrite($conn,"+OK\r\n");
        }
    }else{
        fwrite($conn,"+OK\r\n");
    }
};
$server->run();

通过如下命令来测试PHP实现的性能:

redis-benchmark -h 127.0.0.1 -p 1215 -t set -n 80000 -q
  • 客户端
<?php
namespace xtgxiso;
class Redis {
    private $redis_socket = false;
    private $cmd = '';
    public function __construct($host='127.0.0.1',$port=6379,$timeout = 3) {
        $this->redis_socket = stream_socket_client("tcp://".$host.":".$port, $errno, $errstr,  $timeout);
        if ( !$this->redis_socket) {
            throw new Exception("{$errno} - {$errstr}");
        }
    }
    public function __destruct() {
        fclose($this->redis_socket);
    }
    public function __call($name, $args) {
        $crlf = "\r\n";
        array_unshift($args,$name);
        $command = '*' . count($args) . $crlf;
        foreach ($args as $arg) {
            $command .= '$' . strlen($arg) . $crlf . $arg . $crlf;
        }
        $fwrite = fwrite($this->redis_socket,$command);
        if ($fwrite === FALSE || $fwrite <= 0) {
            throw new Exception('Failed to write entire command to stream');
        }
        return $this->readResponse();
    }
    private function readResponse() {
        $reply = trim(fgets($this->redis_socket, 1024));
        switch (substr($reply, 0, 1)) {
            case '-':
                throw new Exception(trim(substr($reply, 4)));
                break;
            case '+':
                $response = substr(trim($reply), 1);
                if ($response === 'OK') {
                    $response = TRUE;
                }
                break;
            case '$':
                $response = NULL;
                if ($reply == '$-1') {
                    break;
                }
                $read = 0;
                $size = intval(substr($reply, 1));
                if ($size > 0) {
                    do {
                        $block_size = ($size - $read) > 1024 ? 1024 : ($size - $read);
                        $r = fread($this->redis_socket, $block_size);
                        if ($r === FALSE) {
                            throw new Exception('Failed to read response from stream');
                        } else {
                            $read += strlen($r);
                            $response .= $r;
                        }
                    } while ($read < $size);
                }
                fread($this->redis_socket, 2); /* discard crlf */
                break;
            /* Multi-bulk reply */
            case '*':
                $count = intval(substr($reply, 1));
                if ($count == '-1') {
                    return NULL;
                }
                $response = array();
                for ($i = 0; $i < $count; $i++) {
                    $response[] = $this->readResponse();
                }
                break;
            /* Integer reply */
            case ':':
                $response = intval(substr(trim($reply), 1));
                break;
            default:
                throw new RedisException("Unknown response: {$reply}");
                break;
        }
        return $response;
    }
}
/*
$redis = new Client_test();
var_dump($redis->auth("123456"));
var_dump($redis->set("a",'b'));
var_dump($redis->get("a"));
*/

参考:
http://redis.cn/topics/protocol.html
http://www.01happy.com/php-redis-server/
http://www.01happy.com/php-redis-client/

c#实现一个简单浏览器 和 dns查询软件

初中的时候看别人开发的软件很有意思,非常想学,但苦于c的难度,最后转学web来提升成就感,而且一做就这么多年,这两年因为想做游戏的原因又开始由php往底层学c c++ c# java,虽然软件跟互联网或者游戏相比被看做夕阳产业,但能实现小时候的梦想也挺有意思的,刚入门的小东西大家随便看看哈。

dns查询软件,dns协议分析并实现
源码:https://github.com/forthxu/mydns
下载程序:https://github.com/forthxu/mydns/blob/master/exe.rar?raw=true
设计和实施 DNS 服务器和客户端服务时可能用到的RFC相关规范:
RFC
标题
1034
域名 - 概念和工具
1035
域名 - 实现和规范
1123
Internet 主机 - 应用和支持的要求
1886
支持 IP 版本 6 的 DNS 扩展名
1995
DNS 中的增量区域传输
1996
提示通知区域更改的机制 (DNS NOTIFY)
2136
域名系统中的动态更新 (DNS UPDATE)
2181
对 DNS 规范的说明
2308
DNS 查询的负缓存 (DNS NCACHE)
2535
域名系统安全扩展 (DNSSEC)
2671
DNS 的扩展机制 (EDNS0)
2782
指定服务位置的 DNS RR (DNS SRV)
2930
DNS 的密钥建立 (TKEY RR)
3645
DNS (GSS-TSIG) 密钥事务身分验证的通用安全服务算法
3646
IPv6 (DHCPv6) 动态主机配置协议的 DNS 配置选项

浏览器,简单的调用控件
源码:https://github.com/forthxu/forthxu_browser
下载程序:https://github.com/forthxu/forthxu_browser/blob/master/exe/Forthxu_browser.exe?raw=true

手游上线流程

手游上线非常麻烦,有客户端,需要跟渠道紧密沟通,下面是我之前总结的产品上线流程,并不适用所有团队,仅供参考。

上线前一个月

运营工作

  • 游戏信息确定
    • 确定游戏名字
    • icon做多个版本进行测试
    • 截图准备做多个版本进行测试
  • 法律流程
    • 申请软件著作权
    • 申请软件产品登记
    • 美术相关申请美术作品版权登记
  • 游戏官网(适应移动端浏览)
  • 游戏视频制作开始筹备
  • 游戏资料整理(初版)
  • 第三方账号申请
    • 百度贴吧申请
    • 微信账号申请
    • 微博账号申请
    • QQ空间账号申请
  • 提交运营通用需求(详情在另外一个文档)
  • 后台功能需求
    • 领取活动
    • 兑换活动
    • 充值,消费活动
    • 转盘活动
    • 公告
    • 邮件
    • 配置商城
    • buff类活动(经验加成,掉率加成)
    • 特殊掉落活动
  • 游戏设计验证
    • 不同系统投入产出比
    • 扭蛋概率
    • 宝箱概率
  • 第三方SDK接入
    • talkingdata数据统计后台
    • 广告效果跟踪campaign
    • 广告SDK
    • 事件添加

市场工作

  • 推广计划制定
  • 新闻素材整理
  • 早期产品曝光

上线前1周

运营工作

  • 确定icon
    • 尺寸:1024、512、120、114、90、72、48、36
  • 确定截图
    • 横版,竖版各一套
    • 尺寸:1136*640、1024*768、960*640、800*480、480*360、480*320
  • 游戏介绍
    • 简版200字
    • 完整版无字数限制(带图)
  • 游戏攻略(至少3篇)
    • 新手攻略(多篇)
    • 花钱攻略
    • 游戏资源产出图
  • 百度贴吧内容填充
    • 游戏资料区
    • 攻略区
    • 精品区
  • 微信素材填充
    • 游戏资料
    • 攻略链接到官网
    • 自定义回复
    • 自定义菜单
  • 游戏官网资料填充(未来不断更新)
  • 准备20个服务器名称
  • 开通QQ群
  • 检查后台数据准确性
    • 事件记录
    • 自己游戏后台和talkingdata对比
  • 删档封测活动
    • 游戏内活动
    • 社群刷楼活动
    • 好评活动
    • 加Q群活动
    • 加微信活动
  • 渠道接入完成,可上线状态
    • 给渠道提交客户端
    • 充值联调完毕
    • 游戏专区建设完毕(icon,截图,介绍,关键字)

市场工作

  • 产品新闻
    • 上线新闻
    • 游戏特色
    • 故事背景
    • 游戏评测
    • 制作人专访
    • 制作团队探班
    • IP合作新闻
  • 渠道资源准备
    • 确定删档封测的推广资源
    • 软文
    • 媒体合作
    • 外部活动合作(激活码,礼包码,找bug活动,实物奖励活动)
  • 广告投放前期准备
    • 广告素材
    • 短链生成

上线前1天

运营工作

  • 游戏删档
  • 配置好所有活动并测试
  • 检查游戏设置
    • 不开充值
    • 商城测试
    • 服务器名
    • 游戏版本
    • 游戏公告
    • qq群,微信推荐

上线当天

运营工作

  • 提前1小时检查客户端是否上架,游戏介绍页是否正常
  • 上线前10分钟开放服务器,进游戏检查
    • 商城
    • 活动
    • 公告
    • 功能
    • 充值
  • 上线后频繁关注
    • 新增玩家
    • 下载量和激活玩家对比
    • QQ群
    • 在线数据
    • 微信
    • 游戏内聊天
    • 事件数据

市场工作

  • 检查广告资源
    • 媒体新闻是否上线,位置是否正确
    • 广告位是否正常
    • 链接,包是否正确

封测期间:

  • 收集玩家反馈,整理bug,游戏优化建议
  • 整理玩家行为数据,发现前期卡点,提出前期优化建议
  • 提出新的后台需求,完善后台功能
  • 跟活跃玩家深入交流,保持长期沟通
  • 用户访谈(不同类型玩家)
  • 重点优化留存数据

收费内测:

  • 首服作为测试服,可较大改动,改到数据理想为止
  • 查看消费结构,优化游戏付费
  • 针对性的写一些消费攻略和引导
  • 搭建好玩家管理团队,管理贴吧,qq群
  • 接一个小渠道做专服,用来做玩家测试服,每次版本更新先更新测试服
本文来源:原文链接

skynet线上使用的数据,解答性能问题

记录一下MMZB的运维运营数据

陌陌争霸已经运行9个多月,记录一下现在的运维运营数据:

总用户460万,集群服务器共30台,其中Game Server
9台,redis集群内存占用接近900G,mongo集群磁盘占用接近5T(定时删除旧数据,所以增长速度趋缓了)。
redis集群内存每周增长20G,很快突破1T。后期会上线leveldb落地redis冷数据的方案,有望把redis内存占用削减到现在的三分之二,则600G左右。使用TokuMX替换Mongo后,减少了的磁盘空间占用压力,已经处于恒定状态。

统观项目状态,资源需求已经趋于平稳状态,运维方向可向更高层次的调优和架构改造偏重。

从最近的两次崩溃所总结出的及我的2013

公司的手游项目(注:手游 陌陌争霸)上线了半个多月,一直运行的稳定,开始的测试过程中也出现了零零散散的几个小坑,都顺利跨过了。能实现单机支持3W在线用户的水平,按云风GG的说法,这结果出乎之前的预算,有那么一点小鼓舞。
过去的这个周末和今天周一连续出现了两次集群中一台服务崩溃的情况,晓靖和云风GG很快找到了梗所在,并迅速修复了这个坑,让我们对这个项目更加充满了希望,我相信合作方也与我们一样。
说回运维层面的东西,首先我想说的是,好的代码和架构能为运维节省大量的工作并同时提升运维效率,从这一点上,skynet和与之配合的各模块都出色的达到了这个标准:完善的日志输出、高效但并不复杂的架构设计,我还是遵循我一向的标准,只要是简单、高效、安全的设计,就是优秀的设计。

web服务系统连接状态

系统

1.查看TCP连接状态

netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn
netstat -n | awk '/^tcp/ {++S[$NF]};END {for(a in S) print a, S[a]}' 或
netstat -n | awk '/^tcp/ {++state[$NF]}; END {for(key in state) print key,"\t",state[key]}'
netstat -n | awk '/^tcp/ {++arr[$NF]};END {for(k in arr) print k,"\t",arr[k]}'
netstat -n |awk '/^tcp/ {print $NF}'|sort|uniq -c|sort -rn
netstat -ant | awk '{print $NF}' | grep -v '[a-z]' | sort | uniq -c

2.查找请求数请20个IP(常用于查找攻来源)
netstat -anlp|grep 80|grep tcp|awk '{print $5}'|awk -F: '{print $1}'|sort|uniq -c|sort -nr|head -n20
netstat -ant |awk '/:80/{split($5,ip,":");++A[ip1]}END{for(i in A) print A[i],i}' |sort -rn|head -n20

3.用tcpdump嗅探80端口的访问看看谁最高
tcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." '{print $1″."$2″."$3″."$4}' | sort | uniq -c | sort -nr |head -20

4.查找较多time_wait连接
netstat -n|grep TIME_WAIT|awk '{print $5}'|sort|uniq -c|sort -rn|head -n20

5.找查较多的SYN连接
netstat -an | grep SYN | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr | more

6.根据端口列进程
netstat -ntlp | grep 80 | awk '{print $7}' | cut -d/ -f1

日志

1.获得访问前10位的ip地址
cat access.log|awk '{print $1}'|sort|uniq -c|sort -nr|head -10
cat access.log|awk '{counts[$(11)]+=1}; END {for(url in counts) print counts[url], url}'

2.访问次数最多的文件或页面,取前20
cat access.log|awk '{print $11}'|sort|uniq -c|sort -nr|head -20

3.列出传输最大的几个exe文件(分析下载站的时候常用)
cat access.log |awk '($7~/.exe/){print $10 " " $1 " " $4 " " $7}'|sort -nr|head -20

4.列出输出大于200000byte(约200kb)的exe文件以及对应文件发生次数
cat access.log |awk '($10 > 200000 && $7~/.exe/){print $7}'|sort -n|uniq -c|sort -nr|head -100

5.如果日志最后一列记录的是页面文件传输时间,则有列出到客户端最耗时的页面
cat access.log |awk '($7~/.php/){print $NF " " $1 " " $4 " " $7}'|sort -nr|head -100

6.列出最最耗时的页面(超过60秒的)的以及对应页面发生次数
cat access.log |awk '($NF > 60 && $7~/.php/){print $7}'|sort -n|uniq -c|sort -nr|head -100

7.列出传输时间超过 30 秒的文件
cat access.log |awk '($NF > 30){print $7}'|sort -n|uniq -c|sort -nr|head -20

8.统计网站流量(G)
cat access.log |awk '{sum+=$10} END {print sum/1024/1024/1024}'

9.统计404的连接
awk '($9 ~/404/)' access.log | awk '{print $9,$7}' | sort

10. 统计http status.
cat access.log |awk '{counts[$(9)]+=1}; END {for(code in counts) print code, counts[code]}'
cat access.log |awk '{print $9}'|sort|uniq -c|sort -rn

11.蜘蛛分析查看是哪些蜘蛛在抓取内容。
/usr/sbin/tcpdump -i eth0 -l -s 0 -w - dst port 80 | strings | grep -i user-agent | grep -i -E 'bot|crawler|slurp|spider'

12.按域统计流量
zcat squid_access.log.tar.gz| awk '{print $10,$7}' |awk 'BEGIN{FS="[ /]"}{trfc[$4]+=$1}END{for(domain in trfc){printf "%s\t%d\n",domain,trfc[domain]}}'
效率更高的perl版本请到此下载:http://docs.linuxtone.org/soft/tools/tr.pl

13.按步骤分析请求时间
curl -o /dev/null -w %{time_namelookup}::%{time_connect}::%{time_starttransfer}::%{time_total}::%{speed_download}"\n" "http://joke.4399pk.com"

数据库篇

1.查看数据库执行的sql
/usr/sbin/tcpdump -i eth0 -s 0 -l -w - dst port 3306 | strings | egrep -i 'SELECT|UPDATE|DELETE|INSERT|SET|COMMIT|ROLLBACK|CREATE|DROP|ALTER|CALL'

系统Debug分析篇

1.调试命令
strace -p pid

2.跟踪指定进程的PID
gdb -p pid

原文
使用netstat和awk命令来统计网络连接数

一道php引用问题

<?php
    $arr = array('a','b','c');
    foreach($arr as $key=>$val){
            $val=&$arr[$key];
            print_r($arr);
    }
?>

执行结果为:

Array 第一次循环,使$val是$arr[0 ]的引用
(
[0 ] => a
[1 ] => b
[2 ] => c
)
Array 第二次循环,因为$arr[1 ]赋值给$val,而$val是$arr[0 ]的引用,因此数组被改变。 同样第二次循环使$val是$arr[1 ]的引用
(
[0 ] => b
[1 ] => b
[2 ] => c
)
Array 同理
(
[0 ] => b
[1 ] => c
[2 ] => c
)