Docker(尚硅谷)

Docker从基础走向精通

基础篇

一、Docker是什么

1.1 Docker为什么出现
  • 作用

    • 例如:假定您在开发一个谷粒商城,您使用的是一台笔记本电脑而且您的开发环境具有特定的配置。其他开发人员身处的环境配置也各有不同。您正在开发的应用依赖于您当前的配置且还要依赖于某些配置文件。此外,您的企业还拥有标准化的测试和生产环境,且具有自身的配置和一系列支持文件。您希望尽可能多在本地模拟这些环境而不产生重新创建服务环境的开销。那么解决这一系列配置环境问题的就是使用容器
    • Docker之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案——系统平滑移植,容器虚拟化技术。
    • 环境配置相当麻烦,换一台机器,就要重来一次,费力费时。能不能从根本上解决这个问题?软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。
    • Docker的出现使得Docker得以打破过去【程序即应用】的观念。通过镜像(images)将作业系统核心除外,运作应用程序所需要的系统环境,由下而上打包,达到应用程序跨平台间的无缝接轨运作
  • 使用docker before和after对比
    在这里插入图片描述

1.2 docker理念简介
  • 一次搬家、处处运行
    在这里插入图片描述
1.3 docker是什么
  • Docker是基于Go语言实现的云开源项目
  • Docker的主要目标是“Build, Ship and Run Any App, Anywhere”, 也就是通过对应用组件的封装、分发、部署、运作等生命周期的管理,使用户的APP(可以是一个WEB应用或数据库应用等等)及其运行环境做到“一次镜像,处处运行”。
    在这里插入图片描述
  • Linux容器技术的出现就解决了这样一个问题,而Docker就是在它的基础上发展过来的。将应用打成镜像,通过镜像成为运行在Docker容器上面的实例,而Docker容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。只需要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。
1.4 小结一句话
  • Docker解决了运行环境和配置问题软件容器,方便做持续集成并有助于整体发布的容器虚拟化技术。

二、传统虚拟机和容器的对比

2.1 传统虚拟机技术
  • 虚拟机(virtual machine)就是带环境安装的一种解决方案。
    • 它可以在一种操作系统里面运行另外一种操作系统。比如在Window10系统里面运行Linux系统CentOS7。应用程序对此毫无感知。因为在虚拟机看上去跟真实系统一模一样,而对于底层系统来说,虚拟机就是一个普通文件,不需要了删掉,对其他部分毫无影响。这类虚拟机完美的运行了另一套系统,能够应用程序,操作系统和硬件三者之间的逻辑不变。
    • 传统虚拟机技术基于安装在主操作系统上的虚拟机管理系统(如VirtualBox、VMware等),创建虚拟机(虚拟出各种硬件),在虚拟机上安装从操作系统,在从操作系统中安装部署各种应用。
    • 缺点:资源占用多、冗余步骤多、启动慢
2.2 容器虚拟化技术

由于前面虚拟机存在某些缺点,Linux发展了另一种虚拟化技术;

  • Linux容器(Linux Containes,缩写为LXC
    • Linux容器是与系统其他部分隔离开的一系列进程,从另一个镜像运行,并由镜像提供支持进程所需的全部文件。 容器提供的镜像包含了应用的所有依赖项,因而在从开发到测试再到生产的整个过程中,它都具有可移植性和一致性。
    • Linux容器不是模拟一个完整的操作系统而是对进程进行隔离。有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行
      在这里插入图片描述
  • Docker容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统虚拟机则是在硬件层面实现虚拟化
  • 与传统的虚拟机相比,Docker优势体现为启动速度快,占用体积小。
2.3 传统虚拟机技术与容器虚拟化技术对比

在这里插入图片描述

  • 比较了Docker和传统虚拟化方式的不同之处:
    • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
    • 容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
    • 每个容器之间互为隔离,每个容器有自己的文件系统,容器之间进程不会相互影响,能区分计算资源。
    • 图像对比说明
      在这里插入图片描述
特性容器虚拟机
启动秒级分钟级
大小一般为Mb一般为Gb
速度接近原生比较慢
系统支持数量单机支持上千个容器一般几十个

三、Docker能干嘛

  • Devops开发兼运维software enginerr 发展到 DevOps engineer

  • 一次构建、处处运行

    • 更快捷的应用交付和部署
    • 更便捷的升级和扩缩容
    • 更简单的系统运维
    • 更高效的计算资源利用
  • Docker应用场景
    Docker借鉴了标准集装箱的概念。标准集装箱将货物运往世界各地,Docker将这个模型运用到自己的设计中,唯一不同的是:集装箱运输货物,而Docker运输软件。
    在这里插入图片描述

四、Docker官网介绍

docker官网:http://www.docker.com
Docker Hub官网:https://hub.docker.com/

4.1 Centos Docker安装
  • Docker并非是一个通用的容器工具,它依赖于已存在并运行的Linux内核环境。
  • Docker实质上是在已经运行的Linux下制造了一个隔离的文件环境,因此它执行的效率几乎等同于所部署的Linux主机。
  • 因此,Docker必须部署在Linux内核的系统上,如果其他系统想部署Docker就必须安装一个虚拟Linux环境。

在这里插入图片描述

4.2 前提条件

目前,CentOS仅发行版本中的内核支持DockerDocker运行在CentOS7(64-bit)上,
要求系统为64位,Linux系统内核版本为3.8以上,这里选用Centos7.x

4.3 查看自己的内核

uname 命令用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等)。

4.4 三要素

三要素:镜像(image)、容器(container)、仓库(repository)

  • 镜像(image)

    • Docker镜像(image)就是一个只读的模板,镜像可以用来创建Docker容器,一个镜像可以创建很多容器
    • 它相当于是一个root文件系统。比如官方镜像centos7就包含了完整的一套centos7最小系统的root文件系统
    • 相当于容器的“源代码”,docker镜像文件类似于Java的类模板,而docker容器实例类似于javanew出来的实例对象。
  • 容器(container)

    • 从面向对象角度
      Docker利用容器(Container)独立运行的一个或一组应用,应用程序或服务运行在容器里面,容器就类似于一个虚拟化的运行环境,容器是用镜像创建的运行实例。就像是Java中的类和实例对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。
    • 从镜像容器角度
      可以把容器看做是一个简易版的Linux环境(包括root用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。
  • 仓库(repository)
    仓库(Repository)是集中存放镜像文件的场所。
    类似于
    Maven仓库,存放各种jar包的地方;
    github仓库,存放各种git项目的地方;
    Docker公司提供的官方registry被称为Docker Hub,存放各种镜像模板的地方。
    仓库分为公开仓库(Public)私有仓库(Private)两种形式。
    最大的公开仓库Docker Hub (https://hub.docker.com/)
    存放了数量庞大的镜像提供用户下载,国内的公开仓库包括阿里云、网易云等。

  • 小总结
    需要正确的理解仓库/镜像/容器这几个概念:

    • Docker本身是一个容器运行载体或称之为管理引擎,我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个打包好的运行环境就是image镜像文件。只有通过这个镜像文件才能生成Docker容器实例(类似Javanew出来一个对象)。
    • image文件可以看作是容器的模板。Docker根据image文件生成容器的实例。同一个image文件,可以生成多个同时运行的容器实例。
    • 镜像文件
      image文件生成的容器实例,本身也是一个文件,称为镜像文件。
    • 容器实例
      一个容器运行一种服务,当我们需要的时候,就可以通过docker客户
    • 仓库
      就是放一堆镜像的地方,我们可以把镜像发布到仓库中,需要的时候再从仓库中拉下来就可以了。
4.5 Docker架构
  • Docker是一个Client-Server结构的系统,后端是一个松耦合架构,众多模块各司其职。
    在这里插入图片描述
  • Docker守护进程运行在主机上,然后通过Socket连接从客户端访问,守护进程从客户端接受命令并管理运行在主机上的容器。容器,是一个运行时环境,就是我们前面说到的集装箱。可以对比mysql演示对比讲解。

在这里插入图片描述

  • Docker运行的基本流程
    在这里插入图片描述
    • 用户是使用Docker ClientDocker Daemon建立通信,并发送请求给后者。
    • Docker Daemon作为Docker架构中的主体部分,首先提供Docker Server的功能使其接受Docker Client的请求。
    • Docker Engine执行Docker内部的一系列工作,每一项工作都是以一个Job的形式的存在。
    • Job的运行过程中,当需要容器镜像时,则从Docker Registry中下载镜像,并通过镜像管理驱动Graph driver将下载镜像以Graph的形式存储。
    • 当需要为Docker容器运行资源或执行用户指令等操作时,则通过Exec driver来完成。
    • Libcontainer是一项独立的容器管理包,Network driver以及Exec driver都是通过Libcontainer来实现具体对容器进行的操作。

五、Docker安装

5.1 安装步骤

https://docs.docker.com/engine/install/centos/

  1. 确定你是CentOS7及以上版本
  2. 卸载旧版本
    卸载旧版本Docker的旧版本被称为DockerDocker -engine。如果安装了这些组件,则卸载它们以及相关的依赖项。
 $ sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
  1. yum安装gcc相关
    Centos7能上网前提条件
yum -y install gcc
yum -y install gcc-c++

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

  1. 安装需要的软件包
Installation methods
You can install Docker Engine in different ways, depending on your needs:
Most users set up Docker’s repositories and install from them, for ease of installation and upgrade tasks. This is the recommended approach.
Some users download the RPM package and install it manually and manage upgrades completely manually. This is useful in situations such as installing Docker on air-gapped systems with no access to the internet.
In testing and development environments, some users choose to use automated convenience scripts to install Docker.

译文:安装方法你可以根据你的需要,以不同的方式安装Docker Engine:大多数用户设置Docker的存储库并从其中进行安装,以简化安装和升级任务。这是推荐的方法。部分用户下载并手动安装RPM包,完全通过手动方式进行升级管理。这在一些情况下很有用,比如在没有互联网接入的气隙系统上安装Docker。在测试和开发环境中,一些用户选择使用自动化的方便脚本来安装Docker

Install using the repository
Before you install Docker Engine for the first time on a new host machine, you need to set up the Docker repository. Afterward, you can install and update Docker from the repository.
Set up the repository
Install the yum-utils package (which provides the yum-config-manager utility) and set up the repository.

译文:使用存储库进行安装在你第一次在一个新的主机上安装Docker引擎之前,你需要设置Docker存储库。之后,您就可以从存储库中安装和更新Docker了。设置存储库安装yum-utils包(它提供yum-config-manager实用程序)并设置存储库。
执行下面的命令

$sudo yum install -y yum-utils

在这里插入图片描述

  1. 设置stable镜像仓库
以下命令是国外的非常慢 不用
 $ sudo yum-config-manager \     
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
    采用阿里云
    yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  
  1. 更新yum软件包索引
yum makecache fast 不成功执行 yum makecache
  1. 安装DOCKER CE
 $sudo yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin
 或者 yum -y install docker-ce docker-ce-cli containerd.io
  1. 启动docker
 sudo systemctl start docker
 ps -ef | grep docker
  1. 测试
    Verify that Docker Engine is installed correctly by running the hello-world image.
    译文:通过运行hello-world映像来验证Docker Engine是否正确安装。
 sudo docker run hello-world
 或者 docker version

docker run 本地没有镜像会从远程库中下载
在这里插入图片描述
在这里插入图片描述

六、Docker下载加速

6.1 配置阿里云加速
  • 登录阿里云选择控制台按钮,账号可以使用XX宝的,免费
    在这里插入图片描述

  • 选择三条线的选择框,进行展开 在这里插入图片描述

  • 选择容器镜像服务
    在这里插入图片描述

  • 选择CentOS的操作文档
    在这里插入图片描述

  • 修改daemon配置文件,使用阿里云的加速器
    在这里插入图片描述

在这里插入图片描述

七、hello_world分析三要素配合

  • hello_world 执行命令
#由于本地没有hello-world这个镜像,所以会下载一个hello-world的镜像。并在容器内运行。
docker run hello-world
  • hello_world 执行流程
    在这里插入图片描述
    run 命令:先从本地镜像中拉取资源,如果没有再去仓库中去拉取。

八、为什么Docker会比Vm虚拟机快

  1. Docker有着比虚拟机更少的抽象层
    由于docker不需要Hypervisor(虚拟机)实现硬件资源虚拟化,运行在docker容器上的程序直接使用的都是实际物理机的硬件资源。因此在CPU、内存利用率上docker将会在效率上有明显优势。
  2. docker利用的是宿主机的内核,而不需要加载操作系统os内核
    当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。进而避免引寻、加载操作系统内核返回等比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载OS,返回新建过程是分钟级别的。而docker由于直接利用宿主机的操作系统,则省略了返回过程,因此新建一个docker容器只需要几秒钟。
    在这里插入图片描述
Docker容器虚拟机 (VM)
操作系统与宿主机共享OS宿主机OS上运行虚拟机OS
存储大小镜像小,便于存储于传输镜像庞大(vmdk、vdi等)
运行性能几乎无额外性能损失操作系统额外的CPU、内存消耗
移植性轻便、灵活、适应于Linux笨重、与虚拟机技术耦合度高
硬件亲和性面向软件开发者面向硬件运维者
部署速度快速、秒级较慢,10s以上

九、Docker常用命令

9.1 帮助启动类命令
  • 启动docker: systemctl start docker
  • 停止docker: systemctl stop docker
  • 重启docker: systemctl restart docker
  • 查看docker状态systemctl status docker
  • 开机启动systemctl enable docker
  • 查看docker概要信息docker info
  • 查看docker总体帮助文档docker --help
  • 查看docker命令帮助文档docker 具体命令 --help
9.2 镜像命令
9.2.1 docker images
  • 罗列出所有的镜像
    参数说明:-a 表示列出本地所有的镜像(含历史镜像层)、-q 表示只显示镜像ID、-f 表示过滤
  • docker images 展示的各项选项说明
    在这里插入图片描述
    各个选项说明:
    REPOSITORY表示镜像的仓库源
    TAG镜像的标签
    IMAGE ID镜像ID
    CREATED :镜像创建时间
    SIZE镜像大小
    同一仓库源可以有多个TAG版本,代表这个仓库源的不同的版本,我们使用REPOSITORY:TAG来定义不同的镜像。如果你不指定一个镜像的版本标签,例如你只使用ubuntu,docker将默认使用ubuntu:latest(最新版本)镜像
9.2.2 docker search
  • 从仓库中查找某个镜像的信息,可以查询前n条,默认去docker hub中搜索
格式:docker search 【OPTIONS】镜像名字
-f : 过滤
--limit 数量:只展示前几项
参数说明
NAME镜像名称
DESCRIPTION镜像说明
STARS点赞数量
OFFICIAL是否是官方的
AUTOMATED是否是自动构建的

在这里插入图片描述

  • docker search --limit : limit只列出N个镜像,默认25个。
    在这里插入图片描述
    • 验证是否默认拉取25个镜像
      在这里插入图片描述
      在这里插入图片描述
9.2.3 docker pull
  • 作用:从仓库中拉取某个镜像
格式:docker pull 镜像名字[:TAG]  #如果不指定TAG则默认为最新版本(latest)

docker pull redis:6.0.8
在这里插入图片描述

9.2.4 docker system df
  • 作用:查看镜像/容器/数据卷所占的空间
    在这里插入图片描述
9.2.5 docker rmi

作用:删除某个镜像,通过镜像ID -f //强制删除

  • 删除单个镜像
    格式:docker rmi -f 镜像ID
    下图是强制删除镜像ID案例:
    在这里插入图片描述
    在这里插入图片描述
  • 删除多个
    格式:docker rmi -f 镜像名1:TAG 镜像名2:TAG
  • 删除全部
    doker rmi -f $(docker images -qa) //$(docker images -qa)将id全部查询出来
    在这里插入图片描述
9.2.6 面试题:docker 虚悬镜像是什么?

虚悬镜像:仓库名、标签都是的镜像,俗称虚悬镜像dangling image
在这里插入图片描述

9.3 命令自动补全
  • docker支持命令自动补全功能,当输入镜像名前几位时,可以按tab键自动补全镜像名称、tag等。
#如果镜像中有ubuntu,查看输入ub按下tab是否可以补全
docker run ub
  • 如果按下tab时没有自动补全,可以按以下步骤操作:
  1. 检查是否安装了bash-completion(命令补全增强包)
# 检查有 /usr/share/bash-completion/bash_completion这个文件
ls /usr/share/bash-completion/bash_completion
  1. 如果有/usr/share/bash-completion目录,但是没有/usr/share/bash-completion/bash_completion文件(centos6 /etc/bash_completion文件),则需要安装bash_completion
yum -y install bash-completion
  1. 检查是否安装了docker的自动补全
# 检查/usr/share/bash-completion/completions文件夹下是否有docker开头的自动补全
# docker安装完后会在该文件夹下生成自动补全文件docker
# 如果安装了docker-compose,则该文件夹下还会有 docker-compose文件
ll /usr/share/bash-completion/completions/docker*
  1. 如果已经安装了docker自动补全,使用source命令使其生效
souce /usr/share/bash-completion/completions/docker
  1. 再次使用tab查看是否可以自动补全
# 如果镜像中有ubuntu,查看输入ub按下tab是否可以补全
docker run ub
  1. 如果有报错,且报错提示_get_comp_words_by_ref: command not found。说明bash-completion的配置文件没有生效,需要source一下
# 对于centos7,bash-completion安装的是2.x版本,配置文件为/usr/share/bash-completion/bash_completion
source /usr/share/bash-completion/bash_completion

# 如果是centos6,自动安装的bash-completion最新版为1.x版本,配置文件为/etc/bash_completion
# bash /etc/bash_completion
  1. 再次使用tab查看是否可以自动补全
# 如果镜像中有ubuntu,查看输入ub按下tab是否可以补全
docker run ub

十、容器命令

10.1 容器命令——新建+启动容器
  • 格式:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
  • 作用:启动交互式容器(前台命令行)
  • OPTIONS说明(常用):有些是一个减号,有些是两个减号
    • –(两个减号) name=“容器新名字” 为容器指定一个名称;
    • -d: 后台运行容器并返回容器ID,也即启动守护式容器(后台运行)
    • -i: (interactive)以交互模式运行容器,通常与-t同时使用;
    • -t: (tyy)为容器重新分配一个伪输入终端,通常与-i同时使用;也即启动交互式容器(前台有伪终端,等待交互)
    • -e: 为容器添加环境变量
    • -P: 随机端口映射,大写P
    • -p: 指定端口映射,小写p
命令说明
-p hostPort:containerPort端口映射,例如-p 8080:80
-p ip:hostPort:containerPort配置监听地址,例如 -p 10.0.0.1:8080:80
-p ip::containerPort随机分配端口,例如 -p 10.0.0.1::80
-p hostPort1:containerPort1 -p hostPort2:containerPort2指定多个端口映射,例如-p 8080:80 -p 8888:3306
  • 启动交互式容器
  1. 以交互方式启动ubuntu镜像
# -i 交互模式
# -t 分配一个伪输入终端tty
# ubuntu 镜像名称
# /bin/bash(或者bash) shell交互的接口
docker run -it ubuntu /bin/bash
  • 启动守护式容器
  1. 大部分情况下,我们系统docker容器服务时在后台运行的,可以通过-d指定容器的后台运行模式:
docker run -d 容器名
  1. 注意事项
  • 如果使用docker run -d ubuntu 尝试启动守护式的ubuntu,会发现容器启动后就自动退出了。
  • 因为Docker容器如果在后台运行,就必须要有一个前台进程。容器运行的命令如果不是那些一直挂起的命令(例如top、tail),就会自动退出。
10.2 容器命令——列出当前所有正在运行的容器
  • OPTIONS说明(常用):
    -a: 列出当前所有正在运行的容器+历史上运行过
    -l: 显示最近创建的容器
    -n: 显示最近n个创建的容器
    -q: 静默模式,只显示容器编号

    ubuntu为例进行说明:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
10.3 容器命令——退出容器
  • exit
    run进去容器,exit退出,容器停止
  • ctrl + p + q
    run进去容器,ctrl + p + q退出,容器不停止
    在这里插入图片描述
10.4 容器命令——启动已停止运行的容器

docker start 容器ID或者容器名
在这里插入图片描述

10.5 容器命令——重启容器

docker restart 容器ID或者容器名

10.6 容器命令——停止容器

docker stop 容器ID或者容器名

10.7 容器命令——强制停止容器

docker kill 容器ID或容器名

10.8 容器命令——删除已停止的容器

一次性删除多个容器实例

  • docker rm -f $(docker ps -a -q)
  • docker ps -a -q | xargs docker rm
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
10.9 查看容器日志

docker logs 容器ID
在这里插入图片描述

10.10 查看容器内运行的进程

docker top 容器ID
在这里插入图片描述

10.11 查看容器内部细节

docker inspect 容器ID
在这里插入图片描述

10.12 进入正在运行的容器并以命令进行交互
  • 进入正在运行的容器,并以命令行交互
docker exec -it 容器ID bashShell
  • 重新进入
docker attach 容器ID
  • docker exec 和 docker attach 区别

    • attach直接进入容器启动命令的终端,不会启动新的进程,用exit退出会导致容器的停止
    • exec是在容器中打开新的终端,并且可以启动新的进程,用exit退出不会导致容器的停止(推荐使用这个)
  • 如果有多个终端,都对同一个容器执行了 docker attach,就会出现类似投屏显示的效果。一个终端中输入输出的内容,在其他终端上也会同步的显示。

注意:read escape sequence 是快捷键 Ctrl + p + q
在这里插入图片描述

10.13 从容器内拷贝文件到主机上

docker cp 容器ID:容器内路径 目的主机路径
在这里插入图片描述

10.14 导入和导出容器
  • 镜像做备份和容器做备份
    export: 导出容器的内容流作为一个tar归档文件[对应import命令] import: 从tar包中的内容创建一个新的文件系统再导入为镜像[对应export] 一般默认导出当前路径
    案例:
    docker export 容器ID > 文件名.tar
    cat 文件名.tar | docker import - 镜像用户/镜像名:镜像版本号
    在这里插入图片描述
    在这里插入图片描述
10.15 no space left on device
容器满了
  • 解决方法:
    停止所有的container,这样才能够删除其中的images:
    docker stop $(docker ps -a -q)
    如果想要删除所有container的话再加一个指令:
    docker rm $(docker ps -a -q)

  • docker overlay2磁盘爆满,进行目录迁移
    https://blog.csdn.net/zfw_666666/article/details/124314741
    docker 最新版 --data-root 否则为–graph ,路径中不要有双引号
    在这里插入图片描述
    在这里插入图片描述

十一、Docker分层概念

11.1 docker镜像是什么
  • 镜像一种轻量级、可执行的独立软件包,它包含运行某个软件所需的所有内容,我们把应用程序和配置依赖打包好形成一个可交付的运行环境(包括代码、运行时需要的库、环境变量和配置文件等),这个打包好的运行环境就是image镜像文件
  • 只有通过这个镜像文件才能生成Docker容器实例(类似Java中new出来一个对象)
11.2 Docker镜像加载原理
  • UnionFS(联合文件系统)

    • UnionFS(联合文件系统)Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)Union文件系统是Docker镜像的基础。镜像可以通过分层来进行继承。基于基础(没有父镜像),可以制作各种具体的应用镜像。
      在这里插入图片描述
    • 特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
  • bootfs和rootfs

    • docker的镜像实际上由一层一层的文件组成,这种层级的文件系统UnionFS。

    • bootfs(boot file system)主要包含bootloader和kerner、bootloadr主要是引导加载kernerl,Linux刚启动时就会加载bootfs文件系统,在Docker镜像的最底层是引导文件系统bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。

    • rootfs(root file system),在bootfs之上,包含的就是典型Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件,rootfs就是各种不同的操作系统发行版,比如Ubuntu、Centos等等。
      在这里插入图片描述

  • 平时我们安装进虚拟机的CentOS都是好几个G,为什么docker这里才200M?
    对于一个精简的OS,rootfs可以很小,只需要包含最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就行了,由此可见对不同的linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以公用bootfs。

在这里插入图片描述

11.4 分层的镜像
  • Docker支持扩展现有镜像,创建新的镜像。新镜像是从base镜像一层一层叠加生成的。
# Version: 0.0.1
FROM debian  # 直接在debain base镜像上构建
MAINTAINER mylinux
RUN apt-get update && apt-get install -y emacs # 安装emacs
RUN apt-get install -y apache2 # 安装apache2
CMD ["/bin/bash"] # 容器启动时运行bash

在这里插入图片描述

11.5 为什么Docker镜像采用这种分层结构
  • 镜像分层最大的一个好处就是共享资源,方便复制迁移,就是为了复用。
  • 比如说有多个镜像都从相同的base镜像构建而来,那么Docker Host只需在磁盘上保存一份base镜像;
  • 同时内存中也只需加载一份base镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
11.6 commit命令
  • docker commit 提交容器副本使之成为一个新的镜像
  • docker commit -m=“提交的描述信息” -a=“作者” 容器ID要创建的目标镜像名:[标签名]
  • 案例演示ubuntu安装vim

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

11.7 小总结
  • Docker中的镜像分层,支持通过扩展现有镜像,创建新的镜像。新镜像是从base镜像一层一层叠加生成的。
    例如:
# Version: 0.0.1
FROM debian  # 直接在debain base镜像上构建
MAINTAINER mylinux
RUN apt-get update && apt-get install -y emacs # 安装emacs
RUN apt-get install -y apache2 # 安装apache2
CMD ["/bin/bash"] # 容器启动时运行bash

镜像创建过程:
在这里插入图片描述

  • 容器层
    当容器启动时,一个新的可写层将被加载到镜像的顶部,这一层通常被称为容器层,容器层之下的都叫镜像层

所有对容器的改动,无论添加、删除、还是修改文件都只会发生在容器层中。

只有容器层是可写的,容器层下面的所有镜像层都是只读的。
如图:

在这里插入图片描述

十二、本地镜像发布到阿里云

在这里插入图片描述

  • 选择控制台,进入容器镜像服务

  • 选择个人实例

在这里插入图片描述+ 命名空间
在这里插入图片描述

  • 仓库名称
    在这里插入图片描述
    在这里插入图片描述

  • 配置登录密码(不是阿里云登录密码而是凭证密码)
    在这里插入图片描述

  • 进入管理界面获得脚本
    在这里插入图片描述

  • 执行推送命令
    在这里插入图片描述

十三、Docker私有库搭建

  • Docker Registry
    • 官方Docker Hub地址:https://hub.docker.com/但是中国大陆访问太慢了。
    • Dockerhub、阿里云这样的公共镜像仓库不太方便,涉及机密的公司不可能提供镜像到公网,所以需要出创建一个本地私人仓库供给团队使用,基于公司内部项目构建镜像。
    • Docker Registry是官方提供的工具,可以用于构建私有镜像仓库。
  • 环境搭建
    Docker Registry 也是Docker Hub 提供的一个镜像,可以直接拉取运行。
    步骤 如下:
  1. 拉取镜像
docker pull registry
  1. 启动Docker Registry
docker run -d -p 5000:5000 -v /app/myregistry/:/tmp/registry --privileged=true registry
  1. 验证(查看私服中的所有镜像)
curl http://192.168.xxx.xxx:5000/v2/_catalog

Registry会返回json格式的所有镜像目录

  • 向Registry私仓中上传镜像

    • 配置docker允许接收http请求
      (配置方式和上传到nexus私仓相同)
      修改/etc/docker/daemon.json, 添加insecure-registries允许http:
{
    "registry-mirros": ["https://xxxx.mirror.aliyuncs.com"],
    "insecure-registries": ["192.168.xxx.xxx:5000"]
}

然后重启docker:(新版本的docker会立即生效)

# centos6 的命令
sudo chkconfig daemon-reload
sudo service docker restart
# centos7 的命令
sudo systemctl daemon-reload
sudo systemctl restart docker
  • 推送到私仓
    步骤:
  1. 添加一个对应私仓地址的tag
docker tag lee/myubuntu:1.0.1 192.168.xxx.xxx:5000/lee/myubuntu:1.0.1
  1. push 到私仓
docker push 192.168.xxx.xxx:5000/lee/myubuntu:1.0.1
  1. 查看私仓中镜像目录验证
curl http://192.168.xxx.xxx:5000/v2/_catalog
  1. 拉取验证
docker pull 192.169.xxx.xxx:5000/lee/myubuntu:1.0.1

十四、容器数据卷是什么

14.1 容器卷记得加 --privileged=true
  • Docker挂载主机目录访问如果出现cannot open directory.:Permission denied
    解决办法:在挂载目录后多加一个–privileged=true参数即可
  • –privileged=true 作用
    • 如果是CentOS7安全模块会比之前系统版本加强,不安全的会先禁止,所以目录挂载的情况被默认为不安全的行为。
    • 在SELinux里面挂载目录被禁止掉了额。如果要开启,我们一般使用–privileged=true命令,扩大容器的权限解决挂载目录没有权限的问题,也即使用该参数,container内的的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限。
14.2 参数V作用

默认情况,仓库被创建在容器的var/lib/registry目录下,建议自行用容器卷映射,方便于宿主机联调。

docker run -d -p 5000 -v /zzyyuse/myregistry/:/tmp/registry --privileged=true registry
14.3 容器卷是什么?
  • 概念:卷就是目录或文件,存在一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性;
  • 卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。
    • 一句话:有点类似我们Redis里面的rdb和aof文件
    • 将docker容器内的数据保存进宿主机的磁盘中
    • 运行一个带有容器卷存储功能的容器实例
docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录 镜像名
14.4 容器卷作用?
  • 将运用于运行的环境打包镜像,run后形成容器实例运行,但是我们对数据的要求希望是持久化的
    Docker容器产生的数据,如果不备份,那么当容器实例删除后,容器内的数据自然也就没有了。为了能保存数据在docker中我们使用卷。
  • 特点:
    • 数据卷可在容器之间共享或重用数据
    • 卷中的更改可以直接实时生效
    • 数据卷中的更改不会包含在镜像的更新中
    • 数据卷的生命周期一直持续到没有容器使用它为止
14.5 数据卷实例
  1. 宿主VS容器之间映射添加容器卷
  • 命令
格式:docker run -it -v --privileged=true -v /宿主机绝对路径目录:/容器内目录 镜像名
  • 实验:
    容器内没有这个目录docker会自建这个目录

在这里插入图片描述

  • 容器内目录产生的数据会映射到宿主机目录中,主机目录中创建的文件也会同步到宿主机中。

    • 在主机目录和宿主机目录中创建文件会互相同步
      在这里插入图片描述

在这里插入图片描述

  • 查看容器卷是否挂载成功
命令:docker inspect 容器ID

在这里插入图片描述

  • 容器停止,在宿主机中目录中添加stopMyu3Test.txt,重新启动容器发现容器目录stopMyu3Test.txt依然存在
    在这里插入图片描述
    在这里插入图片描述
  1. 读写规则映射添加说明
  • 读写规则默认为读写
docker run -it --privileged=true -v /宿主机绝对路径:/容器内目录:rw 镜像名
默认权限为读写权限
  • 只读权限
docker run -it --privileged=true -v /宿主机绝对路径:/容器内目录:ro 镜像名
容器实例内部被限制,只能读取不能写

在这里插入图片描述
在这里插入图片描述
容器被限制,宿主机不被限制。
在这里插入图片描述

  1. 卷的继承和共享
  • 容器1完成和宿主机的映射就是前面的命令
  • 容器2继承容器1的卷规则
docker run -it --privileged=true --volumes-from 父类 --name u2 ubuntu

在这里插入图片描述
myu5和myu6是独立的
当myu6继承myu5时,数据是一致的
当myu5退出后,myu6和宿主机依旧可以实现数据的共享
当myu5再次启动后,数据和宿主机、myu6保持一致

十五、安装Mysql

15.1 Docker安装Mysql
  1. 以安装Mysql 5.7 为例
docker pull mysql:5.7
  • Mysql单机
  1. 简单版Mysql5.7安装
    简单的启动Mysql容器:
#需要使用 -e 配置环境变量 MYSQL_ROOT_PASSWORD(mysql root 用户的密码)
docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7
  1. 简单版的Mysql会存在以下问题:
  • 中文乱码
  • 没有容器卷映射
    启动docker容器后,可以正常的连接、创建数据库,创建表,插入数据。但是插入中文则会报错。
    例如
-- 创建db01数据库
create database db01;
-- 切换到db01;
use db01;
-- 创建表
create table t1(id int, name varchar(20));

-- 插入英文可以正常插入
insert into t1 values(1, 'abc');

-- 插入中文报错
insert into t1 values(2, '张三');

这是因为docker默认的字符集的问题,可以在mysql中使用以下命令查看数据库字符集:

show variables like 'character%';

返回的字符集中,character_set_databasecharacter_set_server等都为latin1字符集,所以会报错。
而且因为启动容器时没有配置容器卷映射,当容器意外被删时,数据无法找回。

15.2 实际应用版Mysql5.7安装
  1. 启动Mysql容器,并配置容器卷映射:
docker run -d -p 3306:3306 \
           --privileged=true \
           -v /app/mysql/log:/var/log/mysql \
           -v /app/mysql/data:/var/lib/mysql \
           -v /app/mysql/conf:/etc/mysql/conf.d \
           -e MYSQL_ROOT_PASSWORD=root \
           --name mysql \
           mysql:5.7
  1. /app/mysql/conf下新建my.cnf,通过容器卷同步给mysql实例,解决中文乱码问题:
[client]
default_character_set=utf8
[mysqld]
collation_server = utf8_general_ci
character_set_server = utf8
  1. 重启mysql容器,使得容器重新加载配置文件:
docker restart mysql

此时便解决了中文乱码(中文插入报错)问题。
而且因为启动时将容器做了容器卷映射,将mysql的配置(映射到/app/mysql/conf)、数据(映射到/app/mysql/data)、日志(映射到/app/mysql/log)都映射到了宿主机实际目录,所以及时删除了容器,也不会产生太大影响。只需要再执行一下启动Mysql容器命令,容器卷还按相同位置进行映射,所有的数据便可以正常恢复。

15.3 Mysql主从复制安装
15.3.1 安装主服务器容器实例(端口号3307)
  1. 启动容器实例
docker run -p 3307:3306 \
           --name mysql-master \
           --privileged=true \
           -v /app/mysql-master/log:/var/log/mysql \
           -v /app/mysql-master/data:/var/lib/mysql \
           -v /app/mysql-master/conf:/etc/mysql \
           -e MYSQL_ROOT_PASSWORD=root \
           -d mysql:5.7
  1. 进入/app/mysql-master/conf,新建my.cnf配置文件:
[mysqld]
## 设置server_id, 同一个局域网中需要唯一
server_id=101
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql
## 开启二进制日志功能
log-bin=mall-mysql-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=mixed
## 二进制日志过期清理时间。默认值为0,表示不自动清理
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断
## 如:1062错误是指一些主键重复,1032错误是因为主从数据库数据不一致
slave_skip_errors=1062
  1. 重启容器实例
docker restart mysql-master
  1. 进入容器实例内
docker exec -it mysql-master /bin/bash
  1. 登录mysql,创建数据同步用户
-- 首先使用 mysql -uroot -p 登录mysql
-- 创建数据同步用户
create user 'slave'@'%' identified by '123456';
-- 授权
grant replication slave, replication client on *.* to 'slave'@'%';
flush privileges;
15.3.2 安装从服务器容器实例(端口号3308)
  1. 启动容器服务
docker run -p 3308:3306 \
           --name mysql-slave \
           --privileged=true \
           -v /app/mysql-slave/log:/var/log/mysql \
           -v /app/mysql-slave/data:/var/lib/mysql \
           -v /app/mysql-slave/conf:/etc/mysql \
           -e MYSQL_ROOT_PASSWORD=root \
           -d mysql:5.7
  1. 进入/app/mysql-slave/conf目录,创建my.cnf配置文件:
[mysqld]
## 设置server_id, 同一个局域网内需要唯一
server_id=102
## 指定不需要同步的数据库名称
binlog-ignore-db=mysql
## 开启二进制日志功能,以备slave作为其它数据库实例的Master时使用
log-bin=mall-mysql-slave1-bin
## 设置二进制日志使用内存大小(事务)
binlog_cache_size=1M
## 设置使用的二进制日志格式(mixed,statement,row)
binlog_format=mixed
## 二进制日志过期清理时间。默认值为0,表示不自动清理
expire_logs_days=7
## 跳过主从复制中遇到的所有错误或指定类型的错误,避免slave端复制中断
## 如:1062错误是指一些主键重复,1032是因为主从数据库数据不一致
slave_skip_errors=1062
## relay_log配置中继日志
relay_log=mall-mysql-relay-bin
## log_slave_updates表示slave将复制事件写进自己的二进制日志
log_slave_updates=1
## slave设置只读(具有super权限的用户除外)
read_only=1
  1. 修改完配置需要重启slave容器实例
docker restart mysql-slave
15.3.3 配置主从同步
  1. 在主数据库中查看主从同步状态:
  • 进入主数据库容器:
docker exec -it mysql-master /bin/bash
  • 进入Mysql
mysql -uroot -p
  • 查看主从同步状态
show master status;

主要查看返回结果的文件名File、当前位置Position

  1. 进入从数据库容器,配置主从复制:
  • 进入从数据库容器:
docker exec -it mysql-slave /bin/bash
  • 进入数据库
mysql -uroot -p
  • 配置从数据库所属的主数据库:
-- 格式:
-- change master to master_host='宿主机ip',master_user='主数据库配置的主从复制用户名',master_password='主数据库配置的主从复制用户密码',master_port=宿主机主数据库端口,master_log_file='主数据库主从同步状态的文件名File',master_log_pos=主数据库主从同步状态的Position,master_connect_retry=连接失败重试时间间隔(秒);

change master to master_host='192.168.xxx.xxx',master_user='slave',master_password='123456',master_port=3307,master_log_file='mall-mysql-bin.000001',master_log_pos=769,master_connect_retry=30;
  • 查看主从同步状态
# \G 可以将横向的结果集表格转换成纵向展示。
# slave status的字段比较多,纵向展示比友好
show slave status \G;

除了展示刚刚配置的主数据库信息外,主要关注 Slave_IO_RunningSlave_SQL_Running。目前两个值应该都为 No,表示还没有开始。

  • 开启主从同步:
start slave;
  • 再次查看主从同步状态,Slave_IO_RunningSlave_SQL_Running都变为Yes
  1. 主从复制测试:
  • 在主数据库上新建库、使用库、新建表、插入数据
create database db01;
use db01;
create table t1 (id int, name varchar(20));
insert into t1 values (1, 'abc');
  • 在从数据库上使用库、查看记录
show databases;
use db01;
select * from t1;

十六、安装Redis

以Redis6.0.8 为例:

docker pull redis:6.0.8
16.1 简单版Redis

简单的启动Redis容器

docker run -p 6379:6379 -d redis:6.0.8

简单版没有配置容器卷映射,当容器被删除时数据无法恢复。

16.2 实际应用版Redis

配置文件、数据文件都和容器卷进行映射
步骤:

  1. 宿主机创建目录/app/redis
  2. /app/redis 下创建文件redis.conf ,主要修改以下几项配置
# 开启密码验证(可选)
requirepass 123

# 允许redis外地连接,需要注释掉绑定的IP
# bind 127.0.0.1

# 关闭保护模式(可选)
protected-mode no

# 注释掉daemonize yes,或者配置成 daemonize no。因为该配置和 docker run中的 -d 参数冲突,会导致容器一直启动失败
daemonize no

# 开启redis数据持久化, (可选)
appendonly yes
  1. 启动docker容器:(因为要使用自定义的配置文件,所以需要指定容器运行的命令为redis-server容器内配置文件路径)
docker run -d -p 6379:6379 --name redis --privileged=true \
           -v /app/redis/redis.conf:/etc/redis/redis.conf \
           -v /app/redis/data:/data \
           redis:6.0.8 \
           redis-server /etc/redis/redis.conf
16.3 集群存储算法
  1. 分布式存储算法
  • 哈希取余算法分区
  • 一致性哈希算法分区
  • 哈希槽算法分区
  1. 哈希取余算法
    算法描述:hash(key) % N(其中,key是要存入Redis的键名,N是Redis集群的机器台数)。用户每次读写操作,都是根据传入的键名经过哈希运算,对机器台数取余决定该键存储在哪台服务器上。

优点:简单直接有效,只需要预估好数据规划好节点,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。

缺点:原来规划好的节点,如果进行了扩容或者缩容,导致节点有变动,映射关系需要重新进行计算。在服务器个数固定不变时没问题,如果需要弹性扩容或者故障停机的情况下,原来取模公式中的 N就会发生变化,此时经过取模运算的结果就会发生很大变化,导致根据公式获取的服务器变得不可控。

  1. 一致性哈希算法
    算法背景:一致性哈希算法是为了解决哈希取余算法中的分布式缓存数据变动和映射问题。当服务器个数发生变化时,尽量减少影响到客户端与服务器的映射关系。

算法描述:
一致性哈希算法必然有个hash函数并按照算法产生Hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个Hash区间[0, 2^32 - 1],这是一个线性空间。但是在这个算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。

它也是按照使用取模的方式。前面的哈希取余算法是对节点个数进行取模,而一致性哈希算法是对 2^32取模。

简单来说,一致性Hash算法将整个哈希值空间组成一个虚拟的圆环。如假设某个哈希函数H的值空间为 0到2^32 - 1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4…直到2^32 - 1,也就是说0点左侧的第一个点代表 2^32 - 1。0 和 2^32 - 1在零点钟方向重合,我们把这个由 2^32个点组成的圆环称为Hash环。
在这里插入图片描述
有了哈希环之后,还需要进行节点映射,将集群中各个IP节点映射到环上的某一个位置。

将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希。这样每台机器就能确定其在哈希环上的位置。

假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希值后在环空间的位置如下:
在这里插入图片描述
key落到服务器的落键规则。当我们需要存储一个key键值对时,首先计算key的hash值(hash(key)),将这个key使用相同的函数hash,计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储字该节点上。

假如我们有ObjectA、B、C、D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性hash算法,数据A会被定位到NodeA上,B被定位到NodeB上,C被定位到NodeC上,D被定位到NodeD上。
在这里插入图片描述
假设NodeC宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重新定位到NodeD。

一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间的数据,其他不会受到影响。

即:假设NodeC宕机,只会影响到Hash定位到NodeB到NodeC之间的数据,并且这些数据会被转移到NodeD进行存储。
在这里插入图片描述
假如需要扩容增加一台节点NodeX,NodeX的hash(ip)位于NodeB和NodeC之间,那受到影响的就是NodeB 到 NodeX 之间的数据。重新将B到X的数据录入到X节点上即可,不会导致Hash取余全部数据重新洗牌的后果。
在这里插入图片描述
但是Hash环会存在数据倾斜问题。

一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象都集中到某一台或某几台服务器)。
在这里插入图片描述
为了解决数据倾斜问题,一致性哈希算法引入了虚拟节点机制。

对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。具体做法可以先确定每个物理节点关联的虚拟节点数量,然后在IP或主机名后面加上编号。

例如,可以对NodeA节点虚拟出 NodeA#1、NodeA#2、NodeA#3,对NodeB虚拟出NodeB#1、NodeB#2、NodeB#3的节点,形成六个虚拟节点。
在这里插入图片描述
优点:加入和删除节点时,只会影响哈希环中顺时针方向相邻节点,对其他节点无影响。

缺点:数据的分布和节点的位置有关,因为这些节点不是均匀分布在哈希环上的,所以在数据进行存储时达不到均匀部分的效果。

  1. 哈希槽分区
    哈希槽分区是为了解决一致性哈希算法的数据倾斜问题。

哈希槽实质上就是一个数组,数组 [0, 2^14 - 1]形成的 hash slot空间。

目的是为了解决均匀分配的问题。在数据和节点之间又加入了一层,把这层称之为槽(slot),用于管理数据和节点之间的关系。就相当于节点上放的是槽,槽里面放的是数据。

槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。

哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。

一个集群只能有 16394个槽,编号 0 - 16383(2^14 - 1)。这些槽会分配给集群中所有的主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点,集群会记录节点和槽的对应关系。

解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,根据余数决定key落到哪个槽里。

slot = CRC16(key) % 16384

以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

  1. Redis集群存储策略
    Redis集群使用的就是哈希槽。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置在哪个槽,集群的每个节点负责一部分hash槽。

哈希槽数量16384(2^14)的决定原因:
CRC16算法产生的hash值有 16bit,该算法可以产生 2^16 = 65536个值。但是为了心跳方便和数据传输最大化,槽的数量只能有 2^14个。

  • 如果槽位数量为65535个,那么发送心跳信息的消息头将达到 8k,发送的心跳包过于庞大。在消息头中最占空间的是 myslots[CLUSTER_SLOTS/8]。当槽位为65536时,这块的大小是 :
65536 ÷ 8 ÷ 1024 = 8Kb

每秒中redis节点需要发送一定数量的ping消息作为心跳,如果槽位为65536,那么这个ping消息头就会太大浪费带宽。

  • redis集群的主节点数量基本不可能超过1000个。集群节点越多,心跳包的消息体内携带的数据越多。如果节点超过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点超过1000个。对于节点数在1000以内的redis cluster集群,16384个槽位足够了,没有必要扩展到65536个。
  • 槽位越小,节点少的情况下压缩比越高,容易传输。Redis主节点的配置信息中它锁负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率 slots / N(N为节点数)很高的话,bitmap的压缩率就很低。如果节点数很少,而哈希槽数很多的话,bitmap的压缩率就很低。
原文:
正常的心跳数据包带有节点的完整配置,使得可以用幂等方式用旧的节点替换旧节点,以便更新旧的配置。这意味着它们包含原始节点的插槽配置,该节点使用 2k 的空间和 16k 的插槽,而不是使用 8k 的空间(使用65k的插槽)。
同时,因为其他设计折衷,Redis集群的主节点不太可能扩展到1000个以上

Redis集群中内置了16384个哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在Redis集群中放置一个Key-Value时,redis先对key使用 CRC16 算法算出一个结果,然后把结果对 16384 取余,这样每个key都会对应一个编号在0-16383之间的哈希槽,也就是映射到某个节点上。
在这里插入图片描述

@Test
public void test() {
    // import io.lettuce.core.cluster.SlotHash;
    System.out.println(SlotHash.getSlot('A'));  // 计算结果6373,存入上图的Node2
    System.out.println(SlotHash.getSlot('B'));  // 计算结果10374,存入上图的Node2
    System.out.println(SlotHash.getSlot('C'));  // 计算结果14503,存入上图的Node3
    System.out.println(SlotHash.getSlot('Hello'));  // 计算结果866,存入上图的Node1
}
16.4 3主3从Redis集群
16.4.3.1 搭建

使用docker搭建3主3从的Redis集群,每台主机都对应一台从机。
启动6台redis容器

# 启动第1台节点
# --net host 使用宿主机的IP和端口,默认
# --cluster-enabled yes 开启redis集群
# --appendonly yes 开启redis持久化
# --port 6381 配置redis端口号
docker run -d --name redis-node-1 --net host --privileged=true -v /app/redis-cluster/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381

# 启动第2台节点
docker run -d --name redis-node-2 --net host --privileged=true -v /app/redis-cluster/share/redis-node-2:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6382

# 启动第3台节点
docker run -d --name redis-node-3 --net host --privileged=true -v /app/redis-cluster/share/redis-node-3:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6383

# 启动第4台节点
docker run -d --name redis-node-4 --net host --privileged=true -v /app/redis-cluster/share/redis-node-4:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6384

# 启动第5台节点
docker run -d --name redis-node-5 --net host --privileged=true -v /app/redis-cluster/share/redis-node-5:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6385

# 启动第6台节点
docker run -d --name redis-node-6 --net host --privileged=true -v /app/redis-cluster/share/redis-node-6:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6386

构建主从关系

  • 进入节点1(或其中任意一个节点):
docker exec -it redis-node-1 /bin/bash
  • 构建主从关系
# 宿主机IP:端口
redis-cli --cluster create 192.168.xxx.xxx:6381 192.168.xxx.xxx:6382 192.168.xxx.xxx:6383 192.168.xxx.xxx:6384 192.168.xxx.xxx:6385 192.168.xxx.xxx:6386 --cluster-replicas 1
  • redis尝试自动进行主从节点分配
  • 因为我们的docker容器IP相同,所以会出现警告,可以直接忽略该警告
[WARNING] Some slaves are in the same host as their master
  • redis自动分配结果完成后,需要输入 Yes 确认配置信息:
M: f451eb48bbc0a7c31c7da022ffe80cc1696e0f37 192.168.xxx.xxx:6381
   slots:[0-5460] (5461 slots) master
M: 05984211b8c38222a73abeff1d4e459c0fe1efbc 192.168.xxx.xxx:6382
   slots:[5461-10922] (5462 slots) master
M: 1fc935c12b1d34a7df50aed643c195eb29bb3435 192.168.xxx.xxx:6383
   slots:[10923-16383] (5461 slots) master
S: f8d0de47114bf33438747acd713cce4e412ae721 192.168.xxx.xxx:6384
   replicates 1fc935c12b1d34a7df50aed643c195eb29bb3435
S: de0b393c17e452d856f6de2b348e9ca4e5aa4002 192.168.xxx.xxx:6385
   replicates f451eb48bbc0a7c31c7da022ffe80cc1696e0f37
S: 0c0767e13a09ee48541738d4163592cd9842c143 192.168.xxx.xxx:6386
   replicates 05984211b8c38222a73abeff1d4e459c0fe1efbc
Can I set the above configuration? (type 'yes' to accept):
  • 输入Yes确认后,redis会向其他节点发送消息加入集群,并分配哈希槽:
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.....
>>> Performing Cluster Check (using node 192.168.xxx.xxx:6381)
M: f451eb48bbc0a7c31c7da022ffe80cc1696e0f37 192.168.xxx.xxx:6381
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 1fc935c12b1d34a7df50aed643c195eb29bb3435 192.168.xxx.xxx:6383
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 05984211b8c38222a73abeff1d4e459c0fe1efbc 192.168.xxx.xxx:6382
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 0c0767e13a09ee48541738d4163592cd9842c143 192.168.xxx.xxx:6386
   slots: (0 slots) slave
   replicates 05984211b8c38222a73abeff1d4e459c0fe1efbc
S: f8d0de47114bf33438747acd713cce4e412ae721 192.168.xxx.xxx:6384
   slots: (0 slots) slave
   replicates 1fc935c12b1d34a7df50aed643c195eb29bb3435
S: de0b393c17e452d856f6de2b348e9ca4e5aa4002 192.168.xxx.xxx:6385
   slots: (0 slots) slave
   replicates f451eb48bbc0a7c31c7da022ffe80cc1696e0f37
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

查看集群状态:

  • 进入容器节点1(或集群中其他节点):
docker exec -it redis-node-1 /bin/bash
  • 使用redis-cli连接到6381节点:
redis-cli -p 6381
  • 使用redis的相关命令查看集群状态:
cluster info

其中,分配的哈希槽数量 cluster_slots_assigned为16384,集群节点数量cluster_known_nodes为6

  • 查看集群节点信息
cluster nodes
16.4.3.2 Redis集群读写出错

当使用 redis-cli连接redis集群时,需要添加 -c参数,否则可能会出现读写出错。
示例:

  • 进入容器节点1
docker exec -it redis-node-1 /bin/bash
  • 使用redis-cli连接,不加-c参数时
redis-cli -p 6381
  • 此时向redis中添加键值对,可能会成功,也可能会失败
set k1 v1

报错:k1经过计算得到的哈希槽为12706,但是当前连接的redis-server为6381(即节点1),它的哈希槽为:[0,5460](在创建构建主从关系时redis有提示,也可以通过 cluster nodes查看),所以会因为存不进去而报错。
执行 set k2 v2可以成功,因为k2计算出的哈希槽在[0-5460]区间中。

  • 使用-c参数的redis-cli命令连接即可
redis-cli -p 6381 -c
  • 此时可以正常的插入所有数据
set k1 v1

会有提示信息,哈希槽为12706,重定向到6383(即节点3,哈希槽[10923, 16383])

16.4.3.3 集群信息检查

检查查看集群信息:

  • 进入容器节点1
docker exec -it redis-node-1 /bin/bash
  • 进行集群信息检查
# 输入任意一台主节点地址都可以进行集群检查
redis-cli --cluster check 192.168.xxx.xxx:6381

返回的检查结果:

当前集群中各个节点存储的key的数量
192.168.xxx.xxx:6381 (f451eb48...) -> 0 keys | 5461 slots | 1 slaves.
192.168.xxx.xxx:6383 (1fc935c1...) -> 1 keys | 5461 slots | 1 slaves.
192.168.xxx.xxx:6382 (05984211...) -> 0 keys | 5462 slots | 1 slaves.
[OK] 1 keys in 3 masters.  
0.00 keys per slot on average.

主从机器信息
>>> Performing Cluster Check (using node 192.168.xxx.xxx:6381)
M: f451eb48bbc0a7c31c7da022ffe80cc1696e0f37 192.168.xxx.xxx:6381
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 1fc935c12b1d34a7df50aed643c195eb29bb3435 192.168.xxx.xxx:6383
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 05984211b8c38222a73abeff1d4e459c0fe1efbc 192.168.xxx.xxx:6382
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 0c0767e13a09ee48541738d4163592cd9842c143 192.168.xxx.xxx:6386
   slots: (0 slots) slave
   replicates 05984211b8c38222a73abeff1d4e459c0fe1efbc
S: f8d0de47114bf33438747acd713cce4e412ae721 192.168.xxx.xxx:6384
   slots: (0 slots) slave
   replicates 1fc935c12b1d34a7df50aed643c195eb29bb3435
S: de0b393c17e452d856f6de2b348e9ca4e5aa4002 192.168.xxx.xxx:6385
   slots: (0 slots) slave
   replicates f451eb48bbc0a7c31c7da022ffe80cc1696e0f37
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
16.4.3.4 主从扩容缩容
  1. 主从扩容
    假如因为业务量激增,需要向当前3主3从的集群中再加入1主1从两个节点。
    步骤:
  • 启动2台新的容器节点
# 启动第7台节点
docker run -d --name redis-node-7 --net host --privileged=true -v /app/redis-cluster/share/redis-node-7:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6387

# 启动第8台节点
docker run -d --name redis-node-8 --net host --privileged=true -v /app/redis-cluster/share/redis-node-8:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6388
  • 进入6387(节点7)容器内部
docker exec -it redis-node-7 /bin/bash
  • 将6387作为master加入集群
# redis-cli --cluster add-node 本节点地址 要加入的集群中的其中一个节点地址
redis-cli --cluster add-node 192.168.xxx.xxx:6387 192.168.xxx.xxx:6381
  • 检查当前集群状态
redis-cli --cluster check 192.168.xxx.xxx:6381

可以发现,6371节点已经作为master加入了集群,但是该节点没有被分配槽位。

  • 重新分配集群的槽位
redis-cli --cluster reshard 192.168.xxx.xxx:6381

redis经过槽位检查后,会提示需要分配的槽位数量:
例如,我们现在是4台master,我们想要给node7分配4096个槽位,这样每个节点都是4096个槽位。
输入4096后,会让输入要接收这些哈希槽的节点ID,填入node7的节点ID即可。(就是节点信息中很长的一串十六进制串)。
然后会提示,询问要从哪些节点中拨出一部分槽位凑足4096个分给Node7。一般选择 all,即将之前的3个主节点的槽位都均一些给Node7,这样可以使得每个节点的槽位数相等均衡。
输入all之后,redis会列出一个计划,内容是自动从前面的3台master中拨出一部分槽位分给Node7的槽位,需要确认一下分配的计划。
输入yes确认后,redis便会自动重新洗牌,给Node7分配槽位。

重新分配完成后,可以进行集群信息检查,查看分配结果:

redis-cli --cluster check 192.168.xxx.xxx:6381

可以发现重新洗牌后的槽位分配为:

节点1:[1365-5460](供4096个槽位),,,分配前为[0-5460](共5461个槽位)
节点2:[6827-10922](共4096个槽位),,,分配前为[5461-10922](共5461个槽位)
节点3:[12288-16383](共4096个槽位),,,分配前为[10923-16383](共5462个槽位)

节点7:[0-1364],[5461-6826],[10923-12287](共4096个槽位),从每个节点中匀出来了一部分给了节点7

因为可能有些槽位中已经存储了key,完全的重新洗牌重新分配的成本过高,所以redis选择从前3个节点中匀出来一部分给节点7
为主节点6387分配从节点6388:

redis-cli --cluster add-node 192.168.xxx.xxx:6388 192.168.xxx.xxx:6381 --cluster-slave --cluster-master-id node7节点的十六进制编号字符串

redis便会向6388发送消息,使其加入集群并成为6387的从节点。

检查集群当前状态

redis-cli --cluster check 192.168.xxx.xxx:6381
16.4.3.5 主从缩容

假如业务高峰期过去,需要将4主4从重新缩容到3主3从。即从集群中移除node8和node7.

首先删除从节点6388:

  1. 进入容器节点1
docker exec -it redis-node-1 /bin/bash
  1. 检查容器状态,获取6388的节点编号
redis-cli --cluster check 192.168.xxx.xxx:6381
  1. 将6388从集群中移除
redis-cli --cluster del-node 192.168.xxx.xxx:6388 6388节点编号

对node7重新分配哈希槽:

  1. 对集群重新分配哈希槽
redis-cli --cluster reshard 192.168.xxx.xxx:6381
  1. redis经过槽位检查后,会提示需要分配的槽位数量:
How many slots do you want to move (from 1 to 16384)?

如果我们想直接把node7的4096个哈希槽全部分给某个节点,可以直接输入4096。
输入4096后,会让输入要接收这些哈希槽的节点ID。假如我们想把这4096个槽都分给Node1,直接输入node1节点的编号即可。
然后会提示,询问要从哪些节点中拨出一部分槽位凑足4096个分给Node1。这里我们输入node7的节点编号,回车后输入done。

node7上面没有了哈希槽,此时便可以将node7从集群中移除。(如果node7上面有哈希槽,直接从集群中移除会报错)

redis-cli --cluster del-node 192.168.xxx.xxx:6387 node7节点编号
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值