浅谈https服务的SSL免费证书申请

摘要:随着这两年SSL证书的普及,使用了SSL证书启用了HTTPS协议的网站越来越多了,最近为了把网站开启全站HTTPS,特意申请了一个免费的SSL证书用了起来。在百度云、阿里云、腾讯云三大国内的云服务平台都有免费一年的SSL证书。

一、阿里云免费SSL证书

阿里云的免费SSL并不难申请,只要注册阿里云帐号,并通过认证就可以申请了。

申请网址https://www.aliyun.com/product/cas?utm_content=se_442415

点击上面的网址,进入阿里云的SSL证书介绍页面,点击“购买”就会有一个“免费型DV SSL”供选择。支持1个域名,且一次性只能购买1年,最后的费用为0。

阿里云的免费SSL证书为Symantec的,且一个阿云帐户最多签发20张免费证书。

二、百度云免费SSL证书

百度云的SSL证书和阿里云一样,也是Symantec – TrustAsia提供的,而且百度云目前为止还没有推出收费的SSL证书,只有免费的。也是支持1个域名,且一次性只能购买一年,最后的费用为0。

百度云免费DV SSL证书申请网址https://cloud.baidu.com/product/cas.html

百度云的免费SSL证书暂不支持证书导出,必须在成功签发后30天内部署使用,否则百度可能会将证书强制删除。

三、腾讯云免费SSL证书

腾讯云免费SSL证书网址:https://www.qcloud.com/product/ssl

腾讯云的也是Symantec品牌的数字证书,一样也是提供一年1个域名的服务,且申请的时候需要域名所有权验证。不过证书颁发的效率很快,差不多20几分钟就能下证书。

参考:腾讯云ssl证书申请指南

四、亚马逊AWS免费SSL证书

需要特别说明一下这个亚马逊是美国的那个亚马逊Amazon(不是我们卖书的那个卓越亚马逊),其实才是真正的巨头企业,亚马逊Amazon的云服务能力和技术在全球都是知名的,最早阿里云其实就是效仿的亚马逊Amazon的云服务。

申请网址:https://aws.amazon.com/cn/free/

之所以把亚马逊AWS的免费SSL证书放在最后,虽然亚马逊AWS在全球具有领先的影响力,但是因为在国内几大巨头的影响下,他还是被夹击的。而且亚马逊AWS的介绍页面很“老外”,虽然网页是中文的,但是内容却里中国上网习惯相差很大。在BAT的免费政策下其实基本上都够用了,所以也就拿它做个备选吧。

最后,很多人可能看到所有的免费SSL证书都是只提供一年的使用期限,会担心一年后怎么办的问题。其实我觉得不用多想,虽然现在确实是SSL证书的推广期,所以各大云服务厂商才推出免费SSL。但是他们并没有说这个政策进行到什么时候,而且每个平台都是支持申请多个免费证书的,到时候只要一年期结束后,继续申请一个新的免费证书就可以了。

 

 

MySQL中的日志总结

MySQL中有六种日志文件:

分别是:重做日志(redo log)、回滚日志(undo log)、二进制日志(binlog)、错误日志(errorlog)、慢查询日志(slow query log)、一般查询日志(general log),中继日志(relay log)。

其中重做日志和回滚日志与事务操作息息相关,二进制日志也与事务操作有一定的关系,这三种日志,对理解MySQL中的事务操作有着重要的意义。
这里简单总结一下这三者具有一定相关性的日志。
重做日志(redo log)

作用:
  确保事务的持久性。
  防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
内容:
  物理格式的日志,记录的是物理数据页面的修改的信息,其redo log是顺序写入redo log file的物理文件中去的。
什么时候产生:
  事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。
什么时候释放:
  当对应事务的脏页写入到磁盘之后,redo log的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。
对应的物理文件:
  默认情况下,对应的物理文件位于数据库的data目录下的ib_logfile1&ib_logfile2
  innodb_log_group_home_dir 指定日志文件组所在的路径,默认./ ,表示在数据库的数据目录下。
  innodb_log_files_in_group 指定重做日志文件组中文件的数量,默认2
  关于文件的大小和数量,由一下两个参数配置
  innodb_log_file_size 重做日志文件的大小。
  innodb_mirrored_log_groups 指定了日志镜像文件组的数量,默认1
其他:
  很重要一点,redo log是什么时候写盘的?前面说了是在事物开始之后逐步写盘的。
  之所以说重做日志是在事务开始之后逐步写入重做日志文件,而不一定是事务提交才写入重做日志缓存,
  原因就是,重做日志有一个缓存区Innodb_log_buffer,Innodb_log_buffer的默认大小为8M(这里设置的16M),Innodb存储引擎先将重做日志写入innodb_log_buffer中。

1

然后会通过以下三种方式将innodb日志缓冲区的日志刷新到磁盘

  1)Master Thread 每秒一次执行刷新Innodb_log_buffer到重做日志文件。
  2)每个事务提交时会将重做日志刷新到重做日志文件。
  3)当重做日志缓存可用空间 少于一半时,重做日志缓存被刷新到重做日志文件
  由此可以看出,重做日志通过不止一种方式写入到磁盘,尤其是对于第一种方式,Innodb_log_buffer到重做日志文件是Master Thread线程的定时任务。

因此重做日志的写盘,并不一定是随着事务的提交才写入重做日志文件的,而是随着事务的开始,逐步开始的。
另外引用《MySQL技术内幕 Innodb 存储引擎》(page37)上的原话:
即使某个事务还没有提交,Innodb存储引擎仍然每秒会将重做日志缓存刷新到重做日志文件。
这一点是必须要知道的,因为这可以很好地解释再大的事务的提交(commit)的时间也是很短暂的。
回滚日志(undo log)

作用:
  保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即非锁定读
内容:
  逻辑格式的日志,在执行undo的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。
什么时候产生:
  事务开始之前,将当前是的版本生成undo log,undo 也会产生 redo 来保证undo log的可靠性
什么时候释放:
  当事务提交之后,undo log并不能立马被删除,
  而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间。
对应的物理文件:
  MySQL5.6之前,undo表空间位于共享表空间的回滚段中,共享表空间的默认的名称是ibdata,位于数据文件目录中。
  MySQL5.6之后,undo表空间可以配置成独立的文件,但是提前需要在配置文件中配置,完成数据库初始化后生效且不可改变undo log文件的个数
  如果初始化数据库之前没有进行相关配置,那么就无法配置成独立的表空间了。
  关于MySQL5.7之后的独立undo 表空间配置参数如下
  innodb_undo_directory = /data/undospace/ --undo独立表空间的存放目录
  innodb_undo_logs = 128 --回滚段为128KB
  innodb_undo_tablespaces = 4 --指定有4个undo log文件

  如果undo使用的共享表空间,这个共享表空间中又不仅仅是存储了undo的信息,共享表空间的默认为与MySQL的数据目录下面,其属性由参数innodb_data_file_path配置。2
其他:
  undo是在事务开始之前保存的被修改数据的一个版本,产生undo日志的时候,同样会伴随类似于保护事务持久化机制的redolog的产生。
  默认情况下undo文件是保持在共享表空间的,也即ibdatafile文件中,当数据库中发生一些大的事务性操作的时候,要生成大量的undo信息,全部保存在共享表空间中的。
  因此共享表空间可能会变的很大,默认情况下,也就是undo 日志使用共享表空间的时候,被“撑大”的共享表空间是不会也不能自动收缩的。
  因此,mysql5.7之后的“独立undo 表空间”的配置就显得很有必要了。

二进制日志(binlog):

作用:
  1,用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步。
  2,用于数据库的基于时间点的还原。
内容:
  逻辑格式的日志,可以简单认为就是执行过的事务中的sql语句。
  但又不完全是sql语句这么简单,而是包括了执行的sql语句(增删改)反向的信息,
  也就意味着delete对应着delete本身和其反向的insert;update对应着update执行前后的版本的信息;insert对应着delete和insert本身的信息。
  在使用mysqlbinlog解析binlog之后一些都会真相大白。
  因此可以基于binlog做到类似于oracle的闪回功能,其实都是依赖于binlog中的日志记录。
什么时候产生:
  事务提交的时候,一次性将事务中的sql语句(一个事物可能对应多个sql语句)按照一定的格式记录到binlog中。
  这里与redo log很明显的差异就是redo log并不一定是在事务提交的时候刷新到磁盘,redo log是在事务开始之后就开始逐步写入磁盘。
  因此对于事务的提交,即便是较大的事务,提交(commit)都是很快的,但是在开启了bin_log的情况下,对于较大事务的提交,可能会变得比较慢一些。
  这是因为binlog是在事务提交的时候一次性写入的造成的,这些可以通过测试验证。
什么时候释放:
  binlog的默认是保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。
3
对应的物理文件:
  配置文件的路径为log_bin_basename,binlog日志文件按照指定大小,当日志文件达到指定的最大的大小之后,进行滚动更新,生成新的日志文件。
  对于每个binlog日志文件,通过一个统一的index文件来组织。

4
其他:
  二进制日志的作用之一是还原数据库的,这与redo log很类似,很多人混淆过,但是两者有本质的不同
  1)作用不同:redo log是保证事务的持久性的,是事务层面的,binlog作为还原的功能,是数据库层面的(当然也可以精确到事务层面的),虽然都有还原的意思,但是其保护数据的层次是不一样的。
  2)内容不同:redo log是物理日志,是数据页面的修改之后的物理记录,binlog是逻辑日志,可以简单认为记录的就是sql语句
  3)另外,两者日志产生的时间,可以释放的时间,在可释放的情况下清理机制,都是完全不同的。
  4)恢复数据时候的效率,基于物理日志的redo log恢复数据的效率要高于语句逻辑日志的binlog

关于事务提交时,redo log和binlog的写入顺序,为了保证主从复制时候的主从一致(当然也包括使用binlog进行基于时间点还原的情况),是要严格一致的,
MySQL通过两阶段提交过程来完成事务的一致性的,也即redo log和binlog的一致性的,理论上是先写redo log,再写binlog,两个日志都提交成功(刷入磁盘),事务才算真正的完成。
参考:http://www.cnblogs.com/hustcat/p/3577584.html

总结:

MySQL中,对于以上三种日志,每一种细化起来都可以够写一个章节的,这里粗略地总结了一下三种日志的一些特点和作用,以帮助理解MySQL中的事物以及事物背后的原理。

参考:《MySQL技术内幕 Innodb 存储引擎》

设计模式-组合模式

组合模式将对象组合成树形结构,以表示‘部分-整体’的层次结构。

在组合模式,客户端访问独立对象和组合对象(或称对象集合)一样

独立对象是一个有特定功能的对象,它不引用其他任何其他对象。

组合对象则是一个提供相似功能对象集合,主要用来管理独立对象,并为客户端提供和独立对象一样的访问方式。

接下来,我们就以目录和文件来举例,利用它们的一个相同的功能“查看大小”来举例。

1 问题

在文件系统中,我们有文本文件、图片文件、视频文件等类型的文件。

它们格式不同,但都有容量大小。

现在,我们创建文本和图片文件两个类,且各自有固定的大小。

(实际当然不是如此,这里举例所以简化处理。)

接着,我们要获取文件和目录的大小。

首先是文件:

abstract class File
{
    abstract function getSize();
}

class TextFile extends File
{
    public function getSize()
    {
        return 2;
    }
}

class ImageFile extends File
{
    public function getSize()
    {
        return 100;
    }
}

这样,在创建文本或图片对象后,就可以通过getSize()方法获取到它们的大小。

然后,我们创建一个目录类,它可以把文件组合起来

class Dir
{
    private $files = [];

    // 传入参数必须为File文件对象
    public function addFile(File $file)
    {
        $this->files[] = $file;
    }

    public function getSize()
    {
        $size = 0;
        foreach ($this->files as $file) {
            $size += $file->getSize();
        }

        return $size;
    }
}

然后,我们就可以计算目录的大小,它等于目录下所有文件大小之和。

例如,这个目录加入一个文本文件,一个图片文件,那么这个目录大小就是:102

当然,如果问题一直这样简单的话,那么这个模型还是非常令人满意的。

但是,如果有一些新的需求加入会怎样?

比如,要在目录中再加一层目录,那么Dir类就需要变成:

class NewDir
{
    private $files = [];
    private $dirs = [];

    public function addFile(File $file)
    {
        $this->files[] = $file;
    }

    public function addDir(NewDir $newDir) {
        $this->dirs = $newDir;
    }

    public function getSize()
    {
        $size = 0;
        foreach ($this->files as $file) {
            $size += $file->getSize();
        }

        foreach ($this->dirs as $dir) {
            $size += $dir->getSize();
        }

        return $size;
    }
}

是不是比之前又复杂了些?

这还不算,我们还需要修改原来的类,可能无意间又影响原来的功能 。

另外,如果我们现在要计算多级子目录的大小、或者从目录中删除目录,是不是还需要修改原有类?

显然,这个模型无法实现这些复杂的功能,我们需要一个更加灵活的模型。

2 组合模式

组合模式的解决方法是,用抽象类规范统一的对外接口。

然后,让文件类和目录类实现这个接口,并在目录类中递归计算文件的大小

同时,目录类比文件类两个方法:add()remove(),用以管理文件对象。

这样,目录类就能用同样的方式获取自身的大小。

并且,还能灵活从目录总增删子目录和文件。

2.1 接口

接口用于规范独立对象和组合对象,保证能够对外提供一致性的使用方法。

这里以getName()getSize()方法为例:

/**
 * 规范独立对象和组合对象必须实现的方法,保证它们提供给客户端统一的
 * 访问方式
 */
abstract class Filesystem
{
    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public abstract function getName();
    public abstract function getSize();
}

其中,__construct构建函数用于传入文件或目录名称,并非必须。

这个接口中规范的方法要根据需求来定义,并且同时要考虑独立对象拥有的功能

如果独立对象之间有差异的功能,不适合聚合在一起,则不能放在组合类中。

2.2 目录类

目录类是对象集合,通过add()remove()方法管理文件对象和其他目录对象

目录类也需要实现抽象类中的方法,以提供给客户端一致性的使用方式。

/**
 * 目录类
 */
class Dir extends Filesystem
{
    private $filesystems = [];

    // 组合对象必须实现添加方法。因为传入参数规定为Filesystem类型,
    // 所以目录和文件都能添加
    public function add(Filesystem $filesystem)
    {
        $key = array_search($filesystem, $this->filesystems);
        if ($key === false) {
            $this->filesystems[] = $filesystem;
        }
    }

    // 组合对象必须实现移除方法
    public function remove(Filesystem $filesystem)
    {
        $key = array_search($filesystem, $this->filesystems);
        if ($key !== false) {
            unset($this->filesystems[$key]);
        }
    }

    public function getName()
    {
        return '目录:' . $this->name;
    }

    public function getSize()
    {
        $size = 0;
        foreach ($this->filesystems as $filesystem) {
            $size += $filesystem->getSize();
        }

        return $size;
    }
}

2.3 文件类

文件类实现具体的功能,但是没有add()remove()方法。

/**
 * 独立对象:文本文件类
 */
class TextFile extends Filesystem
{
    public function getName()
    {
        return '文本文件:' . $this->name;
    }

    public function getSize()
    {
        return 10;
    }
}

/**
 * 独立对象2:图片文件类
 */
class ImageFile extends Filesystem
{
    public function getName()
    {
        return '图片:' . $this->name;
    }

    public function getSize()
    {
        return 100;
    }
}

/**
 * 独立对象:视频文件类
 */
class VideoFile extends Filesystem
{
    public function getName()
    {
        return '视频:'. $this->name;
    }

    public function getSize()
    {
        return 200;
    }
}

组合模式中,组合对象必须在合适的地方提供独立对象的管理方法,如:add()remove()等。

组合模式分为安全模式透明模式,这是根据接口中是否包含管理对象的方法来区分的。

上面的例子我们举例的是安全模式,在接口中没有声明add()remove()方法管理方法。

这样有一个缺点:组合对象和独立对象不具有相同的接口,客户端调用需要做相应的判断,带来了不便。

另外一种是透明模式,在接口中就声明add()remove()方法。

这样所有的实现类都具备了add()remove(),好处就是组合对象和独立对象具有一致的接口。

但问题也很明显,因为独立对象不具备add()remove()方法的功能,所以实现他是没有意义的。

不管那种模式,都根据实际需要来配置。

2.4 客户端

然后,我们就可以在客户端中使用这个程序。

例如,我们要构建这样一个文件目录结构:

home
├─text1.txt
├─bg1.png
├─film1.mp4
├─source
│  ├─text2.txt

代码就是:

// 创建home目录,并加入三个文件
$dir = new Dir('home');
$dir->add(new TextFile('text1.txt'));
$dir->add(new ImageFile('bg1.png'));
$dir->add(new VideoFile('film1.mp4'));

// 在home下创建子目录source
$subDir = new Dir('source');
$dir->add($subDir);

// 创建一个text2.txt,并放到子目录source中
$text2 = new TextFile('text2.txt');
$subDir->add($text2);

// 打印信息
echo $text2->getName(), '-->', $text2->getSize();
echo '<br />';
echo $subDir->getName(), ' --> ',$subDir->getSize();
echo '<br />';
echo $dir->getName(), ' --> ', $dir->getSize();

可以看到,文件对象(独立对象)获取名称用getName()方法,目录对象(组合对象)用的也是getName()方法。

同样,获取大小用的也都是getSize()方法。

输出的结果为:

文本文件:text2.txt-->10
目录:source --> 10
目录:home --> 320

3 特点

在组合模式中,组合对象和独立对象必须实现一个接口

其中,组合对象必须包含添加和删除节点对象

组合模式通过和装饰模式有着类似的结构图,但是组合模式旨在构造类,而装饰模式重在不生成子类即可给对象添加职责。

并且,装饰模式重在修饰,而组合模式重在表示

组合模式的UML图:

7

设计模式-适配器模式

适配器模式,即根据客户端需要,将某个类的接口转换成特定样式的接口,以解决类之间的兼容问题。

如果我们的代码依赖一些外部的API,或者依赖一些可能会经常更改的类,那么应该考虑用适配器模式。

下面我们以集成支付宝支付功能为例。

1 问题

假设支付宝支付类的功能如下:

/**
 * 支付宝支付类
 */
class Alipay
{
    public function sendPayment()
    {
        echo '使用支付宝支付。';
    }
}

// 客户端代码
$alipay = new Alipay();
$alipay->sendPayment();

我们直接实例化Alipay类完成支付功能,这样的客户端代码可能很多。

一段时间后,如果支付宝的Alipay类升级,方法名由sendPayment()变成goPayment()会怎样?

所有用了sendPayment()的客户端代码都要改变。

如果Alipay类频繁升级,或者客户端在很多地方使用,这会是极大的工作量。

2 解决

现在我们用适配器模式来解决。

我们在客户端和Alipay类之间加一个中间类,也就是适配器类,转换原始的Alipay为客户端需要的形式。

为让客户端能调用到统一的类方法,我们先定义一个适配器接口:

/**
 * 适配器接口,所有的支付适配器都需实现这个接口。
 * 不管第三方支付实现方式如何,对于客户端来说,都
 * 用pay()方法完成支付
 */
interface PayAdapter
{
    public function pay();
}

因为Alipay类我们无法控制,而且它有可能经常更新,所以我们不对它做任何修改。

我们新建一个AlipayAdapter适配器类,在pay()中转换Alipay的支付功能,如下:

/**
 * 支付宝适配器
 */
class AlipayAdapter implements PayAdapter
{
    public function pay()
    {
        // 实例化Alipay类,并用Alipay的方法实现支付
        $alipay = new Alipay();
        $alipay->sendPayment();
    }
}

客户端使用方式:

// 客户端代码
$alipay = new AlipayAdapter();
// 用pay()方法实现支付
$alipay->pay();

这样,当Alipay的支付方法改变,只需要修改AlipayAdapter类就可以了。

3 适配新类

有了适配器后,扩展也变得更容易了。

继续以上的例子,在支付宝的基础上,我们再增加微信支付,它与支付宝的支付方式不同,必须通过扫码才能支付。

这种情况也应该使用适配器,而不是直接使用微信的支付功能。

代码如下:

/**
 * 微信支付类
 */
class WechatPay
{
    public function scan()
    {
        echo '扫描二维码后,';
    }

    public function doPay()
    {
        echo '使用微信支付';
    }
}

/**
 * 微信支付适配器
 */
class WechatPayAdapter implements PayAdapter
{
    public function pay()
    {
        // 实例化WechatPay类,并用WechatPay的方法实现支付。
        // 注意,微信支付的方式和支付宝的支付方式不一样,但是
        // 适配之后,他们都能用pay()来实现支付功能。
        $wechatPay = new WechatPay();
        $wechatPay->scan();
        $wechatPay->doPay();
    }
}

客户端使用:

// 客户端代码
$wechat = new WechatPayAdapter();
// 也是用pay()方法实现支付
$wechat->pay();

这就是适配器的扩展特性。

我们创建了一个用于处理第三方类(支付宝、微信支付)的方法,

如果它们的API有变化,我们仅需修改客户端依赖的适配器类就可以,不用修改、暴露第三方类本身。

4 UML图

以上适配器模式的代码对应UML如下:

6

注意:适配器模式中,适配器类的名称和创建方式一定是不会频繁改动的。

对于客户端来说,引用适配器类的方式应该是统一而不变的,这才算是正确使用适配器。

5 总结

大的应用都会不断地加入新库和新API。

为避免它们的变更引发问题,应该用适配器模式包装起来,提供应用统一的引用方式。

它会让我们的代码更具结构化,便于管理和扩展。

跨域ajax请求如何保持session状态

1.提出问题:

Ajax在跨域请求的时候,session会话会丢失,每请求一次都会生成一个新的session_id,即当成一个新的会话,无法跟踪用户的状态,这个问题该如何解决呢?

2.解决办法:

第一步:前端ajax部分:

$.ajax({
  url: 'http://my.test.com/server.php', //跨域 
  xhrFields:{withCredentials: true}, // 发送凭据,表示保持会话 
  dataType: 'json', 
  type: 'post', 
  data: {'name':'fdipzone'},          
  success:function(ret){ 
    if(ret['success']==true){ 
     alert('cookie:' + ret['cookie']); 
   } 
  } 
});

第二步:后端服务部分(server.php):

//允许跨域访问的来源域名,如:http://localhost:8080,只能设置一个,
//以下设置的是任意来源
header('Access-Control-Allow-Origin:'.$_SERVER['HTTP_ORIGIN']);
//允许跟踪会话,保持session或cookie
header('Access-Control-Allow-Credentials:true');
//允许请求的方式:GET和POST
header('Access-Control-Allow-Methods:GET,POST');
//允许的headers
header('Access-Control-Allow-Headers:x-requested-with,content-type');

第三步:到处前后端就实现了跨域访问,并且能够跟踪会话,大功告成!

注意事项:

1.当Access-Control-Allow-Credentials设为true时,不能如下这么设置,必须绑定一个具体的域名:
header('Access-Control-Allow-Origin:*');//不对
2.在vue.js框架下实现跨域时:在main.js中开启跨域配置即可:
Vue.http.options.emulateJSON = true
Vue.http.interceptors.push(function (request, next) {     
   request.credentials = true     
   next()
})

 

 

网络TCP建立连接为什么需要三次握手而结束要四次

举个打电话的例子:

A : 你好我是A,你听得到我在说话吗

B : 听到了,我是B,你听到我在说话吗

A : 嗯,听到了

建立连接,开始聊天!

8
direct

为什么TCP协议终止链接要四次?

1、当主机A确认发送完数据且知道B已经接受完了,想要关闭发送数据口(当然确认信号还是可以发),就会发FIN给主机B。

2、主机B收到A发送的FIN,表示收到了,就会发送ACK回复。

3、但这是B可能还在发送数据,没有想要关闭数据口的意思,所以FIN与ACK不是同时发送的,而是等到B数据发送完了,才会发送FIN给主机A。

4、A收到B发来的FIN,知道B的数据也发送完了,回复ACK, A等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭链接,B也关闭链接了。

A为什么等待2MSL,从TIME_WAIT到CLOSE?

在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

这个网上转载的例子不错:

三次握手:
A:“喂,你听得到吗?”A->SYN_SEND

B:“我听得到呀,你听得到我吗?”应答与请求同时发出 B->SYN_RCVD | A->ESTABLISHED

A:“我能听到你,今天balabala……”B->ESTABLISHED

四次挥手:
A:“喂,我不说了。”A->FIN_WAIT1

B:“我知道了。等下,上一句还没说完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2

B:”好了,说完了,我也不说了。”B->LAST_ACK

A:”我知道了。”A->TIME_WAIT | B->CLOSED

A等待2MSL,保证B收到了消息,否则重说一次”我知道了”,A->CLOSED
链接:https://zhuanlan.zhihu.com/p/21940234

参考:http://blog.chinaunix.net/uid-25002135-id-3314682.html
http://www.cnblogs.com/vamei/archive/2012/12/16/2812188.html

Thinkphp5 同时使用Mysql和MongoDB数据库

项目需要Thinkphp5同时使用Mysql和Mongodb数据库,本篇文章详细介绍Thinkphp5 如何同时使用Mysql和MongoDB数据库。

官方文档:https://www.kancloud.cn/manual/thinkphp5/167865

一、在database.php配置默认数据库连接

4
‘type’           => ‘mysql’,
‘hostname’       => ‘服务器IP地址’,
‘database’       => ‘数据库名’,
‘username’       => ‘用户名’,
‘password’       => ‘密码’,
‘hostport’       => ‘数据库端口’,

二、在config.php配置第二个数据库连接

5
‘db_mongo’ => [
‘type’    =>   ‘\think\mongo\Connection’,
‘hostname’    =>   ‘数据库服务器IP地址’,
‘database’    =>   ‘数据库名’,
‘username’    =>   ‘用户名’,
‘password’     =>   ‘密码’,

‘hostport’    =>   27017,

],
Thinkphp5扩展MongoDB可参考:Thinkphp5 扩展 MongoDB 详解

三、数据库使用

//默认数据库读取数据
$test = Db::name(“test”)->select();
//第二个数据库读取数据

$test1=Db::connect(“db_mongo”)->name(“test”)->select();

用Redis轻松实现秒杀系统

秒杀系统的架构设计

秒杀系统,是典型的短时大量突发访问类问题。对这类问题,有三种优化性能的思路:
写入内存而不是写入硬盘
异步处理而不是同步处理
分布式处理
用上这三招,不论秒杀时负载多大,都能轻松应对。更好的是,Redis能够满足上述三点。因此,用Redis就能轻松实现秒杀系统。
用我这个方案,无论是电商平台特价秒杀,12306火车票秒杀,都不是事:)

下面介绍一下为什么上述三种性能优化思路能够解决秒杀系统的性能问题:

  • 写入内存而不是写入硬盘
    传统硬盘的读写性能是相当差的。SSD硬盘比传统硬盘快100倍。而内存又比SSD硬盘快10倍以上。因此,写入内存而不是写入硬盘,就能使系统的能力提升上千倍。也就是说,原来你的秒杀系统可能需要1000台服务器支撑,现在1台服务器就可以扛住了。
    你可能会有这样的疑问:写入内存而不是持久化,那么如果此时计算机宕机了,那么写入的数据不就全部丢失了吗?如果你就这么倒霉碰到服务器宕机,那你就没秒到了,有什么大不了?
    最后,后面真正处理秒杀订单时,我们会把信息持久化到硬盘中。因此不会丢失关键数据。
    Redis是一个缓存系统,数据写入内存后就返回给客户端了,能够支持这个特性。
  • 异步处理而不是同步处理
    像秒杀这样短时大并发的系统,在性能负载上有一个明显的波峰和长期的波谷。为了应对相当短时间的大并发而准备大量服务器来应对,在经济上是相当不合算的。
    因此,对付秒杀类需求,就应该化同步为异步。用户请求写入内存后立刻返回。后台启动多个线程从内存池中异步读取数据,进行处理。如用户请求可能是1秒钟内进入的,系统实际处理完成可能花30分钟。那么一台服务器在异步情况下其处理能力大于同步情况下1800多倍!
    异步处理,通常用MQ(消息队列)来实现。Redis可以看作是一个高性能的MQ。因为它的数据读写都发生在内存中。
  • 分布式处理
    好吧。也许你的客户很多,秒杀系统即使用了上面两招,还是捉襟见肘。没关系,我们还有大招:分布式处理。如果一台服务器撑不住秒杀系统,那么就多用几台服务器。10台不行,就上100台。分布式处理,就是把海量用户的请求分散到多个服务器上。一般使用hash实现均匀分布。
    这类系统在大数据云计算时代的今天已经有很多了。无非是用Paxos算法和Hash Ring实现的。
    Redis Cluster正是这样一个分布式的产品。

使用Redis实现描述系统

Redis和Redis Cluster(分布式版本),是一个分布式缓存系统。其支持多种数据结构,也支持MQ。Redis在性能上做了大量优化。因此使用Redis或者Redis Cluster就可以轻松实现一个强大的秒杀系统。
基本上,你用Redis的这些命令就可以了。
RPUSH key value
插入秒杀请求

当插入的秒杀请求数达到上限时,停止所有后续插入。
后台启动多个工作线程,使用
LPOP key
读取秒杀成功者的用户id,进行后续处理。
或者使用LRANGE key start end命令读取秒杀成功者的用户id,进行后续处理。
每完成一条秒杀记录的处理,就执行INCR key_num。一旦所有库存处理完毕,就结束该商品的本次秒杀,关闭工作线程,也不再接收秒杀请求。

要是还撑不住,该怎么办

也许你会说,我们的客户很多。即使部署了Redis Cluster,仍然撑不住。那该怎么办呢?
记得某个伟人曾经说过:办法总比困难多!

下面,我们具体分析下,还有哪些情况会压垮我们架构在Redis(Cluster)上的秒杀系统。

脚本攻击

如现在有很多抢火车票的软件。它们会自动发起http请求。一个客户端一秒会发起很多次请求。如果有很多用户使用了这样的软件,就可能会直接把我们的交换机给压垮了。

这个问题其实属于网络问题的范畴,和我们的秒杀系统不在一个层面上。因此不应该由我们来解决。很多交换机都有防止一个源IP发起过多请求的功能。开源软件也有不少能实现这点。如linux上的TC可以控制。流行的Web服务器Nginx(它也可以看做是一个七层软交换机)也可以通过配置做到这一点。一个IP,一秒钟我就允许你访问我2次,其他软件包直接给你丢了,你还能压垮我吗?

交换机撑不住了

可能你们的客户并发访问量实在太大了,交换机都撑不住了。
这也有办法。我们可以用多个交换机为我们的秒杀系统服务。
原理就是DNS可以对一个域名返回多个IP,并且对不同的源IP,同一个域名返回不同的IP。如网通用户访问,就返回一个网通机房的IP;电信用户访问,就返回一个电信机房的IP。也就是用CDN了!
我们可以部署多台交换机为不同的用户服务。 用户通过这些交换机访问后面数据中心的Redis Cluster进行秒杀作业。

总结

有了Redis Cluster的帮助,做个支持海量用户的秒杀系统其实So Easy!
这里介绍的方案虽然是针对秒杀系统的,但其背后的原理对其他高并发系统一样有效。
最后,我们再重温一下高性能系统的优化原则:
写入内存而不是写入硬盘
异步处理而不是同步处理
分布式处理

选redis还是memcache

memcache和redis是互联网分层架构中,最常用的KV缓存。不少同学在选型的时候会纠结,到底是选择memcache还是redis。

画外音:不鼓励粗暴的实践,例如“memcache提供的功能是redis提供的功能的子集,不用想太多,选redis准没错”。

虽然redis比memcache更晚出来,且功能确实也更丰富,但对于一个技术人,了解“所以然”恐怕比“选择谁”更重要一些

什么时候倾向于选择redis?

业务需求决定技术选型,当业务有这样一些特点的时候,选择redis会更加适合。

复杂数据结构

value是哈希,列表,集合,有序集合这类复杂的数据结构时,会选择redis,因为mc无法满足这些需求。

最典型的场景,用户订单列表,用户消息,帖子评论列表等。

持久化

mc无法满足持久化的需求,只得选择redis。

但是,这里要提醒的是,真的使用对了redis的持久化功能么?

千万不要把redis当作数据库用:

(1)redis的定期快照不能保证数据不丢失

(2)redis的AOF会降低效率,并且不能支持太大的数据量

不要期望redis做固化存储会比mysql做得好,不同的工具做各自擅长的事情,把redis当作数据库用,这样的设计八成是错误的。

缓存场景,开启固化功能,有什么利弊?

如果只是缓存场景,数据存放在数据库,缓存在redis,此时如果开启固化功能:

优点是,redis挂了再重启,内存里能够快速恢复热数据,不会瞬时将压力压到数据库上,没有一个cache预热的过程。

缺点是,在redis挂了的过程中,如果数据库中有数据的修改,可能导致redis重启后,数据库与redis的数据不一致。

因此,只读场景,或者允许一些不一致的业务场景,可以尝试开启redis的固化功能。

天然高可用

redis天然支持集群功能,可以实现主动复制,读写分离。

redis官方也提供了sentinel集群管理工具,能够实现主从服务监控,故障自动转移,这一切,对于客户端都是透明的,无需程序改动,也无需人工介入。

而memcache,要想要实现高可用,需要进行二次开发,例如客户端的双读双写,或者服务端的集群同步。

但是,这里要提醒的是,大部分业务场景,缓存真的需要高可用么?

(1)缓存场景,很多时候,是允许cache miss

(2)缓存挂了,很多时候可以通过DB读取数据

所以,需要认真剖析业务场景,高可用,是否真的是对缓存的主要需求?

画外音:即时通讯业务中,用户的在线状态,就有高可用需求。

存储的内容比较大

memcache的value存储,最大为1M,如果存储的value很大,只能使用redis。

什么时候倾向于memcache?

纯KV,数据量非常大,并发量非常大的业务,使用memcache或许更适合。

这要从mc与redis的底层实现机制差异说起。

内存分配

memcache使用预分配内存池的方式管理内存,能够省去内存分配时间。

redis则是临时申请空间,可能导致碎片。

从这一点上,mc会更快一些。

虚拟内存使用

memcache把所有的数据存储在物理内存里。

redis有自己的VM机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发swap,把冷数据刷到磁盘上。

从这一点上,数据量大时,mc会更快一些。

网络模型

memcache使用非阻塞IO复用模型,redis也是使用非阻塞IO复用模型。

但由于redis还提供一些非KV存储之外的排序,聚合功能,在执行这些功能时,复杂的CPU计算,会阻塞整个IO调度。

从这一点上,由于redis提供的功能较多,mc会更快一些。

线程模型

memcache使用多线程,主线程监听,worker子线程接受请求,执行读写,这个过程中,可能存在锁冲突。

redis使用单线程,虽无锁冲突,但难以利用多核的特性提升整体吞吐量。

从这一点上,mc会快一些。

最后说两点

代码可读性,代码质量

看过mc和redis的代码,从可读性上说,redis是我见过代码最清爽的软件,甚至没有之一,或许简单是redis设计的初衷,编译redis甚至不需要configure,不需要依赖第三方库,一个make就搞定了。

而memcache,可能是考虑了太多的扩展性,多系统的兼容性,代码不清爽,看起来费劲。

例如网络IO的部分,redis源码1-2个文件就搞定了,mc使用了libevent,一个fd传过来传过去,又pipe又线程传递的,特别容易把人绕晕。

画外音:理论上,mc只支持kv,而redis支持了这么多功能,mc性能应该高非常多非常多,但实际并非如此,真的可能和代码质量有关。

水平扩展的支持

不管是mc和redis,服务端集群没有天然支持水平扩展,需要在客户端进行分片,这其实对调用方并不友好。如果能服务端集群能够支持水平扩展,会更完美一些。