本文记录了在centos7下使用certbot配置开启https 的过程

安装

1
2
$ sudo yum install epel-release -y
$ sudo yum install certbot -y

安装成功之后,可以通过执行 certbot -h 来查看使用帮助.

申请证书有两种验证方式,一种是standalone,这种验证方式虽然也可以部署,但是以后更新证书的时候需要重启 web 服务器。

我们可以采用第二种webroot方式,就是在网站根目录下生成一个文件,通过访问该文件来验证,不需要重启 web 服务器。

配置

1
$ certbot certonly --webroot -w /home/wwwroot/yoursite/web -d www.lzuer.net -d lzuer.net

如果希望为多个域名生成共同的证书,可以继续在面输入:-w /home/wwwroot/othersite/web -d admin.lzuer.net

按照提示操作,当看到下面输出后,表示证书已成功生成:

1
2
3
4
5
6
7
8
9
10
11
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at
/etc/letsencrypt/live/your.domain.com/fullchain.pem. Your cert
will expire on 20XX-09-23. To obtain a new or tweaked version of
this certificate in the future, simply run certbot again. To
non-interactively renew *all* of your certificates, run "certbot
renew"
- If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

配置nginx

nginx支持在同一个server配置支持80和443端口,这样可以实现你的网站同时兼容httphttps两种模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
listen  80;
listen 443 ssl;
server_name lzuer.net www.lzuer.net;
root /home/wwwroot/yoursite/web;
index index.php;
charset utf-8;


ssl on;
ssl_certificate /etc/letsencrypt/live/b.w.lzuer.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/b.w.lzuer.net/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/b.w.lzuer.net/chain.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
//...

保存后重启nginx.

注意, 启用https需要在安装nginx的时候开启ssl module,可以通过 nginx -V 来查看,如果没有开启需要重新编译并开启

测试下,访问https开头的网址是否能正常访问,如果能正常访问,表示配置已经生效。

注意, 如果发现无法访问, 可以检查下443这个端口是否开启,如果没有则需要开启 iptables -I INPUT -p tcp --dport 443 -j ACCEPT

如果希望你的网站仅支持https访问,那么可以如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server {
listen 80;
server_name b.w.lzuer.net;
return 301 https://b.w.lzuer.net$request_uri;
}
server {
listen 443 ssl;
server_name b.w.lzuer.net;
root /home/wwwroot/yoursite/web;
index index.php;
charset utf-8;


ssl on;
ssl_certificate /etc/letsencrypt/live/b.w.lzuer.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/b.w.lzuer.net/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/b.w.lzuer.net/chain.pem;
ssl_session_timeout 5m;
ssl_protocols TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
}

定时更新

Let’s Encrypt 只有3个月的有效期,所以我们需要定时去更新证书。

可以通过运行:certbot renew --dry-run 来测试自动生成是否能够正常运行。

如果通过,可以正常运行,可以加入cron来定时执行:

1
0 0 1 * * certbot renew --quiet

https://certbot.eff.org 上建议是每天不定时执行两次,来保证网站能够正常访问。

具体由于证书在没有过期之前是不会进行更新的,所以每天不定时更新两次亦可以。

参考:

Github 开源项目后, 总会有人为你添砖加瓦。当有用户为你的代码提交一个patch后,

你不仅需要在线进行code review,而且还要运行起来,确保提交的代码改动不会产生其他影响。

具体的操作步骤为:

  1. 检出一个单独的分支来查看代码更改
1
$ git checkout -b pull-100
  1. 拉取提交用户改动分支代码
1
2

$ git pull git://github.com/somebody/project.git master

注意, 上面是在用户提交代码分支为 master 的情况, 如果用户提交的改动分支非 master

那么需要改成对应的分支名

  1. 在本地查看代码运行情况, 是否解决了问题

最后, 对于合并可以在本地进行:

1
2
3
$ git checkout master
$ git merge --no-ff pull-100
$ git push origin master

当然, Github 在线处理已经做得足够好, 也可以选择在线进行合并.

开启binlog

/etc/my.conf 中打开 log-bin=/your/dir, 重启Mysql服务

根据不同的安装, MySQL的配置文件的位置可能不同, 视具体情况而定.

配置哪些数据库需要记录binlog:

1
2
3
4
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
binlog-ignore-db=performance_schema
binlog-do-db=kit
  • binlog-ignore-db配置哪些数据不需要记录binlog
  • binlog-do-db 配置哪些数据需要记录binlog

查看binlog状态

查看binlog的名称

1
2
3
4
5
6
7
mysql> show binary logs;
+---------------+-----------+
| Log_name | File_size |
+---------------+-----------+
| binlog.000001 | 616 |
+---------------+-----------+
1 row in set (0.00 sec)

查看二进制日志的记录

1
2
3
4
5
6
7
8
9
10
11
12
13
mysql> show binlog events;
+---------------+-----+-------------+-----------+-------------+-----------------------------------------------------------------------------------------------------+
| Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
+---------------+-----+-------------+-----------+-------------+-----------------------------------------------------------------------------------------------------+
| binlog.000001 | 4 | Format_desc | 1 | 120 | Server ver: 5.6.30-log, Binlog ver: 4 |
| binlog.000001 | 120 | Query | 1 | 197 | BEGIN |
| binlog.000001 | 197 | Query | 1 | 317 | use `kit`; UPDATE `user` SET `username`='xxxx' WHERE `id`=5 |
| binlog.000001 | 317 | Xid | 1 | 348 | COMMIT /* xid=72 */ |
| binlog.000001 | 348 | Query | 1 | 425 | BEGIN |
| binlog.000001 | 425 | Query | 1 | 585 | use `kit`; INSERT INTO `user_profile` (`user_id`, `sex`, `last_login_ip`) VALUES (5, 1, 3232238081) |
| binlog.000001 | 585 | Xid | 1 | 616 | COMMIT /* xid=77 */ |
+---------------+-----+-------------+-----------+-------------+-----------------------------------------------------------------------------------------------------+
7 rows in set (0.01 sec)

导出二进制文件

1
$ mysqlbinlog --start-position=4 --stop-position=585 binlog.000001 > /home/out.txt

或者全部导出:

1
$ mysqlbinlog binlog.000001 > /home/out.txt

导出的文件名可以通过上面的show binary logs; 来查看需要导出的binlog文件

position 可以根据 show binlog events中的Pos字段来确定.

配置主从

查看主库状态

1
2
3
4
5
6
7
mysql> show master status;
+---------------+----------+--------------+---------------------------------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+---------------+----------+--------------+---------------------------------------------+-------------------+
| binlog.000003 | 120 | kit | mysql,information_schema,performance_schema | |
+---------------+----------+--------------+---------------------------------------------+-------------------+
1 row in set (0.00 sec)

主从配置

配置主库

修改Master服务器MySQL配置文件:

1
2
log-bin=binlog //必须开启binlog
server-id=1 // 必须, 服务器唯一ID, 默认是1,一般取IP最后一段

在Master服务器上建立帐户并授权slave:

1
mysql > grant replication slave on *.* to slave01@192.168.10.12 identified by '123qwe';

重启MySQL.然后查看主库状态.

配置从库

修改Slave服务器MySQL配置文件:

1
2
log-bin=binlog //不是必须
server-id=2 //必须, 需要唯一,一般取IP最后一段

重启MySQL.

配置Slave:

1
2
mysql> change master to master_host='192.168.10.11',master_user='slave01',master_password='123qwe';
mysql> start slave;
  • master_host 配置为Master服务器的IP地址
  • master_usermaster_password 分别为在Master服务器上进行授权的账号和密码

查看从库状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql>show slave status;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.10.11
Master_User: slave01
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: binlog.000006
Read_Master_Log_Pos: 1155
Relay_Log_File: mysqld-relay-bin.000009
Relay_Log_Pos: 1315
Relay_Master_Log_File: binlog.000006
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
...

之前需要将主库的数据导到从库.

遇到的问题

在从库执行 show slave staus 后发现:

1
Last_IO_Error: Fatal error: The slave I/O thread stops because master and slave have equal MySQL server UUIDs;  these UUIDs must be different for replication to work.

在网上找到的原因和解决方案是:

这个错误是由于主从使用了相同的UUID「在主从的server_id确保是各不相同的情况下」.

首先分别在主从机器上查看:

1
2
3
[root@master ~]# cat /var/lib/mysql/auto.cnf
[auto]
server-uuid=3636a3ee-21b8-11e6-aebc-080027e27e5e
1
2
3
[root@slave ~]# cat /var/lib/mysql/auto.cnf
[auto]
server-uuid=3636a3ee-21b8-11e6-aebc-080027e27e5e

发现是相同的,可以通过以下方法解决:

在从库机器上:

1
2
[root@slave ~]# mv /var/lib/mysql/auto.cnf /var/lib/mysql/auto.cnf.back
[root@slave ~]# service mysqld restart

通过重启MySQL来重新生成uuid. 最后问题解决.

另外,我们需要在从库上配置授权访问,保证其他服务器可以访问:

1
mysql> grant all on *.* to vagrant@'%' identified by 'vagrant' with grant option;

%标示任何IP地址都可以进行访问, 当然也可以指定服务器IP, 保证自己的服务器能够访问, 而其他IP不能访问。

起因

一天一位vux使用提交了这么一个bug: x-textarea字数统计异常.

具体就是在当在textarea中输入回车之后,就会导致字数统计异常, 而对textarea设置了maxlength属性, 这样就会导致如果在textarea中输入过回车,就会导致统计字数还未达上限,但是已经不能输入了.

首先我们来看一下字数统计功能是怎么做的:

1
2
3
count() {
return this.value.length
}

代码中使用了字符串 length 来做字数统计, 但是为什么当有回车时, 这样会导致数字统计异常呢? 是不是只有在textarea中的表现是这样的呢?

验证问题

首先, 我验证了在input中的表现, 结果是统计表现正常.

发现问题原因

为什么会这样? 在遨游了「全世界」之后找到了答案:

http://www.bennadel.com/blog/161-ask-ben-javascript-replace-and-multiple-lines-line-breaks.htm

Textarea values might have the character combos “\n\r” in the text box, but once they are pulled into Javascript, “\n\r” becomes JUST “\n”.

解决问题

导致问题的原因已经找出来了,那么接下来就是解决问题.

上面我们知道, 在输入一个回车后都会导致少统计一个数字, 那么我们可以在进行统计的时候做下处理:

1
2
3
4
count () {
let len = this.value.replace('\n', 'aa').length
return len > this.max ? this.max : len
}

是不是很tricky? 但是却是能解决问题的!

在实际的开发过程中,经常会用到守护进程来执行相应的代码。例如起一个消费beanstalkd的进程:

1
$nohup php beanstalkd-consumer.php &

使用这种方式,我们会发现这个进程会不明不白的「死掉」,导致消费工作无法正常进行。另外,如果一个项目中会涉及到多个守护进程来做相关工作的话,
这会导致样管理起来十分的麻烦,而且常常会担心它们会自己偷偷的「死掉」。

supervisor就是这样一个工具,可以让我们很轻松愉快的对它们进行统一管理,而且省去了我们担心的某个任务会因为特殊的原因自己偷偷的挂掉。

安装

这里我直接使用 yum 进行安装:

1
$ yum install -y supervisor

安装成功后,会有两个可执行程序:

  • supervisord – supervisor 服务守护进程
  • supervisorctl – supervisor 服务控制程序.例如: status/start/stop/restart program

supervisord启动时可以通过 -c /some/path/supervisor.conf 来指定加载的配置文件。当然如果没有指定配置文件的位置 supervisord 会按照内部的寻找方式来加载配置文件

因为我是用yum来安装的,所以在 /etc/supervisor.conf 已经存在了默认的配置文件,supervisord 会默认加载。

配置

最重要的就是对supervisor的行为方式进行配置,通过修改/etc/supervisord.conf来管理.

例如,我们加入上面提到的消费进程:

1
2
[program:beanstalkd_comsumer]
command=/usr/local/php/bin/php /path/to/beanstalkd-consumer.php //此项指定要执行的命令

默认的,supervisor就自动管理进程挂掉自动重启等,我们来看下比较全的配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
;[program:theprogramname]
;command=/bin/cat ; the program (relative uses PATH, can take args)
;process_name=%(program_name)s ; process_name expr (default %(program_name)s)
;numprocs=1 ; number of processes copies to start (def 1)
;directory=/tmp ; directory to cwd to before exec (def no cwd)
;umask=022 ; umask for process (default None)
;priority=999 ; the relative start priority (default 999)
;autostart=true ; start at supervisord start (default: true)
;autorestart=true ; retstart at unexpected quit (default: true)
;startsecs=10 ; number of secs prog must stay running (def. 1)
;startretries=3 ; max # of serial start failures (default 3)
;exitcodes=0,2 ; 'expected' exit codes for process (default 0,2)
;stopsignal=QUIT ; signal used to kill process (default TERM)
;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10)
;user=chrism ; setuid to this UNIX account to run the program
;redirect_stderr=true ; redirect proc stderr to stdout (default false)
;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO
;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stdout_logfile_backups=10 ; # of stdout logfile backups (default 10)
;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stdout_events_enabled=false ; emit events on stdout writes (default false)
;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO
;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB)
;stderr_logfile_backups=10 ; # of stderr logfile backups (default 10)
;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0)
;stderr_events_enabled=false ; emit events on stderr writes (default false)
;environment=A=1,B=2 ; process environment additions (def no adds)
;serverurl=AUTO ; override serverurl computation (childutils)

启动

启动supervisord来启动和管理所有的进程:

1
$ /usr/bin/supervisord

启动之后我们可以通过supervisor的另外一个工具来查看所有配置的进程状态:

1
$supervisorctl status
1
beanstalkd_comsumer           RUNNING   pid 26995, uptime 4:09:54

  1. 下载源代码到本地
1
$ wget https://github.com/kr/beanstalkd/archive/v1.10.tar.gz
  1. 解压安装
1
2
3
4
$ tar zxvf v1.10.tar.gz && rm -rf v1.10.tar.gz
$ mv beanstalkd-1.10/ /usr/local/beanstalkd
$ cd /usr/local/beanstalkd
$ make && make install
  1. 加入服务以及开机启动
1
2
3
$ cp /usr/local/beanstalkd/adm/systemd/beanstalkd.service /usr/lib/systemd/system
$ systemctl enable beanstalkd.service
$ systemctl start beanstalkd.service
  1. 手动启动并开启binlog
1
2
$ mkdir -p /data/beanstalkd
$ /usr/bin/beanstalkd -b /data/beanstalkd -u root &

参考资料:

在PHP中,范围解析操作符可以用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法。

这是手册中,对于范围解析符的概论。

在日常工作中,我们经常会用在例如:

  1. 调用类常量: SomeCls::THE_CONST,和public的静态变量 SomeCls::$VAR
  2. 调用静态方法: SomeCls::staticMethod()

当然,在于类的内部,我们可以使用 selfparentstatic 关键字来进行访问。

这里,我要强调的是一种比较常见但是通常我们会忽略的一种用法。

我们经常会看到这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Parent
{
public function __construct(){}
}

class Child extends Parent
{
public function __construct()
{
parent::__construct();
//some other logic
}
}

我们都知道,在 Child 的构造方法中 parent::__construct() 是主动调用父类的构造方法。

但是,为什么会需要我们主动去调用呢?

当一个子类覆盖父类的方法时,PHP不会调用父类已被覆盖的方法。是否调用父类的方法取决于子类。
这种机制也作用于构造函数和析构函数,重载以及魔术方法

所以,当见到诸如 parent::__constructparent::__get … 等调用时我们就知道其中的原由了。

为什么会写这篇文章?因为有一天, 我因为看到下面的代码而蒙逼了…当我知道上面的原理后才明白过来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BasePage
{
protected function renderValue($key, $value)
{
//...
}
}

class Page extends BasePage
{
public function renderValue($key, $value)
{
$key = $key + $value;
parent::renderValue($key, $value);
}
}

所以文档熟烂于心才是硬道理!

参考资料:

单例模式和多例模式都禁止外界将其实例化,通过静态工厂方法向外界提供循环使用的自身实例。

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Singleton
{
/**
* @var static
*/
private static $instance;

public static function getInstance()
{
if (null === static::$instance) {
static::$instance = new static();
}

return static::$instance;
}

//单例模式不允许被new
private function __construct(){}

//不允许被clone
final public function __clone()
{
throw new \Exception('This is singleton, Clone is forbidden.');
}

//不允许被unserialize
final public function __wakeup()
{
throw new \Exception('This is singleton, __wakeup is forbidden.');
}
}

多例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Multiton
{
const INSTANCE_1 = '1';

const INSTANCE_2 = '2';

private static $instances = [];

private function __construct(){}

private function __clone(){}

private function __wakeup(){}

public static function getInstance($instanceName)
{
if (!array_key_exists($instanceName, self::$instances)) {
self::$instances[$instanceName] = new self();
}

return self::$instances[$instanceName];
}
}

总结

单例模式的主要优点:

* 提供了对唯一实例的受控访问
* 由于在系统中只存在一个对象,因此可以节约资源,对于一些需要频繁创建和销毁的对象可以提高系统性能
* 允许可变数目的实例

主要缺点:

* 单例模式没有抽象层,因此扩展很麻烦
* 职责过重,一定程度上违背了“单一职责原则”
* 滥用会带来一些负面问题

首先,两种方法都能够实现两个或者多个数组之间的合并。但是,两个方法还是有差别的。

+号操作符返回的是右边的数组附加到左边的结果数组。如果存在一个键值都存在两个数组中,那么左边的将会使用,不会被右边的数据给覆盖。

举个栗子:

1
2
3
4
5
6
7
$a = ['a' => 'apple', 'b' => 'banana'];

$b = ['a' => 'orange', 'c' => 'cherry'];

$c = $a + $b;

var_dump($c);

将会输出:

1
2
3
4
5
6
7
8
array(3) {
["a"]=>
string(5) "apple"
["b"]=>
string(6) "banana"
["c"]=>
string(6) "cherry"
}

那么如果存在数字键值,又会有是什么表现方式呢?稍微改动下上一个栗子:

1
2
3
4
5
6
7
$a = ['a' => 'apple', 'b' => 'banana', 1];

$b = ['a' => 'orange', 'c' => 'cherry', 2];

$c = $a + $b;

var_dump($c);

将会输出:

1
2
3
4
5
6
7
8
9
10
array(4) {
["a"]=>
string(5) "apple"
["b"]=>
string(6) "banana"
[0]=>
int(1)
["c"]=>
string(6) "cherry"
}

可以看到和字符串表现一致。

再来测试下上面的代码, array_merge 的表现是怎样的:

1
2
3
4
5
6
7
$a = ['a' => 'apple', 'b' => 'banana'];

$b = ['a' => 'orange', 'c' => 'cherry'];

$c = array_merge($a, $b);

var_dump($c);

输出结果为:

1
2
3
4
5
6
7
8
array(3) {
["a"]=>
string(6) "orange"
["b"]=>
string(6) "banana"
["c"]=>
string(6) "cherry"
}

那么正如文档中所说的,将会对相同的键值进行覆盖!那么对于数字键值是不是也是同样的处理呢?再看:

1
2
3
4
5
6
7
$a = ['a' => 'apple', 'b' => 'banana', 1];

$b = ['a' => 'orange', 'c' => 'cherry', 2];

$c = $a + $b;

var_dump($c);

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
array(5) {
["a"]=>
string(6) "orange"
["b"]=>
string(6) "banana"
[0]=>
int(1)
["c"]=>
string(6) "cherry"
[1]=>
int(2)
}

可以看到,并没有进行覆盖!由此可以得出结论,当两个数组中含有数值健的时候,会进行附加,并且键值会从0开始重建索引键值。

参看资料:

  1. Redis中,并不是所有的数据都一直存在内存中,这是和memcache的最大区别
  2. Redis不仅仅支持简单的k/v类型的数据,同时还提供list, set, hash等数据结构
  3. Redis支持数据备份,即master-slave模式的数据备份
  4. Redis支持数据的持久化,可以将内存中的数据保存到磁盘上,服务器重启的时候可以再次加载。

Redis作者对Redis和memcache进行的比对:

没有必要过多的关心性能,因为二者的性能都已经足够高了。由于Redis使用单核,而memcache可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比memcache性能更高。而在100k以上的数据中,memcache性能高于Redis,最终,无论使用哪一个,每秒处理请求的次数都不会成为瓶颈。(比如瓶颈可能会在网卡)

如果要说内存使用效率,使用简单的key-value存储的话,memcache的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于memcache。

如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。

当然,最后还得说到你的具体应用需求。Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里, 你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的 GET/SET一样高效。所以,如果你需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。

1、 Redis和Memcache都是将数据存放在内存中,都是内存数据库。不过memcache还可用于缓存其他东西,例如图片、视频等等。
2、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。
3、虚拟内存–Redis当物理内存用完时,可以将一些很久没用到的value 交换到磁盘
4、过期策略–memcache在set时就指定,例如set key1 0 0 8,即永不过期。Redis可以通过例如expire 设定,例如expire name 10
5、分布式–设定memcache集群,利用magent做一主多从;redis可以做一主多从。都可以一主一从
6、存储数据安全–memcache挂掉后,数据没了;redis可以定期保存到磁盘(持久化)
7、灾难恢复–memcache挂掉后,数据不可恢复; redis数据丢失后可以通过aof恢复
8、Redis支持数据的备份,即master-slave模式的数据备份

参考: