精华内容
下载资源
问答
  • 一、oracle 高水位线详解 一、什么是水线(High Water Mark)? 所有的oracle段(segments,在此,为了理解方便,建议把segment作为表的一个同义词) 都有一个在段内容纳数据的上限,我们把这个上限称为"high water ...

    一、oracle 高水位线详解
    一、什么是水线(High Water Mark)?
    所有的oracle段(segments,在此,为了理解方便,建议把segment作为表的一个同义词) 都有一个在段内容纳数据的上限,我们把这个上限称为"high water mark"或HWM。这个HWM是一个标记,用来说明已经有多少没有使用的数据块分配给这个segment。HWM通常增长的幅度为一次5个数据块,原则上HWM只会增大,不会缩小,即使将表中的数据全部删除,HWM还是为原值,由于这个特点,使HWM很象一个水库的历史最高水位,这也就是HWM的原始含义,当然不能说一个水库没水了,就说该水库的历史最高水位为0。但是如果我们在表上使用了truncate命令,则该表的HWM会被重新置为0。
               
    二、HWM数据库的操作有如下影响:
    a) 全表扫描通常要读出直到HWM标记的所有的属于该表数据库块,即使该表中没有任何数据。
    b) 即使HWM以下有空闲的数据库块,键入在插入数据时使用了append关键字,则在插入时使用HWM以上的数据块,此时HWM会自动增大。
                   
    三、如何知道一个表的HWM?
    a) 首先对表进行分析:
    ANALYZE TABLE <tablename> ESTIMATE/COMPUTE STATISTICS;
    b) SELECT blocks, empty_blocks, num_rows
    FROM user_tables
    WHERE table_name = <tablename>;
    说明:
    BLOCKS 列代表该表中曾经使用过得数据库块的数目,即水线。
    EMPTY_BLOCKS 代表分配给该表,但是在水线以上的数据库块,即从来没有使用的数据块。
                
    让我们以一个有28672行的BIG_EMP1表为例进行说明:
    1) SQL> SELECT segment_name, segment_type, blocks
    FROM dba_segments
    WHERE segment_name='BIG_EMP1';
    SEGMENT_NAME       SEGMENT_TYPE    BLOCKS     
    -----------------  --------------  --------- 
    BIG_EMP1           TABLE           1024       
    1 row selected.             
                    
    2) SQL> ANALYZE TABLE big_emp1 ESTIMATE STATISTICS;
    Statement processed.
               
    3) SQL> SELECT table_name,num_rows,blocks,empty_blocks
    FROM user_tables
    WHERE table_name='BIG_EMP1';
    TABLE_NAME  NUM_ROWS  BLOCKS     EMPTY_BLOCKS
    ----------  --------  -------    -------------
    BIG_EMP1    28672     700        323
    1 row selected.
    注意:
    BLOCKS + EMPTY_BLOCKS (700+323=1023)比DBA_SEGMENTS.BLOCKS少1个数据库块,这是因为有一个数据库块被保留用作segment header。DBA_SEGMENTS.BLOCKS 表示分配给这个表的所有的数据库块的数目。USER_TABLES.BLOCKS表示已经使用过的数据库块的数目。
                 
    4) SQL> SELECT COUNT (DISTINCT
    DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)||
    DBMS_ROWID.ROWID_RELATIVE_FNO(rowid)) "Used"
    FROM big_emp1;
    Used
    ----------
    700
    1 row selected.
                  
    5) SQL> delete from big_emp1;
    28672 rows processed.
              
    6) SQL> commit;
    Statement processed.
     
    7) SQL> ANALYZE TABLE big_emp1 ESTIMATE STATISTICS;
    Statement processed.
     
    8) SQL> SELECT table_name,num_rows,blocks,empty_blocks
    FROM user_tables
    WHERE table_name='BIG_EMP1';
    TABLE_NAME  NUM_ROWS   BLOCKS   EMPTY_BLOCKS
    ---------   --------   -------  ----------
    BIG_EMP1    0          700      323
    1 row selected.
     
    9) SQL> SELECT COUNT (DISTINCT
    DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)||
    DBMS_ROWID.ROWID_RELATIVE_FNO(rowid)) "Used"
    FROM big_emp1;
    Used
    ----------
    0 ----这表名没有任何数据库块容纳数据,即表中无数据
    1 row selected.
                            
    10) SQL> TRUNCATE TABLE big_emp1;
    Statement processed.
                       
    11) SQL> ANALYZE TABLE big_emp1 ESTIMATE STATISTICS;
    Statement processed.
                       
    12) SQL> SELECT table_name,num_rows,blocks,empty_blocks
    2> FROM user_tables
    3> WHERE table_name='BIG_EMP1';
    TABLE_NAME  NUM_ROWS  BLOCKS    EMPTY_BLOCKS
    ----------  --------  --------  ------------
    BIG_EMP1    0         0         511
    1 row selected.
                                   
    13) SQL> SELECT segment_name,segment_type,blocks
    FROM dba_segments
    WHERE segment_name='BIG_EMP1';
    SEGMENT_NAME  SEGMENT_TYPE  BLOCKS
    ------------  ------------- ------
    BIG_EMP1      TABLE         512
    1 row selected.
    注意:
    TRUNCATE命令回收了由delete命令产生的空闲空间,注意该表分配的空间由原先的1024块降为512块。
    为了保留由delete命令产生的空闲空间,可以使用TRUNCATE TABLE big_emp1 REUSE STORAGE.
    用此命令后,该表还会是原先的1024块。
                     
    四、Oracle表段中的高水位线HWM
    在Oracle数据的存储中,可以把存储空间想象为一个水库,数据想象为水库中的水。水库中的水的位置有一条线叫做水位线,在Oracle中,这条线被称为高水位线(High-warter mark, HWM)。在数据库表刚建立的时候,由于没有任何数据,所以这个时候水位线是空的,也就是说HWM为最低值。当插入了数据以后,高水位线就会上涨,但是这里也有一个特性,就是如果你采用delete语句删除数据的话,数据虽然被删除了,但是高水位线却没有降低,还是你刚才删除数据以前那么高的水位。也就是说,这条高水位线在日常的增删操作中只会上涨,不会下跌。
                  
    下面我们来谈一下Oracle中Select语句的特性。Select语句会对表中的数据进行一次扫描,但是究竟扫描多少数据存储块呢,这个并不是说数据库中有多少数据,Oracle就扫描这么大的数据块,而是Oracle会扫描高水位线以下的数据块。现在来想象一下,如果刚才是一张刚刚建立的空表,你进行了一次Select操作,那么由于高水位线HWM在最低的0位置上,所以没有数据块需要被扫描,扫描时间会极短。而如果这个时候你首先插入了一千万条数据,然后再用delete语句删除这一千万条数据。由于插入了一千万条数据,所以这个时候的高水位线就在一千万条数据这里。后来删除这一千万条数据的时候,由于delete语句不影响高水位线,所以高水位线依然在一千万条数据这里。这个时候再一次用select语句进行扫描,虽然这个时候表中没有数据,但是由于扫描是按照高水位线来的,所以需要把一千万条数据的存储空间都要扫描一次,也就是说这次扫描所需要的时间和扫描一千万条数据所需要的时间是一样多的。所以有时候有人总是经常说,怎么我的表中没有几条数据,但是还是这么慢呢,这个时候其实奥秘就是这里的高水位线了。
                    
    那有没有办法让高水位线下降呢,其实有一种比较简单的方法,那就是采用TRUNCATE语句进行删除数据。采用TRUNCATE语句删除一个表的数据的时候,类似于重新建立了表,不仅把数据都删除了,还把HWM给清空恢复为0。所以如果需要把表清空,在有可能利用TRUNCATE语句来删除数据的时候就利用TRUNCATE语句来删除表,特别是那种数据量有可能很大的临时存储表。
                 
    在手动段空间管理(Manual Segment Space Management)中,段中只有一个HWM,但是在Oracle9iRelease1才添加的自动段空间管理(Automatic Segment Space Management)中,又有了一个低HWM的概念出来。为什么有了HWM还又有一个低HWM呢,这个是因为自动段空间管理的特性造成的。在手段段空间管理中,当数据插入以后,如果是插入到新的数据块中,数据块就会被自动格式化等待数据访问。而在自动段空间管理中,数据插入到新的数据块以后,数据块并没有被格式化,而是在第一次在第一次访问这个数据块的时候才格式化这个块。所以我们又需要一条水位线,用来标示已经被格式化的块。这条水位线就叫做低HWM。一般来说,低HWM肯定是低于等于HWM的。
                      
    五、修正ORACLE表的高水位线
    在ORACLE中,执行对表的删除操作不会降低该表的高水位线。而全表扫描将始终读取一个段(extent)中所有低于高水位线标记的块。如果在执行删除操作后不降低高水位线标记,则将导致查询语句的性能低下。下面的方法都可以降低高水位线标记。
    1.执行表重建指令 alter table table_name move;
    (在线转移表空间ALTER TABLE 。。。 MOVE TABLESPACE 。。。ALTER TABLE 。。。 MOVE 后面不跟参数也行,不跟参数表还是在原来的表空间,move后记住重建索引。如果以后还要继续向这个表增加数据,没有必要move,只是释放出来的空间,只能这个表用,其他的表或者segment无法使用该空间)
    2.执行alter table table_name shrink space; 注意,此命令为Oracle 10g新增功能,再执行该指令之前必须允许行移动alter table table_name enable row movement;再执行该指令之后必须修改不允许行移动,复位到原来状态alter table table_name disable row movement;
    3.复制要保留的数据到临时表t,drop原表,然后rename临时表t为原表
    4.emp/imp
    5.alter   table  table_name  deallocate   unused  
    6.尽量truncate吧

    展开全文
  • 一、Oracle表段中的高水位线HWM ...在数据库表刚建立的时候,由于没有任何数据,所以这个时候水位线是空的,也就是说HWM最低值。当插入了数据以后,高水位线就会上涨,但是这里也有一个特性,就是如

    一、Oracle表段中的高水位线HWM
    在Oracle数据的存储中,可以把存储空间想象为一个水库,数据想象为水库中的水。水库中的水的位置有一条线叫做水位线,在Oracle中,这条线被称为高水位线(High-warter mark, HWM)。在数据库表刚建立的时候,由于没有任何数据,所以这个时候水位线是空的,也就是说HWM为最低值。当插入了数据以后,高水位线就会上涨,但是这里也有一个特性,就是如果你采用delete语句删除数据的话,数据虽然被删除了,但是高水位线却没有降低,还是你刚才删除数据以前那么高的水位。也就是说,这条高水位线在日常的增删操作中只会上涨,不会下跌。
                  
    下面我们来谈一下Oracle中Select语句的特性。Select语句会对表中的数据进行一次扫描,但是究竟扫描多少数据存储块呢,这个并不是说数据库中有多少数据,Oracle就扫描这么大的数据块,而是Oracle会扫描高水位线以下的数据块。现在来想象一下,如果刚才是一张刚刚建立的空表,你进行了一次Select操作,那么由于高水位线HWM在最低的0位置上,所以没有数据块需要被扫描,扫描时间会极短。而如果这个时候你首先插入了一千万条数据,然后再用delete语句删除这一千万条数据。由于插入了一千万条数据,所以这个时候的高水位线就在一千万条数据这里。后来删除这一千万条数据的时候,由于delete语句不影响高水位线,所以高水位线依然在一千万条数据这里。这个时候再一次用select语句进行扫描,虽然这个时候表中没有数据,但是由于扫描是按照高水位线来的,所以需要把一千万条数据的存储空间都要扫描一次,也就是说这次扫描所需要的时间和扫描一千万条数据所需要的时间是一样多的。所以有时候有人总是经常说,怎么我的表中没有几条数据,但是还是这么慢呢,这个时候其实奥秘就是这里的高水位线了。
                    
    那有没有办法让高水位线下降呢,其实有一种比较简单的方法,那就是采用TRUNCATE语句进行删除数据。采用TRUNCATE语句删除一个表的数据的时候,类似于重新建立了表,不仅把数据都删除了,还把HWM给清空恢复为0。所以如果需要把表清空,在有可能利用TRUNCATE语句来删除数据的时候就利用TRUNCATE语句来删除表,特别是那种数据量有可能很大的临时存储表。
                 
    在手动段空间管理(Manual Segment Space Management)中,段中只有一个HWM,但是在Oracle9iRelease1才添加的自动段空间管理(Automatic Segment Space Management)中,又有了一个低HWM的概念出来。为什么有了HWM还又有一个低HWM呢,这个是因为自动段空间管理的特性造成的。在手段段空间管理中,当数据插入以后,如果是插入到新的数据块中,数据块就会被自动格式化等待数据访问。而在自动段空间管理中,数据插入到新的数据块以后,数据块并没有被格式化,而是在第一次在第一次访问这个数据块的时候才格式化这个块。所以我们又需要一条水位线,用来标示已经被格式化的块。这条水位线就叫做低HWM。一般来说,低HWM肯定是低于等于HWM的。
                     
    二、修正ORACLE表的高水位线
    在ORACLE中,执行对表的删除操作不会降低该表的高水位线。而全表扫描将始终读取一个段(extent)中所有低于高水位线标记的块。如果在执行删除操作后不降低高水位线标记,则将导致查询语句的性能低下。下面的方法都可以降低高水位线标记。
    1.执行表重建指令 alter table table_name move;
    (在线转移表空间ALTER TABLE 。。。 MOVE TABLESPACE 。。。ALTER TABLE 。。。 MOVE 后面不跟参数也行,不跟参数表还是在原来的表空间,move后记住重建索引。如果以后还要继续向这个表增加数据,没有必要move,只是释放出来的空间,只能这个表用,其他的表或者segment无法使用该空间)
    执行alter table table_name shrink space; 注意,此命令为Oracle 10g新增功能,再执行该指令之前必须允许行移动alter table table_name enable row movement;
     实质上构造一个新表(在内部表现为一系列的DML操作,即将副本插入新位置,删除原来位置的记录)靠近末尾处(右端)数据块中的记录往开始处(左端)的空闲空间处移动(DML操作),不会引起DML触发器
    当所有可能的移动被完成,高水位线将会往左端移动(DDL操作)
    新的高水位线右边的空闲空间被释放(DDL操作)
           
    实现前提条件
    必须启用行记录转移(enable row movement)
    仅仅适用于堆表,且位于自动段空间管理的表空间(堆表包括:标准表,分区表,物化视图容器,物化视图日志表)

    3.复制要保留的数据到临时表t,drop原表,然后rename临时表t为原表
    4.emp/imp
    5.alter   table  table_name  deallocate   unused 
    6.尽量truncate吧


     

    -----------高水位线实验---------------------------------------
    create table HWM as select * from dba_objects;
    
    SQL> SELECT segment_name, segment_type, blocks FROM dba_segments
         WHERE segment_name='HWM';    
         DBA_SEGMENTS.BLOCKS 表示分配给这个表的所有的数据库块的数目
    
    SQL> ANALYZE TABLE hwm ESTIMATE STATISTICS;          
    
    SQL> SELECT table_name,num_rows,blocks,empty_blocks
         FROM user_tables
         WHERE table_name='HWM';
         USER_TABLES.BLOCKS表示已经使用过的数据库块的数目,即水线。
         USER_TABLES.EMPTY_BLOCKS 代表分配给该表,但是在水线以上的数据库块,即从来没有使用的数据块。
    
    SQL> SELECT COUNT (DISTINCT
         DBMS_ROWID.ROWID_BLOCK_NUMBER(rowid)||
         DBMS_ROWID.ROWID_RELATIVE_FNO(rowid)) "Used"
         FROM hwm;
         有多少块容纳数据
    
    SQL> delete from hwm;         
    SQL> commit;
    SQL> ANALYZE TABLE hwm ESTIMATE STATISTICS; 
    SQL> SELECT table_name,num_rows,blocks,empty_blocks
         FROM user_tables
         WHERE table_name='HWM';
    
    SQL> TRUNCATE TABLE hwm;  
    SQL> ANALYZE TABLE hwm ESTIMATE STATISTICS;                  
    SQL> SELECT table_name,num_rows,blocks,empty_blocks
         FROM user_tables
         WHERE table_name='HWM';
    
    



    结论:使用delete不能使高水位线降低,使用truncate可以使用高水位线降低,
          所以当删除整表数据或者分区最好是用truncate,
          当用delete删除大表数据时后,还是要重建表,这样可以降低高水位线

    展开全文
  • MySQL 8 复制(一)——异步复制

    千次阅读 多人点赞 2019-05-10 18:25:10
    一、MySQL异步复制介绍 1. 复制的用途 2. 复制如何工作 3. 两阶段提交 二、复制实验环境 三、安装mysql-8.0.16 四、配置异步复制 1. 空库 2. 脱机 3. 联机 一、MySQL异步复制介绍 简单说,复制就是将...

    目录

    一、MySQL异步复制介绍

    1. 复制的用途

    2. 复制如何工作

    3. 两阶段提交

    二、复制实验环境

    三、安装mysql-8.0.16

    四、配置异步复制

    1. 空库

    2. 脱机

    3. 联机


    一、MySQL异步复制介绍

            简单说,复制就是将来自一个MySQL数据库服务器(主库)的数据复制到一个或多个MySQL数据库服务器(从库)。传统的MySQL复制提供了一种简单的Primary-Secondary复制方法,默认情况下,复制是单向异步的。MySQL支持两种复制方式:基于行的复制和基于语句的复制。这两种方式都是通过在主库上记录二进制日志(binlog)、在从库重放中继日志(relylog)的方式来实现异步的数据复制。二进制日志或中继日志中的记录被称为事件。所谓异步包含两层含义,一是主库的二进制日志写入与将其发送到从库是异步进行的,二是从库获取与重放日志事件是异步进行的。这意味着,在同一时间点从库上的数据更新可能落后于主库,并且无法保证主从之间的延迟间隔。

            复制给主库增加的开销主要体现在启用二进制日志带来的I/O,但是开销并不大,MySQL官方文档中称开启二进制日志会产生1%的性能损耗。出于对历史事务备份以及从介质失败中恢复的目的,这点开销是非常必要的。除此之外,每个从库也会对主库产生一些负载,例如网络和I/O开销。当从库读取主库的二进制日志时,可能会造成一定的I/O开销。如果从一个主库上复制到多个从库,唤醒多个复制线程发送二进制日志内容的开销将会累加。但所有这些复制带来的额外开销相对于应用对MySQL服务器造成的高负载来说是很小的。

    1. 复制的用途

    (1)横向扩展
            通过复制可以将读操作指向从库来获得更好的读扩展。所有写入和更新都在主库上进行,但读取可能发生在一个或多个从库上。在这种读写分离模型中,主库专用于更新,显然比同时进行读写操作会有更好的写性能。需要注意的是,对于写操作并不适合通过复制来扩展。在一主多从架构中,写操作会被执行多次,这时整个系统的写性能取决于写入最慢的那部分。

    (2)负载均衡
            通过MySQL复制可以将读操作分不到多个服务器上,实现对读密集型应用的优化。对于小规模的应用,可以简单地对机器名做硬编码或者使用DNS轮询(将一个机器名指向多个IP地址)。当然也可以使用复杂的方法,例如使用LVS网络负载均衡器等,能够很好地将负载分配到不同的MySQL服务器上。

    (3)提高数据安全性
            提高数据安全性可以从两方面来理解。其一,因为数据被复制到从库,并且从库可以暂停复制过程,所以可以在从库上运行备份服务而不会影响相应的主库。其二,当主库出现问题,还有从库的数据可以被访问。但是,对备份来说,复制仅是一项有意义的技术补充,它既不是备份也不能够取代备份。例如,当用户误删除一个表,而且此操作已经在从库上被复制执行,这种情况下只能用备份来恢复。

    (4)提高高可用性
            复制能够帮助应用程序避免MySQL单点失败,一个包含复制的设计良好的故障切换系统能够显著缩短宕机时间。

    (5)滚动升级
            比较普遍的做法是,使用一个高版本MySQL作为从库,保证在升级全部实例前,查询能够在从库上按照预期执行。测试没有问题后,将高版本的MySQL切换为主库,并将应用连接至该主库,然后重新搭建高版本的从库。

    2. 复制如何工作

            如前所述,MySQL复制依赖二进制日志,所以要理解复制如何工作,先要了解MySQL的二进制日志。
    (1)二进制日志
            二进制日志包含描述数据库更改的事件,如建表操作或对表数据的更改等。开启二进制日志有两个重要目的:

    • 用于复制。主库上的二进制日志提供要发送到从库的数据更改记录。主库将其二进制日志中包含的事件发送到从库,从库执行这些事件以对主服务器上的数据进行相同的更改。
    • 用于恢复。当出现介质错误(如磁盘故障)时,数据恢复操作需要使用二进制日志。还原备份后,将重新执行备份后记录的二进制日志中的事件。

            不难看出,MySQL二进制日志所起的作用与Oracle的归档日志类似。二进制日志只记录更新数据的事件,不用于SELECT或SHOW等语句。通过设置log-bin系统变量开启二进制日志,MySQL 8中缺省是开启的。

            二进制日志有STATEMENT、ROW、MIXED三种格式,通过binlog-format系统变量设置:

    • STATMENT格式,基于SQL语句的复制(statement-based replication,SBR)。每一条会修改数据的SQL语句会记录到binlog中。这种格式的优点是不需要记录每行的数据变化,这样二进制日志会比较少,减少磁盘I/O,提高性能。缺点是在某些情况下会导致主库与从库中的数据不一致,例如last_insert_id()、now()等非确定性函数,以及用户自定义函数(user-defined functions,udf)等易出现问题。
    • ROW格式,基于行的复制(row-based replication,RBR)。不记录每一条SQL语句的上下文信息,仅需记录哪条数据被修改了,修改成了什么样子,能清楚记录每一行数据的修改细节。其优点是不会出现某些特定情况下的存储过程、函数或触发器的调用和触发无法被正确复制的问题。缺点是通常会产生大量的日志,尤其像大表上执行alter table操作时候会让日志暴涨。
    • MIXED格式,混合复制(mixed-based replication,MBR)。是语句和行两种格式的混合使用,默认使用STATEMENT模式保存二进制日志,对于STATEMENT模式无法正确复制的操作,会自动切换到基于行的格式,MySQL会根据执行的SQL语句选择日志保存方式。

            MySQL 8缺省使用ROW格式。二进制日志的存放位置最好设置到与MySQL数据目录不同的磁盘分区,以降低磁盘I/O的竞争,提升性能,并且在数据磁盘故障的时候还可以利用备份和二进制日志恢复数据。

    (2)复制步骤
            总的来说,MySQL复制有五个步骤:

    1. 在主库上把数据更改事件记录到二进制日志中。
    2. 从库上的I/O线程向主库询问二进制日志中的事件。
    3. 主库上的binlog dump线程向I/O线程发送二进制事件。
    4. 从库上的I/O线程将二进制日志事件复制到自己的中继日志中。
    5. 从库上的SQL线程读取中继日志中的事件,并将其重放到从库上。

            图1更详细地描述了复制的细节。

    图1 复制如何工作

     

            第一步是在主库上记录二进制日志。在每次准备提交事务完成数据更新前,主库将数据更新的事件记录到二进制日志中。MySQL会按事务提交的顺序而非每条语句的执行顺序来记录二进制日志。在记录二进制日志后,主库会告诉存储引擎可以提交事务了。

            下一步,从库将主库的二进制日志复制到其本地的中继日志中。首先,从库会启动一个工作线程,称为I/O线程,I/O线程跟主库建立一个普通的客户端连接,然后在主库上启动一个特殊的二进制日志转储(binlog dump)线程,它会读取主库上二进制日志中的事件,但不会对事件进行轮询。如果该线程追赶上了主库,它将进入睡眠状态,直到主库发送信号通知其有新的事件时才会被唤醒,从库I/O线程会将接收到的事件记录到中继日志中。

            从库的SQL线程执行最后一步,该线程从中继日志中读取事件并在从库上执行,从而实现从库数据的更新。当SQL线程追赶I/O线程时,中继日志通常已经在系统缓存中,所以重放中继日志的开销很低。SQL线程执行的事件也可以通过log_slave_updates系统变量来决定是否写入其自己的二进制日志中,这可以用于级联复制的场景。

            这种复制架构实现了获取事件和重放事件的解耦,允许这两个过程异步进行。也就是说I/O线程能够独立于SQL线程之外工作。但这种架构也限制了复制的过程,其中最重要的一点是在主库上并发更新的查询在从库上通常只能串行化执行,因为缺省只有一个SQL线程来重放中继日志中的事件。在MySQL 5.6以后已经可以通过配置slave_parallel_workers等系统变量进行并行复制,在后面讨论与复制相关的性能问题时会介绍并行复制的相关细节。

            现在我们已经了解了MySQL复制是以二进制日志为基础的,但是像Innodb这样的事务引擎有自己的事务日志,如ib_logfile,这些事务日志通常被称为重做日志(redo log)。作为背景知识,简单介绍下Innodb重做日志的作用。

            对Innodb的任何修改操作都会首先在称为缓冲池(innodb buffer pool)的内存页面上进行,然后这样的页面将被标记为脏页,并被放到专门的刷新列表上,后续将由master thread或专门的刷脏线程阶段性的将这些页面写入磁盘。这样的好处是避免每次写操作都操作磁盘导致大量的随机I/O,阶段性的刷脏可以将多次对页面的修改合并成一次I/O操作,同时异步写入也降低了访问时延。然而,如果在脏页还未刷入磁盘时,服务器非正常关闭,这些修改操作将会丢失,如果写入操作正在进行,甚至会由于损坏数据文件导致数据库不可用。为了避免上述问题的发生,Innodb将所有对页面的修改操作写入一个专门的文件,并在数据库启动时从此文件进行实例恢复操作,这个文件就是重做日志文件。每当有更新操作时,在数据页变更之前将操作写入重做日志,这样当发生掉电之类的情况时系统可以在重启后继续操作。这就是所谓的预写日志(Write-ahead logging,WAL)。这样的技术推迟了缓冲区页面的刷新,从而提升了数据库的吞吐量。同时由于重做日志的写操作是顺序I/O,相对于写数据文件的随机I/O要快得多。大多数数据库系统都采用类似的技术实现。

            聪明如你可能已经有了这样的疑问,在复制中二进制日志和重做日志如何协同工作?假设Innodb写完重做日志后,服务异常关闭。主库能够根据重做日志恢复数据,但由于二进制日志没写入,会导致从库同步时少了这个事务么?或者反之,二进制日志写成功,而重做日志没有写完,是否导致从库执行事务,而主库不执行?这些情况会不会产生主从数据不一致的问题呢?解决这些问题是MySQL的核心需求,让我们从MySQL基本架构说起。图2是MySQL的逻辑结构图。

    图2 MySQL服务器逻辑架构图

     

            最上层用于处理客户端连接、授权认证、安全等等。第二层架构是MySQL服务器层。大多数MySQL的核心服务功能都在这一层,包括查询解析、分析、优化、缓存以及所有内置函数,所有跨存储引擎的功能(存储过程、触发器、视图等)都在这一层实现。如你所料,二进制日志也在这一层实现。第三层包含了存储引擎,负责MySQL中数据的存储和提取。服务器通过API与存储引擎进行通信,存储引擎只是简单地响应上层服务器的请求。显然Innodb的重做日志在这一层实现。

            由于MySQL的事务日志包含二进制日志和重做日志,当发生崩溃恢复时,MySQL主库通过重做日志进行恢复,而在主从复制的环境下,从库是依据于主节点的二进制日志进行同步数据的。这样的架构对两种日志有两个基本要求:第一,保证二进制日志里面存在的事务一定在重做日志里面存在,也就是二进制日志里不会比重做日志多事务(可以少,因为重做日志里面记录的事务可能有部分没有提交,这些事务最终可能会被回滚)。第二,两种日志中事务的顺序一致,这也是很重要的一点,假设两者记录的事务顺序不一致,那么会出现类似于主库事务执行的顺序是ta、tb、tc、td,但是二进制日志中记录的是ta、tc、tb、td,被复制到从库后导致主从数据不一致。为了达到这两点要求,MySQL使用内部XA来实现(XA是eXtended Architecture的缩写,是X/Open分布式事务定义的事务中间件与数据库之间的接口规范),其核心是两阶段提交(two phase commit,2PC)。

    3. 两阶段提交

            在两阶段提交协议中一般分为事务管理器(协调者)和若干事务执行者(参与者)两种角色。在MySQL内部实现的两阶段提交中,二进制日志充当了协调者角色,由它来通知Innodb执行准备、提交或回滚步骤。从实现角度分析,提交流程和代码框架分别如图3、图4所示。

    图3 MySQL两阶段提交流程

     

    图4 commit命令的MySQL代码框架

     

    (1)先调用binglog_hton和innobase_hton的prepare方法完成第一阶段,binlog_hton的papare方法实际上什么也没做,innodb的prepare持有prepare_commit_mutex,将重做日志刷磁盘,并将事务状态设为TRX_PREPARED。
    (2)如果事务涉及的所有存储引擎的prepare都执行成功,则调用TC_LOG_BINLOG::log_xid将事务(STATEMENT格式或ROW格式)写到二进制日志,此时,事务已经铁定要提交了。否则,调用ha_rollback_trans回滚事务,而事务实际上也不会写到二进制日志。
    (3)最后,调用引擎的commit完成事务的提交。实际上binlog_hton->commit什么也不会做(上一步已经将二进制日志写入磁盘),innobase_hton->commit则清除回滚信息,向重做日志中写入COMMIT标记,释放prepare_commit_mutex,并将事务设为TRX_NOT_STARTED状态。

            如果数据库系统发生崩溃,当重启数据库时会进行崩溃恢复操作。具体到代码层面,Innodb在恢复的时候,不同状态的事务,会进行不同的处理:

    • 对于TRX_COMMITTED_IN_MEMORY的事务,清除回滚段,然后将事务设为TRX_NOT_STARTED;
    • 对于TRX_NOT_STARTED的事务,表示事务已经提交,跳过;
    • 对于TRX_PREPARED的事务,要根据二进制日志来决定事务是否提交,暂时跳过;
    • 对于TRX_ACTIVE的事务,回滚。

            简单来讲,当发生崩溃恢复时,数据库根据重做日志进行数据恢复,逐个查看每条重做条目的事务状态,根据图3的流程,如果已进行到TRX_NOT_STARTED阶段,也就是存储引擎commit阶段,那么说明重做日志和二进制日志是一致的,正常根据重做条目进行恢复即可;事务状态为TRX_ACTIVE,没写到二进制日志中,直接回滚;如果事务状态为TRX_PREPARED,要分两种情况,先检查二进制日志是否已写入成功,如果没写入成功,那么就算是TRX_PREPARED状态,也要回滚。如果写入成功了,那么就进行最后一步,调用存储引擎commit,更改事务状态为TRX_NOT_STARTED,也就是真正提交状态,可以用作数据恢复。

            可见,MySQL是以二进制日志的写入与否作为事务提交成功与否的标志,通过这种方式让Innodb重做日志和MySQL服务器的二进制日志中的事务状态保持一致。两阶段提交很好的保持了数据一致性和事务顺序性。

            了解了所有这些技术细节后,当初的疑问自然也就有了答案。假设在阶段(1)结束之后程序异常,此时没有写入二进制日志,则从库不会同步这个事务。主库上,崩溃恢复时重做日志中这个事务没有trx_commit,因此会被回滚。逻辑上主从库都不会执行这个事务。假设在阶段(2)结束后程序异常,此时二进制日志已经写入,则从库会同步这个事务。主库上,根据重做日志能够正常恢复此事务。也就是说,若二进制日志写入完成,则主从库都会正常完成事务,反之则主从库都回滚事务,都不会出现主从不一致的问题。

            MySQL通过innodb_support_xa系统变量控制Innodb是否支持XA事务的2PC,默认是TRUE。如果关闭,则Innodb在prepare阶段就什么也不做,这可能会导致二进制日志的顺序与Innodb提交的顺序不一致,继而导致在恢复时或者从库上产生不同的数据。在MySQL 8中,innodb_support_xa系统变量已被移除,因为始终启用Innodb对XA事务中两阶段提交的支持,不再让用户来选择。

            上述的MySQL两阶段提交流程并不是天衣无缝的,主从数据是否一致还与重做日志和二进制日志的写盘方式有关。innodb_flush_log_at_trx_commit和sync_binlog系统变量分别控制两者的落盘策略。

    • innodb_flush_log_at_trx_commit:有0、1、2三个可选值。0表示每秒进行一次刷新,但是每次事务提交不进行任何操作(每秒调用fsync使数据落地到磁盘,不过这里需要注意如果底层存储有cache,比如raid cache,那么这时也不会真正落盘,但是由于一般raid卡都带有备用电源,所以一般都认为此时数据是安全的)。1代表每次事务提交都会进行刷新,这是最安全的模式。2表示每秒刷新,每次事务提交时不刷新,而是调用write将重做日志缓冲区里面的内容刷到操作系统页面缓存。从数据安全性和性能比较三种策略的优劣为:1由于每次事务提交都会是重做日志落盘,所以是最安全的,但是由于fsync的次数增多导致性能下降比较严重。0表示每秒刷新,每次事务提交不进行任何操作,所以MySQL或操作系统崩溃时最多丢失一秒的事务。2相对于0来说了多了每次事务提交时的一个write操作,此时数据虽然没有落磁,但是只要没有操作系统崩溃,即使MySQL崩溃,那么事务也是不会丢失的。
    • sync_binlog:MySQL在提交事务时调用MYSQL_LOG::write完成写二进制日志,并根据sync_binlog决定是否进行刷新。默认值是0,即不刷新,从而把控制权交给操作系统。如果设为1,则每次提交事务,就会进行一次磁盘刷新。

            这两个参数不同的值会带来不同的效果。两者都设置为1,数据最安全,能保证主从一致,这也是MySQL 8的默认设置。innodb_flush_log_at_trx_commit非1,假设在二进制日志写入完成后系统崩溃,则可能出现这样的情况:从库能够执行事务,但主库中trx_prepare的日志没有被写入到重做日志中,导致主库不执行事务,出现主从不一致的情况。同理若sync_binlog非1,可能导致二进制日志丢失(操作系统异常宕机),从而与Innodb层面的数据不一致,体现在复制上,从库可能丢失事务。在数据一致性要求很高的场景下,建议就使用缺省的全1配置。

    二、复制实验环境

    1. 主机IP
    172.16.1.125(主)
    172.16.1.126(从)
    172.16.1.127(从)

    2. 软件环境
    OS:CentOS Linux release 7.2.1511 (Core)
    MySQL:MySQL Community Server 8.0.16
    glibc:glibc-2.17-105.el7.x86_64

    3. 硬件环境
    三台虚拟机,每台基本配置为:
    . 双核双CPU,Intel(R) Xeon(R) CPU E5-2420 0 @ 1.90GHz
    . 8G物理内存,8G Swap
    . 100G物理硬盘

    三、安装mysql-8.0.16

            从https://dev.mysql.com/downloads/mysql/下载二进制安装文件mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz,相关选项如图5所示。

    图5 下载mysql-8.0.16安装包

     

    然后用root用户按顺序执行下面的命令,在三台主机上安装MySQL。

    # 进入安装目录
    cd /usr/local
    # 从tar包中把提取文件
    tar xvf /home/mysql/mysql-8.0.16-linux-glibc2.12-x86_64.tar.xz 
    # 建立软连接
    ln -s mysql-8.0.16-linux-glibc2.12-x86_64 mysql
    # 进入mysql目录
    cd mysql
    # 建立secure_file_priv系统变量指向的目录
    mkdir mysql-files
    # 修改属主为mysql
    chown mysql:mysql mysql-files
    # 修改目录权限
    chmod 750 mysql-files
    # mysql系统初始化
    bin/mysqld --initialize --user=mysql
    # 建立SSL/RSA相关文件,如果不启用SSL连接,这步可省略
    bin/mysql_ssl_rsa_setup
    # 启动mysql服务器
    bin/mysqld_safe --user=mysql &
    # 连接mysql服务器
    bin/mysql -u root -p
    -- 修改root密码
    alter user user() identified by "123456";
    -- 创建一个新的mysql管理员账号
    create user 'wxy'@'%' identified with mysql_native_password by '123456';
    grant all on *.* to 'wxy'@'%' with grant option;

            命令说明:
    (1)mysql-files目录用作secure_file_priv系统变量的值。该变量将导入和导出操作限制到特定目录。例如由LOAD DATA和SELECT ... INTO OUTFILE语句和LOAD_FILE()函数所执行的操作。仅允许具有FILE权限的用户执行这些操作。secure_file_priv系统变量设置如下:

    • 空字符串:变量不起作用,是不安全的设置。
    • 目录名:mysql限制导入和导出操作仅用于该目录中的文件。目录必须已经存在,mysql不会创建它。
    • NULL:mysql禁用导入导出操作。

    (2)mysqld --initialize 命令创建默认数据库并退出。在过程中会创建一个超级用户,并为该用户产生一个随机密码。命令执行输出如下所示:

    [root@hdp2/usr/local/mysql]#bin/mysqld --initialize --user=mysql
    2019-05-05T06:31:58.956385Z 0 [System] [MY-013169] [Server] /usr/local/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.16) initializing of server in progress as process 10256
    2019-05-05T06:32:01.287093Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: w1SN3pgRPL*D
    2019-05-05T06:32:02.901171Z 0 [System] [MY-013170] [Server] /usr/local/mysql-8.0.16-linux-glibc2.12-x86_64/bin/mysqld (mysqld 8.0.16) initializing of server has completed
    [root@hdp2/usr/local/mysql]#

            mysql文档中说产生的临时密码会写到.err日志文件里,但在本次安装中,这步并不生成.err文件。保险的做法还是记下临时密码,这点很重要。在initialize情况下,临时密码默认标记为已过期,用户必须在第一次进入mysql后首先修改密码。

    (3)mysql_ssl_rsa_setup程序将创建SSL证书和密钥文件,使用SSL进行安全连接所需的RSA密钥对文件。如果现有的SSL文件已过期,mysql_ssl_rsa_setup也可用于创建新的SSL文件。

            mysql_ssl_rsa_setup使用openssl命令,因此主机上必须安装有OpenSSL。mysql_ssl_rsa_setup检查数据目录中的以下SSL文件:

    ca.pem
    server-cert.pem
    server-key.pem

            如果存在任何这些文件,则mysql_ssl_rsa_setup不会创建任何SSL文件。否则,它会调用openssl来创建它们,以及一些其他文件:

    ca.pem               自签名CA证书
    ca-key.pem           CA私钥
    server-cert.pem      服务器证书
    server-key.pem       服务器私钥
    client-cert.pem      客户端证书
    client-key.pem       客户端私钥

            在启用SSL安全客户端连接时需要这些文件。

            之后mysql_ssl_rsa_setup检查数据目录中的以下RSA文件:

    private_key.pem      私钥/公钥对的私有成员
    public_key.pem       私钥/公钥对的公共成员

            如果存在任何这些文件,则mysql_ssl_rsa_setup不会创建任何RSA文件。否则,它会调用openssl来创建它们。对于sha256_password或caching_sha2_password插件验证的帐户不加密连接时,通过这些文件使用RSA进行安全密码交换。

            mysql 8.0.16缺省支持SSL加密连接:

    mysql> show variables like 'have_ssl';
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | have_ssl      | YES   |
    +---------------+-------+
    1 row in set (0.00 sec)

    (4)第一次进入mysql后,执行任何命令都会报以下错误:

    ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

            提示很明显,需要修改初始化时为用户'root'@'localhost'生成的临时密码。mysql 8缺省使用的认证插件是caching_sha2_password:

    mysql> show variables like 'default_authentication_plugin';
    +-------------------------------+-----------------------+
    | Variable_name                 | Value                 |
    +-------------------------------+-----------------------+
    | default_authentication_plugin | caching_sha2_password |
    +-------------------------------+-----------------------+
    1 row in set (0.01 sec)

            当用老版本mysql的客户端连接mysql 8服务器时,可能报以下错误:

    ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded: /home/mysql/mysql-5.6.14/lib/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory

            可以使用两个方法解决此问题。第一种方法是在配置文件中设置default_authentication_plugin='mysql_native_password',然后重启mysql服务器使之生效。default_authentication_plugin是只读系统变量,不能动态修改。第二种方法是在创建用户时,使用兼容新老版本的认证方式,例如:

    create user 'wxy'@'%' identified with mysql_native_password by '123456';

            通过以下查询可以得到用户所对应的认证插件:

    mysql> select host,user,plugin from mysql.user;
    +-----------+------------------+-----------------------+
    | host      | user             | plugin                |
    +-----------+------------------+-----------------------+
    | %         | wxy              | mysql_native_password |
    | localhost | mysql.infoschema | caching_sha2_password |
    | localhost | mysql.session    | caching_sha2_password |
    | localhost | mysql.sys        | caching_sha2_password |
    | localhost | root             | caching_sha2_password |
    +-----------+------------------+-----------------------+
    5 rows in set (0.00 sec)

    四、配置异步复制

            本实验中分别针对空库、脱机、联机三种方式,配置一主两从的mysql标准异步复制。只做整服务器级别的复制,不考虑对个别库表或使用过滤复制的情况。

    1. 空库

            初始安装后,mysql中还没有任何应用数据。此时事先配置好复制,再投入使用,是最理想也是最简单的一种情况,具体配置步骤如下。
    (1)修改server_id系统变量。

    -- 主库
    set global server_id=1125;
    
    -- 从库1
    set global server_id=1126;
    
    -- 从库2
    set global server_id=1127;

            要求复制中所有MySQL实例的server_id都不相同,这里将三个实例的server_id分别配置为1125、1126、1127。server_id系统变量可以动态修改,这样做的好处是不需要重启实例,配置即可在新连接中生效。但是为了避免MySQL重启后配置信息丢失,还需要同时在/etc/my.cnf配置文件中设置server_id参数。

    (2)查看主库二进制日志信息,

    -- 主库
    show master status;

            因为是一个静态的空库,二进制信息此时不会发生变化,可以作为复制的起始点。本例中重启过MySQL实例,输出的信息为:

    +---------------+----------+--------------+------------------+-------------------+
    | File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
    +---------------+----------+--------------+------------------+-------------------+
    | binlog.000003 |      155 |              |                  |                   |
    +---------------+----------+--------------+------------------+-------------------+

    (3)在主库上建立复制专属用户

    -- 主库
    create user 'repl'@'%' identified with mysql_native_password by '123456';
    grant replication client,replication slave on *.* to 'repl'@'%';

            前面说过MySQL 8的用户认证机制,这里仍然使用mysql_native_password方式。

    (4)在从库创建主库信息。

    change master to
    master_host='172.16.1.125',
    master_port=3306,
    master_user='repl',
    master_password='123456',
    master_log_file='binlog.000003',
    master_log_pos=155;

            连接主库时,需要使用change master to提供连接到主库的连接选项,包括主机地址、端口、用户名、密码、二进制文件名、复制起始事件位置等。change master to后,在mysql.slave_master_info表中就会生成一条记录,此行为是由master_info_repository系统变量控制的。MySQL 8中,该变量的缺省值为TABLE,即将与复制相关的主库信息记录到mysql.slave_master_info表中。随着复制的进行,表中的数据会随之更新。change master to只是为I/O线程连接主库时提供连接参数,这条语句本身不会连接主库。以后启动I/O线程时,I/O线程都会自动读取这条记录来连接主库,不需要再执行change master to语句。

            类似地,MySQL 8缺省会将中继日志的重放信息存到mysql.slave_relay_log_info表中。该行为由系统变量relay_log_info_repository控制。中继日志信息在首次启动复制时生成,并随复制即时改变。SQL线程再次启动时就能从中获取到从中继日志的的哪个地方继续读取、执行。

    (5)在从库启动复制并查看复制信息

    start slave;
    show slave status\G
    select * from mysql.user where user='repl'\G

            刚才我们并没有在从库上建立repl用户,但由于create user语句是在起始位置点后执行的,因此可以正常复制到从库,查询mysql.user表即可确认。       

            start slave语句会启动I/O线程和SQL线程,并创建一个到主库的客户端连接。该命令执行后,在主库的processlist中会看到类似如下的线程,这些就是从库上I/O线程所建立的,Binlog Dump表示由I/O线程在主库上启动了Binlog Dump线程,每个连接上来的从库对应一个线程,如Id 32和56是两个从库的连接线程:

    mysql> show processlist;
    +----+-----------------+------------+------+-------------+--------+---------------------------------------------------------------+------------------+
    | Id | User            | Host       | db   | Command     | Time   | State                                                         | Info             |
    +----+-----------------+------------+------+-------------+--------+---------------------------------------------------------------+------------------+
      ...
    | 32 | repl            | hdp4:3723  | NULL | Binlog Dump | 328879 | Master has sent all binlog to slave; waiting for more updates | NULL             |
    | 56 | repl            | hdp3:57308 | NULL | Binlog Dump | 319204 | Master has sent all binlog to slave; waiting for more updates | NULL             |
      ...

            从库上的processlist中会看到类似如下的线程,Id 325和326分别对应I/O线程与SQL线程:

    mysql> show processlist;
    +--------+-----------------+-----------+-------+---------+--------+--------------------------------------------------------+------------------+
    | Id     | User            | Host      | db    | Command | Time   | State                                                  | Info             |
    +--------+-----------------+-----------+-------+---------+--------+--------------------------------------------------------+------------------+
      ...
    |    325 | system user     |           | NULL  | Connect | 320408 | Waiting for master to send event                       | NULL             |
    |    326 | system user     |           | NULL  | Query   | 320408 | Slave has read all relay log; waiting for more updates | NULL             |
      ...

            从show slave status输出中可以查看复制状态信息:

    mysql> show slave status\G
    *************************** 1. row ***************************
                   Slave_IO_State: Waiting for master to send event
                      Master_Host: 172.16.1.125
                      Master_User: repl
                      Master_Port: 3306
                    Connect_Retry: 60
                  Master_Log_File: binlog.000011
              Read_Master_Log_Pos: 155
                   Relay_Log_File: hdp3-relay-bin.000002
                    Relay_Log_Pos: 319
            Relay_Master_Log_File: binlog.000011
                 Slave_IO_Running: Yes
                Slave_SQL_Running: Yes
                  Replicate_Do_DB: 
              Replicate_Ignore_DB: 
               Replicate_Do_Table: 
           Replicate_Ignore_Table: 
          Replicate_Wild_Do_Table: 
      Replicate_Wild_Ignore_Table: 
                       Last_Errno: 0
                       Last_Error: 
                     Skip_Counter: 0
              Exec_Master_Log_Pos: 155
                  Relay_Log_Space: 526
                  Until_Condition: None
                   Until_Log_File: 
                    Until_Log_Pos: 0
               Master_SSL_Allowed: No
               Master_SSL_CA_File: 
               Master_SSL_CA_Path: 
                  Master_SSL_Cert: 
                Master_SSL_Cipher: 
                   Master_SSL_Key: 
            Seconds_Behind_Master: 0
    Master_SSL_Verify_Server_Cert: No
                    Last_IO_Errno: 0
                    Last_IO_Error: 
                   Last_SQL_Errno: 0
                   Last_SQL_Error: 
      Replicate_Ignore_Server_Ids: 
                 Master_Server_Id: 1125
                      Master_UUID: 8eed0f5b-6f9b-11e9-94a9-005056a57a4e
                 Master_Info_File: mysql.slave_master_info
                        SQL_Delay: 0
              SQL_Remaining_Delay: NULL
          Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
               Master_Retry_Count: 86400
                      Master_Bind: 
          Last_IO_Error_Timestamp: 
         Last_SQL_Error_Timestamp: 
                   Master_SSL_Crl: 
               Master_SSL_Crlpath: 
               Retrieved_Gtid_Set: 
                Executed_Gtid_Set: 
                    Auto_Position: 0
             Replicate_Rewrite_DB: 
                     Channel_Name: 
               Master_TLS_Version: 
           Master_public_key_path: 
            Get_master_public_key: 0
                Network_Namespace: 
    1 row in set (0.00 sec)
    
    mysql> 

            在从库上执行show slave status可以查看从库状态,输出信息非常多,其中除了那些描述I/O线程、SQL线程状态的行,还有几个log_file和pos相关的行。理解这几行的意义至关重要,所以这里完整地描述它们:

    • Master_Log_File:I/O线程正在读取的master binlog;
    • Read_Master_Log_Pos:I/O线程已经读取到master binlog的哪个位置;
    • Relay_Log_File:SQL线程正在读取和执行的relay log;
    • Relay_Log_Pos:SQL线程已经读取和执行到relay log的哪个位置;
    • Relay_Master_Log_File:SQL线程最近执行的操作对应的是哪个master binlog;
    • Exec_Master_Log_Pos:SQL线程最近执行的操作对应的是master binlog的哪个位置。

            (Relay_Master_Log_File, Exec_Master_log_Pos)构成一个坐标,这个坐标表示从库上已经将主库上的哪些数据重放到自己的实例中,它可以用于下一次change master to时指定的二进制日志坐标。与这个坐标相对应的是从库上SQL线程的中继日志坐标(Relay_Log_File, Relay_Log_Pos)。这两个坐标位置不同,但它们对应的数据是一致的。

            还有一个延迟参数Seconds_Behind_Master需要说明一下,它的本质意义是SQL线程比I/O线程慢多少。如果主从之间的网络状况优良,那么从库的I/O线程读速度和主库写二进制日志的速度基本一致,所以这个参数也用来描述“SQL线程比主库慢多少”,也就是说从库比主库少多少数据,只不过衡量的单位是秒。需要注意的是,该参数的描述并不标准,只是在网速很好的时候做个大概估计,很多种情况下它的值都是0,即使SQL线程比I/O线程慢了很多也是如此。

    2. 脱机

            如果数据库已经存在应用数据,但允许一个可接受的脱机时间窗口做复制,这种场景下常用的做法是先直接将主库的数据目录整体拷贝到从库,再启动复制。具体步骤如下。

    (1)在主库上建立复制专属用户

    -- 主库
    create user 'repl'@'%' identified with mysql_native_password by '123456';
    grant replication client,replication slave on *.* to 'repl'@'%';

    (2)停掉复制涉及的实例

    mysqladmin -uroot -p123456 shutdown

            本例中一主两从三个实例都停止。

    (3)复制将主库的数据目录整体拷贝到从库

    scp -r /usr/local/mysql/data/ 172.16.1.126:/usr/local/mysql

    (4)保证所有参与复制实例的server-uuid和server_id都不同
            这是非常重要的一步,相同的server-uuid或server_id会造成复制错误。从MySQL 5.6开始,用 128 位的 server_uuid 代替了原本的 32 位 server_id 的大部分功能。原因很简单,server_id 依赖于 my.cnf 的手工配置,有可能产生冲突。而自动产生 128 位 uuid 的算法可以保证所有的 MySQL uuid 都不会冲突。首次启动时 MySQL 会调用 generate_server_uuid() 自动生成一个 server_uuid,并且保存到 auto.cnf 文件。这个文件目前存在的唯一目的就是保存 server_uuid。在 MySQL 再次启动时会读取 auto.cnf 文件,继续使用上次生成的 server_uuid。使用 show global variables like 'server_uuid' 命令可以查看 MySQL 实例当前使用的 server_uuid​,它是一个全局只读变量。全局唯一的 server_uuid 的一个好处是,可以解决由 server_id 配置冲突带来的 MySQL 主从复制的异常终止。在从库向主库申请二进制日志时,会首先发送自己的 server_uuid,主库用从库发送的 server_uuid 代替 server_id 作为 kill_zombie_dump_threads 的参数,终止冲突或者僵死的 BINLOG_DUMP 线程。

            删除data_dir/auto.cnf文件,实例启动时会自动生成server_uuid的值。本例中删除从库的/usr/local/mysql/data/auto.cnf文件,并编辑/etc/my.cnf文件修改server_id,保证三个实例的配置互不相同。

    (5)重启实例

    mysqld_safe --user=mysql &

            本例中一主两从三个实例都启动。

    (6)查看主库二进制日志信息,

    -- 主库
    show master status;

    (7)使用上一步的输出在从库创建主库信息。

    change master to
    master_host='172.16.1.125',
    master_port=3306,
    master_user='repl',
    master_password='123456',
    master_log_file='binlog.000004',
    master_log_pos=155;

    (5)在从库启动复制并查看复制信息

    start slave;
    show slave status\G

            此时在从库的err日志文件中可以看到复制正常启动的信息:

    [mysql@hdp3~]$tail /usr/local/mysql/data/hdp3.err 
    2019-05-10T02:54:22.226249Z mysqld_safe Logging to '/usr/local/mysql/data/hdp3.err'.
    2019-05-10T02:54:22.277620Z mysqld_safe Starting mysqld daemon with databases from /usr/local/mysql/data
    2019-05-10T02:54:22.710162Z 0 [System] [MY-010116] [Server] /usr/local/mysql/bin/mysqld (mysqld 8.0.16) starting as process 20571
    2019-05-10T02:54:23.370413Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.
    2019-05-10T02:54:23.420160Z 0 [System] [MY-010931] [Server] /usr/local/mysql/bin/mysqld: ready for connections. Version: '8.0.16'  socket: '/tmp/mysql.sock'  port: 3306  MySQL Community Server - GPL.
    2019-05-10T02:54:23.547717Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Socket: '/tmp/mysqlx.sock' bind-address: '::' port: 33060
    2019-05-10T02:56:17.328454Z 144 [Warning] [MY-010604] [Repl] Neither --relay-log nor --relay-log-index were used; so replication may break when this MySQL server acts as a slave and has his hostname changed!! Please use '--relay-log=hdp3-relay-bin' to avoid this problem.
    2019-05-10T02:56:17.333635Z 144 [System] [MY-010597] [Repl] 'CHANGE MASTER TO FOR CHANNEL '' executed'. Previous state master_host='', master_port= 3306, master_log_file='', master_log_pos= 4, master_bind=''. New state master_host='172.16.1.125', master_port= 3306, master_log_file='binlog.000004', master_log_pos= 155, master_bind=''.
    2019-05-10T02:56:25.235523Z 171 [Warning] [MY-010897] [Repl] Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
    2019-05-10T02:56:25.237851Z 171 [System] [MY-010562] [Repl] Slave I/O thread for channel '': connected to master 'repl@172.16.1.125:3306',replication started in log 'binlog.000004' at position 155

    3. 联机

            脱机建立复制的需求太过理想化,大多数情况下,复制是被要求在不影响线上业务的情况下,联机创建的,而且还要求对线上库的影响越小越好。例如,复制过程化中对主库加锁会影响对主库的访问,因此通常是不被允许的。这种场景下有两种备选的复制方案:使用mysqldump程序或使用如XtraBackup的第三方工具。这两种方案有各自的适用场合。使用mysqldump联机建立复制的过程如下。

    (1)在主库上建立复制专属用户

    -- 主库
    create user 'repl'@'%' identified with mysql_native_password by '123456';
    grant replication client,replication slave on *.* to 'repl'@'%';

    (2)在从库上创建主库信息

    change master to
    master_host='172.16.1.125',
    master_port=3306,
    master_user='repl',
    master_password='123456';

            注意在上面这条命令中并没有指定主库二进制文件的文件名和位置。

    (3)在从库用mysqldump建立复制

    mysqldump --single-transaction --all-databases --master-data=1 --host=172.16.1.125 --user=wxy --password=123456 --apply-slave-statements | mysql -uroot -p123456 -h127.0.0.1

            说明:

    • --single-transaction参数可以对Innodb表执行非锁定导出。此选项将事务隔离模式设置为REPEATABLE READ,并在转储数据之前向服务器发送START TRANSACTION SQL语句。它仅适用于Innodb等事务表,因为它会在发出START TRANSACTION时转储数据库的一致状态,而不会阻塞任何应用程序。因此这里假定:1. 所有的应用数据表都使用Innodb引擎。2. 所有系统表数据在备份过程中不会发生变化。
    • --master-data参数会导致转储输出包含类似 CHANGE MASTER TO MASTER_LOG_FILE='binlog.000004', MASTER_LOG_POS=1480; 的SQL语句,该语句指示主库的二进制日志坐标(文件名和位置)。如果选项值为2,则CHANGE MASTER TO语句将写为SQL注释,因此仅提供信息,不会执行。如果参数值为1,则该语句不会写为注释,并在重新加载转储文件时执行。如果未指定选项值,则默认值为1。
    • --apply-slave-statements参数会在CHANGE MASTER TO语句之前添加STOP SLAVE语句,并在输出结尾处添加START SLAVE语句,用来自动开启复制。
    • 通过管道操作符,导出导入一步进行,不需要中间落盘生成文件。

    (4)确认复制状态

    -- 从库
    show slave status\G

            mysqldump方式的优点是可以进行部分复制,如在配置文件中定义replicate-do-table=db1.*,则用这种方法可以只复制db1库而忽略其它复制事件。缺点是由于mysqldump会生成主库转储数据的SQL语句,实际是一种逻辑备份方式所以速度较慢,不适用于大库。

            联机建立复制的另一种可选方案是使用XtraBackup。XtraBackup是Percona公司的开源项目,用以实现类似Innodb官方的热备份工具InnoDB Hot Backup的功能,它支持在线热备份,备份时不影响数据读写。到目前为止,最新的版本为Percona XtraBackup 8.0.6,可以从https://www.percona.com/downloads/下载安装包。XtraBackup有很多功能和优点,例如支持全备、增量备份、部分备份;支持压缩备份;备份不影响数据读写、事务等,但是也有缺陷不足:例如不支持脱机备份、不支持直接备份到磁带设备、不支持Cloud Back,MyISAM的备份也会阻塞。不过这些小瑕疵不影响XtraBackup成为一款流行的MySQL备份工具。另外,注意XtraBackup只支持Linux平台,不支持Windows平台。下面演示用XtraBackup联机搭建主从复制的过程,主库已经建立了用于执行复制的用户repl。

    (1)在主、从库安装XtraBackup

    # 安装依赖包
    yum -y install libev
    
    # 安装XtraBackup
    rpm -ivh percona-xtrabackup-80-8.0.6-1.el7.x86_64.rpm

    (2)配置主库到从库的SSH免密码连接

    # 主库执行
    ssh-keygen    
    ... 一路回车 ...    
    ssh-copy-id 172.16.1.126

    (3)停止从库,并清空从库的数据目录

    # 从库执行
    mysqladmin -u root -p123456 shutdown
    
    # 清空数据目录
    rm -rf /usr/local/mysql/data/*

    (3)备份并传输

    # 主库执行
    xtrabackup -uroot -p123456 --socket=/tmp/mysql.sock --no-lock --backup --compress --stream=xbstream --parallel=4 --target-dir=./ | ssh mysql@172.16.1.126 "xbstream -x -C /usr/local/mysql/data/ --decompress"

            这条命令连接主库,进行并行压缩流式备份,同时将备份通过管道操作符传输到从库,并直接解压缩到从库的数据目录。所有操作一条命令完成,不需要中间落盘生成文件。

    (4)在从库恢复备份

    # 应用日志
    xtrabackup --prepare --target-dir=/usr/local/mysql/data/

    (5)查看二进制日志坐标

    cat /usr/local/mysql/data/xtrabackup_binlog_info

    (6)启动从库

    mysqld_safe --user=mysql &

    (7)登录从库启动复制

    -- 创建主库信息,其中的master_log_file和master_log_pos值来自第(5)步
    change master to
    master_host='172.16.1.125',
    master_port=3306,
    master_user='repl',
    master_password='123456',
    master_log_file='binlog.000011',
    master_log_pos=155;
    
    -- 启动复制
    start slave;
    
    -- 确认复制状态
    show slave status\G

            XtraBackup是物理复制,性能比mysqldump高的多,而且对主库的影响极小,非常适用于从头联机创建高负载、大数据量、全实例从库的场景。

    展开全文
  • 六、主从复制原来这么简单

    千次阅读 2019-08-21 17:54:57
    文章目录什么是redis主从复制主从复制的作用 什么是redis主从复制 总所周知redis之所以火因为它有着读取速度快,可持久化的优点。redis的持久化保证了断电或重启数据不会丢失。但仅仅这样是不够的的,持久化是将数据...

    什么是redis主从复制

    总所周知redis之所以火因为它有着读取速度快,可持久化的优点。redis的持久化保证了断电或重启数据不会丢失。但仅仅这样是不够的的,持久化是将数据定期写入磁盘中,万一哪一天这一台服务器挂掉了,那所有数据依旧会丢失。为了解决这个单点故障问题,所以就有了主从复制。

    主从复制就是 将一台redis服务器的数据自动的复制到其他redis服务器上。 一台服务器出故障的概率很高,但是多台服务器同时出故障的概率就很低了吧。所谓主从主从,当然是有主服务器 (master) 和从服务器 (slave) 啦,一个 master 可以将数据复制到多个 slave,但是特别注意的是,复制是单向的,只能从 master 到 slave 。一个master 可以有多个 slave ,一个slave 也可以有多个slave ,但是一个 slave 只能从属一个master 。如下图这样就是错误的。
    在这里插入图片描述

    主从复制的作用及场景

    1、数据冗余: 主从复制实现了数据的热备份,相当于一份数据在多个服务器上存储了,是持久化之外的一种数据冗余方式,这样做的目的是以空间换取安全性。如果主服务器挂掉了,可以通过从服务将数据恢复。

    2、故障快速修复:当 master 出现问题的时候,可以快速的将一个 slave 切换成 master 继续提供服务 ,保证项目稳定性。

    3、读写分离: 主从复制实现读写分离非常简单,写入的时候只操作 master ,读取的时候只操作 slave ,这样读的话可以多个slave 满足项目高并发的操作。

    4、负载均衡:既然实现了读写分离,当然就能实现负载均衡,多个 slave 承担 数据读取操作 ,从而分担 master 服务器的压力。

    5、高可用的基石:主从复制还是哨兵模式和集群模式能够实施的基础。

    既然主从复制有这些作用,那在实际应用中会用在哪些场景呢?

    1、如果项目对数据安全性稳定性要求较高,就会使用主从复制搭建哨兵模式或者搭建集群。

    2、海量数据读写,需要做读写分离提高防蚊效率,就会用到主从复制

    3、容灾恢复,如果对数据依赖度很高,害怕数据在服务器挂掉后丢失,就可以通过主从复制防止数据丢失。

    主从复制的模式

    一主一从

    这种模式在实际应用中还是比较少见的其实,一主一从主要是实现读写分离和容灾恢复。考虑到成本的问题,所以采用两台服务器,一个redis服务器master 负责读操作,并定期的复制到 另个一服务器slave 。slave 服务器负责读写操作。在项目中配置的时候配置两个redis连接。

    一主多从

    一主多从有可以分为几种,如下图:
    在这里插入图片描述
    这种就是所有的 slave 都从 master 中 进行复制,这样的好处是 配置简单,所有的slave 都只用关系 master 就好了,但是要考虑到其实复制也是会侵占CPU内存的,所有的slave 都从 master 复制,可能增大 master的负荷。

    再来看看下图:
    在这里插入图片描述
    在这里插入图片描述
    这种模式也是一主多从,但是和上面的所有的 slave 都从 master 复制不一样。它是使一个到两个slave 从master 直接复制,其他的slave 从这两个slave 中复制。存在层级关系。这样的好处的降低的master 服务器的负荷,但是这样会导致如果中间某个 slave 挂掉了,那依附于它的所有slave 都不能用了。

    主从复制部署

    这里我使用的是Ubuntu安装的redis,redis怎么安装,可以看我这篇文章
    Redis安装
    安装好redis后,我们启动看看是否正常

    redis-server /usr/local/redis/etc/redis.conf
    redis-cli -a 123456
    

    在这里插入图片描述
    证明是redis是正常启动的,那现在怎么配置主从模式呢?
    按理说主次模式的redis服务器当然是搭建在不同的服务器上,但是我们条件有限,我这里的三台redis服务都搭建在一个服务器上,只是他们监听的端口不同。

    1、修改redis.conf

    我们先修改master的配置文件redis.conf 的一些配置

    bind 0.0.0.0    表示可以被所有机器访问。
    daemonize yes  表示可以后台启动
    port 6379
    pidfile /var/run/redis_6379.pid
    logfile "/usr/local/redis/logs/log_redis.log"
    requirepass 123456
    

    在这里插入图片描述
    在这里插入图片描述

    2、复制两份redis.config

    改好redis.conf 后,将/usr/local/redis/etc/ 目录下的redis.config 复制两份为redis_slave1.conf
    redis_slave2.config
    在这里插入图片描述

    3、修改rdis_slave1.conf

    bind 本机ip      表示只能本机访问
    daemonize yes 
    port 6389
    pidfile /var/run/redis_6389.pid
    logfile "/usr/local/redis/logs/log_redis_slaveof6389.log"
    requirepass 123456 
    ---上面的这些配置和redis.conf中基本一样修改一下就好了,日志是为了方便查看。下面看重点配置
    
    slaveof 192.168.252.53 6379  设置master的ip 和port
    masterauth 123456  设置master的登录密码,就是redis.conf 中配置的requirepass 
    

    在这里插入图片描述
    在这里插入图片描述

    4、修改redis_slave2.conf

    同样的

    bind 192.168.252.53
    daemonize yes 
    port 6399
    pidfile /var/run/redis_6399.pid
    logfile "/usr/local/redis/logs/log_redis_slaveof6399.log"
    requirepass 123456 
    slaveof 192.168.252.53 6379  
    masterauth 123456  
    

    5、启动redis服务

    redis-server /usr/local/redis/etc/redis.conf
    redis-server /usr/local/redis/etc/redis_slaveof1.conf
    redis-server /usr/local/redis/etc/redis_slaveof2.conf
    

    查看一下进程

    ps -ef | grep redis
    

    在这里插入图片描述

    6、启动客户端

    都启动来了,现在连到单个redis服务器看看

    主服务
    redis-cli -a 123456
    从服务
    redis-cli -h 192.168.252.53 -p 6389 -a 123456
    redis-cli -h 192.168.252.53 -p 6399 -a 123456
    

    在这里插入图片描述
    在这里插入图片描述

    7、测试

    启动3台客户端,分别连上master和两个slave
    在master中

    set c 122
    

    在这里插入图片描述

    在slave1和slave2 中分别

    get c
    

    在这里插入图片描述
    在这里插入图片描述
    这样就说明你的主从复制服务已经搭建好啦。

    8、问题记录

    上面的是一个完美的过程搭建的主从复制的例子,但是我相信在实际搭建的时候肯定会出现各种问题,现在记录下我搭建的时候出现的问题吧
    1、权限问题
    在搭建好主从服务后,进入从节点查看info replication发现

    master_link_status:down
    

    表明主从节点并没有建立连接,但是但是为什么一直是down 呢,看了一下日志
    在这里插入图片描述
    提示不能打开rdb这个快照,当然就不能将内容复制到从节点啦,再一看这个路径,是我自己建的路径,第一反应就是权限问题,然后进这个目录一看,发现我的etc目录是root用户的。所以就修改了文件的用户和用户组。在进到etc目录下,就发现了两个快照,再去看的时候发现down变成up 了。

    quellanan@quellanan-Lenovo-G400:/usr/local/redis$ sudo chown quellanan etc
    [sudo] quellanan 的密码: 
    quellanan@quellanan-Lenovo-G400:/usr/local/redis$ sudo chgrp quellanan etc
    quellanan@quellanan-Lenovo-G400:/usr/local/redis$ ll
    总用量 20
    drwxr-xr-x  5 root      root      4096 8月   8 16:50 ./
    drwxr-xr-x 13 root      root      4096 8月   1 16:28 ../
    drwxr-xr-x  2 root      root      4096 8月   1 16:57 bin/
    drwxr-xr-x  2 quellanan quellanan 4096 8月  15 09:50 etc/
    drwxrwxrwx  2 root      root      4096 8月  15 09:16 logs/
    quellanan@quellanan-Lenovo-G400:/usr/local/redis$ cd etc/
    quellanan@quellanan-Lenovo-G400:/usr/local/redis/etc$ ll
    总用量 196
    drwxr-xr-x 2 quellanan quellanan  4096 8月  15 09:50 ./
    drwxr-xr-x 5 root      root       4096 8月   8 16:50 ../
    -rw-r--r-- 1 quellanan quellanan   188 8月  15 09:50 dump6389.rdb
    -rw-rw-r-- 1 quellanan quellanan   188 8月  15 09:50 dump.rdb
    -rw-rw-r-- 1 quellanan quellanan 58810 8月  14 09:18 redis.conf
    -rw-rw-r-- 1 quellanan quellanan 58882 8月  14 09:16 redis_slaveof1.conf
    -rw-r--r-- 1 quellanan quellanan 58882 8月  15 09:11 redis_slaveof2.conf
    

    权限问题我感觉还是挺大的,因为我们一般都是远程到服务器上操作,所以用户权限很多都需要注意。

    2、还有一个也是发现master_link_status:down,但是并不是用户权限问题导致的,查看一下防火墙的状态,将防火墙关闭了试试。

    ufw status  查看防火墙状态
    ufw enable 开启防火墙
    ufw disable 关闭防火墙
    

    3、在主节点插入数据的时候出现问题

    127.0.0.1:6379> set a 1qaz
    (error) MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.
    

    执行这个

    127.0.0.1:6379>  config set stop-writes-on-bgsave-error no
    OK
    127.0.0.1:6379> set a 123456
    OK
    127.0.0.1:6379> get a
    "123456"
    

    主从复制的原理

    上面以及搭建了一个主从复制的样例,是一主两从的,那是怎么redis是怎么具体实现的呢?
    在将原理之前,先来看看主从复制的几个概念。启动master后

    127.0.0.1:6379> info server
    # Server
    redis_version:4.0.9
    redis_git_sha1:00000000
    redis_git_dirty:0
    redis_build_id:514e9a11b2a67dfc
    redis_mode:standalone
    os:Linux 5.0.0-23-generic x86_64
    arch_bits:64
    multiplexing_api:epoll
    atomicvar_api:atomic-builtin
    gcc_version:7.4.0
    process_id:19688
    run_id:136de716105e54294144003a881ba29cdfbccfb2
    tcp_port:6379
    uptime_in_seconds:4515
    uptime_in_days:0
    hz:10
    lru_clock:5556386
    executable:/usr/local/redis/etc/redis-server
    config_file:/usr/local/redis/etc/redis.conf
    

    这个run_id 就是redis服务的唯一标识,重启redis服务号,这个run_id 会改变,多个redis客户端连接到同一个服务端,其run_id 是一样的,也就是说run_id 指的是服务端的id

    127.0.0.1:6379> info replication
    # Replication
    role:master
    connected_slaves:2
    slave0:ip=192.168.252.53,port=6389,state=online,offset=5541,lag=1
    slave1:ip=192.168.252.53,port=6399,state=online,offset=5541,lag=0
    master_replid:f0c89aa8040dfe869de82ee623a1212240456d76
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:5541
    second_repl_offset:-1
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:1
    repl_backlog_histlen:5541
    

    其中repl_backlog_size 复制缓存区大小,默认大小为1M,如果mater_repl_offset在这个范围内,就看是部分复制,否则就开始全量复制。

    全量复制

    先看下图,图画的不是很好见谅
    在这里插入图片描述
    1、首先slave会向 master发送一个 psync 命令,因为是第一次,所以不知道run_id 和offset,所以传过来-1表示全量复制
    2、 master在接收到psync 后,将run_id 和offset 发送给slave,slave存储起来
    3、master进行bgsave生成rdb ,并将rdb 文件发送给slave
    4、在bgsave 和send rdb 的过程中可能会产生write 的数据,那么就会把数据存到repl_back_buffer 中 并将buffer发送给slave .
    5、slave 会清空就数据,然后加载rdb和buffer 将数据存储起来。

    部分复制

    在这里插入图片描述
    既然是部分复制,那就是slave已经知道了master的run_id 和offset ,所以发送psync 命令带上这两个参数,master 就知道这是部分复制,然后通过偏移量将需要复制的数据发送给slave。

    总结

    主从复制的过程中既用到了全量复制也用到了部分复制,二者是相互配合使用的。看下面的流程图:
    在这里插入图片描述
    还有一点需要注意的是,如果master 重启了,那么它的run_id发生了改变,那么依赖它的slave都会进行一次全量复制后在进行部分复制。

    哨兵模式

    哨兵模式介绍

    在将哨兵模式之前,先来说说主从复制的缺点吧。
    如果主节点出了问题,那么主节点不在提供服务,需要手动的将从节点切换成主节点。

    所以这个时候哨兵模式就出现啦,当主节出现故障时,Redis Sentinel会自动的发现主节点的故障并转移,并通知应用方,实现高可用。

    下面是Redis官方文档对于哨兵功能的描述:
    1、监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
    2、自动故障转移(Automatic failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
    3、配置提供者(Configuration provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
    4、通知(Notification):哨兵可以将故障转移的结果发送给客户端。

    哨兵模式的结构拓扑图大概如下,也是照书上画的哈哈
    在这里插入图片描述
    大意就是
    1、每一个哨兵节点会监听其他的哨兵节点以及master 和所有的slave
    2、所有哨兵节点会定期的ping 主节点,监控是否正常
    3、如果认为主节点出现故障的哨兵数量达到阙zhi,就判定主节点死掉,主节点就会客观下线
    4、主节点客观下线后,哨兵节点通过选举模式在 slave 中选择出一个升级为主节点
    5、其他的salve 指向新的主节点
    6、原来的master 变成 slave ,并且指向新的主节点

    引用官方哨兵模式处理流程

    每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
    ●如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel(哨兵)进程标记为主观下线(SDOWN)。
    ●如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态。
    ●当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)。
    ●在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
    ●当Master主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
    ●若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

    哨兵模式部署

    1、首先到我们redis安装目录下,发现有sentinel.conf ,我们把它移到我们自己定义的文件夹中,和redis.conf 放在一起。

    mv sentinel.conf /usr/local/redis/etc/
    

    在这里插入图片描述
    2、修改sentinel.conf文件

    port 26379
    dir /usr/local/redis/etc  
    这里默认的是“/tmp”,如果你没有这个目录的权限就需要换啦,换一个你有权限的目录,不然后果自负哈哈,我就是从坑里爬起来的
    sentinel monitor mymaster 192.168.252.53 6379 2
    sentinel auth-pass mymaster 123456
    设置监控的主节点,2是一个阈值,代表有两台或两台以上哨兵判断主节点redis不通的话就认定这个节点有问题,实行故障转移。
    
    daemonize yes 后台启动
    logfile "/usr/local/redis/logs/redis_sentinel-26379.log" 加上日志 ,不加也无所谓
    

    在这里插入图片描述
    注意,这个auth-pass 要放在放在monitor 下面,不然会报错
    在这里插入图片描述
    配上一些参数说明:
    在这里插入图片描述
    3、将sentinel.conf 复制两份,分别为sentinel26389.conf,sentinel26399.conf并修改这个文件中的prot 和logfile

    port 26389
    logfile "/usr/local/redis/logs/redis_sentinel-26389.log" 
    
    port 26399
    logfile "/usr/local/redis/logs/redis_sentinel-26399.log" 
    

    4、启动哨兵

    redis-sentinel /usr/local/redis/etc/sentinel.conf
    redis-sentinel /usr/local/redis/etc/sentinel26389.conf
    redis-sentinel /usr/local/redis/etc/sentinel26399.conf
    

    查看一下,三个哨兵都已经启动了。
    在这里插入图片描述

    5、验证
    先看看启动日志,,下图所示表明监控了三个节点。如果没有监控到这三个节点,证明没有配置成功。
    在这里插入图片描述
    在这里插入图片描述
    没有配置成功的原因可能是防火墙导致的,关闭调防火墙。还有就是如果redis-server重启过,那在sentinel.conf中生成的pid 和最后的运行添加的几行需要删除掉,下图这些。然后重新运行。
    在这里插入图片描述
    在这里插入图片描述

    好的,日志看的没有问题,如果不想看日志,我们来这样验证,我们已经启动了三个redis服务,三个哨兵。我们现在把 master 杀死看看是什么情况。

    kill -9 19688
    

    看日志,三个哨兵的监控日志基本上是一样的,下图贴出三个哨兵的日志,我们就看一下第一个哨兵的日志分析一下。
    在这里插入图片描述
    看上图,启动哨兵的时候,监控到了master 6379 和两个slave 6389和6399,以及另外两个哨兵,26389和26399.
    然后sdown master mymaster 192.168.252.53 6379 表示刚刚我们杀死的master服务。
    这个时候有一个哨兵表示其主观下线,等到odown 达到我们设置的2时,表明有两个哨兵表示其主观下线,那么就认为6379这个master 已经客观下线。
    然后通过选举,选取26399 这个哨兵为这三个哨兵的领导者(leader)。
    23699 这个leader 在salve中选择6399转为 master
    将6389 这个slave 指向新的 master 6399
    这个时候重启6379 这个redis服务
    将6379 这个slave指向新的master 6399
    在这里插入图片描述
    在这里插入图片描述
    好,下面我们来看看界面
    在这里插入图片描述
    在这里插入图片描述
    可以看到master已经切换到6399 服务了,现在我们再切换一下,看下面这张图应该很清晰啦
    在这里插入图片描述

    到此为止,哨兵模式就搭建好啦,当 master 挂掉时,会自动的将一个slave 升级成 master 并将其他的 slave 指向新的master ,从新把原来的master启动后,会变成slave 执行新的master 。

    总结

    写到这,其实还有一部分没有写完,但是感觉实在是太长了,就先写这么多吧,还差一个怎么在项目中使用搭建的哨兵模式,也就是集群模式,怎么做到读写分离,实现高可用的。
    因为前面这些讲的都是在redis数据库上直接操作,那现在部署好了,怎么在项目代码中使用呢,所以下篇接着将在项目中怎么使用redis集群。喜欢的小伙伴可以持续关注啦。

    好了,上面都是题外话,下面总结一下这篇文章吧。
    1、主从复制是什么以及作用。
    2、怎样部署一个主从复制(案例一主两从)
    3、怎样部署哨兵模式(三哨兵)
    其实我感觉如果你通过这篇文章学会了这三点,就够了,其他的看看当了解。

    谢谢大家

    展开全文
  • 在项目开发中我们会常常遇到tableView 的cell分割线显示不全,左边会空出一截像素,更有甚者想改变系统的分割线,并且只要上下分割线的一个等等需求,今天重点解决以上需求,仅供参考:
  • 使用MHA实现MySQL主从复制高可用

    万次阅读 多人点赞 2018-07-31 16:37:10
    1. 配置主从复制 2. 安装Perl等依赖模块 3. 配置SSH登录无密码验证 4. 安装MHA Node 5. 安装MHA Manager 6. 配置MHA 7. 创建相关脚本 四、检查MHA配置 1. 检查SSH配置 2. 检查整个复制环境状况 3. 检查MHA...
  • 超硬核!数据结构学霸笔记,考试面试吹牛靠它

    万次阅读 多人点赞 2021-03-26 11:11:21
    请看代码 def f0(n): if n==1 or n==2: return 1 return f(n-1)+f(n-2) 分析一下,为什么说递归效率很低呢?咱们来试着运行一下知道了: 比如想求f(10),计算机里怎么运行的? 每次要计算的函数量都是指数型增长,...
  • 面试问:Kafka为什么速度那么快?

    千次阅读 多人点赞 2019-06-02 09:30:00
    下面从数据写入和读取两方面分析,为什么Kafka速度这么快。  一、写入数据 Kafka会把收到的消息都写入到硬盘中,它绝对不会丢失数据。为了优化写入速度Kafka采用了两个技术, 顺序写入和MMFile 。  1、...
  • JS 实现复制粘贴功能

    万次阅读 2017-05-24 16:11:27
    JS 实现复制粘贴功能目前没有做过多的测试,只是测试了几个手机,介绍:支持情况(1)移动端: chrome(版本 58.0.3029.96 (64-bit))、 猎豹(V6.0.114.14559)、 QQ浏览器(Chromium53.022785.104 \ IE11.0.9600....
  • Redis主从复制

    千次阅读 2018-12-07 17:37:17
    主从复制什么主从复制,就是主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写主,Slave以读主 作用读写分离,性能扩展 容灾快速恢复 当一个从数据库启动后,会向主数据库发送...
  • 前言如果说你是一名hadoop集群的日常维护者,那么你肯定经历过很多的节点上下线工作.例如,随着业务规模的高速扩张,集群的资源渐渐的不够使用的时候,一般正常的做法是通过增加机器来达到线性扩展的效果.当然,当这些...
  • 小米手机ADB删除系统应用去广告

    万次阅读 多人点赞 2020-01-25 16:29:22
    小米手机ADB删除系统应用去广告前言1、准备2、进入开发者模式3、连接手机与电脑4...删除APP后,手机不会再显示该APP,也就没有了应用自启,广告等问题。 1、准备 小米手机、Windows电脑、数据线、ADB工具包和报名查...
  • 而且有时候我们仅仅是为了测试某个功能才引入了某个包,但是之后不用了,import语句却没有自动删除,在idea中展现出一行行“波浪线”提醒,贼难受。idea提供了非常友好的自动化功能。如何配置呢?解释:Insert ...
  • 以NATS主线的CloudFoundry原理

    万次阅读 2012-10-31 17:03:28
    本文将试图以CloudFoundry中的消息组件NATS主要线索,以在CF中广泛使用的并发和网络编程框架EventMachine侧重,来串联整个CF主线功能的工作原理,力求能用简单直接的方式描述出较多的架构细节和系统设计。...
  • 顽固文件删除方法

    千次阅读 2009-07-16 17:41:00
    ; color: #800080; font-family: ...">2009年顽固文件删除方法大全 很多时候,我们在删除文件或文件夹时,会提示说当前无法被删除。多数情况下都是正在被其它进程或程序调用。而麻烦的是一时之间却无法找到哪些
  • 蛋疼的QTextEdit删除/右键菜单

    千次阅读 2017-03-09 20:00:29
    对于一般的文本编辑,这个类提供了一系列槽和函数来实现诸如复制,剪切,undo,redo等操作,但缺有2个灰常蛋疼的地方 首先 :删除/delete 对于这样一个很基本的文本操作,QTextEdit缺没有提供对应的槽或者函数...
  • ####### 1, postgresql移步同步 and 主备切换 ...2, postgresql 同步 and 主备切换 ...a, 如果开始的初始话数据库, 不需要删除 /data/目录下的文件. 如果不是,最好删除. b, 备份复制: pg_basebackup -F p ...
  • 用vi删除某一列

    千次阅读 2018-08-06 17:47:28
    vi 中如何一次删除多列?  如:  123aaaaa  !@#aaaaa  ABCaaaaa  +-/aaaaa  在 vi 中如何一次删除前 3 列呢? 最简单的方法: 1. 先ctrl-v进行visual block模式 2. 移动光标,按列选择你要删除的列 3. 然后按x...
  • 现在有一个电路图PCB,其中的一个元件的中间接地焊盘引脚要接地,但是原理图上却是没有的,现在造成必须要在ECO模式下增加地和这个焊盘的走线,这样我觉得很不好,比如,我现在要改变布局,而这些ECO的线都将要被...
  • 这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大 2.弱一致性 这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也...
  • 人的一生中只有七次机会,平均每七年拥有一次,大概在25岁到75岁,...其实人生的机会很快会过去。能真正抓住机会的人,首先靠的是自己个人的能力;其次靠的是环境。 一个人如果素质不好,行为不好,那么即使你再...
  • C#网页复制

    千次阅读 2010-04-04 15:52:00
    这样不会为了让每个用户都有一个有效的终端服务器 (TS) CAL 而根据“最终用户许可协议”(EULA) 的要求删除管理员。在没有使用“设备 CAL”的情况下,如果不是每个用户都有一个“用户 CAL”,会违反 EULA。 ...
  • 不过公众号可以说是不支持修改文章,因为我决定每两三个月整理一次,会非常详细着按照类别整理出来哦,并且也给出了目录哦。大家记得多看看哦,好多文章都是面试中常问滴 文章目录一、经验/经历/所...
  • PCB 布线心得

    千次阅读 2016-03-03 09:13:45
    一、电路版设计的先期工作 1、利用原理图设计工具绘制原理图,并且生成对应...2、手工更改网络表将一些元件的固定用脚等原理图上没有的焊盘定义到与它相通的网络上,没任何物理连接的可定义到地或保护地等。将一些
  • 基于 [博客园-《叩响C#之门》-关于RichTextBox修改字体大小的研究] ...把功能弄成改变字体大小,粗体、斜体、下划、删除线,字符上色。 选中的字符同时具备 粗体、斜体、下划、删除线就要用到 FontStyle。 有两种方式
  • 使用ADB指令删除Android安装包

    千次阅读 2015-09-09 23:01:16
     具体是打开~/.bash_profile文件(如果没有此文件也可以自行添加), 在里面加入一行:  export PATH=${PATH}:<你的sdk目录>/tools  然后可以使用adb命令了.  嫌安装麻烦的同学其实也可以省去上面安装步骤, ...
  • iphone恢复删除的文件-不要钱!

    千次阅读 2017-11-01 19:55:02
    按一个操作说的,把我的iphone上的都移动到icloud上,是移动,所以我的iphone的备忘录里没内容了,然后我一手抖把icloud的给删除了,再一手抖,把“最近删除”里的也给删除了,(不要问我手啥这么抖,no
  • 删除正在被读写的文件 删除正在运行的可执行文件 删除正在使用的动态链接库 结语缘起在linux 系统中上线时经常会遇到需要替换原有可执行程序的操作,我通常的做法是: 删除原有可执行文件。例如:rm a.out 以相同的...
  • 修改TrustedInstaller权限文件(无法删除文件)
  • 如何在Ubuntu 16.04添加和删除用户

    千次阅读 2017-01-04 09:25:05
    你应该知道如何在一个新鲜的Linux服务器上做最基本的任务之一是添加和删除用户。 当您创建一个新的系统,你常常(如DigitalOceanDroplet)只给默认root帐户。 虽然运行在root用户提供了大量的功能和灵活性,这也...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 98,062
精华内容 39,224
关键字:

删除线为什么复制就没有了