文章目录
第一章 Docker复杂安装详说
1.1 安装mysql主从复制
-
新建主服务器容器实例3307
docker run -p 3307:3306 --privileged=true --name mysql-master \ -v /cjz/docker/mysql-master/log:/var/log/mysql \ -v /cjz/docker/mysql-master/data:/var/lib/mysql \ -v /cjz/docker/mysql-master/conf:/etc/mysql/conf.d \ -e MYSQL_ROOT_PASSWORD=root \ -d mysql:5.7
-
进入/cjz/docker/mysql-master/conf新建my.cnf
[client] default_character_set=utf8 [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 collation_server = utf8_general_ci character_set_server = utf8
-
修改完配置后重启master实例
docker restart mysql-master
-
进入mysql-master容器,链接mysql
docker exec -it mysql-master /bin/bash
mysql -uroot -proot
-
master容器实例内创建数据同步用户
CREATE USER 'slave'@'%' IDENTIFIED BY '123456';
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'slave'@'%';
-
新建从服务器容器实例3308
docker run -p 3308:3306 --privileged=true --name mysql-slave\ -v /cjz/docker/mysql-slave/log:/var/log/mysql \ -v /cjz/docker/mysql-slave/data:/var/lib/mysql \ -v /cjz/docker/mysql-slave/conf:/etc/mysql/conf.d \ -e MYSQL_ROOT_PASSWORD=root \ -d mysql:5.7
-
进入/cjz/docker/mysql-slave/conf目录下新建my.cnf
[client] default_character_set=utf8 [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 collation_server = utf8_general_ci character_set_server = utf8
-
修改完配置后重启slave实例
docker restart mysql-slave
-
在主数据库中查看主从同步状态
show master status;
-
进入mysql-slave容器
docker exec -it mysql-slave /bin/bash
mysql -uroot -proot
-
在从数据库中配置主从复制
change master to master_host='宿主机ip', master_user='slave', master_password='123456', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30;
master_host:主数据库的IP地址;
master_port:主数据库的运行端口;
master_user:在主数据库创建的用于同步数据的用户账号;
master_password:在主数据库创建的用于同步数据的用户密码;
master_log_file:指定从数据库要复制数据的日志文件,通过查看主数据的状态,获取File参数;
master_log_pos:指定从数据库从哪个位置开始复制数据,通过查看主数据的状态,获取Position参数;
master_connect_retry:连接失败重试的时间间隔,单位为秒。 -
在从数据库中查看主从同步状态
show slave status \G;
-
在从数据库中开启主从同步
start slave;
-
查看从数据库状态发现已经同步
-
主从测试
主机新建库、使用库、新建表、插入数据OK
从机查看记录ok
1.2 安装redis集群(哈希槽分区进行亿级数据存储)
1.2.1 相关面试题
1~2亿条数据需要缓存,请问如何设计这个存储案例
单机单台100%不可能,肯定是分布式存储,用redis如何落地?
上述问题阿里P6~P7工程案例和场景设计类必考题目,一般业界有3种解决方案
-
哈希取余分区
2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:
hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。优点:简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。 -
一致性哈希算法分区
提出一致性Hash解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系
-
算法构建一致性哈希环
一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。
它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对232取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-232-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、……直到232-1,也就是说0点左侧的第一个点代表232-1, 0和232-1在零点中方向重合,我们把这个由232个点组成的圆环称为Hash环。
-
服务器IP节点映射
将集群中各个IP节点映射到环上的某一个位置。将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:
-
key落到服务器的落键规则
当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
优点:一致性哈希算法的容错性:
假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。
一致性哈希算法的扩展性:
数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。
缺点 :一致性哈希算法的数据倾斜问题
一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器:
-
-
哈希槽分区
解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配。
一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。可以指定哪些编号的槽分配给哪个主节点。集群会记录节点和槽的对应关系。解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取余,余数是几key就落入对应的槽里。slot = CRC16(key) % 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,key之A 、B在Node2, key之C落在Node3上。
1.2.2 集群搭建步骤
https://editor.csdn.net/md/?articleId=125790214
第二章 Dockerfile解析
2.1 是什么
Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。
官网
2.2 DockerFile构建过程解析
2.2.1 Dockerfile内容基础知识
- 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
- 每条指令都会创建一个新的镜像层并对镜像进行提交
2.2.2 Docker执行Dockerfile的大致流程
- docker从基础镜像运行一个容器
- 执行一条指令并对容器作出修改
- 执行类似docker commit的操作提交一个新的镜像层
- docker再基于刚提交的镜像运行一个新容器
- 执行dockerfile中的下一条指令直到所有指令都执行完成
2.2.3 Dockerfile 常用保留字指令
保留字 | 说明 |
---|---|
FROM | 拉取基础镜像,作为模板,然后再在其上进行指令操作,构建自己的镜像 |
MAINTAINER | 镜像维护者的姓名和邮箱地址 |
COPY | 镜拷贝目录或文件进镜像,不自动处理url与tar压缩包 COPY src dest |
ADD | 将宿主机中某个目录下的文件拷贝进镜像,并且会自动处理url以及解压tar压缩包 |
RUN | 容器构建时执行的命令包括:shell命令与exec命令像 |
CMD | 容器构建完毕后运行的指令,同样包括shell命令与exec命令,一个Dockerfile中只有最后一个CMD指令会被运行,也会被docker run后面的参数替换 |
ENV | 配置环境变量 |
EXPOSE | 当前容器对外暴露的端口 |
VOLUME | 容器卷,用于数据保存和持久化工作。类似于linux的镜像挂载 |
ENTRYPOINT | 与CMD指令相似,但是不会被docker run后面的参数给替换掉,而是会被当做参数更新原有参数(与CMD指令同时使用,此时CMD指令起到的作用就是变换参数) |
WORKDIR | 指定终端登录容器的默认工作目录 |
USER | 指定登录容器的用户,默认root,一般不怎么使用 |
需要注意的命令
- CMD
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换 - CMD和RUN的区别
CMD是在docker run 时运行
RUN是在 docker build时运行。 - ENTRYPOINT
ENTRYPOINT可以和CMD一起用,一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参。
当指定了ENTRYPOINT后,CMD的含义就发生了变化,不再是直接运行其命令而是将CMD的内容作为参数传递给ENTRYPOINT指令,他两个组合会变成
如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。
2.3 案例
2.3.1 自定义mycentosjava8
-
要求
Centos7镜像具备vim+ifconfig+jdk8JDK下载镜像地址
-
编写
FROM centos:7 MAINTAINER cjz<[email protected]> ENV MYPATH /usr/local WORKDIR $MYPATH #安装vim编辑器 RUN yum -y install vim #安装ifconfig命令查看网络IP RUN yum -y install net-tools #安装java8及lib库 RUN yum -y install glibc.i686 RUN mkdir /usr/local/java #ADD 是相对路径jar,把jdk-8u171-linux-x64.tar.gz添加到容器中,安装包必须要和Dockerfile文件在同一位置 ADD jdk-8u171-linux-x64.tar.gz usr/local/java/ #配置java环境变量 ENV JAVA_HOME /usr/local/java/jdk1.8.0_171 ENV JRE_HOME $JAVA_HOME/jre ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib:$CLASSPATH ENV PATH $JAVA_HOME/bin:$PATH EXPOSE 80
-
构建
docker build -t 新镜像名字:TAG .
2.4 虚悬镜像
仓库名、标签都是的镜像,俗称dangling image
删除命令
docker images prune
第三章 Docker 微服务实战
3.1 新建一个普通微服务模块
-
pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.6</version> <relativePath/> </parent> <groupId>com.atguigu.docker</groupId> <artifactId>docker_boot</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mapper.version>4.1.5</mapper.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <dependencies> <!--SpringBoot通用依赖模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <version>3.1.0</version> </plugin> </plugins> </build> </project>
-
yml 文件
server: port: 6001
-
主启动类
package com.atguigu.docker; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DockerBootApplication { public static void main(String[] args) { SpringApplication.run(DockerBootApplication.class, args); } }
-
业务类
package com.atguigu.docker.controller; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import java.util.UUID; @RestController public class OrderController { @Value("${server.port}") private String port; @RequestMapping("/order/docker") public String helloDocker() { return "hello docker"+"\t"+port+"\t"+ UUID.randomUUID().toString(); } @RequestMapping(value ="/order/index",method = RequestMethod.GET) public String index() { return "服务端口号: "+"\t"+port+"\t"+UUID.randomUUID().toString(); } }
3.2 通过dockerfile发布微服务部署到docker容器
-
IDEA工具里面搞定jar包
-
编写Dockerfile
# 基础镜像使用java FROM java:8 # 作者 MAINTAINER zzyy # VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp VOLUME /tmp # 将jar包添加到容器中并更名为zzyy_docker.jar ADD docker_boot-0.0.1-SNAPSHOT.jar zzyy_docker.jar # 运行jar包 RUN bash -c 'touch /zzyy_docker.jar' ENTRYPOINT ["java","-jar","/zzyy_docker.jar"] #暴露6001端口作为微服务 EXPOSE 6001
-
将微服务jar包和Dockerfile文件上传到同一个目录下/mydocker
-
构建镜像
docker build -t zzyy_docker:1.6 .
-
运行容器
docker run -d -p 6001:6001 zzyy_docker:1.6
第四章 Docker 网络
4.1 是什么
-
docker不启动,默认网络情况
-
docker 启动后,网络情况
-
查看docker网络模式命令
docker network ls
默认创建3大网络模式
4.2 常用基本命令
命令 | 说明 |
---|---|
docker network --help | 全部命令说明 |
docker network ls | 查看网络 |
docker network inspect XXX网络名字 | 查看网络源数据 |
docker network rm XXX网络名字 | 删除网络 |
docker network create XXX网络名字 | 创建网络 |
4.3 有什么用
- 容器间的互联和通信以及端口映射
- 容器IP变动时候可以通过服务名直接网络通信而不受到影响
4.4 网络模式
-
bridge模式
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),该桥接网络的名称为docker0,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。Docker使用Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。
docker run 的时候,没有指定network的话默认使用的网桥模式就是bridge,使用的就是docker0。在宿主机ifconfig,就可以看到docker0和自己create的network(后面讲)eth0,eth1,eth2……代表网卡一,网卡二,网卡三……,lo代表127.0.0.1,即localhost,inet addr用来表示网卡的IP地址
网桥docker0创建一对对等虚拟设备接口一个叫veth,另一个叫eth0,成对匹配。
代码案例
docker run -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8
docker run -d -p 8082:8080 --name tomcat82 billygoo/tomcat8-jdk8
-
host模式
直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行NAT 转换。
容器将不会获得一个独立的Network Namespace, 而是和宿主机共用一个Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的IP和端口。
docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8
docker启动时指定–network=host或-net=host,如果还指定了-p映射端口,那这个时候就会有此警告,
并且通过-p设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。 -
none模式
禁用网络功能,只有lo标识(就是127.0.0.1表示本地回环)
docker run -d -p 8084:8080 --network none --name tomcat84 billygoo/tomcat8-jdk8
-
container模式
新建的容器和已经存在的一个容器共享一个网络ip配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。
docker run -it --name alpine1 alpine /bin/sh
docker run -it --network container:alpine1 --name alpine2 alpine /bin/sh
-
自定义网络
自定义网络之前通过服务名容器之间是ping不通的
新建自定义网络docker network create cjz_network
新建容器加入上一步新建的自定义网络docker run -d -p 8081:8080 --network cjz_network --name tomcat81 billygoo/tomcat8-jdk8
docker run -d -p 8082:8080 --network cjz_network --name tomcat82 billygoo/tomcat8-jdk8
互相ping测试
自定义网络本身就维护好了主机名和ip的对应关系(ip和域名都能通)
第五章 Docker-compose容器编排
5.1 是什么
Docker-Compose是Docker官方的开源项目,负责实现对Docker容器集群的快速编排
5.2 能干嘛
Compose允许用户通过一个单独的docker-compose.yml模板文件(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
可以很容易地用一个配置文件定义一个多容器的应用,然后使用一条指令安装这个应用的所有依赖,完成构建。Docker-Compose 解决了容器与容器之间如何管理编排的问题。
5.3 安装
-
官方资料
-
安装步骤
curl -SL https://github.com/docker/compose/releases/download/v2.6.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose chmod +x /usr/local/bin/docker-compose #测试 docker-compose --version
-
卸载
sudo rm /usr/local/bin/docker-compose
5.4 核心概念
-
一文件
docker-compose.yml
-
两要素
服务(service):一个个应用容器实例,比如订单微服务、库存微服务、mysql容器、nginx容器或者redis容器
工程:由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
5.5 Compose使用的三个步骤
- 编写Dockerfile定义各个微服务应用并构建出对应的镜像文件
- 使用 docker-compose.yml 定义一个完整业务单元,安排好整体应用中的各个容器服务。
- 最后,执行docker-compose up命令 来启动并运行整个应用程序,完成一键部署上线
5.6 Compose常用命令
命令 | 说明 |
---|---|
docker-compose -h | 查看帮助 |
docker-compose up | 启动所有docker-compose服务 |
docker-compose up -d | 启动所有docker-compose服务并后台运行 |
docker-compose down | 停止并删除容器、网络、卷、镜像。 |
docker-compose exec | docker-compose exec docker-compose.yml文件中写的服务id /bin/bash |
docker-compose ps | 展示当前docker-compose编排过的运行的所有容器 |
docker-compose top | 展示当前docker-compose编排过的容器进程 |
docker-compose logs | yml里面的服务id # 查看容器输出日志 |
docker-compose config | 检查配置 |
docker-compose config -q | 检查配置,有问题才有输出 |
docker-compose restart | 重启服务 |
docker-compose start | 启动服务 |
docker-compose stop | 停止服务 |
5.7 Compose编排微服务
5.7.1 改造之前的docker_boot微服务
-
pow文件
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>5.1.47</mysql.version> <druid.version>1.1.16</druid.version> <mapper.version>4.1.5</mapper.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--guava Google 开源的 Guava 中自带的布隆过滤器--> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>23.0</version> </dependency> <!-- redisson --> <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.4</version> </dependency> <!--SpringBoot通用依赖模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!--SpringBoot与Redis整合依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--springCache--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!--springCache连接池依赖包--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency> <!--Mysql数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--SpringBoot集成druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <!--mybatis和springboot整合--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.10</version> </dependency> <!--通用基础配置junit/devtools/test/log4j/lombok/hutool--> <!--hutool--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> <!--persistence--> <dependency> <groupId>javax.persistence</groupId> <artifactId>persistence-api</artifactId> <version>1.0.2</version> </dependency> <!--通用Mapper--> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>${mapper.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.0.0.RELEASE</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
-
yml文件
server: port: 6001 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.80.131:3306/db2021?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 druid: test-while-idle: false redis: database: 0 host: 192.168.80.131 port: 6379 password: 123456 lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 swagger2: enabled: true mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.atguigu.docker.entities
-
主启动类
package com.docker.test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import tk.mybatis.spring.annotation.MapperScan; @SpringBootApplication @MapperScan("com.docker.test.mapper") //import public class Application { public static void main(String[] args) { SpringApplication.run(Application.class,args); } }
-
config
package com.docker.test.config; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.io.Serializable; @Configuration @Slf4j public class RedisConfig { /** * @param lettuceConnectionFactory * @return * * redis序列化的工具配置类,下面这个请一定开启配置 * 127.0.0.1:6379> keys * * 1) "ord:102" 序列化过 * 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过 */ @Bean public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { RedisTemplate<String,Serializable> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); //设置key序列化方式string redisTemplate.setKeySerializer(new StringRedisSerializer()); //设置value的序列化方式json redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
package com.docker.test.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.text.SimpleDateFormat; import java.util.Date; @Configuration @EnableSwagger2 public class SwaggerConfig { @Value("${spring.swagger2.enabled}") private Boolean enabled; @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(enabled) .select() .apis(RequestHandlerSelectors.basePackage("com.docker.test")) //你自己的package .paths(PathSelectors.any()) .build(); } public ApiInfo apiInfo() { return new ApiInfoBuilder() .title("尚硅谷Java大厂技术"+"\t"+new SimpleDateFormat("yyyy-MM-dd").format(new Date())) .description("docker-compose") .version("1.0") .termsOfServiceUrl("https://www.atguigu.com/") .build(); } }
-
entity
package com.docker.test.entity; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import java.util.Date; @Table(name = "t_user") public class User { @Id @GeneratedValue(generator = "JDBC") private Integer id; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 性别 0=女 1=男 */ private Byte sex; /** * 删除标志,默认0不删除,1删除 */ private Byte deleted; /** * 更新时间 */ @Column(name = "update_time") private Date updateTime; /** * 创建时间 */ @Column(name = "create_time") private Date createTime; /** * @return id */ public Integer getId() { return id; } /** * @param id */ public void setId(Integer id) { this.id = id; } /** * 获取用户名 * * @return username - 用户名 */ public String getUsername() { return username; } /** * 设置用户名 * * @param username 用户名 */ public void setUsername(String username) { this.username = username; } /** * 获取密码 * * @return password - 密码 */ public String getPassword() { return password; } /** * 设置密码 * * @param password 密码 */ public void setPassword(String password) { this.password = password; } /** * 获取性别 0=女 1=男 * * @return sex - 性别 0=女 1=男 */ public Byte getSex() { return sex; } /** * 设置性别 0=女 1=男 * * @param sex 性别 0=女 1=男 */ public void setSex(Byte sex) { this.sex = sex; } /** * 获取删除标志,默认0不删除,1删除 * * @return deleted - 删除标志,默认0不删除,1删除 */ public Byte getDeleted() { return deleted; } /** * 设置删除标志,默认0不删除,1删除 * * @param deleted 删除标志,默认0不删除,1删除 */ public void setDeleted(Byte deleted) { this.deleted = deleted; } /** * 获取更新时间 * * @return update_time - 更新时间 */ public Date getUpdateTime() { return updateTime; } /** * 设置更新时间 * * @param updateTime 更新时间 */ public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } /** * 获取创建时间 * * @return create_time - 创建时间 */ public Date getCreateTime() { return createTime; } /** * 设置创建时间 * * @param createTime 创建时间 */ public void setCreateTime(Date createTime) { this.createTime = createTime; } }
package com.docker.test.entity; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; /** * @Author cjz * @Date 2022/7/18 21:08 */ @NoArgsConstructor @AllArgsConstructor @Data @ApiModel(value = "用户信息") public class UserDTO { @ApiModelProperty(value = "用户ID") private Integer id; @ApiModelProperty(value = "用户名") private String username; @ApiModelProperty(value = "密码") private String password; @ApiModelProperty(value = "性别 0=女 1=男 ") private Byte sex; @ApiModelProperty(value = "删除标志,默认0不删除,1删除") private Byte deleted; @ApiModelProperty(value = "更新时间") private Date updateTime; @ApiModelProperty(value = "创建时间") private Date createTime; /** * @return id */ public Integer getId() { return id; } /** * @param id */ public void setId(Integer id) { this.id = id; } /** * 获取用户名 * * @return username - 用户名 */ public String getUsername() { return username; } /** * 设置用户名 * * @param username 用户名 */ public void setUsername(String username) { this.username = username; } /** * 获取密码 * * @return password - 密码 */ public String getPassword() { return password; } /** * 设置密码 * * @param password 密码 */ public void setPassword(String password) { this.password = password; } /** * 获取性别 0=女 1=男 * * @return sex - 性别 0=女 1=男 */ public Byte getSex() { return sex; } /** * 设置性别 0=女 1=男 * * @param sex 性别 0=女 1=男 */ public void setSex(Byte sex) { this.sex = sex; } /** * 获取删除标志,默认0不删除,1删除 * * @return deleted - 删除标志,默认0不删除,1删除 */ public Byte getDeleted() { return deleted; } /** * 设置删除标志,默认0不删除,1删除 * * @param deleted 删除标志,默认0不删除,1删除 */ public void setDeleted(Byte deleted) { this.deleted = deleted; } /** * 获取更新时间 * * @return update_time - 更新时间 */ public Date getUpdateTime() { return updateTime; } /** * 设置更新时间 * * @param updateTime 更新时间 */ public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } /** * 获取创建时间 * * @return create_time - 创建时间 */ public Date getCreateTime() { return createTime; } /** * 设置创建时间 * * @param createTime 创建时间 */ public void setCreateTime(Date createTime) { this.createTime = createTime; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", sex=" + sex + '}'; } }
-
mapper
package com.docker.test.mapper; import com.docker.test.entity.User; import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper<User> { }
-
service
package com.docker.test.service; import com.docker.test.entity.User; import com.docker.test.mapper.UserMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service @Slf4j public class UserService { public static final String CACHE_KEY_USER = "user:"; @Resource private UserMapper userMapper; @Resource private RedisTemplate redisTemplate; /** * addUser * @param user */ public void addUser(User user) { //1 先插入mysql并成功 int i = userMapper.insertSelective(user); if(i > 0) { //2 需要再次查询一下mysql将数据捞回来并ok user = userMapper.selectByPrimaryKey(user.getId()); //3 将捞出来的user存进redis,完成新增功能的数据一致性。 String key = CACHE_KEY_USER+user.getId(); redisTemplate.opsForValue().set(key,user); } } /** * findUserById * @param id * @return */ public User findUserById(Integer id) { User user = null; String key = CACHE_KEY_USER+id; //1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql user = (User) redisTemplate.opsForValue().get(key); if(user == null) { //2 redis里面无,继续查询mysql user = userMapper.selectByPrimaryKey(id); if(user == null) { //3.1 redis+mysql 都无数据 //你具体细化,防止多次穿透,我们规定,记录下导致穿透的这个key回写redis return user; }else{ //3.2 mysql有,需要将数据写回redis,保证下一次的缓存命中率 redisTemplate.opsForValue().set(key,user); } } return user; } }
-
controller
package com.docker.test.TesControlelr; import cn.hutool.core.util.IdUtil; import com.docker.test.entity.User; import com.docker.test.service.UserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.Random; @Api("用户User接口") @RestController @Slf4j public class TestController { @Resource private UserService userService; @ApiOperation("数据库新增3条记录") @RequestMapping(value = "/user/add",method = RequestMethod.POST) public void addUser() { for (int i = 1; i <=3; i++) { User user = new User(); user.setUsername("zzyy"+i); user.setPassword(IdUtil.simpleUUID().substring(0,6)); user.setSex((byte) new Random().nextInt(2)); userService.addUser(user); } } }
-
mapper
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.docker.test.mapper.UserMapper"> <resultMap id="BaseResultMap" type="com.docker.test.entity.User"> <!-- WARNING - @mbg.generated --> <id column="id" jdbcType="INTEGER" property="id" /> <result column="username" jdbcType="VARCHAR" property="username" /> <result column="password" jdbcType="VARCHAR" property="password" /> <result column="sex" jdbcType="TINYINT" property="sex" /> <result column="deleted" jdbcType="TINYINT" property="deleted" /> <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" /> <result column="create_time" jdbcType="TIMESTAMP" property="createTime" /> </resultMap> </mapper>
5.7.2 将微服务构建成镜像
-
编写Dockerfile
# 基础镜像使用java FROM java:8 # 作者 MAINTAINER cjz # VOLUME 指定临时文件目录为/tmp,在主机/var/lib/docker目录下创建了一个临时文件并链接到容器的/tmp VOLUME /tmp # 将jar包添加到容器中并更名为zzyy_docker.jar ADD Docker_boot-1.0-SNAPSHOT.jar cjz_docker.jar # 运行jar包 RUN bash -c 'touch /cjz_docker.jar' ENTRYPOINT ["java","-jar","/cjz_docker.jar"] #暴露6001端口作为微服务 EXPOSE 6001
-
构建镜像
docker build -t cjz_docker:1.6 .
5.7.3 使用Compose
-
编写docker-compose文件
version: "3" services: microService: image: cjz_docker:1.6 container_name: ms01 ports: - "6001:6001" volumes: - /cjz/dockerCompose/microService:/data networks: - atguigu_net depends_on: - redis - mysql redis: image: redis:6.0.8 ports: - "6379:6379" volumes: - /cjz/docker/redis/redis.conf:/etc/redis/redis.conf - /cjz/docker/redis/data:/data networks: - atguigu_net command: redis-server /etc/redis/redis.conf mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: '123456' MYSQL_ALLOW_EMPTY_PASSWORD: 'no' MYSQL_DATABASE: 'db2021' MYSQL_USER: 'zzyy' MYSQL_PASSWORD: 'zzyy123' ports: - "3306:3306" volumes: - /cjz/dockerCompose/mysql/db:/var/lib/mysql - /cjz/dockerCompose/mysql/my.cnf:/etc/my.cnf - /cjz/dockerCompose/mysql/init:/docker-entrypoint-initdb.d networks: - atguigu_net command: --default-authentication-plugin=mysql_native_password #解决外部无法访问 networks: atguigu_net:
-
在dokcer-compose.yml文件当前目录执行docker-compose
docker-compose up -d
-
进入mysql容器实例并新建库db2021+新建表t_user
docker exec -it 容器实例id /bin/bash mysql -uroot -p create database db2021; use db2021; CREATE TABLE `t_user` ( `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名', `password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码', `sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ', `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除', `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
访问 http://localhost:你的微服务端口/swagger-ui.html#/ 测试通过
第六章 Docker 轻量级可视化工具Portainer
6.1 是什么
Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便地管理Docker环境,包括单机环境和集群环境。
6.2 安装
-
官网
-
安装步骤
安装命令
docker run -d -p 8000:8000 -p 9000:9000 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v /cjz/docker/portainer_data:/data portainer/portainer
第一次登录需创建admin 访问路径 IP:9000
第七章 Docker容器监控之 CAdvisor+lnfluxDB+Granfana
7.1 是什么
CAdvisor监控收集+InfluxDB存储数据+Granfana展示图表
-
CAdvisor
CAdvisor是Google开源的容器资源监控和性能分析工具,它是专门为容器而生。同时提供了WEB页面用于查看容器的实时运行状态。CAdvisor默认存储2分钟的数据,而且只是针对单物理机。不过,CAvisor提供了很多数据集成接口,支持influxdb、redis\kafka、ES等,可以对应配置将监控数据存储起来 -
InfluxDB
influxDB是用Go语言编写的一个开源分布式时序、事件和指标数据库,无需外部依赖。CAdvisor默认只在本机保存最近2分钟的数据,为了持久化存储数据和统一收集展示监控
数据,需要将数据存储到InfluxDB中。InfluxDB是一个时序数据库,专门用于存储时序相关数据,很适合存储CAdvisor的数据。而且,CAdvisor本身已经提供了InfluxDB的集成方法,指定配置即可。InfluxDB主要功能:
基于时间序列,支持与时间有关的相关函数(如最大、最小、求和等);
可度量性:你可以实时对大量数据进行计算;·
基于事件:它支持任意的事件数据; -
Granfana
Grafana是一个开源的数据监控分析可视化平台,支持多种数据源配置(支持的数据源包括InfluxDB,MySQL,Elasticsearch,OpenTSDB,Graphite等)和丰富的插件及模板功能,支持图表权限控制和报警。
7.2 compose容器编排,一套带走
-
新建目录
-
新建3件套组合docker-compose.ym文件
version: '3.1' volumes: grafana_data: {} services: influxdb: container_name: influxdb image: tutum/influxdb:0.9 restart: always environment: - PRE_CREATE_DB=cadvisor ports: - "8083:8083" - "8086:8086" volumes: - ./data/influxdb:/data networks: - cignet cadvisor: image: google/cadvisor container_name: cadvisor links: - influxdb:influxsrv command: - -storage_driver=influxdb - -storage_driver_db=cadvisor - -storage_driver_host=influxsrv:8086 restart: always privileged: true ports: - "8080:8080" volumes: - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro networks: - cignet grafana: container_name: grafana user: "104" image: grafana/grafana user: "104" restart: always links: - influxdb:influxsrv ports: - "3000:3000" volumes: - grafana_data:/var/lib/grafana environment: - HTTP_USER=admin - HTTP_PASS=admin - INFLUXDB_HOST=influxsrv - INFLUXDB_PORT=8086 - INFLUXDB_NAME=cadvisor - INFLUXDB_USER=root - INFLUXDB_PASS=root networks: - cignet networks: cignet: driver: bridge
-
启动docker-compose文件
docker-compose up
-
测试
浏览cAdvisor收集服务 http:ip:8080/
第一次访问慢,请稍等,cadvisor也有基础的图形展现功能,这里主要用它来作数据采集
浏览influxdb存储服务,http://ip:8083/
浏览grafana展现服务,http://ip:3000默认帐户密码(admin/admin)
配置步骤
- 配置数据源
- 选择influxdb数据源
3.配置面板panel
- 配置数据源