0


SpringCloud学习笔记【尚硅谷2024版】

文章目录

一、笔记内容技术选型

技术版本Javajdk17+boot3.2.0cloud2023.0.0cloud alibaba2022.0.0.0-RC2Maven3.9+MySQL8.0+

二、Spring Cloud介绍

1.为什么需要Spring Cloud?

传统的单体架构足以满足中小型项目的需求,但是如果对于一个用户量庞大的系统就会出现各种问题。

例如:如果只有一个支付系统,那么系统崩溃了整个系统就运作不了了。

而微服务解决了这个问题,它允许系统以集群的形式部署,形成负载均衡,尽量减少系统崩溃带来的问题。

2.相关组件介绍

在2019年之前,使用的大部分技术都是Netflix提供的,但是由于开发SpringCloud的相关技术不挣钱,因此Netflix就暂停开发相关技术了,但是他提供的那些技术依旧可以使用,但是已经不推荐了。

因此该笔记只学习新的架构,对于老的技术栈,如果老项目中需要用到,请去B站继续学习

image-20240529170240440

  • 注册与发现 1. Eureka【Netflix最后的火种,不推荐】2. Consul【推荐使用】3. Etcd4. Nacos【推荐使用,发音:呐扣丝,阿里巴巴提供的】
  • 服务调用和负载均衡 1. Ribbon【Netflix提供的,建议直接弃用】2. OpenFeign3. LoadBalancer
  • 分布式事务 1. Seata【推荐使用,阿里巴巴的】2. LCN3. Hmily
  • 服务熔断和降级 1. Hystrix【已经停更了,不推荐】2. CircuitBreaker【这只一套规范,使用的是它的实现类】3. Resilience4J【CircuitBreaker的实现类,可以使用】4. Sentinel【阿里巴巴的,推荐使用】
  • 服务链路追踪 1. Sleuth【逐渐被替代了,不推荐】2. Micrometer Tracing【推荐使用】
  • 服务网关 1. Zuul【不推荐使用】2. Gate Way
  • 分布式配置管理 1. Config+Bus【不推荐了】2. Consul3. Nacos

三、单体项目构建

需求说明:下订单,调用支付接口

要求:

​ 1.先做一个通用的boot微服务

​ 2.逐步引入cloud组件,最后编程cloud架构

1.SpringBoot单体服务

1.1 项目构建

  1. 新建一个Maven工程,除了pom.xml其他的东西都删了image-20240529171225327
  2. 检查项目的编码格式,统一为UTF-8image-20240529171413352
  3. 检查注解支撑是否打开image-20240529171529930
  4. 检查java编译版本image-20240529171753282
  5. 父工程的pom文件导入依赖,然后刷新<properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><hutool.version>5.8.22</hutool.version><lombok.version>1.18.26</lombok.version><druid.version>1.1.20</druid.version><mybatis.springboot.version>3.0.2</mybatis.springboot.version><mysql.version>8.0.11</mysql.version><swagger3.version>2.2.0</swagger3.version><mapper.version>4.2.3</mapper.version><fastjson2.version>2.0.40</fastjson2.version><persistence-api.version>1.0.2</persistence-api.version><spring.boot.test.version>3.1.5</spring.boot.test.version><spring.boot.version>3.2.0</spring.boot.version><spring.cloud.version>2023.0.0</spring.cloud.version><spring.cloud.alibaba.version>2022.0.0.0-RC2</spring.cloud.alibaba.version></properties><dependencyManagement><dependencies><!--springboot 3.2.0--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>${spring.boot.version}</version><type>pom</type><scope>import</scope></dependency><!--springcloud 2023.0.0--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring.cloud.version}</version><type>pom</type><scope>import</scope></dependency><!--springcloud alibaba 2022.0.0.0-RC2--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-dependencies</artifactId><version>${spring.cloud.alibaba.version}</version><type>pom</type><scope>import</scope></dependency><!--SpringBoot集成mybatis--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis.springboot.version}</version></dependency><!--Mysql数据库驱动8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><!--SpringBoot集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>${druid.version}</version></dependency><!--通用Mapper4之tk.mybatis--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>${mapper.version}</version></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId><version>${persistence-api.version}</version></dependency><!-- fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>${fastjson2.version}</version></dependency><!-- swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>${swagger3.version}</version></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool.version}</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><optional>true</optional></dependency><!-- spring-boot-starter-test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>${spring.boot.test.version}</version><scope>test</scope></dependency></dependencies></dependencyManagement>
  6. 建库建表,表名t_pay``````DROPTABLEIFEXISTS`t_pay`;CREATETABLE`t_pay`(`id`INT(10)UNSIGNEDNOTNULLAUTO_INCREMENT,`pay_no`VARCHAR(50)NOTNULLCOMMENT'支付流水号',`order_no`VARCHAR(50)NOTNULLCOMMENT'订单流水号',`user_id`INT(10)DEFAULT'1'COMMENT'用户账号ID',`amount`DECIMAL(8,2)NOTNULLDEFAULT'9.9'COMMENT'交易金额',`deleted`TINYINT(4)UNSIGNEDNOTNULLDEFAULT'0'COMMENT'删除标志,默认0不删除,1删除',`create_time`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT'创建时间',`update_time`TIMESTAMPNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT'更新时间',PRIMARYKEY(`id`))ENGINE=INNODBAUTO_INCREMENT=1DEFAULTCHARSET=utf8mb4 COMMENT='支付交易表';INSERTINTO t_pay(pay_no,order_no)VALUES('pay17203699','6544bafb424a');SELECT*FROM t_pay;

1.2 MyBatis逆向工程

本次使用Mapper4,可以不用写单表操作了

  1. 在父工程下面创建一个子模块,给子模块导入依赖> 说明:这个工程只是为了暂时存储生成的代码,等到业务工程使用的时候,会将对应的类复制过去<dependencies><!--Mybatis 通用mapper tk单独使用,自己独有+自带版本号--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version></dependency><!-- Mybatis Generator 自己独有+自带版本号--><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.4.2</version></dependency><!--通用Mapper--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--mysql8.0--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><resources><resource><directory>${basedir}/src/main/java</directory><includes><include>**/*.xml</include></includes></resource><resource><directory>${basedir}/src/main/resources</directory></resource></resources><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.2</version><configuration><configurationFile>${basedir}/src/main/resources/generatorConfig.xml</configurationFile><overwrite>true</overwrite><verbose>true</verbose></configuration><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.33</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>4.2.3</version></dependency></dependencies></plugin></plugins></build>
  2. 在子模块的resources下新建文件config.properties,将内容改成自己的#t_pay表包名package.name=com.atguigu.cloud# mysql8.0jdbc.driverClass = com.mysql.cj.jdbc.Driverjdbc.url= jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=truejdbc.user = rootjdbc.password =123456
  3. 在子模块的resources下新建文件generatorConfig.xml``````<?xml version="1.0" encoding="UTF-8"?><!DOCTYPEgeneratorConfigurationPUBLIC"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><propertiesresource="config.properties"/><contextid="Mysql"targetRuntime="MyBatis3Simple"defaultModelType="flat"><propertyname="beginningDelimiter"value="`"/><propertyname="endingDelimiter"value="`"/><plugintype="tk.mybatis.mapper.generator.MapperPlugin"><propertyname="mappers"value="tk.mybatis.mapper.common.Mapper"/><propertyname="caseSensitive"value="true"/></plugin><jdbcConnectiondriverClass="${jdbc.driverClass}"connectionURL="${jdbc.url}"userId="${jdbc.user}"password="${jdbc.password}"></jdbcConnection><javaModelGeneratortargetPackage="${package.name}.entities"targetProject="src/main/java"/><sqlMapGeneratortargetPackage="${package.name}.mapper"targetProject="src/main/java"/><javaClientGeneratortargetPackage="${package.name}.mapper"targetProject="src/main/java"type="XMLMAPPER"/><tabletableName="t_pay"domainObjectName="Pay"><generatedKeycolumn="id"sqlStatement="JDBC"/></table></context></generatorConfiguration>
  4. 双击运行Maven中的插件image-20240529174957727

1.3 编写业务逻辑

  1. 创建一个业务逻辑模块cloud-provider-payment8001
  2. 给模块导入依赖<dependencies><!--SpringBoot通用依赖模块--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--SpringBoot集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!-- Swagger3 调用方式 http://你的主机IP地址:5555/swagger-ui/index.html --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId></dependency><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--Mysql数据库驱动8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><!-- fastjson2 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--test--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
  3. 编写yaml配置文件server:port:8001# ==========applicationName + druid-mysql8 driver===================spring:application:name: cloud-payment-service datasource:type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: root password:123456# ========================mybatis===================mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.cloud.entities configuration:map-underscore-to-camel-case:true
  4. 创建启动类@SpringBootApplication@MapperScan("com.atguigu.cloud.mapper")//import tk.mybatis.spring.annotation.MapperScan;publicclassMain8001{publicstaticvoidmain(String[] args){SpringApplication.run(Main8001.class, args);}}
  5. 将逆向工程生成的代码拷贝到业务工程中,删除原本逆向工程中生成的代码
  6. 编写业务逻辑
  7. 启动项目

1.4 整合Swager3

注解标注位置@TagController类@Operation方法上@Schemamodel层的bean上

  1. 添加依赖<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${swagger3.version}</version></dependency>
  2. Controller加上@Tag注解@Tag(name ="支付模块")
  3. Controller的方法上加@Operation注解@Operation(summary="查询所有订单")
  4. 编写配置类,配置Swaggerimportio.swagger.v3.oas.models.ExternalDocumentation;importio.swagger.v3.oas.models.OpenAPI;importio.swagger.v3.oas.models.info.Info;importorg.springdoc.core.models.GroupedOpenApi;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;@ConfigurationpublicclassSwaggerConfiguration{@BeanpublicGroupedOpenApiPayApi(){//以/pay开头的请求都是支付模块returnGroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();}@BeanpublicGroupedOpenApiOtherApi(){//以/other开头的都是其他模块的请求returnGroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**","/others").build();}@BeanpublicOpenAPIdocsOpenApi(){returnnewOpenAPI().info(newInfo().title("cloud2024").description("通用设计rest").version("v1.0")).externalDocs(newExternalDocumentation().description("www.atguigu.com").url("https://yiyan.baidu.com/"));}}
  5. 启动项目,访问swagger的地址,调试接口localhost:8001/swagger-ui/index.html

1.5 统一返回结果Result

  1. 定义一个枚举类,用于状态码的返回【枚举类的书写方法1.举值2.构造3.遍历】@GetterpublicenumReturnCodeEnum{//1.举值RC999("999","操作XXX失败"),RC200("200","success"),RC201("201","服务开启降级保护,请稍后再试!"),RC202("202","热点参数限流,请稍后再试!"),RC203("203","系统规则不满足要求,请稍后再试!"),RC204("204","授权规则不通过,请稍后再试!"),RC403("403","无访问权限,请联系管理员授予权限"),RC401("401","匿名用户访问无权限资源时的异常"),RC404("404","404页面找不到的异常"),RC500("500","系统异常,请稍后重试"),RC375("375","数学运算异常,请稍后重试"),INVALID_TOKEN("2001","访问令牌不合法"),ACCESS_DENIED("2003","没有权限访问该资源"),CLIENT_AUTHENTICATION_FAILED("1001","客户端认证失败"),USERNAME_OR_PASSWORD_ERROR("1002","用户名或密码错误"),BUSINESS_ERROR("1004","业务逻辑异常"),UNSUPPORTED_GRANT_TYPE("1003","不支持的认证模式");//2.构造privatefinalString code;//自定义状态码,对应前面枚举的第一个参数privatefinalString message;//自定义信息,对应前面枚举的第二个参数ReturnCodeEnum(String code,String message){this.code = code;this.message = message;}//3.遍历publicstaticReturnCodeEnumgetReturnCodeEnum(String code){//传入一个状态码,如果有,就返回整个枚举信息,如果没有就返回空for(ReturnCodeEnum element :ReturnCodeEnum.values()){if(element.getCode().equalsIgnoreCase(code)){return element;}}returnnull;}}
  2. 定义统一返回类Result@Data@Accessors(chain =true)publicclassResultData<T>{privateString code;privateString message;privateT data;privatelong timestamp;//调用方法的时间戳publicResultData(){this.timestamp =System.currentTimeMillis();}publicstatic<T>ResultData<T>success(T data){ResultData<T> resultData =newResultData<>(); resultData.setCode(ReturnCodeEnum.RC200.getCode()); resultData.setMessage(ReturnCodeEnum.RC200.getMessage()); resultData.setData(data);return resultData;}publicstatic<T>ResultData<T>fail(String code,String message){ResultData<T> resultData =newResultData<>(); resultData.setCode(code); resultData.setMessage(message);return resultData;}}
  3. 修改原来接口的返回值@Operation(summary ="添加支付记录")@PostMapping(value ="/pay/add")publicResultData<String>addPay(@RequestBodyPay pay){System.out.println(pay.toString());int add = payService.add(pay);returnResultData.success("添加成功"+add+"条记录");}

1.6 优化时间格式

有两种解决方式:

  • 方式一:在实体类的时间属性上加@JsonFormat注解@JsonFormat(pattern ="yyyy-MM-dd HH-mm-ss",timezone ="GMT+8")privateDate createTime;
  • 方式二:SpringBoot项目在yml中进行配置spring:jackson:date-format: yyyy-MM-dd HH-mm-ss time-zone: GMT+8

1.7 异常处理

@RestControllerAdvicepublicclassGlobalExceptionHandler{//注解的参数是处理的异常信息类型,什么都不加就是全局异常处理//@ExceptionHandler(SQlException.class)这个就是专门处理sql异常@ExceptionHandler()publicResultData<String>globalException(Exception e){
        e.printStackTrace();returnResultData.fail(ReturnCodeEnum.RC500.getCode(),ReturnCodeEnum.RC500.getMessage());}}

1.8 编写订单模块【模块构建参考上述步骤】

这个模块的controller使用http请求调用pay模块的方法就行。

因此将entities、utils包中的代码复制过去即可,然后编写controller。

@RestControllerpublicclassOrderController{privateString url="http://localhost:8001";//使用httpclient调用pay模块的相关接口@GetMapping("/consumer/pay/add")publicResultDataaddOrder(PayDTO payDTO)throwsIOException{//创建httpclient客户端CloseableHttpClient aDefault =HttpClients.createDefault();//创建一个post请求HttpPost httpPost =newHttpPost(url+"/pay/add");//将本方法的参收构建为json字符串String jsonString =JSON.toJSONString(payDTO);//将json字符串构建为StringEntityStringEntity stringEntity =newStringEntity(jsonString);//设置请求头和编码格式
        stringEntity.setContentType("application/json");
        stringEntity.setContentEncoding("UTF-8");//将参数传入post请求
        httpPost.setEntity(stringEntity);//httpclient客户端执行请求CloseableHttpResponse execute = aDefault.execute(httpPost);//获取响应实体HttpEntity entity = execute.getEntity();//将实体转化为json字符串String string =EntityUtils.toString(entity);//将字符串转化为json对象JSONObject jsonObject =JSON.parseObject(string);//从对象中获取对应的参数String code =(String) jsonObject.get("code");String data =(String) jsonObject.get("data");if(code.equals("200")){returnResultData.success("调用成功data="+data);}else{returnResultData.fail(code,(String) jsonObject.get("message"));}}@GetMapping("/consumer/pay/get/{id}")publicResultDatagetPayInfo(@PathVariable("id")Integer id)throwsIOException{CloseableHttpClient aDefault =HttpClients.createDefault();HttpGet httpGet =newHttpGet(url+"/pay/get/"+id);CloseableHttpResponse execute = aDefault.execute(httpGet);HttpEntity entity = execute.getEntity();String string =EntityUtils.toString(entity);JSONObject jsonObject =JSON.parseObject(string);String code =(String) jsonObject.get("code");Object data = jsonObject.get("data");if(code.equals("200")){returnResultData.success(data);}else{returnResultData.fail(code,(String) jsonObject.get("message"));}}}

1.9 重复代码抽取

问题:两个模块中有很多重复的代码。例如实体类、返回结果、异常处理类等。

解决方法:将公共代码抽取到一个模块中,其他模块引用公共模块

  1. 创建一个模块cloud-api-commons,引入依赖<dependencies><!--SpringBoot通用依赖模块--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency></dependencies>
  2. 将前面两个模块中的公共代码抽取出来放到这个新的模块中> 例如:entities包、utils包、exception包
  3. 然后前面连个模块在pom文件中也纳入公共模块<dependency><groupId>com.atguigu.cloud</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</version></dependency>
  4. 启动项目,测试功能

2.问题引入

问题:为什么一定要引入SpringCloud?

回答:我们刚才那样将每一个模块拆成一个个微服务之后,使用http调用起来很麻烦,而且地址是写死的。如果我们的项目地址变了,我们的代码不得不修改。而且后面如果每个模块以集群部署,那地址该怎么写呢?

四、Consul

1.基本介绍

  • Consul是什么?Consul是一款开源的分布式服务发现与配置管理系统,由HashiCorp公司使用Go语言开发。官方:http://consul.io/
  • Consul能干什么? 1. 服务发现:提供HTTP和DNS两种发现方式2. 健康检测3. KV存储4. 多数据中心5. 可视化WEB界面
  • 为什么不使用Eureka了? 1. Eureka停更了,不在开发新版本了2. Eureka对初学者不友好3. 我们希望注册中心能够从项目中分离出来,单独运行,而Eureka做不到这一点

2.下载运行

  1. 下载地址:https://developer.hashicorp.com/consul/install
  2. 下载对应的版本【adm64版本的就是x86_64版本的,386就是x86_32版本的】
  3. windows使用下面的命令启动,然后缩放到最小化就行consul agent -dev
  4. 访问8500端口,进入ui界面localhost:8500

3.服务注册与发现

需求说明:将前面单体服务中的支付模块、订单模块注册到Consul中

  1. 对应模块的pom文件中引入依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency>
  2. 编写配置文件yamlspring:#配置服务名application:name: cloud-payment-service cloud:#配置注册中心的地址consul:host: localhost port:8500discovery:#配置当前服务注册到里面使用的名字service-name: ${spring.application.name}
  3. 启动类加上@EnableDiscoveryClient注解,开启服务发现功能@EnableDiscoveryClient
  4. 启动boot项目
  5. 去consul的ui页面查看是否注册成功
  6. 将订单接口中支付模块的url地址改为consul中注册的名字privateString url="http://cloud-payment-service";
  7. 因为consul默认支持负载均衡,所以http客户端加上@LoadBalanced注解//说明:上面项目使用的是httpclient,但是我不知道怎么配,因此这里该长城restTemplate客户端了@Bean@LoadBalancedpublicRestTemplaterestTemplate(){returnnewRestTemplate();}
  8. 测试接口调用是否成功

4.服务配置

问题说明

系统拆分之后,会产生大量的微服务。每个微服务都有其对应的配置文件yml。如果其中的某个配置项发生了修改,一个一个微服务修改会很麻烦。因此一套集中式的、动态的配置管理设施是必不可少的。从而实现一次修改,处处生效。

案例:给班里同学通知下节课不上了

麻烦的方法:一个个发送消息

简单的方法:直接在班级群@所有人

思路:既然是全局配置信息,那么可以把信息注册到Consul中,需要什么去Consul中获取

  1. 给对应的模块添加服务配置的依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-consul-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId></dependency>
  2. 在resources下新建一个bootstrap.yml文件,将公共配置从application.yml中抽取出来> 说明:> > > 1. bootstrap.yml和applicaiton.yml一样都是配置文件。applicaiton.yml是用户级的,bootstrap.yml是系统级的,优先级更加高> 2. Spring Cloud会创建一个“Bootstrap Context”,作为Spring应用的Application Context父上下文。初始化的时候,Bootstrap Context负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment。> 3. Bootstrap属性有高优先级,默认情况下,它们不会被本地配置覆盖。> 4. application.yml和bootstrap.yml可以共存,公共的配置项写到bootstrap.yml中,项目特有的配置项写到application.yml> 5. bootstrap.yml比application.yml先加载的spring:application:name: cloud-payment-service cloud:consul:host: localhost port:8500discovery:service-name: ${spring.application.name}config:#这个是配置文件名以-连接【consul的k-v存储用到】,例如:cloud-payment-serviceprofile-separator:'-'format: YAML
  3. application.yml就只剩下没有抽取出去的配置了#剩下的配置server:port:8001spring:datasource:type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db2024?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: root password:123456profiles:active: dev # 多环境配置加载内容,不写就是默认default配置mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.atguigu.cloud.entities configuration:map-underscore-to-camel-case:true
  4. 打开consul的ui界面,找到key-value,点击右上角的create,创建文件夹image-20240531022245351
  5. 在consul中创建三个文件夹以及文件,供项目测试是否能够读取> 说明:配置默认存储到config/微服务名-配置文件后缀名/data中,项目启动的时候使用的哪套application.yaml文件就会来这里找对应的文件> > 例如:cloud-payment-service微服务如果在application.yml指定启用的配置文件是application-dev.yml> > config/cloud-payment-service-dev/data> > 例如:cloud-payment-service微服务如果在application.yml没有指定启用的配置文件> > config/cloud-payment-service/data> > 例如:cloud-payment-service微服务如果在application.yml指定启用的配置文件是application-prod.yml> > config/cloud-payment-service-prod/dataimage-20240531023026068image-20240531023205025
  6. 创建data文件,随便输入几个值,测试项目是否能读取image-20240531023911248
  7. 编写代码,查看项目能否读取到consul中的k-v值//从application.yml中获取@Value("${server.port}")privateString port;@GetMapping(value ="/pay/get/consul")//从consul中获取publicResultDatagetConsul(@Value("${altman.info}")String info){returnResultData.success(info+"当前端口号"+port);}

5.动态刷新

需求说明:希望Consul的配置变动之后,项目读取的内容也能立马改变

  1. 在主启动类加上@RefreshScope注解【如果不生效,就放到controller上】
  2. 然后在bootstrap.yml中设置刷新的间隔【这一步不设置也可以,因为官网默认设置了1s刷新】spring:application:name: cloud-payment-service cloud:consul:host: localhost port:8500discovery:service-name: ${spring.application.name}config:profile-separator:'-'format: YAML #设置了这里,1s刷新watch:wait-time:1

6.配置数据持久化

场景:如果我们把Consul的运行窗口关了,下次启动的时候,之前配置的yaml数据就会全丢了。我们现在需要解决这个问题。

他讲的不行,以后在写这里

六、LoadBlancer

1.基本介绍

LoadBlancer的前身是Ribbon,是一套负责负载均衡的客户端工具。

主要功能:目前,LoadBlancer的主要作用就是提供客户端软件的负载均衡,然后由OpenFeign去调用具体的微服务

  • 负载均衡:通过算法,将请求平均分摊到多个服务上

2.基本使用

场景:订单模块通过负载均衡访问支付模块的8001/8002/8003服务

使用步骤:

  1. 先从注册中心拉取可调用的服务列表,了解他有多少个服务
  2. 按照指定的负载均衡策略,从服务列表中选择一个地址,进行调用
  1. 使用前提:已经使用了注册中心
  2. 启动两个支付模块的项目【为了方便,就不启动三个了】
  3. 因为spring-cloud-starter-consul-discovery 中已经集成了spring-cloud-starter-loadbalancer,所以不需要额外加注解了如果没有loadbalancer的依赖,那就自己加上
  4. 在订单模块的RestTemplate客户端上加@LoadBalanced,开启负载均衡RestTemplate和WebClient支持使用@LoadBalanced注解实现负载均衡,而HttpClient不支持使用@LoadBalanced注解实现负载均衡
  5. 将调用的url改成在注册中心注册的名称public static final String PaymentSrv_URL = "http://cloud-payment-service";
  6. 测试接口

3.基本原理

  1. 会在项目中创建一个DiscoveryClient对象
  2. 通过DiscoveryClient对象,就能够获取注册中心中所有注册的服务
  3. 然后将获取的服务与调用地址中传入的微服务名称进行对比
  4. 如果一致,就会微服务集群的相关信息返回
  5. 然后通过负载均衡算法,选择出一个进行调用

4.负载均衡算法

LoadBlancer默认包含两种负载均衡算法,轮询算法和随机算法,同时还可以自定义负载均衡算法。默认使用轮询算法。

  • 轮询算法【LoadBlancer默认使用这个】实际调用服务器位置下标=rest接口第几次请求数 % 服务器集群总数量【每次服务重启动后rest接口计数从1开始】如: List[0] instances =127.0.0.1:8002List[1] instances =127.0.0.1:80018001+8002 组合成为集群,它们共计2台机器,集群总数为2, 按照轮询算法原理:当总请求数为1时: 1%2=1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001当总请求数位2时: 2%2=0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002当总请求数位3时: 3%2=1 对应下标位置为1 ,则获得服务地址为127.0.0.1:8001当总请求数位4时: 4%2=0 对应下标位置为0 ,则获得服务地址为127.0.0.1:8002如此类推......
  • 随机算法【LoadBlancer中也包含】随机给一个数,然后请求下标对应的微服务
  • 支持自定义负载均衡算法

5.负载均衡算法切换

@Configuration//下面的value值大小写一定要和consul里面的名字一样,必须一样//value的只是指定对哪个微服务生效@LoadBalancerClient(value ="cloud-payment-service",configuration =RestTemplateConfig.class)publicclassRestTemplateConfig{@Bean@LoadBalancedpublicRestTemplaterestTemplate(){returnnewRestTemplate();}@BeanReactorLoadBalancer<ServiceInstance>randomLoadBalancer(Environment environment,LoadBalancerClientFactory loadBalancerClientFactory){String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);//这里切换成了随机算法returnnewRandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name,ServiceInstanceListSupplier.class), name);}}

七、OpenFeign

1.基本介绍

OpenFeign编写了一套声明式的Web服务客户端,使用LoadBlancer实现负载均衡,从而使WEB服务的调用变得很简单。

OpenFeign已经是当前微服务调用最常用的技术

2.能干什么

前面的LoadBalancer章节,我们在使用LoadBalancer+RestTemplate实现了微服务的负载均衡调用,但是在实际开发中,一个接口往往会被多处调用,这就需要多次定义重复的代码,而OpenFeign简化了这个过程。

3.基本使用

  1. 引入OpenFeign和LoadBlancer的依赖> 哪个服务需要调用其他服务的接口,就在哪个服务中引用【例如:订单服务调用支付服务的接口,就在订单服务中引入依赖】> > 引入LoadBlancer的依赖,是因为它使用LoadBlancer实现负载均衡<!--openFeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--loadbalancer做负载均衡--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>
  2. 启动类加上@EnableFeignClients注解,启动OpenFeign功能//如果FeignClient不在SpringBootApplication的扫描范围内可以在@EnableFeignClients中指定扫描范围@EnableFeignClients(basePackages="com.atguigu.cloud")
  3. 在项目中创建一个api包,专门存放OpenFegin接口这里以订单模块调用支付模块的接口为例,因此在订单模块中创建
  4. 创建OpenFeign的接口,加上@FeignClient注解,注解的值就是被调用微服务的name//例如:被调用的模块是支付模块,支付模块在注册中心的名字叫cloud-payment-service@FeignClient("cloud-payment-service")publicinterfacePayFeignApi{}
  5. 编写接口中的方法@FeignClient("cloud-payment-service")publicinterfacePayFeignApi{//方法上的注解就是被调用方法的请求类型和地址//这样他就合成了http://cloud-payment-service/pay/getall@GetMapping("/pay/getall")//这里的返回值需要和被调用接口的返回值一致ResultDatagetOrders();}
  6. controller中注入feign接口对象,然后在需要的地方调用feign接口的方法//注入feign对象@AutowiredprivatePayFeignApi payFeignApi;@GetMapping("/feign/pay/getall")publicResultDatagetPayInfo(){ResultData orders = payFeignApi.getOrders();List<Pay> payList =(List<Pay>) orders.getData();returnResultData.success(payList);}

4.最佳实践

上面的基本使用步骤只是基本用法,他暴露了几个基本问题。

如果某个接口需要在不同的微服务中被多次调用,那我们上面的这个写法就需要写多次,从而造成代码的冗余。

因此我们可以把所有的Feign接口抽取成一个公共的模块,然后其他模块引入这个Feign模块调用它里面的方法

  1. 在项目中创建一个模块
  2. 添加openfeign的依赖和公共模块的依赖【@FeignClient注解需要Feign依赖】<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
  3. 创建包,创建接口,编写接口中的方法
  4. 其他模块引用这个模块,调用模块中的方法

5.超时控制

问题引入:比较简单的业务使用默认配置是没有问题的,但是如果是复杂业务需要进行很多操作,就可能会出现Read Timeout异常。因此学习定制化超时时间是有必要的

  • OpenFeign客户端的默认等待时间60S,超过这个时间就会报错(这个时间太长了,我们应该设置短一点)

通过两个参数控制超时时间:

  • connectTimeout:连接超时时间【多长时间内必须建立链接】
  • readTimeout:请求处理超时时间【多长时间内必须处理完成】【默认60S】

5.1 全局配置

全局配置能直接控制所有的Feign超时时间

直接修改yaml文件

spring:cloud:openfeign:client:config:default:#指定超时时间最大:3Sread-timeout:3000#指定连接时间最大:3Sconnect-timeout:3000

5.2 指定配置

指定配置能够控制指定微服务的接口超时时间。

如果全局配置和指定配置同时存在,指定配置生效

spring:cloud:openfeign:client:config:#这里将default换成微服务的名称cloud-payment-service:#指定超时时间最大:3Sread-timeout:3000#指定连接时间最大:3Sconnect-timeout:3000

6.重试机制

超时之后不会直接结束请求,而是会重新尝试连接

重试机制默认是关闭的,如何开启呢?只需要编写一个配置类,配置Retryer对象

//1.创建一个配置类@ConfigurationpublicclassRetryerConfig{//2.配置Retryer@BeanpublicRetryerretryer(){//3,设置重试机制//return Retryer.NEVER_RETRY;这个是默认的//第一个参数是多长时间后开启重试机制:这里设置100ms//第二个参数是重试的间隔:这里设置1s一次//第三个参数是最大请求次数:3次【这个次数是一共的,也就是最大请求几次,而不是第一次请求失败后再请求几次】returnnewRetryer.Default(100,1,3);}}

7.连接池

OpenFeign允许指定连接方式,但是默认方式使用jdk自带的HttpURLConnection,但是HttpURLConnection不支持连接池,因此性能较低。

HttpClient和OkHttp都支持连接池,因此为了提升OpenFeign的性能,可以改成使用HttpClient5

  1. 引入HttpClient5和Feign-hc5依赖<!-- httpclient5--><dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifactId><version>5.3</version></dependency><!-- feign-hc5--><dependency><groupId>io.github.openfeign</groupId><artifactId>feign-hc5</artifactId><version>13.1</version></dependency>
  2. 在配置文件中开启hc5spring:cloud:openfeign:httpclient:hc5:enabled:true

8.请求/响应压缩

OpenFeign支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗

spring:cloud:openfeign:compression:request:#开启请求压缩enabled:true#达到多大才触发压缩min-request-size:2048#触发压缩的类型mime-types: types=text/xml,application/xml,application/json
        response:#开启响应压缩enabled:true

9.日志打印

OpenFeign需要输出日志需要符合两个条件:

  1. FeignClient所在的包日志级别为debug
  2. Feign的日志级别在NONE以上
Feign的日志级别:
    NONE:不记录任何日志信息,这是默认值。
    BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
    HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
    FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
  1. 定义一个类定义Feign的日志级别publicclassDefaultFeignConfig{@BeanpublicLogger.LevelfeignLogLevel(){returnLogger.Level.FULL;//日志级别}}
  2. 配置文件中设置feign所在包的打印级别logging:level:#下面是feign接口的包com:atguigu:cloud:apis:PayFeignApi: debug

https://www.bilibili.com/video/BV1gW421P7RD/?p=43&spm_id_from=pageDriver&vd_source=b246a40ef435cdf32c518bf3f296775d

五、面试题

1.常见的注册中心和他们的特点

常见的注册中心:Eureka、Consul、Zookeeper、Nacos

image-20240531013700815

Eureka:保证数据的可用性和容错性。为了保证高可用,牺牲了一定程度的数据一致性,这意味着服务列表可能不是实时准确的。同时不支持配置中心

Consul:在设计上更倾向于提供一致性和分区容错性。强一致性模型在某些情况下可能导致更高的延迟,尤其是在写操作频繁或网络状况不佳时。支持配置中心

Zookeep:类似Consul,Zookeeper也实现了CP原则。

2.客户端负载均衡和服务器负载均衡有什么区别?

Nging是服务器负载均衡,所有请求都交给Nginx,由Nginx决定去访问哪个服务器的接口【类似于中介】

LoadBlancer是服务端负载均衡,他在本地自己决定调用哪个服务器的接口【没有中间商赚差价】


本文转载自: https://blog.csdn.net/qq_64225133/article/details/139359850
版权归原作者 君擎 所有, 如有侵权,请联系我们删除。

“SpringCloud学习笔记【尚硅谷2024版】”的评论:

还没有评论