【实用篇】SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,系统详解springcloud分布式
文章目录
- 一、服务拆分
-
- 1.1 服务拆分Demo
- 1.2 微服务远程调用
- 二、Eureka
-
- 2.1 Eureka原理
- 2.2 Eureka-server服务搭建
- 2.3 eureka-client服务注册
- 2.4 eureka-client服务复制
- 2.5 eureka服务发现
- 三、Ribbon负载均衡
-
- 3.1 负载均衡原理
- 3.2 负载均衡策略
- 3.3 自定义负载均衡策略
- 3.4 饥饿加载与懒加载
- 四、Nacos
-
- 4.1 Nacos安装教程
- 4.2 服务注册到nacos
- 4.3 服务分级存储模型
- 4.4 同集群优先的负载均衡
- 4.5 权重配置
- 4.6 环境隔离namespace
- 4.7 配置管理
- 4.8 配置热更新
- 4.9 多环境配置共享
- 4.10 nacos集群搭建
- 五、Feign远程调用
-
- 5.1 Feign替代RestTemplate
- 5.2 Feign自定义配置
- 5.3 Feign使用优化
- 六、Gateway统一网关
-
- 6.1 为什么使用Gateway统一网关
- 6.2 搭建Gateway服务
- 6.3 断言工厂
- 6.4 过滤器工厂(路由过滤器、默认过滤器)
- 6.5 全局过滤器(GlobalFilter)
- 6.6 过滤器执行顺序
- 6.7 跨域问题
- 七、Docker容器
-
- 7.1 认识Docker容器
- 7.2 Docker的架构
- 7.3 Centos7安装Docker
- 7.4 启动Docker
- 7.5 配置Docker镜像
- 7.6 镜像基本操作
- 7.7 容器基本操作
- 7.8 进入容器
- 7.9 Docker的数据卷
- 7.10 Dockerfile自定义镜像
- 7.11 构建Java项目
- 7.12 DockerCompose下载安装
- 7.13 DockerCompose部署微服务集群
- 八、RabbitMQ消息队列
-
- 8.1 同步通讯与异步通讯
- 8.2 MQ技术对比
- 8.3 RabbitMQ下载和安装
- 8.4 RabbitMQ组件的介绍
- 8.5 RabbitMQ消息模型
- 8.6 简单队列模型
- 九、SpringAMQP
-
- 9.1 Basic Queue简单队列模型
- 9.2 WorkQueue消息队列
- 其他比较复杂的MQ模型,在工作遇到时再学习
学习资料百度网盘: https://pan.baidu.com/s/1LxIxcHDO7SYB96SE-GZfuQ
密码:dor4
一、服务拆分
1.1 服务拆分Demo
两个服务放在同一个项目里面,然后分别启动,从而实现了服务拆分
1.2 微服务远程调用
1. 在配置类中注册RestTemplate
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication{public static void main(String[] args){SpringApplication.run(OrderApplication.class, args);}
}
2. 在OrderService中远程调用UserService服务
@Service
public class OrderService{@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;public OrderqueryOrderById(Long orderId){// 1. 查询订单Order order = orderMapper.findById(orderId);// 2. url路径String url = "http://localhost:8081/user/" + order.getUserId();// 3. 利用RestTemplate发起http请求,查询用户User user = restTemplate.getForObject(url, User.class);// 4. 封装User到Orderorder.setUser(user);// 5. 返回orderreturn order;}
}
二、Eureka
2.1 Eureka原理
2.2 Eureka-server服务搭建
1. 在cloud-demo父工程下,创建一个子模块eureka-server
2. 引入eureka-server依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3. 编写启动类
@SpringBootApplication
@EnableEurekaServer //该启动类设置为eureka-server服务
public class EurekaApplication {public static void main(String[] args) {SpringApplication.run(EurekaApplication.class, args);}
}
4. 编写配置文件application.yml
# eureka服务信息配置
server:port: 10086
spring:application:name: eureka-server# 注册到eureka中
eureka:client:service-url: #eureka的地址信息defaultZone: http://127.0.0.1:10086/eureka
5. 启动微服务,然后在浏览器访问:http://127.0.0.1:10086
2.3 eureka-client服务注册
1. 在user-service和order-service服务模块中,引入eureka-client依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2. 修改文件application.yml
# 设置服务信息
spring:application:name: user-service# 将服务注册到http://127.0.0.1:10086/eureka服务中
eureka:client:service-url:defaultZone: http://127.0.0.1:10086/eureka
3. 启动eureka-server服务及user-service服务,然后在浏览器访问:http://127.0.0.1:10086
启动eureka-server服务及user-service服务,我们发现当前已在Eureka中注册的实例栏中有了user-service的信息。
2.4 eureka-client服务复制
复制之后并不是又多了一个user-service模块,而是只是多了一个UserApplication启动服务
可以看到eureka-server管理页面数值变化
2.5 eureka服务发现
1. 修改orde-service代码用服务名称代替ip端口
我们要去http://127.0.0.1:10086/eureka
中拉取user-service
服务的实例列表,并通过负载均衡找到其中一个实例。不过这些动作不用我们去做,只需要添加一些注解@LoadBalance
即可。
2. 在order-service的OrderApplication中,给RestTemplate这个Bean添加一个@LoadBalanced注解
3. 重启order-service服务,再次访问http://localhost:8080/order/101
发现user-service服务的两个实例都打印了sql信息,并且选择其中一个发送请求。所以我们发现spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。
三、Ribbon负载均衡
上述操作已经完成了负载均衡了,这里就说说关于负载均衡的SpringCloud底层完成负载均衡的Ribbon组件。
3.1 负载均衡原理
SpringCloudRibbon的底层采用了一个拦截器,拦截了RestTemplate发出的请求,对地址做了修改
3.2 负载均衡策略
负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类
默认的实现就是ZoneAvoidanceRule,是一种轮询方案.
3.3 自定义负载均衡策略
配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则(针对某个服务的负载均衡策略)
user-service: # 给某个微服务配置负载均衡规则,这里是user-service服务ribbon:NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
3.4 饥饿加载与懒加载
Ribbon采用懒加载:第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
Ribbon采用饥饿加载:会在项目启动时创建LoadBalanceClient,降低第一次访问的耗时。
文件位置:例如order-service要访问user-service服务,那就在order-service的application.yml中配置user-service服务
# 配置单个clients
ribbon:eager-load:enabled: true # 开启饥饿加载clients: user-service # 开启饥饿加载的服务名称
ribbon:eager-load:enabled: true clients: - user-service # 配置多个饥饿加载服务- other-service
四、Nacos
4.1 Nacos安装教程
- 进入GitHub主页:https://github.com/alibaba/nacos
- 下拉点击Release Version的全部发行版
- 下载自己想要的版本
.zip是windows版本
.tar.gz是Linux版本
- 解压nacos-service,并且进入bin目录
- 启动startup.cmd,但是不要双击startup.cmd文件启动nacos服务,因为默认是集群模式,使用命令进行单体启动.
startup.cmd -m standalone
但是我的电脑需要输入这样的命令
.\\ startup.cmd -m standalone
- 在浏览器输入地址:http://127.0.0.1:8848/nacos即可访问,默认账号密码都是nacos:
4.2 服务注册到nacos
因为项目是在eureka的基础上创建的,所以引入nacos的时候,需要删除以前的eureka一些配置,直接看下面示例就行了。
- 在cloud-demo父工程的pom文件中的
<dependencyManagement>
中引入SpringCloudAlibaba的管理依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>2.2.6.RELEASE</version><type>pom</type><scope>import</scope>
</dependency>
- 如果依赖中有如下的eureka依赖,那就删除或注释掉。
<!--eureka客户端依赖--><!--<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>-->
- 在
user-service
和order-service
中的pom文件中引入nacos-discovery依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- 配置nacos地址:在user-service和order-service的application.yml中添加nacos地址,不要忘了注释掉eureka地址
spring:cloud:nacos:server-addr: localhost:8848
- 重启微服务后,登录nacos管理页面,可以看到微服务信息
4.3 服务分级存储模型
一个服务可以有多个实例,例如我们的user-service,可以有
127.0.0.1:8081
127.0.0.1:8082
127.0.0.1:8083
假如这些实例分布于全国各地的不同机房
127.0.0.1:8081(在上海机房)
127.0.0.1:8082(在上海机房)
127.0.0.1:8083(在杭州机房)
也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成Nacos服务分级存储模型
给user-service配置集群
- 把UserApplication和UserApplication2添加到杭州集群,先找到user-service模块下的application.yml,集群配置如下:
spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ # 集群名称-杭州
-
启动UserApplication和UserApplication2实例,我们可以在nacos控制台看到下面结果
-
把UserApplication3添加到杭州集群,先找到user-service模块下的application.yml,集群配置如下:
spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: SH # 集群名称-上海
- 启动UserApplication3实例,我们可以在nacos控制台看到下面结果
4.4 同集群优先的负载均衡
默认的ZoneAvoidanceRule
并不能实现根据同集群优先来实现负载均衡。因此Nacos中提供了一个NacosRule的实现,可以优先从同集群中挑选实例。
给order-service配置集群信息,修改order-service的application.yml文件,添加集群配置:
spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ # 集群名称
修改order-service的application.yml文件,修改负载均衡规则为优先选择同集群
user-service:ribbon:NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
4.5 权重配置
实际部署中会出现这样的场景
- 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。
- 但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。
- 因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重
注意:如果权重修改为0,则该实例永远不会被访问
4.6 环境隔离namespace
不同namespace之间相互隔离,例如不同namespace的服务互相不可见
1. 默认情况下所有的服务都在一个名为public的namespace
2. 我们可以点击页面新增按钮,添加一个namespace
3. 然后填写表单,命名空间ID不填,让它自动生成
4. 就能在页面看到一个新的namespace
5. 给微服务配置namespace
spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZnamespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填nacos上面生成的ID
6. 重启order-service后,访问控制台,可以看到下面的结果
7. 此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错
4.7 配置管理
1. 为什么需要nacos配置管理
当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。
其实也不是说服务的所有配置都交给nacos管理,只是一些经常改动的配置需要nacos统一管理。例如数据库的驱动器、地址、账号、密码就不是经常改动的,所以就不用放在nacos统一管理了。
2. 怎样在nacos添加配置管理
在nacos首页找到配置列表,并且点击添加
然后在弹出的表单中,填写配置信息,然后发布
3. 在服务模块user-service中,引入nacos-config依赖
<!--nacos配置管理依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
4. 配置bootstrap.yaml文件
在user-service中\\src\\main\\resources添加一个bootstrap.yaml文件
spring:application:name: user-service # 服务名称profiles:active: dev #开发环境,这里是dev cloud:nacos:server-addr: localhost:8848 # Nacos地址config:file-extension: yaml # 文件后缀名
5. 测试是否能通过bootstrap.yaml拉取到配置
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Value("${pattern.dateformat}") //通过value读取配置中的pattern.dateformatprivate String dateformat;@GetMapping("now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));}
}
在页面访问http://localhost:8081/user/now,可以看到效果
4.8 配置热更新
我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是配置热更新。
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
在user-service的Controller层添加@RefreshScope
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {@Autowiredprivate UserService userService;@Value("${pattern.dateformat}") //通过value读取配置中的pattern.dateformatprivate String dateformat;@GetMapping("now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));}
}
方式二:使用@ConfigurationProperties注解代替@Value注解
在user-service服务中,添加一个类,读取patterrn.dateformat属性
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {private String dateformat;
}
在UserController中使用这个类代替@Value
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate PatternProperties patternProperties;@GetMapping("now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));}
}
查看效果
启动UserApplication启动类,这时候再去改动nacos统一管理配置,就会热配置到User Application中,而不需要重新启动UserApplication。
项目启动:
第一次的配置文件如下
第一次访问形式如下
项目依然保持运行状态,不能重新启动
第二次的配置文件如下
第二次访问形式如下
4.9 多环境配置共享
1. 我们重新来看一下bootstrap.yaml配置文件
spring:application:name: user-service # 服务名称profiles:active: dev #开发环境dev cloud:nacos:server-addr: localhost:8848 # Nacos地址config:file-extension: yaml # 文件后缀名
2. 多环境配置共享规则
我们知道user-service服务需要经过dev(开发)、test(测试)、release(发布)等过程,我们当然知道不同的过程就会有不同的环境配置,但是这些不同的环境配置中也有一些相同的参数值,可不可以把这些相同的参数值放到一个地方,尽量一改动,这个服务的环境全部改动呢?
其实上述bootstrap.yaml文件是加载了user-service.yaml配置文件和user-service-dev.yaml配置文件。是的,无论你是加载user-service-dev.yaml、还是user-service-test.yaml,配置文件是默认会加载的。那么user-service.yaml就成为了多环境共享配置文件了。
那么这个user-service.yaml配置文件在nacos的配置就跟其他配置文件一样了,具体操作可在上面找到。
3. 当nacos、服务本地同时出现相同属性时,优先级有高低之分
4.10 nacos集群搭建
1. 配置Nacos
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf,然后添加如下内容。
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
2. 修改nacos\\conf\\application.properties文件,添加数据库配置
spring.datasource.platform=mysqldb.num=1db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123
3. 将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
4. 分别修改三个文件夹中的application.properties
nacos1
server.port=8845
nacos2
server.port=8846
nacos1=3
server.port=8847
5. 分别启动三个nacos节点
这一次直接双击nacos\\bin\\startup.cmd就可以启动了。
注意:后面的启动报错,有可能是内存不足
6. 为三个naocs节点配置nginx反向代理
找到nginx/conf/nginx.conf文件,配置如下
upstream nacos-cluster { # nginx就在这三个nacos进行负载均衡server 127.0.0.1:8845;server 127.0.0.1:8846;server 127.0.0.1:8847;
}server {listen 80;server_name localhost;location /nacos { #访问http://localhost/nacosproxy_pass http://nacos-cluster; #就会跳到下面http://localhost/nacos-cluster,即上面的三个地址}
}
7. 启动nginx服务器
8. 输入localhost/nacos,访问浏览器
五、Feign远程调用
5.1 Feign替代RestTemplate
1. 引入依赖
我们在order-service服务的pom文件中引入feign的依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 添加注解
在order-service的启动类添加注解开启Feign的功能
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients
public class OrderApplication {// ...
}
3. 编写Feign客户端
在order-service中新建一个接口,内容如下:
并且在@FeignClient会对user-service模块的实例进行Ribbon负载均衡
@FeignClient("user-service") //远程调用user-service服务模块下的实例
public interface UserClient {@GetMapping("/user/{id}") //匹配到user-service模块下的/user/{id}对应的方法User findById(@PathVariable("id") Long id); //所以这里findById方法对应user-service的queryById方法
}
4. 远程调用
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate UserClient userClient;public Order queryOrderById(Long orderId) {// 1.查询订单Order order = orderMapper.findById(orderId);// 2.用Feign远程调用User user = userClient.findById(order.getUserId());// 3.封装user到Orderorder.setUser(user);// 4.返回return order;}
}
5. 测试
5.2 Feign自定义配置
1. Feign可以支持很多的自定义配置
一般情况下,默认值就能满足我们使用,如果要自定义时,下有两种Feign自定义的方式。下面以日志为例来演示如何自定义配置。
2. 基于application.yml自定义Feign
# 可以针对单个服务
feign: client:config: userservice: # 针对某个微服务的配置loggerLevel: FULL # 日志级别NONE、BASIC、HEADERS、FULL # 也可以针对多个服务
feign: client:config: default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置loggerLevel: FULL # 日志级别
3. 基于Java代码方式自定义Feign
// 如果想要全局生效
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
public class DefaultFeignConfiguration {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; // 日志级别为BASIC}
}// 如果想要局部生效
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
public class DefaultFeignConfiguration {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; // 日志级别为BASIC}
}
5.3 Feign使用优化
1. 为什么Feign需要优化?
Feign底层发起http请求,依赖于其他框架,包括:
- URLConnection:不支持连接池;
- Apache HttpClient:支持连接池;
- OKHttp:支持连接池;
Feign底层默认使用URLConnection
2. 如何优化Feign?
使用支持连接池的Apache HttpClient或者OKHttp代替默认的URLConnection
3. 使用Apache HttpClient替代URLConnection
1. 在order-service的pom文件中引入Apache HttpClient依赖
<!--httpClient的依赖 -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>
2. 在order-service的application.yml中添加配置
# 这里包含了对日志级别的优化,一般选择BASIC或NONE
feign:client:config:default: # default全局的配置loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息# 这里包含了对底层连接的优化,使用Apache HttpClient来替代URLConnectionhttpclient:enabled: true # 开启feign对HttpClient的支持max-connections: 200 # 最大的连接数max-connections-per-route: 50 # 每个路径的最大连接数
3. 测试是否使用了Apache HttpClient
在FeignClientFactoryBean中的loadBalance方法中打断点
Debug方式启动order-service服务,可以看到这里的client,底层就是Apache HttpClient
六、Gateway统一网关
6.1 为什么使用Gateway统一网关
上面演示的过程中,我们通过访问的order-service服务模块获取数据,但是一般情况下不能直接访问。
因为有些微服务模块是不给外人访问的,所以我们需要用Gateway统一网关来限定外部访问。
6.2 搭建Gateway服务
1. 创建Gateway服务模块
2. 引入依赖
<!--网关-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3. 编写启动类
@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
4. 编写基础配置和路由规则
我们将 /user/开头的请求,代理到lb://userservice,lb是负载均衡,根据服务名拉取服务列表,实现负载均衡。
server:port: 10010 # 网关端口
spring:application:name: gateway # 服务名称cloud:nacos:server-addr: localhost:8848 # nacos地址gateway:routes: # 网关路由配置- id: user-service # 路由id,自定义,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址uri: lb://user-service # 路由的目标地址 lb就是负载均衡,后面跟服务名称predicates: # 路由断言,也就是判断请求是否符合路由规则的条件- Path=/user/ # 这个是按照路径匹配,只要以/user/开头就符合要求
5. 重启测试
访问http://localhost:10010/user/1时,符合/user/规则,请求转发到uri:http://userservice/user/1。nacos根据userservice服务名发现服务,给出服务列表,并根据负载均衡返回其中一个实例。
6. 总体流程
6.3 断言工厂
我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件,例如Path=/user/是按照路径匹配,这个规则是由PathRoutePredicateFactory
类来处理的,像这样的断言工厂在SpringCloudGateway还有十几个,我们只需要掌握Path这种路由工程就可以了。
6.4 过滤器工厂(路由过滤器、默认过滤器)
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
1. 添加AddRequestHeader过滤器
Spring提供了31种不同的路由过滤器工厂,下面我们以AddRequestHeader为例来讲解
只需要修改gateway服务的application.yml文件,即可添加过滤器
路由过滤器
# 当前过滤器卸载userservice路由下,因此仅仅对的访问userservice的请求有效
spring:cloud:gateway:routes:- id: user-service uri: lb://user-service predicates: - Path=/user/ filters: # 过滤器- AddRequestHeader=Truth, bcb is freaking awesome! # 添加请求头
默认过滤器
# 当前过滤器写在gateway下,则可以对所有路由都生效
spring:cloud:gateway:routes:- id: user-service uri: lb://user-service predicates: - Path=/user/default-filters: # 默认过滤项- AddRequestHeader=Truth, bcb is freaking awesome!
2. 测试:来到UserController修改queryById方法,代码如下
@GetMapping("/{id}")public User queryById(@PathVariable("id") Long id,@RequestHeader(value = "Truth", required = false) String truth) {log.info(truth);return userService.queryById(id);
}
重启网关服务以及user-service服务,访问http://localhost:10010/user/1,可以看到控制台打印出了对应的日志
6.5 全局过滤器(GlobalFilter)
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
1. 实现GlobalFilter接口
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求参数MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();// 2.获取authorization参数String auth = params.getFirst("authorization");// 3.校验if ("admin".equals(auth)) {// 放行return chain.filter(exchange);}// 4.拦截// 4.1.禁止访问,设置状态码exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);// 4.2.结束处理return exchange.getResponse().setComplete();}
}
6.6 过滤器执行顺序
1. 不同类型过滤器之间的优先级,如下
2. 同一类型过滤器的优先级
默认过滤器:按照在配置文件中的排序,order从1开始计算
路由过滤器:按照在配置文件中的排序,order从1开始计算
全局过滤器:在GlobalFilter实现类的类名加上注解的@Order()
6.7 跨域问题
1. 什么是跨域问题
浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
- 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com
- 域名相同,端口不同:localhost:8080和localhost8081
2. 跨域报错演示
3. 解决上述跨域报错问题
spring:cloud:gateway:# 。。。globalcors: # 全局的跨域处理add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题corsConfigurations:'[/]':allowedOrigins: # 允许哪些网站的跨域请求 - "http://localhost:8090"allowedMethods: # 允许的跨域ajax的请求方式- "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowedHeaders: "*" # 允许在请求中携带的头信息allowCredentials: true # 是否允许携带cookiemaxAge: 360000 # 这次跨域检测的有效期
七、Docker容器
7.1 认识Docker容器
1. 微服务部署问题及其解决办法。
问题1:依赖关系复杂,容易出现兼容性问题
- 将应用的Libs(函数库)、Deps(依赖)、配置、应用一起打包
问题2:操作系统环境差异问题
- 因为无论是Ubuntu还是CentOS环境,都是基于Linux内核,那就直接把Linux内核打包就好了
2. Docker与虚拟机的区别
7.2 Docker的架构
1. Docker重要组件
1. 注册器(Registry)
- 一个 Docker Registry 中可以包含多个仓库(Repository);
2. 仓库(Repository)
- 一个仓库会包含同一个软件不同版本的镜像,我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
3. 镜像(Image)
- 回忆一下虚拟机VM安装的时候是不是要导入centos镜像,这个centos镜像就是Image。其他的应用也有各自的镜像,例如MySQL等等。Image需要从Repository拉取到Docker容器中才能运行。
4. 容器(Container)
- 镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
5. Dockerfile
- Dockerfile 是一个用来构建镜像Image的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。
2. Docker采用C/S模式
7.3 Centos7安装Docker
1. 版本问题
Docker CE支持64位版本CentOS7,并且要求内核版本不低于3.10,CentOS7满足最低内核的要求,所以我们在CentOS 7安装Docker
2. 如果之前安装过旧版本的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
3. 安装yum工具
yum install -y yum-utils \\device-mapper-persistent-data \\ lvm2 --skip-broken
4. 更新本地镜像源
# 设置docker镜像源
yum-config-manager \\--add-repo \\https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposed -i 's/download.docker.com/mirrors.aliyun.com\\/docker-ce/g' /etc/yum.repos.d/docker-ce.repoyum makecache fast
5. 下载docker
yum install -y docker-ce
7.4 启动Docker
Docker应用需要用到各种端口,逐一去修改防火墙装置。非常麻烦,因此建议大家直接关闭防火墙
# 关闭
systemctl stop firewalld# 禁止开机启动防火墙
systemctl disable firewalld
启动docker
# 启动docker
systemctl start docker# 查看是否启动成功
systemctl status docker
7.5 配置Docker镜像
# 创建/etc/docker文件
sudo mkdir -p /etc/docker
# 向文件写入内容
sudo tee /etc/docker/daemon.json <<-'EOF'
{"registry-mirrors": ["https://n0dwemtq.mirror.aliyuncs.com"]
}
EOF
# 重新加载
sudo systemctl daemon-reload
# 重新启动
sudo systemctl restart docker
7.6 镜像基本操作
1. 拉取、查看镜像
首先去镜像仓库搜索nginx镜像,比如DockerHub:
根据查看到的镜像名称,拉取自己需要的镜像,通过命令:docker pull nginx
通过命令:docker images 查看拉取到的镜像
2. 保存、导入镜像
保存命令:docker save -o [保存的目标文件名称] [镜像名称]
# 把镜像nginx:latest保存到nginx.tar文件
docker save -o nginx.tar nginx:latest
导入命令:先删除本地的nginx镜像
docker rmi nginx:lateset
然后运行命令,加载本地文件
docker load -i nginx.tar
7.7 容器基本操作
docker run:创建并运行一个容器,处于运行状态
docker pause:让一个运行的容器暂停
docker unpause:让一个容器从暂停状态恢复运行
docker stop:停止一个运行的容器
docker start:让一个停止的容器再次运行
docker rm:删除一个容器
1. 创建并运行一个容器
docker run --name containerName -p 80:80 -d nginx
docker run :创建并运行一个容器
–name : 给容器起一个名字,比如叫做mn
-p :将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口
-d:后台运行容器
nginx:镜像名称,例如nginx
2. 创建nginx镜像
docker run --name mn -p 80:80 -d nginx
docker ps
3. 向nginx所在的服务区发起访问
访问http://192.168.133.128:80
7.8 进入容器
进入容器
在我看来就是进入了虚拟机终端里面的另一个终端,仔细品一下吧
# 我要进入mn容器中,并且能够进入输入输出,并且打开交互命令
docker exec -it mn bash
- docker exec:进入容器,执行一个命令
- -it:给当前进入的容器创建一个标准输入、输出终端、允许我们与容器交互
- mn:要进入的容器的名称
- bash:进入容器后执行的命令,bash是一个linux终端交互命令
7.9 Docker的数据卷
1. 原先的Docker是容器与数据耦合
在之前的nginx案例中,修改nginx的html页面时,需要进入nginx内部,并且因为没有编辑器,修改文件也很麻烦,这就是容器与数据耦合带来的后果。
- 不便于修改:当我们要修改Nginx的html内容时,需要进入容器内部修改,很不方便
- 数据不可复用:在容器内的修改对外是不可见的,所有修改对新创建的容器是不可复用的
- 升级维护困难:数据在容器内,如果要升级容器必然删除旧容器,所有数据都跟着删除了
要解决这个问题,必须将数据与容器解耦,这就是数据卷。
2. 数据卷原理
数据卷(volume)是一个虚拟目录,指向宿主机文件系统中的某个目录。
一旦完成数据卷挂载,对容器的一切操作都会作用在数据卷对应的宿主机目录了。
这样,我们操作宿主机的/var/lib/docker/volumes/html目录,就等于操作容器内的/usr/share/nginx/html目录了
3. 数据卷基本操作命令
# 创建数据卷
docker volume create html# 查看所有数据
docker volume ls# 查看数据卷详细信息卷
docker volume inspect html
4. 指定数据卷挂载的目录
docker run \\--name mn \\-v html:/root/html \\-p 8080:80nginx \\
7.10 Dockerfile自定义镜像
1. 创建Dockerfile文件
在/docker目录
下,新建一个Dockerfile 文件
,并在文件内添加以下内容:
`FROM` 定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。
`RUN` 用于执行后面跟着的命令行命令(等同于,在终端操作的 shell 命令)
2. 开始构建镜像
在 Dockerfile 文件的存放目录/docker下,执行docker build -t nginx:v3 .
命令。
nginx:v3
:镜像名称:镜像标签
.
:代表本次执行的上下文路径
7.11 构建Java项目
需求:搭建一个JavaWeb镜像,然后在上面运行Java代码
步骤1:新建一个空文件夹docker-demo
步骤2:拷贝课前资料中的docker-demo.jar文件到docker-demo这个目录
步骤3:拷贝课前资料中的jdk8.tar.gz文件到docker-demo这个目录
步骤4:拷贝课前资料提供的Dockerfile到docker-demo这个目录
Dockerfile内容如下
# 指定基础镜像
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar# 安装JDK
RUN cd $JAVA_DIR \\&& tar -xf ./jdk8.tar.gz \\&& mv ./jdk1.8.0_144 ./java8# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin# 暴露端口
EXPOSE 8090
# 入口,java项目的启动命令
ENTRYPOINT java -jar /tmp/app.jar
步骤5:将准备好的docker-demo上传到虚拟机任意目录,然后进入docker-demo目录下
步骤6:运行命令docker build -t javaweb:1.0 .
步骤7:运行镜像docker run --name web -p 8090:8090 -d javaweb:1.0
步骤8:最后访问 http://ip:8090/hello/count,其中的ip改成你的虚拟机ip,结果如下:
7.12 DockerCompose下载安装
1. 什么是DockerCompose
Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器,Compose文件不是一个文本文件,通过指令定义集群中的每个容器如何运行。
2. DockerCompose的使用实例
还记得Docker启动镜像吗?其实就是将Docker run启动命令都放在Docker Compose文件中,只是语法稍有差异。
version: "3.8"services:mysql:image: mysql:5.7.25environment:MYSQL_ROOT_PASSWORD: 123 volumes:- "/tmp/mysql/data:/var/lib/mysql"- "/tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf"web:build: .ports:- "8090:8090"
3. DockerCompose的下载安装
1. 百度网盘下载docker-compose文件,通过xftp放到/usr/local/bin目录下
2. 修改文件权限
chmod +x /usr/local/bin/docker-compose
执行后文件变绿了,代表文件可以执行了
3. Base自动补全命令
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
如果出错了,无法下载,那就执行如下命令
echo "199.232.68.133 raw.githubusercontent.com" >> /etc/hosts
7.13 DockerCompose部署微服务集群
1. 查看课前资料提供的cloud-demo文件夹,里面已经编写好了docker-compose文件
2. 查看docker-compose.yml文件
version: "3.2"services: # 包含了下面五个服务nacos、mysql、userservice、orderservice、gatewaynacos: # 由于所有服务都需要注册到nacos,所以nacos先启动image: nacos/nacos-server # 镜像environment:MODE: standalone # 单点模式ports:- "8848:8848" # 端口mysql:image: mysql:5.7.25 # 镜像environment:MYSQL_ROOT_PASSWORD: 123 # 密码volumes: # 数据挂载- "$PWD/mysql/data:/var/lib/mysql" # $PWD是相对于/cloud-demo/docker-compose.yml文件下的目录的当前- "$PWD/mysql/conf:/etc/mysql/conf.d/"userservice:build: ./user-serviceorderservice:build: ./order-servicegateway:build: ./gatewayports:- "10010:10010"
3. 修改自己的cloud-demo项目,将数据库、nacos地址都命名为docker-compose中的服务名
具体可以看视频讲解
4. 打包
接下来需要将我们的每个微服务都打包。因为之前查看到Dockerfile中的jar包名称都是app.jar,因此我们的每个微服务都需要用这个名称。
可以通过修改pom.xml中的打包名称来实现,每个微服务都需要修改:
<build><!-- 服务打包的最终名称 --><finalName>app</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>
5. 拷贝jar包到部署目录
编译打包好的app.jar文件,需要放到Dockerfile的同级目录中。注意:每个微服务的app.jar放到与服务名称对应的目录,别搞错了。
6. 部署
最后,我们需要将文件整个cloud-demo文件夹上传到虚拟机中,理由DockerCompose部署。上传到任意目录
进入cloud-demo目录,然后运行下面的命令:docker-compose up -d
7. 这时我们发现有的服务启动失败了
究其原因,原来是因为nacos还未启动成功,导致其他微服务同时启动时无法注册成功,这时我们可以运行
docker-compose restart gateway userservice orderservice
8. 重新启动项目
重启这三个微服务,重启完成后可以查看日志,看看服务是否正常启动了.
9. 开启项目
最后可以访问http://10.100.2.210:10010/order/101?authorization=admin,ip记得改成虚拟机ip,结果如图,说明服务正常启动了.
八、RabbitMQ消息队列
8.1 同步通讯与异步通讯
1. 同步通讯问题
- 耦合度高:每次加入新的需求,都要修改原来的代码
- 性能下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用的时间
- 资源浪费:调用链中的每个服务都在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源
- 级联失败:如果服务提供者出现问题,所有调用都会跟着出问题,迅速导致整个微服务集群故障
2. 异步通讯解决了同步通讯的问题
- 服务解耦:支付服务不再调用订单服务来,而是支付服务直接发布消息,订单服务订阅到消息并且执行服务
- 性能提升:支付服务发送订单消息后,直接做其他的事情了,而不是还在等待订单服务执行
- 解决级联失败问题:服务没有了相互调用,就不用担心级联失败问题
- 流量削峰:由Broker承担流量压力分配
8.2 MQ技术对比
MQ,中文是消息队列(MessageQueue),字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。
8.3 RabbitMQ下载和安装
1. 下载镜像
docker pull rabbitmq:3-management
2. 安装MQ
docker run \\-e RABBITMQ_DEFAULT_USER=itcast \\ # MQ页面登陆的账号-e RABBITMQ_DEFAULT_PASS=123321 \\ # MQ页面登陆的密码--name mq \\ # 给MQ起个名字--hostname mq1 \\ # 起个主机名,分布式集群的时候使用-p 15672:15672 \\ # MQ提供一个管理平台的UI,通过这个端口访问-p 5672:5672 \\ # 用来消息通信的端口-d \\ # 后台运行rabbitmq:3-management # 镜像的名称
3. 运行后访问
运行后访问http://192.168.133.128:15672/,其中ip需要改成自己虚拟机的ip,可以看到
8.4 RabbitMQ组件的介绍
channel:操作MQ的工具
exchange:路由消息到队列中
queue:缓存消息
virtual host:虚拟主机,对exchange、queue等资源的逻辑分组
8.5 RabbitMQ消息模型
- BasicQueue和WorkQueue都是基于Queue队列来完成消息发送,并没有使用到交换机。
- 发布订阅使用到Exchange交换机。
8.6 简单队列模型
1. 课前资料提供了一个Demo工程,mq-demo
2. 查看项目结构
3. 代码查看
publisher实现
package cn.itcast.mq.helloworld;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class PublisherTest {@Testpublic void testSendMessage() throws IOException, TimeoutException {// 1.建立连接ConnectionFactory factory = new ConnectionFactory();// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码factory.setHost("192.168.133.128");factory.setPort(5672);factory.setVirtualHost("/");factory.setUsername("itcast");factory.setPassword("123321");// 1.2.建立连接Connection connection = factory.newConnection();// 2.创建通道ChannelChannel channel = connection.createChannel();// 3.创建队列String queueName = "simple.queue";channel.queueDeclare(queueName, false, false, false, null);// 4.发送消息String message = "hello, rabbitmq!";channel.basicPublish("", queueName, null, message.getBytes());System.out.println("发送消息成功:【" + message + "】");// 5.关闭通道和连接channel.close();connection.close();}
}
consumer实现
package cn.itcast.mq.helloworld;import com.rabbitmq.client.*;import java.io.IOException;
import java.util.concurrent.TimeoutException;public class ConsumerTest {public static void main(String[] args) throws IOException, TimeoutException {// 1.建立连接ConnectionFactory factory = new ConnectionFactory();// 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码factory.setHost("192.168.133.128");factory.setPort(5672);factory.setVirtualHost("/");factory.setUsername("itcast");factory.setPassword("123321");// 1.2.建立连接Connection connection = factory.newConnection();// 2.创建通道ChannelChannel channel = connection.createChannel();// 3.创建队列String queueName = "simple.queue";channel.queueDeclare(queueName, false, false, false, null);// 4.订阅消息channel.basicConsume(queueName, true, new DefaultConsumer(channel){@Overridepublic void handleDelivery(String consumerTag, Envelope envelope,AMQP.BasicProperties properties, byte[] body) throws IOException {// 5.处理消息String message = new String(body);System.out.println("接收到消息:【" + message + "】");}});System.out.println("等待接收消息。。。。");}
}
4. 运行
先运行PublisherTest,再运行ConsumerTest,在ConsumerTest控制台中可以获得如下结果
九、SpringAMQP
SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
9.1 Basic Queue简单队列模型
1. 在父工程中引入mq-demo依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 消息发送
首先配置MQ地址,在publisher服务的application.yml中添加配置
spring:rabbitmq:host: 10.100.2.210 # 主机名port: 5672 # 端口virtual-host: / # 虚拟主机username: itcast # 用户名password: 123321 # 密码
然后在publisher服务中编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送:
package cn.itcast.mq.spring;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSimpleQueue() {// 队列名称String queueName = "simple.queue";// 消息String message = "hello, spring amqp!";// 发送消息rabbitTemplate.convertAndSend(queueName, message);}
}
3. 消息接收
首先配置MQ地址,在comsumer服务的application.yml中添加配置:
spring:rabbitmq:host: 10.100.2.210 # 主机名port: 5672 # 端口virtual-host: / # 虚拟主机username: itcast # 用户名password: 123321 # 密码
然后在consumer服务的cn.itcast.mq.listener包中新建一个类SpringRabbitListener,代码如下:
package cn.itcast.mq.listener;import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue")public void listenSimpleQueueMessage(String msg) throws InterruptedException {System.out.println("spring 消费者接收到消息:【" + msg + "】");}
}
9.2 WorkQueue消息队列
Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。
1. 在父工程中引入mq-demo依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. 消息发送
首先配置MQ地址,在publisher服务的application.yml中添加配置
spring:rabbitmq:host: 10.100.2.210 # 主机名port: 5672 # 端口virtual-host: / # 虚拟主机username: itcast # 用户名password: 123321 # 密码
然后在publisher服务中编写测试类SpringAmqpTest,并利用RabbitTemplate实现消息发送:
/* workQueue* 向队列中不停发送消息,模拟消息堆积。*/
@Test
public void testWorkQueue() throws InterruptedException {// 队列名称String queueName = "simple.queue";// 消息String message = "hello, message_";for (int i = 0; i < 50; i++) {// 发送消息rabbitTemplate.convertAndSend(queueName, message + i);Thread.sleep(20);}
}
3. 消息接收
首先配置MQ地址,在comsumer服务的application.yml中添加配置:
spring:rabbitmq:host: 10.100.2.210 # 主机名port: 5672 # 端口virtual-host: / # 虚拟主机username: itcast # 用户名password: 123321 # 密码
然后在consumer服务的cn.itcast.mq.listener包中新建一个类SpringRabbitListener,代码如下:
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());Thread.sleep(20);
}@RabbitListener(queues = "simple.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());Thread.sleep(200);
}
4. 测试
启动ConsumerApplication后,在执行publisher服务中刚刚编写的发送测试方法testWorkQueue。可以看到消费者1很快完成了自己的25条消息。消费者2却在缓慢的处理自己的25条消息。
也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。这样显然是有问题的。
在spring中有一个简单的配置,可以解决这个问题。我们修改consumer服务的application.yml文件,添加配置:
spring:rabbitmq:listener:simple:prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息
运行consumer服务可以看到,结果是处理消息快的处理的次数也多,即能者多劳.