【实用篇】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服务可以看到,结果是处理消息快的处理的次数也多,即能者多劳.
其他比较复杂的MQ模型,在工作遇到时再学习
相关文章:

【实用篇】SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式,系统详解springcloud分布式
文章目录一、服务拆分1.1 服务拆分Demo1.2 微服务远程调用二、Eureka2.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 饥饿加载与…...

私人飞机、公务机包机会成为富豪圈的主流出行方式吗?
从炫耀性消费到按需使用,私人飞机的消费群体正在被拓宽,但离“成为主流”还有一段距离。“时间就是金钱”为有钱人消费私人飞机提供合理动机,而这群高净值人群的数量增长则成为撑起市场基本面。据相关数据显示,2018年全球超级富豪…...

Oracle组织架构
组织架构 (一)业务组(BG) (二)法律实体(LE) (三)业务实体(OU) (四)库存组织(INV) …...
最小公倍数
目录 最小公倍数 程序设计 程序分析 最小公倍数 【问题描述】给定两个正整数,计算这两个数的最小公倍数。 【输入形式】输入包含多组测试数据,每组只有一行,包括两个不大于1000的正整数. 【输出形式】 对于每个测试用例,给出这两个数的最小公倍数,每个实例输出一行。…...

二叉树的后序遍历(力扣145)
目录 题目描述: 解法一:递归法 解法二:迭代法 解法三:Morris遍历 二叉树的后序遍历 题目描述: 给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。 示例 1: 输入:root …...
《Effective C++》读书纪实 -- 诸君同享
文章目录《Effective C》是一本经典的C编程指南,共包含50条C编程的最佳实践。 确定你的构造函数的行为 在构造函数中,应该尽可能地避免调用虚函数、非静态成员函数和虚基类的函数。 尽量使用const、enum、inline替换#define 使用const、enum、inline可以…...
【云原生】K8S-ConfigMap 实现应用和配置分离
文章目录前言ConfigMap 背景ConfigMap 创建方式ConfigMap 的使用使用 ConfigMap 的注意事项总结前言 Kubernetes 是目前最流行的容器编排系统之一,它提供了丰富的功能来支持容器化应用程序的管理和部署。 ConfigMap 是 Kubernetes 中重要的资源对象,用…...
java -测距工具(经纬度)
代码 /*** 测距工具* author qb*/ public class DistanceUtils {/*** 赤道半径*/private static final double EARTH_RADIUS 6378.137;private static double rad(double d) {return d * Math.PI / 180.0;}/*** Description : 通过经纬度获取距离(单位:米)* Group…...
postgres分区表的创建-基于继承
参考文档: http://postgres.cn/docs/12/ddl-partitioning.html 创建基于继承的分区表的步骤 1 创建父表 2 创建子表,从父表继承过来 3 创建函数及触发器,使插入的数据根据规则,插入到对应的子表中 -- 创建父表 CREATE TABLE a…...

Docker应用部署
文章目录Docker 应用部署一、部署MySQL二、部署Tomcat三、部署Nginx四、部署RedisDocker 应用部署 一、部署MySQL 搜索mysql镜像 docker search mysql拉取mysql镜像 docker pull mysql:5.6创建容器,设置端口映射、目录映射 # 在/root目录下创建mysql目录用于存…...

使用golang实现日志收集系统的logagent
整体架构 参考 七米老师的日志收集项目 主要用go实现logagent的部分,logagent的作用主要是实时监控日志追加的变化,并将变化发送到kafka中。 之前我们已经实现了 用go连接kafka并向其中发送数据,也实现了使用tail库监控日志追加操作。 我们…...
小红书点赞不显示怎么回事?小红书笔记评论被吞怎么办
小红书作为一个互联网产品,是一个软件。既然是软件就会有一定的程序漏洞,这是无法避免的。但是很多时候其实并不一定是漏洞的问题。今天就来和大家谈谈小红书点赞不显示怎么回事,小红书评论被吞又是怎么一回事,这些难道都是程序性…...

地址变换和缺页置换习题
1.设某进程页面的访问序列为4,3,2,1,4,3,5,4,3,2,1,5,当分配给该进程的内存页框数分别为3和4时,对于先进先出,最近最少使用,最佳页面置换算法,分别发生多少次缺页中断? 答: 分配的…...
PAT 乙级 1010 一元多项式求导(解题思路+AC代码)
题目: 设计函数求一元多项式的导数。(注:xn(n为整数)的一阶导数为nxn−1。) 输入格式: 以指数递降方式输入多项式非零项系数和指数(绝对值均为不超过 1000 的整数)。数字间以空格分…...

一维河流污染持续排放模拟(水污染扩散)
一、处理河道转换为geojson数据 以淮河为例处理示例数据: {"type": "FeatureCollection","features": [{"geometry": {"coordinates": [[[115.5803,34.4982],[115.5922,34.498],[115.6061,34.4994],[115.6203,…...

数据优化 | CnOpenDataA股上市公司招聘数据
就业是经济的“晴雨表”,更是社会的“稳定器”。稳定和扩大就业一直是国家宏观调控的重要目标,2021年中央经济工作会议八次提到“就业”这一关键词。在新冠肺炎疫情蔓延、世界经济下行及人口老龄化加快等多重因素的叠加之下,稳就业保民生成为…...

nacos和eureka的区别
nacos和eureka的区别 Eureka是什么 Eureka详解Nacos是什么 Nacos详解Nacos和Eureka的区别 CAP理论连接方式服务异常剔除操作实例方式自我保护机制 Eureka是什么 Eureka 是Spring Cloud 微服务框架默认的也是推荐的服务注册中心,由Netflix公司与2012将其开源出来,Eureka基于RE…...
canvas.toDataURL生成图片报错的解决方案
问题原因: toDataURL方法存在跨域限制,如果执行时dom内含有跨域的图片则浏览器执行时会报错。 这个根据不同的系统有不同的表现,例如:生成完毕但控制台有warning类型的警告,或者直接异常报error。 解决思路ÿ…...

电容笔和Apple pencil的区别是什么?好用电容笔推荐
Apple Pencil与目前市场上常见的电容笔最大的不同之处在于,普通电容笔并不具备苹果Pencil特有的重力压感,而仅仅是一种倾斜的压感。不过,其在其它方面的表现也很出色,与Apple Pencil相似,而且价格仅为200元。现在&…...
关于onnx 转ncnn 的问题
文章目录修改模型Detect层设计转换后处理优质文章由于有些操作是没法支持的 如5维的操作: Unsupported slice axes ! Unsupported slice axes ! Unsupported slice axes ! Unsupported slice axes ! Unsupported slice axes ! Unsupported slice axes !参考&#…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...

MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...

WebRTC调研
WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...
RLHF vs RLVR:对齐学习中的两种强化方式详解
在语言模型对齐(alignment)中,强化学习(RL)是一种重要的策略。而其中两种典型形式——RLHF(Reinforcement Learning with Human Feedback) 与 RLVR(Reinforcement Learning with Ver…...