简述
最近看了这部分的网课,但网课只是大概了解了一下这部分的基础知识,后期还会专门找视频学习
有些人喜欢看书学习,有些人喜欢看视频学习,我觉得初期了解一样东西,看视频更好,后期熟悉了这部分内容
可能看书能学到更多的东西,话不多说,直接来进入正题。
Docker
Docker一些基础命令我就不介绍了,我会把docker基础内容pdf放到github上,
这里我记录一下docker部署一个前后端分离项目,使用基本的Dockerfile部署项目,没有使用Docker-compose的方式
下次有时间尝试一下使用Docker-compose方式部署项目,到时候再记录一下
回归正题,使用docker部署前后端分离的项目步骤如下:
先部署mysql容器
拉取mysql镜像:
docker pull mysql
配置宿主机和容器的映射文件
这样我们可以通过修改宿主机文件从而修改mysql容器中的数据和配置//1,创建三个宿主机映射文件,在/dockerFile/myproject/arsystem/arsystem-mysql/下 mkdir data logs conf //2,在该conf目录下创建一个my.cnf文件,这会覆盖掉mysql容器中的配置文件 cd conf touch my.cnf
创建mysql容器
docker run \ --name arsystem-mysql \ -p 3306:3306 \ -v /dockerFile/myproject/arsystem/arsystem-mysql/data:/var/lib/mysql \ -v /dockerFile/myproject/arsystem/arsystem-mysql/conf:/etc/mysql/conf.d \ -v /dockerFile/myproject/arsystem/arsystem-mysql/logs:/var/log/mysql \ -e MYSQL_ROOT_PASSWORD=123765 \ -d mysql //上述,mysql最新版,所以没有加tag版本号,设置了mysql密码,进行了文件映射,完美~ //上述宿主机文件路径一定要是绝对路径,不然会当成数据卷
创建mysql容器后,使用sqlyog连接该数据库,账号:root,密码:123765,IP地址就是宿主机IP地址
你可能出现连接不上远程mysql的情况:连接可能爆:Authentication plugin 'caching_sha2_password' cannot be loaded错误 报错原因: MySQL8.0以后,身份验证插件默认使用caching_sha2_password,旧版则是mysql_native_password 这可能造成一些MySQL客户端连接不上的问题,从而报错:plugin caching_sha2_password could not be loaded 解决办法: 1,登录mysql容器中的mysql: docker exec -it arsystem-mysql bash mysql -uroot -p 输入123765(密码) 2,执行以下命令: #用于更换MySQL root账户的密码和加密方式 ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY '123765'; #用于刷新权限 FLUSH PRIVILEGES;
使用sqlyog连接远程mysql后,根据
xxx.sql
文件创建数据库和表
接着部署springboot容器
首先修改项目中数据库的地址
修改项目中mysql的IP地址为mysql容器的地址,即宿主机地址,然后再将项目打成jar包配置springboot项目Dockerfile文件
//创建Dockerfile文件,并将项目jar包放在同一目录下,Dockerfile文件内容如下: FROM java:8 ADD arsystem-0.0.1-SNAPSHOT.jar app.jar RUN bash -c 'touch /app.jar' ENTRYPOINT ["java","-jar","/app.jar"] //根据java8镜像获取底层依赖,将项目jar包加入镜像文件系统的根目录下并改名称app.jar //第三句是修改文件的创建修改时间,最后一句是运行我们的项目
根据Dockerfile文件制作镜像
docker build -t arsystem-backend . //-t 表示设置项目的名字和版本号,版本号不设置则为latest //. 表示Dockerfile的位置,.表示Dockerfile就在当前目录下
启动项目容器
制作好项目镜像后,查看:docker images 然后根据该镜像启动一个容器: docker run \ --name arsystem-backend \ -p 8081:8081 \ -d arsystem-backend //当然,我觉得在Dockerfile中应该要暴露8081端口的,这里我漏了 //另外我的Dockerfile并不好,可能需要配置一些其他宿主机文件映射啥的
最后配置nginx容器
拉取nginx镜像,创建宿主机映射文件
1,拉取nginx镜像:docker pull nginx 2,创建宿主机映射文件,这里有两个地方需要映射, 第一个是nginx容器的配置文件目录,/etc/nginx/conf.d目录 第二个是前端vue文件在nginx容器中存放的目录,/usr/share/nginx/html目录 所以我们需要把vue项目生成的dist目录拿到宿主机上, 我把dist目录在了/dockerFile/myproject/arsystem/arsystem-frontend下,用来映射html目录 然后在dist同级目录创建好nginx-config目录,用来映射容器中conf.d目录
宿主机nginx-config目录用来映射nginx容器的conf.d目录,该目录下所有
*.conf
文件都会被加载进
nginx配置文件nginx.conf中,所以我们需要在nginx-config文件中创建一个nginx配置文件//nginx-config目录下创建keyi.conf,该文件内容如下: server { listen 80; server_name 47.98.138.53; location / { root /usr/share/nginx/html; index index.html index.htm; } } //请求一进来,被转到html目录下的index.html中
创建nginx容器
//宿主机映射目录创建好,nginx配置文件创建好后,下面就创建nginx容器了 docker run \ > --name arsystem-nginx \ > -p 80:80 \ > -v /dockerFile/myproject/arsystem/nginx-config/:/etc/nginx/conf.d \ > -v /dockerFile/myproject/arsystem/arsystem-frontend/dist/:/usr/share/nginx/html/ \ > -d nginx //将宿主机映射目录挂载到nginx容器目录中,这样,我们就可以通过修改keyi.conf来修改容器中的nginx配置文件 //我们的vue项目也被部署在nginx中,当我们访问http://宿主机ip/,就被转发到html目录下index.html中
以上就是docker部署前后端分离项目的步骤,其中还有很多注意的地方,
例如docker容器文件挂载问题 如果宿主机目录有文件,容器目录中有文件会导致容器目录中文件被覆盖,如果宿主机目录没有文件 而容器目录中有文件,则容器内文件会拷贝到宿主机目录中 例如docker创建容器时,宿主机目录务必是绝对路径,不然会被识别成数据卷 例如docker的文件挂载最好是目录挂载目录,如果是文件挂载文件,可能导致宿主机文件修改后,容器内文件没有被修改
下面是一些大佬docker文章
- docker部署vue项目
- 使用docker容器化开发部署Vue项目
- 使用docker部署springboot项目到服务器
- docker数据的覆盖问题
- Docker MySql报2059错误
- 关于Docker目录挂载的总结
- 解密 Docker 挂载文件,宿主机修改后容器里文件没有修改
- Docker的常用管理命令&Docker将数据挂载到容器的三种方式
最后是网课中关于docker的基础笔记PDF:docker笔记,如果访问不了尝试使用外网访问。
RabbitMQ
RabbitMQ,一款消息队列工具,让我们可以实现异步通信,更多具体内容见最下方的Rabbit基础文档
我大概了解了一下Rabbit基本概念,SpringAMQP的基本使用,队列,和3种交换机(分发规则不同)的使用
本小节不深入RabbitMQ,只记录下RabbitMQ的5个基本消息模型,使用SpringAMQP实现一下这5种模型
以后有机会将专门找这类网课学习,本次实验通过docker搭建rabbitMQ服务。
配置rabbitMQ:
//拉取rabbitMQ镜像
docker pull rabbitmq:3-management
//开启rabbitMQ容器
docker run -d -p 5672:5672 -p 15672:15672 --name my-rabbit rabbitmq:3-management
配置依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置application.yaml:
spring:
rabbitmq:
host: 47.98.138.53
port: 5672
virtual-host: /
username: wanyi
password: 123765
#rabbitMQ初始用户名和密码为:guest
5种基本消息模型:
BasicQueue,基本消息模型
发送方
@Autowired private RabbitTemplate rabbitTemplate; @Test public void simpleQueueProvider(){ String queueName = "simple.queue"; String msg = "这里是简单消息队列测试消息"; rabbitTemplate.convertAndSend(queueName,msg); //参数应该少给了,rabbit需要有该队列 }
接收方
@Component public class MyRabbitMQConsumerComponent { /*simpleQueue测试*/ @RabbitListener(queues = "simple.queue") public void simpleQueueTest(String msg){ System.out.println("simple.queue----接收消息为:"+msg); } }
WorkQueue,工作消息模型
发送方
@Autowired private RabbitTemplate rabbitTemplate; //发送50条消息 @Test public void workQueueProvider(){ String queueName = "work.queue"; String msg = "工作队列第"; for (int i = 1; i <= 50; i++) { rabbitTemplate.convertAndSend(queueName,msg+i+"条消息"); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }
接收方
/*workQueue测试,一个queue,两个消费者*/ @RabbitListener(queues = "work.queue") public void workQueue1Test(String msg) throws InterruptedException { System.out.println("work.queue----消费者1接收消息为:"+msg+ LocalTime.now()); Thread.sleep(20); } @RabbitListener(queues = "work.queue") public void workQueue2Test(String msg) throws InterruptedException { System.err.println("work.queue----消费者2接收消息为:"+msg+ LocalTime.now()); Thread.sleep(200); } /* 我们可以看到,queue1消费者比queue2消费者处理数据更快,本该处理更多请求,但由于rabbitMQ中消费者 可以预取消息,即消费者处理之前,先把消息平均分配好给各个消费者,所以导致queue1和queue2 处理的消息数量一样,并没有利用好queue1的优势,我们可以通过配置,实现每次预取从默认无上限 改成每次预取1条消息 */ spring: rabbitmq: host: 47.98.138.53 port: 5672 virtual-host: / username: wanyi password: 123765 listener: simple: prefetch: 1 #控制消费者预取的消费数量,改成1,表示每个消费者先从消息队列中拿一个消息。
FanoutExchange,订阅模型之Fanout
订阅模式中使用到Exchange,交换机,其作用:交换机本身不存储消息,而是接收消息并路由给队列根据路由的规则不同大致分为FanoutExchange,DirectExchange,TopicExchange三种,它们区别如下:
fanoutExchange会将消息发给每一个绑定的队列 directExchange会将接收到的消息根据规则(routingKey)路由到指定的Queue,因此称为路由模式(routes) topicExchange,和directExchange类似,不同的是,topicExchange的routingKey可以设置成通配符的形式,简化操作 例如如果队列routingKey为:china.# 则匹配所有key是china.xxx的消息,类似正则匹配,#代表0或多个单词,*代表一个单词
发送方
@Test public void fanoutExchangeProvider(){ String exchangeName = "wanyi.fanout"; String msg = "hello everyone!"; rabbitTemplate.convertAndSend(exchangeName,"",msg); } /* 第二个参数是routingKey,如果不指定则是向每个关联的队列发送消息, 如果指定则是向带有该key的队列发送消息 */
接收方
接收方需要配置一下队列与交换机的关联,配置方式有两种,一种是通过注解的方式,一种是通过
注册bean的方式,FanoutExchange案例这里通过注册bean的方式实现,下面DirectExchange,Topic通过注解实现//FanoutConfig @Configuration public class FanoutConfig { //交换机 @Bean public FanoutExchange fanoutExchange(){ return new FanoutExchange("wanyi.fanout"); } //两个队列 @Bean public Queue fanoutQueue1(){ return new Queue("fanout.queue1"); } @Bean public Queue fanoutQueue2(){ return new Queue("fanout.queue2"); } //队列绑定到交换机上 @Bean public Binding fanoutBinding1(FanoutExchange fanoutExchange, Queue fanoutQueue1){ return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange); } @Bean public Binding fanoutBinding2(FanoutExchange fanoutExchange, Queue fanoutQueue2){ return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange); } }
接收消息:
/*fanoutExchange,两个queue,每个queue一个消费者*/ @RabbitListener(queues = "fanout.queue1") public void fanoutQueue1Test(String msg){ System.out.println("fanout.queue1----接收消息为:"+msg); } @RabbitListener(queues = "fanout.queue2") public void fanoutQueue2Test(String msg){ System.out.println("fanout.queue2----接收消息为:"+msg); }
DirectExchange,订阅模型之Direct
发送方
@Test public void directExchangeProvider(){ String exchangeName = "wanyi.direct"; String msg = "hello red!!!"; rabbitTemplate.convertAndSend(exchangeName,"red",msg); } //与DirectExchange没什么差别,无非指定某些队列发送消息
接收方
/*directExchange,两个queue,每个queue一个消费者*/ @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue1"), exchange = @Exchange(name = "wanyi.direct",type = ExchangeTypes.DIRECT), key = {"red","blue"} )) public void directQueue1Test(String msg){ System.out.println("direct.queue1----接收消息为:"+msg); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "direct.queue2"), exchange = @Exchange(name = "wanyi.direct",type = ExchangeTypes.DIRECT), key = {"red","yellow"} )) public void directQueue2Test(String msg){ System.err.println("direct.queue2----接收消息为:"+msg); }
TopicExchange,订阅模型之Topic
发送方
@Test public void topicExchangeProvider(){ String exchangeName = "wanyi.topic"; String msg = "china fake new !!!"; rabbitTemplate.convertAndSend(exchangeName,"china.news",msg); }
接收方
/*directExchange,两个queue,每个queue一个消费者*/ @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue1"), exchange = @Exchange(name = "wanyi.topic",type = ExchangeTypes.TOPIC), key = {"china.#"} )) public void topicQueue1Test(String msg){ System.out.println("topic.queue1----接收消息为:"+msg); } @RabbitListener(bindings = @QueueBinding( value = @Queue(name = "topic.queue2"), exchange = @Exchange(name = "wanyi.topic",type = ExchangeTypes.TOPIC), key = {"#.news"} )) public void topicQueue2Test(String msg){ System.err.println("topic.queue2----接收消息为:"+msg); } //接收方使用key进行匹配消息
上述便是5种基本消息模型的案例,最后说一下消息转换器,我们可以通过消息队列工具发送任意类型消息
对象,map等等,底层使用JDK的序列化,这种方式数据体积大,可读性差,解决办法就和SpringMVC一样
SpringMVC使用了Jackson做数据格式转换,所以我们也需要配置一个Jackson的消息转换器
添加依赖
<dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.9.10</version> </dependency>
将消息转换器注册进容器中
@Bean public MessageConverter jsonMessageConverter(){ return new Jackson2JsonMessageConverter(); }
最后rabbitMQ的基本资料已经放在github上了,点击下载:rabbitMQ笔记