
专注于互联网系统设计
谈谈后端业务系统的微服务化改造本文所提倡的微服务,是结合作者所在team自身业务特点来说的,适合自身的场景,是建立在团队人员素质到了,有成熟的基础设施和框架、中间件辅助,流程也规范,包括CI、敏捷等,团队都做好了准确去做这个转变,有足够的能力来实施,微服务化也就是水到渠成的事了。相反,小团队在前期或者野蛮生长时期,不宜选择微服务,不但影响效率还带来额外的复杂度。成长型或者大公司,有成熟的流程、规范、基础设施、平台等,要想在整条交付链路上加速,就需要投入更多的资源保障微服务化,一切自动化了,能治理了,回头看来这一切就都是值得的,远期收益非常可观。--------------------------------------------------------今天看到这个是这么回事,去小公司就是这样,不要把大公司的技术架构去折腾。
一、软件需求 1、nginx源码包 下载地址:http://nginx.org/。笔者下载的是1.10.3。 2、pcre源码包。这是一个正则表达式库。nginx会用到这个开源库来做正则匹配。很多软件都会引用,比如php引擎编译的时候也会用到。 下载地址:https://ftp.pcre.org/pub/pcre/pcre-8.36.zip 3、nginx-rtmp-module源码包 这才rtmp服务真正要的工具。 下载地址:https://github.com/arut/nginx-rtmp-module 4、openssl源码包。这个不是必须。只有nginx 版本在1.3.14 - 1.5.0之间的,才需要安装。nginx-rtmp-module的文档特意提到了这点。 在编译nginx的时候,加上参数--with-http_ssl_module。如:./configure --add-module=rtmp模块的源码位置 --with-http_ssl_module 5、推流客户端工具:ffmpeg。Ffmpeg工具下载官网:http://www.ffmpeg.org/download.html 6、拉流播放工具:需要一个很常用的流媒体播放工具VLC。下载地址:http://www.videolan.org/vlc/ 二、编译nginx 解压nginx。进入到nginx源码目录,执行如下命令: 第一步: ./configure --prefix=nginx安装到哪个目录 --with-pcre=pcre库的源码目录,注意是源码目录 --add-module=rtmp模块的源码目录 注:预先将pcre库、rtmp模块解压。以便上面使用。 一般两种压缩包。命令不同,如下: tar.gz包命令:tax -xzvf 软件包 zip包:unzip 软件包 第二步: make && make install 三、配置nginx 配置nginx.conf,增加rtmp与http是同级别的,内容如下: user root; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; error_log logs/error.log error; pid logs/nginx.pid; events { worker_connections 1024; } #服务于rtmp协议的请求 rtmp://开头 rtmp{ server{ listen 1395; chunk_size 4096; #自定义的直播名称,路径中将会用到 #rtmp://192.168.56.88:1395/my_live/rtmpstream中的my_live就是下面定义的名称 application my_live{ #开启直播功能 live on; #接收的rtmp视频流是否落地存储到flv文件,不开启就直接在内存中,存储也是一个视频拆成很多小flv文件 record all; #存储路径 record_path /tmp/nginx_rtmp_av; record_max_size 128K; #为同一个视频文件的视频分片加时间戳 record_unique on; } #hls协议,一样是一个application后面接着自定义名称,区别是hls on; application hls { live on; #开启hls hls on; #hls的视频分片文件(.ts)存放路径 hls_path /tmp/nginx_hls_av; #每个视频分片文件包含多少秒 hls_fragment 5s; #是否清理掉旧的ts文件。默认是开启的。现在关闭 hls_cleanup off; hls_playlist_length 5h; } } #这里可以加其他server侦听其他端口 } #服务于http请求 http{ } 注:nginx-rtmp-module在nignx.conf中使用的指令有很多,需要慢慢消化。先搭建一个简单的骨架。比如还可以进行hls协议的服务。 使用指令的说明文档:https://github.com/arut/nginx-rtmp-module/wiki/Directives 四、推流和拉流 怎么推流 使用ffmpeg工具来推流。Ffmpeg是一套开源的库:视频采集功能、视频格式转换、视频抓图、给视频加水印。很多软件引入了这个库来完成视频的采集、转码。 命令格式如下: ffmpeg -re -i E:\test.mp4 -f flv rtmp://192.168.56.88:1395/my_live/rtmpstream 上述命令,就会读取-i参数指定的视频文件,推流到指定地址去。需要将ffmpeg添加到环境变量中去。或者直接输入ffmpeg的完整安装路径来执行也是可以的。 其中,my_live是nginx.conf中配置的直播名称。rtmpstream是自定义的名称。后续拉流的时候,就必须使用这个名称来拉流,nginx-rtmp落地存储数据(若开启了)的时候,就是使用这个名称命名flv文件的。如一个视频文件才服务器磁盘上被拆成了:rtmpstream-1502794875.flv、rtmpstream-1502794879.flv、rtmpstream-1502794885.flv.................。 -r设定帧速率,默认为25。 -i 设定输入流,也就是读取哪个文件推流到服务端。 运行命令后,会在命令行看到推流过程: 客户端使用ffmpeg完成视频的推流,接着可以在另外一端,输入拉流地址,拉流观看视频。 怎么拉流观看视频 需要一个很常用的流媒体工具:VLC。 软件安装好后(笔者安装的是window下VLC工具),打开软件,选择"打开网络串流",如下所示: 输入拉流的地址,推流和拉流都是同一个地址(说的是rtmp协议)。如:rtmp://192.168.56.88:1395/my_live/rtmpstream 到此,完成了,一边推流,一边拉流的效果。 特别注意: 使用hls方式来推流给nginx,客户端ffmpeg的参数要不一样了:需要增加两个参数-vcodec copy和-acodec copy。如果没有这两个参数,是不会把视频落地到hls对应的目录去的。 如下: ffmpeg -re -i E:\test.mp4 -vcodec copy -acodec copy -f flv rtmp://192.168.56.88:1395/hls/hls_stream -acodec表示音频编码,copy表示不改变编解码器,只是改封装器。 -vcodec表示视频编码,copy表示不改变编解码器,只是改封装器。
联想到discuz,ecshop发布一个新版本的系统给大家使用,会提供utf-8,gb2312版本的代码下载。所以肯定是批量转换编码出来的。 这种是转换html文件。 http://blog.csdn.net/iseagold/article/details/5472377 我需要找一个批量转换文件编码的工具才行。 问题在于:目录中混合了gb2312和utf8编码的。如何才能用工具去自动判断呢。 不要强制转换一次编码。 gb2312和utf8编码的。如果是utf8编码的文件,就不要转换,如果是gb2312的编码才执行转换成utf8编码。 http://blog.csdn.net/liu_qiqi/article/details/38706497 按照给定的字符集存储文件时,在文件的最开头的三个字节中就有可能存储着编码信息,所以,基本的原理就是只要读出文件前三个字节,判定这些字节的值,就可以得知其编码的格式。其实,如果项目运行的平台就是中文操作系统,如果这些文本文件在项目内产生,即开发人员可以控制文本的编码格式,只要判定两种常见的编码就可以 了:GBK和UTF-8。由于中文Windows默认的编码是GBK,所以一般只要判定UTF-8编码格式。 对于UTF-8编码格式的文本文件,其前3个字节的值就是-17、-69、-65,所以,判定是否是UTF-8编码格式的代码片段如下: php的mb_detect_encoding函数,我正准备试一试:function characet($data){ if( !empty($data) ){ $filetype = mb_detect_encoding($data , array('utf-8','gbk','latin1','big5')) ; if( $filetype != 'utf-8'){ $data = mb_convert_encoding($data ,'utf-8' , $filetype); } } return $data; } http://blog.csdn.net/wydycrtd/article/details/4793124 有人也说了,重新认识一下此问题,当时版主回复的时候我就觉得mb函数里一定有这样的功能,但今日研究了mb库,并没有这样的功能。用mb_detect_encoding总是不准确。 mbstring 当前实现了以下编码检测筛选器。 如有以下编码列表的无效字节序列,编码的检测将会失败。 UTF-8, UTF-7, ASCII, EUC-JP,SJIS, eucJP-win, SJIS-win, JIS, ISO-2022-JP var_dump(mb_detect_encoding($str,array('UTF-8','GB2312'))); EUC-CN EUC-CN是GB2312最常用的表示方法。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法。 php中用mb_detect_encoding测出来的euc-cn是gb2312编码:EUC-CN是GB2312最常用的表示方法 GB 2312字元使用两个字节来表示。 “第一位字节”使用0xA1-0xFE “第二位字节”使用0xA1-0xFE 网上找到的不能解决自动检测编码的问题,这里我根据自己需要。检测两种编码就可以了:gb2312和utf-8 <?php header("Content-type: text/html; charset=utf-8"); /* * +--------------------------------------------------- * 遍历指定目录,形成一个目录句柄返回 * +--------------------------------------------------- * * +--------------------------------------------------- */ function explorer_dir($sDir) { static $aTempArr = array(); $dp = opendir($sDir); while ($sFileName = readdir($dp)) { if ($sFileName != '.' && $sFileName != '..') { $sPath = $sDir . "/" . $sFileName; if (is_dir($sPath)) { explorer_dir($sPath); } else { // $filetime=date("Y-m-d H:i:s",filectime("$path")); // $fp=$path.",".$filetime; $fp = $sPath; $aTempArr[] = $fp; } } } closedir($dp); return $aTempArr; } /* * +---------------------------------------------------------------------- * //搜索指定目录下的gb2312编码文件,转换成utf-8编码文件 * +---------------------------------------------------------------------- */ function change_gb2312_to_utf8_dir($dir) { $all_dirs = explorer_dir($dir); $suffix_list = array('php', 'htm', 'html', 'txt', 'js'); echo 'get files count:'; echo count($all_dirs) . '<br />'; $i = 0; foreach ($all_dirs as $dir_key => $file_path) { $file_content = file_get_contents($file_path); $i++; echo $i . '、' . $file_path.'<br />'; var_dump($file_encode_type = mb_detect_encoding($file_content, array('UTF-8', 'EUC-CN'), true)); //EUC-CN是gb2312的另外称呼,php这个扩展返回的是值,不是gb2312 echo '<br />'; //获取文件的后缀,指定文件类型采取做操作,比如图片就不能去修改的 $file_name = basename($file_path); $suffix = get_file_suffix($file_name); if (in_array($suffix, $suffix_list)) { if ($file_encode_type == 'EUC-CN') { echo '<font color="red">had changed the file from ' . $file_encode_type . '(gb2312) to UTF-8:' . $file_path . '</font><br /><br />'; //就是gb2312编码的 $after_change_encode_content = iconv("gb2312", "UTF-8", $file_content); unlink($file_path); file_put_contents($file_path, $after_change_encode_content); unset($after_change_encode_content); } } else { echo '<font color="red">the file not in allow file type:' . $file_path . '</font><br /><br />'; } unset($file_content); echo '--------------------------------------------------------------------<br /><br />'; } } /* * +---------------------------------------------------------------------- * //搜索指定目录下指定编码的文件,目的就是帮助我们看出自己的项目哪些是什么编码的文件 * +---------------------------------------------------------------------- */ function dir_file_encode_list($dir) { $all_dirs = explorer_dir($dir); echo 'get files count:'; echo count($all_dirs) . '<br />'; $i = 0; foreach ($all_dirs as $dir_key => $file_path) { $file_content = file_get_contents($file_path); $i++; echo $i . '、' . $file_path.'<br />'; var_dump($file_encode_type = mb_detect_encoding($file_content, array('UTF-8', 'EUC-CN'), true)); //EUC-CN是gb2312的另外称呼,php这个扩展返回的是值,不是gb2312 echo '<br />'; unset($file_content); echo '--------------------------------------------------------------------<br /><br />'; } } /* * +---------------------------------------------------------------------- * 扫描指定目录下的指定目录下的html文件,批量将里面的 * <meta http-equiv="Content-Type" content="text/html; charset=gbk" /> * 指定的编码替换成另外一个编码 * +---------------------------------------------------------------------- */ function replace_html_charset($dir) { $all_dirs = explorer_dir($dir); $suffix_list = array('htm', 'html','php'); echo 'get files count:'; echo count($all_dirs) . '<br />'; $i = 0; $charset = '<meta http-equiv="Content-Type" content="text/html; charset=gbk" />'; $to_charset = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />'; foreach ($all_dirs as $dir_key => $file_path) { $file_content = file_get_contents($file_path); $i++; echo $i . '、' . $file_path.'<br />'; //获取文件的后缀,指定文件类型采取做操作,比如图片就不能去修改的 $file_name = basename($file_path); $suffix = get_file_suffix($file_name); if (in_array($suffix, $suffix_list)) { $patten = '%' . $charset . '%i'; if (preg_match($patten, $file_content)) { $after_change_encode_content = str_ireplace($charset, $to_charset, $file_content); unlink($file_path); file_put_contents($file_path, $after_change_encode_content); unset($after_change_encode_content); echo 'find limit :' . $file_path . '<br /><br />'; } }else{ echo '<font color="red">the file not in allow file type:' . $file_path . '</font><br /><br />'; } } } //dir_file_encode_list("D:\\static\\develop\\mama\\test_change_encode"); //change_gb2312_to_utf8_dir("D:\\static\\develop\\mama\\test_change_encode"); //replace_html_charset("D:\\static\\develop\\mama\\test_change_encode"); function get_file_suffix($file_name){ $file_name_arr = explode(".", $file_name); $suffix = array_pop($file_name_arr); return $suffix; } View Code
笔者觉得,分库分表确实好的。但是,动不动搞分库分表,太麻烦了。分库分表虽然是提高数据库性能的常规办法,但是太麻烦了。所以,尝试研究mysql的分区到底如何。 之前写过一篇文章,http://www.cnblogs.com/wangtao_20/p/7115962.html 讨论过订单表的分库分表,折腾起来工作量挺大的,需要多少技术去折腾。做过的人才知道有多麻烦 要按照什么字段切分,切分数据后,要迁移数据;分库分表后,会涉及到跨库、跨表查询,为了解决查询问题,又得用其他方案来弥补(比如为了应对查询得做用户订单关系索引表)。工作量确实不小。 从网上也可以看到,大部分实施过的人(成功的)的经验总结:水平分表,不是必须的,能不做,尽量不做。 像阿里这些系统,数据库单表数量十多亿,达到瓶颈了,不得不做分库分表,扩容也方便。没有选择。 那么,针对起步阶段的业务,技术人员不够,产品还处在试错阶段。是不是可以考虑一下分区方案。 笔者几年前,也犯了思维错误,在小公司做系统,产品还在开发,有待推向市场验证。那个时候,笔者就去考虑什么评论表数据量大的情况下要怎么做,其实伤脑,又费时间,业务没有做起来,其实没多少用处。 架构是演变出来的,不是设计出来的。企图一开始就设计大炮,结果只有蚊子。笔者做试验看看mysql的分区到底是什么个原理。研究发现,其实跟分表差不多,比如按hash打散数据、按值范围分散数据。 一、探讨分区的原理 了解分区到底在做什么,存储的数据文件有什么变化,这样知道分区是怎么提高性能的。 实际上:每个分区都有自己独立的数据、索引文件的存放目录。本质上,一个分区,实际上对应的是一个磁盘文件。所以分区越多,文件数越多。 现在使用innodb存储较多,mysql默认的存储引擎从mysiam变为了innodb了。 以innodb来讨论: innodb存储引擎一张表,对应两个文件:表名.ibd、表名.frm。 如果分区后,一个分区就单独一个ibd文件,如下图: 将fs_punch_in_log表拆分成4个分区,上图中看到,每个分区就变成一个单独的数据文件了。mysql会使用"#p#p1"来命名数据文件,1是分区的编号。总共4个分区,最大值是4。 分表的原理,实际上类似,一个表对应一个数据文件。分表后,数据分散到多个文件去了。性能就提高了。 分区后的查询语句 语句还是按照原来的使用。但为了提高性能。还是尽量避免跨越多个分区匹配数据。 如下图,由于表是按照id字段分区的。数据分散在多个分区。现在使用user_id作为条件去查询。mysql不知道到底分配在哪个分区。所以要去全部分区扫描,如果每个分区的数据量大,这样就耗时很长了。 分区思路和分区语句 id字段的值范围来分区:在1-2千万分到p0分区,4千万到-6千万p1分区。6千万到8千万p2分区。依此推算下去。这样可以分成很多的分区了。 为了保持线性扩容方便。那么只能使用range范围来算了。 sql如下 CREATE TABLE `fs_punch_in_log` ( `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键自增' , `user_id` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '签到的用户id' , `punch_in_time` int(10) UNSIGNED NULL DEFAULT NULL COMMENT '打卡签到时间戳' , PRIMARY KEY (`id`) ) partition BY RANGE (id) ( PARTITION p1 VALUES LESS THAN (40000000), PARTITION p2 VALUES LESS THAN (80000000), PARTITION p3 VALUES LESS THAN (120000000), PARTITION p4 VALUES LESS THAN MAXVALUE ); 以上语句经过笔者测验,注意点: 按照hash均匀分散。传递给分区的hash()函数的值,必须是一个整数(hash计算整数计算,实现均匀分布)。上面的id字段就是表的主键,满足整数要求。 partition BY RANGE 中的partition BY表示按什么方式分区。RANGE告诉mysql,我使用范围分区。 情况:如果表结构已经定义好了,里面有数据了,怎么进行分区呢?使用alter语句修改即可,经过笔者测验了。 ALTER TABLE `fs_punch_in_log` PARTITION BY RANGE (id) ( PARTITION p1 VALUES LESS THAN (40000000), PARTITION p2 VALUES LESS THAN (80000000), PARTITION p3 VALUES LESS THAN (120000000), PARTITION p4 VALUES LESS THAN MAXVALUE ) 注:由于表里面已经存在数据了,进行重新分区,mysql会把数据按照分区规则重新移动一次,生成新的文件。如果数据量比较大,耗时间比较长。 二、四种分区类型 mysql分区包括四种分区方式:hash分区、按range分区、按key分区、list分区。 四种有点多,实际上,为了好记,把类再缩小点,就两大类方式进行分区:一种是计算hash值、一种是按照范围值。 其实分库分表的时候,也会用到两大类,hash运算分、按值范围分。 1、HASH分区 有常规hash和线性hash两种方式。 常规hash是基于分区个数取模(%)运算。根据余数插入到指定的分区。打算分4个分区,根据id字段来分区。 怎么算出新插入一行数据,需要放到分区1,还是分区4呢? id的值除以4,余下1,这一行数据就分到1分区。 常规hash,可以让数据非常平均的分布每一个分区。比如分为4个取,取余数,余数总是0-3之间的值(总到这几个分区去)。分配打散比较均匀。 但是也是有缺点的:由于分区的规则在创建表的时候已经固定了,数据就已经打散到各个分区。现在如果需要新增分区、减少分区,运算规则变化了,原来已经入库的数据,就需要适应新的运算规则来做迁移。 实际上在分库分表的时候,使用hash方式,也是数据量迁移的问题。不过还好。 针对这个情况,增加了线性hash的方式。 线性HASH(LINEAR HASH)稍微不同点。 实际上线性hash算法,就是我们memcache接触到的那种一致性hash算法。使用虚拟节点的方式,解决了上面hash方式分区时,当新增加分区后,涉及到的数据需要大量迁移的问题。也不是不需要迁移,而是需要迁移的数据量小。 在技术实现上:线性哈希功能使用的一个线性的2的幂(powers-of-two)运算法则,而常规哈希使用的是求哈希函数值的模数。 线性哈希分区和常规哈希分区在语法上的唯一区别在于,在“PARTITION BY”子句中添加“LINEAR”关键字。 两者也有有相同的地方: 都是均匀分布的,预先指定n个分区,然后均匀网几个分区上面分布数据。根据一个字段值取hash值,这样得到的结果是一个均匀分布的值。后面添加新的分区多少需要考虑数据迁移。 常规HASH和线性HASH,因为都是计算整数取余的方式,那么增加和收缩分区后,原来的数据会根据现有的分区数量重新分布。 HASH分区不能删除分区,所以不能使用DROP PARTITION操作进行分区删除操作; 考虑以后迁移数据量少,使用线性hash。 2、按照range范围分区 范围分区,可以自由指定范围。比如指定1-2000是一个分区,2000到5000千又是一个分区。范围完全可以自己定。后面我要添加新的分区,很容易吗? 3、按key分区 类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。 4、按list方式分区 可以把list看成是在range方式的基础上的改进版。list和range本质都是基于范围,自己控制范围。 range是列出范围,比如1-2000范围算一个分区,这样是一个连续的值。 而list分区方式是枚举方式。可以指定在1,5,8,9,20这些值都分在第一个分区。从list单词的字面意思命名暗示就是列表,指定列表中出现的值都分配在第几个分区。 三、如何根据业务选择分区类型 1、何时选择分区,何时选择分表 分表还是比分区更加灵活。在代码中可以自己控制。一般分表会与分库结合起来使用的。在进行分表的时候,顺带连分库方案也一起搞定了。 分表分库,性能和并发能力要比分区要强。分表后,有个麻烦点:自己需要修改代码去不同的表操作数据。 比如用户表分表后,计划分4个表,每个表4千万用户。按照用户编号取模为4。代码很多处要做专门的匹配如下: 每次操作用户资料,先要根据uid算出是哪个表名称。然后再去写sql查询。 当然,是可以使用数据库中间件来做完成分库、分表。应用代码不用修改。大部分中间件是根据他们自己的业务特点定制的,拿来使用,不见得适合自己的业务。所以目前缺少通用的。 如果使用分区的方式。代码不用修改。sql还是按照原来的方式写。mysql内部自动做了匹配了。 非常适合业务刚刚起步的时候,能不能做起来,存活期是多久不知。不用把太多精力花费在分库分表的适应上。 考虑到现在业务才起步,使用分区不失为一种既省事又能提高数据库并发能力的办法。等以后业务发展起来了,数据量过亿了,那个时候经济实力已增强,再做改进方案不迟。 架构是演变出来的,不是设计出来的。适应当前业务的方案,就是好的方案。 过度设计也是一种负担:很多技术,企图一开始就设计出一个多大量的系统,实际上没有那种量,为了显示自己技术牛逼。 总结:访问量不大,但是数据表行数很多。可以使用分区的方式。访问量大,数据量大,可以重构成分表的方式。 这是因为虽然数据量多,但是访问量不大,如果使用分表的话,要修改代码很多地方,弄起来太麻烦了。投入多,产出少就没必要了。 2、如何选择适合自己的分区类型 使用分区和分表一样的思想:尽量让数据均匀分散,这样达到分流、压力减小的效果。如果不能均匀分布,某个分区的操作量特别大,出现单点瓶颈。 虽然4种类型的分区方式。其实总共两大类,按范围分区和按hash运算分区。 range范围分区,适合按照范围来切分数据。比如按时间范围分区。 hash,适合均匀分散数据。使用hash分区,麻烦点是后续增加分区,数据要迁移。有了线性hash分区法,这个迁移量减低了很多。 以用户表为例子,如果要使用分区方案。改使用哪种分区类型呢? 考虑到user_id一般不会设计成自增数字。有人会奇怪,怎么不是自增的,我见过好多用户编号都是自增的! 的确,有自增数字做uid的,不过一般是开源系统为了省事,比如discuz、ecshop等。人家没那么多工作量给你设计用户编号方案。 自增的用户编号,由于是每次加1进行递增的。这规律太明显了,很容易被别有用途的人猜测user_id。再说,别人一看就知道你有多少用户! 有个原则,设计编号的时候,尽量不要让外部知道你的生成规律。比如订单号,如果是逐个加1的订单号,外界可以猜测出你的业务订单总数出来。 说一个自增用户编号的例子。笔者曾经在一家上市互联网公司,有几千万的用户,uid过去是discuz那一套自增的方式。后来不得不改掉user_id的生成方式。笔者当时负责了这个改造方案。 不是自增的数字,会是这种:注册一个是1897996,再注册一个是9689898,外界完全找不到数字的规律。 不是自增的编号,如果使用范围来分区,各个分区的数据做不到均匀分布的。原因如下: 比如说用户编号为1-200000000的用户分配到p1分区,20000000-40000000分配到p2分区,40000000-60000000分配到p3区,这样类推下去。 由于用户编号不是自增,注册分配到的用户编号,可能是1到2千万之间的数字,也可能是4千万到6千万之间的一个数字。如果被分配到4千万到6千万的数字会更多,那么各个分区给到的数据是不均匀的。 故不好使用范围来分区。 比较好的办法是,使用hash取模,user_id%分区数。数据就可以分散均匀到4个分区去了。
原创文章,转载注明出处 一、两种方案分库分表 一般业界,对订单数据的分库分表,笔者了解,有两类思路:按照订单号来切分、按照用户id来切分。 方案一、按照订单号来做hash分散订单数据 把订单号看作是一个字符串,做hash,分散到多个服务器去。 具体到哪个库、哪个表存储数据呢?订单号里面的数字来记录着。 现在的微信红包。它的订单分库分表,是对订单号进行hash计算。不是什么取模、取整数。这样数据是均匀分散的。 然后订单号的末尾3个数,里面是包含了库名称、表名称的。 如果要查询某用户的所有订单呢? 由于是根据订单号来分散数据的。他的订单分散在了多个库、多个表中。 总不能去所有的库,所有的表扫描吧。这样效率很低。 其实按照uid切分订单,一样会遇到查询的问题。比如要查询a订单的信息。分库分表的规则是按照uid,都不知道数据在哪个库,无从查。 后续说明解决思路。 一般使用方案二的比较多,一个用户的所有订单,都在一张表里面,那么做分页展示的时候,就容易。 方案二、按照用户id打散订单数据。 以uid来切分数据,有两种思路: 一种是,某个范围的uid订单到哪些库。0到2千万uid,对应的订单数据到a库、a表。2千万到4千万对应的订单到b库。 为什么这种方案用得比较少呢? 容易出现瓶颈吗。某个范围内的用户,下单量比较多,那么造成这个库的压力特别大。其他库却没什么压力。 第二种是,使用uid取模运算。第二种方案业界用得比较多。 一方面、处理简单,程序上做取模运算就好了。 另一方面、使用取模的方式,数据比较均匀分散到多个库去了。不容易出现单个库性能瓶颈。 但是不好处也有:即要扩容的时候,比较麻烦。就需要迁移数据了。 要扩容的时候,为了减少迁移的数据量,一般扩容是以倍数的形式增加。比如原来是8个库,扩容的时候,就要增加到16个库,再次扩容,就增加到32个库。这样迁移的数据量,就小很多了。这个问题不算很大问题,毕竟一次扩容,可以保证比较长的时间,而且使用倍数增加的方式,已经减少了数据迁移量。 下面笔者,分析一下按照用户id取模的方式分库分表。 按照用户id作为key来切分订单数据,具体如下: 1、 库名称定位:用户id末尾4位 Mod 32。 Mod表示除以一个数后,取余下的数。比如除以32后,余下8,余数就是8。 代码符号是用%表示:15%4=3。 2、表名称定位:(用户id末尾4位 Dev 32) Mod 32。 Dev表示除以一个数,取结果的整数。比如得到结果是25.6,取整就是25。 代码用/来表示:$get_int = floor(15/4)。15除以4,是一个小数3.75,向下取整就是3。一定是向下取整,向上取整就变成了4了。 按照上面的规则:总共可以表示多少张表呢?32个库*每个库32个表=1024张表。如果想表的数量小,就把32改小一些。 上面是用计算机术语来表示, 下面用通俗的话描述。 1、库名称计算 用户id的后4位数,取32的模(取模就是除以这个数后,余多少)。余下的数,是0-31之间。 这样可以表示从0-31之间,总共32个数字。用这个32个数字代表着32个库名称:order_db_0、order_db_2.........................order_db_31 2、表名称计算 最后要存储定到哪个表名里面去呢? 用户id的最后4位数,除以32,取整数。将整数除以32,得到余数,能够表示从0-31之间32个数字,表示表名称。 表名称类似这样:order_tb_1、order_tb_2..........................order_tb_31。一个库里面,总共32个表名称。 比如用户id:19408064,用最后4位数字8064除以32,得到是251.9,取它的整数是251。 接着将251除以32,取余数,余数为27。 为了保持性能,每张表的数据量要控制。单表可以维持在一千万-5千万行的数据。1024*一千万。哇,可以表示很多数据了。 三、思考优点和缺点 优点 订单水平分库分表,为什么要按照用户id来切分呢? 好处:查询指定用户的所有订单,避免了跨库跨表查询。 因为,根据一个用户的id来计算节点,用户的id是规定不变的,那么计算出的值永远是固定的(x库的x表) 那么保存订单的时候,a用户的所有订单,都是在x库x表里面。需要查询a用户所有订单时,就不用进行跨库、跨表去查询了。 缺点 这种方式也不是没有缺点。 缺点在于:数据分散不均匀,某些表的数据量特别大,某些表的数据量很小。因为某些用户下单量多,打个比方,1000-2000这个范围内的用户,下单特别多, 而他们的id根据计算规则,都是分到了x库x表。造成这个表的数据量大,单表的数据量撑到极限后,咋办呢? 总结一下:每种分库分表方案也不是十全十美,都是有利有弊的。目前来说,这种使用用户id来切分订单数据的方案,还是被大部分公司给使用。实际效果还不错。程序员省事,至于数据量暴涨,以后再说呢。毕竟公司业务发展到什么程度,不知道的,项目存活期多久,未来不确定。先扛住再说。 比较好的方案是不是:又能均匀分散、又能避免单表数据量暴涨方便扩容。以前看过一篇文章介绍过使用节点来存储分库分表。笔者暂时没完整的思路。 二、查询需求的考虑 方案一的查询问题 方案一的情况下,由于是按照订单号做分散数据到多个库、多个表。如果需要查询a用户的所有订单,咋办?需要跨库、跨表查询。 这样效率低。不可行。 方案二的查询问题 如果是按照uid来切分订单数据,在实际应用中一些很频繁的查询需求像下面这样: 1、后台、前台,往往是输入一个订单号,查询这个订单的数据。select操作 2、然后修改这个订单的相关状态。update操作。 由于是,按照用户编号将订单数据分散在各个库、各个表中。 那输入订单号,怎么知道去哪个库、哪个表查询呢?不可能所有的库、所有表都查询一遍,效率太低,不可行。 三、解决办法:建立用户id和订单号的索引关系表 无论是根据用户id来切分订单,还是根据订单号切分数据。总不能十全十美的。 写到这里,发现真的没有一种技术方案是十全十美的,看,使用用户id来切分订单,好处是有了,坏处也出来了。 不过没关系,早要有心里承受:不要觉得技术是完美无缺的。针对这种情况,想办法去解决办法。 思路:既然是根据订单号分散订单数据,如果需要知道某个用户所有的订单。只要我能知道了a用户的所有的订单号,那么就可以根据订单号定位到表名称了。 思路:既然是根据用户id来分散订单数据的。那么只要知道了这个订单号是谁的(得到了用户id),就能知道去哪个库、哪个表查询数据了。 那怎么知道是谁的呢?建立一个索引关系表,暂且叫做订单用户关系索引表order_user_idx。咱们命名为了保持维护性,还是一看能够知道是干嘛用的。 存储的数据包括两项:订单号、用户编号。 这样输入订单号,可以去查询索引关系表,获取到用户编号。 得到了用户编号,问题解决了。订单信息是根据用户编号分库分表的,可以直接定位到x库x表了。 当创建订单的时候,就要把关系插入到表里面去了。保存关系记录时,为了减低用户等待时间,不需要实时,做成异步。加入到消息队列中去操作。 订单用户索引关系表的性能优化 考虑到,一个用户的下的订单可能是几十个,也可能是几百个,随着时间的推移,会越来越多。这个索引关系表,也不能使用单表存储。 所以对这个订单用户关系索引表,也要进行分库分表:直接根据订单号取模进行分库分表。是不是感觉挺麻烦了。确实麻烦。不过能解决问题就好。暂时没想到其他办法了。 一个订单,在创建的时候,就已经分配好给指定用户了。只是一个关系对应,以后也不会变化。 根据这个特点。订单用户索引关系表,其实可以放到内存中缓存起来应对查询需求(数据库那张索引关系表也要有,数据要持久化)。 平时查询的时候,走内存缓存查询。如果查询不到,再走数据库查询一下关系。这样速度就很快了。 结语:水平分表,其实折腾起来工作量挺大的,切分了后,出现新的问题,代码查询又得改,要提供其他解决办法。所以经常看到别人说,能不水平分表,尽量不要分,业务没达到瓶颈,先用硬件扛住,后面再考虑水平切分数据。看银行、联通这些有钱的企业,使用性能强劲的oracle搭配小型机服务器,单表的数据量达到十多亿。小型机是专门定制的,几十万一台。性能很强。分库分表是很耗费时间、当你交易量做到上亿规模的时候,那时,公司的实力应该可以了,经济方面有足够的实力聘请经验丰富的技术来重构。 思考一、b2b平台的订单分卖家和买家的时候,选择什么字段来分库分表呢? 上面讨论的情况是,b2c平台。订单的卖家就一个,就是平台自己。 b2b平台,上面支持开店,买家和卖家都要能够登陆看到自己的订单。 先来看看,分表使用买家id分库分表和根据卖家id分库分表,两种办法出现的问题 如果按买家id来分库分表。有卖家的商品,会有n个用户购买,他所有的订单,会分散到多个库多个表中去了,卖家查询自己的所有订单,跨库、跨表扫描,性能低下。 如果按卖家id分库分表。买家会在n个店铺下单。订单就会分散在多个库、多个表中。买家查询自己所有订单,同样要去所有的库、所有的表搜索,性能低下。 所以,无论是按照买家id切分订单表,还是按照卖家id切分订单表。两边都不讨好。 淘宝的做法是拆分买家库和卖家库,也就是两个库:买家库、卖家库。 买家库,按照用户的id来分库分表。卖家库,按照卖家的id来分库分表。 实际上是通过数据冗余解决的:一个订单,在买家库里面有,在卖家库里面也存储了一份。下订单的时候,要写两份数据。先把订单写入买家库里面去,然后通过消息中间件来同步订单数据到卖家库里面去。 买家库的订单a修改了后,要发异步消息,通知到卖家库去,更改状态。 思考二:那可以按订单号来分库分表吗? 这样分库分表的话,用户有10个订单,订单不见得都在一个库、一个表里面。查询a用户的所有订单,就会变得麻烦了。尤其是要进行分页展示,分散在不同的表,甚至不同的数据库服务器,也比较耗费性能。 那么订单号里面,最好是要有分库分表信息。淘宝的是在订单号里面添加了卖家id末2位、买家id末2位。这样的好处是干嘛呢?直接定位到具体的库、具体的表去了? 怎么根据这个呢。因为分库、分表的规则,买家库是按照卖家id末尾2位数分,卖家库是按照卖家id末尾两位分。 所以,只要从订单号里面拿到了这些数字信息,就知道在哪个库,哪个表了。 这种办法,与微信的红包订单号是类似的,末尾三位数包含了库信息、表信息。 按照这样,其实就没必要使用订单号来计算了? 如果是按照用户id的后4位数取模分散订单数据。那么订单号的生成,可以在后面加上用户id的后4位数。 那么,虽然是按照用户id来对订单表分库分表的。其实可以直接根据订单号,知道这个订单在哪个库哪个表了。 如果是b2b系统,涉及到卖家和买家。那么可以把卖家和买家的id后面4位都加进去。不过是不是订单号太长了? 思考三、按照订单的时间来分表如何? 一月一张表。一年一张表。用户的所有订单,会分散在不同的库、不同的表中。 按照时间分,在切分订单数据的时候,业界用得比较少。 出现如下两个问题: 1、如果需要分页查询某个用户的所有订单数据,就会出现跨库、跨表查询。效率低。 可以做折中:限制只能查一个范围内的订单,比如一次只能查询,一年以内或者一个月以内的订单。 2、某个时间集中写入数据,出现瓶颈。如一个月一张表。这个月的订单量暴涨呢。那么写入新的订单数据都会操作这张表。造成性能低下。影响整个业务系统交易。 真正好的分表方案,尽量将写数据分散到多个表去,达到分流效果,系统的并发能力就提高了。
本文为原创文章,转载希望注明出处。 抢购业务数据库需要考虑的点如下: 一、超卖现象 场景如下: 库存数是5。现在3个用户来购买,a用户购买2个,b用户购买3个,c用户购买1个。合起来就是准备购买6个。 如果三个用户是同时并发购买,会出现怎样的情况呢? 每个用户进行减库存的时候,语句类似于: update goods set amount=amount-购买数量 where goods_id=xxx。 mysql会锁定这一行数据(使用innodb存储引擎),数据库加的是排他锁。根据排他锁的特点:其他线程不能读、不能写此行数据。 排他锁情况下,那么其他用户就是等待状态了。 1、A用户执行update的时候,锁定库存数据。update执行完毕后,减去了2个后,mysql自动释放锁。 2、b用户执行,减去了3个。此时,已经卖掉5个库存了。库存数为0了。 3、但是c用户接着执行,Update goods set amount=amount-1 where goods_id=xxx 结果库存数量变成-1了。 思考:把库存数量字段的类型,设计成正数类型,不允许出现负数,会怎么样呢? 测验结果:数据库会直接报错。通不过。 解决办法:只有库存数量,大于或等于购买数量的时候,才能去减库存。其他情况,提示信息,库存不足。 sql语句如下: update goods set amount=amount-购买数量 where goods_id=xxx AND amount>=购买数量 这样,轮到c执行的时候,由于使用了amount>=购买数量做限制条件,update语句返回的受影响的行数是0,意味着执行失败了。直接提示,库存不足。 二、并发抢购造成的速度慢问题 1、实现方式对比:悲观锁与乐观锁 第一种问题中描述的超卖现象,其实是并发抢购时出现的情况。用到的是数据库内带的加排他锁方式,阻止了其他线程读取、访问数据,这要等待操作完毕后其他线程才能操作,这种方式是悲观锁方式。这样会等待下去。 使用数据库的悲观锁,是避免了数据并发更新,但是,加锁毕竟是很耗服务器资源的,用户要等待下去。所以并不能达到好的性能和高并发。 业界使用乐观锁的办法来解决:使用数据库的乐观锁是通用解决办法。通用锁实现了版本控制。不会进行排斥掉。减少资源的消耗。 乐观锁是相对悲观锁而言的,使用的是更加宽松的锁定方式。 乐观锁,通俗说就是:修改数据的时候,不给数据加锁。 既然不加锁,其他线程也是可以访问、修改数据。但是,修改的时候会获取一个版本号,只有版本号符合了,才算更新成功。 不成功的,都算抢购失败。 2、乐观锁的具体实现方式 乐观锁的机制与代码版本库svn很相似,这种方式,叫做多版本记录方式。 如果在我提交代码之前用本地代码的版本号与服务器做对比,如果本地版本号小于服务器上最新版本号,则提交失败,产生冲突代码,让用户决定选择哪个版本继续使用。 逻辑描述: if(之前读取到行的版本号+1=数据库此行现在的版本号+1){ //符合预期,数据库的数据没有给其他用户修改掉。可以直接写入数据了 }else{ //数据已经被修改了。所以当前的版本已经落后了。不能进行更新 } 例子: 给表goods加一个版本字段version,用来记录行数据值的版本号。 版本号version字段,设计成一个正整数,比如是时间戳。每次修改后,要将version字段的值加1: 1496916794、1496916795、1496916796、1496916797、1496916798................. 读数据的时候,顺便将版本号的值读取出来。update时,做版本号对比,如下: 1、先读取这个商品的信息,顺便将版本号读取出来 select amount,version from t_goods where goods_id=8899; 2、更新数据 update goods set amount=amount-2,version=version+1 where goods_id=8899 and version=#{version} and amount>=2; sql解释: #{version}是之前select读取到的版本号,填入进去,意思是只能修改这样的。 修改的时候,限制条件-必须版本号等于原来的版本号才能去修改。否则不能修改。更新成功的同时,版本号要加1。 优点:使用数据库的乐观锁是通用解决办法。通用锁实现了版本控制。线程之间同时操作,不加锁,线程不用等待了。减少了数据库资源的消耗。 缺点:会增加cpu的计算开销。不过值得这样做,由于没有加锁进行阻塞,用户不用等待结果,很快能等到执行结果了,用户体验更好。抢购的并发数其实提高了。 三、减库存和下单保持在一个事务内 如果不在一个事务内,可能出现两种现象: 1、订单入库失败、减库存成功。发现订单入库失败,减库存就不要继续进行下去了。 2、订单入库成功、减库存失败。实际下了20个订单,库存却没有减。数据不一致了。 四、虚拟库存和真实库存两套方案 考虑几种情况: 1、有些人下单完后,最终并不会去付款。如果一下单就马上减库存,很多人下单,最终并不会去付款,可能导致库存数最后为0,别的用户无法下单了。而实际中仓库中却有库存在,这样库存数据是不准确的。 2、什么时候减库存? 是下单完成减库存、还是付款完后减库存呢? 付款后,才减库存,可能出现的现象:用户下完单,接着去付款,结果库存不够了。这样用户体验很不好。 下完单就减库存,能够保证用户下单只要付款,就一定能买到这个商品。用户体验较好。 针对一些人下单后,不付款,占着库存资源,其他人无法下单。这个问题好解决,给付款设置一个有效期限,比如30分钟。超过这个时间,库存就释放掉了。 具体技术实现办法:下单后,马上减去库存。另外设置一个定时脚本,扫描超过30分未支付的订单,把订单中的商品数量返回到库存中去。 为什么使用虚拟库存和真实库存两套方案? 假设库存数是50,a订单购买了5个件商品,支付完毕,库存数减去5,库存数变成了45件。由于还没有发货,实际库存中还有50件商品。这样会出现混淆了。 使用两套库存记录方案是有必要的! 下单-操作虚拟库存数 商品发货出库-操作真实库存数 真实库存数,记录下仓库中这件商品真有多少件。真实库存,其实非常方便内部人员查看,它只有商品出库,这个库存才减。 虚拟库存,用来应对商品购买的。表明,还有多少数量可以给用户去购买。并不表示仓库中的库存数。 五、频繁读库存的压力 因为,每次点击,都要读取库存,判断:有没有库存。如果读库存走的是数据库判断,很多人来抢购的情况下,数据库的压力会很大。 假设是1万个用户同时访问抢购页面。数据库接受的访问次数是1万个并发。 用户还要进行刷新页面操作,由于每次刷新都会走数据库判断库存。数量会更大。数据库的压力就更大了。 所以最好是,把库存总数,缓存在redis中去。 内存中缓存的库存数量,只用来做读判断。这样压力扛住了。而更改数据库的库存总数了,程序马上要把库存总数,同步到缓存中去。 系统抗压力问题 一、如何限流 二、如何防止恶意刷数据。 防止的就是写代码去频繁请求,为了识别是机器还是人工。加友好一点的验证码。
一、如何监控发生了主从延迟? 在从库机器上,执行show slave status,查看Seconds_Behind_Master值,代表主从同步从库落后主库的时间,单位为秒,若同从同步无延迟,这个值为0。 Mysql主从延迟一个重要的原因之一是:mysql是以单线程串行执行。 主从复制数据时,在从服务器上的mysql,是一个线程在同步数据。 串行的方式,它是指,执行一个后才继续执行下一个。如果一个卡住了,要等待时间,才会继续下一个。串行与并行是相反的。 二、同步延迟发生的场景 当主库的TPS并发较高时,产生的DDL(修改类的sql语句)数量,超过了slave机器sql线程所能承受的能力,那么延时就会产生了。 主库写binlog日志到文件的时候,是顺序写入到磁盘,顺序写入速度是很快,避免了磁盘随机寻址。 从库的同步线程(Slave_IO_Running),将binlog在slave上执行的时候,实际上是随机的,速度肯定要慢点。 从库的同步线程(Slave_IO_Running)只有应该线程在操作,整个mysql实例就一个这样的线程,那么,如果mysql有n个库的数据需要同步,全部要这个线程来处理。人手不够啊(mysql-5.6.3) 三、解决思路 如何避免或解决主从延迟?可以用来解决的办法,有如下的: 从库优化Mysql参数。比如增大innodb_buffer_pool_size,让更多操作在Mysql内存中完成,减少磁盘操作。 从库使用高性能主机。包括cpu强悍、内存加大。避免使用虚拟云主机,使用物理主机,这样提升了i/o方面性。 从库使用SSD磁盘。机械硬盘是靠磁头旋转到指定位置来读数据、写数据。转来转去的,我们叫做i/o。磁盘i/o存在速度瓶颈。固态硬盘是一个电子设备,电子设备不需要机械旋转,读写固态硬盘上任意位置的数据,速度都是一样的。 业务代码的妥协。将实时性要求高的某些操作,使用主库做读操作。比如我写了数据到主库了,需要马上展示数据,不要到从库去读数据,因为从库可能还没同步过去呢。直接从主库读数据,保证是最新的数据展示。 从库的线程改为多个同步线程同步数据。mysql-5.6.3为了解决这个问题,从服务器上,每一个库开一个线程来同步。 网络优化。网络堵塞,也会导致同步延迟。跨机房的数据库同步,会存在同步延迟。保证主从在同一个机房里面去。 附:mysql主从复制的三种格式数据 第一种是,statement格式。也就是记录下原来执行的sql语句。 这是最早的一种方式,后来发现也不是很完美,在复制的过程中会存在一些问题。 举例:由于sql语句中使用了某些mysql函数,而这个mysql函数是特定版本才有的,其他版本是没有这个函数,放到slave端运行,假如slave的mysql版本不一样,就可能执行出现问题。 使用了特定的功能,如果sql中使用了last_insert_id()函数,当同样的sql语句复制到slave端执行的时候,last_insert_id()所得到的结果是不同的。 上面两种情况导致了:复制过程中,slave端的结果没有完全与master端一致了。 而基于row格式的就不会。于是发明了row格式的。 第二种,基于row格式的。会记录下每一行修改前和修改后的值。binlog中存储的就是被修改行的修改前和修改后的值,直接拿到结果即可。重做。 基于row的格式有个缺点:涉及到ddl操作,比如alter table,加一个字段,那么意味着整个表的行都要进行修改。那么binlog中记录的是整个表中行的数据,造成binlog中的数据量很大。 于是,又发明了基于statement格式和基于row格式的综合版,叫做mixed 第三种:mixed 遇到ddl表变更操作,则使用statement格式,遇到delete或update格式,则使用row格式。 三种格式的发展过程总结: 一直使用statement格式,到了5.1.5版本才支持row格式。后来存储过程的出现,又带来了新的问题。存储过程中调用一些函数,在slave端运行结果会不同。所以5.1.8版本开始支持mixed格式。上面所有策略的做法目标是,让master与slave的数据保持一致。从这个角度出发。
以前也没有深刻意识到它的重要性。直到后来,去接手一些遗留系统,那种混乱,寻找代码和代码文件多么费力。系统经过了很多人手,人员调岗,人员离职。每个人都有自己的风格,折腾一下,就闪了。丢下一个千疮百孔的系统。 人的眼睛是相信现实的东西,没有经历过那种坑,就无法理解。所以当我们怎么说要规划好目录结构,要好的命名方式,一些技术都不以为然。包括我以前也一样,我以前能够接受好的东西,但是我没理解,就不会有深刻发自自觉性去这样做。内心只是想:这个问题不大吧。 因为现实案例最让人印象深刻。总结一下遇到的问题 上面两个是比较坑人的目录结构,接手的人员太多了,加的目录也重复。 ------------------------------------------------------- 后面这个系统使用mvc框架,相对好,但是也出现不清晰的地方,暂时系统的功能不是很多,所以目录很少,看起来很清晰。但是,随着时间的推移,加的功能越来越多,才会看起来混乱。 比如:上面这个curl目录就不太好。不应该放到根目录下的。这样会给人误导,使得接手的技术,某天增加新功能,也会新增加一个文件夹放到根目录。那么,后面功能加的多了,大家都会这样子做。 那个时候才去思考解决办法。我一直在想,怎样才能让项目在可控的范围内呢? 出现问题的原因是什么虽然好的风格在业界都是有共识的。但为什么造成那么混乱的系统。原因是什么呢?有人说原因是:各个技术人员水平不一样。所以这个问题很难解决。束手无策。也有人说,原因是,系统经受的人太多了。这个技术维护一段时间,然后另一个技术维护一段时间。这样就造成了千疮百孔。除非公司保证技术人员的稳定性,但这个很难。所以这个问题也很难解决。仔细,深入去思考。我在想,这些都只是表明上的原因。难道因为这样就没有解决办法吗?如何解决,很多人也有自己的想法,归纳起来有:1、要求系统维护人员遵循风格统一。写好规范文档。让大家按照规范来做。提交修改代码,需要进行代码审核,可是这需要成本。一个中小型公司,技术人员有限,都是忙着开发功能去了,抽出时间来审核代码(哪怕是大体审核),也是需要时间投入的。所以现实情况是,进行代码审核,在很多公司往往做不到。2、成员培训。归根结底还是要放到人的自觉性上去。所以要经常给大家灌输好的风格意识,进行培训。这个方案其实有效果。具体得依赖于团队技术领导本人的个人影响力,能不能以身作则,用代码示范影响到成员。所以还是依赖于某一个经验丰富且有影响力的人,一旦缺了这个人,就会导致一盘散沙。尤其是当这个人如果离职怎么办呢? 上面办法都是正确的。但是会有阻碍和困难。我在想,能不能做到不依赖于某个特别的人而让团队成员自觉做到呢? 并且,可不可以,忽略掉团队成员技术水平的差异性呢? 根据心理学,就是示范效果。虽然大家水平不一样,但是看到一个好的示范,会不自觉的遵守。另外就算其不遵守,也会感到无法使用。自己都会感到羞耻。比如mvc的框架,定好了controllers,views,models,后面刚入门的人,都能按照目录结构去加代码。具体要做的是:对系统的代码和目录预先设计好。规划好目录结构。什么目录放哪种类型的文件的。以前听过一句话说:架构师的目标就是让程序员变得更加不用很费很多精力,按照预先规划设计好的方式去做就可以。现在看来这个非常有道理。心理学:如果已经有的东西是混乱的,那么也会觉得痛苦,干脆凑合一下算了做系统前,把系统的规划好 不规划好,后面接手的人就会不知道标准。 安装目录包括一下几个文件夹: 安装目录/public/ 安装目录/app/ 安装目录/cache/ 说明: 1、public目录下是对外开放的目录。也就就是nginx配置指向的目录。目录里面的子目录接下来在详细介绍 2、app,所有源码存位置。 3、cache,模版编译缓存存放到这个里面。为什么要放到这个里面呢? public目录下的子目录 public/index.php public/js/ public/images/ public/css/ app目录下的子目录 app/controllers 控制器文件 app/models 模型文件存放目录 app/views 模版文件目录 app/config/ 所有的配置文件存放 app/library/ 所有的公共库文件。文件夹名称也可以命名为lib,就是library单词的简写。 app/logs/ 日志文件目录。php代码记录的日志信息 app/api/ 里面放一个sdks文件夹。所有的sdk放入里面去。每个sdk一个文件夹。比如sdks/pmsSdk/ sdks/memberSdk/ app/tools/ 一些工具放入到里面去。比如定时脚本,则放入task文件夹中去。app/tools/tasks
一、session_id()对原来session文件和里面的数据,是怎么处理的? 测验办法:<?php$sid = md5("aaad");session_id($sid);session_start();var_dump(session_id());$_SESSION['ddd'] = 123;?> 是新创建一个session文件。那么原来PHPSESSID对应的服务器上的数据就不会用到了,因为新创建了一个文件。 二、研究上面这个有什么用途? 业务中需要 问题的背景: * um.mama.cn/passport 和passport.mama.cn都是访问新版本passport系统。 * * 当app跳转到网页时,app先在打开的webview中,请求passport的一个接口。 * app>>>>>http://passport.mama.cn/appapi/setWebViewSession?app_code=xxx * passport验证成功后,会设置webview为登录状态(即把登录状态存储到memcache) * * 但,网页访问的域名却是um.mama.cn/passport,两个域名不同,PHPSESSID就不同。之前设置的登录状态无法同步到um.mama.cn域名 * * 目前思路是:登录状态是放在共享位置-memcache中,登录状态要想共享,让两个域名的PHPSESSID保持一样即可。同样的sid,可以去memcache查询数据。 * * 具体做法是: * app请求接口时,设置登录状态后,顺便将passport.mama.cn的sid备份到一个让passport.mama.cn也能读取的公共域名下:mama.cn * 进入um.mama.cn时候,从mama.cn获取备份的sid,于是将um.mama.cn的sid重置 代码如下: <?php if(isset($_COOKIE['app_passport_login_sid']) && $_COOKIE['app_passport_login_sid']){ $sid = trim($_COOKIE['app_passport_login_sid']); session_id($sid); setcookie(session_name(), $sid, time()+86400, '/'); session_start(); //这个cookie用完毕后删除掉,避免影响 setcookie('app_passport_login_sid', $sid, time()-86400, '/','.mama.cn');} ?> 我想知道session_id()重置为指定的sid,有以下疑惑,解决这些疑惑以便评估对业务的影响: 重置为一个sid,是新创建一个session文件?还是把原来的session文件重命名即可呢? 如果是新创建一个新的session文件,那么:php会对原来的磁盘上的session文件怎么处理呢? 通过上面的试验,答案为: 1、只是新创建一个session文件。而且是一个空文件。以前文件的数据并不会带到新文件中 2、原来的session文件并不会删除掉。保留在磁盘上。估计是垃圾回收机制的时候会自动删除? 三、顺便研究session_regenerate_id()对原来的session文件和数据的处理方式 只是将原来的文件名称重命名为一个新的么。这样数据还是在的。session_regenerate_id() 在不修改当前会话中数据的前提下使用新的 ID 替换原有会话 ID。 delete_old_session 是否删除原 ID 所关联的会话存储文件。这个看介绍:拷贝一份原来的session数据文件,然后重命名为一个新的sid名称比如,session_sid1 新建了一个文件 session_sid2原来的数据文件会带到新的session文件中去的。测验办法:session_start();session_regenerate_id();var_dump(session_id());
$log_file_name = 'D:/static/develop/kuai_zhi/acagrid.com/public/Logs/'.date('Ym').'/'.date('d').'_error.log'; //$log_file_name = 'D:/static/develop/kuai_zhi/acagrid.com/public/Logs/201701/19_error.log'; if(!file_exists($log_file_name)) return; $handle = fopen($log_file_name,'rb'); if (FALSE === $handle) { exit("Failed to open stream to URL"); } // $stream = fread($handle, $length);//从文件当前指针位置,往后读取n个字节长度 //重置文件指针的位置。指定指针的位置,指针位置修改后。读取文件,后面是从这个位置开始读取了 //fseek($handle,105); //fgets表示每次读取文件的一行 $error_log_array = []; while( ($line = fgets($handle) ) !==false){ //每次读取一行 //匹配出现[1],tp日志中用这种表示致命错误类型 if(preg_match("/\[1\]/", $line)){ $error_log_array[] = $line; } } fclose($handle); 需要注意的几个点:1、如果是使用fwrite,注意避免将原来文件的内容清空掉了。关键是fopen的打开方式。r或者w。如果使用追加方式则是a标记。2、fopen的时候,注意判断是否成功打开文件。避免使用feof的时候进入死循环。因为这个函数,当传入进去不是指针,则这个函数永远会返回falsefeof的本意是:判断是否为文件结尾。如果是结尾,则返回true。不是结尾返回false。如果恰好传入非法的指针,那么永远不是文件结尾了,一直返回false。 feof()函数,当传入进去的不是指针类型的时候,使用如下判断会出现死循环 while(!feof($fp)){ } 3、fread和fgets。读取文件一行一行读取,则使用fgets。不是按照行读取,则使用fread()读取。 要注意这一点细节:如果没有更多的内容,则返回false,也就是两种情况,如果里面的内容为空。也会返回false。当读取到文件的结尾的时候,这两个函数也是返回false(难怪我们使用feof()使得我们不会发现这个细节,因为这个函数已经帮助我们判断文件结尾了) 4、使用追加方式(即a标记)打开文件,要注意,这种方式下,无法读取文件内容的,只能往里面写入文件。所以针对这个句柄进行fread()是会得到false的 总结是,如果只是仅仅读取文件的内容,就只用读的方式打开,如果是写入新内容进去,则用a的方式打开 现在明白,为什么要分多种模式进行区分了。以前觉得没有用。现在看来,打开的文件方式决定了,你能够针对文件做什么(添加新内容还是读取内容。)
Redis和Memcache对比及选择 http://www.cnblogs.com/EE-NovRain/p/3268476.html 在选择内存数据库的时候到底什么时候选择redis,什么时候选择memcache,然后就查到下面对应的资料,是来自redis作者的说法(stackoverflow上面)。 You should not care too much about performances. Redis is faster per core with small values, but memcached is able to use multiple cores with a single executable and TCP port without help from the client. Also memcached is faster with big values in the order of 100k. Redis recently improved a lot about big values (unstable branch) but still memcached is faster in this use case. The point here is: nor one or the other will likely going to be your bottleneck for the query-per-second they can deliver. You should care about memory usage. For simple key-value pairs memcached is more memory efficient. If you use Redis hashes, Redis is more memory efficient. Depends on the use case. You should care about persistence and replication, two features only available in Redis. Even if your goal is to build a cache it helps that after an upgrade or a reboot your data are still there. You should care about the kind of operations you need. In Redis there are a lot of complex operations, even just considering the caching use case, you often can do a lot more in a single operation, without requiring data to be processed client side (a lot of I/O is sometimes needed). This operations are often as fast as plain GET and SET. So if you don’t need just GEt/SET but more complex things Redis can help a lot (think at timeline caching). 有网友翻译如下[1]: 没有必要过多的关注性能。由于Redis只使用单核,而Memcached可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。说了这么多,结论是,无论你使用哪一个,每秒处理请求的次数都不会成为瓶颈。 你需要关注内存使用率。对于key-value这样简单的数据储存,memcache的内存使用率更高。如果采用hash结构,redis的内存使用率会更高。当然,这些都依赖于具体的应用场景。 你需要关注关注数据持久化和主从复制时,只有redis拥有这两个特性。如果你的目标是构建一个缓存在升级或者重启后之前的数据不会丢失的话,那也只能选择redis。 你应该关心你需要的操作。redis支持很多复杂的操作,甚至只考虑内存的使用情况,在一个单一操作里你常常可以做很多,而不需要将数据读取到客户端中(这样会需要很多的IO操作)。这些复杂的操作基本上和纯GET和POST操作一样快,所以你不只是需要GET/SET而是更多的操作时,redis会起很大的作用。 对于两者的选择还是要看具体的应用场景,如果需要缓存的数据只是key-value这样简单的结构时,我在项目里还是采用memcache,它也足够的稳定可靠。如果涉及到存储,排序等一系列复杂的操作时,毫无疑问选择redis。 关于redis和memcache的不同,下面罗列了一些相关说法,供记录: redis和memecache的不同在于[2]: 1、存储方式: memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小 redis有部份存在硬盘上,这样能保证数据的持久性,支持数据的持久化(笔者注:有快照和AOF日志两种持久化方式,在实际应用的时候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。 2、数据支持类型: redis在数据支持上要比memecache多的多。 3、使用底层模型不同: 新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。 4、运行环境不同: redis目前官方只支持Linux 上去行,从而省去了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其写了补丁。但是没有放到主干上 个人总结一下,有持久化需求或者对数据结构和处理有高级要求的应用,选择redis,其他简单的key/value存储,选择memcache。
//gb2312的话preg_match_all("/[".chr(0xa1)."-".chr(0xff)."]+/", $str, $chinese);echo implode("", $chinese[0]);//utf-8的话preg_match_all("/[\x{4e00}-\x{9fa5}]+/u", $str, $chinese);
做统计的时候,null是不计算在count以内的。所以字段的值最好不要设置为null。 比如:select count(user_id) as beyond_num from fs_users_added where credits<410 && user_id!=75语句,就统计不到null的数据行。 我需要统计出多少个用户的学分比这个低。计算排名。结果由于credits值有null的情况,造成了数据统计不准确。明明是90个用户超过,结果算出来是54个用户。 解决办法是:创建字段的时候,设置为not null。或者一个默认值0 如果允许为null,没有插入值的情况下,默认被mysql给字段一个默认值是null,此时即便是后续使用如下语句修改掉字段的默认值: ALTER TABLE `table_name`MODIFY COLUMN `credits` float(10,2) UNSIGNED NULL DEFAULT 0 COMMENT '用户学分' AFTER `user_id`;原来存储进去的null值也不会改变。于是造成了麻烦。 此时的解决办法是:使用如下语句,对值为null的行都设置成一个默认值0update table_name set credits=0 where credits is null null与空值的区别 1、null值就是null,空值是'',两个引号(单引号双引号都可以吗?) 2、null值是会占用空间的。空值是不占用空间的。 3、B树索引时不会存储NULL值的(也恰好解释了,使用语句count统计的的时候不会计算在内)。MySQL字段尽量避免NULL,应该指定列为NOT NULL。然后给字段设置一个默认值0或者空''。
一、每个用户都有自己的家目录 访问方式是:~/.ssh/id_rsa.pub 使用~就是表示家目录。 具体家目录在哪里,在用户密码配置文件中:/etc/passwd中。第6列的值就是。 可以使用~访问家目录。也可以直接输入绝对路径来访问:/home/git/。 每个用户都有一个自己信任列表文件,配置在:~/.ssh/authorized_keys 里面放的就是每个用户信任哪些公钥。 a机器》》通过ssh连接》b机器的linux用户。 使用某个用户连接到b机器,那么,就要把公钥加到b机器上,此用户的信任列表(~/.ssh/authorized_keys )中去。记住是此用户的信任列表中去。 二、信任列表中的公钥是一行一个公钥 ~/.ssh/authorized_keys文件中,公钥之间,必须要有换行,一行一个公钥,没有换行,会当成一个公钥。 如下方式是错误的: 思考,怎么对每一个公钥值添加注释呢。比如想知道这个公钥到底是给谁用户。不然这么长,以后要删除的时候,怎么确定要删除哪个公钥。只能靠注释了。 此文件中空行和以'#'开头的行将被当作注释忽略。 三、远程机器目录和文件的权限要设置对 之所以没生效,要保证,是因为权限设置不对。 配置用户的公钥登陆时,配置完authorized_keys居然一直不生效,于是google之,发现原来是因为.ssh目录和下面文件的权限问题导致的,因为目录的权限已经超过了sshd的要求权限。如果希望ssh公钥生效需满足至少下面两个条件: 1).ssh目录的权限必须是700 。目录要可以读写执行。其他用户都没有任何权限,因为这个目录是这个用户专有使用的。 2) .ssh/authorized_keys文件权限必须是600。思考:只有自己才可以读写(不需要执行),所以第一个值是6。其他用户都没任何权限,所以是00 整个ssh软件已经限制死了,必须是上面这样的权限才行,多一点都不能通过。比如设置authorized_keys文件的权限是660,那也通不过。 四、公钥的生成方式 ssh-keygen -t rsa -C “注释说明,一般是填写邮箱” 最后得到了两个文件,在~/.ssh/目录里:id_rsa和id_rsa.pub。 私钥文件是:~/.ssh/id_rsa文件中。公钥文件是,id_rsa.pub。 看到网上,有人直接使用如下命令添加公钥到信任文件列表中去: cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys 加入进去的公钥,会自动换行? 实际上就是一行一条数据。加到末尾,其实就是新的一行? 五、如何进行ssh登陆 ssh 用户名@ip 注:这个用户名,指的是远程机器上的linux用户名。表示说,用这个用户名,去登陆到远程机器上去。假设这个用户名是为git_remote。 1、既然是使用这个用户名去登陆远程机器。那么使用的就是远程机器上,git_remote用户的信任公钥文件。 远程机器上这个文件,位置在“git_remote用户的家目录/.ssh/authorized_keys"。 2、要把客户机上,当前linux账号的公钥值,添加到远程机器“git_remote用户家目录/.ssh/authorized_keys"中去。这样才能信任通过验证。 怎么确定当前linux用户,假设是使用git用户在执行ssh命令,那么就是git用户。 客户机,当前linux的公钥文件,在"git用户的家目录/.ssh/id_dsa.pub"文件中。
从网上看到的,还不错。 来源: https://github.com/jobbole/awesome-programming-books 计算机系统与网络 《图灵的秘密:他的生平、思想及论文解读》 《计算机系统概论》 《深入理解Linux内核》 《深入Linux内核架构》 《TCP/IP详解 卷1:协议》 《Linux系统编程(第2版)》 《Linux内核设计与实现(第3版)》 《深入理解计算机系统(原书第2版)》 《计算机程序的构造和解释(原书第2版)》 《编码:隐匿在计算机软硬件背后的语言》 《性能之颠:洞悉系统、企业与云计算》 《UNIX网络编程 卷1:套接字联网API(第3版)》 《UNIX网络编程 卷2:进程间通信》 《Windows核心编程(第5版)》 《WireShark网络分析就这么简单》 《WireShark网络分析的艺术》 编程通用 《编程原本》 《代码大全》 《UNIX编程艺术》 《代码整洁之道》 《编程珠玑(第2版)》 《编程珠玑(续)》 《软件调试的艺术》 《修改代码的艺术》 《编程语言实现模式》 《编写可读代码的艺术》 《解析极限编程:拥抱变化》 《精通正则表达式(第3版)》 《编译原理(第2版)》龙书 《重构:改善既有代码的设计》 《七周七语言:理解多种编程范型》 《调试九法:软硬件错误的排查之道》 《程序设计语言:实践之路(第3版)》 《计算的本质:深入剖析程序和计算机》 《设计模式 : 可复用面向对象软件的基础》 算法与数据结构 《算法(第4版)》 《算法导论(原书第2版)》 《Python算法教程》 《算法设计与分析基础(第3版)》 《学习 JavaScript 数据结构与算法》 《数据结构与算法分析 : C++描述(第4版)》 《数据结构与算法分析 : C语言描述(第2版)》 《数据结构与算法分析 : Java语言描述(第2版)》 职业修炼与规划 《大教堂与集市》 《卓有成效的程序员》 《程序员的职业素养》 《程序员修炼之道:从小工到专家》 《软件开发者路线图:从学徒到高手》 《我编程,我快乐: 程序员职业规划之道》 《程序员的思维修炼:开发认知潜能的九堂课》 《高效程序员的45个习惯:敏捷开发修炼之道(修订版)》 大师访谈 《编程大师智慧》 《编程大师访谈录》 《编程人生 : 15位软件先驱访谈录》 《奇思妙想 : 15位计算机天才及其重大发现》 《图灵和ACM图灵奖》 架构/性能 《微服务设计》 《大数据日知录》 《企业应用架构模式》 《Web性能权威指南》 《SRE:Google运维解密》 《发布!软件的设计与部署》 《高扩展性网站的 50 条原则》 《大型网站技术架构:核心原理与案例分析》 《恰如其分的软件架构:风险驱动的设计方法》 《软件系统架构:使用视点和视角与利益相关者合作(第2版)》 Web前端 《高性能 JavaScript》 《锋利的 jQuery(第2版)》 《JavaScript 忍者秘籍》(感谢@joker-danta 补充推荐) 《编写可维护的 JavaScript》 《你不知道的 JavaScript(上)》 《JavaScript 权威指南(第6版)》 《JavaScript 语言精粹(修订版)》 《JavaScript DOM编程艺术 (第2版)》 《JavaScript 高级程序设计(第3版)》 《JavaScript 异步编程:设计快速响应的网络应用》 《Effective JavaScript:编写高质量JavaScript代码的68个有效方法》 《HTML5 权威指南》 《HTML5 秘籍(第2版)》 《HTML5 与 CSS3 基础教程(第八版)》 《CSS 揭秘》 《CSS 设计指南(第3版)》 《CSS 权威指南(第3版)》 《深入浅出 HTML 与 CSS》 Java开发 《Java8 实战》 《Java并发编程实战》 《Java性能权威指南》 《Java程序员修炼之道》 《实战Java高并发程序设计》 《Java编程思想 (第4版)》 《深入理解Java虚拟机(第2版)》 《Effective java 中文版(第2版)》 《Java核心技术·卷1:基础知识(原书第9版)》 《Java核心技术·卷2:高级特性(原书第9版)》 .NET 《精通C#(第6版)》 《深入理解C#(第3版)》 《CLR via C#(第4版)》 Python 《集体智慧编程》 《笨办法学Python》 《Python基础教程》 《Python源码剖析》 《Head First Python》 《与孩子一起学编程》 《Python学习手册(第4版)》 《Python Cookbook(第3版)》 《Python参考手册(第4版)》 《Python核心编程(第3版)》 《Python科学计算(第2版)》 《利用 Python 进行数据分析》 《Think Python:像计算机科学家一样思考Python(第2版)》 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》 《Python绝技:运用Python成为顶级黑客》 《Flask Web开发:基于Python的Web应用开发实战》 Android 《Android编程权威指南(第2版)》 《移动应用UI设计模式(第2版)》 iOS 《iOS编程实战》 《iOS编程(第4版)》 《Objective-C高级编程》 《Effective Objective-C 2.0:编写高质量iOS与OS X代码的52个有效方法》 PHP 《Head First PHP & MySQL(中文版)》 《深入PHP:面向对象、模式与实践(第3版)》 C语言 《C标准库》 《C和指针》 《C专家编程》 《C陷阱与缺陷》 《C语言接口与实现》 《C程序设计语言(第2版)》 《C语言参考手册(第5版)》 C++ 《C++标准库》 《C++编程思想》 《C++语言的设计与演化》 《C++程序设计原理与实践》 《C++ Primer (中文第5版)》 《C++ Primer习题集(第5版) 》 《C++程序设计语言(第1-3部分)(原书第4版) 》 《Effective C++:改善程序与设计的55个具体做法(第3版)(中文版) 》 《More Effective C++:35个改善编程与设计的有效方法(中文版) 》 机器学习和数据挖掘 《数据之巅》 《矩阵分析》 《机器学习》 《统计学习方法》 《机器学习导论》 《推荐系统实践》 《机器学习实战》 《Web数据挖掘》 《深入浅出统计学》 《模式分类(第2版)》 《概率论与数理统计》 《统计学习基础(第2版)(英文) 》 《数据挖掘:概念与技术(第3版)》 《数据挖掘:实用机器学习工具与技术(原书第3版)》 《大数据:互联网大规模数据挖掘与分布式处理(第2版)》 数据库 《SQL应用重构》 《SQL Cookbook》 《高性能MySQL (第3版)》 《深入浅出SQL(中文版)》 《MySQL技术内幕 : InnoDB存储引擎(第2版)》 《深入浅出MySQL : 数据库开发、优化与管理维护》 测试 《探索式软件测试》 《有效的单元测试》 《Google软件测试之道》 项目与团队 《人月神话》 《快速软件开发》 《人件(原书第3版)》 《门后的秘密:卓越管理的故事》 《极客与团队:软件工程师的团队生存秘笈》 求职面试 《程序员面试金典(第5版)》 《编程之美 : 微软技术面试心得》 《金领简历:敲开苹果、微软、谷歌的大门》 《剑指Offer:名企面试官精讲典型编程题(纪念版)》 编程之外 《暗时间》 《数学之美》 《赢得朋友》 《精益创业》 《批判性思维》 《世界是数字的》 《程序员的数学》 《程序员健康指南》 《禅与摩托车维修艺术》 《关键对话:如何高效能沟通》 《写作法宝:非虚构写作指南》 《黑客与画家 : 来自计算机时代的高见》 《软件随想录(卷1)》《软件随想录(卷2)》 《如何把事情做到最好:改变全球9800万人的人生指导书》
c语言系的命名风格:单词之间使用下划线分隔。如上图。 java语言是另外一个系,javascript属于java语系(当年就是想借助java的名气所以命名javascript)。java语系是驼峰式命名法,如getElementById()。如果使用c语系命名风格则使用下划线分隔 get_element_by_id()。 php属于c语系。大家最好使用c语系命名风格。有的同学担心使用下划线太长,看起来不雅观,上面的截图是mysql数据库的源码。c语言编写,遵循了下划线的命名风格。 thinkphp框架的作者是编写java出身,所以恰好把java的命名风格和框架思想用了thinkphp中。 最后说一句,使用c语系还是java语系命名风格,都可以的。关键要有表示分隔单词就好了。比如下划线,比如大小写间隔。 多个单词,千万不要没有区分符,那样就显得不专业了。比如getelementbyid(),这样的命名风格,可以恰好组合起来是一个单词,造成阅读代码的人误解意思。 代码毕竟是写给人看的,不是写给机器看的,如果是写给机器看,其实不用发明高级语言(c,java,php),直接使用汇编,性能更快,机器能识别。之所以需要高级语言,一个原因之一是,高级语言比较容易让人大脑理解。如if else之类的单词,很容易理解意思。 命名遵循原则:方便阅读最好,至于长度很长,其实没关系的。上图中的load_db_opt_by_name()函数够长了,但好处是让人容易理解代码的意思。
本文为纯原创文章,应朋友邀请而编写。文章花费了笔者大量的精力投入。琢磨着如何行文,怎么样通俗点,提炼出有用的观点来,放在标题中。目标的解决广大求职者的最关心的问题。 转载请注明来源地址。 一、概念理解篇 1、理解A股、B股、C股等 2、通俗理解期权 1.2.1、期权 1.2.2、期权和限制性股票区别 1.2.3、为什么老板这么热衷于搞期权 3、随处可见的ADR 二、期权行权时需要注意的坑 4.1、未上市行权的障碍 4.2、考虑期权以后被稀释的情况 4.3、要考虑公司被卖掉的情况 4.4、注意期权合同里的附加条款 4.5、公司上市之前被解雇 4.6、认真看合同里的行权条件 4.7、故意推迟上市 4.8、与中国公司签vs与外国公司签 4.9、避免白干:被公司收回期权的情况 三、期权其他注意事项 3.1、行权资格不及时用就失效 3.2、需了解股票的解禁期 3.3、股票套现涉及到的纳税 前言 笔者以前也在网上看过一篇文章,文中说的是这样一个事情:一位技术员加入一家创业公司,老板给他期权,还签了期权协议。但是在干了2年后,离开公司了,问公司要行权,结果是-中国法律不支持行权。曾经加入这家公司,无偿加班,于是感觉自己被坑了似的。现在好多的互联网公司都喜欢弄期权。公司言必称期权,变成了创业公司吸引人加盟的画饼。 有了白纸黑字,员工就会觉得,这总不会骗人了吧,因为公司有合约,白纸黑字都签字了呢。 而实际却存在一个这样的现象,非常普遍:员工对期权方面将要面临的坑,不是很清楚。在法律知识方面是弱势的。 一般公司制定期权协议的时候,都得到人力资源专家和法律专家给予建议,他们在劳动合同和法律知识方面是专家; 而这些人是为公司服务的,当然会争取让老板利益最大化。 对于员工,他们并没有这样专业人员的去协助,在面对长达十多页的期权协议,又是英文的,只能硬着头皮去看,其实看了也不太了解里面可能存在的陷阱。公司方面明明知道未来将会存在什么风险,仍然会秘而不宣。 是的,现在互联网上这么多信息,查询好方便。员工也可以去查到相关资料。但笔者发现,期权方面的文章,写得都不够全,资料是零零散散的。比较费时。有的文章只写了某一个方面,只谈了海外公司行权的障碍,其他方面没说,所以不够全面;而有的文章解释时也不够深入。 怎么办?所以笔者决定,专门针对打工一族的来写这样一个话题,希望通过多方面来介绍期权方面的知识,让打工一族跳过一些坑。 一、概念理解篇 1、理解A股、B股、C股等 很多炒股的人,估计很清楚A股,B股,但是很多工薪族不了解什么是a股,什么是b股。什么又是a股上市公司、b股上市公司。 经常看新闻说,某某公司要回归A股。有必要来了解什么是A股,什么是B股等,这样对于了解给员工的期权的时候,才有意义。 A股:在国内大陆上市发行的股票,叫做A股。目前,中国大陆地区,就深圳、上海两个股票交易所,所以在这两个股票交易所上市发行的股票都叫做A股。 怎么区别是深圳上市还是上海上市的股票呢? 有个技巧 深圳上市的股票代码以000、001、002开头,其中002开头的是中小板指数;上海上市的企业是以600、601开头。 B股: 也是在中国大陆上市发行的股票。那跟A股什么区别呢? A股是针对国内的股民为对象的,所以购买单位是人民币。而B股是针对外国的投资者的。所以购买单位是外币。国内投资者要想购买,必须是开外币账户。 针对A、B股票的补充知识:经常会看到ST板块股。ST其实是英文Special Treatment 缩写,意即“特别处理”,概括起来就是要特别处理的股票。 它的来源是:1998年4月22日,沪深交易所宣布,将对财务状况或其它状况出现异常的上市公司股票交易进行特别处理(Special treatment),由于“特别处理”,在简称前冠以“ST”,因此这类股票称为ST股。 从上面可以看出,针对的是在上海、深圳发行的股票,财务状况出现异常,证监会进行保护,怎么个保护法?每天的涨幅度不能超过原股票价的5%;每天的跌幅不能超过原股价的5%。经常说的跌涨停,就是这个意思。一个股跌得太厉害了,涨得太厉害了,也不行,证监会会纳入到ST板块股中去。 C股: 太术语化了。没搞明白。网上解释如下, C股指“上市公司非流通法人股”。C股市场指的就是法人股的规范转让而产生的无形市场,C股就是进入协议转让的法人股,俗称股东。 据人说,现在的新三板,就相当于c股。因为都是协议转让,股票都不是流通股(在市面上公开自由转让就是流通股) 不用过度研究,不常见。其实已经被新三板替代了。了解新三板市场。 D股: 德国与中国合资建了一个”中欧国际交易所”,在这个交易所发行的股票,就是D股。 中欧所是没多久才成立的,2015年11月19日正式在德国法兰克福开业。 上海证券交易所、德意志交易所集团、中国金融期货交易所此前签署三方协议,按照40%、40%和20%的比例,共同投资2亿元人民币在法兰克福成立中欧国际交易所股份有限公司(英文简称:CEINEX)。 H股:H其实就是HOngKOng的第一个字母。因为香港的英文称呼是HOngKOng。一个公司跑去香港上市发行股票,那么这种股称为H股。 以国企为主,所以又称红筹股。 N股:美国纽约市的英文名字是New York。使用N来表示,在美国纽约股票交易所发行的股票。 S股: 新加坡英文称呼是:Singapore。取名字的第一个英文字母S来表示是在新加坡发行的股票。 新三板市场:叫做定向市场。为什么叫做定向市场? 定向融资的。定向的目标是谁?普通的股民并不能去新三板市场购买股票。只能是资产超过500万的人,才能去开帐号投资买股份。定向的目标就是这些群体。 新三板有个比较大的特点,不能公开发行股票。也就是,不像A股,B股这些股票,可以在股票市场自由转让(炒股)。这样就很难套现,价格也炒不上去。 上新三板,公司不需要盈利,哪怕是亏损的公司都可以上。 由于不能公开进行交易,所以就算你所在的公司拿了期权,公司上了新三板,也不能转手卖给股民,不能公开进行交易。 2、通俗理解期权 1.2.1、期权 期权的英文叫法是:Option。 期权,约定日期拥有某种权利。什么权利?可以以某种约定的低价购买股票的权利。 比如:期权协议中约定,在某年某月某日,员工可以享受一个权利,以每股3块钱,购买公司的5千股股票的权利。 这么看,通俗点说,就是能以低价购买多少股的权利。价格当然不是随便定是,在协议中约定好的。 到时候赚一个差价。约定3块钱购买5千股。股票价格涨到了10块钱一股,那么自己就赚7块钱一股了。 误解:一些员工以为自己拿了期权(option),自己就是公司的股东的。 这点是错误的。没有投票权,不是股东来着。期权也只是一种低价购买的权利而已。抓住其中的关键字比较重要:”某种价格”、”购买的权利”、”一定条件才能行权”。 当然公司也不可能让你具备投票权的。你并不是公司的股东,明白这点非常重要,避免自己心里期望越大,失望心情越大。 期权的行权年份一般是4年,4年是从什么时候开始算的呢? 从工作年限后往后开始算4年。而不是从上市成功日期后往后推算4年 1.2.2、期权和限制性股票区别 ESOP是Employee Stock Ownership Plans的缩写,翻译过来是”员工持股计划”。期权是员工持股计划的一种办法(还有其他办法,已经上市的公司是直接发放限制性股票也是员工持股计划的一种)。 期权(option)和限制性股票(restricted stock unit,RSU)都是员工持股计划的具体方式。如何区别期权和限制性股票? 笔者一般这样理解:期权是针对未上市的公司,约定未来低价购买股票。限制性股票,是针对已经上市的公司。直接发放股票,由于股票限制了条件,要满多少年才能拿到。所以叫做限制性股票。 公司如果不能上市,期权其实就废纸。对员工,没有什么实际意义。 1.2.3、为什么老板这么热衷于搞期权 笔者有时候也在想,为什么很多老板这么热衷于搞期权? 期权是老板请客,市场买单。 现在很多创业公司,言必称期权,没有期权,都不好意思说自己的公司了。 当笔者深入去研究期权后,会发现:这对于老板真心是非常划算的。怎么说? 用这个空头支票可以少发工资,可以让大家无偿地加班。用这个空头支票吸引、留住员工。反正行权,要收点钱的,老板也并不冒什么风险。 对此,外国人写文章这样描述的,杜国栋律师翻译过来了,如下: 我几乎可以听到创始人的反对意见响彻整个山谷。他们说:“如果你是脸书、谷歌和优步的早期员工呢?”事实上,这种论调我经常听到。这是许多创始人的特殊欺骗手段。他们在自己并未创造任何事迹的时候,已经把自己比作了传奇人物。 这些创始人动不动就不自量力地与谷歌和脸书等创业传奇相提并论。如果你不能接受, 你就必须认清这个现实,即:员工股权只是一个工资谈判手段。它的目的是说服你接受一个较低的工资。股权安排仅仅是公司降低其成本的多种方式之一。 如果你不信,那咱们试着做一个实验:下次你得到一份创业公司的工作时,建议你跟公司协商,将这个工作附带的股权,兑换为期权的现金价值。也就是说,你要求的薪酬是:招聘启示上说明的工资,以及授予股权所对应的现金。看看有没有公司能答应。祝你好运!....................... 文章的意思归纳起来就是:对于老板而言,期权是用来说服接受一个较低的工资、更多的加班时间的方式。最后是市场买单。对于老板而言,确实是一本万利的事情。 公司在设计采用期权的时候,往往会咨询专业的律师,让他们来起草协议。对于其中的风险,律师会告诉他。因为律师是拿老板的咨询费,当然要替老板考虑,把里面可能存在的问题早告知了老板。而员工往往不太清楚期权存在的法律问题,谁来告诉你?签订合同的时候,可没人跟你说未来存在的风险:涉及到被稀释掉、限制性条款等。员工往往觉得,有期权,还有白纸黑字的合同,那自己就是股东了。 有些人还觉得,自己很重要。因为是很重要的员工,老板才会授予期权。信心满满的。 对于期权,老板不需要投入赚到的钱给员工去分。这多爽。一张空头支票。 一些老板,自己都不清楚期权和股权,为了赶上潮流,为了方便吸引到人,也是说有期权。 3、随处可见的ADR 海外上市与ADR的关系 之所以要介绍ADR,是因为中国的很多企业,想实现去美国上市,一般都是这样的操作方式: 在美国以外的国家,注册一家公司。比如典型的是开曼群岛注册比较方便。然后开曼群岛这家公司,去美国上市。 读者会好奇:为什么要去这些岛屿注册公司?搞得这么麻烦干嘛? 因为这些岛屿有一些好处:税收少、不需要验资、股东资料绝对保密、不用在注册地开股东大会等。 接着,这家开曼岛的公司,以外资公司的名义去美国股票市场发行股票。 看下面新闻: 优酷与土豆进行合并,成立一家新的公司"优酷土豆公司"。优酷股东及美国存托凭证持有者将拥有新公司约71.5%的股份。土豆股东及美国存托凭证持有者将拥有新公司约28.5%的股份。 实际上,优酷和土豆两家公司都是以ADR的方式在美国上市的。所以他们发行的都是美国存托凭证给股民。公司合并,原来持有存托凭证的股民怎么办?采用了换股合并的方式。土豆所有已发行和流通中的A类普通股和B类普通股将退市,每股兑换成7.177股优酷A类普通股;土豆的美国存托凭证 (Tudou ADS)将退市并兑换成1.595股优酷美国存托凭证(Youku ADS)。每股Tudou ADS相当于4股土豆B类普通股,每股Youku ADS相当于18股优酷A类普通股。 ADR的产生背景 ADR的英文全称为American depositary receipt,depositary是存放处,receipt是收据的意思。翻译为美国存托凭证。顾名思义,本质是一个凭证。 接着上面的例子说,由于开曼群岛的这家公司,并不是在美国注册的,对于美国而言,他是也外资公司。 由于美国证券法律规定。 规定一:在美国上市的企业,不能是外资公司。注册地必须是在美国。于是,外国的公司,要想在美国发行股票进行融资,那么就必须在美国注册一家公司来发行股票才行。但这对于公司来说,比较麻烦。 规定二:美国国内的投资者,只能购买美国资本市场上流通的股票,不能购买美国国外的股票。 上面的两个限制条件,导致了:外国公司进不到美国去发行股票,美国国内的钱投资不出去。 于是,美国人J.P摩根为了方便美国人投资英国的股票发明了存托凭证。 具体操作办法为:美国的银行去国外购买一定数量的股票(指定公司的)。比如2亿份股票。存放在银行里面。然后打包销售给美国国内的人,以美元形式来算价格,这样美国的股民是可以直接用美元来购买,返股息的时候也直接是美元。 这种体系下,会按照一定比例来做对应关系。比如1股外国的股票对应着一份美国国内的股票。或者2份、3份对应着美国国内的一股。ADR是一个凭证。上面会写着,每份ADR表示了实际多少个外国普通股(ADS)。 中国公司<<<<<股票卖给<<<<美国银行(存托银行)<<<<<发行ADR给<<<<<美国股民 ADR是存托凭证,一张凭证!ADS是存托股份,一张ADR实际表示了多少股份(ADS)。 总结:根据美国的法律限制,美国以外的公司,常常使用ADR的方式去美国发行股票。美国的银行购买这家开曼群岛公司的股票,然后打包以美元的形式发行给美国的股民来购买。美国的股民购买的是美国存托股份(英文缩写是ADS)。 二、期权行权时需要注意的坑 4.1、未上市行权的障碍 计划有时候却赶不上变化的,当初跟公司是约定4年后可以完全行权,每年可以获得25%。可是,会出现预料不到的情况,自己需要离开公司了,想行权。你打算离职,你跟老板说,我要行使期权。答复是:公司没上市,中国的法律不支持未上市公司行权。 笔者在跟身边的打工族聊起这个情况的时候,他们觉得很正常:你离开公司了,期权当然没有啦。 从这个想法,可以看出,很多人忘记了一点:你当初加入公司是为了什么? 如果是为了工资,那就按照行情的工资来。你忍受的是低薪、免费无偿加班,就冲着那个期权的回报。所以,其实这是你应该得的。 法律的弱势方 很多公司在与员工签署期权合同的时候,并都没有告诉员工这里会存在障碍和风险。而公司制定期权协议时,往往是经过人力资源和法律专家给予专业建议的。所以员工往往是弱势,他们只能自己去读一堆的法律条文,而且还是英文的法律条文。 为什么未上市公司行权会存在障碍?笔者查阅法律知识,归纳起来有两个方面原因。 原因一:因为期权只有海外公司才有的概念 谈到期权专门指海外上市公司。国内公司只有股权。 先了解一下期权和股权的区别 国内的法律只有股权(工商局备案)概念,没有期权的概念。所以,只有准备去海外上市的公司才有期权的说法。美国法律则是支持期权的。 如果你呆的公司,是在国内注册的,那么就是国内公司,就按照国内法律来。如果是在国外注册(维京群岛、开曼群岛等),那么就是国外公司,会按照国外法律来实行。 所以呢,要谈到期权的话,其实是指去准备海外上市的公司。 原因二:持股海外公司会涉及到外汇管制问题 员工行权的时候,由于这家公司是准备在美国去发行股票。那么它会注册成海外公司(开曼岛、维京岛)。就需要把人民币换成美元,用美元去美国购买股票。 如果要套现退出,卖掉成美元后,要换成人民币。人民币换成美元、美元换成人民币,这就是外汇交易。 由于中国是严格外汇管制的国家,这种外汇交易必须按照外汇管理局的规定来进行。不符合规定的,银行不会给办理外汇购回结汇手续。 外管局并没有规定未上市的境外公司如何操作期权。导致无法律依据。于是,很多没有上市的海外公司,当员工要求行权的时候,他们会以外管局没有规定为由,拒绝为员工办理行权。 在2014年的时候,国家外汇管理局虽然颁布了《关于境内居民通过特殊目的公司境外投融资及返程投资外汇管理有关问题的通知》(汇发[2014]37号),未上市公司的员工期权也是可以办理外汇登记的。但是,这在实际操作中,外管局还是没有放开,仍然办不了外汇登记。 有什么解决办法吗? 笔者觉得一般两种办法: 方法一, 在合同中约定,留着期权,等到上市后可以去行权。 其实这样操作的公司公司也不少。虽然外汇局是有限制。离职后可以暂时不行权。等到公司上市后,去行权。笔者看到那个搜房网的员工与公司打官司的案例,就是这样的。 方法二、解除劳动合同后可由公司进行回购期权。公司没上市,没法行权。可以回购期权。比如原来有3块钱价格认购的权利,公司回购的时候,用10块钱(只是举例)。让员工离职的时候,多少有点补偿。 4.2、考虑期权以后被稀释的情况 优酷就出现这种情况。老员工的股权被稀释掉了。 很多打工族不知道有这么个情况。笔者跟一些同事提起稀释,他们确实不知道这是怎么回事。有人疑惑,怎么会被稀释掉呢? 答:因为需要继续融资。别人投钱给公司,肯定是要占公司股份的嘛。那这些股份从哪里来呢?要从原来公司的股份中,拿一部分去给新的投资人。 那是让出谁的股份呢。是创始人的,还是员工的? 创始人又出多少,员工又出多少? 一般是按照比例来稀释。比如创始人稀释掉1份,员工则稀释掉10份。 根据网上资料,优酷当时被稀释的比例是:18:1。优酷是这么操作,自己稀释1,则员工要稀释掉18。这就是1:18的比例,对外宣称是为了保证创始人对公司的绝对控股。一般期权在3W以上,稀释之后普通员工的股票大概有1000股左右。 现在笔者明白了那句话,关键是决策权掌握在谁手里。基层没有决策权,那么就算你有股权,也意味着是被刀宰割一样的。因为决策层可以用各种办法来获取员工应有的利益。比如稀释股份。决策层导致股价下跌。员工结果得不到什么。 有人看到这里,会感叹:果然发财不是那么容易滴;数年忍受着低薪、无偿加班着、一心盼着上市后就可以一夜后可以解放,对于这样的老员工来说,确实是梦想破灭了。 总结:要不要继续融资,怎么个稀释法,这个规则的决定权,往往在老板的手里面,由老板和新投资人进行协商,而作为基层员工,其实并没有多少资格去参与进去讨论。 对策:当规则的制定你没有机会参与的时候,未来还得看老板的人品了。老板为员工着想,就会替员工争取利益。 4.3、考虑公司被卖掉的情况 期权还没行权,公司可能恰好卖给其他公司。这个时候,员工的期权怎么办? 先要搞清楚有几种被收购的办法:现金收购、换股收购。 第一种办法、对方公司现金收购你们公司。那你就可以直接拿现金了。因为对方的钱直接给到了你所在的公司。 第二种办法、对方是换股收购你们公司。怎么个换股法? 你们公司的股份都换成是对方公司的股份。最后,你现在持有公司的股份,就直接变成了对方公司的股份了。所以你持有的就是买方公司的股份。 换股收购,还要分清楚,对方是上市公司还是未上市公司。如果对方是上市公司,你就直接持有对方公司的股份了。 如果对方公司是未上市公司,那么,你得等到那家公司的股票可以变现或自由买卖的时候才能变成钱了。 还有一种换股方式,几股合成对方公司一股的形式。经常听到一个专业术语ADR,没错,说的就是这种。文章会专门开一个章节介绍ADR。 还有一种可能:老板或投资人从员工手里回购期权。 这样可能性多大? 不知道。那得看投资人对公司的前景非常看好,那么愿意拿钱来回购员工的期权。 总之,对于被收购的情况,这需要看管理层与另外买方公司进行沟通的结果。沟通采用哪种方式:是全部废弃,还是补偿一部分现金给员工,还是现金购买公司公司。 4.4、注意期权合同里的附加条款 对于期权合同,一定要认真地一条一条去看。多花点时间是值得的。以前笔者看过有个这样的事情: 2011年,Skype 的高管 Yee Lee 在 Skype 工作一年多之后主动离职。按照典型的硅谷四年奖励传统,Lee 可以得到最初期权的四分之一以上。这部分期权据说价值接近百万美元。但是离职之后,Lee 惊讶的发现:他一分钱也拿不到。 原来 Skype 的股票授权协议中隐藏了一个条款:员工在主动或者被迫离职的时候,Skype 有权选择收回所有的期权。 其实,期权授权协议一般是几十页的法律文件,大部分的员工并没有耐心仔细去读。 比如搜房网以前就发生过一个期权纠纷的事情,上市后,最后赖债不兑现。员工打官司告公司,公司用合同里面的附加条款来作为理由,企图不给,比如说这是外国公司应该按照外国的法律来审理、在多久以内没有行权。这个事情说明了,附加条款里面是有猫腻的,如果不认真、仔细研读,到时候扯上纠纷了,对自己不利。 如果计划是在国外去上市,为了方便上市,那么公司一般会注册成国外公司(如英国开曼岛)。这种情况下,期权合同是用英文写的,十多页纸。即便是英文,即便条款很长,也要认真研读里面的条款才好,做到心中有数。 4.5、公司上市之前被解雇 如果被公司解雇后,期权要怎么处理? 这种情况,不是不可能。完全有可能发生。要考虑进去。 笔者特意找了几个现实版的例子扒出来: 1、傅盛被360开除。 2、搜房网副总经理孙宝云。在上市前一年被开除。 3、冯大辉与丁香园。在丁香园干了6年,任首席技术官。解雇后,期权没谈拢。 对策 看明白期权合同里面针对劳动关系解除后,期权怎么处理。在这方面进行约定,一般的办法是:劳动关系解除后,期权按照工作年数来行权。 比如干了2年,就拥有2年的部分。干了3年,就3年的部分。 合同里面一般会写明一句:当受让人因其他原因终止同公司的雇佣关系时,则所授予的期权,在雇佣关系终止日起的30天后终止,股票期权中尚不能行使的部分将失效。 也就是离职后,30天内必须行权,不行权。就失效了。 针对这种情况,离职后,就要申请行权。就算公司没上市,按照中国法律不能行权,但也是可以进行约定的。至少你申请了行权,就表示你没有放弃行权的权利。 4.6、认真看合同里的行权条件 自己辛苦加班,忍受低工资,结果因为合同一个条款,导致自己没法行权。 这需要认真研究合同里面写的行权条件部分。 比如合同里面写:离职后就不能行权了;公司未上市不能行权(一般写着:行权的前提条件如下)。业绩必须达标才能行权等等。 如果合同约定,公司不上市,不能行权,此时如果也没有相应的补偿方案。这样的期权方案确实是忽悠员工的。因为一直不上市,那就没法行权。 按照业界规矩,都是分年限,每满一年可以得到25%的行权资格,分四年,刚好是100%。所以如果干满了2年后离开公司了,那么可以得到50%的行权。不管是主动还是被动(开除、合同到期等)离开了公司,都要保证可以行权。 业绩未达标,这里面是一个隐藏的坑。公司如果想从你手里收回期权,可以给你设置一个较高的业绩目标,让你无法完成这个目标,最后按照协议,无论你工作多努力,表现多优异,公司借此开除你,并收回所有“授予”你的股权。 4.7、故意推迟上市 一般会捆绑4年成熟期的期权来招聘员工。员工们如果要拿到这些他们辛苦赚来的期权,工作的时间可能要远远超过这所谓的4年。 很多公司上市的时间越来越晚(目前科技企业上市的平均时间需要11年),目的就是:不允许员工自行转让手里的股权,员工就不能轻易离开公司,否则要么期权就被收回了,要么马上行权、交了高昂的所得税换来不能卖出去变现的股权。员工被带上了一副“金手铐”。 而老板们似乎从来不会面临员工的这些问题。他们甚至毫不掩饰公司不打算尽快上市,完全不考虑员工是不是能尽早将自己赚来的期权变现。 “我们会尽可能推迟上市的时间”。说这话的,是Uber 的CEO特拉维斯·卡兰尼克,他不止一次表示Uber在最近3-5年内没有上市的计划。 “我建议Palantir尽可能保持不上市的状态。”说这话的是彼得·蒂尔,他是《从0到1》的作者、Paypal创始人,同时也是Palantir的投资人。 Palantir目前估值近90亿美元,专注于大数据处理,据说帮助美国CIA和军方调查到本拉登的下落。 但是,对于Palantir的员工们来说,这却是一个苦果。Palantir用远远低于市场价的工资标准,聘用了大量优秀的工程师,原因是给了这些工程师更多的股权。但是,却不让他们有机会卖出股权、变现赚钱。 创始人却不会遇到这个问题,因为他们有投资人的支持。一旦创始人把公司做到了某个阶段,比如做到了独角兽级别,那么投资人会积极促使创始人提前变现出一部分股权,让创始人在带领公司高速发展时,生活上也比较宽裕,不会面临经济压力。 有的创始人是很慷慨, 他们可能会组织员工通过类似交易渠道共同出售股份,但大部分创始人或公司不提倡这种做法。一方面,大多数员工根本没有足够的股权来出售。另一方面,对于公司来说,让你继续持有股票是最符合他们利益的。 员工持有股票(少量),可以使其对公司更忠诚。因此,创始人的这种私下退出,变现的交易,通常是在秘密进行的,除非创始人变现后新买的法拉利跑车太过招摇,那就马上知道创始人有钱的。 4.8、与中国公司签vs与外国公司签 与谁签订期权很重要! 请看下面的分析! 现状 由于很多互联网企业,融资和上市都是以海外公司的名义去发行股票的。如在开曼群岛、维京岛注册,而实际的经营主体是在中国的公司。境外的公司控制着中国国内的公司。这就是常常说的vip架构。 员工往往是与中国公司签订劳动合同。而签订期权协议时,有可能是与中国公司签订,也可能是与外国公司签订。 问题 员工是与中国公司签订期权协议好,还是与国外公司签订协议呢? 各自又存在什么样的风险呢? 归纳如下: 1、跟中国公司签的期权协议。此时分到的是境外公司的期权。可能存在的风险:行权的时候,中国的公司可以说,我这里没有境外公司的期权。我也给不了你股份。你找境外公司去要。到时候员工也没其他办法。公司赔点钱给员工了事了。 2、跟境外公司签订期权协议。此时,可以直接跟境外公司要股份。这些境外公司,按照当地的法律规定,是可以给员工发股份的。 法律纠纷的适用法律 如果出现了纠纷,适用哪个地区的法律? 在协议里面的术语是”司法管辖”。如果写着是,境外法院的排他性管辖,就是说,管辖权是外国公司,排他,意思是不允许用中国的法律来审判。这种情况出现纠纷,还要那个国家去起诉。要请当地的律师。耗费的时间和金钱成本就比较大。手里是很小的股份,不是很值钱,那打官司的成本员工也耗不起,最后员工也会放弃了。 解决办法:所以员工要睁大眼睛看清楚,合同里面写着这份合同适应于哪个地区的法律。最好是写明白,有争议时在中国打官司。 举例 笔者恰好找到一个现实版的例子 海淀法院受理了原告刘先生与被告北京三快科技有限公司(以下简称三快科技公司)劳动争议纠纷一案。该案系因美团股权激励计划引发的争议,与刘先生一同提起诉讼的还有三快科技公司的前员工包女士,二人诉请类似,均为离职后股票期权的行权事宜相关。 三快公司辩护的说明是这样的: 1、你告错人了。《股票期权授予通知》是与海外公司签订的。又不是三快公司签订的。海外公司与你又不存在劳动合同关系。 2、你应该找香港法院。《股票期权授予通知》及《股票期权授予协议》中载明管辖法院为香港法院,海淀法院没有管辖权 看到这里,是不是感觉是如出一辙? 两个坑被踩着了。这个案子是最新看到的新闻,还在审判中,不知道结果。 4.9、避免白干:被公司收回期权的情况 有些员工干了8-9年,之前忍受着低薪、无偿加班,由于意外导致,协议中约定的所有的期权都没了,对此感到很无奈。找老板理论也没用,协议中明明写着那么个条款,怨谁。 所以,一定要注意合同中的条款,哪些情况公司可以收回期权。 比如协议里面写着:劳动合同解除后,公司有权收回所有期权。 笔者看资料提到,某某员工在A的期权,离职的时候,公司找了个理由以一元收回,理由之一就是“同业竞争”。 过错离职,通常公司以 1 元回购所有期权,你一夜就回到了解放前。 什么行为会被认定为“过错”,自己搞清楚就特别重要了。 员工违反公司规章制度,比如违反保密义务、违反竞业限制规定等等,都有可能成为公司取消、收回期权的理由。总之,合同要认真研究,必要时可以找一个律师帮自己看看,里面有哪些坑。付点咨询费就好。 笔者截了一个国内公司的期权合同(海外公司的合同是英文的)图 注意看笔者描红的部分。辞职、辞退、解雇,就直接解除了期权了。所以啊,哪怕你在公司辛苦干了n年,之前很多期权,但是一瞬间就没了。再强大的撕逼都抵不过一纸协议。 笔者认为,像上面的这样的期权合同确实是一个坑来着,明显是倾向于公司利益最大化。为什么这么说?如果老板想把你搞走,可以找很多的理由与你解雇劳动关系,根据合同中的约定,一旦劳动关系解除了,期权就失效了。 再比如,按照上面协议,可以说你业绩没达标啊,然后取消掉期权。这个业绩标准谁来定? 我定得很高,你肯定难实现,那么就达不了标了。 笔者认为,公平的期权协议需要写明白,若出现劳动关系解除时,员工如何行权,能够行权百分之多少。在里面约定。员工在公司干了多久,哪怕离职了,也要按照约定给予一定量的期权,既对离开的员工是安慰,对在职员工也吃个定心丸。 三、期权其他注意事项 3.1、行权资格不及时用就失效 为什么一些公司约定雇佣关系结束后,3个月内(90天)没有行驶期权,就被认为是无效呢? 既然是权利,那么就不能无限期,必须在指定日期内使用完这个权利。 就好比一些公司,当月的加班假期,必须当月用完,不用完就消掉了。一些手机的流量包,当月没有用完,下月就失效了。 所以离职后,赶紧申请行权。这个时候,就算公司没上市,也是可以的。表明了你的意愿。到时候纳入的是合同纠纷上去。 3.2、需了解股票的解禁期 原来是3块钱的低价,从公司内部认购了几千股股票。股票的市场价格是涨到了10块,自己能不能马上卖掉套现呢?不能。 如果我低价认购了几千股后,是不是可以离职了呢? 不行的。公司有个约定,叫做解禁期。 一般公司设置了一个解禁期。一般是两年。每年可以套现50%,两年可以套现完毕。如果干了一年后离开,那么也只能套现50%。 哇,本来有好的机会,也不敢跳槽离职,就是为了等到股票解禁啊。等了2年,好不容易熬到了股票解禁了,现在终于可以套现了呀。 但新情况来了:恰好这个时候股价跌幅很大。原来是3块钱买的,后面是涨到了20块钱一股,信心满满的。可是恰好解禁的时候,股价跌到了5块钱,那相当于只赚了2块钱。后面还要纳税的。如果你的股票份数比较少,比如就3千股,那么你赚到了3000*2=6000元。这6000元并不是完全拿到手的,还要纳个人所得税的。 3.3、股票套现涉及到的纳税 股票套现是需要纳税的!别忘记了这点! 按照”工资薪金所得”的税率来算。 应该纳税的金额=(每股市场价格-每股行权价格)x股票数量-税费。 打个比方,如果从公司认购的价格是3块钱一股,认购了5千股。假设市场股票价格是15块钱一股。那么15-3=12块钱。套现的时候,12*5000=60000元。6万元这部分算是个人所得,需要纳税的,这个逃避不了。缴纳多少税,对照下面的表 纳税的起征点是3500元。也就是超过3500元的部分,按照上面的表格对应去纳税。 计算公式 应纳税所得额 = 工资收入金额 - 各项社会保险费 - 起征点(3500元) 应纳税额 = 应纳税所得额 x 税率 - 速算扣除数。 假设股票获得的收入是150000元。 应纳税所得额=150000-0-3500=146500元 这里由于是股票收入,各项社会保险费是0。 146500元这部分是需要进行纳税的,具体需要纳税多少,看税率。税率要对着上面表格,是属于超过8万元的部分,那么税率就是45% 需要缴纳税收=146500x45%-13505=52420元。13505是速算扣除数,也是上面表格中对应的值。 总结:15万需要纳税52420元。 四、总结 最后对本文进行总结一下,本文主要包括了以下几点: 1、区分各种类型的股票 2、了解什么是期权。 3、期权行权要注意的一些坑。 4、股票套现涉及到的纳税,而且纳税的比例比较高,最高达到了45%。
参考:http://www.cnblogs.com/thinksasa/archive/2013/02/26/2934206.html http://blog.csdn.net/alongken2005/article/details/8056910 socket_accept()是服务端接受客户端请求,一旦有一个客户端链接上来的话,则这个函数会返回一个新的socket资源,这个资源是与客户端通信的资源。 socket_accept()是阻塞的,会一直卡在那里。 发现情况:一旦客户端断开链接了,会影响到服务端的主进程。如何避免这个影响呢? 正常的服务器,应该是新开一个子进程来处理请求。 socket_connect()是链接一个socket去。是客户端使用的。 c语言的介绍:connect, send, recv都是同步阻塞工作模式。 那么,可以得出结论,socket_connect()也是阻塞性的。 php中使用协程: http://www.laruence.com/2015/05/28/3038.html socket_recv和socket_read($msgsock, 2048, PHP_NORMAL_READ) 有什么区别呢? stream_select()接受一个socket流,可以多个,一个数组形式。等待这些流改变了状态。改变了状态,则会返回大于0的一个值<?php/* Prepare the read array */$read = array($stream1, $stream2);//创建的两个socket$write = NULL;$except = NULL;if (false === ($num_changed_streams = stream_select($read, $write, $except, 0))) { /* Error handling */} elseif ($num_changed_streams > 0) { /* At least on one of the streams something interesting happened */}?>要使用引用传递,值会被修改。 php多路复用参考资料:http://blog.csdn.net/phphot/article/details/2020269 总结 如果没有安装socket扩展,则使用如下相关函数:stream_socket_client、fread()、fwrite()、stream_select()、fclose()如果安装了sokcet扩展,则可以替换掉扩展的函数:stream_socket_client等价于socket_create()socket_select()等价于stream_select()socket_close()等价于fclose()socket_write()等价于fwrite()socket_read()等价于fread() stream_socket_client()实际上是在调用linux系统的 connect()函数。 socket_select()、stream_select()都是在调用操作系统提供的select()函数。 思考:stream和socket本质有什么区别呢? 不知道,想着去去思考socket_accept和socket_listen()区别,也许就能找到答案了。 我总把socket_accept和socket_listen()进行混淆了。 socket_accept是开始接受客户端的链接,如果有客户端链接上来,这个函数会返回一个socket通道的句柄,这个句柄是专门与客户端单独的句柄。socket_listen是开始侦听这个socket通道,只是侦听,并不是接受客户端的连接请求。
函数库调用和系统调用的区别。操作系统层面上:系统调用是调用操作系统内核的一部分。系统调用,每个操作系统会存在不同。速度上:系统调用一次需要35微秒。函数库一次调用耗费半微秒。系统调用大概有70个。文件描述符和文件指针的区别将会进行干嘛呢。文件描述符是一个整型数字。操作系统的系统调用,都需要传递这个值进去。作者建议,要使用标准的i/o库调用。这样会出现移植性。文件指针则是一个内存地址,指向了一个数据结构。结构里面保持哪些内容?描述符,可以有很多种描述办法。用途不同,文件描述符是进行系统调用时需要传递的。文件指针,,是库调用需要的参数。为什么会这种区别呢。指针,本质是个指针。文件描述符的作用是索引?
http://blog.csdn.net/lgouc/article/details/8235471 http://blog.sina.com.cn/s/blog_67c294ca01012qbu.html 数据对齐并不是操作系统的内存结构的一部分,而是C P U结构的一部分 是这么理解吗?cpu要读取内存中的数据,以多少为单位进行读取呢?以4个字节,还是8个字节。还是16个字节为单位来读取内存数据? 目前主要以2个字节为单位吗?是的。2个字节作为对齐单位。 以多少个字节为单位来读取内存的数据,这是cpu的知识。与cpu有关系。 数据项只能存储在地址是数据项大小的整数倍的内存位置上。如int类型占用4个字节,地址只能在0,4,8等位置上。 也就是说,这个数据的首个地址必须是它的倍数。 比如一个数据大小是6个字节。现在要存入内存中去,首个内存地址的位置必须是6的倍数,即6*n才行:6,12,18........这样就可以。 这是结论,有这个限制要求。原因是什么,减少cpu读取内存的次数。不进行内存对齐的话,读取次数将会增加。 内存对齐的本质:减少cpu读取内存的次数。一次性尽可能多读取数据进来。是这样的吗? 处理器的内存存取边界是什么意思? http://www.cnblogs.com/xkfz007/archive/2012/10/08/2715163.html 这篇文章讲述了cpu与内存之间的关系 内存对齐是操作系统为了快速访问内存而采取的一种策略,简单来说,就是为了放置变量的二次访问。操作系统在访问内存 时,每次读取一定的长度(这个长度就是操作系统的默认对齐系数,或者是默认对齐系数的整数倍)。如果没有内存对齐时,为了读取一个变量是,会产生总线的二 次访问。 几个基础知识:内存的每个地址能够存储多少数据。1个字节。 cpu的执行指令速度 大部分简单指令的执行只需要一个时钟周期,也就是1/3纳秒。光在这个时间点也只能走10厘米。 由于主存中使用电容来存储信息,为了防止因自然 放电而导致的信息丢失,就需要周期性的刷新它所存储的内容,这也带来额外的等待时间。
以下是根据看书后的理解做的总结: 最早,unix是使用汇编编写,但是非常简单。后来觉得汇编,换种机器又得重新用另外一种机器汇编重写,太麻烦。于是想设计一种通用的语言,到各种机器上都能运行 当时发明了b语言,但是b语言并不成功(据说是很多缺陷,缺乏数据结构),后来者在此基础上改进,发明了c语言。使用c语言重写unix,后来c语言就成为一种编写操作系统的语言了。当时有c语言编译器,使得转化成机器语言很容易。现在终于明白了,为什么以前看的资料说:c语言是为了编写Unix操作系统而发明的语言。 当时其实存在了这个操作系统,但问题是它使用汇编编写,移植到新机器又得重新写用新的汇编实现。后来有了c语言这样通用的语言、 为什么操作系统的编写,一部分还要使用汇编呢? 因为硬件,两台电脑的硬盘不可能完全一样的方式工作,驱动程序是用c编写,驱动编译好后放到新机器无法工作的,所以要重新写,只能用对应机器的汇编编写。Unix的网络的发明,原来都是出于解决实际问题需要产生。最初发明是在一台废弃的pdp-7上。后来unix都是运行在pdp-11上。当时要把unix移植到一种新型机器interdata上,当时pdp-7在一楼,interdata在五楼,每次都要编译好后,去五楼的interdata机器上调试,这样折腾了几个月后,感觉很麻烦。于是发明的网络。这个时候是局域网。 现在明白posix的真实含义了: pos是可移植操作系统的简称。ix是unix的后面两个字母。合起来就是可移植的unix系统。 posix实际上是一个标准。并不是指特定的东西。就是工业界的iso9001标准,iso9001是一个标准。 posix标准,是为了给所有市面上大家开发的unix制定一个标准。之所以要制定一个标准约束。因为,每个人是可以拿到源码进行修改,于是当时的unix版本很多。于是迫切地需要一种统一的标准。 unix的开源过程是怎样的? 当时,贝尔实验室的发明了unix。在一台废弃的pdp-7机器上。后来公开发布了unix的论文。使得许多大学纷纷向实验室索要unix的拷贝。 当时贝尔实验室的母公司-AT&T,因为受到垄断法的管制,是不能经营计算机业务的。所以他们愿意大学支付适当的费用获取unix的源码。后来一些商业公司愿意花钱购买unix的源码进行修改,修改成自己的版本来发布。当时其实微软也花钱购买了unix,以xenix的名义出售版本7好几年了。 AT&T公司后来发布unix版本,进行商业化(因为通过了政府的拆分,允许设立子公司来经营计算机业务)。但是失败了,没有受到市场很大的反响。最终在1993年决定卖给novell,novell在1995年又卖给了santa cruz operation公司。 那个时候很多公司拥有了unix的许可证。 linux的出生 当时有minix。这个系统是一个类unix系统。它的特点是,微内核。如何解释这种微内核呢? 当时的很多人要求在minix上增加功能,得不到响应。越来越不满。当时又没有freebsd。 后来,当时的linux作者,就参考了minix的源码,对它进行了重构一次。唯一不同的是,它是使用整体设计方式。搞不清楚是怎么样的。是开发操作系统的方式不同。 1991年的时候发布一个版本。 加州大学伯利克分校早期获得了unix第6个版本的源码,他们自己修改源码进行发布,这就是著名的freebsd系统。
我发现,我们很多技术人员往往无限度适应需要人员,跟着提需求人的思路走,陷入进去了。 我的经验是:"以问题为导向,而非需求为导向"。提需求者,可能很多自己都不是很清楚自己想要什么样,想得与说的往往不一样。只有技术辛苦做出来后,用了一下,发现不是自己想要的。重做!双方都痛苦。这是很多同行的技术人员的深切感悟。我觉得,我们陷入了提需求者的思维引导中去了。以解决问题为导向,实际就是把:"你想做成什么样?"。换成:"你要解决什么问题?",技术了解他要解决的问题后会发现,使用技术手段解决这个问题的思路很多种。技术人员完全可以考虑用更好的方式解决需求者提的问题,陷入"你想要怎么样子"就很局限了,思维误导了。问题为导向后,做出来的功能切中对方的真实需求了。 这个感悟一方面是基于自己的实际工作中的观察,另外看的一本书《顾客想得与说得不一样》,里面特别介绍了,顾客往往自己说的并不一定是自己想要的东西,只是他自己不会表达。我们做调查问卷,有时候是被误导,明明按照用户说的做出产品来,结果顾客发现,自己要的不是这样的。 所以书中提出以问题为导向比较好。
1、数据库:<MYSQL性能调优与架构设计>,作者为阿里巴巴专业的mysqlDBA,学到许多的思维方式,如sql优化策略、功能撤下来的成本和系统的影响、有时候是需求存在问题没法达到完全实时所以实现准实时《数据库系统实现》从原理层面学到数据库系统的实现,所有关系型数据库都会遵循通用的理论,尤其是事务的实现原理通过此书看明白(以前只知道使用事务)。讲解磁盘的原理与性能的关系对性能优化有一定帮助2、架构设计:《淘宝技术这10年》,了解一个网站架构是演变出来,不是设计出来的;《大型网站技术架构》,归纳了常见的技术架构,提到一个思维:架构不是照搬,而要搞清楚为什么那样子设计,才能结合到自己应用中去3、搜索:《这就是搜索引擎核心技术》,带我通俗地了解了搜索引擎的技术实现.作者讲述道与术的关系深有体会,了解知识背后的原因,出于什么考虑和目标,这是学到道的层面,具体的技术方案只是术,适应道.4、linux:<鸟哥的linux私房菜>,这本书特点通俗易懂,当时以此入门linux.<linux命令行和脚本编程>,这本书我没看完,里面讲解知识点比较细致,会说来龙去脉,方便知其所以然
1、避免使用魔数 if($age<18){ } 这个18不太明白为什么要这样子。 可以将28定义在一个变量里面,这个变量命名表明了这个值的含义 $adult_age = 18;//成年的分界点年龄 if($age<$adult_age){ } 2、函数的返回结果:不要使用一个变量来存储返回结果 一旦你知道了返回结果,就应该马上返回。这样做的好处,是可以减少错误。 3、函数带有很多的参数。不要超过三个 如果参数很多,尽量聚合成一个model传递进去。比如一个数组,一个实例都可以。 太多的参数为什么影响方法的稳定性呢? 比如修改就会变得很麻烦。 我现在明白了。这个方法需要新加一个参数传递进去,那么原来调用这个方法的代码,就得跟着改变。 在做接口的时候,经常会遇到类似的问题。 function forgot($userName, $email, $email_url, $format = 'json') 上面是三个参数。 原来别人调用这个函数是, forgot($userName, $email, $email_url, $format = 'json'); 现在需求要变化了,需要新增加一个参数。怎么办? 原来的代码就要跟着修改。方法并不稳定。要么就重新开一个方法适应新的需求。要么就修改调用方的调用代码。 但是有种办可以避免这种问题 把原来传递参数做成数组形式,如下: forgot($params=array(),$format = 'json'); 聚合成一个数组。这样可以加任意多个参数。 如何理解:php引擎的内置函数,带有多个参数的情况呢? 设计缺陷? 4、方法的参数中含有布尔参数。 这意味着,这个方法不是完成一个单一的目标。违背了单一职责。增加了复杂性。 思考:如何理解我们现在代码中的问题呢
a,b,c,d.....分别代表时间,从最早往后面。 白色区域表示空闲内存。灰色区域表示进程占据的内存,蓝色区域表示操作系统占据的内存,这部分是固定好的。 看内存碎片的发展过程: a图表示,假设内存目前有56m的内存空间。到了b图,有个进程占了20m的内存,现在还剩下36m了。到了d图,新的进程占据内存,只剩下4m。 c,f图,有的进程释放掉内存空间,腾出来了。腾出来的空间被其他进程可以申请。随着时间的推移,到后面就会有空隙的内存,无法使用。 比如h图显示,中间有空隙的6m,6m,4m。假设一个进程目前需要10m空间。那么现在根本找不到连贯的内存空间可以用。相当于这部分内存是没有使用的,像碎片一样(比如玻璃碎片,不是整块的,没法拿来使用),这些内存碎片是浪费掉的。 理解了内存碎片,也方便理解磁盘碎片了。都是碎片,只不过是磁盘,而不是内存。
以前看了一个观点,不错:不要的代码删除掉删除你没有使用的功能清理的时间正比于代码的数量,复杂性和糟糕的程度。如果代码的功能你目前没有使用,而且在可预见的将来也不会使用,那么就删除它,这会减少你浏览的代码数,降低复杂度(删除不必要的概念和依赖)。你会清理的更快的,而且最后的结果会更简单。不要留着代码仅仅因为“谁知道呢,你可能某一天需要它”。代码是有代价的 – 它需要被移植,修正错误,被阅读以及被理解。你有更少的代码,就更好。就算在最不可能的情况下,你需要这个旧代码,你也能从代码库中找到它。
用途:这个扩展是用来操作rabbitmq服务端的 一、安装总括 1、编译安装librabbitmq库 这是一个开源c语言的库。用来与rabbitmq进行通信 而php的php-amqp扩展就是使用这个库与服务端通信。所以必须先安装这个开源库。 下载地址:https://github.com/alanxz/rabbitmq-c/ 2、编译php-amqp扩展的源码 注:两个的版本要指定,避免冲突。librabbitmq库是0.5.2版本。php-amq是1.4.0版本 这个扩展是php官方在维护,去php官方下载:http://pecl.php.net/package/amqp 二、编译步骤 第一步:先安装库:librabbitmq 版本需求:需要0.5.2版本的。编译顺序 1、tar -xzvf rabbitmq-c-0.5.2.tar.gz 2、cd rabbitmq-c-0.5.23、autoreconf -i4、./configure --prefix=指定库安装到哪个目录,第二步安装php-amqp时需要引入这个库。建议目录:/usr/local/librabbitmq/0.5.2/5、make && make install 第二步:编译php-amqp模块 版本需求:需要1.4.0的。 下载位置:http://pecl.php.net/package/amqp 编译顺序1、解压amqp-1.4.0源码压缩包2、cd amqp-1.4.03、phpize #phpize路径根据服务器phpize的位置而定4、 ./configure --with-php-config=/apps/php-5.5.18/bin/php-config --with-amqp --with-librabbitmq-dir=这里填写第一步librabbitmq库的安装位置5、make && make install 第三步、重启php-fpm使新模块生效 php-fpm的重启命令一般是:php安装目录/sbin/init.d.php-fpm restart
有一种方式是:下载rabbitmq-server-generic-unix压缩包,是不用编译的。是已经编译好的源码了 下面介绍编译源码安装 总括: 需要以下步骤: 1、安装erlange。因为rabbitmq是使用erlange语言编写。所以需要安装erlange。 2、安装python。自动化安装脚本是使用一个python脚本-setup.py来自动安装。所以要能够运行python语言。 3、安装simplejson.py 官网提到了:RabbitMQ requires a recent version of Python and simplejson.py (an implementation of a JSON reader and writer in Python), for generating AMQP framing code. simplejson.py is included as a standard json library in the Python core since 2.6 release. 4、编译rabbitmq源码 二、安装步骤 1、编译安装erlange 下载源码:http://www.erlang.org/ 解压erlange源码压缩包 cd 源码目录 ./configure --prefix=指定安装目录 make && make install 2、编译安装python 官网下载源码:https://www.python.org/ 下载2.7.x版本。我下载的是2.7.10版本。3.x版本有些软件还不支持。 解压源码 cd 源码目录 ./configure --prefix=指定安装目录 make && make install 3、安装simplejson 下载地址:https://pypi.python.org/pypi/simplejson $ tar xvzf 源码压缩包 $ cd 源码目录 $ sudo /usr/local/python/2.7.10/bin/python2.7 setup.py install 成功后如下图: 4、安装rabbmitmq服务端 官方安装手册:http://www.rabbitmq.com/build-server.html 下载RabbitMQ 3.1.5的源码包 解压源码包 cd 源码包目录 make sudo make TARGET_DIR=/usr/local/rabbitmq/3.1.5 SBIN_DIR=/usr/local/rabbitmq/3.1.5/sbin MAN_DIR=/usr/local/rabbitmq/3.1.5/man install #TARGET_DIR是指定将源码安装到哪个目录去。TARGET_DIR最好是填写TARGET_DIR下的一个目录 #安装成功后,去SBIN_DIR指定的目录,即/usr/local/rabbitmq/3.1.5/sbin,会看到编译生成好的rabbitmq-server、rabbitmqctl等文件。 启动 ./rabbitmq-server 测试是否成功的办法:telnet ip 端口 telnet 127.0.0.1 5672 启动的时候,如果没有使用-c指定配置文件路径,默认是去:/etc/rabbitmq/ 一般情况下,RabbitMQ的默认配置就足够了。如果希望特殊设置的话,有两个途径:一个是环境变量的配置文件 rabbitmq-env.conf ;一个是配置信息的配置文件 rabbitmq.config;注意,这两个文件默认是没有的,如果需要必须自己创建。 停止: ./rabbitmqctl stop 启用插件 ./rabbitmq-plugins enable rabbitmq_management management UI是static HTML页面,其通过Javascript查询HTTP API得到实时数据。实质上Web UI最终也是调用的Management command line tool 访问web监控界面 The web UI is located at: http://server-name:15672/ The port for RabbitMQ versions prior to 3.0 is 55672. 实际上是rabbitmq安装了一个web服务。它侦听端口15672,所以能够访问这个端口。 默认登录帐号是:guest/guest 编译rabbitmq会遇到不少的问题。 总结如下: 1、需要安装xmlto命令 没有会报错:/bin/sh: line 1: xmlto: command not found 使用yum命令去网络安装这个命令 yum install xmlto 2、make erl command not found 安装脚本会用到erl命令。erl就是erlange的简写。 在安装脚本中,使用的命令是erl。默认是去/usr/bin/erl下寻找。 由于,前面编译安装erlange的时候,使用--prefix参数,将erlang安装到了指定的目录了,而不是使用默认的路径。但是这会导致后面 rabbitMQ报错:找不到erl 执行文件。 解决办法: sudo ln -s /usr/local/erlange/17.5/bin/erl /usr/bin/erl 3、/bin/sh: escript: command not found 解决办法: sudo ln -s /usr/local/erlange/17.5/bin/escript /usr/sbin/escript 4、make: erlc: Command not found 解决办法: sudo ln -s /usr/local/erlange/17.5/bin/erlc /usr/bin/erlc 附:资料收集 1、插件下载地址:http://www.rabbitmq.com/community-plugins/ 2、如何配置帐号的文章 http://my.oschina.net/fhd/blog/375620 http://www.cnblogs.com/AloneSword/p/4200051.html 这篇文章讲解了各种用户角色,已经如何用命令添加用户、设置用户所属角色,用户的权限 3、官网:http://www.rabbitmq.com 4、php操作rabbitmq的资料,通过安装一个php-amqp扩展 http://nonfu.me/p/8833.html
为什么开发功能变得越来越慢? 某天来一个技术,他跟老板说:这个系统太臃肿了。很乱,我很难开展工作下去,至少很难按照我的经验和设想来实施。如果想让我顺利干下去,办法就是对系统进行重构一次(重构代码,或者开发新的系统替代原来系统)。 我们让项目变得可维护性有很多。对公司,对接手的技术,都是有利而无害的。 自己做的成果没法让下一任衔接。就像官员上任,任期满了后。这个烫手的山芋丢给下一任去解决。我这一任期内,维护稳定不出事情就可以。 片面追求gdp指标,就好像片面追求功能的完成,不管功能完成的质量。外行也没法评价功能完成的质量,他们只能说:这个功能达到我的预期了。就是质量好。 这就好比,gdp达到预期指标了。就是质量好。可是会忽略掉一些重要的东西。 我发现非常像系统一样:只要保证我在这公司干这段时间内,系统是稳定的,可以继续加功能完成上面的任务即可。至于定时炸弹什么时候爆发,只要不在我任期内爆发就可以了。 于是我在任期内,明明知道这里是一个坑,都懒得去做代码优化,做重构了。干嘛要浪费自己时间做这种事情。 为什么招聘经验丰富的技术投入和产出很值得。避免了很多坑,留给以后的技术债务。 我觉得,至少要招聘经验丰富的技术作为领头羊带领下面的人,有一个正面的能量。 俗话说,上梁不正下梁歪,下面的人都是看领导是什么水平的。领导是一个什么样技术思想,下面的人就能够很好的施展开来。 命名是可维护性的第一步,代码的功底倒是其次,因为每个人的技术经验不一样。用拼音命名带来接手人员的阅读成本。比如用拼音命名变量或者程序文件zhuantipt其实是拼音的缩写,看不懂在干嘛我们第一眼看不出这个要表达的意思,维护一个系统只能靠看代码来沟通了好的命名,就是减少误解、减少沟通比如,以前有code,后来有app跳转到网页时也有一个code,但是是app_code命名上没有区分开,造成了一些沟通障碍。开源组件amqp扩展,一个函数原来用的命名是:AMQPConnection::setTimeout():设置超时时间?读还是写,还是连接超时时间?后来他们就改为了:AMQPConnection::setReadTimeout() ,好的命名看到就知道,噢,这是设置读的超时时间哪怕是刚毕业的技术,没啥经验。这种风格也是很容易学的。这样他写的代码,就可以让别人好接手维护。
开发中三个经典的原则 单一职责:一个类,一个方法专注做一件事情。不要混合多个目标。 比如我的"绑定手机接口",目标的确是干一件事情:绑定但是里面还是要判断:这个手机号是不是被占用,还要判断绑定历史,判断手机白名单只不过把判断绑定历史,判断白名单,封装成单独的方法来调用。 把判断绑定历史,判断白名单,封装成单独的方法这样就是单一职责。以后其他地方需要用到,就调用这个方法 开放-闭合:是 1998年提出来的。对扩展要开放,容易扩展就是开放。对修改不允许,这是闭合。电脑硬件中的各个组件,是可以自己组装,方便进行扩展(cpu和内存都是 自己可以换)。但是有些东西是不能让你修改的。比如内存,电源设备,这些内部封闭起来。不允许去修改内存里面,把电源拆开修改。这样以保证稳定性。 归纳为:对扩展功能是开放,对修改内部机制是屏蔽掉,不允许修改。 接口之间要进行隔离原则:比如有多个接口,不要混合成一个接口实现。能拆分成多个接口,隔离起来。这样做的好处,修改a接口,不会污染到b接口。从另外一个角度来看,一个接口完成了多件事情,与单一职责的思想是类似的。 以上纯根据自己理解写出来。上面原则其实太抽象,空洞。需要随着编码遇到的坑越来越多,把例子举出来,会更加加深对这些原则的理解。
http://www.zhihu.com/question/19732473 本质都是为了提高效率为目的 我做一件事情,请求外部协助。外部还没响应结果,我要怎么办,一种办法是,我一直等着对方给我答复结果。另外一种是,对方主动通知我。这是同步和异步的区别。 比如水壶,有没有主动报警(当水开了时)的机制。 而我那个时候在干嘛,我可以去干别的工作,这种就是阻塞还是非阻塞。 阻塞指的是调用者。异步一般是非阻塞模式。 同步,关注的是调用者,调用者自己要不要定期去看返回结果(烧开水的人要不要定去看水壶里的水是否开)。 如果不需要看,那么就是被调用者主动通知调用者(水壶自动报警通知人),这种情况,永远是异步。 同步,异步关注的是通信状态:调用者和被调用者相互如何通信。是同步通知,还是异步通知? 阻塞,关注的是,等待时能不能干别的事情:调用者此时没有得到调用结果前,能不能去干别的事情。能去干别的事情,就是非阻塞。 同步一般都是阻塞模式。因为要卡着等待结果嘛,不能干其他事情。 --------------------------------------地铁上思考 现实生活中很多类似的例子。异步、非阻塞,都是对原来方式的提高效率。所以,应该是从现实生活中借鉴过来的 那有没有,同步非阻塞模式呢? 有! 现实生活中,人等待水壶烧开水。 人可以等着水烧开,什么都不做,也可以去干其他事情。 去干其他事情,就是非阻塞。 人自己定期去看开水开没开,这个过程,需要自己去看,是同步。 发现这样效率不高,看一次,水没有开,再看一次,也没开。于是人们想到,能不能这样的思路:水壶的水开了后,水壶自己通知人(调用者)呢?安一个报警器,鸣叫的方式通知人。这种方式是异步。 异步与同步解决的问题:是定期去看调用结果,还是有结果了自动通知去接受。 邮局的信件:我有没有来信,邮政局的人打电话通知我。这是异步。我需要隔一天跑到邮政局看有没有自己的信件。这是同步。 同步调用,异步调用,这样的叫法可能误导了我们。是从调用的角度来说的。 我理解异步,往往是与非阻塞一起使用的,否则异步没多少实际意义,并不能达到提高效率的目的。 比如,我对比现实中,我等待邮局给我通知信件。我不用去邮局跑了,但是这个时间,我们会去干其他事情(可以干其他事情就是非阻塞)。而不是等在这里,别的事情什么都不干,这样提高不了效率(的确避免了人跑去看,人会疲劳,机器没这个概念,所以忽略掉) 因为异步与非阻塞往往混在一起了使用,于是很难去区别两个的区别。实际要解决的问题不同。 归纳 通俗地记忆:等待结果的过程中,能不能干别的事情。能,就是非阻塞,不能,就是阻塞。 是调用者自己定期去看调用结果,还是被通知有结果。需要自己去看有没有返回结果,是同步模式。是被通知,则是异步。 ------------------------------------------ 网 上摘录:对 unix来讲,阻塞式I/O(默认),非阻塞式I/O(nonblock),I/O复用(select/poll/epoll)都属于同步I/O,因为它 们在数据由内核空间复制回进程缓冲区时,都是阻塞的(不能干别的事)。只有异步I/O模型(AIO)是符合异步I/O操作的含义的,即在1数据准备完成、 2由内核空间拷贝回缓冲区后通知进程,在等待通知的这段时间里可以干别的事。 下阶段,研究select,poll,epoll机制
在free命令展示机器的内存消耗情况,会像这样展示 buffered 和cached本质内容有什么区别呢? 我没搞明白。我觉得需要追根溯源会更加理解本质。 英文是这样解释 A buffer is something that has yet to be "written" to disk. 这些数据准备写到磁盘的,但还没有写到磁盘,缓存在内存中。 之所以有这样的机制,因为频繁地写入磁盘,会造成磁盘i/0,所以一般数据不会马上写入到磁盘去,而是定期积累到一定量后写入磁盘去。 buffer的英文本意是缓冲器,缓冲一下,不要马上写入到磁盘,冲击磁盘。 这个buffer大小由什么设置的呢? 不知道。待补充。 A cache is something that has been "read" from the disk and stored for later use. 从磁盘上读取数据存储到内存中缓存起来,方便下一次使用。目的是避免频繁的去磁盘上读取数据,直接从内存中读取使用了。 之所以有cache,一般是对频繁使用到的数据(读的数据),进行缓存到内存。 ------------------------------------------ 从上面分析看,两个内存区域要解决的问题和目标都不同。一个是解决频繁读的问题。另外一个是解决频繁写入到磁盘的问题。 但相同点是:他们都是为了减少磁盘i/0次数。因为磁盘是机械性的旋转,靠磁头定位来读、写数据。频繁读、写磁盘,就会造成磁头频繁定位,这个就是磁盘i/0(数据库系统的实现里面有磁盘的原理介绍,然后专门讲解实现一个数据库系统如何针对磁盘做优化) free命令中的used项(已经使用的内存),其实是已经包含了buffer和cache部分。 根据上面对buffer和cache的分析,buffer和cache部分实际上是可以拿来使用的内存区(都是缓存数据,释放掉这部分内存空间并不影响) 正因为这样,为了想要准确得知到底有多少可用的内存,linux的free命令,会第二列专门给出减去buffer和cache部分的统计结果来看。 第二列 :-/+ buffers/cache 显示两个值,第一个值,是used-buffers-cache的计算结果。第二个值 是used+buffers+cache的计算结果 思考 从操作系统角度来看,它只关注真正的物理内存剩余多少。所以buffer区域和cache区域它也认为不是剩余的。这没有错。操作系统关注是物理内存真实有多少。 而应用程序角度来看,它关注的是有多少物理内存是自己可以调用的。它认为buffer和cache部分也是可以拿过来用的。也没有错误。 现实中例子用什么来形象化描述呢? 一个人有多少钱,站在不同的角度算法不一样。 有人存款10万。我们只是看它真实钱有多少。那就是10万(操作系统这么认为) 而从另外一个角度,他有房子,如果需要,房子可以卖掉,卖成钱,估算一下价格。假设房子80万,那么他的钱就是80+10=90万(应用程序这么认为) 两个计算方式都没有错。角度不同。 buffer和cache,对于应用程序而言,觉得这部分是可以回收的(我需要的时候随时可以用。房子随时可以套现拿到钱使用)
tmpfs是一种基于内存的文件系统, tmpfs有时候使用rm(物理内存),有时候使用swap(磁盘一块区域)。根据实际情况进行分配。 rm:物理内存。real memery的简称? 真实内存就是电脑主板上那块内存条,叫做真实内存不为过。 swap:交换分区。是硬盘上一块区域 tmpfs最大可使用的大小为什么是rm+swap。 SWAP就是LINUX下的虚拟内存分区,它的作用是在物理内存使用完之后,将磁盘空间(也就是SWAP分区)虚拟成内存来使用. 它和Windows系统的交换文件作用类似,但是它是一段连续的磁盘空间,并且对用户不可见。 疑惑:window操作系统经常叫的虚拟内存和linux操作系统中的交换分区(swap)有什么区别? 一听说,虚拟内存,我常常就知道含义了。因为在学校使用window,我们可以自己设置虚拟内存的大小。可以随时调。实际上虚拟内存就是window系统下的一个这样的文件,如下: 每次看到交换分区,就一时不知道什么东西了。 现在整理一下 实际上,在window系统上叫做虚拟内存。而在linux操作系统的概念中叫做交换分区。实际上本质都是一样,都是虚拟内存。 后来看资料说,两个操作系统对于什么时候使用虚拟内存,是不同的。明显,linux的性能更好。 window平时也会使用虚拟内存。而linux只有在内存不足的时候才会用到虚拟内存? 原理基本都是一样的 区别就是windows即使物理内存没有用完也会去用到虚拟内存而Linux不一样。 Linux只有当物理内存用完的时候才会去动用虚拟内存(即swap分区)这就是两者的区别。。。 windows的虚拟内存是电脑自动设置的 Linux的swap分区是我们装系统的时候分好的区,大小是固定了的(难怪叫做交换区,把它当成一个分区的概念了) 看设计思想的不同: Linux 只不过是把交换文件设计为可以用分区而已,Linux 也可以用文件的。他们的区别就是实现方式不同而已。Windows 的虚拟内存文件好处是可以动态变动大小,这个归系统自己管理的。Windows 的虚拟内存设置默认好像是 50%-100% 内存在 C 盘上动态分配。Linux 没有这个自动的设计,但分区时安装程序会根据习惯自动分区出一个适合大小的 SWAP 分区。Windows 的虚拟内存交换文件坏处是混在系统分区里面使用,文件碎片问题和容量变动问题都会影响交换文件的效率,Linux 因为是独立的分区,所以没有文件碎片和容量变动的问题。Windows 其实也可以借用 Linux 的 SWAP 理念,单独分一个分区,只用来放 Windows 的虚拟内存交换文件。 总结:虚拟内存和交换分区,本质都是同一个东西,都是解决内存不够用时候,把硬盘当内存来使用。怎么个使用机制,window和linux稍微有不同。 tmpfs 的另一个主要的好处是它闪电般的速度。因为典型的 tmpfs 文件系统会完全驻留在 RAM 中,读写几乎可以是瞬间的。即使用了一些交换分区,性能仍然是卓越的,当更多空闲的 VM 资源可以使用时,这部分 tmpfs 文件系统会被移动到 RAM 中去。让 VM 子系统自动地移动部分 tmpfs 文件系统到交换分区实际上对性能上是好的,因为这样做可以让 VM 子系统为需要 RAM 的进程释放空间。这一点连同它动态调整大小的能力,比选择使用传统的 RAM 磁盘可以让操作系统有好得多的整体性能和灵活性。 我可以这么理解:tmpfs是一种文件系统,这种文件系统的特殊性在于,其有时候使用ram,有时候使用vm(虚拟内存,磁盘上的交换分区) mount -t 文件类型 -o option 哪个设备 挂节点 mount -t tmpfs -o size=20m tmpfs /mnt/tmp mount tmpfs /dev/shm -t tmpfs -o size=32m 设备:就是将哪个设备挂接到linux某个目录去。这里是将tmpfs设备挂接到 /mnt/tmp中去。 这样操作/mnt/tmp目录实际上就是操作tmpfs设备了。 tmpfs为什么变成一个设备了呢? vm子系统做管理工作。虚拟内存。 参考光盘文件的挂接加深理解: 光盘镜像文件的挂接(mount) #mkdir /mnt/vcdrom 注:建立一个目录用来作挂接点(mount point) #mount -o loop -t iso9660 /home/sunky/mydisk.iso /mnt/vcdrom 注:使用/mnt/vcdrom就可以访问盘镜像文件mydisk.iso里的所有文件了。 可以这里理解挂节点:linux上的一个目录,就是挂节点,要把一个设备挂接到挂节点上。
想用php生成一个mysql数据字典导出来,用到下面代码会 $mysql_conn = mysql_connect ( "$dbserver", "$dbusername", "$dbpassword" ) or die ( "Mysql connect is error." ); 在php5.5.12版本运行会提示 Deprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead in D:\soft\develop\php\wamp\2.5\wamp\www\generate_mysql.php on line 16 看来会废弃了,不建议使用了,程序无法运行的。使用mysqli or PDO 来替代。到高版本,根本无法使用这个函数了。 我想知道哪个php版本开始就会开始不建议使用这个函数了,所以去官网www.php.net搜索这个函数。有这样的介绍: 本扩展自 PHP 5.5.0 起已废弃,并在将来会被移除。应使用 MySQLi 或 PDO_MySQL 扩展来替换之。参见 MySQL:选择 API 指南以及相关 FAQ 以获取更多信息。用以替代本函数的有: mysqli_connect() PDO::__construct() 地址:http://php.net/manual/zh/function.mysql-connect.php MySQL Native Driver is a replacement for the MySQL Client Library (libmysqlclient). MySQL Native Driver is part of the official PHP sources as of PHP 5.3.0. The MySQL database extensions MySQL extension, mysqli and PDO MYSQL all communicate with the MySQL server. In the past, this was done by the extension using the services provided by the MySQL Client Library. The extensions were compiled against the MySQL Client Library in order to use its client-server protocol. With MySQL Native Driver there is now an alternative, as the MySQL database extensions can be compiled to use MySQL Native Driver instead of the MySQL Client Library. MySQL Native Driver is written in C as a PHP extension. php5.3版本开始,使用的mysql扩展是集成在php源码中发布。 1.版权考虑:原来与mysql进行通信的库(mysql Client Library),是由mysqlAB公司(现在是甲骨文)所写,那么就是在该公司的协议下发布(版权)。那么有些功能就会被禁用掉。 mysqlnd这个驱动,是由zend 公司开发的MySQL数据库驱动,采用PHP开源协议(即 PHP license)避免了任何可能存在的版权问题。而旧的libmysql是有Mysql AB公司(现在的Oracle Corporation)开发,依照mysql license。 还记得我们在编译php的时候,如果需要php链接mysql数据库,那么必须编译的时候指定一个项: --with-mysql=/usr/local/mysql 这里是指定mysql客户端库的位置。 后面就是mysql的安装目录。因为与mysql进行通信,需要按照mysql的协议来进行通信,而mysql官方是发布了客户端((libmysqlclient库),所以这里就是指定去mysql的安装目录下搜索客户端库的位置。 这样的确麻烦。 现在写入php源码一部分,会解决过去的版本发布的问题。相当于安装了php源码,就安装了与mysql进行通信的库。 是这样编译了 ./configure --with-mysql=mysqlnd \ --with-mysqli=mysqlnd \ --with-pdo-mysql=mysqlnd \ 上面的结果是,mysql、mysqli、pdo这三个操作mysql的扩展,都配置使用mysqlnd库来操作mysql --with-mysql项指定使用mysqlnd客户端库了,根本不需要依赖于mysql的安装路径了。因为是使用php源码中自己的的库。 这个库是一个c语言编写的,以扩展形式加入php引擎。 这个库就在php的源码包中,自己编译安装,就生成在自己在php中,根本不需要依赖于神马mysql提供的客户端了 所以顾名思义,叫做Native driver,本地驱动(操作mysql的驱动)。 2.内存使用效率提高。以前是复制数据两份,现在只需一份。 新版本由于扩展集成在php源码中,是php一部分。mysql_fetch_assoc()是复制一份数据到php中了。以前就是复制一份到扩展中,同时复制一份到php中。所以是两份。 总结一句话就是:操作mysql有三个可供选择的扩展,mysql、mysqli、pdo。而mysqli and PDO MYSQL作为推荐的扩展。而不是mysql原来的扩展 给我们做接口服务的启发 到高版本,没有兼容旧太旧版本的函数,为什么这样子?从php官方组织维护源码角度来说,这个函数肯定是没啥优势了,去进行优化这个函数,还不如对它进行废弃掉 我们在网站,需要提供给接口给公司内部其他子系统调用。也会存在接口升级,原来的接口设计有缺陷,不想去修复了。干脆建议使用新版本的接口了。 思考,我何不也弄一个类似的提升呢。提示调用方升级到新接口去使用。 突然发现,从沟通成本角度考虑,把提示信息放在接口返回给调用方,会推进改进的速度。 为什么官方要把这个函数禁用掉?而不是去修复,优化呢? 性能考虑,安全考虑。去官网找答案 Recommended API It is recommended to use either the mysqli or PDO_MySQL extensions. It is not recommended to use the old mysql extension for new development, as it has been deprecated as of PHP 5.5.0 and will be removed in the future. A detailed feature comparison matrix is provided below. The overall performance of all three extensions is considered to be about the same(三个扩展的性能不相上下). Although the performance of the extension contributes only a fraction of the total run time of a PHP web request(性能考虑在php的web请求中是一小部分考虑,即不仅仅是性能考虑). Often, the impact is as low as 0.1%(影响是很小的,0.1%) 看官网介绍,不是性能考虑才弃用那个扩展。反正就是弃用,不想去维护那个了。我也没搞清楚。不过深有同感,旧的扩展代码量也不少,去优化,接口结构始终无法达到质的提升,还不如新开一个? mysqli扩展使用了mysqlnd这个库来操作数据库。更加推荐。
刚才看到文章这个看法很有同感,以前也没有深刻理解到可维护性的重要性。在现在的公司呆了一年半,才明白。因为现在的公司用户量大,团队开发人员多,遇到很多难以维护的代码,花费人员沟通成本,延缓功能的开发进度,去填补遇到的坑..... http://www.cnblogs.com/freeflying/p/4788494.html 一、性能不是不重要,而是他没有可维护性重要。要理解这一点,首先要理解可维护性的重要(请再读上一篇我花数周找bug的段子);然后要明白:解决性能问题,我们可以有很多代码以外行之有效的方法,而可维护性基本上就只能靠代码了;最后,还是要牢记:没有牺牲,就没有胜利!二、所以,在绝大多数情况下,当性能和可维护性相冲突的时候,性能让位于可维护性。我们采用其他办法来弥补代码性能不够高的问题。优化首先需要找到性能“瓶颈”。否则,任何人都可以随手一指,“这段代码需要优化”。可读性更强的代码总是更好优化硬件永远比软件便宜(即硬件比技术人员便宜,看系统规模而定) 合理浪费堆硬件 说了这么多,不知道有没有引起同学们的反思。可能大家还是过不去心里那道坎:明明有一种性能更高的方法我们为什么不用? 因为浪费呗! 什么?你有没有搞错?我的代码,至少省了一块内存条!那是你还没从“穷学生”的角色里转换过来。你花一周的时间对代码进行了优化(就先不考虑你的优化带来的维护成本增加了),为老板省下了一块内存条的钱。你以为老板会拍着你的肩膀表扬你么?老板打不死你! 兄弟,账不是你那样算的。当你是学生的时候,你的时间成本是0;但你进入工作岗位,每一天都是要发工资的。 通过代码来调高性能,是一种无奈——对硬件性能不够的妥协(参考:80年代游戏开发者的辛苦困境。这样写性能就高,但为什么现在没有谁再这么写代码了?)。否则,绝大多数情况下,堆硬件比优化代码的效果好得多,而且便宜得多。硬件的成本按摩尔定律往下降,我们程序员的工资也能按摩尔定律减么?(注:硬件越来越便宜,而人员工资越来越增加,就拿工厂的普通工人用工成本也在增加) 明明window 10 比window 95更耗性能,为什么今天没人用window 95?为什么VS 2013要10G的空间我们都还屁颠屁颠的赶紧装上?为什么现在大家都用C#,没人用汇编?(注:汇编比高级语言性能强,接近机器) 我们站在人类文明积累的今天,就应该理所当然的享受这一切成 果。有打火机你不用,你要钻木取火。如果你是因为要学贝爷荒野求生装逼,可以理解;如果你说你是因为怕浪费天然气,我……我……我怎么说你呢?“给做打火 机的一条活路,行不?”同样的,程序员大神同学,你就当做好事,给下面写底层做硬件的一条活路吧!你的代码都是 010001000010000001010101……了,你让其他人怎么活啊? 最后,我突然想到的一个程序员为什么对性能如此敏感疯狂,对可维护性毫不在意的一个可能原因: 性能很好理解,卡得要死和跑得飞快;可维护性很不好理解,至少得跑个两三年才能体现,那时候,谁知道爷在哪里偷着乐呢 性能上不来,程序员只有羞愧的低着头,都是我的错;需求有变更,开口就骂,“哪个SB又要改……”; 大家觉得是不是这样的?所以,愿意把代码百炼成钢绕指柔的人少。想来,是一种莫名的悲哀和凄凉。 某网友说: 感觉性能问题属于眼前问题,可维护性问题属于以后的问题。很多人就是感觉先把眼前自己的事儿处理完就行,程序只要能运行,老板满意就万事大吉,至于以后维护爱谁谁吧,那个时候老子已经另谋高就了,烂摊子留给后面的倒霉蛋吧。 这段话的确说出了目前的现状,被逼的,上面只看功能完成与否,功能完成的质量不管。那还关系代码的可维护性干嘛。于是把代码复制,拷贝函数,只要能跑起来就好了。 性能、可维护性从来都是要折中,过分从代码中追求性能会增大开发成本、降低可维护性。现实中老板、客户真不在意那点硬件成本。只要项目快点上线。二期三期四期优化都好说。 张口闭口 谈性能的 经理 我之前在北京 也见过 几个。 最后 用几个 实际的项目,堵住了他的嘴 —— 别跟我谈性能,即使我用滥反射,性能也比你的快。 同意,除了核心功能和高并发的页面,一直以来写代码的风格,首先就是可读性,可维护, 然后再是性能。 我的思考: 性能问题:关键是找到性能的瓶颈点。而不是纠结于细微的。也就是主要矛盾。把主要矛盾解决掉后,就好多了 就拿php来说,是解释性语言,解释性语言是比不上编译型语言快。但是在web应用中,不涉及到复杂的cpu计算(简单的增删查改数据库,不是分词这么复杂的算法,这种属于cpu密集型),瓶颈在磁盘读写(磁盘i/0)和数据库上。 所以看不出php的劣势出来。当达到facebook这样的应用,他们就会感到一点点php优化,对整个成本的减低,省去很多服务器。 所以有规模,就是规模成本。经济学中有个规模效益。通俗例子理解,生产100个要这么多成本,生产1000也是差不多的成本,但是可以得到更多利润。所以只能把规模扩大,才能赚到钱。 你达不到那个规模的时候,去谈优化性能,带来的是得不偿失的。 而且,就算是解决性能问题,关键是找到瓶颈在哪里,解决了瓶颈,就会带来质的变化。而不是纠结于细节优化。主次矛盾要分清楚。 单纯说性能,那直接用汇编写代码,发明高级语言干嘛,汇编语言更加接近机器二进制,所以性能更强。但是汇编语言不容易维护,因为难懂,难上手。不接近人类的思维习惯。 记得国外有本书中提到一个观点:代码是写给人(对象是程序员)看的,不是写给机器看的,如果是写给机器看的,那么,直接使用二进制01011这样的方式去写代码呢,机器识别就是二进制。而且,性能更强,不用经过中间转换,是不是。
有个资料看得我云里雾里的。现在用自己的言语来总结一下,写文字,能够加深自己的理解。也会在写的过程中帮助自己发现理解方面瑕疵,继续查资料求证。 短链接的缺点:创建一个连接,程序执行完毕后,就会自动断掉与mysqlserver的链接。于是多少次php执行,就会多少次这样的创建和释放过程。频繁地创建和释放连接,比较耗费cpu资源。 长连接就可以避免每次请求都创建连接的开销,节省了时间和IO消耗。 长连接是提高了性能。不过还有一些细节的问题需要解决,即mysql发现一个链接长时间没有执行查询请求,就会自动断掉这个连接。 具体多长时间后断掉,有个timeout设置时间。通过sql:"show global variables like '%timeout';" 查看。 my.conf中的 wait_timeout=2880000interactive_timeout = 2880000 当链接已经失效了,仍然去执行查询操作,一个明显的表现形式就是提示:MySQL server has gone away 启发:MySQL server has gone away这个信息是mysql服务器提示出来的呢?还是php的mysql扩展提示出来的呢? 据判断,肯定是应用程序服务器报出来的(php)。想一想,如果mysql都已经接到请求了,那么还出现什么链接不上。明明都已经链接上了。 既然mysql服务器都能够接受请求,那么还怎么处理不过来呢。 我们去百度搜索:MySQL server has gone away。从来没有看到java链接mysql出现这样的情况。如果是mysql 服务器报出来的。那么应该与应用程序无关。所以应该也会搜索到相关信息的。 据此判断,这是php抛出来的信息。php链接不上mysql了。 http://ronaldbradford.com/blog/sqlstatehy000-general-error-2006-mysql-server-has-gone-away-2013-01-02/ 使用mysql_ping()函数能够检测与mysql服务器是不是链接状态。避免出现MySQL server has gone away。 每次执行查询前,先使用mysql_ping()去检测一下连接有没有断掉。如果断掉了。重新建立一次链接。 具体代码为: if(mysql_ping()!=0){ //链接已经断开,需要重新建立链接 $this->conn = mysql_connect($ip,$user_name,$password); } 小缺点是:每次都要去检测执行mysql_ping(),耗费资源。 一种改进办法是:根据mysql_query()的返回错误码来决定是不是要重新链接 $res = mysql_query($sql, $this->conn); if($res===false){ if(mysql_errno($this->conn)==2006 || mysql_errno($this->conn)==2003){ //去检测一下与mysql服务器的链接是不是有效 if(mysql_ping()!=0){ //重新建立链接 } } } 备注: 2003对应的错误信息是,Can't connect to MySQL 2006 对应的错误信息是 MySQL server has gone away思考:真正意义上自己实现的连接池,是长期与数据库服务器链接起链接的。如何建立起链接呢。就是定期发送心跳包。通过心跳包与服务器进行通信。如果没有发送心跳包,则会被数据库服务器断掉这个链接。因为长时间没有通信的链接,要断掉。待完善MYSQL has gone away的解释:http://database.51cto.com/art/201105/261107.htm
看这篇文章:http://www.cnblogs.com/greyzeng/p/4077732.html 对评论引发我的思考。 网上有人说这句话我赞同: 优化和重构是两个概念啊,楼主还是没有搞清楚优化不宜过早主要指的是性能的优化不宜过早,因为很多性能优化其实没有对系统有明显的提升。而重构主要指的是修正代码中不好的味道,提高代码的可读性和可扩展性优化的确不宜过早,但是重构是应该持续在整个开发过程中的当需求比较稳定的时候,就应该考虑通过重构来整理代码 另外一个人的观点: 我们的做法是,将重构这件事当做一种思想,还不是行为来做。简单的说,就是在日常的开发中,发现任何一个别扭的地方,就可以在自己时间和质量可控范围内,进行完善,并且不要求自己一次性做到尽善尽美。经过了一段时间的积累就会,重构就会由量变转为质变。不管当初的设计有多好,代码都需要持续不继的成长和改变,所以,重构也需要当作思想,在任何一次开发时持续来做。 ----------------- 启发思考:量变到质变。而不是专门花费时间,丢掉手头开发的新任务,新需求,通过这样的方式来重构,成本太高,风险太大(业务部门压力)。的确不可取。在实际工作环境的确时间上不太允许。 像打扫房间,清理桌面,定期清理一下,还一还技术债务。每一次改一点点,慢慢就会形成量变到质变了。我发现这样的方式还不错。看到有坏的代码,在力所能及的范围内重构一下,把代码抽一下。等到病入膏肓的时候,再去重构,所花费的时间将会是绝大的,而且面对一堆量大的工作,根本就没有信心开展下去。内心会放弃。 看别人优秀的代码,对自己是一种提高。看别人不好的代码也是对自己的提高:知道自己以后不要这样子写。 以博客园和CSDN为代表的社区中,普遍认为重构是修改代码,这完全是错误的观点,一份工单的完成就是以通过测试为标志,一旦通过测试,谁都没有权利或者义务去修改代码(成本是需要买单的,是企业承担还是让程序员免费劳动?),如果后来发现性能问题或者BUG,请重新开出工单,重新核算工价,这些都必须体现在开发成本当中,出于成本的考量,设计师自然会把性能测试的工单投放到最合适的时机 这个观点很奇怪: 真正的重构绝不会修改代码,而是以消灭代码为目的的增加代码,对程序员来说,就是一份份普通的新开出的工单,并且重构是设计师的日常工作,是企业行为,绝不仅仅针对某个项目,重构影响的是未来,长期的看,重构会使新增的代码量趋近于零 启发性思考: 有些人是做事业,有些人只是做一份工作,或者是混日子,混工资生活而已。 那也告诉我们,精兵强将的思路招聘人的话,的确是要花价钱招聘精英,实际上是一个抵住10个普通的热。 那么问题来了,如果花不起钱请呢?我觉得,也并不一定要招聘厉害的。那么退而求其次,招聘有理想、有事业心的人。愿意把工作当成自己的理想,至少说是有人生理想,信念追求的人,这样的人会为了理想而奉献,勤奋,执着行动。 在看到唐太宗的讲解中,唐太宗注重有信义的人,就是那个时代的德。这样的人,当信念与利益冲突的时候,会选择坚持自己的道义(信念)。而不是唯利是图的人。 所以并不是要能力强的,能干的。而是有事业心追求的人。
下面这篇思考,是在地铁上突然想到,然后把理解用自己的文字写在手机上。 扇区和磁盘块的区别是什么? 这么多的单位真的很难记忆,很难区别,最好是自己了解原理。物理层面分为磁道,扇区。磁盘块是个虚拟出来的概念,是操作系统中的。操作系统为什么要虚拟个这样的概念出来呢?操作系统与磁盘打交道的最小单位是磁盘块。目前是4k大小。操作系统操作磁盘,也需要通过磁盘驱动器进行。所以离不开扇区的。最小单位,好比我们生活中约定最小单位是一毛。没有一分的单位了。为什么要这样,方便管理?扇区是真实的东西。磁盘驱动器操作磁盘数据,每次都按照扇为最小单位操作。簇也是操作系统弄出来的概念(不禁问,整这么多概念干啥呢)这好比汉语中干嘛整这么多成语,什么叔叔阿姨之类的称呼干嘛呢,全部叫你不就省事了么?有的题目会问,磁盘的读写单位是?千万不要联系到操作系统层面去了。读写基本单位是扇区。磁盘驱动器是按照这个单位操作磁盘数据的。又没特意指明操作系统读写磁盘的基本单位。文件系统就是操作系统的一部分,所以文件系统操作文件的最小单位是块。块,听这个词语会明白,是抽象概念。真的有块形状的东西吗? 是因为我们老喜欢叫磁盘块,磁盘块,这个块让我们以为磁盘的基本单位是块。当我们说块的时候,是从软件角度(即操作系统)来说的。因为我们编程大部分是在特定的操作系统上运行,与硬件打交道不用我们关注,交给操作系统去处理。本来操作系统的一个任务之一就是与硬件通信,控制各种硬件(不然怎么叫操作系统?操作硬件的系统)由于操作系统以块为单位操作磁盘,于是,我们不会去提扇区,而是总说磁盘块。当介绍磁盘原理的时候,才会说扇区,磁道,盘片之类的概念。这些本来就是实际存在,摸得找的部件。比如扇区,在盘片上,的确是像扇形状的物理区域磁盘块与扇区大小问题既然磁盘块是一个虚拟概念。是操作系统自己"杜撰"的。软件的概念,不是真实的。所以大小由操作系统决定,操作系统可以配置一个块多大。一个块大小=一个扇区大小*2的n次方。N是可以修改的。顿时我思考:为什么磁盘块大小必须是扇区大小的整数倍呢?因为,磁盘驱动器,磁盘附带的硬件设备,与磁盘读写数据,操作系统也要靠它。它读取磁盘数据就是扇区的大小。一个扇区是512字节。有些硬盘厂商会提供4k大小扇区。这是物理结构。磁盘定下来的结构就是没法修改的块与页总是迷糊 操作系统操作需要与内存、硬盘这两种硬件设备打交道。 都需要虚拟一种单位来操作。与内存操作,是虚拟一个页的概念来作为最小单位。 与硬盘打交道,就是以块为最小单位。
以后慢慢添加经验。随手把遇到的问题记录下来。 一、地址要定义在变量中,千万不要写死在代码中。 遇到一个坑。把地址都写死在模版或代码中了。以后要修改地址,变得很困难 下面是遇到的问题,要将通行证的地址修改为新版本的地址,这个应用中代码请求要改为请求新版本的地址。我去修改的时候,就变得麻烦。要修改的地方非常多。 解决办法:把地址定义在一个变量中。多处使用地址的时候,就引用这个变量。 定义一个js变量放在头部模版中定义。这样模版的其他地方引用这个变量。这个js变量的值来源于后端php赋值。 以后要修改,就方便多了。直接在php中修改即可了。 二、文件的命名要清晰表达意思。不要用拼音,拼音很难识别含义。也尽量不要使用简写。比如简写o,很难知道这个o表达什么意思。 header.php,我去维护的时候,发现多了一个oheader.php。 看文件名称很难知道这个文件是干嘛的 去看代码,里面差不多。只是疑惑了:不知道为什么,有的引入的是header.php,有的引入的是oheader.php 区别是什么呢?接手的技术很难明白。 代码的维护性减低了很多。 三、多余的文件不要残留在目录里面。尽量避免拷贝文件重命名的方式 ----------------------------------- 其实这些知识与技术知识的无关。不是技术知识的部分。是人做事情的一种条理性和思路清晰的体现。乱的代码体现了一个人大脑是乱的,没有清晰的规划。 比如说,多思考某一天修改了域名怎么办呢?考虑到变化的部分。 路径其实不会修改。域名修改是很正常的。因为还会是同一套系统,那么里面的路径是一样不会变的。所以我们常常会习惯把域名部分定义成变量。 我的经验是,同理心思考(换位思考):怎么样让自己写的代码清晰,容易让接手的技术明白呢?我会特意问对方哪里不容易理解。这样验证自己哪里做的不够好。哪里还可以更加通俗化点,比如需要增加一些注释特意说明。 站在接手你代码的人角度去考虑问题。这样自己写的代码不会被对方给鄙视。 大道至简啊。把复杂的事情通俗化,简单化,这样能够锻炼出自己的能力。
Strict standards: Only variables should be passed by reference 网上查到资料有这么一句话: 在php5.3以上版本会出这个问题,应该也和php的配置有关,只要把这一句拆成两句就没有问题了。因为array_walk的参数是引用传递的,5.3以上默认只能传递具体的变量,而不能通过函数返回值。当然你也可以修改php.ini 里的 error_reporting = E_ALL | E_STRICT,但这终究不符合规范。 $suffix = array_pop(explode(".",$file_name)); 为什么高版本的会这样子限制呢? 我去看手册中的函数原型: mixed array_pop ( array &$array ) bool array_walk ( array &$array , callable $funcname [, mixed $userdata = NULL ] ) &符号指定了变量传入进去,是引用方式传递。 改为: $file_name_arr = explode(".",$file_name); $suffix = array_pop($file_name_arr); 就可以。 实际上也只是报出一个警告。不是致命错误。正常结果是能够得到。比如这里得到文件名的后缀,是正常的。
从网上看了一些资料,为了方便自己理解,于是把它的编码原理,自己放在excel表格中清晰列出来,方便以后查阅。做的图如下: ascii编码表 这个表很大,截图不出来。网上有。0-255的整数表示256个字符,即2的8次方=256。2的7次方是128。 参考:http://1024tools.com/ascii,图很清晰。 思考:base64编码算法,要求先去ascci表中对应字符的整数值。而ascci编码不支持中文的。从这个角度来看,base64_encode编码其实不支持中文编码的。具体中文编码后是什么情况,有待做一下试验后补充。 base64编码对应表 在程序中,用一个数组表示就是这样: $base64_char = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 比如整数24,那么就对应数组第25个元素:Y。 网上用的移位法,使用>>和<<,没有看明白怎么回事。根据编码的思路,拙劣地用php模拟实现了一个,对比一下与php内置的base64_encode()编码结果是不是一样 $string = 'abcde'; echo 'origin php 64 encode:'; var_dump(base64_encode($string)); function my_base64_encode($string = "") { $string_length = strlen($string); //看余数 $mod = $string_length % 3; //echo 'mod:<br />'; //var_dump($mod); $add_bin_value = ''; switch ($mod) { case 1: $add_bin_value = "0000000000000000"; break; case 2: $add_bin_value = "00000000"; break; } $group_count = ceil($string_length / 3); //得到多少组 //echo 'group <br />'; //var_dump($group_count); $string_arr = str_split($string, 1); $all_bin_value = ''; foreach ($string_arr as $key => $value) { $ascii_int = ord($value); //得到这个字符在ascii表中的对应的10进制整数 //然后把这个10进制整数,转换为二进制 //echo '$ascii_int:' . $ascii_int . "<br />"; $bin_value = decbin($ascii_int); //由于ascii的范围是0-255,如果是84,54这样比较小的值,这个函数会得到是一个1-7位的二进制。为了做到8位。于是增加判断一下,最高位要加0填充 $str_len = strlen($bin_value); if ($str_len < 8) { $add_zero_count = 8 - $str_len; //要增加这么多个0在前面,以便补充8位 for ($i = 0; $i < $add_zero_count; $i++) { $bin_value = '0' . $bin_value; } } //echo '$bin_value:' . $bin_value . "<br />"; $all_bin_value.= $bin_value; } $all_bin_value = $all_bin_value . $add_bin_value; //var_dump($all_bin_value); $bin_group_list = str_split($all_bin_value, 6); //每6个二进制一组,进行分组 //分组结果: //var_dump($bin_group_list); $base64_char = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; $base64_char_arr = str_split($base64_char, 1); //var_dump($base64_char_arr); $dec_int = 0; $base64_encode_result = ''; foreach ($bin_group_list as $key => $value) { if ($value == '000000') { $base64_encode_result.='='; } else { $dec_int = bindec($value); //将二进制转换为10进制整数 //echo $dec_int . "<br />"; $base64_encode_result .= $base64_char_arr[$dec_int]; } } return $base64_encode_result; } $base64_encode_result = my_base64_encode($string); echo 'encode result:'; var_dump($base64_encode_result); View Code 思考: 1、为什么要按照6位二进制一组进行分,而不是4位,7位等方式呢? 因为要考虑到只有64个字符供选择,十进制只能从0到63。最大值63。使用6位,2的6次方,值是64。 于是使用6位二进制的话,就能刚好对应出来。如果使用7位二进制。那么最大值是2的7次方,最大值是128。 假设计算得到的是数字是112,那么base64编码表中如何去对应呢? 2、为什么要每三个字符为一组呢?不是2个,也不是4个字符一组。是什么考虑? 不太清楚。尝试去分析一下,3个字符,,刚好是3*8=24位二进制。 24个字符要分成6个二进制一组。就是4个组。 2个字符呢,2*8=16 16/6也无法整除 如果是4个字符一组,4*8=32个字符。32/6 无法整除。 5个字符:5*8=40 40/6也无法整除。 6个字符一组,6*8=48 48/6=8 刚好可以整除。怎么没用这个方式呢? 应该是取小的原则。2个字符不可以,3个字符刚好可以。宁愿取小的。 待完善
Pass_proxy走内网,被请求方的php使用remote_addr得到就是转发机器的内网地址,如192.168.10.141这样的。走外网,被请求方php的remote_addr得到就是转发机器的外网地址,如118.198.10.141。 如何会影响走内网还是外网呢? host中进行域名绑定会影响。将域名绑定到内网。得到就是内网地址。将域名绑定成外网,就走外网地址。 上面的情况导致线上的问题:转发到a.test.com,a.test.com上的php获取$_SERVER['REMOTE_ADDR']变成了公网地址,即转发服务器的公网地址 先记录下来,待完善。以此方式提醒自己以后要补充
什么是内存泄漏,我以前以为是内存被人可以拿到里面内容。 现在发现概念是内存区域没有释放掉。内存泄漏造成的表现形式是,进程越来越慢。慢的原因是,它申请的内存越来越多,没有释放掉内存。而操作系统对进程的内存占用有限制。操作系统会将大的进程置换到磁盘去。换到磁盘去就导致速度慢了。主要是磁盘的速度跟不上。这让我联想到以前自己的系统,变得那么慢。磁盘原因。虚拟的内存,不是真实占用这么多物理内存。而是需要申请更多内存 。操作系统只能将这样的进程换到虚拟内存中去,虚拟内存就是在磁盘的空间。 为什么内存占用越来越多后,就会导致进程慢呢?根源是什么? 感觉与内存溢出一样?应用程序可以申请很多内存,操作系统并没有对进程申请多少内存进行限制,也就是说,可以无限度地申请。为什么操作系统没有做限制?不知道!操作系统没有做限制,需要的时候就分配内存,这就是动态创建内存叫法的本义。如果没有更多内存可用呢?内核程序就处在等待分配中,机器挂起状态。这是虚拟内存,操作系统没有更多内存申请,就会调度。这个调度算法是怎么算的呢? 一个是存储的数据,内存存不下了。 溢出,想象装水的容器,水装不下就会溢出。内存也是这样。为什么有些解释法是,内存区域没有指针指向它。这种内存空间就是内存泄漏。Linux上有什么工具可以检测内存泄漏呢? 原理是不是检测内核对内存的申请,有没有处在等待中种。申请不到内存就会是等待中。思路是检测内核。内核的调用非常频繁。可以实时监测到。 一般一个操作系统上对进程使用内存的最大限制是4g。32位操作系统是这样。大的进程移动到磁盘需要花费时间(磁盘i/0) 如何才能让自己用这个知识解决php的问题呢。内存泄漏,将内存耗尽。什么情况会?因为没有释放掉。内存溢出,申请不到可用的内存。为什么站在操作系统的角度来说泄漏的呢?参考http://m.111cn.net/art-55371.htm。Bad-gatway,表示的是很累。而502则是超时。 引用计数的变量,只有在引用数变为0时才释放掉。网上说,常见的bug就是没有将引用计数器减1。导致没有释放掉。有垃圾回收机制可以让程序员省心。关键是这个垃圾回收机制要怎么设计。删除引用数为0的变量?一个进程占用20m。怎么理解这个进程调度呢。如何调用哪个进程处理呢。
这里系统专门指的是那种用户量大的系统,比如有几百万或者上千万的注册会员。因为小系统因为用户量少,不存在这种思考,考虑有时候是多余的。另外还有内部系统,给自己公司内部人员使用的,即便是出现了问题,也不会造成很大的问题,内部协调一下即可。 而针对客户的系统,公司的收入和价值来源于给客户提供稳定的服务。这是关系到公司命脉的。如果系统不稳定,在客户心中造成的印象就会不好。 快速修复与稳定测试之间的权衡 如果线上系统出现了bug,用户反馈问题。作为开发人员,肯定要修复bug。是马修复代码后上传到生产环境,还是在灰度或测试环境把修复的代码测一遍后,再上传到生产环境呢? 有时候为了快速解决线上问题,所以修改代码后,就想发布上去。大一点的网站,都要走发布流程,填写发布单的。不能随便ftp上传代码的。都是业务系统,一点问题会存在影响。 看《淘宝技术这10年》里面也出现过类似问题,改改,编译(java语言要编译)好后发布上去,发现还是有问题,又得重新找,一天过去了。 我自己也有类似的体会:有时候发现bug,想快速修复bug,就懒得在灰度测试了。于是发布到线上。但是会出现其他问题来。 有的时候还会犯低级错误。 比如我自己亲身经历过好几次了。一次是邮箱的激活状态。发现有这个bug,去修复,想快速修复,在测试环境测验了后,程序是没问题,但是发布到线上,就出现问题。 这次不是程序出现问题。是没沟通好,不应该改为激活状态。这种办法只是一个临时办法,没有从整体角度考虑,即其他系统也会用到数据库的状态,根据这个状态来拦截发广告行为。这样改掉就造成数据错误了。 很多人都有类似的习惯,干脆懒得测了,自己觉得有信心,就发布到生产环境去(身边一些开发人员改好代码,自己不测试,直接发到灰度去给测试人员测试,实际上还是要打回来。这样来回折腾的耗费的精力和时间其实还更多) 表面以为快,实际上并没有快。有时候,我们修改简单的功能,发布上去,没有出现问题,于是就养成这样的习惯了。几次没有出现问题,但是某次就会出现意外,造成了系统的不稳定,也让开发人员到处救火的行为,比如这里修复好后,出现新的问题,继续修复,到处救火弄得精疲力竭的。 我如今越来越有如下感悟:追求快速可以,但如果追求快速,质量得不到保证,这种快速有多大意义呢?为了保证质量,宁愿慢一点,放到测试环境和灰度环境把问题还原出来,测验没问题再发布。 靠人的经验和能力来控制是否靠谱 开发人员出于自信和经验考虑,觉得自己修改的东西不用经过测验,反正就修改那么一点点代码,我有信心保证不出错。 我现在发现,靠人的经验来保证质量,不太靠谱了。因为任何人再厉害,都有犯错的时候,都会有疏忽。比如今天刚好因为家庭有事,情绪比较低落。修改代码就忽略掉一些部分了。眼睛看失误了。或者今天睡眠不充足,或者今天心情不好。就会造成类似的事故出来。一个人的大脑负担多的时候,就更加容易失误了。 靠一个人的智力终究有限,怎么防火才能从源头上来解决问题。如果能够设计一种办法,哪怕是人会犯错,但是可以纠错,就会好很多。 很多时候之所以那么赶,是因为觉得自己再去测验浪费时间,还有上面也不给你时间来测验? 这方面的投入时间相比后续出现问题再去救火到处的成本,是非常值得的。 古代人说,慢工出细活,的确非常有道理。编程就是一个慢共出细活的工种。心理越乱月容易出错。 线上的bug怎么处理? 分清楚优先级,重要程度。如果影响的面不是很广,只是一部分用户。可以放在测试环境把这个问题还原出来。这样确保找到了原因,再去修复问题。 避免越修越乱的局面! 没有找到确切的原因,像苍蝇一样,各个去尝试,这样会造成更多的问题出来。以前只是影响一部分用户,后来影响更多的用户了。得到反馈回来,这个时候会惊动更多人(比如产品、老板),开发人员得到的心里压力会更大。这样干的也不愉快。产品对系统频繁出问题也心里不爽,反馈到老板那里,老板也觉得是这样,开发人员也因为受到压力干的不愉快。最后是一个双输的局面。 总结:不要因为线上出了问题,为了快速修复bug,而忽略掉了节奏。开发人员能够做到面临外界压力,不乱其实是一种心里素质。 如果乱,心智不稳定的状态下,还会造成更多的问题出来,以前的修改代码就白劳动一场。有时候要庆幸现在自己冷静,没有去乱动,还没有因为乱动而造成更多问题(到时候吃不了兜着走)。 以前我的思想是,既然是面对用户的系统出现了bug,那么就要快速修复,我或多或少是出于假设某天我的公司遇到类似问题我应该如何办的思维模式来做事。 面对用户的bug,会引起我的特别重视。但是后来我发现,完全这样子也不行的。要权衡一下质量。如果没有质量保证的修复,那就会造成其他问题的出现。其实有些用户是可以缓一缓的,没想象那么紧急。比如线上程序就的确有这个bug:在app注册后,跑到pc版本去登录,需要邮箱激活。我仔细跟产品沟通会发现,没有想象的那么重要。周五本来想发布修复bug,但是可以缓到周一去发布的(这样有足够的时间来测验修改代码后造成的影响)。我没有抓住里面的关键点,目前只有这一个用户反馈这个问题。没有出现大面积的用户反馈。 因为通过手机app注册的用户,并不像我们想象那样,会去pc端页面进行登录。所以用户没有遇到这样的问题。 由于一个瓶子放在桌面上,每个人观察到的面是不同的。我们会忽略掉一些看不到的面。 我们内部开发人员观察到的永远是bug,因为产品反馈给我们了,我们看到的就是这个bug这一面。但是我们没有从整体角度来考虑。我们只是关注这是一个影响用户登录的bug。 我们以为改掉可以很有成就感,但可能是杨白劳,周五去发布,如果无法确保自己的代码不会造成其他影响。就少干。原地不动,反而风险更低。 顶着错误前进,错误次数多了后,就会是经验。有人宣扬,人生没有后退的路子。但我在想,如果一个人无法从错误的经历中吸取教训,避免下一次犯错,那么还是一样的浪费折腾。比如修复bug的事情,如何权衡,这样的错误继续再犯,总是到处救火。还是没有形成稳定的局面。
对文件系统原理学习的理解 按需分配。打开的文件属性才放入内存中。关键点是记录某个文件用到哪几块磁盘地址。一个文件可能占多个磁盘块,可能是一个。关键在于文件的的大小文件名,磁盘块地址,多个。连续分配:一个文件占据了3个磁盘块。是连续在一起的。读的性能很好,读一个文件的内容只要一次操作,找到第一个磁盘块,后面的磁盘块就知道了。不用继续旋转。缺点是,磁盘碎片多。要想避免碎片,要多进行碎片整理(不显示,太耗费性能)。所以这种方式不适合变化大小的文件。适合固定大小。比如CD文件。链表分配:解决磁盘碎片。文件占用三个磁盘块,这三个磁盘块可以在任意位置,很方便扩充文件的容量。比如文件增加内容,要加一个磁盘块,随便去哪里拖一个磁盘块挂上去。有一个指针指向磁盘块地址。缺点是,随机读写耗费性能。比如要读一个文件的第6个磁盘块的内容,必须从找到文件的第一个磁盘块然后顺着顺序第二,第三.....第五全部读到,才能知道第六个磁盘块在哪里。为了提高速度。把文件的链表放到内存中。在内存中存储链表。多少个文件。就要多少个链表项吗?为了解决占用内存过多问题(磁盘容量比内存大多了。 疑问,这个链表项是如何设计的呢。链表这种数据结构要看看。。文件名,目录是特殊的文件。目录下有子目录如何实现的。文件最终在目录下 文件系统几种实现方式 文件系统要解决的本质问题是,一个文件在磁盘上如何组其内容。然后记录这个文件分布在哪几个磁盘块上,这样当读取文件内容的时候知道去磁盘哪里找。1、连续分配法一个文件的内容存储在连续的磁盘块上。这样实现很简单:记录文件第一块磁盘块位置,然后记录一个往后推的数字号。做简单的加法即可找到内容。缺点:造成磁盘碎片。发生在删除文件的时候,这个文件占据的磁盘块就是空闲状态。刚开始不会有这问题,因为磁盘有足够的空间可用。不用管这些空闲块。但是后面磁盘空间不够用的时候呢,还是要来使用它们。如何让新加的文件使用这些空闲块呢? 你存入数据之前必先先知道这个文件大小,文件大小要是固定的。比如5k。不是固定的话,文件内容增长,超过5k后呢?这样计算大小的方式实际好不好?假设你打开一个wps进行编辑,必须要先问用户,你的文件打算多大空间。这样才能去选择合适的磁盘块存储这个文件。用户怎么知道?2、链表分配法磁盘块形成一个链表。串起来。相信我们生活中用的链子,是不是一个节点一个一起串起来的。磁盘块看成是节点。每个磁盘块第一个字节,存一个指针,这个指针就是指向下一个磁盘块。想象一下房间号。找出一批量房间给这些运动员的居住的。现在明白了,内存中的文件分配表。实际上就是为每个文件做一个条目。这个条目里面记着磁盘块的指针,指针是从开始到结束,按照顺序来的。 为什么window要进行分区而linux系统不用分区? 现在明白其中原理了。 window 之所以要进行分区。就是因为,它需要区分不同的文件系统。当你请求一个文件时,给出路径,操作系统就能知道向哪个文件系统去请求,就是因为分区。一个分区只能有一个文件系统。window通过盘符(c,d,e,f等字母)来确定文件系统。 Linux不需要分区。因为它做了一个抽象层。来管理所有文件系统。操作系统针对这个抽象层来获取数据。使用挂载的方式。a 和b两个磁盘可以挂载到同一个目录下去。读取文件的时候,关心的是所有文件系统的抽象层操作,可以理解成接口。 分段和分页技术 至今都没搞明白,段和页的区别。页是操作系统的看法,它将内存划分成一块一块。有什么用呢?搞清楚操作系统为什么要这样子做。难道是为了解决内存不够的问题吗? 分段技术早于分页技术。最先有分段,它的特点是,程序需要多少内存空间,就以多少空间为整体换入磁盘,在磁盘和内存之间移动。这里的段很形象解释了,程序运行在哪个内存区间,这就是段。分段,以段为单位来分配内存?这样效率并不高。提出一个分页思想,通俗就是等份大小的内存。明显的问题我还没看到。 操作系统为每个进程分配时间片 为什么操作系统要设计成抢占资源的方式呢?这一让每个任务都能得到处理 如果一个任务将要耗费很长时间那么,它就会占着cpu,结果导致其他任务无法处理。现实中有这样的思想和案例。上下文切换,是不是指的就是这种调度呢?上下文切换:保持当前进程状态(应该是放到磁盘上去?),然后把另外一个进程调入进去供cpu执行。
文件系统是操作系统的一部分,最终是目的是管理文件。 操作系统中之所以产生文件的概念,是为了方便多个进程可以共享一些数据,那么这些数据就要存储在磁盘上。多个进程可以进行访问。 把文件看成是磁盘上的地址空间。 文件的内容其实对计算机而言,就是字节序列。对用户看到的才是一行一行数据。 文件系统要解决的关键性问题是什么? 就是记录一个文件用到哪些磁盘块(哪些磁盘块分配给了哪些文件),这样找一个文件的时候,就知道去哪个磁盘块上寻找。 不同的操作系统使用不同的方式来实现这一目标。 大体分为三种方式: 1、连续分配。 没怎么理解,到底区别是在哪里呢? 一个文件,占据着多个磁盘块,特点是这些磁盘块是连续相邻在一起的。 每个文件是预先分配大小吗?预先申请多少个磁盘块? 这种方式的缺点是,一旦删除,增加文件,就会形成大量的空闲磁盘块(叫做磁盘碎片)。如果想要新加入的文件,去使用这些空闲磁盘块,那么就要计算一个文件的大小,然后才能找合适大小的磁盘块存入。关键问题是,很难确定一个文件的大小(因为一个文件以后会写入新的数据进去,或者会删除文件里面数据,大小总是在变化中)。正因为文件大小很难固定死,所以反而比较适合cd上的文件来存储,因为cd上的文件大小是固定的,不会改变的。 磁盘碎片多是它的缺点。 总体来说,这种文件分配方式只适合文件大小固定的文件。 2、链表分配(fat方案,文件分配表简称) 包括在链表存储在磁盘上,链表放入内存中。放在内存中的时候,速度是快。 链表是相对于连续分配磁盘块的方式而言的。这样子,一个文件不需要固定在连续的磁盘块。比如文件a的内容,在连续分配方式中。随着文件内容的增加,扩容。会使用磁盘块1,磁盘块2,磁盘块3,也就是必须是连续(位置相邻,连续的一片区域)的磁盘块。 优点:避免了连续分配方式中的磁盘碎片。 而链表方式,不需要连续的磁盘块。每个磁盘块的第一个字节存储指针,指向下一个磁盘块的地址。这样就能顺着指针去寻找。不需要连续的磁盘块都可以了。 为了提高速度,将链表存入到内存中去。内存中维护的这样一个表格(实际上可以理解成一个key->value的映射表),英文名称叫做file acllocation table(文件分配表),缩写是取每个单词的首个字母,就叫FAT。 联想到实际例子加深印象 我们经常在使用window的时候,会有fat32文件系统。就是这种原理来维护的。 某天,我在安装软件的时候,注意到一个现象,我删除了目录,但是这个目录还是在列表中: 这让我想起了fat文件系统的知识,把这个映射表放入了内存中(为了提高速度)。所以即便是删除了目录,在选择的时候还是会列出来(什么时候会删除呢?)。 我新加一个目录,看来只要往内存中的映射表(fat)加一条记录项即可,所以上图看到,新增加的目录develop会显示出来,因为直接是从内存的表中载入进来的。 重启电脑后,内存中的映射表会重新加载一次 3、i节点 链表分配法的缺点是,要占据着很大的内存(链表放入内存中为提高速度)。一个目录多少个文件,那么就要维护多少个项在内存中。 那么n多的目录,就会更加多。 磁盘空间越大,所需要维护的链表就越大,意味着内存中链表占据的内存空间就越大。比如200g的磁盘,每个磁盘块是1kb。那么总共就有200g*1kb个项。 这个项的目的,就是指明这个磁盘块的位置。 这个表需要2亿个项,大致需要600-800m的内存。太浪费内存空间了。 于是发明了一种改进办法,只有用户打开的文件,才将其节点信息载入内存中。这样子就会占据内存少很多。 3、目录的实现 每个目录,就会建一个目录表。目录表里面的每一项叫做目录项,其实就是这个目录中的一个文件对应一个项,通俗点说,就是把这个目录所有的文件都放到目录表里面记录起来。 查找一个目录里面的文件,或者是加入文件,都要搜索这个目录表里面的文件项。 目录的本质其实也是文件,只不过是一种特殊的文件,因为它包含了多个文件。所以目录其实是包括这几项:目录名称,目录的开始磁盘块编号,结束磁盘块编号。 两种实现算法,线性表和hash表。hash表的长度是一个问题。 理解操作系统,理解了它的三个概念,就几乎成为一个操作系统专家了: 1、进程(线程)。对cpu建立模型 2、地址空间。操作系统对内存的抽象模型 3、文件。难怪在linux操作系统中,一切皆是文件的概念。 操作系统有自己的文件系统。那么数据库系统如何与磁盘打交道,难道是按照自己的组织方式,还是说没有使用操作系统提供的文件系统呢。 但是,要知道,数据库系统最终是在操作系统上运行的,那么要操作磁盘数据,就离不开文件系统的使用。 数据库的物理管理有两种方式: 1、 借助操作系统的文件系统来组织数据。 由文件系统负责与磁盘交互,申请与分配磁盘块。 2、 自己实现一套管理方式,负责申请磁盘块与分配。可以理解为自己实现一套文件系统 实际上,大部分数据库系统一开始就申请固定大小的磁盘空间,然后由自己来进行分配和管理。 备份:磁盘控制器处理磁盘坏块的操作是透明的,甚至连操作系统都不知道。
$username = '刷_单8元1单淘宝客服20元1小时_我Q125556733jff'; var_dump(strlen($username)); var_dump(Library\Common::username_patten($username)); 一、使用strlen来判断长度 里面的一个汉字会认为长度是3个字符(utf-8编码情况下一个汉字3个字符,gbk编码就是2个字符长度) 例子: $username = '刷_单8元'; var_dump(strlen($username)); 长度是11。 刷,单,元 3*3=9 "_“和"8", 总共是2个长度 9+2=11 具体做试验: $username = '刷'; var_dump(strlen($username));//得到是长度值是3 二、使用正则匹配 $username_patten = '/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]{3,30}$/u'; //字母、数字、下划线、中文,汉字或字符加起来30个即可 //注:\x{4e00}-\x{9fa5}作为汉字的范围判断只适合utf-8编码情况下,gb2312不适合目前我们的项目统一使用utf8项目。 if (preg_match($username_patten, $username)){ } 特点:正则中会把一个汉字当成一个长度来计算。因为\x{4e00}已经指定了汉字编码 解释不是非常科学。记住这个细节的差异就可以了。 javascript的验证长度研究 /* * +--------------------------------- * 验证指定的字符长度,用来做标题长度验证 * +--------------------------------- */function getStrLeng(str){ var len = str.length;//js内部使用的是utf16编码,如果是中文,一个汉字也算一个长度。所以返回的是字符长度,而不是字节大小 return len; }var len = getStrLeng("我们_j");//在js中,一个中文汉str.length字也是一个长度,/* JavaScript中的String内部表示方式始终是UTF16,而它的length也是始终按UTF16 code point去计算,简而言之,length始终返回字符数量,而非字节大小! 为什么有人测试发现对于同一个字符串"张三",用UTF8编码时返回2,用GBK则得到3呢?那是因为浏览器没能正确识别文件编码,所以判断错了,返回3的结果是一个程序运行出错的结果。 参考: http://segmentfault.com/q/1010000000445715 因为使用的是utf-16,所以这个编码表里面能够找到所有的汉字,这样情况下,就能够当成一个字符了? 反正它返回的不是实际的字节长度,而是多少个字符。 http://www.puritys.me/docs-blog/article-107-String-Length-%E4%B8%AD%E6%96%87%E5%AD%97%E4%B8%B2%E9%95%B7%E5%BA%A6.html 使用 string.length 來計算長度時,中文字串會被當成是一個字來計算,因為 Javascript 是使用 multiple byte 計算,就像是 php 的 mb_strlen。 不管是中文,英文,都當成一個字來算,所以總共是 6 個字,不過有時候我們並不想把中文字當成一個字來計算,因為中文字明明佔的 byte 數就是比較多,所以來看一下中文編碼吧。*/ alert(len);/* * +--------------------------------- * 验证指定的字符长度,用来做标题长度验证 * 这个函数验证是按照字节长度算,所以中文汉字在utf-8编码中是算3个字节 * +--------------------------------- */function getStrLengByBytle(str){ }
我们的用户量大,修改js文件后,用户反馈登录出现问题。实际上刷新一下就没事了。就是因为用户的浏览器使用的还是本地缓存的js代码。 强制刷新一般就会重新去服务器获取新的js代码。但不能让用户每次都这样子去做。 于是我思考一个问题:如果修改了js文件中的js代码,发布代码到线上后。用户的浏览器使用的还是原来js缓存。所以并不会马上生效。如何才能让浏览器使用最新的js文件呢?很多人想到的第一反应是,在<script type="text/javascript" src="/js/common.js" ></script>在后面加一个时间戳来解决。这样url地址每次变化,浏览器就会请求服务端的js,而不会使用缓存。这样是解决了。但是会导致浏览器每次都要去请求服务端的js文件。占用带宽。作为技术,能不能有种更好的办法呢?既能避免用户的浏览器每次去请求服务端获取js文件。又能在发布新的js代码后,能够使用最新的js文件?据说,在问号后面加版本号,现在很多网站都这么干。加个版本号能够解决问题吗? 加个版本号,js有个版本。如果每次发布新的js代码。后面就会附加新的版本号。然后用户加载html页面的时候。版本号附加在在 <script type="text/javascript" src="/js/common.js?v=99" ></script> 如果没有修改,那么版本号还是原来的,这样做到了:不发布代码的时候,浏览器使用的是本地缓存。因为版本号没有变化。 现在疑问是,js的版本号如何生成呢? 生成一个日期吗? 当天的日期比较好。 这样的确解决了问题。让用户可以使用。 只不过出现一个新的问题来临了。 js文件加上当天的发布日期作为版本号即可了。 有些人针对url后面带时间戳的做法,会导致浏览器每次请求都不会缓存,因为每次请求时间都会变化,url就变化了,于是浏览器认为是一个新的地址了。 有人针对这个问题的解决办法是:这里URI不是静态,可能会造成某些浏览器不会进行缓存,可以采用伪静态配合URL重写来解决 网上查询资料,纵观大家的解决思路总结如下: 1、修改js的文件名。我觉得这样很麻烦。造成版本系统的维护困难。不建议。除非是完全ftp。不过每次发布都修改文件名称。的确造成维护的时候很茫然,接手的人看到文件名称变化,会比较难维护 2、路径后面加时间戳或者随机数的方式。 一般都是在html模版中使用一个时间戳或者随机数函数生成一个值。 <script type="text/javascript" src="{{passport_host}}js/common.js?t={{date("Y-m-d")}}" ></script> 今天和明天的值不同了,重新请求服务器。 <script type="text/javascript" src="{{passport_host}}js/common.js?t={{time()}}" ></script> 使用时间戳,每刷新一次html,值都不同。随机数也是一样的 不推荐使用这种方式。 因为这样的方式导致的问题是,每次刷新html,时间戳都是变化的,url就总是唯一的,于是浏览器总是去请求服务端获取js文件,不会使用浏览器本地的缓存。占用带宽,影响速度 3、路径后面加js的版本号。这样是业界比较成熟的做法。 关键是这个版本号,怎么做版本? 难道真的纳入版本系统里面去?不是的。我突然灵感来,想到一种程序员自己控制的办法。 自己主动加时间,如果本次发布,修改了哪几个js文件。那么就在后面加上一个时间点:年月日 如果一天会发布多次对js文件的修改,那么程序员还要精确点。年月日时分秒即可。 如下: <script type="text/javascript" src="/js/common.js?time=20150518" ></script> 我去看了一下淘宝,发现也是这样一种方式额,不知道对不对? 如下: 15年8月12日补充: 公司有好几千万注册会员,于是第三方应用使用我们网站会员帐号实现在第三方网站登录,需要设计oauth2.0授权的平台,于是需要参考微博的oauth体制。 无意中发现他们的css也是使用年月日来控制 进一步思考: 这种加时间方法是可行。。不是系统生成的时间,不是所有js文件都加。 是不是可以进一步考虑一种办法,用程序来进行开关呢? 自己勾选。如果这个文件修改了。那么就设置为更新。模版中判断,就根据这个开关,把时间戳自动打上去? 不过这样子觉得没必要。因为还没到那么重大。其实初期,完全可以程序手动把日期打上去即可了。 该了什么js文件,就给哪个js文件加,这样已经是折衷了。就跟改代码一样。代码都要修改的,这点改也没多少工作量。 总结思路: js文件的内容修改了,可以加个t参数表明一下日期,用这个日期来作为版本号,看到日期也能知道是哪天发布的。 没有修改js文件根本就不用修改日期。 实践: <script type="text/javascript" src="{{passport_host}}js/common.js?t=20150622" ></script> 如果下一次修改了这个js文件,那么发布的时候,就修改日期 <script type="text/javascript" src="{{passport_host}}js/common.js?t=20150628" ></script> 没有修改的js文件,保留原来的值不动即可。 思考:这样子是不是很麻烦,有更好的方式吗。目前我没想到更好的办法了。这种方式凑合能用。既能够按需修改,也能够保证浏览器使用缓存。达到了折衷和平衡。
常见的坑有两个: 一、获取的是内网的ip地址。在nginx作为反向代理层的架构中,转发请求到php,java等应用容器上。结果php获取的是nginx代理服务器的ip,表现为一个内网的地址。php获取REMOTE_ADDR就是这样一个情况(内网地址)。 二、获取的是攻击者伪造的ip地址。攻击者可以随便伪造一个头部信息,随便填写一个ip放到头部发过来,php获取到HTTP_CLIENT_IP就是这样一个情况。伪造的ip,导致我们数据库存储是假的ip,无从真实去判断攻击者的来源。比如批量注册帐号的注册ip,登录的ip等。 为避免伪造,不要使用discuz原来的获取ip函数,里面的判断优先级会使得攻击者容易伪造ip。 php代码: function getIP() { if (getenv("HTTP_X_FORWARDED_FOR")) { //这个提到最前面,作为优先级,nginx代理会获取到用户真实ip,发在这个环境变量上,必须要nginx配置这个环境变量HTTP_X_FORWARDED_FOR $ip = getenv("HTTP_X_FORWARDED_FOR"); } else if (getenv("REMOTE_ADDR")) { //在nginx作为反向代理的架构中,使用REMOTE_ADDR拿到的将会是反向代理的的ip,即拿到是nginx服务器的ip地址。往往表现是一个内网ip。 $ip = getenv("REMOTE_ADDR"); } else if ($_SERVER['REMOTE_ADDR']) { $ip = $_SERVER['REMOTE_ADDR']; } else if (getenv("HTTP_CLIENT_IP")) { //HTTP_CLIENT_IP攻击者可以伪造一个这样的头部信息,导致获取的是攻击者随意设置的ip地址。 $ip = getenv("HTTP_CLIENT_IP"); } else { $ip = "unknown"; } return $ip; } 说明 在反向代理架构中,不能通过REMOTE_ADDR来获取用户的真实ip! 以前的理解方式有误(更新一下)一般的方式是这样:nginx>>(fastcgi方式)>>php引擎nginx把REMOTE_ADDR传递给了php。代表的是当前与nginx通信的客户端ip,一般情况下(非反向代理),这个客户就是用户的浏览器,所以得到的用户的ip。假设做了反向代理架构,是下面这样子的:用 户>>>>>>>>>>>服务器 a>>>>>>>>>>>>>>>>>>nginx>>>>>>>>>>>>>> (fastcgi方式通信)>>>>>>>>>>>>php引擎 用户访问一个域名,实际上是通过服务器a做了转发,转发到nginx去(反向代理架构经常会这样部署) 于是,当前与nginx通信的客户端,就是服务器a的地址, REMOTE_ADDR就是a服务器的地址了。 如何判断:nginx的上一层是不是还有一层。像上面的情况就还有一层。所以得到的将会是服务器a的地址。 总结:在nginx作为反向代理的架构中,php的REMOTE_ADDR(其他语言也是类似的名称)拿到的将会是nginx代理的ip地址。拿不到用户的真实ip,拿到是nginx反向代理服务器地址。 REMOTE_ADDR本意就是远程的地址,nginx是代理层,转发请求到php,php获取到的远程地址实际上是nginx反向代理服务器ip,这是符合协议规则的。 但是,可以让nginx帮助我们拿到用户的真实ip,写到一个环境变量中,然后转发给我们,只要按照某个约定的名称即可,比如约定名称为HTTP_X_FORWARD_FOR(也可以约定其他名称,关键看nginx中配置,可以全公司考虑统一)。 nginx配置类似于这样: fastcgi_param HTTP_X_FORWARD_FOR $remote_addr; 上一句的目的是,将HTTP_X_FORWARD_FOR的值设置为$remote_addr的值。也就是将用户真实的ip(或用户使用代理的ip)放到HTTP_X_FORWARD_FOR中去。 $remote_addr是nginx的内置变量,这个变量它得到是用户真实的ip地址(用户使用了代理,则就是代理的ip地址)。 于是在php端通过getenv("HTTP_X_FORWARDED_FOR")就可以获取到nginx传递过来的值,是用户真实的ip地址。
/** * * 检查手机号码是否可用 * @param $cellphone 手机号码 */ public function checkPhone($cellphone) { $cellphone = trim($cellphone); if (InputCheck::CheckCellphone($cellphone) == false) throw new ApiException(4001023); $member = Member::findFirstByCellphone($cellphone); //todo by wangtao //这里的返回方式没有统一,可以注册或不可以注册都返回的一个文字,让调用方很难判断,一旦文字变化就会让调用方代码变得很麻烦。 //应该以状态码的形式统一起来返回,哪怕是true or false都要更好 //提示信息应该只能作为一个接口的辅助形式,不能作为调用方判断依据的。 //不知道现在有哪些应用已经在调用这个接口,如果要修改,新开一个接口来保证统一。如果确定没有调用,可以删除掉这个接口,避免影响后续接手人员,造成风格的不统一 if (!empty($member)) { $this->response->render('手机号已经被注册啦,换一个手机号码吧!');//返回false会更好,有利于调用方的代码判断 } else { $this->response->render('手机号可以注册');//成功应该要返回一个成功状态码,比如true } }