微服务基础知识概述
1.从单体架构到微服务架构的演进
-
单体架构:一个包含所有功能的应用,适用于初期用户量不大的情况,常使用SPring+Structs+Hibernate+Mysql+Tomcat进行构建
-
微服务架构:将每个具体的业务服务构建成可独立运行的微服务,每个微服务只关注某个特定的功能,服务之间采用REST API(GET/POST/PUT/DELETE)进行通信
2.微服务的优缺点
-
优点:复杂度可控(一个服务只需要关注一个特定的业务领域)、技术选型更灵活(每个微服务都可结合业务特性选择不同的技术栈,如用Java开发的程序或者用C#开发的程序都可以作为一个微服务)、可扩展性更强(对某个微服务进行灵活扩展,如增加集群)、独立部署(每个微服务都是独立运行的进程)、容错性(某个微服务发送故障可通过某些机制实现服务隔离,不影响其他服务)
-
缺点:故障排查(一个请求可能需要经过多个微服务的处理)、服务监控(对数量众多的微服务进行监控开销是很大的)、分布式架构的复杂性(微服务之间的远程通信受网络延迟和网络故障影响)、服务依赖(如微服务之间有生产者(暴露接口给其他服务方)和消费者(调用其他服务的接口方)关系,在更新服务时需要先更新生产者,再更新消费者)、运维成本(如单个服务流量激增时的快速扩容、故障点增多、快速部署和管理众多服务等)
3.微服务架构图
-
多个独立的仅完成某个业务功能的微服务(单一职责),每个微服务独立开发和部署,统称为服务集群
-
注册中心,记录每一个服务的ip、端口、功能项等信息,用于服务拉取或注册服务信息
-
配置中心,统一管理整个服务集群成千上百的服务配置
-
服务网关,一方面对用户身份的校验,另一方面把请求路由到具体的服务,还可以进行负载均衡的工作
-
数据库
-
分布式缓存,把数据库数据放入到内存中,如redis
-
分布式搜索,针对海量数据的复杂搜索、统计和分析
-
消息队列,A调用B,B调用C...,对于请求链路很长的业务,请求时长等于每个服务执行时长之和,而使用消息队列之后,将请求放到消息队列中,不需要等待,从而响应时间缩短(将同步通讯变更为异步通讯的方式),如RabbitMQ
-
分布式日志服务,可以统计所有服务的运行日志,对日志做存储、统计和分析,便于异常定位,如ELK
-
系统监控和链路追踪:实时监控每一个服务的运行状态、CPU负载、内存占用等情况,一旦出现问题则可定位到具体的方法,从而找到异常所在,如Zipkin、Sleuth、Hystrix、Sentinel
-
自动化编译,Jenkins
-
自动化部署,docker、kubernetes、Rancher,11和12统称为持续集成
4.微服务的服务组件包括哪些
5.微服务解决方案技术的对比
Dubbo:2012年开源,当时并没有微服务的概念,Dubbo的提出主要是用于分布式系统的远程调用的,redis用于缓存,zookeeper用于集群缓存,没有配置中心和服务网关
SpringCloud:2015-2017,将全球微服务技术进行整合形成一套完整的微服务体系
SpringCloudAlibaba:在兼容Dubbo和SpringCloud大部分组件的基础上研制了自己的新型组件,实现了自己的注册中心和配置中心Nacos、监控系统Sentinel、在远程调用上支持Dubbo和Feign(Dubbo协议和Http协议)
Nacos —— 服务注册与发现(注册中心)
1.准备工作
什么是注册中心?
N多个消费者服务,N多个生产者服务,依赖关系太复杂,所以需要一个注册中心统一管理
注册中心的运行原理:
-
服务提供者(A)将自身信息(ip、端口号等信息)注册到注册中心(B)
-
服务消费者(C)在访问服务提供者(A)前,先从注册中心(B)获取服务列表,然后从列表中找到目标服务地址(ip、端口号等)然后去请求服务提供者(A)
我们先创建俩个服务,分别是order-service(服务消费者)和user-service(服务提供者),其中Order服务会使用到User的接口,例如:
2.Nacos基础知识
介绍:Nacos是阿里巴巴的产品,现在是SpringCloud中得一个组件,相比Eureka功能更加丰富,在国内受欢迎程度较高
安装:Releases · alibaba/nacos · GitHub,我选择稳定的1.4.3,下载完成后加压到自定义目录即可
启动命令:在nacos\bin下执行 startup.cmd -m standalone (单机模式启动)
访问:http://127.0.0.1:8848/nacos
登录:默认账号和密码均为nacos
在项目中使用nacos服务:
-
父工程添加spring-cloud-alilbaba依赖
-
子工程添加spring-cloud-starter-alibaba-nacos-discovery依赖
-
application.xml添加配置信息
#父工程 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.5.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> #子工程 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> #application.xml配置信息 spring: application: name: orderservice #另一个是userservice cloud: nacos: server-addr: localhost:8848
注册服务列表更新:
3.Nacos服务分级存储模型
-
级别分布:一级是服务,二级是集群(同类型服务处在不同地区),三级是实例(同类服务同地区的不同服务)
-
作用:将同种服务类型的集群服务按地区位置再分类,本地集群不可访问时,再去访问其他集群,从而达到服务高可用性(如北京地区服务集群全部瘫痪则可去访问上海地区服务集群)
我们新建一个user-service02服务来构建一个userservice集群
配置:在application.xml配置文件中指定集群名称(order-service和user-service指定为BJ,user-service02指定为SH)
spring: cloud: nacos: discovery: cluster-name: SH #配置集群类别
为order-service配置负载均衡规则(此规则下:优先选择本地区集群服务进行负载均衡,在同地区集群内采用随机访问机制,本地集群全部瘫痪才会去其他地区集群访问)
userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
点击查看
根据权重来进行负载均衡:
-
老机器少处理请求新机器多处理请求
-
在nacos界面->服务管理->服务列表中找到服务器结点选择 “编辑” 即可配置
-
配置权重控制访问频率,权重值范围为0~1,权重越大访问频率越高,权重为0则不会被访问,这样很方便版本升级维护
4.临时实例和非临时实例
-
nacos默认情况下所有实例均为临时实例,而临时实例在nacos中采用心跳检测(定时发送通信包,规定时间内未收到则视为已离线)来验证服务运行状态(与eureka一致)当心跳检测失败时会从注册表中剔除该服务;
-
而非临时实例不会通过心跳检测来验证运行状态,而是由nacos主动去发请求询问服务是否正常运行,如果反馈异常,则不会剔除而是标记为不健康状态;
-
nacos采用 “定时拉取服务列表注册信息” 和 “注册中心主动推送注册信息变更消息” 相结合的方式(平时每隔一段时间去拉去信息,但如果注册列表发生变化时会立即通知使其迅速更新列表信息)
-
配置非临时实例:在配置文件中指定ephemeral为false
spring: cloud: nacos: discovery: ephemeral: false #设置为非临时实例
5.nacos和eureka的比较
共同点:
-
都支持服务注册和服务拉取
-
都支持对服务提供者以心跳方式做健康检测
不同点:
-
Nacos支持服务端主动检测服务提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
-
临时实例心跳不正常会被剔除,非临时实例则不会被剔除
-
Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
-
Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
AP架构:牺牲数据一致性,失去联系的服务,其信息仍会提供给调用者(宁可返回错误的数据,也比不反回结果要好得多)
CP架构:牺牲可用性,调用者需要等待,直到服务联系重新建立(只会返回正确的数据,否则一直等待)
6.nacos配置管理
新建配置:
在nacos界面->配置管理->配置列表->点击“+”新建配置->Data ID命名为“服务名-开发环境.yaml”(如userservice-dev.yaml),配置内容为需要经常更新的配置信息,最后点击发布
配置获取的过程:
项目启动->从bootstrap.yml(优先级高于application.yaml)中获取nacos地址->读取nacos中添加的配置文件->读取本地application.yml文件->合并两个application.yml文件->创建Spring容器->加载bean
引入nacos配置管理:
添加spring-cloud-starter-alibaba-nacos-nacos-config依赖->新建bootstrap.yml文件并进行配置(主要配置服务名称、环境种类、nacos地址、config文件后缀名等)->将原先在application.yml的配置去掉
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
bootstrap.yml
spring: application: name: userservice profiles: active: dev # 环境 cloud: nacos: server-addr: localhost:8848 # nacos地址 config: file-extension: yaml # 文件后缀名 discovery: cluster-name: BJ #配置集群名称
application.yml
server: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver mybatis: type-aliases-package: cn.test.entity configuration: map-underscore-to-camel-case: true logging: level: com.test: debug
测试
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Value("${pattern.dateformat}") private String dateformat; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat)); } }
热更新配置(无需重启服务即可更新配置)
方法一:在对应类下添加@RefreshScope注解,然后在nacos界面中修改配置内容并发布即可在不重启微服务下实现更新
@RestController @RequestMapping("/user") @RefreshScope public class UserController { ... }
方法二:新建一个PatternProperties类,然后添加@ConfigurationProperties(prefix="pattern")(这里的pattern是nacos配置文件的一个路径关键字用于找到对应的信息,如dateformat),然后在nacos界面中修改配置内容并发布即可在不重启微服务下实现更新
@Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
@Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private PatternProperties patternProperties; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat())); } }
多环境配置共享:即将一些经常变动的多个环境公共的配置信息放在"服务名.yaml文件中",如果公共配置文件、环境配置文件、本地配置文件都有一个相同的属性, 优先级:服务名-profile.yaml > 服务名.yaml > 本地配置(即userservice-dev.yaml>userservice.yaml>本地application.yml)
@Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; private String value; }
@RestController @RequestMapping("/user") public class UserController { @Autowired private PatternProperties properties; @GetMapping("properties") public PatternProperties properties(){ return properties; } }
Docker —— 容器化部署
1.基础知识
普通项目部署常见的问题:
-
不同软件需要不同的依赖和函数库
-
开发、测试、生产环境有差异(如开发环境时Windows、测试环境Centos、生产环境是RedHat)
Docker是什么?
-
Docker是一个快速交付应用、运行应用的技术
-
可以将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任意Linux操作系统
-
运行时利用沙箱机制形成隔离容器,各个应用互不干扰
-
启动、移除都可通过一行命令完成,方便快捷
Docker与虚拟机不同:
-
虚拟机是在操作系统中模拟硬件设备,然后运行另一个操作系统。应用执行时先调用内置操作系统,然后与Hypervisor(Hypervisor可以模拟计算机各种硬件如CPU、内存等)进行交互,然后再将信息传递给外部操作系统,然后与计算机硬件进行交互,性能较差。硬盘占用一般为GB,启动时间为分钟级。
-
Docker相当于启动一个进程,应用执行时直接调用操作系统,其性能更好。硬盘占用一般为MB,启动时间为秒级。
镜像:Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。(镜像实则就是一个文件集,镜像只允许读操作,如用来创建容器和读取镜像中数据)
容器:镜像中的应用程序运行后形成的进程就是容器,Docker会给容器做隔离,对外不可见(容器不能对镜像中数据进行修改,只能拷贝镜像中数据到自身,然后再去修改)
DockerHub:一个Docker镜像的托管平台(类似Github)这样的平台统称为Docker Registry,国内有阿里云镜像库
Docker的架构(CS架构):
-
服务端(server),又叫Docker守护进程,负责处理Docker命令,管理镜像、容器等;
-
客户端(client),发送命令或RestAPI(通过四个HTTP即GET、POST、PUT、DELETE动词,对服务端资源进行操作)请求到Docker服务端
客户端与服务端交互指令介绍:
-
本地构建镜像命令:docker build,命令到达守护进程后利用本地提供的数据构建镜像(适用于自己的微服务或个性化第三方组件)
-
远程拉取镜像命令:docker pull,命令到达守护进程后从Registry拉取指定的镜像(适用于第三方组件,无需自己构建镜像)
-
启动容器命令:docker run,命令到达守护进程后根据已有的镜像创建容器
2.Docker的下载和安装
卸载旧版本的Docker(\代表换行符)
yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-selinux \ docker-engine-selinux \ docker-engine \ docker-ce
运行后没有任何匹配,说明没有装过docker
安装yum工具
yum install -y yum-utils \ device-mapper-persistent-data \ lvm2 --skip-broken
安装完毕
更新本地镜像源(将国外的镜像源更改为国内的镜像源,这样下载和安装Docker的速度会快很多)
# 设置docker镜像源 yum-config-manager \ --add-repo \ https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
更新Linux内核
yum update
执行安装docker的命令
yum install -y docker-ce
关闭防火墙(Docker应用需要用到各种端口,实际生产环境需要逐个防火墙设置,这里我们采用关闭防火墙)
# 关闭 systemctl stop firewalld # 禁止开机启动防火墙 systemctl disable firewalld
启动docker
systemctl start docker # 启动docker服务
查看docker启动状态
systemctl status docker
配置国内镜像服务器(这样在下载镜像时会很快)
进入到阿里云(https://www.aliyun.com/)首页-》产品-》容器与中间件-》容器镜像服务-》管理控制台,然后登录-》镜像加速器,然后选择linux系统版本(我这里是Centos),找到加数器地址并复制
#创建文件夹 sudo mkdir -p /etc/docker #写入内容 sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"] } EOF #重新加载文件 sudo systemctl daemon-reload #重启docker sudo systemctl restart docker
3.Docker的基本操作
3.1 镜像的相关命令
镜像名称:[repository]:[tag],repository是资源名,tag是版本号,如果tag没有指定则默认代表最新版本镜像,例如 mysql:5.7
镜像操作命令:
镜像的拉取
docker pull nginx #拉取最新版本的nginx镜像
查看所有镜像
docker images
导出镜像
docker save --help #查看该命令的作用和相关的参数,以后的所有命令都可以这样去查,即使忘记了自己查一遍就好了
如上图我们可以看到它的命令格式为 docker save [OPTIONS] IMAGE [IMAGE...],该命令用于将一个或多个镜像保存为一个tar文件,然后他只有一个options选项-o,用于指定输出文件,所以我们导出镜像的命令即为
docker save -o nginx.tar nginx:latest
删除镜像
docker rmi nginx:latest #可以用镜像名(Repository:tag) docker rmi 605c77e624dd #也可以用镜像ID(ImageID)
加载镜像
docker load -i nginx.tar #-i代表指定一个输入文件,-q代表组织输出日志信息
3.2 容器的相关命令
docker run --name mynginx --net=host -p 80:80 -d nginx
-
docker run :创建并运行一个容器
-
--name:给容器起一个名字,如上mynginx
-
--net=host:不指定host,默认使用bridge桥接模式进行网络连接(而我们使用的是nat模式),造成无法访问虚拟器服务
-
-p:将宿主机端口与容器端口映射(冒号左侧为宿主机端口),如上任何对宿主机80端口的请求都会被转发到容器的80端口(用户无法直接访问容器,因为容器是对外隔离的)
-
-d:后台运行容器,即关掉控制台页面程序仍然会在后台运行
-
nginx:镜像名称,容器是基于nginx镜像创建的
docker ps #查看所有运行中的容器状态 docker ps -a #查看所有容器状态(包含未启动的容器)
测试nginx是否启动
docker logs -f mynginx #查看指定容器的日志,-f代表跟踪日志进度显示
进入容器
docker exec -it mynginx bash
-
docker exec:声明需要进入容器内部然后执行bash命令
-
-it:给进入的容器创建一个标准输入输出终端,从而允许通过命令与容器进行交互
-
mynginx:指定的容器
-
bash:进入容器后执行的命令(windows的shell就是cmd.exe,而Linux的shell就是bash.exe,我们常用的cd、ls、pwd等都是bash命令)
进入到容器内后,可以看到容器目录结构与Linux系统有类似的目录结构,但仅包含容器所需的文件,整体容量很小
如何知道组件放在了容器的哪个目录下?(以nginx为例)
此时我们需要去到Docker Hub下搜索nginx,找到官方版nginx镜像,如下图我们就找到了nginx的静态文件保存位置为/usr/share/nginx/html
我们在进入容器后很容易发现无法执行vi或vim命令,这是因为nginx容器逻辑上不需要安装这些命令,所以默认是没有安装的,如果需要修改文件内容,可使用sed命令做替换(但这种在容器内修改是不推荐的,一方面很麻烦,另一方面没有任何记录会造成维护麻烦),例如
sed -i 's#Welcome to nginx#456#g' index.html #替换index.html文件中的Welcome to nginx文本为456
退出容器
exit
停止容器
docker stop mynginx #停止mynginx容器的运行
重新启动容器
docker start mynginx
删除容器
docker rm mynginx #容器需要先停止才可以删除 docker rm -f mynginx #强制删除容器(即容器运行中也可以删除)
3.3 数据卷挂载
Docker存在的不便或问题:
-
不便于修改(需要进入容器内部修改很不方便)
-
数据不可复用(修改新创建的容器是不可复用的,再新创建的容器需要再次执行修改操作)
-
升级维护困难(数据在容器中,如果要升级组件版本必须删除旧容器)
针对以上的问题,开发者提出了数据卷的概念(类似windows下快捷方式)
数据卷:一个虚拟目录,它存在于宿主机文件系统中的某个目录。
数据卷的作用:借助数据卷作为桥梁可以实现以上问题的解决,如我更新volumes/html下的信息会同步到与之关联的容器内同目录(问题一);修改容器一的nginx/html信息会同步到volumes/html,进而同步到容器二同目录(问题二);如因为版本更新需要删除容器一,但volumns/html中的信息不会删除,当创建好新版本的容器后再将信息同步到新容器中即可(问题三)
操作数据卷
docker volume [COMMAND]
COMMAND(可通过docker volume --help查询):
-
create:创建一个volume
-
inspect:显示一个或多个volume的详细信息
-
ls:列出所有的volume
-
prune:删除未使用的volume
-
rm:删除指定的一个或多个volume
创建一个名为html数据卷
docker volume create html
查看html数据卷的详细信息,如下图,可看到挂载点位置
docker volume inspect html
删除未使用数据卷,如下图,我们看到删了俩条,这是因为目前只有一个容器处于启动中,另外俩个数据卷明显没有被使用
docker volume prune
删除指定数据卷
docker volume rm html
数据卷挂载
docker run --name mynginx --net=host -p 80:80 -v html:/usr/share/nginx/html -d nginx
-
-v [数据卷名称]:[容器内目录]:把html数据卷挂载到容器内的/usr/share/nginx/html目录
-
“/usr/share/nginx/html”可以在hub.docker.com中搜索nginx找到
/var/lib/docker/volumes/html/_data目录和/usr/share/nginx/html目录文件内容相同,说明挂载成功
此时我们再去修改html下文件就很方便了,可以使用vim修改,也可以使用finalShell用文本编辑器打开修改
修改成功
在启动容器时,如果宿主机没有创建相应的数据卷,docker自动创建对应数据卷
3.4 目录和文件挂载:
-
-v [宿主机目录]:[容器内目录]
-
-v [宿主机文件]:[容器内文件]
拉取mysql镜像或导入mysql.tar文件并生成镜像(导入tar会快很多)
docker load -i mysql.tar
创建mysql挂载目录(我这里是在/tmp目录下执行的)
mkdir -p mysql/data #-p代表文件是有嵌套的 mkdir -p mysql/conf
将提前准备好的mysql配置文件放在conf文件夹下
启动mysql容器
docker run \ --name mysql \ --net=host \ -e MYSQL_ROOT_PASSWORD=123456 \ -p 3306:3306 \ -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \ -v /tmp/mysql/data:/var/lib/mysql \ -d \ mysql:5.7.25
-
-e 代表设置root用户登录密码的环境变量
-
/etc/mysql/conf.d/hmy.cnf和/var/lib/mysql均是在hub.docker.com搜索mysql帮助文档找到的,前者是文件挂载用于保存配置文件后者是目录挂载用于保存数据库数据文件
3.5 俩种挂载方式的对比
-
数据卷挂载耦合度低,由docker管理目录,但是目录较深不好找
-
文件/目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找和查看
3.6 自定义镜像
镜像:是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成
镜像的结构:
-
镜像是分层结构,每一层称为一个Layer(分层的目的是为了便于版本的更新,对于不变的层选择保留,对于变化的层选择重建)
-
BaseImage层:基础镜像层,包含基本的系统函数库、环境变量、文件系统
-
EntryPoint层:入口层,是镜像中应用启动的脚本和参数
-
其他层:在基础镜像层上添加依赖、安装程序、配置
DockerFile:包含一个个指令的文本文件,这些指令用于说明要执行怎样的操作来创建镜像,每一条指令都会形成一层Layer,以下为常用指令
-
EXPOSE:和docker run -p 区分开,这里指的是容器内使用端口号的指定,如这里指定了8080,则-p冒号后面的端口必须写8080
-
其他指令:Dockerfile reference | Docker Documentation(需要使用VPN)
创建文件保存目录
mkdir -p /tmp/docker-demo
导入Dockerfile文件、自定义项目jar包文件(test.jar)、java8文件包(java8_331.tar.gz)
Dockerfile文件(注意几个需要根据自己的文件名修改的地方,如gz包名、gz解压后jdk文件夹名、jar包名等):
# 指定基础镜像 FROM ubuntu:16.04 # 配置环境变量,指定JDK的安装目录 ENV JAVA_DIR=/usr/local # 拷贝jdk包 COPY ./java8_331.tar.gz $JAVA_DIR/ # 安装JDK RUN cd $JAVA_DIR \ && tar -xf ./java8_331.tar.gz \ && mv ./jdk1.8.0_331 ./java8 # 配置环境变量 ENV JAVA_HOME=$JAVA_DIR/java8 ENV PATH=$PATH:$JAVA_HOME/bin #拷贝java项目的包 COPY ./test.jar /tmp/app.jar # 暴露端口 EXPOSE 8090 # 入口,java项目的启动命令 ENTRYPOINT java -jar /tmp/app.jar
自定义jar包:
java8文件包(下载地址:Java Archive Downloads - Java SE 8u211 and later):
编辑
构建镜像
docker build -t javaweb:1.0 .
-
-t:指定镜像名称(repository:tag)
-
. :表示Dockerfile文件在当前目录下
可以看到执行时,首先先拉取ubuntu最新的镜像,然后逐行执行命令,最后生成我们自定义镜像
启动自定义容器
docker run --name myjavaweb --net=host -p 8090:8090 -d javaweb:1.0
其实java文件包的拷贝对于所有java应用来说都是公共需要的,所以为了简化Dockerfile指令,我们可以直接以java:8-alpine镜像为基础镜像(它本身已经拥有了java依赖),此时我们的Dockerfile内容为:
# 指定基础镜像 FROM java:8-alpine #拷贝java项目的包 COPY ./test.jar /tmp/app.jar # 暴露端口 EXPOSE 8090 # 入口,java项目的启动命令 ENTRYPOINT java -jar /tmp/app.jar
我们修改Dockerfile文件后,再次构建镜像
docker build -t javaweb:2.0 .
4.DockerCompose
-
DockerCompose可以基于Compose文件帮助我们快速的部署微服务集群(分布式应用),而无需手动一个个创建和运行容器
-
Compose文件是一个文本文件,它可以通过指令定义集群中的每个容器如何运行
DockerCompose与docker run的对比:
docker run \ --name mysql \ --net=host \ -e MYSQL_ROOT_PASSWORD=123456 \ -p 3306:3306 \ -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \ -v /tmp/mysql/data:/var/lib/mysql \ -d \ mysql:5.7.25 docker build -t web:1.0 docker run --name web -p 8090:8090 -d web:1.0
更多DockerCompose语法:Compose specification | Docker Documentation
DockerCompose的安装:
-
使用命令下载安装:curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-
uname -s
-uname -m
-o /usr/local/bin/docker-compose(但有可能下载速度慢或连接不上) -
使用本地安装包安装:
首先去官网下载docker-compose文件:https://github.com/docker/compose/releases?page=7(选择1.23.1版本,在Assets目录下选择docker-compose-Linux-x86_64下载即可
然后修改文件名为docker-compose,将该文件上传到虚拟机的/usr/local/bin/目录下并修改文件权限:chmod +x /usr/local/bin/docker-compose(即添加可执行权限),此时Dockercompose安装完成
-
配置DockerCompose自动补全命令:
curl -L https://raw.githubusercontent.com/docker/compose/1.23.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
(如果无法下载则需先执行echo "199.232.68.133 raw.githubusercontent.com" >> /etc/hosts,即添加域名解析到hosts)
集群部署实战:
docker-compose.yml文件内容:
version: "3.2" services: nacos: image: nacos/nacos-server environment: MODE: standalone #单机运行模式(非nacos集群) ports: - "8848:8848" mysql: image: mysql:5.7.25 environment: MYSQL_ROOT_PASSWORD: 123 volumes: - "$PWD/mysql/data:/var/lib/mysql" #$pwd表示当前目录地址(即本dokcer-compose.yml文件的当前目录) - "$PWD/mysql/conf:/etc/mysql/conf.d/" userservice: build: ./user-service #基于Dockerfile构建镜像,Dockerfile位于当前目录的user-service文件夹下 orderservice: build: ./order-service #基于Dockerfile构建镜像,Dockerfile位于当前目录的order-service文件夹下 gateway: build: ./gateway #基于Dockerfile构建镜像,Dockerfile位于当前目录的gateway文件夹下 ports: - "10010:10010" #只有网关需要对外暴露端口,其他服务都不需要,因为网关是唯一入口
用DockerCompose部署时,所有的服务间都可以用服务名互相访问
user-service下的bootstrap.yml,将服务地址中的localhost改为nacos,端口改为8848
spring: application: name: userservice profiles: active: dev # 环境 cloud: nacos: server-addr: nacos:8848 # nacos地址 config: file-extension: yaml # 文件后缀名
user-service下的application.yml,将mysql连接信息的url中的localhost改为mysql
server: port: 8081 spring: datasource: url: jdbc:mysql://mysql:3306/cloud_user?useSSL=false username: root password: 123 driver-class-name: com.mysql.jdbc.Driver shardingsphere: sharding: default-database-strategy: tables: # discovery: # cluster-name: HZ mybatis: type-aliases-package: cn.itcast.user.pojo configuration: map-underscore-to-camel-case: true logging: level: cn.itcast: debug pattern: dateformat: MM-dd HH:mm:ss:SSS #eureka: # client: # service-url: # eureka的地址信息 # defaultZone: http://127.0.0.1:10086/eureka pattern: name: 本地环境local
同理order-service下的application,将服务地址中的localhost改为nacos,端口改为8848,将mysql连接信息的url中的localhost改为mysql
server: port: 8088 spring: datasource: url: jdbc:mysql://mysql:3306/cloud_order?useSSL=false username: root password: 123 driver-class-name: com.mysql.jdbc.Driver application: name: orderservice cloud: nacos: server-addr: nacos:8848 # nacos服务地址 # discovery: # namespace: 4d6ce343-9e1b-44df-a90f-2cf2b6b3d177 # dev环境 # ephemeral: false # 是否是临时实例 mybatis: type-aliases-package: cn.itcast.user.pojo configuration: map-underscore-to-camel-case: true logging: level: cn.itcast: debug pattern: dateformat: MM-dd HH:mm:ss:SSS #eureka: # client: # service-url: # eureka的地址信息 # defaultZone: http://127.0.0.1:10086/eureka userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 ribbon: eager-load: enabled: true # 开启饥饿加载 clients: # 指定饥饿加载的服务名称 - userservice feign: httpclient: enabled: true # 支持HttpClient的开关 max-connections: 200 # 最大连接数 max-connections-per-route: 50 # 单个路径的最大连接数
将gateway中的application.yml,将服务地址中的localhost改为nacos,端口改为8848
server: port: 10010 logging: level: cn.itcast: debug pattern: dateformat: MM-dd HH:mm:ss:SSS spring: application: name: gateway cloud: nacos: server-addr: nacos:8848 # nacos地址 gateway: routes: - id: user-service # 路由标示,必须唯一 uri: lb://userservice # 路由的目标地址 predicates: # 路由断言,判断请求是否符合规则 - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合 - id: order-service uri: lb://orderservice predicates: - Path=/order/** default-filters: - AddRequestHeader=Truth,Itcast is freaking awesome!
Dockerfile内容(Dockerfile文件分别在user-service、order-service、gateway三个文件夹下,内容相同)
FROM java:8-alpine COPY ./app.jar /tmp/app.jar ENTRYPOINT java -jar /tmp/app.jar
将三个服务分别打包成app.jar(与Dockerfile中命令保持一致),在这之前需要在每个服务的pom文件中引入如下配置,然后clean->package打包,然后把app.jar分别放到对应文件夹内
<!--用于文件打包--> <build> <finalName>app</finalName> <!--指定打包后的文件名称--> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
为了方便起见,将之前的mysql文件夹放在cloud-demo下
最终文件目录结构应为
测试:
查看docker-compose命令提示
docker-compose --help
-
up:创建并启动容器
-
stop:停止运行容器
-
down:停止并删除容器
-
restart:重启容器
-
logs:查看运行日志
创建并启动所有之前配置好的容器
cd /tmp/cloud-demo #进入到docker-compose.yml文件所在目录 docker-compose up -d #执行命令
5.搭建私有Docker仓库
1.简化版(是一个基础版本的Docker镜像仓库,具备仓库管理的完整功能,但是没有图形化界面)
docker run -d \ --restart=always \ --name registry \ -p 5000:5000 \ -v registry-data:/var/lib/registry \ registry
2.带图形化界面版(使用DockerCompose构建)
配置Docker信任地址(私服采用http协议,默认不能被Docker信任,所以需要做配置)
# 打开要修改的文件 vi /etc/docker/daemon.json # 添加内容(与上一行内容用逗号分隔,ip为虚拟机的ip地址): "insecure-registries":["http://192.168.91.124:8080"] # 重加载 systemctl daemon-reload # 重启docker systemctl restart docker
然后新建文件夹registry-ui,然后再新建一个docker-compose.yml文件,添加以下内容到文件
mkdir registry-ui touch docker-compose.yml
version: '3.0' services: registry: image: registry volumes: - ./registry-data:/var/lib/registry ui: image: joxit/docker-registry-ui:static ports: - 8080:80 environment: - REGISTRY_TITLE=我的私有仓库 #私有仓库标题 - REGISTRY_URL=http://registry:5000 #UI访问registry容器时使用的地址 depends_on: - registry #依赖于registry镜像
执行命令
docker-compose up -d
访问ui界面
http://192.168.91.124:8080
上传镜像到私有仓库
docker tag nginx:latest 192.168.91.124:8080/nginx:1.0 #对本地镜像重建tag,即建一个名字为192.168.91.124:8080/nginx的镜像,与原镜像ID一致(所以其实是同一个镜像只是重命名而已) docker push http://192.168.91.124:8080/nginx:1.0
从私有仓库拉取镜像
docker pull 192.168.91.124:8080/nginx:1.0 #拉取时需要先将之前的容器和镜像全部卸载
ELK —— 分布式搜索和日志分析
1.基础知识
1.1 ElasticStack(ELK):即ElasticSearch+Logstash/Beats+Kibana,被广泛用于微服务系统的日志数据分析、实时监控等领域。
-
ElasticSearch:是ELK的核心, 简称ES,负责数据的存储、搜索和分析,其底层是建立在全文搜索引擎 Apache Lucene (使用 Java 语言编写)基础上的,区别于mysql的正向索引,ES基于倒排索引,查询性能更好。ElasticSearchS可用于海量数据的保存和搜索
-
Logstash/Beats:用于数据抓取
-
Kabana:用于数据可视化展示
1.2 两种不同的查询思维
正向索引(用id找词):一个id对一条数据,每条数据包含多项信息
倒排索引(用词找id):将一条数据拆分为多个词,一个词对应多个文档id
综上所述,当我们在进行搜索操作时,基于正向索引的模糊查询时,需要逐条查询;而基于倒排索引查询时效率会好很多
具体查找过程:
备注:关联文档是1、2、3,但明显2号文档多次出现具有更高的关联性,因此会放在结果集靠前的位置;
1.3 ES的数据存储
1.4 mysql与ES的使用场景
-
mysql擅长事务类型操作,可以确保数据的安全性和一致性
-
ES擅长海量数据的搜索、分析和计算
备注:如图,使用Mysql进行写操作保证数据安全一致性,ES进行读操作保证查询的高性能,然后两者进行数据的同步即可
2.ELK安装
2.1 安装ElasticSearch
下载:Index of elasticsearch-local/7.8.0
运行:解压压缩包后进入bin目录双击执行elasticsearch.bat即可
2.2 安装Kibana
下载:Index of kibana-local/7.8.0
运行:解压压缩包后进入bin目录双击执行kinaba.bat即可
2.3 安装Logstash
下载:Index of logstash-local/7.8.0
配置:在bin目录下新建logstash.conf配置文件,内容如下
input { //配置输入信息 tcp { mode => "server" host => "0.0.0.0" port => 4560 codec => json_lines } } output { //配置输出信息 elasticsearch { hosts => "localhost:9200" //ES地址 index => "springboot-logstash-%{+YYYY.MM.dd}" //索引库名称 } }
运行:在bin目录下,鼠标右键打开powershell,然后执行 ./logstash.bat -f logstash.conf 即可
2.4 DevTools的使用
-
在Kibana界面提供了DevTools工具,它可以编写DSL命令来操作ES(发送一个RestFul的Http请求到ES)
2.5 分词器
ES在创建倒排索引时需要对文档进行分词,在搜索时需要对用户输入内容分词,但默认的ES分词规则对中文的处理不太好(例如:把“我的手机”分成“我”,“的”,“手”,“机”,而不是“我的”、“手机”),所以我们需要安装分词器,如IK分词器
安装IK分词器
下载:Releases · medcl/elasticsearch-analysis-ik · GitHub
安装:将压缩包解压到es的plugins目录下,然后重新启动ES即可
在重启后得日志中可以看到如下内容,说明添加插件成功
ik分词器的两种模式
-
ik_smart:最少切分(把“我的智能手机”分成“我“、”的”、“智能手机”,即只要是一个词就不再继续拆分)
-
ik_max_word:最细切分(把“我的智能手机”分成“我“、”的”、“智能手机”、“智能”、“能手”、“手机”)
词汇量扩展与无意义词停用
修改ik/config目录下的ikAnalyzer.cfg.xml文件,将新的词加入到ik/config/ext.dic(需要新建这个文件,每个词用回车符隔开)中
有些语气助词没有实际的意义例如“的”、“哦”等,所以我们可以停止匹配这些词,修改ik/config目录下的ikAnalyzer.cfg.xml文件,将希望停用的词加入到ik/config/stopwprd.dic(每个词用回车符隔开)中
新建ext.dic文件,然后添加扩展词
在stopword.dic中添加停用词
配置完成后,重启es
3.索引库操作
mapping常见属性
1.type(字段数据类型)
-
字符串:text(可分词的文本)、keyword(不可分词的文本,如国家名、商品名)
-
数值:long、integer、short、byte、double、float
-
布尔:boolean
-
日期:date
-
对象:object
{ "age":30, "weight":65.5, "isMarried":false, "info":"我是张三", "email":"zs@qq.com", "score":"[99.1,99.5,90.7]", "name":{ "firstName":"三", "lastName":"张" } }
2.index:是否创建索引,默认为true(选择true则创建倒排索引用于搜索,若选择false则不会创建倒排索引,不能用于搜索)
3.analyzer:使用哪种分词器
4.properties:字段的子字段,如上面的firstName和lastName
创建索引库
PUT /myindex #myindex是索引库名称 { "mappings":{ "properties":{ "info":{ "type":"text", "analyzer":"ik_smart" }, "email":{ "type":"keyword", "index":false }, "name":{ "type":"object", "properties":{ "firstName":{ "type":"keyword" }, "lastName":{ "type":"keyword" } } } } } }
查询索引库
GET /索引库名称
删除索引库
DELETE /索引库名称
修改索引库
#索引库一旦创建无法修改结构,智能添加新的字段 PUT /索引库名称/_mapping { "properties":{ "新字段名":{ #新字段名不能与已有字段名重复,否则会报错 "type":"integer" } } }
4.文档操作
新增文档(相当于在表中新增数据)
POST /索引库名/_doc/文档id { "字段1":"值1", "字段2":"值2", "字段3":{ "子属性1":"值3", "子属性2":"值4" } } POST /myindex/_doc/1 { "info": "张三", "email": "zs@qq.com", "name": { "firstName": "三", "lastName": "张" } }
查询文档
GET /索引库名/_doc/文档id
删除文档
DELETE /索引库名/_doc/文档id #每次写操作(每次插入或删除会增加_version值)
修改文档
1.全量修改(删除旧文档,添加新文档,即如果文档id存在未修改操作,如果文档id不存在未新增操作)
PUT /索引库名/_doc/文档id { "字段1":"值1", "字段2":"值2", }
2.局部修改
POST /索引库名/_update/文档id { "doc":{ "字段名":"新的值" } }
5.使用ELK收集系统日志
在项目中添加logstash依赖
<dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>5.3</version> </dependency>
添加配置信息
logging: config: classpath:logback-spring.xml
logback-spring.xml文件内容如下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <include resource="org/springframework/boot/logging/logback/console-appender.xml"/> <!--应用名称--> <property name="APP_NAME" value="order-service"/> <!--日志文件保存路径--> <property name="LOG_FILE_PATH" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/logs}"/> <contextName>${APP_NAME}</contextName> <!--每天记录日志到文件appender--> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_FILE_PATH}/${APP_NAME}-%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>${FILE_LOG_PATTERN}</pattern> </encoder> </appender> <!--输出到logstash的appender--> <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender"> <!--可以访问的logstash日志收集端口--> <destination>localhost:4560</destination> <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE"/> <appender-ref ref="LOGSTASH"/> </root> </configuration>
运行项目后,调用order接口,然后在StackManagement->Index patterns下创建索引模型
最后菜单栏中选择Discover菜单,可查看实时日志信息,并有统计图展示