打开终端(Terminal),把下面这行命令贴进去,回车。

这个命令的作用就是在证书验证缓存数据库里面清除globalsign下发的缓存。运行结束重启chrome就可以了。
sqlite3 ~/Library/Keychains/*/ocspcache.sqlite3 ‘DELETE FROM responses WHERE responderURI LIKE “tp://%.globalsign.com/%”;’

升级xcode 后 Qt 出问题了,google 找到了解决方法。

http://stackoverflow.com/questions/33728905/qt-creator-project-error-xcode-not-set-up-properly-you-may-need-to-confirm-t

~> Xcode 8

This problem occurs when command line tools are installed after Xcode is installed. What happens is the Xcode-select developer directory gets pointed to /Library/Developer/CommandLineTools.

Step 1:

Point Xcode-select to the correct Xcode Developer directory with the command:

sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer

Step 2:

Confirm the license agreement with the command:

xcodebuild -license

This will prompt you to read through the license agreement. 

Enter agree to accept the terms.

>= Xcode 8

Step 1:

As Bruce said, this happens when Qt tries to find xcrun when it should be looking for xcodebuild.

Open the file:

Qt_install_folder/5.7/clang_64/mkspecs/features/mac/default_pre.prf

Step 2:

Replace:

isEmpty($$list($$system(“/usr/bin/xcrun -find xcrun 2>/dev/null”))))

With:

isEmpty($$list($$system(“/usr/bin/xcrun -find xcodebuild 2>/dev/null”)))

安装macOS Sierra后,会发现系统偏好设置的“安全与隐私”中默认已经去除了允许“任何来源”App的选项,无法运行一些第三方应用。


如果需要恢复允许“任何来源”的选项,即关闭Gatekeeper,请在终端中使用spctl命令:

  1. sudo spctl –master-disable

复制代码


久违的“任何来源”回来了:


需要说明的是,如果在系统偏好设置的“安全与隐私”中重新选中允许App Store 和被认可的开发者App,即重新打开Gatekeeper后,允许“任何来源”App的选项会再次消失,可运行上述命令再次关闭Gatekeeper。

文章目录

MYSQL 5.5 之前, UTF8 编码只支持1-3个字节,只支持BMP这部分的unicode编码区, BMP是从哪到哪,到http://en.wikipedia.org/wiki/Mapping_of_Unicode_characters这里看,基本就是0000~FFFF这一区。 从MYSQL5.5开始,可支持4个字节UTF编码utf8mb4,一个字符最多能有4字节,所以能支持更多的字符集。

utf8mb4 is a superset of utf8

utf8mb4兼容utf8,且比utf8能表示更多的字符。

至于什么时候用,看你的做什么项目了。。

在做移动应用时,会遇到ios用户会在文本的区域输入emoji表情,如果不做一定处理,就会导致插入数据库异常。

Emoji表情符号兼容方案

一 什么是Emoji

emoji就是表情符号;词义来自日语(えもじ,e-moji,moji在日语中的含义是字符)
表情符号现已普遍应用于手机短信和网络聊天软件。
emoji表情符号,在外国的手机短信里面已经是很流行使用的一种表情。
手机上如何使用emoji:
1.iphone、ipad系统:安装emoji free,再设置-通用-键盘-国际键盘-添加新的键盘,然后把emoji添加在里面即可在发短信和一些输入文本的文本框中输入表情。
IOS 5用户可直接从通用中添加emoji 键盘,无需再安装emoji free
2.android系统:安装“GO输入法国际版”后,在输入法里面点选安装emoji插件可以使用。另外“百度输入法”也自带emoji表情
3.Windows Phone : 安装此 Emoji Keys,在其中输入之后复制粘贴到需要输入表情的地方即可

<此段摘自百度百科 http://baike.baidu.com/view/2631589.htm>

二 Emoji表情符号问题
1 问题:
IOS版本之间发送的Emoji表情符号不兼容,只看到方块
不同IOS版本在数据库存数据时,有时会发生系统错误
2 现象:
IOS 4 输入Emoji表情符,在IOS5.01 显示正常,在IOS5.1中(大陆版)显现为方块, 但IOS5.01/5.1输入的表情符号,显示正      常
IOS5.01/5.1 输入表情符,在IOS5.01/5.1中显示正常,但在IOS4.X显示为方块
输入Emoji入帖子正文, 可正常存储。 但用户昵称在IOS4.X 输入Emoji,系统正常, 而IOS5.01/5.1则提示系统错误。
3 本质:
iOS 5 and OS X 10.7 (Lion) use the Unicode 6.0 standard ‘unified’ code points for emoji.
iOS 5 Emoji  采用Unicode 6 标准来统一code points

iOS 4 on SoftBank iPhones used a set of unofficial code points in the Unicode Private Use Area, and so aren’t      compatible with any other systems
iOS 4 采用SoftBank Unicode, 一种非官方的, 采用私有Unicode 区域。
4 举例:
one emoji symbol “tiger”, it is “\U0001f42f” in iOS5, but “\ue050” in earlier iOS version
虎脸Emoji符号在iOS5 为Unicode:\U0001f42f;而在IOS4.x 为:\ue050 (SoftBank 编码)
另外: 按理讲, 从iOS5 应该兼容以前版本的emoji, 但现在出现5.01版本完美兼容(无论大陆版,美版,还是港版), 而5.1     大陆版出现了不兼容现象(腾讯微信也出现了同样的问题)。
三 问题分析
1 系统存储错误问题(如昵称,帖子内容)
原因:
由于IOS5.X 采用新的Unicode, 其UTF8 编码大多为4个字节, 而由于昵称/帖子内容column并没设成utf8mb4,因此存储会    发生错误。
解决方法:
将昵称/帖子内容设成utf8mb4
2 不同iOS 之间Emoji 不兼容的问题。
原因:
iOS 5 到4 不兼容的问题,很简单,unicode6 和softbank编码的不同
iOS 4 到 5,按理说应该兼容,也就是说,iOS应该自动判断如果是softbank编码,自动转成unicode6。但现在看来, iOS5.1(大陆版)好像只支持unicode6, 而不支持softbank.
解决方法:
客户端发送emoji-encoding: Softbank或unicode6, 由服务端分别给出相应的编码表。
四 解决方案
1 数据存储(MySQL varchar  数据类型对UTF8 支持问题)
MYSQL 5.5 之前, UTF8 编码只支持1-3个字节, 从MYSQL5.5开始,可支持4个字节UTF编码,但要特殊标记。例如我们的帖子内容项,我们加上了这个支持。服务端mysql统一存储为ios5.x也就是Unicode编码。
对应alter语句:

[sql]  view plain copy

  1. ALTER TABLE topic MODIFY COLUMN content varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘内容’;

2 编码转换:
iphone手机方案
客户端输入内容时候,统一存储为unicode编码(这里需要从softbank编码转换为unicode编码)。客户端请求内容的时候,需要根据不同的客户端给出不同的编码,ios4采用softbank编码做替换,ios5采用unicode编码直接支持。
android或wp其他手机方案:
如果没有emoji表情库,将无法输入。针对输入问题,将统一采用unicode编码存储。客户端请求内容的时候,将统一用softbank编码,客户端需要把emoji表情符号内置到客户端,做对应的编码和img替换。
web解决方案:
参考android或wp其他手机方案
五 部分代码
1 sql代码

[sql]  view plain copy

  1. CREATE TABLE `ios_emoji` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘自增ID’,
  3.   `unicode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘Unicode编码’,
  4.   `utf8` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘UTF8编码’,
  5.   `utf16` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘UTF16编码’,
  6.   `sbunicode` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘SBUnicode编码’,
  7.   `filename` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT ‘文件名’,
  8.   `filebyte` longblob COMMENT ‘文件内容字节’,
  9.   PRIMARY KEY (`id`)
  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT=’ios表情编码表’;

2 java代码

[java]  view plain copy

  1. import java.io.UnsupportedEncodingException;
  2. import org.apache.commons.lang.StringUtils;
  3. public class IOSEmojiUtil {
  4.     public static String[] ios5emoji ;
  5.     public static String[] ios4emoji ;
  6.     public static String[] androidnullemoji ;
  7.     public static String[] adsbuniemoji;
  8.     public static void initios5emoji(String[] i5emj,String[] i4emj,String[] adnullemoji,String[] adsbemoji){
  9.         ios5emoji = i5emj;
  10.         ios4emoji = i4emj;
  11.         androidnullemoji = adnullemoji;
  12.         adsbuniemoji = adsbemoji;
  13.     }
  14.     //在ios上将ios5转换为ios4编码
  15.     public static String transToIOS4emoji(String src) {
  16.         return StringUtils.replaceEach(src, ios5emoji, ios4emoji);
  17.     }
  18.     //在ios上将ios4转换为ios5编码
  19.     public static String transToIOS5emoji(String src) {
  20.         return StringUtils.replaceEach(src, ios4emoji, ios5emoji);
  21.     }
  22.     //在android上将ios5的表情符替换为空
  23.     public static String transToAndroidemojiNull(String src) {
  24.         return StringUtils.replaceEach(src, ios5emoji, androidnullemoji);
  25.     }
  26.     //在android上将ios5的表情符替换为SBUNICODE
  27.     public static String transToAndroidemojiSB(String src) {
  28.         return StringUtils.replaceEach(src, ios5emoji, adsbuniemoji);
  29.     }
  30.     //在android上将SBUNICODE的表情符替换为ios5
  31.     public static String transSBToIOS5emoji(String src) {
  32.         return StringUtils.replaceEach(src, adsbuniemoji, ios5emoji);
  33.     }
  34.     //eg. param: 0xF0 0x9F 0x8F 0x80
  35.     public static String hexstr2String(String hexstr) throws UnsupportedEncodingException{
  36.         byte[] b = hexstr2bytes(hexstr);
  37.         return new String(b, “UTF-8”);
  38.     }
  39.     //eg. param: E018
  40.     public static String sbunicode2utfString(String sbhexstr) throws UnsupportedEncodingException{
  41.         byte[] b = sbunicode2utfbytes(sbhexstr);
  42.         return new String(b, “UTF-8”);
  43.     }
  44.     //eg. param: 0xF0 0x9F 0x8F 0x80
  45.     public static byte[] hexstr2bytes(String hexstr){
  46.         String[] hexstrs = hexstr.split(” “);
  47.         byte[] b = new byte[hexstrs.length];
  48.         for(int i=0;i<hexstrs.length;i++){
  49.             b[i] = hexStringToByte(hexstrs[i].substring(2))[0];
  50.         }
  51.         return b;
  52.     }
  53.     //eg. param: E018
  54.     public static byte[] sbunicode2utfbytes(String sbhexstr) throws UnsupportedEncodingException{
  55.         int inthex = Integer.parseInt(sbhexstr, 16);
  56.         char[] schar = {(char)inthex};
  57.         byte[] b = (new String(schar)).getBytes(“UTF-8”);
  58.         return b;
  59.     }
  60.     public static byte[] hexStringToByte(String hex) {
  61.         int len = (hex.length() / 2);
  62.         byte[] result = new byte[len];
  63.         char[] achar = hex.toCharArray();
  64.         for (int i = 0; i < len; i++) {
  65.             int pos = i * 2;
  66.             result[i] = (byte) (toByte(achar[pos]) << 4 | toByte(achar[pos + 1]));
  67.         }
  68.         return result;
  69.     }
  70.     private static byte toByte(char c) {
  71.         byte b = (byte) “0123456789ABCDEF”.indexOf(c);
  72.         return b;
  73.     }
  74.     public static void main(String[] args) throws UnsupportedEncodingException {
  75.         // TODO Auto-generated method stub
  76.         byte[] b1 = {-30,-102,-67}; //ios5 //0xE2 0x9A 0xBD
  77.         byte[] b2 = {-18,-128,-104}; //ios4 //”E018″
  78.         //————————————-
  79.         byte[] b3 = {-16,-97,-113,-128};    //0xF0 0x9F 0x8F 0x80
  80.         byte[] b4 = {-18,-112,-86};         //E42A
  81.         ios5emoji = new String[]{new String(b1,”utf-8″),new String(b3,”utf-8″)};
  82.         ios4emoji = new String[]{new String(b2,”utf-8″),new String(b4,”utf-8″)};
  83.         //测试字符串
  84.         byte[] testbytes = {105,111,115,-30,-102,-67,32,36,-18,-128,-104,32,36,-16,-97,-113,-128,32,36,-18,-112,-86};
  85.         String tmpstr = new String(testbytes,”utf-8″);
  86.         System.out.println(tmpstr);
  87.         //转成ios4的表情
  88.         String ios4str = transToIOS5emoji(tmpstr);
  89.         byte[] tmp = ios4str.getBytes();
  90.         //System.out.print(new String(tmp,”utf-8″));
  91.         for(byte b:tmp){
  92.             System.out.print(b);
  93.             System.out.print(” “);
  94.         }
  95.     }
  96. }

六 参考资料
1 Emoji 全编码表:(我参考的这个)
http://punchdrunker.github.com/iOSEmoji/table_html/flower.html
2 Emoji全编码表
http://code.iamcal.com/php/emoji/

3 iOS5/4 Emoji  兼容性:
http://stackoverflow.com/questions/7856775/how-to-convert-the-old-emoji-encoding-to-the-latest-encoding-in-ios5
4 MySQL emoji问题
http://dropblood.com/archives/ios-mysql-emoji
5 Emoji 中文对应表
http://www.iapps.im/wp-content/uploads/2012/02/emoji-pinyin.png?r=010

七 下载资源 

emoji图片和编码表 http://download.csdn.net/detail/qdkfriend/4309051

包括emoji文件表,emoji数据编码表(Unicode编码,UTF8编码,UTF16编码,SBUnicode编码)

mysql支持utf8mb4升级方案

[http://mathiasbynens.be/notes/mysql-utf8mb4#utf8-to-utf8mb4](

How to support full Unicode in MySQL databases

)

link: http://my.oschina.net/wingyiu/blog/153357

为了应对无线互联网的机遇和挑战、避免 emoji 表情符号带来的问题、涉及无线相关的 MySQL 数据库建议都提前采用 utf8mb4 字符集。注意Mysql5.5.3以上的版本才支持。 本文描述如何让Mysql5.5.31在原来采用UTF8字符集的情况下,升级编码为UTF8MB4。

1.修改/etc/my.cnf 文件
[client]
default-character-set = utf8mb4

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[mysql]
default-character-set = utf8mb4

2.重启Mysql服务
/etc/init.d/mysqld stop
/etc/init.d/mysqld start

3.升级Mysql 驱动jar包

如果经过上述步骤,应用无法连接数据的话,请检查当前使用的mysql-connector-java.jar 的版本,建议升级到mysql-connector-java-5.1.21.jar。

link: http://blog.itpub.net/28624388/viewspace-1064046/

文章目录

形式

#include <sys/ptrace.h>

int ptrace(int request, int pid, int addr, int data);

描述

Ptrace 提供了一种父进程可以控制子进程运行,并可以检查和改变它的核心image。它主要用于实现断点调试。一个被跟踪的进程运行中,直到发生一个信号。则进程被中止,并且通知其父进程。在进程中止的状态下,进程的内存空间可以被读写。父进程还可以使子进程继续执行,并选择是否是否忽略引起中止的信号。

Request参数决定了系统调用的功能:

PTRACE_TRACEME

本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。

PTRACE_PEEKTEXT, PTRACE_PEEKDATA

从内存地址中读取一个字节,内存地址由addr给出。

PTRACE_PEEKUSR

从USER区域中读取一个字节,偏移量为addr。

PTRACE_POKETEXT, PTRACE_POKEDATA

往内存地址中写入一个字节。内存地址由addr给出。

PTRACE_POKEUSR

往USER区域中写入一个字节。偏移量为addr。

PTRACE_SYSCALL, PTRACE_CONT

重新运行。

PTRACE_KILL

杀掉子进程,使它退出。

PTRACE_SINGLESTEP

设置单步执行标志

PTRACE_ATTACH

跟踪指定pid 进程。

PTRACE_DETACH

结束跟踪

Intel386特有:

PTRACE_GETREGS

读取寄存器

PTRACE_SETREGS

设置寄存器

PTRACE_GETFPREGS

读取浮点寄存器

PTRACE_SETFPREGS

设置浮点寄存器

init进程不可以使用此函数

返回值

成功返回0。错误返回-1。errno被设置。

错误

EPERM

特殊进程不可以被跟踪或进程已经被跟踪。

ESRCH

指定的进程不存在

EIO

请求非法

ptrace系统函数。 ptrace提供了一种使父进程得以监视和控制其它进程的方式,它还能够改变子进程中的寄存器和内核映像,因而可以实现断点调试和系统调用的跟踪。使用ptrace,你可以在用户层拦截和修改系统调用(sys call).

功能详细描述

1)   PTRACE_TRACEME

形式:ptrace(PTRACE_TRACEME,0 ,0 ,0)

描述:本进程被其父进程所跟踪。其父进程应该希望跟踪子进程。

2)  PTRACE_PEEKTEXT, PTRACE_PEEKDATA

形式:ptrace(PTRACE_PEEKTEXT, pid, addr, data)

        ptrace(PTRACE_PEEKDATA, pid, addr, data)

描述:从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据。在Linux(i386)中用户代码段与用户数据段重合所以读取代码段和数据段数据处理是一样的。

3)  PTRACE_POKETEXT, PTRACE_POKEDATA

形式:ptrace(PTRACE_POKETEXT, pid, addr, data)

        ptrace(PTRACE_POKEDATA, pid, addr, data)

描述:往内存地址中写入一个字节。pid表示被跟踪的子进程,内存地址由addr给出,data为所要写入的数据。

4)  TRACE_PEEKUSR

形式:ptrace(PTRACE_PEEKUSR, pid, addr, data)

描述:从USER区域中读取一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为用户变量地址用于返回读到的数据。USER结构为core文件的前面一部分,它描述了进程中止时的一些状态,如:寄存器值,代码、数据段大小,代码、数据段开始地址等。在Linux(i386)中通过PTRACE_PEEKUSER和PTRACE_POKEUSR可以访问USER结构的数据有寄存器和调试寄存器。

5)  PTRACE_POKEUSR

形式:ptrace(PTRACE_POKEUSR, pid, addr, data)

描述:往USER区域中写入一个字节,pid表示被跟踪的子进程,USER区域地址由addr给出,data为需写入的数据。

6)   PTRACE_CONT

形式:ptrace(PTRACE_CONT, pid, 0, signal)

描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。

7)  PTRACE_SYSCALL

形式:ptrace(PTRACE_SYS, pid, 0, signal)

描述:继续执行。pid表示被跟踪的子进程,signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。与PTRACE_CONT不同的是进行系统调用跟踪。在被跟踪进程继续运行直到调用系统调用开始或结束时,被跟踪进程被中止,并通知父进程。

8)   PTRACE_KILL

形式:ptrace(PTRACE_KILL,pid)

描述:杀掉子进程,使它退出。pid表示被跟踪的子进程。

9)   PTRACE_SINGLESTEP

形式:ptrace(PTRACE_KILL, pid, 0, signle)

描述:设置单步执行标志,单步执行一条指令。pid表示被跟踪的子进程。signal为0则忽略引起调试进程中止的信号,若不为0则继续处理信号signal。当被跟踪进程单步执行完一个指令后,被跟踪进程被中止,并通知父进程。

10)  PTRACE_ATTACH

形式:ptrace(PTRACE_ATTACH,pid)

描述:跟踪指定pid 进程。pid表示被跟踪进程。被跟踪进程将成为当前进程的子进程,并进入中止状态。

11)  PTRACE_DETACH

形式:ptrace(PTRACE_DETACH,pid)

描述:结束跟踪。 pid表示被跟踪的子进程。结束跟踪后被跟踪进程将继续执行。

12)  PTRACE_GETREGS

形式:ptrace(PTRACE_GETREGS, pid, 0, data)

描述:读取寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有17个基本寄存器的值。

13)  PTRACE_SETREGS

形式:ptrace(PTRACE_SETREGS, pid, 0, data)

描述:设置寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有17个基本寄存器的值。

14)  PTRACE_GETFPREGS

形式:ptrace(PTRACE_GETFPREGS, pid, 0, data)

描述:读取浮点寄存器值,pid表示被跟踪的子进程,data为用户变量地址用于返回读到的数据。此功能将读取所有浮点协处理器387的所有寄存器的值。

15)  PTRACE_SETFPREGS

形式:ptrace(PTRACE_SETREGS, pid, 0, data)

描述:设置浮点寄存器值,pid表示被跟踪的子进程,data为用户数据地址。此功能将设置所有浮点协处理器387的所有寄存器的值。

 

link: http://blog.sina.com.cn/s/blog_4ac74e9a0100n7w1.html

更新了 Mac OS X 11后发现,MacVim 不再能够通过Terminal用命令打开了。

mvim hello.txt

于是尝试将 mvim 重新复制到/usr/bin/中去

sudo cp -f mvim /usr/bin/

然而出现了权限问题:

cp: /usr/bin/mvim: Operation not permitted

搜索之后发现,是El Capitan 加入了Rootless机制,不再能够随心所欲的读写很多路径下了。设置 root 权限也不行。

Rootless机制将成为对抗恶意程序的最后防线

于是尝试关闭 Rootless。重启按住 Command+R,进入恢复模式,打开Terminal。

csrutil disable

重启即可。如果要恢复默认,那么

csrutil enable
附录:

csrutil命令参数格式:

csrutil enable [–without kext | fs | debug | dtrace | nvram][–no-internal]

禁用:csrutil disable

(等同于csrutil enable –without kext –without fs –without debug –without dtrace –without nvram)

其中各个开关,意义如下:

  • B0: [kext] 允许加载不受信任的kext(与已被废除的kext-dev-mode=1等效)
  • B1: [fs] 解锁文件系统限制
  • B2: [debug] 允许task_for_pid()调用
  • B3: [n/a] 允许内核调试 (官方的csrutil工具无法设置此位)
  • B4: [internal] Apple内部保留位(csrutil默认会设置此位,实际不会起作用。设置与否均可)
  • B5: [dtrace] 解锁dtrace限制
  • B6: [nvram] 解锁NVRAM限制
  • B7: [n/a] 允许设备配置(新增,具体作用暂时未确定)

link: http://www.jianshu.com/p/22b89f19afd6

几乎每一个 PHP 程序员都发布过代码,可能是通过 ftp 或者 rsync 同步的,也可能是通过 svn 或者 git 更新的。一个活跃的项目可能每天都要发布若干次代码,但是现实却是很少有人注意其中的细节,实际上这里面有好多坑,很可能你就在坑中却浑然不知。

一个正确实现的发布系统至少应该支持原子发布。如果说每一个版本都表示一个独立的状态的话,那么在发布期间,任何一次请求只能在单一状态下被执行。如此称之为支持原子发布;反之如果在发布期间,一次请求跨越不同的状态,那么就不能称之为原子发布。我们不妨举个例子来说明一下:假设一次请求需要 include 两个 PHP 文件,分别是 a.php 和 b.php,当 include a.php 完成后,发布代码,接着 include b.php,如果处理不当的话,那么就可能会导致旧版本的 a.php 和新版本的 b.php 同时存在于同一个请求之中,换句话说就是没有实现原子发布。

开源世界里有很多不错的发布代码工具,比如 ruby 社区的 capistrano,其流程大致就是发布代码到一个全新的目录,然后再软链接到真正的发布目录。

.
├── current -> releases/v1
└── releases
    ├── v1
    │   ├── foo.php
    │   └── bar.php
    └── v2
        ├── foo.php
        └── bar.php

不过鉴于 PHP 本身的特殊性,如果只是简单套用上面的流程,那么将很难实现真正的原子发布。要理清个中缘由,还需要了解一下 PHP 中的两个 Cache 的概念:

  • opcode cache
  • realpath cache

先聊聊 opcode cache,早先大家一直用 apc,现在多半选择 zend opcode,关于它的作用,大家都已经很熟悉,不必多言,唯一需要注意的是 apc 和 zend opcode 对缓存键的选择有所差异:apc 选择的是文件的 inode,zend opcode 选择的是文件的 path。

再聊聊 realpath cache,它的作用是缓冲获取文件信息的 IO 操作,大多数时候它对我们而言是透明的,以至于很多人都不知道它的存在,需要注意的是 realpath cache 是进程级别的,也就是说,每一个 php-fpm 进程都有自己独立的 realpath cache。

相关的技术细节特别琐碎,建议大家仔细阅读如下资料:

在采用软链接发布代码的时候,通常遇到的第一个问题多半是新代码不生效!不管是开启了 apc.stat 或者 opcache.validate_timestamps 配置,还是调用了 apc_clear_cache 或者 opcache_reset 方法,均无效,重启 php-fpm 自然是能够解决问题,不过对脚本语言来说重启太重了!难道除了重启就没有别的办法了么?

事实上之所以会出现这样的问题,主要是因为 opcode cache 是通过 realpath cache 获取文件信息,即便软链接已经指向了新位置,但是如果 realpath cache 里还保存着旧数据的话,opcode cache 依然无法知道新代码的存在,缺省情况下,realpath_cache_ttl 缓存有效期是两分钟,这意味着发布代码后,可能要两分钟才能生效。为了让发布尽快生效,需要以进程为单位清除 realpath cache:

<?php

$key = 'php.pid_' . getmypid();

if (($rev = apc_fetch($key)) != DEPLOY_VERSION) {
    if($rev < DEPLOY_VERSION) {
        apc_store($key, DEPLOY_VERSION);
    }
    
    clearstatcache(true);
}

?>

如此在 apc 环境下基本就能工作了,但是在 zend opcode 环境下还可能有问题。因为在缺省情况下 opcache.revalidate_path 是关闭的,此时会缓存符号链接的值,这会导致即便软链接指向修改了,也永远无法生效,所以在使用 zend opcode 的时候,如果使用了软链接,视情况最好把 opcache.revalidate_path 激活。

分析到这里,我们不妨反思一下:在 PHP 中原子发布之所以是一个棘手的问题,归根结底是因为软链接和缓存之间的的矛盾。不管是 opcode cache 还是 realpath cache,都是 PHP 固有的缓存特性,基于客观需要无法绕开,如此说来是否有办法绕开软链接,使其成为马奇诺防线呢?答案是 nginx 的 $realpath_root

fastcgi_param SCRIPT_FILENAME $realpath_root $fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

有了 $realpath_root,即便 DOCUMENT_ROOT 目录中含有软链接,nginx 也会把软链接指向的真正的路径发给 PHP,也就是说,对 PHP 而言,软链接已经不存在了!不过作为代价,每一次请求,nginx 都要通过相对昂贵的 IO 操作获取 $realpath_root 的值,通过 strace 命令我们能监控这一过程,下图从 current 到 foo 的过程:

realpath

在本例中,压测发现使用 $realpath_root 后,性能下降了大约 5% 左右,不过明眼人一下就能发现,虽然 $realpath_root 导致了 lstat 和 readlink 操作,但是 lstat 操作的次数是和目录深度成正比的,也就是说目录越深,执行的 lstat 次数越多,性能下降也就越大。如果能够降低发布目录的深度,那么可以预计性能下降能够控制在 1% 左右。

结尾介绍一下 Deployer,它是 PHP 中做得比较好的工具,有很多特色,比如支持并行发布,具体演示如下图,左边是串行,右边是并行,使用「vvv」能得到更详细信息:

deploy

不过 Deployer 在原子发布上有一点瑕疵,具体见 deploy:release 代码:

<?php

run("cd {{deploy_path}} && if [ -h release ]; then rm release; fi");
run("ln -s $releasePath {{deploy_path}}/release");

?>

也就是说,在切换软链接的时候,它是先删除再创建,是一个两步操作,理论上如果有请求在这两步中间进入的话,那么将会出现找不到文件的错误。

问题到这里,大部分人会觉得使用「ln -sfn」就好了,实际上也是错误的:

shell> strace ln -sfn releases/foo current
symlink("releases/foo", "current")      = -1 EEXIST (File exists)
unlink("current")                       = 0
symlink("releases/foo", "current")      = 0

通过 strace 我们能清晰的看到,虽然表面上使用「ln -sfn」是一步操作,但是内部依然是按照先删除再创建的逻辑执行的,实际上这里应该搭配使用「ln & mv」:

shell> ln -sfn releases/foo current.tmp
shell> mv -fT current.tmp current

先通过 ln 创建一个临时的软链接,再通过 mv 实现原子操作,此时如果使用 strace 监控,会发现 mv 的「T」选项实际上仅仅执行了一个 rename 操作,所以是原子的。

link: http://huoding.com/2016/05/27/515

首先从整体上介绍git服务器的工作原理:多个客户端,其中可以包括仓库管理员,通过将自己的ssh公钥上传到服务器仓库keydir目录,统一调用Git专用账号git进行访问git仓库,不同的用户可以根据不同的ssh公钥校验登录,进行项目版本的各种操作,包括clone,commit,push,pull等等。
    下面具体介绍Git服务器的搭建,机器环境:ubuntu12.04,Git服务器软件采用gitosis(https://github.com/res0nat0r/gitosis ,网上很多文章给的地址根本无法使用,都是copy的,不给力啊! )。
一、软件安装
    1.1 安装ssh的服务端和客户端:sudo apt-get install openssh-server openssh-client
    1.2 安装git-core软件,这个是git服务的基础:sudo apt-get install git-core
    1.3 安装 Gitosis,这个是git服务器软件

de>sudo apt-get install python-setuptoolsde>de>
de>de>cd /tmp
de>de>git clone https://github.com/res0nat0r/gitosis.git
de>de>
de>de>cd gitosisde>

sudo python setup.py install 

 

    ok,软件的安装都此结束。
 
二、创建Git专用账号git
    sudo useradd -m -s /bin/bash git    //创建git账号,用户家目录默认为/home/git,shell为/bin/bash
    sudo passwd git    //设置git用户的密码
 
三、初始化Git仓库
    1、初始化Git仓库需要一个管理员账号,如上图所示,管理员也是一个客户端用户,所以需要在客户端主机(我的客户端机器是Win7系统)生成一个用户,并且生成ssh-key。具体操作如下:
        
        在客户端安装ssh服务,包括客户端和服务端。如果你的客户端系统是:
            (1)Windows机器,建议直接安装git bash软件,其中包括了ssh这个服务;
            (2)Linux系统,可以按照先前的命令apt-get install openssh-server openssh-client安装ssh服务。
        
        在客户端机器我的账号是huixiao200068,在用户家目录中生成ssh-key:
            (1)Windows机器,打开git bash软件,输入命令
                        cd ~
                        ssh-keygen -t rsa
            (2)Linux系统,也执行以上相同的命令,即可以生成当前用户的ssh-key。
            执行完成之后,输入命令:ls -al,如果出现了.ssh目录,则表示ssh-key成功创建。
    2、把ssh的公钥上传到服务器,我这里假设上传到服务器的临时目录/tmp。
        scp  ~/.ssh/id_rsa.pub git@server:/tmp    //命令中的server改成你自己服务器的IP
    到此为止,仅仅只是做好了初始化仓库的准备工作,上面的2步操作都是在客户端操作的。下面是关键的第三步——初始化,该步骤在服务器端执行。
 
    3、初始化
        sudo -H -u git gitosis-init < /tmp/id_rsa.pub    //将git仓库目录初始化到了git用户家目录下
        此时,git用户的home目录中将出现repositories目录,该目录为git的仓库。
        修改目录权限:sudo chmod 755 /home/git/repositories/gitosis-admin.git/hooks/post-update,该步骤尚不清楚其具体的作用,修改了这个目录的权限有啥用呢?望各位指教。
恭喜你,到此为止,你已经创建了一个git仓库,而且账号huixiao200068是git仓库的管理员啦。下面要做的就是仓库配置啦,管理项目和用户。
四、下载仓库配置项目gitosis-admin到本地客户端
        因为git仓库的配置文件都是以git方式来管理的,所以你需要先下载一份到客户端本地 。
        在你的用户目录下面创建一个临时目录work,
        然后 进入到该目录:cd work,
        然后执行命令:
        git clone git@server:gitosis-admin.git    // 命令中的server改成你自己服务器的IP
        执行完成之后,work目录下会生成gitosis-admin目录,目录下面有一个gitosis.conf文件和一个keydir目录,它们将是下面配置任务的主要操作对象,请牢记它们的位置。
五、新建项目
       1、修改配置文件gitosis.conf,增加如下内容。
            [group first-pro]    //用户组名
            members = huixiao200068    //成员名,多个成员可以用空格隔开
            writable = first-pro    //项目名及其用户对于此项目的权限,目前是可写
        2、创建项目目录 mkdir first-pro
             初始化该目录 cd first-pro;    git init
             添加远程仓库 git remote add origin git@SERVER:first-pro.git
             创建工程文件 touch 1.txt
             添加工程文件到本地仓库中 git add ./1.txt
             提交整个项目到本地仓库 git commit -m “comment”
             提交整个项目到远程仓库 git push origin master    //只有这样子,团队其他开发人员才能看到你修改的代码
             查看提交日志 git log
             查看本地库当前的状态,比如是否有文件需要提交。 git status
        新的项目仓库已经生成,其他开发人员可以git clone 命令从服务器上下载一份工程到自己的本地机器上,协同开发啦!
 
六、新建用户

        关于上一节最后提到的内容,“其他开发人员”,他们是需要管理员来增加和配置的,这一节主要讲怎么添加用户。

(1)客户端操作:
        首先要生成ssh-key,方法和上述说明的一样。
        cd ~
        ssh-keygen -t rsa
        然后一直回车,就OK。然后将生成的id_rsa.pub文件传给GIT服务器管理员
 
(2)服务器端操作:
        管理员将客户上传的id_rsa.pub文件移到gitosis-admin/keydir目录中,并且改名为CLIENT_NAME.pub。注意:如果客户端如下图所示
        ,则CLIENT_NAME为sean@bogon,否则后续操作会出现“Repository read access denied”的错误。
         给项目first-pro增加新的开发者,编辑gitosis.conf文件,vi gitosis.conf。
          [group first-pro]    //用户组名
          members = huixiao200068 sean@bogon    //成员名,多个成员可以用空格隔开
          writable = first-pro    //项目名及其用户对于此项目的权限,目前是可写
         提交修改的管理文件:
            git commit -a -m “add user sean@bogon”
            git push origin master
完成上述2步之后,即可以使用该账号共同开发项目first-pro啦!
        cd ~
        git clone git@SERVER:first-pro.git    //克隆项目到本地
        ……    //do anything you want to do
        commit -am “comment”
        commit push origin master
link: http://blog.163.com/xiaohui_1123@126/blog/static/398052402012102751349705/