SpringCloud微服务技术栈

1.认识微服务

 

服务治理

分布式架构的要考虑的问题:

  • 服务拆分粒度如何?
  • 服务集群地址如何维护?
  • 服务之间如何实现远程调用?
  • 服务健康状态如何感知?

Springcloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:Spring Cloudicon-default.png?t=N7T8https://spring.io/projects/spring-cloud

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:

2.分布式服务架构案例

 

 1. 服务拆分Demo
服务拆分(也叫项目拆分)注意事项
●不同的微服务,不要重复开发相同的业务
●要求微服务之间的数据独立,不要访问其它微服务的数据库
●微服务可以将自己的业务暴露为接口,供其它微服务调用
首先有一个已经写好的项目工程,为cloud-demo.zip,需要把这个压缩包解压并导入进idea,然后就是根据功能来拆分这个项目。这个项目的原有结构如下:
cloud-demo是父工程,里面有两个模块是order-service、user-service,这俩模块分别的作用是根据id查询订单、根据id查询用户。这俩模块就是所谓的微服务,分是订单服务和用户服务
除了把项目工程解压导入,还有两份sql文件(cloud-order.sql和cloud-user.sql)需要导入自己本地数据库(创建两个不同的database),作为数据层面的分离。订单服务只能查询cloud-order.sql表,用户服务只能查询cloud-user.sql表

数据库的具体操作如下:
第一步: 在mysql创建两个数据库,为cloud_order、cloud_user

# 创建两个数据库
create database if not exists cloud_order;
create database if not exists cloud_user;

 第二步: 分别在这两个数据库导入对应的sql文件

第三步: 下载cloud-demo.zip项目工程文件,解压到D盘的springcloud目录,并在idea打开cloud-demo项目工程

 第四步: 启动cloud-demo项目工程

 下面将在这个Demo进行练习,如何正确使用微服务项目

2. 服务远程调用
案例: 根据订单id查询订单功能
需求: 根据订单id查询订单的同时,把订单所属的用户信息一起返回
难点: 订单表、用户表在两个数据库,不是同一个数据库。订单业务、用户业务在两个项目,不是同一个项目
解决: 服务远程调用
目前这个Demo还没有实现服务远程调用,也就是在查订单表时,无法查到用户信息


分析:
用户项目对外暴露了一个Restful接口,如下
可以在订单项目使用Spring提供的RestTemplate工具,作用是发送http请求,也就是在订单项目向用户项目发送http请求,用户项目就会返回数据给订单项目
具体操作:
第一步: 在订单项目的OrderApplication引导类,添加如下

/*** 创建RestTemplate并注入Spring容器* @return*///以后我们都要习惯使用上面这种注释,写法: /**+回车@Beanpublic RestTemplate xxrestTemplate(){return new RestTemplate();}

第二步: 在订单项目的OrderService类,添加如下

@Autowiredprivate RestTemplate kkrestTemplate;/*** 使用RestTemplate,向用户项目发起http请求,查询用户*/String xxurl = "http://localhost:8081/user/"+order.getUserId();User gguser = kkrestTemplate.getForObject(xxurl, User.class);//第一个参数是路径,第二个参数是你想要拿到什么类型的数据order.setUser(gguser);//把向用户项目拿到的数据封装到这个订单项目


第三步: 测试。重新启动用户项目和订单项目,在浏览器输入如下,查看在订单项目是否能获取到用户项目的数据

http://localhost:8080/order/101


总结: 跨服务的远程调用其实就是发送一次http请求,首先是在Spring容器里面注入RestTemplate对象,然后在你发送请求的类里面自动装配这个RestTemplate对象,并且在方法里面调用这个RestTemplate对象,第一个参数是路径,第二个参数是你想拿到什么类型的数据


3.eureka注册中心

 实用篇-Eureka注册中心
读音: yī yōu ruī kǎ
1. 提供者与消费者
服务提供者: 一次业务中,被其它微服务调用的服务。简单来说,服务提供者就是提供接口给其它微服务
服务消费者: 一次业务中,调用其它微服务的服务。简单来说,服务消费者就是调用其它微服务提供的接口
例如上面的案例中,order-service微服务是服务提供者,user-service微服务是服务消费者
思考: 如果服务A调用服务B,服务B调用服务C,那么服务B是什么角色 ?
答案: 一个服务既可以是提供者,也可以是消费者。所以服务B相对于服务A而言,服务B是提供者。服务B相对于服务C而言,服务B是消费者
2. eureka原理分析
还是以上面的案例为例,order-service微服务和user-service微服务之间,服务调用出现的问题,如下
●order-service去向user-service发送请求,使用的是硬编码,也就是 "http://localhost:8081/user/"+order.getUserId();
●硬编码每次修改需要重新打包
●如果user-service微服务(提供者)部署成了多实例,形成集群来应对并发,那么order-service微服务(消费者)硬编码到底是写哪个实例的地址
●即使你知道这些实例的地址,那么如何挑选其中一台实例来使用呢
解决: 下面要学的Eureka注册中心
Eureka的原理:


●Eureka有两个角色,其中一个是eureka-server,叫Eureka的注册中心。作用是记录和管理所有微服务
●Eureka的另一个角色就是我们的所有微服务,也就是消费者/提供者,叫Eureka的客户端EurekaClient。

●可以把注册中心理解为Key,我们的微服务项目理解为Value,Eureka理解为字典
●当我们的微服务项目(例如order-service)启动时,会主动把自己(user-service微服务)的信息注册给注册中心
●多个微服务的话,注册中心就会有多个Value,一个Value就是一个微服务的信息,这些Value会放到一个列表里面
●当其它微服务(例如user-service)要使用某个微服务(例如order-service)时,这个微服务(例如user-service就会向注册中心去拉取对应微服务(例如order-service)的信息
●注册到注册中心的微服务会每隔30秒,向注册中心发起心跳,证明自己还在健康运行
●当微服务没有正常向注册中心发起心跳,此时注册中心就会自动在列表把这个异常的微服务剔除掉
●当注册中心有多个提供者(微服务),那么消费者是通过负载均衡算法,在注册中心的服务列表中挑选一个
●负载均衡: 简单理解就是如果有很多个微服务,且这些服务都是一个相同的请求,看谁现在工作压力小就调用哪个谁
3. 搭建eureka服务


步骤有三步:


1、搭建注册中心。搭建EurekaServer注册中心,也就是创建一个项目(在cloud-demo项目内部创建eureka-server项目),把这个项目做成注册中心
2、服务注册。将user-service(前面导入的服务拆分Demo)、order-service(前面导入的服务拆分Demo)都注册到eureka
3、服务发现。在order-service中完成服务拉取,然后通过负载均衡挑选一个服务,实现远程调用
这里只学第一个步骤,也就是搭建注册中心
第一步: 创建一个新的项目,作为独立的微服务,用于搭建Eureka,也就是在cloud-demo工程里面新建eureka-server微服务项目

第二步: 在eureka-server微服务的pom.xml添加如下

    <!--添加注册中心的依赖坐标--><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency></dependencies>

 

 第三步: 在eureka-server微服务的src/main/java目录新建cn.huanf.eureka.EurekaApplication类,写入如下

package cn.itcast.eureka;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;/*** @Author:豆浆* @name :EurekeApplication* @Date:2024/3/28 11:32*/
@SpringBootApplication
@EnableEurekaServer//注册中心的开关
public class EurekeApplication {public static void main(String[] args) {SpringApplication.run(EurekeApplication.class);}
}

第四步: 在eureka-server微服务的src/main/resources目录新建File,名字为application.yml,写入如下

第五步: 启动eureka-server微服务。也就是运行EurekaApplication类,浏览器访问 http://localhost:8686

eureka-server微服务的Eureka在启动时,会把自己(eureka-server)也注册到Eureka。所以这就是为什么我们会在eureka-server微服务的application.yml里面写name: eurekaserver,这个其实就是自己的地址,把自己注册到Eureka,注册之后自己的名字就是EUREKASERVER

4. 服务注册


步骤有三步:
1、搭建注册中心。搭建EurekaServer注册中心
2、服务注册。将user-service(前面导入的服务拆分Demo)、order-service(前面导入的服务拆分Demo)都注册到eureka
3、服务发现。在order-service中完成服务拉取,然后通过负载均衡挑选一个服务,实现远程调用
这里只学第二个步骤,也就是服务注册。就是把user-server微服务注册到eureka-server微服务(这个微服务我们已经在上面 '3. 搭建eureka服务' ,做成了注册中心)里面。具体操作如下
第一步: 在user-service微服务的pom.xml添加如下

<!--引入Eureka客户端依赖-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

第二步: 在user-service微服务的application.yml添加如下

spring:# Eureka相关配置application:# user的服务名称。也就是这个user-service注册到Eureka之后,这个user-service会叫什么名字name: UserServiceeureka:client:service-url:# eureka的服务地址。如果有多个的话,逗号隔开。也就是把当前这个user-service微服务注册给哪个EurekadefaultZone: http://localhost:8686/eureka

第三步: 启动user-service。也就是运行UserApplication类,浏览器访问 http://localhost:8686,就可以看到user-service已经添加到Eureka里面

同理,给order-service微服务也注册到Eureka。具体操作跟上面一样

 建议:

思考: 如何给user-service微服务启动多个实例呢,也就是启动多次,每次user-service微服务启动的端口都不相同

如下
具体操作如下图

-Dserver.port=8082

5. 服务发现
服务发现也叫服务拉取,我们需要在order-service完成服务拉取。服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡
服务发现是我们学习的重点,负载均衡不是,所以不详细介绍负载均衡,实现负载均衡只需要一个注解
首先回想一下,前面的远程调用,我们实现了在订单项目(也就是现在的order-service微服务)去查询用户项目(也就是现在的user-service微服务)的案例需求,
当时是在order-service里面使用url请求ip地址的方式,去请求user-service,从而获取user-service的用户信息。那么,学习了上面的Eureka之后,并且我们已经把order-service和user-service注册到注册中心(eureka-server)了,所以就可以在order-service里面,通过 '服务发现' 去获取user-service里面的用户信息。


具体操作也非常简单,也是使用url请求的方式,但请求的路径不是ip,而是服务名称,如下:


第一步: 在order-server微服务中的src/main/java/cn.itcast.order/service/OrderService类,修改访问的url路径,用服务名代替ip、端口。修改为如下

  String url = "http://UserService/user/"+order.getUserId();

 

第二步: 负载均衡。在order-server微服务中的OrderApplication引导类修改为如下

 第三步: 重新启动在order-server微服务的OrderApplication引导类,浏览器输入http://localhost:8080/order/101,并向user-service微服务发送多次请求


4.Ribbon负载均衡原理

1. 负载均衡原理

 回想一下上面的 '服务发现',order-service微服务向user-service微服务发送请求,但是user-service有两个,也就是开启了两个user-service实例,且端口不同,一个是8081,另一个是8222(对应的是下图的8082),下面我们将详细学一下请求在过程中经历了什么,如下图

其中负载均衡的各种策略是在IRule接口里面,下面将会深入学习这个IRule接口
2. 负载均衡策略
Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则,IRule有很多实现类,如下继承关系图

每一个实现类都是一种规则,上图只是简单标注一下,下图是详细的

如何修改负载均衡策略。负载均衡策略默认是轮询,如何修改为随机呢。有两种方式如下


第一种: 代码方式(作用于全局)。在order-service中的OrderApplication类中,定义一个新的IRule。简单理解就是在项目的引导类创建一个类型为IRule的bean

@Bean
//bean的类型必须是IRule。bean的id是方法名,随便写
public IRule randomRule(){//实现类不一定是RandomRule,还可以是其它,如上图那些都可。RandomRule表示把负载均衡策略修改为随机return new RandomRule();
}

 第二种: 配置文件方式(只作用于该服务名称)。在order-service的application.yml文件中,添加新的配置也可以修改规则

# UserService是你注册到Eureka时的服务名称。注意顶格写就行,不用写在spring:属性里面
UserService:ribbon:# 负载均衡策略。不一定是RandomRule,还可以是其它,如上图那些都可。RandomRule表示把负载均衡策略修改为随机NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

如果同时使用上面这两种方式,那么配置文件的优先级比代码方式低,也就是代码方式的优先级高
注意在使用的时候,很简单,就字面意思,不需要改变你其它地方的任何代码,只需要添加如上两种方式提供的代码即可
3. 饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建Ribbon的LoadBalanceClient客户端,请求时间会很长(第一次访问时间长)。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置就可以开启饥饿加载。在order-service微服务的application.yml添加如下 

# 注意顶格写就行,不用写在spring:属性里面
ribbon:eager-load:# 开启饥饿加载enabled: true# 指定对UserService这个服务开启饥饿加载。UserService是你注册到Eureka时的服务名称。如果有多个服务需要做饥饿加载,就-往下写clients: - UserService- UserService2


5.nacos注册中心

国内公司一般都推崇阿里巴巴的技术,比如注册中心,SpringCloudAlibaba也推出了一个名为Nacos的注册中心。

Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。

主要差异在于:

  • - 依赖不同
  • - 服务地址不同

## 5.1.认识和安装Nacos

[Nacos](https://nacos.io/)是阿里巴巴的产品,现在是[SpringCloud](https://spring.io/projects/spring-cloud)中的一个组件。相比[Eureka](https://github.com/Netflix/eureka)功能更加丰富,在国内受欢迎程度较高。

# Nacos安装指南

# 1.Windows安装

开发阶段采用单机安装即可。

## 1.1.下载安装包

在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:

GitHub主页:https://github.com/alibaba/nacos

GitHub的Release下载页:https://github.com/alibaba/nacos/releases

如图:

本课程采用1.4.1.版本的Nacos,资料已经准备了安装包:

windows版本使用`nacos-server-1.4.1.zip`包即可。

## 1.2.解压

将这个包解压到任意非中文目录下,如图:

目录说明:

- bin:启动脚本

- conf:配置文件

## 1.3.端口配置

Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。

**如果无法关闭占用8848端口的进程**,也可以进入nacos的conf目录,修改配置文件中的端口:

修改其中的内容:

## 1.4.启动

启动非常简单,进入bin目录,结构如下:

cmd

然后执行命令即可:

- windows命令:

startup.cmd -m standalone

执行后的效果如图:

## 1.5.访问

在浏览器输入地址:http://127.0.0.1:8848/nacos即可:

默认的账号和密码都是nacos,进入后:

# 2.Linux安装

Linux或者Mac安装方式与Windows类似。

## 2.1.安装JDK

Nacos依赖于JDK运行,索引Linux上也需要安装JDK才行。

上传jdk安装包:

上传到某个目录,例如:`/usr/local/`

然后解压缩:

tar -xvf jdk-8u144-linux-x64.tar.gz

然后重命名为java

mv jdk-8u144-linux-x64 java

配置环境变量:

export JAVA_HOME=/usr/local/javaexport PATH=$PATH:$JAVA_HOME/bin

设置环境变量:

source /etc/profile

## 2.2.上传安装包

也可以直接使用课前资料中的tar.gz:

上传到Linux服务器的某个目录,例如`/usr/local/src`目录下:

## 2.3.解压

命令解压缩安装包:

tar -xvf nacos-server-1.4.1.tar.gz

然后删除安装包:

rm -rf nacos-server-1.4.1.tar.gz

## 2.4.端口配置

与windows中类似

## 2.5.启动

在nacos/bin目录中,输入命令启动Nacos:

sh startup.sh -m standalone

例如:# 3.Nacos的依赖

父工程:

<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>

客户端:

<!-- nacos客户端依赖包 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>



spring:  cloud:nacos:server-addr:localhost:8848


## 5.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,在杭州机房

Nacos就将同一机房内的实例 划分为一个**集群**。

也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如图:

微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。

当本集群内不可用时,才访问其它集群。例如:

杭州机房内的order-service应该优先访问同机房的user-service。

### 5.3.1.给user-service配置集群

1.修改user-service的application.yml文件,添加集群配置:

spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ # 集群名称

重启两个user-service实例后,我们可以在nacos控制台看到下面结果:

spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: SZ # 集群名称

启动UserApplication3后再次查看nacos控制台:

### 5.3.2.同集群优先的负载均衡

默认的`ZoneAvoidanceRule`并不能实现根据同集群优先来实现负载均衡。

因此Nacos中提供了一个`NacosRule`的实现,可以优先从同集群中挑选实例。

(同集群间随机)

1)给order-service配置集群信息

修改order-service的application.yml文件,添加集群配置:

spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZ # 集群名称

2)修改负载均衡规则

修改order-service的application.yml文件,修改负载均衡规则:

userservice:ribbon:NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则

## 5.4.权重配置

实际部署中会出现这样的场景:

服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。

但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。

因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。

在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:

在弹出的编辑窗口,修改权重:

> **注意**:如果权重修改为0,则该实例永远不会被访问

## 5.5.环境隔离

Nacos提供了namespace来实现环境隔离功能。

  • - nacos中可以有多个namespace
  • - namespace下可以有group、service等
  • - 不同namespace之间相互隔离,例如不同namespace的服务互相不可见

 例如,修改order-service的application.yml文件:

spring:cloud:nacos:server-addr: localhost:8848discovery:cluster-name: HZnamespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID

重启order-service后,访问控制台,可以看到下面的结果:

此时访问order-service,因为namespace不同,会导致找不到userservice,控制台会报错:


 

## 5.6.Nacos与Eureka的区别

Nacos的服务实例分为两种l类型:

  • - 临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
  • - 非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例。

配置一个服务实例为永久实例:

spring:cloud:nacos:discovery:ephemeral: false # 设置为非临时实例

Nacos和Eureka整体结构类似,服务注册、服务拉取、心跳等待,但是也存在一些差异:

- Nacos与eureka的共同点

  •   - 都支持服务注册和服务拉取
  •   - 都支持服务提供者心跳方式做健康检测

- Nacos与Eureka的区别

  •   - Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  •   - 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  •   - Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  •   - Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

        

        

       

# 1.实用篇-Nacos配置管理

Nacos除了可以做注册中心,同样可以做配置管理来使用。

## 1.1.统一配置管理

当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。

Nacos一方面可以将配置集中管理,另一方可以在配置变更时,及时通知微服务,实现配置的热更新。

### 1.1.1.在nacos中添加配置文件

如何在nacos中管理配置呢?

在弹出的表单中,填写配置信息:

> 注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。
 

### 1.1.2.从微服务拉取配置

微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动。

但如果尚未读取application.yml,又如何得知nacos地址呢?

因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下:

1)引入nacos-config依赖

首先,在user-service服务中,引入nacos-config的客户端依赖:

<!--nacos配置管理依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

2)添加bootstrap.yaml

然后,在user-service中添加一个bootstrap.yaml文件,内容如下:

spring:application:name: userservice # 服务名称profiles:active: dev #开发环境,这里是devcloud:nacos:server-addr: localhost:8848 # Nacos地址config:file-extension: yaml # 文件后缀名

这里会根据spring.cloud.nacos.server-addr获取nacos地址,再根据

`${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}`作为文件id,来读取配置。

本例中,就是去读取`userservice-dev.yaml`:
 

3)读取nacos配置

在user-service中的UserController中添加业务逻辑,读取pattern.dateformat配置:

代码:

    @Value("${pattern.dateformat}")private String dateformat;@GetMapping("now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));}

在页面访问,可以看到效果:

## 1.2.配置热更新

我们最终的目的,是修改nacos中的配置后,微服务中无需重启即可让配置生效,也就是**配置热更新**。

要实现配置热更新,可以使用两种方式:

### 1.2.1.方式一

在@Value注入的变量所在类(UserController)上添加注解@RefreshScope:

### 1.2.2.方式二

使用@ConfigurationProperties注解代替@Value注解。

在user-service服务中,添加一个类,读取patterrn.dateformat属性:

在UserController中使用这个类代替@Value:

代码:

    @Autowiredprivate PatternProperties patternProperties;@GetMapping("now")public String now(){return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));}

## 1.3.配置共享

下面我们通过案例来测试配置共享
 

### 1)添加一个环境共享配置

我们在nacos中添加一个userservice.yaml文件:

### 2)在user-service中读取共享配置

在user-service服务中,修改PatternProperties类,读取新添加的属性:

在user-service服务中,修改UserController,添加一个方法:

### 3)运行两个UserApplication,使用不同的profile

修改UserApplication2这个启动项,改变其profile值:

这样,UserApplication(8081)使用的profile是dev,UserApplication2(8082)使用的profile是test。

启动UserApplication和UserApplication2

访问http://localhost:8081/user/prop,结果:

访问http://localhost:8082/user/prop,结果:

可以看出来,不管是dev,还是test环境,都读取到了envSharedValue这个属性的值。

### 4)配置共享的优先级

当nacos、服务本地同时出现相同属性时,优先级有高低之分:

## 1.4.搭建Nacos集群

Nacos生产环境下一定要部署为集群状态

nacos集群搭建1
上面我们一直使用的都是单点的nacos,这种方式适合测试,但是在生产环境是不使用单点nacos。原因: 生产环境强调高可用,所以nacos要做成集群
官方Nacos集群图如下。其中包含3个nacos节点,然后一个负载均衡器(例如nginx)来代理3个Nacos服务


我们将要学习的Nacos集群图如下


三个nacos节点的地址:

节点

ip

port

nacos1

127.0.0.1

8845

nacos2

127.0.0.1

8846

nacos3

127.0.0.1

8847

下面的操作是集群配置
第一步: 准备数据库数据。在你数据库执行如下语句

create database if not exists nacos;
use nacos;
CREATE TABLE `config_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(255) DEFAULT NULL,`content` longtext NOT NULL COMMENT 'content',`md5` varchar(32) DEFAULT NULL COMMENT 'md5',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',`src_user` text COMMENT 'source user',`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',`app_name` varchar(128) DEFAULT NULL,`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',`c_desc` varchar(256) DEFAULT NULL,`c_use` varchar(64) DEFAULT NULL,`effect` varchar(64) DEFAULT NULL,`type` varchar(64) DEFAULT NULL,`c_schema` text,PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_aggr   */
/******************************************/
CREATE TABLE `config_info_aggr` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(255) NOT NULL COMMENT 'group_id',`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',`content` longtext NOT NULL COMMENT '内容',`gmt_modified` datetime NOT NULL COMMENT '修改时间',`app_name` varchar(128) DEFAULT NULL,`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_beta   */
/******************************************/
CREATE TABLE `config_info_beta` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(128) NOT NULL COMMENT 'group_id',`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',`content` longtext NOT NULL COMMENT 'content',`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',`md5` varchar(32) DEFAULT NULL COMMENT 'md5',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',`src_user` text COMMENT 'source user',`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_info_tag   */
/******************************************/
CREATE TABLE `config_info_tag` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(128) NOT NULL COMMENT 'group_id',`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',`content` longtext NOT NULL COMMENT 'content',`md5` varchar(32) DEFAULT NULL COMMENT 'md5',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',`src_user` text COMMENT 'source user',`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',PRIMARY KEY (`id`),UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = config_tags_relation   */
/******************************************/
CREATE TABLE `config_tags_relation` (`id` bigint(20) NOT NULL COMMENT 'id',`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',`data_id` varchar(255) NOT NULL COMMENT 'data_id',`group_id` varchar(128) NOT NULL COMMENT 'group_id',`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',`nid` bigint(20) NOT NULL AUTO_INCREMENT,PRIMARY KEY (`nid`),UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = group_capacity   */
/******************************************/
CREATE TABLE `group_capacity` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = his_config_info   */
/******************************************/
CREATE TABLE `his_config_info` (`id` bigint(64) unsigned NOT NULL,`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`data_id` varchar(255) NOT NULL,`group_id` varchar(128) NOT NULL,`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',`content` longtext NOT NULL,`md5` varchar(32) DEFAULT NULL,`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,`src_user` text,`src_ip` varchar(50) DEFAULT NULL,`op_type` char(10) DEFAULT NULL,`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',PRIMARY KEY (`nid`),KEY `idx_gmt_create` (`gmt_create`),KEY `idx_gmt_modified` (`gmt_modified`),KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';/******************************************/
/*   数据库全名 = nacos_config   */
/*   表名称 = tenant_capacity   */
/******************************************/
CREATE TABLE `tenant_capacity` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';CREATE TABLE `tenant_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`kp` varchar(128) NOT NULL COMMENT 'kp',`tenant_id` varchar(128) default '' COMMENT 'tenant_id',`tenant_name` varchar(128) default '' COMMENT 'tenant_name',`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',PRIMARY KEY (`id`),UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';CREATE TABLE `users` (`username` varchar(50) NOT NULL PRIMARY KEY,`password` varchar(500) NOT NULL,`enabled` boolean NOT NULL
);CREATE TABLE `roles` (`username` varchar(50) NOT NULL,`role` varchar(50) NOT NULL,UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);CREATE TABLE `permissions` (`role` varchar(50) NOT NULL,`resource` varchar(255) NOT NULL,`action` varchar(8) NOT NULL,UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');

 

第二步: 在G盘新建nacosGroup文件夹,下载链接里的nacos-server-1.4.1压缩包,并解压到nacosGroup文件夹

第三步: 把解压后的nacos文件夹,里面的cluster.conf.example文件重命名为cluster.conf


第四步: 在cluster.conf文件添加如下


第五步: 把application.properties文件修改为如下


第六步: 把做好的nacos文件夹复制三份,分别命名为nacos1、nacos2、nacos3


第七步: 把这三个nacos文件夹的conf/application.properties文件的端口改一下,分别是8845、8846、8847

 

nacos集群启动之后,接下来就是安装启动nginx负载均衡器
第一步: 在G盘新建nacosNginx目录,把下载好的nginx-1.18.0压缩包解压到nacosNginx目录


第二步: 把nacosNginx目录的nginx-1.18.0/conf/nginx.conf文件,修改为如下


        注意三个server地址要跟你上面做的nacos一致,作用是nginx对谁进行负载均衡,也就是对我们启动的三台nacos节点进行负载均衡
listen的作用是反向代理,简单说就是你浏览器访问nacos时,不需要输入8848端口,直接就是80端口
/nacos表示代理路径,也就是你访问/nacos路径时,实际访问的是 http://nacos-clusternacos 集群路径

Nginx复制代码

upstream nacos-cluster {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 {proxy_pass http://nacos-cluster;}
}


第三步: 启动nginx。在命令行输入如下。然后浏览器访问: http://localhost/nacos 。用户名和密码都是nacos

Plain Text复制代码

d:
cd D:\nacosNginx\nginx-1.18.0
start nginx.exe


集群搭建成功,看着好像是只访问了一台nacos,实际上已经在三台nacos之间做了一个负载均衡,也就是三台nacos都可被访问

6.Feign远程调用

http客户端Feign

6.1  Feign替代RestTemplate

 

先来看我们以前利用RestTemplate发起远程调用的代码:

存在下面的问题:

  • •代码可读性差,编程体验不统一
  • •参数复杂URL难以维护

Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign

其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。

Fegin的使用步骤如下:

### 1)引入依赖

我们在order-service服务的pom文件中引入feign的依赖:
 

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>

### 2)添加注解

在order-service的启动类添加注解开启Feign的功能:

@EnableFeignClients

### 3)编写Feign的客户端

在order-service中新建一个接口,内容如下:

@FeignClient("userservice")
public interface UserClient {@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}

这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • - 服务名称:userservice
  • - 请求方式:GET
  • - 请求路径:/user/{id}
  • - 请求参数:Long id
  • - 返回值类型:User

这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。

### 4)测试

修改order-service中的OrderService类中的queryOrderById方法,使用Feign客户端代替RestTemplate:

是不是看起来优雅多了。


6.2  自定义配置

 Feign可以支持很多的自定义配置,如下表所示:

一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。

下面以日志为例来演示如何自定义配置。

### 2.2.1.配置文件方式

基于配置文件修改feign的日志级别可以针对单个服务:

feign:  client:config:userservice: # 针对某个微服务的配置loggerLevel: FULL #  日志级别

也可以针对所有服务:

feign:  client:config:default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置loggerLevel: FULL #  日志级别

而日志的级别分为四种:

  • - NONE:不记录任何日志信息,这是默认值。
  • - BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • - HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • - FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

### 2.2.2.Java代码方式

也可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象:

public class DefaultFeignConfiguration  {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; // 日志级别为BASIC}}

如果要**全局生效**,将其放到启动类的@EnableFeignClients这个注解中:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)

如果是**局部生效**,则把它放到对应的@FeignClient这个注解中:

@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)

 


6.3  Feign使用优化

Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:

  • •URLConnection:默认实现,不支持连接池
  • •Apache HttpClient :支持连接池
  • •OKHttp:支持连接池

因此提高Feign的性能主要手段就是使用**连接池**代替默认的URLConnection。

这里我们用Apache的HttpClient来演示。

1)引入依赖

在order-service的pom文件中引入Apache的HttpClient依赖:

<!--httpClient的依赖 --><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>

2)配置连接池

在order-service的application.yml中添加配置:
 

feign:client:config:default: # default全局的配置loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息httpclient:enabled: true # 开启feign对HttpClient的支持max-connections: 200 # 最大的连接数max-connections-per-route: 50 # 每个路径的最大连接数

总结,Feign的优化:

1.日志级别尽量用basic

2.使用HttpClient或OKHttp代替URLConnection

        ①  引入feign-httpClient依赖

        ②  配置文件开启httpClient功能,设置连接池参数


6.4  最佳实践

所谓最近实践,就是使用过程中总结的经验,最好的一种使用方式。

自习观察可以发现,Feign的客户端与服务提供者的controller代码非常相似

没有一种办法简化这种重复的代码编写呢?
 

方式一: 继承。给消费者的FeignClient和提供者的controller定义统一的父接口作为标准
特点: 紧耦合,仅适用于面向契约编程的思想上来使用
操作: 让controller和FeignClient继承同一接口


方式二: 抽取。 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
特点; 低耦合,但是高冗余
操作: 将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用

演示上面说到的方式二"抽取"的Feign实践,实现步骤:
1、首先创建一个module,命名为feign-api,然后引入feign的starter依赖
2、就可以把order-service中的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
3、要使用怎么办,直接在order-service中引入feign-api的依赖即可
4、然后修改order-service中的所有与"UserClient、User、DefaultFeignConfiguration"有关的Import部分,改成导入feign-api中的包
具体步骤如下:
第一步: 在cloud-demo总项目中新建一个项目,项目名为feign-api


第二步: 把feign-api微服务项目的pom.xml修改为如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>cloud-demo</artifactId><groupId>cn.itcast.demo</groupId><version>1.0</version></parent><modelVersion>4.0.0</modelVersion><artifactId>feign-api</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency></dependencies></project>

第三步: 在feign-api项目的src/main/java目录下新建cn.itcast.feign包,接着把order-service项目的src/main/java/cn.itcast.order/clients目录、config目录、pojo目录复制到feign-api项目的src/main/java/cn.itcast.feign目录里面。注意粘贴后的clients目录的UserClient接口会爆红,改一下import即可


第四步: 以后哪个项目需要使用Feign时,就直接在pom.xml使用刚写好的feign-api项目即可。例如把order-service项目中使用,我们可以把在order-service项目中有关Feign配置的接口和类删掉(此时order-service项目有关Feign的功能类会报错,等下会解决),然后在order-service项目的pom.xml中引入刚写好的feign-api项目依赖,接着在报错的有关Feign功能类里面重新引入一下即可解决报错

<!--引入feign-api项目(自己写的)的依赖-->
<dependency><groupId>cn.itcast.demo</groupId><artifactId>feign-api</artifactId><version>1.0</version>
</dependency>

第五步: 解决bug。当我们启动OrderApplication、UserApplication、UserApplication2服务时,会发现报错了,找不到feign-api项目的UserClient接口。解决方式有两种
第一种解决方式: 在order-service项目的启动类,指定FeignClient所在包,适合于有多个UserClient时,缺点是会把其它用不上的UserClient也加入进来

@EnableFeignClients(basePackage = "cn.itcast.feign.clients")


第二种解决方式: 在order-service项目的启动类,指定FeignClient字节码,也就是直接指定扫描哪个写好的UserClient,也可以指定多个。推荐这种解决方案

@EnableFeignClients(clients = {UserClient.class})


第六步。测试(先确保你的nacos已经启动),重启OrderApplication、UserApplication、UserApplication2服务
浏览器访问: http://localhost:8080/order/103

7.Gateway

8.Docker

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/588714.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

车载电子电器架构 —— 局部网络管理汇总

车载电子电器架构 —— 局部网络管理汇总 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明…

使用Vue实现CSS过渡和动画

01-初识动画和过渡 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>使用vue实现css过渡和动画&l…

搭建 Qt 开发环境

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;QT❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、QT SDK 的下载和安装 1.QT SDK 的下载 二、QT SDK的安装 1、找到下载的文件并双击 2、双击之…

牛客网BC-71 三角形判断(操作符注意事项)

例题如下 这道题的编程很容易实现&#xff0c;但恰恰因为太简单导致容易忘记注意事项 代码如下 #include<stdio.h> int main() {int a 0,b 0,c 0;while(scanf("%d%d%d",&a,&b,&c)!EOF){if(ab>c&&ac>b&&bc>a){ //三…

蓝桥杯 - 玩具蛇

解题思路&#xff1a; dfs public class Main {static final int N 4;static int[][] visited new int[N][N];static int count;public static void main(String[] args) {for (int i 0; i < N; i) { //16种位置开始的可能for (int j 0; j < N; j) {dfs(i, j, 1);}…

深信服:借助观测云实现全链路可观测性

导读 深信服科技股份有限公司 简称「深信服」&#xff08; Sangfor Technologies Inc. &#xff09;&#xff0c;是一家领先的网络安全和云计算解决方案提供商&#xff0c;致力于为全球客户提供高效、智能、安全的网络和云服务。随着公司业务的不断扩展&#xff0c;也面临着监…

线上剧本杀小程序开发,剧本杀行业的发展趋势

剧本杀一时火爆全网&#xff0c;剧本杀门店也是迅速占领了大街小巷&#xff0c;成为年轻人热衷的游戏娱乐方式。 不过&#xff0c;线下剧本杀因为价格高、剧本质量不过关等问题&#xff0c;迎来了“寒冬期”&#xff0c;线下剧本杀门店的发展逐渐“降温”。 随着互联网的发展…

CAS的使用以及底层原理详解

什么是 CAS &#xff1f; CAS 全称 Compare And Swap&#xff0c;翻译为中文是比较并交换&#xff0c;是一种无锁的原子操作&#xff0c;CAS 可以不使用锁来保证多线程修改数据的安全性&#xff0c;虽说是无锁但实际上使用了一种乐观锁的思想&#xff0c;也可以认为 CAS 是乐观…

【Go】十七、进程、线程、协程

文章目录 1、进程、线程2、协程3、主死从随4、启动多个协程5、使用WaitGroup控制协程退出6、多协程操作同一个数据7、互斥锁8、读写锁9、deferrecover优化多协程 1、进程、线程 进程作为资源分配的单位&#xff0c;在内存中会为每个进程分配不同的内存区域 一个进程下面有多个…

QT-自定义参数设计框架软件

QT-自定义参数设计框架软件 前言一、演示效果二、使用步骤1.应用进行参数注册2.数据库操作单例对象3.参数操作单例对象 三、下载链接 前言 常用本地数据参数通常使用的是xml等文本的格式&#xff0c;进行本地的数据参数的存储。这种参数的保存方式有个致命的一点&#xff0c;就…

基于深度学习的机场航拍小目标检测系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)

摘要&#xff1a;在本博客中介绍了基于YOLOv8/v7/v6/v5的机场航拍小目标检测系统。该系统的核心技术是采用YOLOv8&#xff0c;并整合了YOLOv7、YOLOv6、YOLOv5算法&#xff0c;从而进行性能指标的综合对比。我们详细介绍了国内外在机场航拍小目标检测领域的研究现状、数据集处理…

STM32 直接修改寄存器来输出内部时钟的方法

1. 在特殊情况下使能 MCO 功能的方法 在对某些不容易复现的问题进行代码调时&#xff0c;需要观察内部时钟的情况&#xff0c;但往往代码之前并没有使能 MCO 功能&#xff0c;在这种情况下就可以使用寄存器直接配置来输出内部时钟到GPIO 脚位上进行观察和测试。 下面的例子就…