【初阶】基于redis的抢购秒杀设计

秒杀系统架构思路

应用场景

限时抢购,10w人抢购100个商品。这种情况下如果直接走到数据存储层,瞬间会把数据库打挂掉,如果业务处理不好(流程过长),会出现超卖现象

优化方向

尽量将请求拦截在上游

充分利用缓存
1. 前端方案
- 页面静态
- 防重复提交
- 用户限流
2. 后端方案
- 网关限制
- 缓存
- 消息队列削峰

优化细节

这里我们由简单往细节深入(因为要考虑实际的用户量,先做最简单最有效的处理)

简单高效处理方式

  1. 前端防重复提交
    这是最基本的
  2. redis缓存校验
    如果没有缓存,由于业务校验时间过长,比如在200ms内10w个人同时读了数据库库存仅剩1,都是可购买的,然后同时走到下单流程就会超卖,另外就是如果并发量过高,数据库会挂掉。
    如果是缓存校验,则只有一个可下单。其余全部直接拦截。

redis数据类型选型
如果不考虑抢购的下单数量和购买限制大于1,那直接kv或者list都是可以的。否则可以考虑hash或者zset。

这里对比一下kv和list:
kv的一般处理方式是预加或者预减:累计购买数量超过总库存或者剩余库存小于0,则校验不通过,同时回退。
list:预存库存长度的list,不断pop,无法pop则校验不通过。

tip:注意原子性
判断最好走lua。
比如kv,如果库存1,10人同时下单,如果是程序判断,则全部不能下单。而如果用lua,用户判断和回退是原子性的,则有一个人可以下单成功。
如果使用list,当抢购数量大于1时,回退也需要用事务,不然会出现,比如库存3,A下单5,B下单2。B在回退push的过程中,又被Apop了1从而判断库存不足。导致两人都不能下单。

复杂完善处理方式

  1. redis集群,主从同步,读写分离(读多写少)。
  2. nginx负载均衡
  3. 前端资源静态化:只有少部分内容是动态的的
  4. 按钮控制
  5. 缓存预热
  6. mq削峰:队列下单

详细内容可以参考大佬们的文章,我只是一个搬运工

架构设计图:
秒杀架构设计

参考文档:
https://www.zhihu.com/question/54895548
https://yq.aliyun.com/articles/69704?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&utm_content=m_10737

延迟队列使用说明

一、应用场景

  1. 对于商城系统而言,定时任务会非常多,比如优惠券过期、订单定时关闭、2小时订单未支付自动取消等等。常用做法是写数据库指定过期时间,定时循环读表,当数据量一大,这种会严重影响性能,产生巨大的io。
  2. 而另外一些基于redis实现的延迟队列,也是基于pull拉模式去实现的,也会产生io,只是变成了内存读取。
  3. 一种好的做法是 push 推模式,服务端主动推送,这里就是rabbitmq的死信队列去实现

二、延迟队列原理

延迟队列一般分为两种:
- 基于消息的延迟和基于队列的延迟。基于消息的延迟是指为每条消息设置不同的延迟时间,那么每当队列中有新消息进入的时候就会重新根据延迟时间排序,当然这也会对性能造成极大的影响。
- 实际应用中大多采用基于队列的延迟,设置不同延迟级别的队列,比如 5s、10s、30s、1min、5mins、10mins 等,每个队列中消息的延迟时间都是相同的,这样免去了延迟排序所要承受的性能之苦,通过一定的扫描策略(比如定时)即可投递超时的消息。
- 这里主要通过rabbitmq的死信队列实现,需要同时声明一个缓冲队列和一个延迟队列,给缓冲队列设置统一消息过期时间,当消息过期后,会自动被重新投递到死信队列(死信队列也是一个普通队列),只需要监听死信队列即可

三、api项目使用说明

  1. 首先需要定义延迟消费队列:即不管是订单关闭,还是优惠券过期的后续处理,都需要去监听这个消息
    在  application/libraries/mq/MqDlxQueue 去定义
    image.png
  2. 运行声明脚本
    php mq/StartDlx.php DELAY_QUEUE_TEST
  3. 定义缓冲区队列:即所有投递到这个队列的消息,设置统一的过期时间,比如固定2小时关闭订单,可以设置统一的队列过期时间为2小时。

    这里需要指定 dlx_key:即过期后投递到的死信队列的路由
    需要指定 message_ttl:即统一的消息过期时间,单位 毫秒

    注意:该 缓冲队列的 key,不要有任何消费者,不然被其他消费者消费后,就不会过期

    在 application/libraries/mq/MqQueue 去定义
    image.png

  4. 声明 缓冲队列
    php mq/Start.php BUFFER_QUEUE_TEST
  5. 设置 延迟队列消费者 
    image.png
  6. 发送 消息到 缓冲区
    image.png
  7. 测试效果:
    image.png

    image.png

redis实战经验

如何避免key冲突?

  • select db
    • 默认db16个,编号0~15,可通过配置修改db个数
    • 默认使用db-0
  • key命名格式
    • 以"xxx:yyy:zzz"格式命名

安全考虑

  • 访问限制
    • 只对内网访问,以防外部通过6379端口访问
  • 危险函数
    • 禁用危险函数:flush、flushall
  • 使用秘钥
    • $redis->auth(password)

性能提升

  • 连接
    • 使用官方扩展,弃用predis
    • 使用单例方式连接,使用pconnect
    • 用中间件,实现连接池
  • 持久化策略
    • 根据实际情况,关闭持久化
  • 服务部署
    • 与web服务共存,本地调用
    • 与存储服务隔离(mysql),避免高io
    • 一主多从,提升性能,或使用集群
  • 版本升级
    • 低版本:setnx + expire
    • 高版本:set(k, v, array(ex, nx))
  • 批量操作
    • 使用管道命令,批量导入数据
    • 使用mset, mget
  • 合理操作
    • 使用hset存放json,而不是set
    • 使用scan代替keys
    • Nginx + Lua + Redis

问题:
如何检查redis服务是否健康?

缓存设计中的要点

高性能

  • 简洁的通讯协议,快速连接
  • 操作基于内存,快速读写
  • 成熟的数据结构,快速定位
  • 基于牛X的语言实现,快速运行
  • 主从架构,读写分离

高可用

  • 异常监控,自动化应急
  • 数据灾备,快速恢复
  • 主从架构,从服务上位
  • 分布式、集群化
  • 服务自动降级
  • 多级缓存

存储

  • 成熟的数据结构,减少冗余
  • 数据压缩方案,减小数据大小
  • 置换算法:FIFO、LRU、LFU
  • 索引,以空间换时间
  • 数据分片,快捷扩容、无限扩容
  • 一定要设置过期时间!

常见缓存过期策略

策略 优点 缺点
定时过期
每个设置了过期时间的key,都携带一个定时器做倒计时,到期自动删除
立即清除,无空间浪费 定时器占用大量cpu,影响性能
惰性过期
只有当访问的时候,才会判断是否过期,过期则删除
最大化节省cpu 过期数据未被访问时,会占用存储,造成空间浪费
定期过期
每隔一定时间,随机扫描一定数量带有有效期的key,并清除其中已过期的key
前两种的这种方案 难点
需要合理设置 “时间”、 “数量”

常见存储置换策略:FIFO、LRU、LFU

  • FIFO(first in first out)
    • 淘汰最早的数据
  • LRU(least recently used)
    • 淘汰最长时间未被使用的数据
  • LFU(least frequently used)
    • 淘汰使用次数最少的数据

Redis置换策略配置(maxmemory-policy)

  • noeviction:不置换
  • volatile-[ lru | random | ttl ]
    • 对具备有效期的key,按lru/随机/最短置换
  • allkeys-[ lru | random ]
    • 对所有key,按lru/随机置换

一致性

缓存与数据库保持一致
- 低效做法:使用事务,数据库与缓存的更新
- 符合ACID
- 业务普遍做法:先更新数据库,再删除缓存
- 严谨但复杂的做法:消息队列、订阅binlog

旁路缓存原则:

读操作:
- 先读缓存
- 如果命中,直接返回
- 如果未命中,访问DB,并写入缓存

写操作:
- 删缓存,而不是更新缓存
- 先写DB,再删缓存

问题:
以下方式,存在什么隐患?
- 先更新缓存,再更新数据库
- 先删除缓存,再更新数据库
- 先更新数据库,再更新缓存

防雪崩

高并发时失效
雪崩:在用户高并发瞬间,如果缓存不可用(失效),用户的请求压力,都转到数据库,导致数据库挂掉,并最终导致整个系统挂掉。

如何防止缓存雪崩的发生?

  • 缓存服务高可用
    • 服务挂了,做什么都没用
  • 多个缓存不能同时过期
    • 随机失效,而不是同时或定点
  • 多级缓存
    • 多级缓存,尽可能把数据库挡在后面
  • 使用互斥锁
    • 只有拿到锁的请求,才能查库
  • 排队限流
    • 控制查库的并发峰值
  • 提前预热
    • 预先更新缓存
  • 公用性质缓存更应重视
    • 如:首页、推荐位
    • 个性化数据相对风险小
// 业务中改成这样是否有问题
function getDataBuyKey($strKey) {
    // 从缓存中取数据
    $arrData = getCacheData($strKey);
    // 如果未命中缓存,或缓存即将失效,抢占锁
    if (!$arrData || $arrData['ttl'] - time() < 10) {
        // 如果抢锁成功(带有效期的锁),可以查库
        if (redis::set('lock_' . $strKey, 1, array('nx', 'ex' => 10))) {
            $arrData = getDbData($strKey); // 查库
            setCacheData($arrData); // 更新缓存
        }
    }
    return $arrData;
}

防穿透

被故意不命中
用户伪造大量请求,故意不命中缓存,当这些请求集中转到数据库时,导致数据库挂掉,并最终导致整个系统挂掉。

如何防止缓存穿透的发生?

  • 缓存空值
    • 额外缓存一份空值,以防反复查库
  • 缓存数据范围
    • 如,应用ID范围、页码范围、时间段
  • 预先使用布隆过滤(bloom filter)
    • 一定不存在的数据,请求将被拦截
function getDataBuyKey($strKey) {
    $arrData = [];
    // 先执行key的有效验证
    if (checkKey($strKey)) {
        // 从缓存中取数据,未取到,返回null
        $arrData = getCacheData($strKey);
    }
    // 如果未命中缓存,或者缓存即将失效,抢占锁
    if (is_null($arrData) || $arrData['ttl'] - time() < 10) {
        // 如果抢锁成功(带有效期的锁),可以查库
        if (redis::set('lock_' . $strKey, 1, array('nx', 'ex' => 10))) {
            $arrData = (array)getDbData($strKey); // 查库
            setCacheData($arrData); // 更新缓存,若为空值,写入"[]"
        }
        is_null($arrData) && $arrData = []
    }
    return $arrData; // 返回空数据,或即将失效的数据,或最新的数据
}

用户系统演进

用户系统演进.ppt

编   写:袁 亮
时   间:2016-12-21
说   明:用户系统演进

一、用户系统限制因素
    1、使用域名
        单个域名
        多个域名
    2、使用系统是单服务器还是多服务器
    3、使用域名是否属于同一个域名的不同子域名
    4、系统访问环境
        PC浏览器
        是否兼容IE浏览器
        是否兼容手机浏览器
    5、用户数据库是否唯一
        单一用户数据库
        多个用户数据库
        
二、单用户中心,单域名
    1、条件限制
        1.1 单个域名
        1.2 单一用户数据库
    2、实现方案
        2.1 session
            磁盘IO 
            多服务器session共享
        2.2 cookie
            域名,端口,路径,cookie名
            数据签名
            大小限制
            敏感信息
    3、其他
        3.1 可以有多种登陆方式
        3.2 可以接入很多种第三方登陆
        
三、单用户中心,单个根域名
    1、条件限制
        1.1 单个用户数据库
        1.2 多个域名同属于某个根域名
    2、实现方案
        2.1 cookie共享 (育儿网)
            a. cookie设置在根域名
            b. 所有子域名都可以正常读取并验证签名
            c. 不可扩展,安全性无保障
        2.2 token形式 (共享session机制的变种)
            a. cookie设置在根域名
            b. 子域名读取到cookie之后,调用用户中心接口获取用户数据
            c. 将用户数据写入当前域名下
            
四、单用户中心,多个根域名
    1、条件限制
        1.1 单个用户数据库
    2、实现方案
        2.1 单点登陆 jsonp方式跨域 (淘宝)
            a. 统一的登陆,查收ticket
            b. jsonp方式将ticket循环传递给所有业务
            c. 业务域名根据ticket获取用户信息设置登陆
            d. 退出同样方式处理
            缺点:
                ios下,浏览器默认禁止第三方域名cookie设置,直接歇菜
        2.2 单点登陆 cas方式 (新浪)
            a. 通过jsonp+ajax+iframe的方式,在各个子域名框用户中心登陆
            b. 
            缺点:手机浏览器对iframe支持的很差
        2.3 服务端ticket形式,jsonp形式单点登录的变种
        
五、多用户中心
    1、条件限制
        无
    2、实现方案
        2.1 单域名或者多根域名跨域同三和四
    3、额外增加处理
        3.1 统一跳转地址
        3.2 用户聚合统一
        3.3 平台信息维护
        3.4 vtoken登录

如何配置测试环境,支持多分支开发

标签: 运维 测试环境搭建 apache


编 写:袁 亮
时 间:2016-09-05
说 明:如何配置测试环境,支持多分支开发

一、目的

1、业务开发,多人合作的时候,需要开启多个分支
2、每个分支的开发、调试、访问需要能跟其他分支独立

二、分支独立需要考虑如下问题

1、需要配置独立的访问规则,来支持各个分支
    要求:访问规格更改不需要改动程序
2、配置文件,每个分支可以自行配置,也可以使用通用的
    包含配置:
        数据库、缓存、静态文件等等
    解决方案:
        SetEnv CI_ENV development
        development配置目录不要放在svn里,通过svn ignore忽略
3、图片、文件上传
    解决办法:所有上传,走统一的接口,返回上传之后的地址即可,各个分支一样
4、api接口
    解决办法:在各自分支里,调用不同的api分支

三、访问规则配置方案

1、方案一:
    实施:将分支名以目录的形式放在测试hosts之后,比如shopdev的配置
    优点:新建分支很简单,在相应目录下建个分支目录即可
    缺点:因为多了一层目录,会导致整个的访问规则都不一样了,代码中需要做相应的兼容判断,比如出现这种:
        http://shopdev.ci123.com/svn/yungou/webroot/index_yiqigou.php/item-5287.html?store_id=120&share_uid=406259
        或者直接/目录开头的访问等等

2、方案二:
    实施:将分支名以泛域名解析的形式,隐藏到hosts里
    优点:对程序不用做任何改动,访问规则完全兼容(ps:代码里写死了绝路路径的这种自己挖坑的另算)
    缺点:测试域名一般不是实际解析的域名,也意味着没有走DNS解析的过程,只能通过hosts来实现
        而hosts并不具备解析功能,隐藏泛解析这种hosts是做不了的,只能每个分支自己加hosts
        ps:如果嫌加hosts麻烦,可以考虑将测试域名做实际解析
            或者在公司内部自建DNS(bind),缓存非测试域名,测试域名自己做解析
            比较麻烦,没太大意义,暂时不考虑
    其他:
        直接使用nginx+fastcgi会更简单,只是我们使用的apache为主,因此是配置的nginx+apache的方式

四、方案二实施

1、nginx 配置泛域名解析
    server_name通配符即可

2、apache支持泛域名解析(rewrite方式)
    1.1 ServerAlias 通配符实现泛解析
    1.2 rewrite 实现将分支名转为相应的目录名
    1.3 框架本身的rewrite重写
    1.4 静态文件rewrite重写

3、hosts 处理
    需要使用的分支,自行添加hosts

五、yungou.ws测试机最终使用

1、svn 创建分支,checkout到/opt/ci123/www/html/seller_shop下
    ps:分支名只支持大小写字母、数组、_组成的字符串
2、本地添加hosts
    192.168.0.249 分支名.yungou.ws
3、浏览器访问 (一些老旧浏览器需要强刷或者重启)
    分支名.yungou.ws

六、apache rewrite 概述

RewriteEngine On

# 将所有非static静态文件的请求,全部达到webroot的index中,框架本身的rewrite要求,将分支名嵌入进去
RewriteCond %{HTTP_HOST} [\w_]+.yungou.ws
RewriteCond %{REQUEST_URI} !static.*
#将hosts放到rewrite匹配源中,需要其中的分支名作为后续使用 跟下一条规则关联
RewriteRule ^(.+) %{HTTP_HOST}$1 [C] 
RewriteRule ([\w_]+).yungou.ws(.*) /$1/webroot/index.php/$2 [L]

# static 静态文件目录不rewrite,直接访问
RewriteCond %{HTTP_HOST} [\w_]+.yungou.ws
RewriteCond %{REQUEST_URI} static.*
RewriteRule ^(.+) %{HTTP_HOST}$1 [C]
RewriteRule ([\w_]+).yungou.ws(.*) /$1/webroot/$2 [L]

附:参考文档

1、nginx泛解析域名实现多级域名多个同时绑定
2、nginx泛域名解析实现二级域名多域名
3、Apache 2.2 + Tomcat 泛域名解析 动态二级域名
4、使apache解析域名到目录的方法
5、Apache的Rewrite规则详细介绍
6、Apache Module mod_rewrite
7、解析无限个二级域名的方法

xhprof实际使用过程中的一些注意事项


编	写:袁	亮
时	间:2016-01-12
说	明:xhprof实际使用过程中的一些注意事项

一、扩展安装
	1、前置说明
		1.1 这个组件作为性能分析工具,测试环境有时候不能重现问题
		1.2 直接在生产环境安装,容易导致当前服务受影响或者不可用
		1.3 开启对性能也有一定影响,特别是大流量的情况下,直接全部开始,那就悲剧了
	2、分流开启(成本适中、危险性低)
		2.1 采用nginx分流,使一小部分流量请求发送到安装了xhprof的服务上
		2.2 安装的那台服务器,可用开启监控,并跟生产环境对比
		2.3 既可以达到采集到实际数据,也不影响大部分的用户和业务
	3、流量复制(成本较高,基本无危险)
		3.1 将正式环境的流量复制到测试机上,开启监控查看
		3.2 优点是对原业务几乎无影响,也方便进行流量放大,做压力测试等
		3.3 缺点是之前没做过,略有点麻烦

二、程序开启并记录
	ps:下载http://pecl.php.net/get/xhprof-0.9.2.tgz,参考其中的example/example.php的写法
	
	1、开启监控
		xhprof_enable();
		ps:只在需要的地方开,不要全部开,或者以一定概率开启	
	2、停止监控采集
		$xhprof_data = xhprof_disable();
	3、采集监控数据
		$XHPROF_ROOT = realpath(dirname(__FILE__) .'/..');
		include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
		include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
		$xhprof_runs = new XHProfRuns_Default('可以保存到自己定义的文件夹中,未设置则采用php中的默认设置');
	4、保存监控数据
		$run_id = $xhprof_runs->save_run($xhprof_data, "保存数据的后缀,作为同一项目的区分用");
		ps:可以将数据保存在一个挂载盘中,这样可以在一台统一的服务器上统一查看调用

	ps:后期也可以考虑自动化,针对慢的请求,直接在php配置中auto_prepend_file增加一段监控程序,按一定比例采样,并汇总分析
	
三、显示性能分析
	1、可以在任意一台能访问存储的监控数据的地方
	2、复制下载文件中的xhprof_html文件夹,并放在可web访问的地方
	3、通过保存时候生成的run_id和自己设置的后缀,即可访问
	4、重点关注callgraph.php,生成的调用链接,主要耗时的调用链有非常明显的颜色标注出来,一眼就能看到性能瓶颈在哪
		ps:这个需要graphviz和graphviz-gd


使用token来解决用户信息同步问题


编	写:袁	亮
时	间:2015-11-27
说	明:多应用系统用户信息统一安全升级策略

一、问题
	1、post请求之后,后退按钮导致加载失败,提示需要重新提交数据问题的解决
	2、用户信息被抓包,会导致账号泄漏问题
	
二、原因
	1、根据http协议,post请求非幂等,不能缓存,因此有响应头Pragma:no-cache
	2、后退的时候,浏览器会强制提醒用户,确认是否重新输入数据
	3、因此后退会出现白页

三、可能解决思路
	1、post请求换成get请求,绕过限制
		1.1 私密数据不安全,如果链接发给其他人,会导致账号被盗
		1.2 统计代码中,会把这些链接都给存储下来,太危险
	2、对get的数据进行可逆加密并加过期时间
		还是解决不了上面的两个问题
	3、先post发送私密数据过去,然后get跳转
	
四、完整思路
	1、私密数据,必须要走post,不能get
	2、先通过服务器间请求,将私密数据post过去
		2.1 在客户端对数据进行签名及有效期
		2.2 通过post将数据通过服务器与服务器传输
		2.3 服务端返回该数据存储的token(类似session存储)
			为防止服务器返回较慢,导致token丢失,token以客户端传过去的签名为准
		2.4 该数据,只存储一个小时,超过则失效
	3、get形式带上token去请求数据
		3.1 根据token,取到对应的数据
		3.2 用户之后的token要删除,不允许重复使用
		3.3 取出的数据需要验证是否有效期内,并验证签名是否正确
	4、解决了哪些问题
		4.1 没有通过get传输私密数据
		4.2 浏览器没有发起post请求,因此不存在后退刷新的问题
		4.3 私密数据通过服务器间的post请求,因此对用户抓包,并不能获取到这份私密数据
			抓更原始的包那就没办法,虽然不知道原始数据,但还是能登陆到用户账户上
	5、导致的问题及解决办法
		5.1 比之前多了一次http请求,会变的更慢
			在相应的服务器端加host,降低解析时间(同一机房,走内网)
			服务端只做数据存储到内存,不做额外操作降低运行时间
			客户端设置超时,防止卡住
		5.2 memcache存储的数据,存在一定几率丢失
			可以暂时不考虑,概率太低,而且最坏的影响也就是用户某一次可能登陆不上(只有新用户)
		5.3 token丢失或被抓包
			32位的加盐散列,基本可以不考虑被暴力强刷
			token即使丢失,问题也不大,因为token生成之后,用户基本上马上就使用,之后token就失效了
	

使用范例见附件:build_query

php序列化对象导致的错误


编	写:袁	亮
时	间:2014-04-11
说	明:序列化对象导致的错误

一、问题描述
	在迁移blogadmin项目的时候遇到了一个报错:(迁移前php版本:5.2.6、迁移后php版本:5.3.7)

	Fatal error: PostsController::index(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "Information" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition

二、解决过程
	1、根据报错信息搜索,查找到文档:http://www.php.net/manual/zh/language.oop5.serialization.php
	2、按文档里的说明,结合程序,出现问题的原因是:
		a、程序中,会把对象存储会话中(session,session会调用php的serialize函数序列化对象)
		b、在新的页面中,程序从session中,取出数据,并自行调用unserialize函数,反序列化对象,并且正常使用对象
		c、以上要正常使用,必须保证对象所对应的类定义要在unserialize之前,否则反序列化出来的对象会有缺失,从而导致错误
		d、一般情况下,调整session_start和引用类定义文件的顺序即可
		
	3、根据上述信息,调试程序,发现调用对象的时候,类已经定义,而且类定义的引用在session_start之前
	4、经查,发现是该服务器的php配置中,自动开启了session_start,导致session_start永远在类定义前,从而导致错误发生
	5、修改php配置,并重启apache即可

	

linux screen命令的简单使用


编	写:袁	亮
时	间:2015-07-27
说	明:linux screen命令的简单使用

一、解决什么问题?
	1、在服务器执行一些比较耗时的事情:下载、系统备份、传输文件、安装软件或者执行一个脚本等
	2、当因为某些原因与服务器断开连接,所有的执行都会白费(网突然断了,putty不小心关了,电脑卡死等等原因)
	3、或者我们在同一台服务器上需要做多件事,经常需要切换目录等等
	当出现以上情况时,都可以考虑使用screen来解决
	
二、大致原理
	1、正常我们使用putty连接到服务器,相当于一次会话
	2、当连接终止的时候,该会话相关的进程都会被终止,关闭,因此就会出现任务还没执行完,就挂掉了
	3、screen相当于在服务器上使用后台又开了一个会话,因此当前这个会话被关闭,不影响新开的那个会话中的任务执行
	4、当我们putty断掉之后,可以重新连接上去,查看之前的screen会话中的执行情况
	
三、简单使用
	1、添加一个screen窗口
		screen -S 自己定义窗口名,方便查看
	2、查看现在有几个screen
		screen -ls
	3、中断跳出某个screen窗口
		ctrl+a+d
	4、选择恢复某个screen窗口
		screen -r 创建时候定义的名字,或者通过screen -ls中看到的数字编号
	5、彻底关闭某个screen窗口
		5.1 在那个窗口中直接exit
		5.2 在那个窗口下,ctrl+d
		5.3 在那个窗口下,ctrl+a+k
		
四、原理(有兴趣的可以了解下,没兴趣的知道上面的即可)
	1、进程组、会话期
		进程组(process group):一个或多个进程的集合,每一个进程组有唯一一个进程组ID,即进程组长进程的ID
		会话期(session):一个或多个进程组的集合,有唯一一个会话期首进程(session leader)。会话期ID为首进程的ID
		会话期可以有一个单独的控制终端(controlling terminal)。与控制终端连接的会话期首进程叫做控制进程(controlling process)。当前与终端交互的进程称为前台进程组。其余进程组称为后台进程组
	2、挂断信号(SIGHUP)默认的动作是终止程序
		当终端接口检测到网络连接断开,将挂断信号发送给控制进程(会话期首进程)
		如果会话期首进程终止,则该信号发送到该会话期前台进程组
		一个进程退出导致一个孤儿进程组中产生时,如果任意一个孤儿进程组进程处于STOP状态,
		发送SIGHUP和SIGCONT信号到该进程组中所有进程
	3、因此当网络断开或终端窗口关闭后,控制进程收到SIGHUP信号退出,会导致该会话期内其他进程退出
	4、nohup使用
		nohup [要执行的shell命令] &
		很多时候,我是没加nohup,直接最后加&也可
	5、screen是直接新建会话,然后避免掉了上面的这个问题,nohup是忽略挂断信号来达到目的
	
附录:
	http://www.cnblogs.com/mchina/archive/2013/01/30/2880680.html
	http://www.ibm.com/developerworks/cn/linux/l-cn-screen/