可能是把Docker的概念讲的*清楚的一篇文章

转载自:https://juejin.im/post/5b260ec26fb9a00e8e4b031a

本文只是对Docker的概念做了较为详细的介绍,并不涉及一些像Docker环境的安装以及Docker的一些常见操作和命令。

阅读本文大概需要15分钟,通过阅读本文你将知道一下概念:

  • 容器
  • 什么是Docker?
  • Docker思想、特点
  • Docker容器主要解决什么问题
  • 容器 VS 虚拟机
  • Docker基本概念: 镜像(Image),容器(Container),仓库(Repository)

Docker 是世界*的软件容器平台,所以想要搞懂Docker的概念我们必须先从容器开始说起。

一 先从认识容器开始

1.1 什么是容器?

先来看看容器较为官方的解释

一句话概括容器:容器就是将软件打包成标准化单元,以用于开发、交付和部署。

  • 容器镜像是轻量的、可执行的独立软件包 ,包含软件运行所需的所有内容:代码、运行时环境、系统工具、系统库和设置。
  • 容器化软件适用于基于Linux和Windows的应用,在任何环境中都能够始终如一地运行。
  • 容器赋予了软件独立性 ,使其免受外在环境差异(例如,开发和预演环境的差异)的影响,从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。

再来看看容器较为通俗的解释

如果需要通俗的描述容器的话,我觉得容器就是一个存放东西的地方,就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。

 

认识容器

 

 

1.2 图解物理机、虚拟机与容器

关于虚拟机与容器的对比在后面会详细介绍到,这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解。

物理机

物理机

 

 

虚拟机:

 

虚拟机

 

 

容器:

 

容器

 

 

通过上面这三张抽象图,我们可以大概可以通过类比概括出: 容器虚拟化的是操作系统而不是硬件,容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。


相信通过上面的解释大家对于容器这个既陌生又熟悉的概念有了一个初步的认识,下面我们就来谈谈Docker的一些概念。

二 再来谈谈Docker的一些概念

 

Docker的一些概念

 

 

2.1 什么是Docker?

说实话关于Docker是什么并太好说,下面我通过四点向你说明Docker到底是个什么东西。

  • Docker 是世界*的软件容器平台。
  • Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核 的cgroup,namespace,以及AUFS类的UnionFS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。 由于隔离的进程独立于宿主和其它的隔离的进 程,因此也称其为容器。Docke*初实现是基于 LXC.
  • Docker 能够自动执行重复性任务,例如搭建和配置开发环境,从而解放了开发人员以便他们专注在真正重要的事情上:构建杰出的软件。
  • 用户可以方便地创建和使用容器,把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改,就像管理普通的代码一样。

 

什么是Docker

 

 

2.2 Docker思想

  • 集装箱
  • 标准化: ①运输方式 ② 存储方式 ③ API接口
  • 隔离

2.3 Docker容器的特点

  • 轻量

    在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核;它们能够迅速启动,只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的,并共享一些公共文件。这样就能尽量降低磁盘用量,并能更快地下载镜像。

  • 标准

    Docker 容器基于开放式标准,能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。

  • 安全

    Docker 赋予应用的隔离性不仅限于彼此隔离,还独立于底层的基础设施。Docker 默认提供*强的隔离,因此应用出现问题,也只是单个容器的问题,而不会波及到整台机器。

2.4 为什么要用Docker

  • Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊” 这类问题;——一致的运行环境
  • 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间
  • 避免公用的服务器,资源会容易受到其他用户的影响。——隔离性
  • 善于处理集中爆发的服务器使用压力;——弹性伸缩,快速扩展
  • 可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便
  • 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署

每当说起容器,我们不得不将其与虚拟机做一个比较。就我而言,对于两者无所谓谁会取代谁,而是两者可以和谐共存。

三 容器 VS 虚拟机

简单来说: 容器和虚拟机具有相似的资源隔离和分配优势,但功能有所不同,因为容器虚拟化的是操作系统,而不是硬件,因此容器更容易移植,效率也更高。

3.1 两者对比图

传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便.

 

容器 VS 虚拟机

 

 

3.2 容器与虚拟机 (VM) 总结

 

容器与虚拟机 (VM) 总结

 

 

  • 容器是一个应用层抽象,用于将代码和依赖资源打包在一起。 多个容器可以在同一台机器上运行,共享操作系统内核,但各自作为独立的进程在用户空间中运行 。与虚拟机相比, 容器占用的空间较少(容器镜像大小通常只有几十兆),瞬间就能完成启动 。
  • 虚拟机 (VM) 是一个物理硬件层抽象,用于将一台服务器变成多台服务器。 管理程序允许多个 VM 在一台机器上运行。每个VM都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源,因此 占用大量空间 。而且 VM 启动也十分缓慢 。

通过Docker官网,我们知道了这么多Docker的优势,但是大家也没有必要完全否定虚拟机技术,因为两者有不同的使用场景。虚拟机更擅长于彻底隔离整个运行环境。例如,云服务提供商通常采用虚拟机技术隔离不同的用户。而 Docker通常用于隔离不同的应用 ,例如前端,后端以及数据库。

3.3 容器与虚拟机 (VM)两者是可以共存的

就我而言,对于两者无所谓谁会取代谁,而是两者可以和谐共存。

 

两者是可以共存的

 

 


Docker中非常重要的三个基本概念,理解了这三个概念,就理解了 Docker 的整个生命周期。

四 Docker基本概念

Docker 包括三个基本概念

  • 镜像(Image)
  • 容器(Container)
  • 仓库(Repository)

理解了这三个概念,就理解了 Docker 的整个生命周期

 

Docker 包括三个基本概念

 

 

4.1 镜像(Image)——一个特殊的文件系统

操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而Docker 镜像(Image),就相当于是一个 root 文件系统。

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。 镜像不包含任何动态数据,其内容在构建之后也不会被改变。

Docker 设计时,就充分利用 Union FS的技术,将其设计为 分层存储的架构 。 镜像实际是由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。 比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在*终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

4.2 容器(Container)——镜像运行时的实体

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等 。

容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储,容器也是如此。

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker *佳实践的要求,容器不应该向其存储层内写入任何数据 ,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此, 使用数据卷后,容器可以随意删除、重新 run ,数据却不会丢失。

4.3 仓库(Repository)——集中存放镜像文件的地方

镜像构建完成后,可以很容易的在当前宿主上运行,但是, 如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry就是这样的服务。

一个 Docker Registry中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。所以说:镜像仓库是Docker用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本 。我们可以通过<仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签.。

这里补充一下Docker Registry 公开服务和私有 Docker Registry的概念:

Docker Registry 公开服务 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。

*常使用的 Registry 公开服务是官方的 Docker Hub ,这也是默认的 Registry,并拥有大量的高质量的官方镜像,网址为:hub.docker.com/ 。在国内访问Docker Hub 可能会比较慢国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 时速云镜像库网易云镜像服务DaoCloud 镜像市场阿里云镜像库等。

除了使用公开服务外,用户还可以在 本地搭建私有 Docker Registry 。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。


Docker的概念基本上已经讲完,*后我们谈谈:Build, Ship, and Run。

五 *后谈谈:Build, Ship, and Run

如果你搜索Docker官网,会发现如下的字样:“Docker – Build, Ship, and Run Any App, Anywhere”。那么Build, Ship, and Run到底是在干什么呢?

 

build ship run

 

 

  • Build(构建镜像) : 镜像就像是集装箱包括文件以及运行环境等等资源。
  • Ship(运输镜像) :主机和仓库间运输,这里的仓库就像是超级码头一样。
  • Run (运行镜像) :运行的镜像就是一个容器,容器就是运行程序的地方。

Docker 运行过程也就是去仓库把镜像拉到本地,然后用一条命令把镜像运行起来变成容器。所以,我们也常常将Docker称为码头工人或码头装卸工,这和Docker的中文翻译搬运工人如出一辙。

六 总结

本文主要把Docker中的一些常见概念做了详细的阐述,但是并不涉及Docker的安装、镜像的使用、容器的操作等内容。这部分东西,希望读者自己可以通过阅读书籍与官方文档的形式掌握。如果觉得官方文档阅读起来很费力的话,这里推荐一本书籍《Docker技术入门与实战第二版》。

超级有用的15个mysqlbinlog命令

转载自:https://www.cnblogs.com/zhengchunyuan/p/8175975.html

在MySQL或MariaDB中,任意时间对数据库所做的修改,都会被记录到日志文件中。例如,当你添加了一个新的表,或者更新了一条数据,这些事件都会被存储到二进制日志文件中。二进制日志文件在MySQL主从复合中是非常有用的,主服务器会发送其数据到远程服务器中。

 

当你需要恢复MySQL时,也会需要使用到二进制日志文件。

mysqlbinlog 命令,以用户可视的方式展示出二进制日志中的内容。同时,也可以将其中的内容读取出来,供其他MySQL实用程序使用。

在此示例中,我们将会涉及以下内容:

  • 获取当前二进制日志列表
  • mysqlbinlog默认行为
  • 获取特定数据库条目
  • 禁止恢复过程产生日志
  • 在输出中控制base-64 BINLOG
  • mysqlbinlog输出调试信息
  • 跳过前N个条目
  • 保存输出到文件
  • 从一个特定位置提取条目
  • 将条目截止到一个特定的位置
  • 刷新日志以清除Binlog输出
  • 在输出中只显示语句
  • 查看特定开始时间的条目
  • 查看特定结束时间的条目
  • 从远程服务器获取二进制日志

1 获取当前二进制日志列表

在mysql中执行以下命令,即可查看二进制日志文件的列表。

 

1

2

3

4

5

6

7

8

mysql> SHOW BINARY LOGS;

+———————-+———-+

| Log_name              | File_size |

+————————–+————+

| mysqld-bin.000001 |     15740 |

| mysqld-bin.000002 |       3319 |

..

..

如果熊没有开启此功能,则会显示:

 

1

2

mysql> SHOW BINARY LOGS;

ERROR 1381 (HY000): You are not using binary logging

二进制日志文件默认会存放在 /var/lib/mysql 目录下

 

1

2

3

4

5

$ ls -l /var/lib/mysql/

-rw-rw—-. 1 mysql mysql 15740 Aug 28 14:57 mysqld-bin.000001

-rw-rw—-. 1 mysql mysql  3319 Aug 28 14:57 mysqld-bin.000002

..

..

 

2 mysqlbinlog 默认行为

下面将以一种用户友好的格式显示指定的二进制日志文件(例如:mysqld.000001)的内容。

 

1 $ mysqlbinlog mysqld-bin.000001

mysqlbinlog默认会显示为以下内容:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

/*!40019 SET @@session.max_insert_delayed_threads=0*/;/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;DELIMITER /*!*/;# at 4#170726 14:57:37 server id 1  end_log_pos 106   Start: binlog v 4, server v 5.1.73-log created 170726 14:57:37 at startup# Warning: this binlog is either in use or was not closed properly.

ROLLBACK/*!*/;

BINLOG ‘ IeZ4WQ8BAAAAZgAAAGoAAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC ‘/*!*/;

# at 106

#170726 14:59:31 server id 1  end_log_pos 182   Query   thread_id=2     exec_time=0     error_code=0

SET TIMESTAMP=1501095571/*!*/;

SET @@session.pseudo_thread_id=2/*!*/;

SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=1, @@session.unique_checks=1, @@session.autocommit=1/*!*/;

SET @@session.sql_mode=0/*!*/;

SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;

/*!\C latin1 *//*!*/;

SET @@session.character_set_client=8,@@session.collation_connection=8,@@session.collation_server=8/*!*/;

..

..

..

# at 14191

#170726 15:20:38 server id 1  end_log_pos 14311         Query   thread_id=4     exec_time=0     error_code=0SET TIMESTAMP=1501096838/*!*/;

insert into salary(name,dept) values(‘Ritu’, ‘Accounting’)/*!*/;

DELIMITER ;

# End of log file

ROLLBACK /* added by mysqlbinlog */;

/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*

上面的命令将会显示出,在该系统上数据库发生的所有改变事件。

3 获取特定数据库条目

默认情况下,mysqlbinlog会显示所有的内容,太过于杂乱。使用 -d 选项,可以指定一个数据库名称,将只显示在该数据库上所发生的事件。

 

 

1 $ mysqlbinlog -d crm mysqld-bin.000001 > crm-events.txt

也可以使用 –database 命令,效果相同。

 

1 $ mysqlbinlog -database crm mysqld-bin.000001 > crm-events.txt

 

4 禁止恢复过程产生日志

在使用二进制日志文件进行数据库恢复时,该过程中也会产生日志文件,就会进入一个循环状态,继续恢复该过程中的数据。因此,当使用mysqlbinlog命令时,要禁用二进制日志,请使用下面所示的-D选项:

 

1 $ mysqlbinlog -D mysqld-bin.000001

也可以使用 –disable-log-bin 命令,效果相同。

 

1 $ mysqlbinlog –disable-log-bin mysqld-bin.000001

备注:在输出中,当指定-D选项时,将看到输出中的第二行。也就是SQL_LOG_BIN=0

 

1

2

3

/*!40019 SET @@session.max_insert_delayed_threads=0*/;

/*!32316 SET @OLD_SQL_LOG_BIN=@@SQL_LOG_BIN, SQL_LOG_BIN=0*/;

/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;

 

当使用-to-last-log选项时,这个选项也会有所帮助。另外,请记住,该命令需要root权限来执行。

5 在输出中控制base-64 BINLOG

使用base64-output选项,可以控制输出语句何时是输出base64编码的BINLOG语句。以下是base64输出设置的可能值:

 

  • never
  • always
  • decode-rows
  • auto(默认)

never:当指定如下所示的“never”时,它将在输出中显示base64编码的BINLOG语句。

 

1 $ mysqlbinlog –base64-output=never mysqld-bin.000001

将不会有任何与下面类似的行,它具有base64编码的BINLOG。

 

1 BINLOG ‘ IeZ4WQ8BAAAAZgAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC

always:当指定“always”选项时,只要有可能,它将只显示BINLOG项。因此,只有在专门调试一些问题时才使用它。

 

1 $ mysqlbinlog –base64-output=always mysqld-bin.000001

下面是“always”的输出,它只显示了BINLOG项。

 

1

2

3

4

5

6

7

8

9

10

BINLOG ‘ IeZ4WQ8BAAAAZgAAAGoAAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC ‘/*!*/;

# at 106

#170726 14:59:31 server id 1  end_log_pos 182

BINLOG ‘ k+Z4WQIBAAAATAAAALYAAAAIAAIAAAAAAAAADAAAGgAAAEAAAAEAAAAAAAAAAAYDc3RkBAgACAAI AHRoZWdlZWtzdHVmZgBCRUdJTg== ‘/*!*/;

# at 182

#170726 14:59:30 server id 1  end_log_pos 291

BINLOG ‘ kuZ4WQIBAAAAbQAAACMBAAAAAAIAAAAAAAAADAAAGgAAAEAAAAEAAAAAAAAAAAYDc3RkBAgACAAI AHRoZWdlZWtzdHVmZgBJTlNFUlQgSU5UTyB0IFZBTFVFUygxLCAnYXBwbGUnLCBOVUxMKQ== ‘/*!*/;

# at 291

#170726 14:59:30 server id 1  end_log_pos 422

BINLOG ‘ kuZ4WQIBAAAAgwAAAKYBAAAAAAIAAAAAAAAADAAAGgAAAEAAAAEAAAAAAAAAAAYDc3RkBAgACAAI AHRoZWdlZWtzdHVmZgBVUERBVEUgdCBTRVQgbmFtZSA9ICdwZWFyJywgZGF0ZSA9ICcyMDA5LTAx LTAxJyBXSEVSRSBpZCA9IDE=

decode-rows:这个选项将把基于行的事件解码成一个SQL语句,特别是当指定-verbose选项时,如下所示。

 

1 $ mysqlbinlog –base64-output=decode-rows –verbose mysqld-bin.000001

auto:这是默认选项。当没有指定任何base64解码选项时,它将使用auto。在这种情况下,mysqlbinlog将仅为某些事件类型打印BINLOG项,例如基于行的事件和格式描述事件。

 

1

2

$ mysqlbinlog –base64-output=auto mysqld-bin.000001

$ mysqlbinlog mysqld-bin.000001

 

6 mysqlbinlog输出调试信息

下面的调试选项,在完成处理给定的二进制日志文件之后,将检查文件打开和内存使用。

 

1 $ mysqlbinlog –debug-check mysqld-bin.000001

如下所示,在完成处理给定的二进制日志文件之后,下面的调试信息选项将显示额外的调试信息。

 

1

2

3

4

5

6

$ mysqlbinlog –debug-info mysqld-bin.000001 > /tmp/m.di

User time 0.00, System time 0.00

Maximum resident set size 2848, Integral resident set size 0

Non-physical pagefaults 863, Physical pagefaults 0, Swaps 0

Blocks in 0 out 48, Messages in 0 out 0, Signals 0

Voluntary context switches 1, Involuntary context switches 2

 

7 跳过前N个条目

除了读取整个mysql二进制日志文件外,也可以通过指定偏移量来读取它的特定部分。可以使用 -o 选项。o代表偏移。

 

下面将跳过指定的mysql bin日志中的前10个条目。

 

1 $ mysqlbinlog -o 10 mysqld-bin.000001

为了确保它正常工作,给偏移量提供一个巨大的数字,将看不到任何条目。下面的内容将从日志中跳过10,000个条目(事件)。

 

1

2

3

4

5

6

$ mysqlbinlog -o 10000 mysqld-bin.000001

/*!40019 SET @@session.max_insert_delayed_threads=0*/;

/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/; .. ..

# End of log file

ROLLBACK /* added by mysqlbinlog */;

/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;

在本例中,由于这个特定的日志文件没有10,000个条目,所以在输出中没有显示任何数据库事件。

8 保存输出到文件

也可以使用简单的Linux重定向命令,将输出存储到一个文件中,如下所示。

 

1 $ mysqlbinlog mysqld-bin.000001 > output.log

或者也可以使用 -r (结果文件)选项,如下所示,将输出存储到一个文件中。

 

1 $ mysqlbinlog -r output.log mysqld-bin.000001

备注:还可以使用 -server-id 指定mysql服务器,确保是由给定服务器id的mysql服务器所生成的日志。

 

1 $ mysqlbinlog –server-id=1 -r output.log mysqld-bin.000001

 

9 从一个特定位置提取条目

通常在mysql二进制日志文件中,你将看到如下所示的位置号。下面是mysqlbinlog的部分输出,你可以看到“15028”是一个位置编号。

 

1

2

3

4

5

6

7

8

#170726 15:38:14 server id 1  end_log_pos 15028         Query   thread_id=5     exec_time=0     error_code=0

SET TIMESTAMP=1501097894/*!*/;

insert into salary values(400,’Nisha’,’Marketing’,9500)

/*!*/;

# at 15028

#170726 15:38:14 server id 1  end_log_pos 15146         Query   thread_id=5     exec_time=0     error_code=0

SET TIMESTAMP=1501097894/*!*/;

insert into salary values(500,’Randy’,’Technology’,6000)

下面的命令将从位置编号为15028的二进制日志条目处开始读取。

 

1 $ mysqlbinlog -j 15028 mysqld-bin.000001 > from-15028.out

当在命令行中指定多个二进制日志文件时,开始位置选项将仅应用于给定列表中的*个二进制日志文件。还可以使用 -H 选项来获得给定的二进制日志文件的十六进制转储,如下所示。

 

 

1 $ mysqlbinlog -H mysqld-bin.000001 > binlog-hex-dump.out

 

10 将条目截止到一个特定的位置

就像前面的例子一样,你也可以从mysql二进制日志中截止到一个特定位置的条目,如下所示。

 

1 $ mysqlbinlog –stop-position=15028 mysqld-bin.000001 > upto-15028.out

上面的示例将在15028的位置上停止binlog。当在命令行中指定多个二进制日志文件时,停止位置将仅应用于给定列表中的*后一个二进制日志文件。

11 刷新日志以清除Binlog输出

当二进制日志文件没有被正确地关闭时,将在输出中看到一个警告消息,如下所示。

 

1 $ mysqlbinlog mysqld-bin.000001 > output.out

如下所示,报告中提示binlog文件没有正确地关闭。

 

1

2

3

4

5

6

7

# head output.log

/*!40019 SET @@session.max_insert_delayed_threads=0*/;

.. ..

# Warning: this binlog is either in use or was not closed properly.

..

.. .. BINLOG ‘ IeZ4WQ8BAAAAZgAAAGoAAAABAAQANS4xLjczLWxvZwAAAAAAAAAAAAAAAAAAAAAAAA

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAh5nhZEzgNAAgAEgAEBAQEEgAAUwAEGggAAAAICAgC

当看到这个提示时,需要连接到mysql并刷新日志,如下所示。

 

1 mysql> flush logs;

刷新日志之后,再次执行mysqlbinlog命令,将不会看到在mysqlbinlog输出中binlog未正确关闭的警告消息。

12 在输出中只显示语句

默认情况下,正如在前面的示例输出中看到的一样,除了SQL语句之外,在mysqlbinlog输出中还会有一些附加信息。如果只想查看常规的SQL语句,而不需要其他内容,那么可以使用 -s 选项,如下所示。

 

也可以使用 –short-form 选项,效果相同。

 

1

2

3

$ mysqlbinlog -s mysqld-bin.000001

 

$ mysqlbinlog –short-form mysqld-bin.000001

 

下面是上述命令的部分输出。在这里,它将只显示来自给定二进制日志文件的SQL语句。

 

 

1

2

3

4

5

6

SET TIMESTAMP=1501096106/*!*/;

insert into employee values(400,’Nisha’,’Marketing’,9500)/*!*/;

SET TIMESTAMP=1501096106/*!*/;

insert into employee values(500,’Randy’,’Technology’,6000)

..

..

 

不会显示像下面这样的条目:

 

 

1

2

# at 1201

#170726 15:08:26 server id 1  end_log_pos 1329  Query   thread_id=3     exec_time=0     error_code=0

 

13 查看特定开始时间的条目

下面将只提取从指定时间开始的条目。在此之前的任何条目都将被忽略。

 

 

1 $ mysqlbinlog –start-datetime=”2017-08-16 10:00:00″ mysqld-bin.000001

 

当你想要从一个二进制文件中提取数据时,这是非常有用的,因为你希望使用它来恢复或重构在某个时间段内发生的某些数据库活动。时间戳的格式可以是MySQL服务器所理解的DATETIME和timestamp中的任何类型。

 

14 查看特定结束时间的条目

与前面的开始时间示例一样,这里也可以指定结束时间,如下所示。

 

1 $ mysqlbinlog –stop-datetime=”2017-08-16 15:00:00″ mysqld-bin.000001

上面的命令将读取到给定结束时间的条目。任何来自于超过给定结束时间的mysql二进制日志文件的条目都不会被处理。

15 从远程服务器获取二进制日志

在本地机器上,还可以读取位于远程服务器上的mysql二进制日志文件。为此,需要指定远程服务器的ip地址、用户名和密码,如下所示。

 

此处使用-R选项。-R选项与-read-from-remote-server相同。

 

1 $ mysqlbinlog -R -h 192.168.101.2 -p mysqld-bin.000001

在上面命令中:

 

  • -R 选项指示mysqlbinlog命令从远程服务器读取日志文件
  • -h 指定远程服务器的ip地址
  • -p 将提示输入密码。默认情况下,它将使用“root”作为用户名。也可以使用 -u 选项指定用户名。
  • mysqld-bin.000001 这是在这里读到的远程服务器的二进制日志文件的名称。

下面命令与上面的命令完全相同:

 

1 $ mysqlbinlog –read-from-remote-server –host=192.168.101.2 -p mysqld-bin.000001

如果只指定 -h 选项,将会得到下面的错误消息。

 

1

2

$ mysqlbinlog -h 192.168.101.2 mysqld-bin.000001

mysqlbinlog: File ‘mysqld-bin.000001’ not found (Errcode: 2)

当你在远程数据库上没有足够的特权时,将得到以下“不允许连接”错误消息。在这种情况下,确保在远程数据库上为本地客户机授予适当的特权。

 

1

2

3

$ mysqlbinlog -R –host=192.168.101.2 mysqld-bin.000001

ERROR: Failed on connect: Host ‘216.172.166.27’ is not allowed to connect

to this MySQL server

如果没有使用 -p 选项指定正确的密码,那么将得到以下“访问拒*”错误消息。

 

1

2

$ mysqlbinlog -R –host=192.168.101.2 mysqld-bin.000001

ERROR: Failed on connect: Access denied for user ‘root’@’216.172.166.27’ (using password: YES)

下面的示例显示,还可以使用-u选项指定mysqlbinlog应该用于连接到远程MySQL数据库的用户名。请注意,这个用户是mysql用户(不是Linux服务器用户)。

 

 

1 $ mysqlbinlog -R –host=192.168.101.2 -u root -p mysqld-bin.000001<span style=”text-indent: 2em;”> </span>

mysql误删数据快速恢复

转载自:https://www.jianshu.com/p/c9a2fe3f4534

相信后端研发的同学在开发过程经常会遇到产品临时修改线上数据的需求,如果手法很稳那么很庆幸可以很快完成任务,很不幸某一天突然手一抖把表里的数据修改错误或者误删了,这个时候你会发现各种问题反馈接踵而来。如果身边有BDA或者有这方面经验的同事那么可以很快解决这个问题,如果没有那么希望这篇文章可以帮到你。

binglog介绍

首先*步保证mysql已经开启binlog,查看命令:

show variables like '%log_bin%'

mysql binlog分三种格式 :

Statement : 会在binlog中记录每一条执行修改数据的sql语句的相关信息,优点是不需要记录每一行的变化,减少了binlog日志量,节约了IO
Row : 会在binlog中记录每一修改语句的详细信息,包括数据在修改之前和修改之后的数据的具体信息,好处是会清晰记录每一条修改的详细信息,不好的地方是会产生大量日志
Mixed :这种格式实际上就是Statement和Row的结合体,如果遇到表结构变更就会以Statement来记录,如果涉及语句修改那么就以Row格式记录

这里的binlog格式推荐row,my.cnf 的配置可参考 :

  1. server_id = 1001
  2. log_bin = /var/log/mysql/mysql-bin.log
  3. max_binlog_size = 1G
  4. binlog_format = row
  5. binlog_row_image = full

我们来模拟一些数据:

  1. CREATE TABLE `user` (
  2. `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  3. `name` varchar(125) NOT NULL DEFAULT COMMENT ‘名称’,
  4. `age` tinyint(3) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘年龄’,
  5. `sex` tinyint(3) unsigned NOT NULL DEFAULT ‘0’ COMMENT ‘性别’,
  6. `deleted` tinyint(4) unsigned DEFAULT ‘0’,
  7. `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  8. PRIMARY KEY (`id`)
  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT=‘用户表测试’;
  1. INSERT INTO `user` (`id`, `name`, `age`, `sex`, `deleted`, `created`) VALUES (‘1’, ‘小王’, ’21’, ‘1’, ‘0’, ‘2017-12-18 13:21:52’),
  2. (‘2’, ‘小张’, ’28’, ‘1’, ‘0’, ‘2017-12-18 13:21:52’),(‘3’, ‘小红’, ’22’, ‘0’, ‘0’, ‘2017-12-18 13:21:52’),
  3. (‘4’, ‘小楠’, ’23’, ‘0’, ‘0’, ‘2017-12-18 13:21:52’)
  4. ,(‘5’, ‘小柱’, ’25’, ‘1’, ‘0’, ‘2017-12-18 13:21:52’);

然后我们把数据全部删掉

delete from `user`

数据恢复方法一 :

使用开源框架binlog2sql : https://github.com/danfengcao/binlog2sql
好处是成熟,稳定,上手难度比较低且可直接生成可执行sql,示例 :

python /binlog2sql/binlog2sql.py --flashback -h127.0.0.1 -P3306 -uroot -p'123456' -dlocal -tuser --start-file='mysql-bin.000038' --sql-type=DELETE --start-datetime='2017-12-17 19:39:33' --stop-datetime='2017-12-17 19:40:01' >/**/data6.sql

解析后的结果大概是这样

  1. INSERT INTO `local`.`user`(`name`, `created`, `deleted`, `age`, `sex`, `id`) VALUES (‘小柱’, ‘20171218 13:21:52‘, 0, 25, 1, 5); #start 1890 end 2244 time 20171219 09:20:26
  2. INSERT INTO `local`.`user`(`name`, `created`, `deleted`, `age`, `sex`, `id`) VALUES (‘小楠’, ‘20171218 13:21:52‘, 0, 23, 0, 4); #start 1890 end 2244 time 20171219 09:20:26
  3. INSERT INTO `local`.`user`(`name`, `created`, `deleted`, `age`, `sex`, `id`) VALUES (‘小红’, ‘20171218 13:21:52‘, 0, 22, 0, 3); #start 1890 end 2244 time 20171219 09:20:26
  4. INSERT INTO `local`.`user`(`name`, `created`, `deleted`, `age`, `sex`, `id`) VALUES (‘小张’, ‘20171218 13:21:52‘, 0, 28, 1, 2); #start 1890 end 2244 time 20171219 09:20:26
  5. INSERT INTO `local`.`user`(`name`, `created`, `deleted`, `age`, `sex`, `id`) VALUES (‘小王’, ‘20171218 13:21:52‘, 0, 21, 1, 1); #start 1890 end 2244 time 20171219 09:20:26

参数–sql-type建议加上,因为可能会有其他类型语句生成干扰了执行结果
如果是线上阿里云或者其他产品建议先去管理后台找到事发时间的binlog日志下载下来,先在测试环境验证数据回滚结果.

数据恢复方法二:

当线上数据出现错误的时候首先可以询问具体操作人记录时间点,这个时候可以借助mysql自带的binlog解析工具mysqlbinlog,具体位置在mysql安装目录**/mysql/bin/下,示例:

mysqlbinlog --base64-output=decode-rows -v --start-datetime="2017-12-15 17:48:49" --stop-datetime="2017-12-16 23:59:49" /usr/local/mysql/mysql-bin.000038 >/**/data.sql

如果是阿里云rds或者其他产品可通过远程方式解析

mysqlbinlog --no-defaults -u账号 -p密码 -h ***.rds.aliyuncs.com --read-from-remote-server mysql-bin.000180 --base64-output=decode-rows -v > /data.sql

这里因为binlog文件默认是通过base64编码过的,所以需要加上–base64-output=decode-rows -v
解析后的格式大概是这样的 :

  1. ### DELETE FROM `local`.`user`
  2. ### WHERE
  3. ### @1=1
  4. ### @2=’小王’
  5. ### @3=21
  6. ### @4=1
  7. ### @5=0
  8. ### @6=’2017-12-18 13:21:52′
  9. ### DELETE FROM `local`.`user`
  10. ### WHERE
  11. ### @1=2
  12. ### @2=’小张’
  13. ### @3=28
  14. ### @4=1
  15. ### @5=0
  16. ### @6=’2017-12-18 13:21:52′
  17. ….

仔细查看这种格式文件,发现这种格式文件并不能直接执行,但是在where条件后面记录了被删除之前的原始数据,需要借助sed、awk把SQL文本转换成真正的SQL。或者当你在遇到开源框架解决不了的情况下,可以根据具体场景尝试手动把这种格式的文件解析成可执行的sql语句。

mysql redo log以及binlog

转载自:http://blog.itpub.net/30126024/viewspace-2216099/

总结

1、ib_logfile类似oracle的online redo log,包含commit和uncommit的数据

2、binary log类似oracle的online redo log和archive redo log,但是只有commit的数据

statement 格式的 binlog,*后会有 COMMIT;
row 格式的 binlog,*后会有一个 XID event

3、为什么MySQL有binlog,还要redo log?因为MySQL是多存储引擎的,不管使用那种存储引擎,都会有binlog,而不一定有redo log。而redo log 事务日志ib_logfile文件是InnoDB存储引擎产生的

4、ib_logfile是循环使用,binary log不是循环使用,在写满或者重启之后,会生成新的binary log文件

5、两种日志记录的内容差不多类似,都是事务对应DML、DDL的信息,只是作用不同,内容可能重复,比如一个DML记录在了ib_logfile也记录在了binary log

6、ib_logfile作为异常宕机后启动时恢复使用

7、binary log作为数据恢复使用,主从复制搭建使用

8、两种日志写入磁盘的触发点不同,二进制日志只在事务提交完成后进行一次写入,重做日志在事务提交会写入每隔1秒也会写入。MySQL为了保证master和slave的数据一致性,就必须保证binlog和InnoDB redo日志的一致性(因为备库通过二进制日志重放主库提交的事务,如果主库commit之前就写入binlog,一旦主库crash,再次启动时会回滚事务。但此时从库已经执行,则会造成主备数据不一致)。所以必须保证二进制日志只在事务提交完成后进行一次写入

9、在主从复制结构中,要保证事务的持久性和一致性,对两种日志的相关变量设置为如下*为妥当:sync_binlog=1(即每提交一次事务同步写到磁盘中);innodb_flush_log_at_trx_commit=1(即每提交一次事务都写到磁盘中)。这两项变量的设置保证了:每次提交事务都写入二进制日志和事务日志,并在提交时将它们刷新到磁盘中

10、innodb中,表数据刷盘的规则只有一个:checkpoint。但是触发checkpoint的情况却有几种(1.重用redo log文件;2.脏页达到一定比例)

11、ib_logfile作为redo log记录的是“做了什么改动”,是物理日志,记录的是”在某个数据页上做了什么修改”;

binary log记录的是这个语句的原始逻辑,分两种模式,statement格式记录的是sql语句,row格式记录的是行的内容,记录更新前和更新后的两条数据。

 

详述

ib_logfile

官方文档https://dev.mysql.com/doc/refman/5.7/en/glossary.html

一组文件,通常名为ib_logfile0和ib_logfile1,构成重做日志。 有时也称为日志组。 这些文件记录了尝试更改InnoDB表中数据的语句。 在崩溃后启动时,会自动重播这些语句以更正由不完整事务写入的数据。

此数据不能用于手动恢复; 对于该类型的操作,请使用binlog二进制日志

 

binary log

官方文档https://dev.mysql.com/doc/refman/5.7/en/binary-log.html

二进制日志包含描述数据库更改的“事件”,例如表创建操作或对表数据的更改。 它还包含可能已进行更改的语句的事件(例如,不匹配任何行的DELETE),除非使用基于行的日志记录。 二进制日志还包含有关每个语句获取更新数据的时间长度的信息。

二进制日志有两个重要目的:

用于复制

某些数据恢复操作需要使用二进制日志。备份恢复后,将重新执行备份后记录的二进制日志中的事件。 这些事件使数据库从备份点更新。

二进制日志不用于不修改数据的SELECT或SHOW等语句,也就是说select show等语句不会产生binlog

 

checkpoint

https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_checkpoint

当对缓冲池中缓存的数据页进行更改时,这些更改将在稍后的某个时间写入数据文件,这一过程称为刷新。 检查点是已成功写入数据文件的*新更改(由LSN值表示)的记录。

 

sharp checkpoint

将重做条目包含在重做日志的某些部分中的所有脏缓冲池页面刷新到磁盘的过程。 在InnoDB覆盖重用日志文件之前发生 ; 日志文件以循环方式使用。 通常发生写入密集型工作负载。

 

flush

将发生在内存区域或临时磁盘存储区域中缓冲的 更改写入数据库文件 。 定期刷新的InnoDB存储结构包括重做日志,撤消日志和缓冲池。

刷新可能是因为 内存区域已满并且系统需要释放一些空间 ,因为提交操作意味着可以*终确定事务的更改,或者因为慢速关闭操作意味着应该*终完成所有未完成的工作。 当一次刷新所有缓冲数据并不重要时,InnoDB可以使用一种称为 模糊检查点 的技术来刷新小批量页面以分散I / O开销。

redis 数据库主从不一致问题解决方案

在聊数据库与缓存一致性问题之前,先聊聊数据库主库与从库的一致性问题。

 

问:常见的数据库集群架构如何?

:一主多从,主从同步,读写分离。

%title插图%num

如上图:

(1)一个主库提供写服务

(2)多个从库提供读服务,可以增加从库提升读性能

(3)主从之间同步数据

画外音:任何方案不要忘了本心,加从库的本心,是提升读性能。

 

问:为什么会出现不一致?

:主从同步有时延,这个时延期间读从库,可能读到不一致的数据。

%title插图%num

如上图:

(1)服务发起了一个写请求

(2)服务又发起了一个读请求,此时同步未完成,读到一个不一致的脏数据

(3)数据库主从同步*后才完成

画外音:任何数据冗余,必将引发一致性问题。

 

问:如何避免这种主从延时导致的不一致?

:常见的方法有这么几种。

 

方案一:忽略

任何脱离业务的架构设计都是耍流氓,*大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间不一致。

画外音:如果业务能接受,*推崇此法。

 

如果业务能够接受,别把系统架构搞得太复杂。

 

方案二:强制读主

%title插图%num

如上图:

(1)使用一个高可用主库提供数据库服务

(2)读和写都落到主库上

(3)采用缓存来提升系统读性能

这是很常见的微服务架构,可以避免数据库主从一致性问题。

 

方案三:选择性读主

强制读主过于粗暴,毕竟只有少量写请求,很短时间,可能读取到脏数据。

 

有没有可能实现,只有这一段时间,可能读到从库脏数据的读请求读主,平时读从呢?

 

可以利用一个缓存记录必须读主的数据。

%title插图%num

如上图,当写请求发生时:

(1)写主库

(2)将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延”

画外音:key的格式为“db:table:PK”,假设主从延时为1s,这个key的cache超时时间也为1s。

 

%title插图%num

如上图,当读请求发生时:

这是要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果,

(1)cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询

(2)cache里没有这个key,说明*近没有发生过写请求,此时就可以去从库查询

以此,保证读到的一定不是不一致的脏数据。

 

总结

数据库主库和从库不一致,常见有这么几种优化方案:

(1)业务可以接受,系统不优化

(2)强制读主,高可用主库,用缓存提高读性能

(3)在cache里记录哪些记录发生过写请求,来路由读主还是读从

Redis Sentinel 和Redis Cluster对比

1、Redis Sentinel

Redis-Sentinel(哨兵模式)是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自懂切换。它的主要功能有以下几点:

不时地监控redis是否按照预期良好地运行;

如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);

能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。

Redis master-slave 模式如下图:

%title插图%num

从上图片中可以看到,一个master 节点可以挂多个slave  ,Redis Sentinel 管理Redis 节点结构如下:

%title插图%num

从上图中可以得出Sentinel其实就是Client和Redis之间的桥梁,所有的客户端都通过Sentinel程序获取Redis的Master服务。首先Sentinel是集群部署的,Client可以链接任何一个Sentinel服务所获的结果都是一致的。其次,所有的Sentinel服务都会对Redis的主从服务进行监控,当监控到Master服务无响应的时候,Sentinel内部进行仲裁,从所有的 Slave选举出一个做为新的Master。并且把其他的slave作为新的Master的Slave。*后通知所有的客户端新的Master服务地址。如果旧的Master服务地址重新启动,这个时候,它将被设置为Slave服务。

Sentinel 可以管理master-slave节点,看似Redis的稳定性得到一个比较好的保障。但是如果Sentinel是单节点的话,如果Sentinel宕机了,那master-slave这种模式就不能发挥其作用了。幸好Sentinel也支持集群模式,Sentinel的集群模式主要有以下几个好处:

即使有一些sentinel进程宕掉了,依然可以进行redis集群的主备切换;

如果只有一个sentinel进程,如果这个进程运行出错,或者是网络堵塞,那么将无法实现redis集群的主备切换(单点问题);

如果有多个sentinel,redis的客户端可以随意地连接任意一个sentinel来获得关于redis集群中的信息。

 Redis Sentinel 集群模式可以增强整个Redis集群的稳定性与可靠性,但是当某个节点的master节点挂了要重新选取出新的master节点时,Redis Sentinel的集群模式选取的复杂度显然高于单点的Redis Sentinel 模式,此时需要一个比较靠谱的选取算法。下面就来介绍Redis Sentinel 集群模式的 “仲裁会”(多个Redis Sentinel共同商量谁是Redis 的 master节点)

1.1、Redis Sentinel 集群模式的 “仲裁会”

当一个master被sentinel集群监控时,需要为它指定一个参数,这个参数指定了当需要判决master为不可用,并且进行failover时,所需要的sentinel数量,本文中我们暂时称这个参数为票数,不过,当failover主备切换真正被触发后,failover并不会马上进行,还需要sentinel中的大多数sentinel授权后才可以进行failover。当ODOWN时,failover被触发。failover一旦被触发,尝试去进行failover的sentinel会去获得“大多数”sentinel的授权(如果票数比大多数还要大的时候,则询问更多的sentinel)这个区别看起来很微妙,但是很容易理解和使用。例如,集群中有5个sentinel,票数被设置为2,当2个sentinel认为一个master已经不可用了以后,将会触发failover,但是,进行failover的那个sentinel必须先获得至少3个sentinel的授权才可以实行failover。如果票数被设置为5,要达到ODOWN状态,必须所有5个sentinel都主观认为master为不可用,要进行failover,那么得获得所有5个sentinel的授权。

2、Redis Cluster

使用Redis Sentinel 模式架构的缓存体系,在使用的过程中,随着业务的增加不可避免的要对Redis进行扩容,熟知的扩容方式有两种,一种是垂直扩容,一种是水平扩容。垂直扩容表示通过加内存方式来增加整个缓存体系的容量比如将缓存大小由2G调整到4G,这种扩容不需要应用程序支持;水平扩容表示表示通过增加节点的方式来增加整个缓存体系的容量比如本来有1个节点变成2个节点,这种扩容方式需要应用程序支持。垂直扩容看似*便捷的扩容,但是受到机器的限制,一个机器的内存是有限的,所以垂直扩容到一定阶段不可避免的要进行水平扩容,如果预留出很多节点感觉又是对资源的一种浪费因为对业务的发展趋势很快预测。Redis Sentinel 水平扩容一直都是程序猿心中的痛点,因为水平扩容牵涉到数据的迁移。迁移过程一方面要保证自己的业务是可用的,一方面要保证尽量不丢失数据所以数据能不迁移就尽量不迁移。针对这个问题,Redis Cluster就应运而生了,下面简单介绍一下RedisCluster。

Redis Cluster是Redis的分布式解决方案,在Redis 3.0版本正式推出的,有效解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Cluster架构达到负载均衡的目的分布式集群首要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整个数据的一个子集。Redis Cluster采用哈希分区规则中的虚拟槽分区。虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有的数据映射到一个固定范围内的整数集合,整数定义为槽(slot)。Redis Cluster槽的范围是0 ~ 16383槽是集群内数据管理和迁移的基本单位。采用大范围的槽的主要目的是为了方便数据的拆分和集群的扩展,每个节点负责一定数量的槽。Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0 ~ 16383,计算公式:slot = CRC16(key)&16383。每一个实节点负责维护一部分槽以及槽所映射的键值数据。下图展现一个五个节点构成的集群,每个节点平均大约负责3276个槽,以及通过计算公式映射到对应节点的对应槽的过程。

%title插图%num

Redis Cluster节点相互之前的关系如下图所示:

https://img1.mukewang.com/5bd6ba7a0001ba0705700569.jpg

 

优先选择redis cluster,redis cluster集群可以使用redis-trix.rb脚本完成

Project Reactor 核心原理解析

一、开篇

本文将解析 Spring 的 Reactor 项目的源码。主要目的是让自己能深入理解 Reactor 这个项目,以及 Spring 5 和 Spring Boot 2。

Project Reactor 项目地址:https://github.com/reactor

Reactor 项目主要包含 Reactor Core 和 Reactor Netty 两部分。Reactor Core 实现了反应式编程的核心功能,Reactor Netty 则是 Spring WebFlux 等技术的基础。本文将介绍 Core 模块的核心原理。

本文从一个例子开始:

  1. public static void main(String[] args) {
  2. Flux.just(“tom”, “jack”, “allen”)
  3. .filter(s -> s.length() > 3)
  4. .map(s -> s.concat(“@qq.com”))
  5. .subscribe(System.out::println);
  6. }

整个 Project Reactor 的核心调用分为下面几个阶段:

  • 声明阶段
  • subscribe 阶段
  • onSubscribe 阶段
  • request 阶段
  • 调用阶段

接下来对每个阶段详细介绍。

二、声明阶段

简介

之前的文章介绍过,反应式编程和传统风格的编程一个*大的不同点在于,开发人员编写的大部分代码都是在声明处理过程。即这些代码并不会被实际的执行,直到开始被订阅。这便是为何*阶段是声明阶段的原因。

详解

先来看*个方法 Flux.just 的源码:

  1. public static <T> Flux<T> just(T… data) {
  2. return fromArray(data);
  3. }
  4. public static <T> Flux<T> fromArray(T[] array) {
  5. // 省略不重要的部分
  6. return onAssembly(new FluxArray<>(array));
  7. }

just 方法只是创建反应式流的众多方式的一个。在实际工作中,更常见的通过反应式 Repository 将数据库查询结果,或通过 Spring 5 的 WebClient 将 HTTP 调用结果*为流的开始。

onAssembly 是一个扩展机制,因为实例中并没有使用,所以可简单理解为将入参直接返回。

接下来 new FluxArray<>(array) 做的事情也很简单,仅仅是把入参赋给成员变量。

再接下来 filter(s -> s.length() > 3) 做的事情是把上一个 Flux,即 FluxArray,以及 s -> s.length() > 3 所表示的 Predicate 保存起来。

看一下源码

  1. public final Flux<T> filter(Predicate<? super T> p) {
  2. if (this instanceof Fuseable) {
  3. return onAssembly(new FluxFilterFuseable<>(this, p));
  4. }
  5. return onAssembly(new FluxFilter<>(this, p));
  6. }
  7. FluxFilterFuseable(Flux<? extends T> source,
  8. Predicate<? super T> predicate) {
  9. super(source);
  10. this.predicate =
  11. Objects.requireNonNull(predicate, “predicate”);
  12. }

这里有个逻辑判断,就是返回的值分为了 Fuseable 和非 Fuseable 两种。简单介绍一下,Fuseable 的意思就是可融合的。我理解就是 Flux 表示的是一个类似集合的概念,有一些集合类型可以将多个元素融合在一起,打包处理,而不必每个元素都一步步的处理,从而提高了效率。因为 FluxArray 中的数据一开始就都准备好了,因此可以打包处理,因此就是 Fuseable

而接下来的 map 操作也对应一个 FluxMapFusable。原理与 filter 操作,这里不再重复。

三、subscribe 阶段

简介

subscribe 阶段同行会触发数据发送。在本例中,后面可以看到,对于 FluxArray,数据发送很简单,就是循环发送。而对于像数据库、RPC 这样的长久,则会触发请求的发送。

详解

当调用 subscribe(System.out::println) 时,整个执行过程便进入 subscribe 阶段。经过一系列的调用之后,subscribe 动作会代理给具体的 Flux 来实现。就本例来说,会代理给 FluxMapFuseable,方法实现如下(经过简化):

  1. public void subscribe(CoreSubscriber<? super R> actual) {
  2. source.subscribe(
  3. new MapFuseableSubscriber<>(actual, mapper)
  4. );
  5. }

其中的 source 变量对应的是当前 Flux 的上一个。本例中,FluxMapFuseable 上一个是 FluxFilterFuseable

new MapFuseableSubscriber<>(actual, mapper) 则是将订阅了 FluxMapFuseable 的 Subscriber 和映射器封装在一起,组成一个新的 Subscriber。然后再订阅 source,即 FluxArraysource 是在上一个阶段被保存下来的。

这里强调一下 Publisher 接口中的 subscribe 方法语义上有些奇特,它表示的不是订阅关系,而是被订阅关系。即 aPublisher.subscribe(aSubscriber) 表示的是 aPublisher 被 aSubscriber 订阅。

接下来调用的就是 FluxFilterFuseable 中的 subscribe 方法,类似于 FluxMapFuseable,源码如下:

  1. public void subscribe(CoreSubscriber<? super T> actual) {
  2. source.subscribe(
  3. new FilterFuseableSubscriber<>(actual, predicate)
  4. );
  5. }

这时 source 就成了 FluxArray。于是,接下来是调用 FluxArray 的 subscribe 方法。

  1. public static <T> void subscribe(CoreSubscriber<? super T> s,
  2. T[] array) {
  3. if (array.length == 0) {
  4. Operators.complete(s);
  5. return;
  6. }
  7. if (s instanceof ConditionalSubscriber) {
  8. s.onSubscribe(
  9. new ArrayConditionalSubscription<>(
  10. (ConditionalSubscriber<? super T>) s, array
  11. )
  12. );
  13. }
  14. else {
  15. s.onSubscribe(new ArraySubscription<>(s, array));
  16. }
  17. }

*重要的部分还是 s.onSubscribe(new ArraySubscription<>(s, array))。不同于 FluxMapFuseable 和 FluxFilterFuseableFluxArray 没有再调用 subscribe 方法,因为它是数据源头。而 FluxMapFuseable 和 FluxFilterFuseable 则是中间过程。

可以这样简单理解,对于中间过程的 Mono/Flux,subscribe 阶段是订阅上一个 Mono/Flux;而对于源 Mono/Flux,则是要执行 Subscriber.onSubscribe(Subscription s) 方法。

四、onSubscribe 阶段

简介

在调用 FluxArray 的 subscribe 方法之后,执行过程便进入了 onSubscribe 阶段。onSubscribe 阶段指的是 Subscriber#onSubscribe 方法被依次调用的阶段。这个阶段会让各 Subscriber 知道 subscribe 方法已被触发,真正的处理流程马上就要开始。所以这一阶段的工作相对简单。

详解

s.onSubscribe(new ArraySubscription<>(s, array));

s 是 FilterFuseableSubscriber,看一下 FilterFuseableSubscriber 的 onSubscribe(Subscription s) 源码:

  1. public void onSubscribe(Subscription s) {
  2. if (Operators.validate(this.s, s)) {
  3. this.s = (QueueSubscription<T>) s;
  4. actual.onSubscribe(this);
  5. }
  6. }

actual 对应 MapFuseableSubscriberMapFuseableSubscriber 的 onSubscribe 方法也是这样,但 actual 对于的则是代表 System.out::println 的 LambdaSubscriber

调用过程:

FluxArray.subscribe -> FilterFuseableSubscriber.onSubscribe -> MapFuseableSubscriber.onSubscribe -> LambdaSubscriber.onSubscribe

五、request 阶段

含义

onSubscribe 阶段是表示订阅动作的方式,让各 Subscriber 知悉,准备开始处理数据。当*终的 Subscriber 做好处理数据的准备之后,它便会调用 Subscription 的 request 方法请求数据。

详解

下面是 LambdaSubscriber onSubscribe 方法的源码:

  1. public final void onSubscribe(Subscription s) {
  2. if (Operators.validate(subscription, s)) {
  3. this.subscription = s;
  4. if (subscriptionConsumer != null) {
  5. try {
  6. subscriptionConsumer.accept(s);
  7. }
  8. catch (Throwable t) {
  9. Exceptions.throwIfFatal(t);
  10. s.cancel();
  11. onError(t);
  12. }
  13. }
  14. else {
  15. s.request(Long.MAX_VALUE);
  16. }
  17. }
  18. }

由上可见 request 方法被调用。在本例中,这里的 s 是 MapFuseableSubscriber

这里需要说明,如 MapFuseableSubscriberFilterFuseableSubscriber,它们都有两个角色。一个角色是 Subscriber,另一个角色是 Subscription。因为它们都位于调用链的中间,本身并不产生数据,也不需要对数据暂存,但是需要对数据做各式处理。因此,在 onSubscribe、request 阶段,以及后面要讲到的调用阶段都需要起到代理的作用。这就解释了 actual.onSubscribe(this) onSubscribe 自己的原因。

下面是 FilterFuseableSubscriber 的 request 方法的源码。充分可见其代理的角色。

  1. public void request(long n) {
  2. s.request(n);
  3. }

*后 ArraySubscription 的 request 方法将被调用。

与 map、filter 等操作不同,flatMap 有比较大的差异,感兴趣的同学可以自己研究一下。本文先不详细介绍。

六、调用阶段

含义解释

这一阶段将会通过调用 Subscriber 的 onNext 方法,从而进行真正的反应式的数据处理。

流程分解

在 ArraySubscription 的 request 方法被调用之后,执行流程便开始了*后的调用阶段。

  1. public void request(long n) {
  2. if (Operators.validate(n)) {
  3. if (Operators.addCap(REQUESTED, this, n) == 0) {
  4. if (n == Long.MAX_VALUE) {
  5. fastPath();
  6. }
  7. else {
  8. slowPath(n);
  9. }
  10. }
  11. }
  12. }
  13. void fastPath() {
  14. final T[] a = array;
  15. final int len = a.length;
  16. final Subscriber<? super T> s = actual;
  17. for (int i = index; i != len; i++) {
  18. if (cancelled) {
  19. return;
  20. }
  21. T t = a[i];
  22. if (t == null) {
  23. s.onError(new NullPointerException(“The “
  24. + i
  25. + “th array element was null”)
  26. );
  27. return;
  28. }
  29. s.onNext(t);
  30. }
  31. if (cancelled) {
  32. return;
  33. }
  34. s.onComplete();
  35. }

ArraySubscription 会循环数据中的所有元素,然后调用 Subscriber 的 onNext 方法,将元素交由 Subscriber 链处理。

于是接下来由 FilterFuseableSubscriber 处理。下面是其 onNext 方法(做了一些简化)

  1. public void onNext(T t) {
  2. boolean b;
  3. try {
  4. b = predicate.test(t);
  5. }
  6. catch (Throwable e) {
  7. Throwable e_ =
  8. Operators.onNextError(t, e, this.ctx, s);
  9. if (e_ != null) {
  10. onError(e_);
  11. }
  12. else {
  13. s.request(1);
  14. }
  15. Operators.onDiscard(t, this.ctx);
  16. return;
  17. }
  18. if (b) {
  19. actual.onNext(t);
  20. }
  21. else {
  22. s.request(1);
  23. Operators.onDiscard(t, this.ctx);
  24. }
  25. }

其*要部分是通过 predicate 进行判断,如果满足条件,则交由下一个 Subscriber 处理。

下一个要处理的是 MapFuseableSubscriber,原理类似,不再重复。

*终要处理数据的是 LambdaSubscriber,下面是它的 onNext 方法源码:

  1. public final void onNext(T x) {
  2. try {
  3. if (consumer != null) {
  4. consumer.accept(x);
  5. }
  6. }
  7. catch (Throwable t) {
  8. Exceptions.throwIfFatal(t);
  9. this.subscription.cancel();
  10. onError(t);
  11. }
  12. }

consumer.accept(x) 会使数据打印在命令行中。

整个流程的解释到这里基本结束。

七、总结

接下来我们用一个图来回顾一下整体的流程。

Reactor Core

参考

需要感谢下面这篇文章,帮助我很好理解了 Project Reactor 的核心处理流程。

由表及里学 ProjectReactor:http://blog.yannxia.top/2018/06/26/java/spring/projectreactor/

ubuntu云服务器远程登陆以及基本操作日记

我采购的是腾讯云Ubuntu服务器,现在比较火的像阿里云啊等等。
一.远程登陆
在windows系统登陆云服务器需要借助相关的软件,本人比较喜欢xshell,因为对比于putty它的界面更易于开发,比如它包含的ftp模块,可以实现本地文件和远程服务器文件的传输。

二.ubuntu的简单操作
1.创建目录
如:在data/release文件下创建webapp目录
sudo mkdir -p /data/release/webapp
2.进入工作目录
cd /data/release/webapp
3.在工作目录下创建文件并且修改文件的访问权限
如:在webapp中创建package.json文件
sudo touch package.json
sudo chmod a+r+w package.json
(备注:可以修改package.json添加我们的服务器的名称和版本号 如:
{“name”: “webapp”,
“version”:”1.0.0″};
同时可以在该打开的工作目录下添加我们自己的其它文件 如:.js为后缀的文件等;
目录以及文件的操作也可以通过之前介绍的ftp的方式)
4.查看目录文件下的子文件目录
如:查看data下有什么目录文件
ls /data

注:以上四点操作完全可以用ftp远程文件操作文件夹,新建,删除,移动等,但是操作文件夹一般需要·root权限,这需要你以root的身份登录。
5.编辑文件内容
在云服务器中编辑文件内容需要借助vim这个编辑器
1)安装vim
sudo apt-get install vim-gtk
安装完成后可以使用vim-v命令检查一下版本
2)用vim查看编辑文件内容
如:编辑我们之前创建的package.json文件只需在命令行输入:
vim/package.json
之后按i(insert进入插入模式)来编辑我们的文件
3)vim退出

使用Esc键切换到命令模式:

输入ZZ(保存退出)/ZQ(不保存退出)或者输入冒号进入EX模式并输入q(命令退出·)/wq(保存退出)

6.ubuntu云服务器的快捷键

ctrl+s:保存文档

ctrl+f:搜索文档

ctrl+c:终止服务运行
今天的日记总结就暂时到这里啦~~

ubuntu终端连接云服务器并上传文件

ubuntu终端连接云服务器并上传文件

一、连接
1,打开终端,输入命令:ssh -q  -l 你的用户名 -p 22 你服务器的IP

2,接下来输入yes,和你的密码

二、修改云服务器的配置,使root用户也能登录
1,设置root密码:sudo passwd root

2,修改配置:sudo vim /etc/ssh/ssh_config

3,找到PermitRootLogin 这项 将其改为 yes,保存退出

4,重启ssh服务:sudo service ssh restart

ps:如果不用root登录,会没有权限往服务器上传文件

三、开始上传
命令:scp 本地文件地址+文件名  远程用户名@IP地址:+服务器内存放文件的地址。(这里用户名用root)

例如:scp /home/wj/桌面/aa.txt root@111.231.1.101:/home/aa.txt

之后输入密码即可。