docker-实战
前言
Dockerfile 搭建 mysql主从集群
基础知识
Docker compose 构建参数
build
• 功能
在docker-compose.yml 文件中使用 build选项编译镜像。
build会指定一个上下文或者一个Dockerfile
这样就会完成一个镜像的制作了
指定多个build就可以完成多个镜像的制作
格式
services:
# 格式一
frontend:
image: awesome/webapp
build: ./webapp
#./webapp 下有Dockerfile文件
# 格式二
backend:
image: awesome/database
build:
#构建上下文目录
context: ./backend
dockerfile: ./backend.Dockerfile
#Dockerfile文件名为backend.Dockerfile ,所在目录为./backend
cd /data/maxhou/mydockerfile/
mkdir mytestbuild
cd mytestbuild
ll
vi docker-compose.yml
version: "3.8"
services:
mynginx:
image: mytestbuildimage:v1.0
build: ./mynginx
docker compose config
mkdir mynginx
cd mynginx
vi Dockerfile
FROM nginx:1.24.0
RUN echo "hello my bit!" > /usr/share/nginx/html/index.html
cd …
并且mytestbuildimage:v1.0这个镜像不存在
docker compose build
docker images mytestbuildimage
这样就存在了
version: "3.8"
services:
mynginx:
image: mytestbuildimage:v1.0
build: ./mynginx
mynginx2:
image: mytestbuildimage:v2.0
build:
context: ./mynginx2
dockerfile: dockerfile2
mkdir mynginx2
cd mynginx2
cp …/mynginx/Dockerfile dockerfile2
cd …
docker compose build
docker images mytestbuildimage
所以可以完成自定义镜像的制作,还可以多个镜像
mysql 主从同步原理
什么是mysql主从同步
MySQL 主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个
从节点。MySQL 默认采用异步复制方式,这样从节点不用一直访问主服务器来更新自
己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数
据库或者特定的数据库,或者特定的表。 实时同步数据,主节点实时同步数据给从节点
为什么要mysql主从同步
• 读写分离,性能提升:让主库负责写,从库负责读,这样,即使主库出现了锁表
的情景,通过读从库也可以保证业务的正常运作。扩展架构提升读写能力。
• 数据实时备份:主库数据实时保存到存库,万一主库故障也有从库的数据备份
• 高可用HA:当系统中某个节点发生故障时,可以方便的故障切换
主从同步的架构图
在主从复制的过程中,会基于三个线程来操作,一个是binlog dump线程,位于
master 节点上,另外两个线程分别是I/O线程和SQL线程,它们都分别位于slave节
点上。
• 当 master 节点接收到一个写请求时,这个写请求可能是增删改操作,此时会把写
请求的更新操作都记录到 binlog 日志中。
• master 节点会把数据复制给 slave 节点,如图中的 slave01 节点和 slave02 节点,
这个过程,首先得要每个 slave 节点连接到 master 节点上,当 slave 节点连接到
master 节点上时,master 节点会为每一个 slave 节点分别创建一个 binlog dump 线程,
用于向各个 slave 节点发送 binlog 日志。
• binlog dump 线程会读取 master 节点上的 binlog 日志,然后将 binlog 日志发送给
slave 节点上的 I/O 线程。当主库读取事件的时候,会在 Binglog 上加锁,读取完成之
后,再将锁释放掉。
• slave 节点上的 I/O 线程接收到 binlog 日志后,会将 binlog 日志先写入到本地的
relaylog 中,relaylog 中就保存了 binlog 日志。
• slave 节点上的 SQL 线程,会来读取 relaylog 中的 binlog 日志,将其解析成具体
的增删改操作,把这些在 master 节点上进行过的操作,重新在 slave 节点上也重做一
遍,达到数据还原的效果,这样就可以保证 master 节点和 slave 节点的数据一致性了。
主从同步的数据内容其实是二进制日志(Binlog),它虽然叫二进制日志,实际上存储
的是一个又一个的事件(Event),这些事件分别对应着数据库的更新操作,比如
INSERT、UPDATE、DELETE 等。
什么是binlog
主库每提交一次事务,都会把数据变更,记录到一个二进制文件中,这个二进制文件
就叫binlog。需注意:只有写操作才会记录至binlog,只读操作是不会的(如select、
show语句)。
Bin Log 共有三种日志格式,可以binlog_format配置参数指定。
参数值
含义
Statement
记录原始SQL语句,会导致更新时间与原库不一致。
比如 update_time=now()
Row 记录每行数据的变化,保证了数据与原库一致,缺点是数据量较大。
Mixed
Statement 和 Row的混合模式,默认采用Statement模式,涉及日期、函
数相关的时候采用Row模式,既减少了数据量,又保证了数据一致性。
主从同步方式
主从同步的方式
• 全同步方式:就是当主库执行完一个事务之后,要求所有的从库也都必须执行完
该事务,才可以返回处理结果给客户端;因此,虽然全同步复制数据一致性得到保证
了,但是主库完成一个事务需要等待所有从库也完成,性能就比较低了,从库如果挂
了主库也受影响。
• 异步同步:默认方式,主库在执行完客户端提交的事务后会立即将结果返给给客
户端,并不关心从库是否已经接收并处理。
因为主库只管自己执行完事务,就可以将处理结果返回给客户端,而不用关心从
库是否执行完事务,这就可能导致短暂的主从数据不一致的问题了,比如刚在主库插
入的新数据,如果马上在从库查询,就可能查询不到。
而且,当主库提交事物后,如果宕机挂掉了,此时可能 binlog 还没来得及同步给
从库,这时候如果为了恢复故障切换主从节点的话,就会出现数据丢失的问题,所以
异步复制虽然性能高,但数据一致性上是最弱的。
mysql 主从复制,默认采用的就是异步复制这种复制策略。
• 半同步:基于传统异步存在的缺陷,mysql在5.5版本推出半同步复制。可以说半
同步复制是传统异步复制的改进,在master事务的commit之前,必须确保一个
slave 收到relay log 并且响应给master以后,才能进行事务的commit。相当于添加
多了一个从库反馈机制。在 MySQL5.7 版本中还增加了一个
rpl_semi_sync_master_wait_for_slave_count 参数,我们可以对需要响应的从库数量
进行设置,默认为 1,也就是说只要有一个从库进行了响应,就可以返回给客户端。
如果将这个参数调大,可以提升数据一致性的强度,但也会增加主库等待从库响应的
时间。
对应配置参数为rpl_semi_sync_master_wait_point=after_commit。核心流程为,
主库执行完事务后,主库提交commit ,同步binlog给从库,从库ack反馈接收到
binlog,反馈给客户端,释放会话;(主库生成binlog,主库提交,再同步binlog,等
ACK反馈,返回客户端)
半同步复制存在以下几个问题:
○ 半同步复制的性能,相比异步复制而言有所下降,相比于异步复制是不需要
等待任何从库是否接收到数据的响应,而半同步复制则需要等待至少一个从库确
认接收到 binlog 日志的响应,性能上是损耗更大的。
○ 主库等待从库响应的最大时长是可以配置的,如果超过了配置的时间,半同
步复制就会变成异步复制,那么,异步复制的问题同样也就会出现了。
○ 半同步复制存在着幻读问题的:当主库成功提交事物并处于等待从库确认的过
程中,这个时候,从库都还没来得及返回处理结果给客户端,但因为主库存储引
擎内部已经提交事务了,所以,其他客户端是可以到从主库中读到数据的。但是,
如果下一秒主库突然挂了,此时正好下一次请求过来,因为主库挂了,就只能把
请求切换到从库中,因为从库还没从主库同步完数据,所以,从库中当然就读不
到这条数据了,和上一秒读取数据的结果对比,就造成了幻读的现象了。
• 增强半同步复制
增强半同步复制,是 对半同步复制做的一个改进,原理上几乎是一样的,主要是解决
幻读的问题。
主库配置了参数 rpl_semi_sync_master_wait_point = AFTER_SYNC 后,主库在存储
引擎提交事务前,必须先收到从库数据同步完成的确认信息后,才能提交事务,以此
来解决幻读问题。
核心流程为主库执行完事务后,同步binlog给从库,从库ack反馈接收到binlog,主
库提交commit,反馈给客户端,释放会话;(主库生成binlog,再同步binlog,等
ACK反馈,主库提交,返回客户端)
但是slave对于relay log的应用仍然是异步进行的
组复制:
MySQL官方在5.7.17版本正式推出组复制(MySQL Group Replication,简称
MGR)
由若干个节点共同组成一个复制组,一个事务的提交,必须经过组内大多数节点
(N / 2 + 1)决议并通过,才能得以提交。如上图所示,由 3个节点组成一个复制组,
Consensus层为一致性协议层,在事务提交过程中,发生组间通讯,由2个节点决议
(certify)通过这个事务,事务才能够最终得以提交并响应。
引入组复制,主要是为了解决传统异步复制和半同步复制可能产生数据不一致的问
题。组复制依靠分布式一致性协议(Paxos协议的变体),实现了分布式下数据的最终一
致性,提供了真正的数据高可用方案(是否真正高可用还有待商榷)。其提供的多写方案,
给我们实现多活方案带来了希望。
MGR的解决方案有一定的局限性,如仅支持InnoDB表,并且每张表一定要有一
个主键,用于做write set的冲突检测;开启GTID特性等
MySQL 主从形式
一主一从
一主多从,提高系统的读性能
一主一从和一主多从是最常见的主从架构,实施起来简单并且有效,不仅可以实现HA,
而且还能读写分离,进而提升集群的并发能力。
多主一从
多主一从可以将多个mysql数据库备份到一台存储性能比较好的服务器上。
双主复制
双主复制,也就是互做主从复制,每个master既是master,又是另外一台服务器的
slave。这样任何一方所做的变更,都会通过复制应用到另外一方的数据库中。
级联复制
级联复制模式下,部分slave的数据同步不连接主节点,而是连接从节点。因为如果
主节点有太多的从节点,就会损耗一部分性能用于replication,那么我们可以让3~5
个从节点连接主节点,其它从节点作为二级或者三级与从节点连接,这样不仅可以缓
解主节点的压力,并且对数据一致性没有负面影响。
mysql 主从集群搭建步骤
创建主库,并在主库中创建单独的mysql用户用于同步数据,授予该用户数据同步
权限
创建从库,配置从库的数据同步的主库信息
启动从库,开始同步
实战
创建一个一主二从,借助Dockerfile和docker-compose
先弄主节点
主节点的Dockerfile
vi master/Dockerfile-master
FROM mysql:5.7
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY ./master/master.sql /docker-entrypoint-initdb.d
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 这个是设置时区
docker-entrypoint-initdb.d里面的sql文件会作为初始化脚本的
vi master/master.sql
CREATE USER 'root'@'%' IDENTIFIED BY 'root';
grant replication slave, replication client on *.* to 'root'@'%';
flush privileges;
因Mysql默认创建了root用户不需要手动创建配置 ,所有这sql文件可以不用执行
CREATE USER ‘root’@‘%’ IDENTIFIED BY ‘root’; 创建了一个root用户,密码也是root
grant replication slave, replication client on . to ‘root’@‘%’; 授权root用户能给拷贝,主从数据的复制
flush privileges; 这个是刷新结点的权限
现在是从节点
vi c/Dockerfile-slave
FROM mysql:5.7
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY ./slave/slave.sql /docker-entrypoint-initdb.d
vi slave/slave.sql
change master to master_host='mysql-master', master_user='root',master_password='root',master_port=3306;
start slave;
change master to master_host=‘mysql-master’, master_user=‘root’,master_password=‘root’,master_port=3306; 就是指定主节点是谁,主节点的主机是谁,主节点端口是谁,用户名,密码是什么
start slave; 就是作为从节点启动
然后就是用docker-compose.yml把这些容器串联起来了
vim docker-compose.yml
version: "3"
services:
mysql-master:
build:
context: ./
dockerfile: ./master/Dockerfile-master
image: mysqlmaster:v1.0
restart: always #这个就是挂了,自动把自己拉起来
container_name: mysql-master #容器名字
volumes: #存储卷
- ./mastervarlib:/var/lib/mysql
ports:
- 9306:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true #开启特权
command: ['--server-id=1', #常用在mysql集群里面,实例服务id
'--log-bin=master-bin', #指定binlog的名字,就是要开启binlog
'--binlog-ignore-db=mysql', #binlog忽视mysql的数据库,因为同步的是业务数据库
'--binlog_cache_size=256M', #这个是binlog的缓存大小
'--binlog_format=mixed', #binlog的日志模式是混合模式
'--lower_case_table_names=1', #默认的表是小写的
'--character-set-server=utf8', #默认的服务器编码
'--collation-server=utf8_general_ci'] #默认的排序规则
#指定了一些作为主节点的一些属性,这些属性都是mysql服务的,command覆盖容器启动的默认命令。
#容器启动的时候,会自动执行弄好的sql脚本命令, 授权root用户有数据复制的权限
mysql-slave:
build:
context: ./
dockerfile: ./slave/Dockerfile-slave
image: mysqlslave:v1.0
restart: always
container_name: mysql-slave
volumes:
- ./slavevarlib:/var/lib/mysql
ports:
- 9307:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true
command: ['--server-id=2',
'--relay_log=slave-relay', #日志,明确告诉 MySQL 服务器将中继日志存储在特定的位置或使用特定的文件名,以便更好地管理和维护主从复制环境中的数据同步
'--lower_case_table_names=1', #表名是小写的
'--character-set-server=utf8',
'--collation-server=utf8_general_ci'] #排序规则
depends_on:
- mysql-master #依赖于主节点
mysql-slave2:
build:
context: ./
dockerfile: ./slave/Dockerfile-slave
image: mysqlslave:v1.0
restart: always
container_name: mysql-slave2
volumes:
- ./slavevarlib2:/var/lib/mysql
ports:
- 9308:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true
command: ['--server-id=3',
'--relay_log=slave-relay',
'--lower_case_table_names=1',
'--character-set-server=utf8',
'--collation-server=utf8_general_ci']
depends_on:
- mysql-master
第三步就完成了
现在开始构建镜像
docker compose build
docker images
docker images ls
现在开始第五步启动服务
docker compose up -d
第一个构建的是网络
docker compose ps
容器是正常的
现在开始检查同步的状态
先进入主节点容器
docker exec -it mysql-master bash
mysql -p
输入密码
show master status;
我们就可以看到binlog了
show slave status;
因为是主节点,所以看到是空的
exit
exit
docker exec -it mysql-slave bash
mysql -p
show master status;
show slave status;
看起来有点乱
show slave status \G;
这个是竖着看
所以主从状态是正常的
现在进行第六步,写数据
exit
exit
docker exec -it mysql-master bash
mysql -p
密码
create database test;
打开另一个shell
docker exec -it mysql-slave2 bash
mysql -p
密码
show database;
我们发现数据库已经同步过来了
主节点:
use test;
create table users(sno int,sname varchar(20));
从节点:
show tables;
主节点:
insert into users values (1,‘pony’);
从节点
select * from users;
这些都是配置主从同步的关键,这个是指定作为从节点启动,指定主节点的相关信息
我们实战的是一主多从
Dockerfile 构建 Redis 集群
下载redis原码,配置集群信息
打开redis官网https://redis.io/,找到下载https://redis.io/download/,下载7.0.x
对应的版本的源码文件,无法下载的话使用 http://download.redis.io/releases/redis
7.0.11.tar.gz 替换对应版本进行下载
点击上边的download
可以这样下载
这个下载用的是github来下载的
我们可以使用http://download.redis.io/releases/redis-7.0.11.tar.gz
然后拷贝到桌面上
mkdir myrediscluster
cd myrediscluster
mkdir redis
myrediscluster存放docker-compose.yml
制作镜像的文件放在redis里面
源代码放在redis里面
cd redis
ll
我们在桌面上进行解压
因为要配置集群的参数,所以要解压
找到redis.conf
修改它的配置
编辑的参数
#表示前台运行
daemonize no #相当于nginx的daemon of,不要后天运行,要前台运行,作为入口
#端口
port 6379
#持久化
dir /data/redis #/data/redis持久化目录
#启用集群
cluster-enabled yes
#集群参数配置
cluster-config-file nodes.conf
#集群超时时间
cluster-node-timeout 5000
#密码配置
requirepass 123456
#从节点使用主节点时主节点 密码配置
masterauth 123456
#表示远端可以连接
bind * -::*
每个参数挨个挨个查找,看有没有设置好·
改
改,因为井号注释掉了
这个也改
改
改
改
这样绑定的话,默认只能本机访问
改
这个就是谁都可以访问
然后把配置文件拷贝过去
编写Dockerfile制作镜像
vi Dockerfile
FROM ubuntu:22.04 as buildstage #指定基础镜像,buildstage 是构建阶段的名称
RUN sudo sed -i 's@//.*archive.ubuntu.com@//mirrors.ustc.edu.cn@g' /etc/apt/sources.list & apt update #这个是更新源,和刷新apt
RUN apt install -y build-essential #这个是编译器,相当于gcc
#wget http://download.redis.io/releases/redis-7.0.11.tar.gz
ADD redis-7.0.11.tar.gz / #自动完成解压
ADD redis.conf /redis/ #集群化的配置文件
WORKDIR /redis-7.0.11 #因为解压了redis-7.0.11.tar.gz,所以有这个目录,自动进入这个目录了
RUN make #这个是构建的命令,用gcc来,这样redisserver就生成了
RUN mv /redis-7.0.11/src/redis-server /redis/ && mv /redis-7.0.11/src/redis-cli /redis/ #redis-server服务端和redis-cli客户端都拷贝到redis目录了里面
ENTRYPOINT ["/redis/redis-server", "/redis/redis.conf"] #启动redis-server,配置文件是redis.conf
#这个是构建阶段,,安装了很多东西
#这个是运行态
FROM ubuntu:22.04 #基础镜像
RUN mkdir -p /data/redis && mkdir -p /redis # /data/redis这个是持久化目录,/redis是工作目录
COPY --from=buildstage /redis /redis #将第一阶段构建好的redis拷贝过来
EXPOSE 6379
ENTRYPOINT ["/redis/redis-server", "/redis/redis.conf"]
#多阶段构建,第一阶段是原码的编译,第二阶段就是把编译好的redis和配置文件直接拿过来
注意里面的换行
然后就是制作镜像
docker build -t myrediscluster:v0.1 .
docker images myrediscluster
当然我们也可以使用官方的redis镜像
启动容器 ,测试
docker run -d --name myrediscluster01 myrediscluster:v0.1
docker ps
编写docker-compose.yml
version: "3"
services:
redis01:
image: myredis:v1.0
build: ./redis #因为Dockerfile位于宿主机redis 目录下面
ports:
- 6379:6379
container_name: redis01
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis02:
image: myredis:v1.0
container_name: redis02
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis03:
image: myredis:v1.0
container_name: redis03
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis04:
image: myredis:v1.0
container_name: redis04
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis05:
image: myredis:v1.0
container_name: redis05
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis06:
image: myredis:v1.0
container_name: redis06
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis07:
image: myredis:v1.0
container_name: redis07
entrypoint: ["/redis/redis-cli","--cluster","create","redis01:6379","redis02:6379","redis03:6379","redis04:6379","redis05:6379","redis06:6379","--cluster-replicas","1","-a","123456","--cluster-yes"] #额外的节点,初始化集群,完成6个节点关系的创建,这个初始化依赖于六个容器的健康,第七个容器组成了redis集群
depends_on:
redis01:
condition: service_healthy
redis02:
condition: service_healthy
redis03:
condition: service_healthy
redis04:
condition: service_healthy
redis05:
condition: service_healthy
redis06:
condition: service_healthy
该 Docker Compose 文件的主要功能是创建并启动 7 个 Docker 容器,其中 redis01 - redis06 是 Redis 节点容器,redis07 是用于初始化 Redis 集群的容器。redis07 容器会在 redis01 - redis06 容器都处于健康状态时启动,完成 Redis 集群的创建工作。
vi docker-compose.yml
docker compose config
启动集群
docker compose build
因为外面已经构建过这个镜像了,所有很快,用的就是缓存
docker compose up -d启动集群
redis07最后启动的
docker compose ps -a
我们07退出了,01~06正常的运行
而且01~06都是server的
docker logs -f redis07
最后那个all slots
说明我们的集群成功创建了
这六个redis互为主从
使用第七个节点完成他们关系的创建,集群的创建
测试功能&清理资源
docker exec -it redis01 bash
/redis/redis-cli -c -a 123456
-c表示自动重定向
-a指定密码
cluster info
我们可以看到这个集群的状态是OK的
set a 123
get a
exit
exit
docker compose down
Dockerfile 配合 docker-compose搭建微服务
方案设计
创建mysql和redis集群
因为系统架构是由下往上的,所以先弄这两个
ll
创建1一个目录,目录里面有这些东西
ll app
这个user模块就是用做用户管理的
app里面就是放各个的子模块
ll mysql
这个MySQL的目录就是之前的目录
ll mysql/slave
ll mysql/master
ll redis/
vim docker-compose.yml
version: "3"
services:
mysql-master:
build:
context: ./
dockerfile: ./master/Dockerfile-master
image: mysqlmaster:v1.0
restart: always #这个就是挂了,自动把自己拉起来
container_name: mysql-master #容器名字
volumes: #存储卷
- ./mastervarlib:/var/lib/mysql
ports:
- 9306:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true #开启特权
command: ['--server-id=1', #常用在mysql集群里面,实例服务id
'--log-bin=master-bin', #指定binlog的名字,就是要开启binlog
'--binlog-ignore-db=mysql', #binlog忽视mysql的数据库,因为同步的是业务数据库
'--binlog_cache_size=256M', #这个是binlog的缓存大小
'--binlog_format=mixed', #binlog的日志模式是混合模式
'--lower_case_table_names=1', #默认的表是小写的
'--character-set-server=utf8', #默认的服务器编码
'--collation-server=utf8_general_ci'] #默认的排序规则
#指定了一些作为主节点的一些属性,这些属性都是mysql服务的,command覆盖容器启动的默认命令。
#容器启动的时候,会自动执行弄好的sql脚本命令, 授权root用户有数据复制的权限
mysql-slave:
build:
context: ./
dockerfile: ./slave/Dockerfile-slave
image: mysqlslave:v1.0
restart: always
container_name: mysql-slave
volumes:
- ./slavevarlib:/var/lib/mysql
ports:
- 9307:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true
command: ['--server-id=2',
'--relay_log=slave-relay', #日志,明确告诉 MySQL 服务器将中继日志存储在特定的位置或使用特定的文件名,以便更好地管理和维护主从复制环境中的数据同步
'--lower_case_table_names=1', #表名是小写的
'--character-set-server=utf8',
'--collation-server=utf8_general_ci'] #排序规则
depends_on:
- mysql-master #依赖于主节点
redis01:
image: myredis:v1.0
build: ./redis #因为Dockerfile位于宿主机redis 目录下面
ports:
- 6379:6379
container_name: redis01
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis02:
image: myredis:v1.0
container_name: redis02
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis03:
image: myredis:v1.0
container_name: redis03
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis04:
image: myredis:v1.0
container_name: redis04
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis05:
image: myredis:v1.0
container_name: redis05
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis06:
image: myredis:v1.0
container_name: redis06
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis07:
image: myredis:v1.0
container_name: redis07
entrypoint: ["/redis/redis-cli","--cluster","create","redis01:6379","redis02:6379","redis03:6379","redis04:6379","redis05:6379","redis06:6379","--cluster-replicas","1","-a","123456","--cluster-yes"] #额外的节点,初始化集群,完成6个节点关系的创建,这个初始化依赖于六个容器的健康,第七个容器组成了redis集群
depends_on:
redis01:
condition: service_healthy
redis02:
condition: service_healthy
redis03:
condition: service_healthy
redis04:
condition: service_healthy
redis05:
condition: service_healthy
redis06:
condition: service_healthy
我们这个MySQL配置的是一主一从,因为内存不够了
docker compose build
docker compose up -d
docker compose ps
检查集群功能和初始化数据
我们用可视化工具链接上MySQL
端口是9306
直接运行这个脚本
MySQL集群就OK了
我们也可以用命令行进入容器检查MySQL
现在我们检查redis
docker exec -it redis01 bash
/redis/redis-cli -c -a 1223456
cluster info
可以看到我们的集群是OK的
开发Java用户微服务
要引入springboot,mysql,redis的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data
redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.11.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
添加一个简单的用户bean
@Data
public class UserBean {
//用户id
private Integer sno;
//用户名称
private String sname;
}
创造一个用户类
这个是把redis的默认的序列化器变成了字符串的序列化器
添加redis使用json来完成存储的配置
@Configuration
public class CustomRedis {
@Bean
public RedisTemplate<String,Object>
redisSerTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new
RedisTemplate<>();
//设置工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置序列化器
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
redisTemplate.setHashValueSerializer(RedisSerializer.string());
return redisTemplate;
}
}
创建简单控制器
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService ;
@GetMapping("/userGet/{id}")
public Object getUsersById(@PathVariable(name = "id") Integer
sno){
return userService.getUsersById(sno);
}
}
创建用户服务
@Service
public class UserService {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
JdbcTemplate jdbcTemplate;
ObjectMapper objectMapper = new ObjectMapper();
@GetMapping("/userGet/{id}")
public Object getUsersById(@PathVariable(name = "id") Integer
sno){
String userKey = "u:"+sno;
//先查询缓存
if (stringRedisTemplate.hasKey(userKey)){
try {
return
objectMapper.readValue(stringRedisTemplate.opsForValue().get(userK
ey),UserBean.class) ;
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
//再查询数据库
String sql = "select * from users where sno= ?";
UserBean userBean= new UserBean();
try{
userBean= jdbcTemplate.queryForObject(sql, new
BeanPropertyRowMapper<>(UserBean.class),sno);
}
catch (Exception e){
return userBean;
}
//查询数据库说明没缓存,添加下缓存下次直接查询redis
try {
if (userBean != null){
stringRedisTemplate.opsForValue().set(userKey,objectMapper.writeVa
lueAsString(userBean));
}
else {
return new UserBean();
}
} catch (Exception e) {
//缓存更新失败,可以再度重试,再度失败可以引入消息中间件记
录失败信息过段时间再重试
throw new RuntimeException(e);
}
return userBean;
}
}
配置默认的应用
@SpringBootApplication
public class TestmymysqlApplication {
public static void main(String[] args) {
SpringApplication.run(TestmymysqlApplication.class, args);
}
}
配置application-docker.yml来读取mysql和redis ,里面的ip就是域名,就是容器名
spring:
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://mysql-master:3306/test
username: root
password: root
redis:
password: 123456
lettuce:
pool:
max-active: 50
max-idle: 5
min-idle: 0
max-wait: 5000
database: 0
cluster:
nodes:
redis01:6379,redis02:6379,redis03:6379,redis04:6379,redis05:6379,r
edis06:6379
#host: 127.0.0.1
connect-timeout: 5000
timeout: 5000
配置application-local.yml来测试,注意ip地址为我们集群所在服务器的ip地址。
spring:
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://172.27.0.6:9306/test
username: root
password: root
redis:
password: 123456
lettuce:
pool:
max-active: 50
max-idle: 5
min-idle: 0
max-wait: 5000
database: 0
cluster:
nodes: 172.27.0.6:6379
#host: 127.0.0.1
connect-timeout: 5000
timeout: 5000
这个是我们进行本地测试的文件
服务器上输入ifconfig
127.27.0.6这个就是我们服务器节点对应的ip,9306可以转发到mysql的3306
6379转发到集群的6379
测试微服务
进入user目录
把jar包考过来
然后就是考过来
java -jar xxx.jar --spring.profiles.active=local
因为我们是在容器外宿主机上测试,所以是local
编写微服务dockerfile
ll
cd app
cd user
vi Dockerfile
# 指定java环境基础镜像 注意jdk的版本和你idea实际生成的版本要对应上,
#原来的java变为了openjdk,调整java:8为openjdk:8
FROM openjdk:8
# 拷贝jar包到容器中
ADD ./testmymysql-0.0.1-SNAPSHOT.jar /app.jar
# 运行jar包
CMD ["java", "-jar", "/app.jar","--spring.profiles.active=docker"]
编写nginx代理Dockerfile
cd …
cd nginx
ll
vi bit.conf
这个是nginx的配置文件
upstream myapi{
server myuser:8080;
server myuser2:8080;
}
server {
listen 80;
access_log off;
location / {
proxy_pass http://myapi/user/;
}
}
upstream 是nginx路由的关键参数,配置了两个服务器,myuser是ip或者域名,服务名,当一个请求过来的时候,会均衡的路由到这两个节点myuser2和myuser
access_log off;# 关闭了日志
location 就是访问的默认节点会被代理到http://myapi/user/
所以当我们访问http://139.155.24.167:80/userGet/1会被转化为 http://myuser:8080/user/userGet/1或者http://myuser2:8080/user/userGet/1
这个就是nginx负载均衡的作用
vi Dockerfile
FROM nginx:1.24.0
COPY ./bit.conf /etc/nginx/conf.d/
CMD ["nginx","-g","daemon off;"]
ENTRYPOINT ["/docker-entrypoint.sh"]
编写docker-compose.yml
version: "3"
services:
mysql-master:
build:
context: ./
dockerfile: ./master/Dockerfile-master
image: mysqlmaster:v1.0
restart: always #这个就是挂了,自动把自己拉起来
container_name: mysql-master #容器名字
volumes: #存储卷
- ./mastervarlib:/var/lib/mysql
ports:
- 9306:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true #开启特权
command: ['--server-id=1', #常用在mysql集群里面,实例服务id
'--log-bin=master-bin', #指定binlog的名字,就是要开启binlog
'--binlog-ignore-db=mysql', #binlog忽视mysql的数据库,因为同步的是业务数据库
'--binlog_cache_size=256M', #这个是binlog的缓存大小
'--binlog_format=mixed', #binlog的日志模式是混合模式
'--lower_case_table_names=1', #默认的表是小写的
'--character-set-server=utf8', #默认的服务器编码
'--collation-server=utf8_general_ci'] #默认的排序规则
#指定了一些作为主节点的一些属性,这些属性都是mysql服务的,command覆盖容器启动的默认命令。
#容器启动的时候,会自动执行弄好的sql脚本命令, 授权root用户有数据复制的权限
mysql-slave:
build:
context: ./
dockerfile: ./slave/Dockerfile-slave
image: mysqlslave:v1.0
restart: always
container_name: mysql-slave
volumes:
- ./slavevarlib:/var/lib/mysql
ports:
- 9307:3306
environment:
MYSQL_ROOT_PASSWORD: root
privileged: true
command: ['--server-id=2',
'--relay_log=slave-relay', #日志,明确告诉 MySQL 服务器将中继日志存储在特定的位置或使用特定的文件名,以便更好地管理和维护主从复制环境中的数据同步
'--lower_case_table_names=1', #表名是小写的
'--character-set-server=utf8',
'--collation-server=utf8_general_ci'] #排序规则
depends_on:
- mysql-master #依赖于主节点
redis01:
image: myredis:v1.0
build: ./redis #因为Dockerfile位于宿主机redis 目录下面
ports:
- 6379:6379
container_name: redis01
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis02:
image: myredis:v1.0
container_name: redis02
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis03:
image: myredis:v1.0
container_name: redis03
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis04:
image: myredis:v1.0
container_name: redis04
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis05:
image: myredis:v1.0
container_name: redis05
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis06:
image: myredis:v1.0
container_name: redis06
healthcheck:
test: /redis/redis-cli ping
interval: 10s
timeout: 5s
retries: 10
redis07:
image: myredis:v1.0
container_name: redis07
entrypoint: ["/redis/redis-cli","--cluster","create","redis01:6379","redis02:6379","redis03:6379","redis04:6379","redis05:6379","redis06:6379","--cluster-replicas","1","-a","123456","--cluster-yes"] #额外的节点,初始化集群,完成6个节点关系的创建,这个初始化依赖于六个容器的健康,第七个容器组成了redis集群
depends_on:
redis01:
condition: service_healthy
redis02:
condition: service_healthy
redis03:
condition: service_healthy
redis04:
condition: service_healthy
redis05:
condition: service_healthy
redis06:
condition: service_healthy
web:
image: mynginx:v2.0
build:
context: ./nginx
ports:
- 80:80
depends_on: #nginx依赖两个微服务
myuser:
condition: service_started
myuser2:
condition: service_started
myuser:
image: myuser:v2.0
build:
context: ./app/user
depends_on:
redis01:
condition: service_healthy
redis02:
condition: service_healthy
redis03:
condition: service_healthy
redis04:
condition: service_healthy
redis05:
condition: service_healthy
redis06:
condition: service_healthy
mysql-master:
condition: service_started
myuser2:
image: myuser:v2.0
build:
context: ./app/user
depends_on:
redis01:
condition: service_healthy
redis02:
condition: service_healthy
redis03:
condition: service_healthy
redis04:
condition: service_healthy
redis05:
condition: service_healthy
redis06:
condition: service_healthy
mysql-master:
condition: service_started
myuser和myuser2就是我们启动的两个服务,依赖的Dockerfile都是一样的,这里我们写的是一样的,但是可以写成不一样的
docker compose build
启动服务
docker compose up -d
docker compose ps
直接请求80端口
这样就成功了
另一个shell
docker logs -f myuser
这个是服务类里面的打印内容
docker logs -f myuser2
我们请求的时候,会由于nginx的负载均衡,然后打印到不同的日志里面
两个容器的微服务交替提供服务,这样微服务的负载就是大大降低
我们现在进入redis看一下有没有缓存
docker exec -it redis01 bash
/redis/redis-cli -c -a 123456
get u:1
get u:2
这个是空的,因为我们没有请求
get u:2
请求之后就可以写入redis了
C++微服务搭建
略
镜像制作常见问题
ADD与COPY的区别
ADD:不仅能够将构建命令所在的主机本地的文件或目录,而且能够将远程URL
所对应的文件或目录,作为资源复制到镜像文件系统。所以,可以认为ADD是增强版
的COPY,支持将远程URL的资源加入到镜像的文件系统。 可以解压缩
COPY:COPY指令能够将构建命令所在的主机本地的文件或目录,复制到镜像
文件系统。
有的时候就是只需要拷贝压缩包,那么我们就要用COPY指令了
2.
CMD与EntryPoint的区别
ENTRYPOINT 容器启动后执行的命令,让容器执行表现的像一个可执行程序一
样,与CMD 的 区 别 是 不 可 以 被 docker run 覆 盖 , 会 把 docker run 后 面 的
参 数 当 作 传 递 给ENTRYPOINT 指令的参数。
Dockerfile 中只能指定一个 ENTRYPOINT,如果指定了很多,只 有 最 后 一 个 有
效 。 docker run 命 令 的 -entrypoint 参 数 可 以 把 指 定 的 参 数 继 续 传 递 给
ENTRYPOINT
组合使用ENTRYPOINT和CMD, ENTRYPOINT指定默认的运行命令, CMD
指定默认的运行参数
3.
多个From指令如何使用
多个 FROM 指令并不是为了生成多根的层关系,最后生成的镜像,仍以最后一条
FROM 为准,之前的 FROM 会被抛弃,那么之前的FROM 又有什么意义呢?
每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后
生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到
后边的阶段中,这就是多阶段构建的最大意义。
最大的使用场景是将编译环境和运行环境分离.
4.
快照和dockerfile制作镜像有什么区别?
等同于为什么要使用Dockerfile。
5.
什么是空悬镜像(dangling )
仓库名、标签均为 的镜像被称为虚悬镜像,一般来说,虚悬镜像已经失去了存
在的价值,是可以随意删除的。
造成虚悬镜像的原因:
原因一:
原本有镜像名和标签的镜像,发布了新版本后,重新 docker pull *** 时,旧的镜像名
被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消;
原因二:
docker build 同样可以导致这种现象。比如用dockerfile1构建了个镜像tnone1:v1,又
用另外一个Dockerfile2构建了一个镜像tnone1:v1,这样之前的那个镜像就会变成空
悬镜像。
可以用下面的命令专门显示这类镜像:
Shell
docker image ls -f dangling=true
中间层镜像是什么?
为了加速镜像构建、重复利用资源,Docker 会利用 中间层镜像。所以在使用一段时间
后,可能会看到一些依赖的中间层镜像。默认的 docker image ls 列表中只会显示
顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。
Shell
docker image ls -a
这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是
中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上
层镜像因为依赖丢失而出错。实际上,这些镜像也没必要删除,因为之前说过,相同
的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多
存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的
中间层镜像也会被连带删除。
总结
我们的docker镜像制作就讲完了