文章目录
一、笔记内容技术选型
技术版本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站继续学习
- 注册与发现 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 项目构建
- 新建一个Maven工程,除了pom.xml其他的东西都删了
- 检查项目的编码格式,统一为UTF-8
- 检查注解支撑是否打开
- 检查java编译版本
- 父工程的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>
- 建库建表,表名
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,可以不用写单表操作了
- 在父工程下面创建一个子模块,给子模块导入依赖> 说明:这个工程只是为了暂时存储生成的代码,等到业务工程使用的时候,会将对应的类复制过去
<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>
- 在子模块的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
- 在子模块的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>
- 双击运行Maven中的插件
1.3 编写业务逻辑
- 创建一个业务逻辑模块
cloud-provider-payment8001
- 给模块导入依赖
<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>
- 编写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
- 创建启动类
@SpringBootApplication@MapperScan("com.atguigu.cloud.mapper")//import tk.mybatis.spring.annotation.MapperScan;publicclassMain8001{publicstaticvoidmain(String[] args){SpringApplication.run(Main8001.class, args);}}
- 将逆向工程生成的代码拷贝到业务工程中,删除原本逆向工程中生成的代码
- 编写业务逻辑
- 启动项目
1.4 整合Swager3
注解标注位置@TagController类@Operation方法上@Schemamodel层的bean上
- 添加依赖
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>${swagger3.version}</version></dependency>
- Controller加上@Tag注解
@Tag(name ="支付模块")
- Controller的方法上加@Operation注解
@Operation(summary="查询所有订单")
- 编写配置类,配置Swagger
importio.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/"));}}
- 启动项目,访问swagger的地址,调试接口
localhost:8001/swagger-ui/index.html
1.5 统一返回结果Result
- 定义一个枚举类,用于状态码的返回【枚举类的书写方法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;}}
- 定义统一返回类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;}}
- 修改原来接口的返回值
@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 重复代码抽取
问题:两个模块中有很多重复的代码。例如实体类、返回结果、异常处理类等。
解决方法:将公共代码抽取到一个模块中,其他模块引用公共模块
- 创建一个模块
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>
- 将前面两个模块中的公共代码抽取出来放到这个新的模块中> 例如:entities包、utils包、exception包
- 然后前面连个模块在pom文件中也纳入公共模块
<dependency><groupId>com.atguigu.cloud</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</version></dependency>
- 启动项目,测试功能
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.下载运行
- 下载地址:https://developer.hashicorp.com/consul/install
- 下载对应的版本【adm64版本的就是x86_64版本的,386就是x86_32版本的】
- windows使用下面的命令启动,然后缩放到最小化就行
consul agent -dev
- 访问8500端口,进入ui界面
localhost:8500
3.服务注册与发现
需求说明:将前面单体服务中的支付模块、订单模块注册到Consul中
- 对应模块的pom文件中引入依赖
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-consul-discovery</artifactId></dependency>
- 编写配置文件yaml
spring:#配置服务名application:name: cloud-payment-service cloud:#配置注册中心的地址consul:host: localhost port:8500discovery:#配置当前服务注册到里面使用的名字service-name: ${spring.application.name}
- 启动类加上
@EnableDiscoveryClient
注解,开启服务发现功能@EnableDiscoveryClient
- 启动boot项目
- 去consul的ui页面查看是否注册成功
- 将订单接口中支付模块的url地址改为consul中注册的名字
privateString url="http://cloud-payment-service";
- 因为consul默认支持负载均衡,所以http客户端加上
@LoadBalanced
注解//说明:上面项目使用的是httpclient,但是我不知道怎么配,因此这里该长城restTemplate客户端了@Bean@LoadBalancedpublicRestTemplaterestTemplate(){returnnewRestTemplate();}
- 测试接口调用是否成功
4.服务配置
问题说明:
系统拆分之后,会产生大量的微服务。每个微服务都有其对应的配置文件yml。如果其中的某个配置项发生了修改,一个一个微服务修改会很麻烦。因此一套集中式的、动态的配置管理设施是必不可少的。从而实现一次修改,处处生效。
案例:给班里同学通知下节课不上了
麻烦的方法:一个个发送消息
简单的方法:直接在班级群@所有人
思路:既然是全局配置信息,那么可以把信息注册到Consul中,需要什么去Consul中获取
- 给对应的模块添加服务配置的依赖
<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>
- 在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
- 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
- 打开consul的ui界面,找到key-value,点击右上角的create,创建文件夹
- 在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/data
- 创建data文件,随便输入几个值,测试项目是否能读取
- 编写代码,查看项目能否读取到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的配置变动之后,项目读取的内容也能立马改变
- 在主启动类加上
@RefreshScope
注解【如果不生效,就放到controller上】 - 然后在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服务
使用步骤:
- 先从注册中心拉取可调用的服务列表,了解他有多少个服务
- 按照指定的负载均衡策略,从服务列表中选择一个地址,进行调用
- 使用前提:已经使用了注册中心
- 启动两个支付模块的项目【为了方便,就不启动三个了】
- 因为spring-cloud-starter-consul-discovery 中已经集成了spring-cloud-starter-loadbalancer,所以不需要额外加注解了
如果没有loadbalancer的依赖,那就自己加上
- 在订单模块的RestTemplate客户端上加
@LoadBalanced
,开启负载均衡RestTemplate和WebClient支持使用@LoadBalanced注解实现负载均衡,而HttpClient不支持使用@LoadBalanced注解实现负载均衡
- 将调用的url改成在注册中心注册的名称
public static final String PaymentSrv_URL = "http://cloud-payment-service";
- 测试接口
3.基本原理
- 会在项目中创建一个DiscoveryClient对象
- 通过DiscoveryClient对象,就能够获取注册中心中所有注册的服务
- 然后将获取的服务与调用地址中传入的微服务名称进行对比
- 如果一致,就会微服务集群的相关信息返回
- 然后通过负载均衡算法,选择出一个进行调用
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.基本使用
- 引入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>
- 启动类加上@EnableFeignClients注解,启动OpenFeign功能
//如果FeignClient不在SpringBootApplication的扫描范围内可以在@EnableFeignClients中指定扫描范围@EnableFeignClients(basePackages="com.atguigu.cloud")
- 在项目中创建一个api包,专门存放OpenFegin接口
这里以订单模块调用支付模块的接口为例,因此在订单模块中创建
- 创建OpenFeign的接口,加上
@FeignClient
注解,注解的值就是被调用微服务的name//例如:被调用的模块是支付模块,支付模块在注册中心的名字叫cloud-payment-service@FeignClient("cloud-payment-service")publicinterfacePayFeignApi{}
- 编写接口中的方法
@FeignClient("cloud-payment-service")publicinterfacePayFeignApi{//方法上的注解就是被调用方法的请求类型和地址//这样他就合成了http://cloud-payment-service/pay/getall@GetMapping("/pay/getall")//这里的返回值需要和被调用接口的返回值一致ResultDatagetOrders();}
- 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模块调用它里面的方法
- 在项目中创建一个模块
- 添加openfeign的依赖和公共模块的依赖【@FeignClient注解需要Feign依赖】
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
- 创建包,创建接口,编写接口中的方法
- 其他模块引用这个模块,调用模块中的方法
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
- 引入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>
- 在配置文件中开启hc5
spring: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需要输出日志需要符合两个条件:
- FeignClient所在的包日志级别为debug
- Feign的日志级别在NONE以上
Feign的日志级别:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
- 定义一个类定义Feign的日志级别
publicclassDefaultFeignConfig{@BeanpublicLogger.LevelfeignLogLevel(){returnLogger.Level.FULL;//日志级别}}
- 配置文件中设置feign所在包的打印级别
logging:level:#下面是feign接口的包com:atguigu:cloud:apis:PayFeignApi: debug
五、面试题
1.常见的注册中心和他们的特点
常见的注册中心:Eureka、Consul、Zookeeper、Nacos
Eureka:保证数据的可用性和容错性。为了保证高可用,牺牲了一定程度的数据一致性,这意味着服务列表可能不是实时准确的。同时不支持配置中心
Consul:在设计上更倾向于提供一致性和分区容错性。强一致性模型在某些情况下可能导致更高的延迟,尤其是在写操作频繁或网络状况不佳时。支持配置中心
Zookeep:类似Consul,Zookeeper也实现了CP原则。
2.客户端负载均衡和服务器负载均衡有什么区别?
Nging是服务器负载均衡,所有请求都交给Nginx,由Nginx决定去访问哪个服务器的接口【类似于中介】
LoadBlancer是服务端负载均衡,他在本地自己决定调用哪个服务器的接口【没有中间商赚差价】
版权归原作者 君擎 所有, 如有侵权,请联系我们删除。