1、新建项目
我们用maven管理项目
第一步:选择maven
第二步:项目命名,项目路径
第三步:进入项目,把src文件夹删掉(不删也没事,主要是用不到这个文件夹)
2、引入项目依赖
在父项目pom文件加入下面依赖。
注:springboot项目版本与cloud版本有一定的对应关系,在官网可以看到,下面是对应的版本关系,选择合适的即可。
本项目所用依赖版本:
<?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"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>project_springcloud</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><!--springboot版本--><spring.boot.version>2.3.8.RELEASE</spring.boot.version><!--springcloud版本--><spring.cloud.version>Hoxton.SR10</spring.cloud.version><!--springcloudalibaba版本--><spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version></properties><!-- 管理依赖的版本 --><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring.boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring.cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
</project>
注意:
- 父工程dependencyManagement中管理的依赖在你们仓库中可能没有,所以要注释dependencyManagement先下载对应版本的依赖
- dependencyManagement只是声明依赖,并不实现引入,因此子项目需要显示声明需要用的依赖
3、Nacos服务注册和发现
3.1、Nacos下载与启动
模拟生产环境,采用Linux部署
第一步:官网下载nacos(Nacos | Nacos),下载不了的可以关注公众号【JavaCoding】回复nacos获取
第二步:进入bin文件,修改startup.sh配置文件,下载的默认启动配置需要的内存太大,改小一点
第三步:命令启动sh startup.sh -m standalone,注:这里standalone参数代表着单机模式运行,非集群模式
控制台输出这样的内容代表启动成功,我们打开start.out日志查看内容:
3.2、服务注册
第一步:新建一个服务service1
要在父级目录下创建service1项目,具体步骤与上述创建父项目类似,过程省略,创建后的项目结构如下:
pom依赖:
<?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>project_springcloud</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>service1</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>${spring.boot.version}</version></dependency><!--nacos服务注册--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId><version>${spring.cloud.alibaba.version}</version><exclusions><exclusion><artifactId>nacos-client</artifactId><groupId>com.alibaba.nacos</groupId></exclusion></exclusions></dependency><!--nacos配置中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>${spring.cloud.alibaba.version}</version><exclusions><exclusion><artifactId>nacos-client</artifactId><groupId>com.alibaba.nacos</groupId></exclusion></exclusions></dependency><!--单独指定nacos client版本(自带的版本会在控制台一直输出日志)--><dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId><version>1.4.2</version></dependency></dependencies></project>
第二步:登录我们启动的nacos地址,新建一个命名空间
第三步:点击配置列表右侧加号,新建一个配置
第四步:yml中配置(注意这里项目中配置文件名一定要是bootstrap)
spring:application:name: service1cloud:nacos:discovery:# 你的服务器ipserver-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaconfig:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaprefix: service1group: DEFAULT_GROUPfile-extension: yaml
最后启动项目:
我们可以看到,项目端口是用的我们nacos里配置里写好的端口
点开nacos服务列表:
我们可以看到我们启动的服务已经注册进来了,这里的服务名就是我们项目里spring.application.name配置的名
最后接口测试:
注意:
我们这里的启动类并没有加@EnableDiscoveryClient注解,是因为从Spring Cloud Edgware开始,这个注解可以省略。但是maven依赖中必须要有spring-cloud-starter-alibaba-nacos-discovery服务注册的依赖。
但是值得注意的是,如果使用了Eureka作为注册中心,那么需要使用@EnableEurekaClient注解来启动Eureka客户端。因此,是否需要省略@EnableDiscoveryClient注解取决于你的具体需求和使用的服务注册中心类型。
4、Gateway网关服务
4.1、项目引入
第一步:新建Gateway项目,过程省略,项目结构如下
第二步:引入pom依赖
<?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>project_springcloud</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>gateway</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><!--gateway依赖--><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><version>${spring.cloud.alibaba.version}</version><exclusions><exclusion><artifactId>nacos-client</artifactId><groupId>com.alibaba.nacos</groupId></exclusion></exclusions></dependency><!--nacos配置中心--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>${spring.cloud.alibaba.version}</version><exclusions><exclusion><artifactId>nacos-client</artifactId><groupId>com.alibaba.nacos</groupId></exclusion></exclusions></dependency><!--单独指定nacos client版本(自带的版本会在控制台一直输出日志)--><dependency><groupId>com.alibaba.nacos</groupId><artifactId>nacos-client</artifactId><version>1.4.2</version></dependency></dependencies></project>
第三步:nacos新增配置文件,与service1一样,端口我们设置为8002
第四步:配置文件
spring:application:name: gatewaycloud:nacos:discovery:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaconfig:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaprefix: gatewaygroup: DEFAULT_GROUPfile-extension: yaml
第五步:启动项目
在nacos里可以看到我们的Gateway服务已经注册进来了
4.2、路由配置
上面步骤我们只是把Gateway服务启动,并没有体现出Gateway的作用,我们想要看到的是通过访问不通的接口,Gateway可以帮我自动找到到相应的服务。
简单介绍Gateway配置项:
路由(Route):由ID、目标URI、断言集合和过滤器集合组成。如果聚合断言结果为真,则转发到该路由。
(1)id:路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)
(2)uri:请求最终被转发到的目标地址
(3)order: 路由优先级,数字越小,优先级越高
(4)predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中
(5)filters:过滤器数组,在请求传递过程中,对请求做一些修改
断言(Predicate):参照 Java8 的新特性Predicate,允许开发人员匹配 HTTP 请求中的任何内容,比如请求头或请求参数,最后根据匹配结果返回一个布尔值。
Predicate 来自于 Java8 的接口。Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
Predicate 可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。Spring Cloud Gateway 内置了许多 Predict,这些 Predict 的源码在 org.springframework.cloud.gateway.handler.predicate 包中,有兴趣可以阅读一下。
过滤器(Filter):可以在返回请求之前或之后修改请求和响应的内容。
Gateway 过滤器的生命周期:
PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Gateway 过滤器从作用范围可分为两种:
GatewayFilter:应用到单个路由或者一个分组的路由上(需要在配置文件中配置)
GlobalFilter:应用到所有的路由上(无需配置,全局生效)
有两种方式可以实现Gateway路由配置:
准备工作:我们先按照service1服务创建一个service2服务,端口用8003,创建后的项目架构:
service1和service2接口方法如下:
第一种:yml配置
在Gateway项目的yml中加入以下配置:
spring:application:name: gatewaycloud:nacos:discovery:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaconfig:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaprefix: gatewaygroup: DEFAULT_GROUPfile-extension: yaml# gateway 配置gateway:# 路由数组:指当请求满足什么样的断言时,转发到哪个服务上routes:# 路由唯一标识- id: gateway-service1# 要转发到哪个服务,我们这里不直接写ip:端口的形式,因为一旦服务的域名或IP地址发生修改,路由配置中的 uri 就必须修改# 使用了lb形式,从注册中心负载均衡的获取uriuri: lb://service1# 设置断言predicates:# 满足/service1的请求路径会路由到localhost:8001服务- Path=/service1/**# 过滤器filters:# 去除原始请求路径中的前1级路径,也就是/service1- StripPrefix=1# 路由唯一标识- id: gateway-service2# 要转发到哪个服务uri: lb://service2# 设置断言predicates:# 满足/service1的请求路径会路由到localhost:8001服务- Path=/service2/**# 过滤器filters:# 去除原始请求路径中的前1级路径,也就是/service1- StripPrefix=1
启动项目:
通过网关访问service1服务的接口:
通过网关访问service2服务的接口:
结论:虽然两个服务接口地址一样,但通过网关配置,我们可以根据不同的请求路径前缀来访问不通的服务
第二种:代码配置
通过代码我们可以更加灵活的对Gateway进行配置,面对的场景也更加复杂。
第一步:先把yml中Gateway配置注释
第二步:添加自定义配置类
package com.javacoding.config;import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.net.URI;
import java.util.*;@Component
public class MyRouteDefinitionRepository implements RouteDefinitionRepository {@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {List<RouteDefinition> routeDefinitions = new ArrayList<>();// service1的route配置RouteDefinition service1 = setService1();// service2的route配置RouteDefinition service2 = setService2();routeDefinitions.add(service1);routeDefinitions.add(service2);return Flux.fromIterable(routeDefinitions);}private RouteDefinition setService2() {RouteDefinition definition = new RouteDefinition();// iddefinition.setId("service2");URI uri = UriComponentsBuilder.fromUriString("lb://service2").build().toUri();// uridefinition.setUri(uri);PredicateDefinition predicate = new PredicateDefinition();predicate.setName("Path");Map<String, String> predicateParams = new HashMap<>(8);predicateParams.put("pattern", "/service2/**");predicate.setArgs(predicateParams);//定义FilterFilterDefinition filter = new FilterDefinition();filter.setName("StripPrefix");Map<String, String> filterParams = new HashMap<>(8);//该_genkey_前缀是固定的,见org.springframework.cloud.gateway.support.NameUtils类filterParams.put("_genkey_0", "1");filter.setArgs(filterParams);definition.setFilters(Collections.singletonList(filter));definition.setPredicates(Collections.singletonList(predicate));return definition;}private RouteDefinition setService1() {RouteDefinition definition = new RouteDefinition();// iddefinition.setId("service1");URI uri = UriComponentsBuilder.fromUriString("lb://service1").build().toUri();// uridefinition.setUri(uri);PredicateDefinition predicate = new PredicateDefinition();predicate.setName("Path");Map<String, String> predicateParams = new HashMap<>(8);predicateParams.put("pattern", "/service1/**");predicate.setArgs(predicateParams);//定义FilterFilterDefinition filter = new FilterDefinition();filter.setName("StripPrefix");Map<String, String> filterParams = new HashMap<>(8);//该_genkey_前缀是固定的,见org.springframework.cloud.gateway.support.NameUtils类filterParams.put("_genkey_0", "1");filter.setArgs(filterParams);definition.setFilters(Collections.singletonList(filter));definition.setPredicates(Collections.singletonList(predicate));return definition;}@Overridepublic Mono<Void> save(Mono<RouteDefinition> route) {return null;}@Overridepublic Mono<Void> delete(Mono<String> routeId) {return null;}
}
第三步:启动项目测试
总结:这些只是基础的配置,实际生产中还会有自定义全局过滤器等。
5、Feign服务调用
5.1、服务调用
当我们在service1调用service2中的接口时,传统的项目我们直接用maven引入service2项目就可以直接调用。但在微服务中,各个项目都是独立存在的,我们如果用http去调用每次都非常繁琐,而且不方便拓展。因此Feign就是解决这个问题。
第一步:创建Feign项目,过程省略,项目结构如下
第二步:pom依赖
<?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>project_springcloud</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>feign</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>
第三步:yml配置
spring:application:name: feigncloud:nacos:discovery:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaconfig:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaprefix: feigngroup: DEFAULT_GROUPfile-extension: yaml
第四步:写feign客户端接口
package com.javacoding.service;import com.javacoding.service.impl.Service2ClientBack;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;@FeignClient(value = "service2", fallback = Service2ClientBack.class)
public interface Service2Client {@RequestMapping("/test/msg")public String test(@RequestParam("msg") String msg);}
说明:
1. @FeignClient注解标识当前接口是feign调用接口,value代表哪个服务调用接口,这里我们想调用service2服务,所以写service2(为什么写service2,因为service2服务里的配置文件spring.application.name,与他保持一致);fallback是如果服务调用失败,我该怎么处理,这个类就是处理这个问题的。
2. 接口请求路径,请求名和方法与service2要调用的接口保持一致。
package com.javacoding.service.impl;import com.javacoding.service.Service2Client;
import org.springframework.stereotype.Component;/*** 调用失败返回*/
@Component
public class Service2ClientBack implements Service2Client {@Overridepublic String test(String msg) {return "service2服务调用失败!!!";}
}
第五步:在service1通过feign实现service2接口请求响应
1、在pom中加入feign依赖
<!--feign类引用-->
<dependency><groupId>org.example</groupId><artifactId>feign</artifactId><version>1.0-SNAPSHOT</version>
</dependency>
<!--feign服务依赖-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、启动类添加@EnableFeignClients注解
package com.javacoding;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication
@EnableFeignClients
public class Service1Application {public static void main(String[] args) {SpringApplication.run(Service1Application.class, args);}
}
3、controller添加调用的方法
package com.javacoding.controller;import com.javacoding.service.Service2Client;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;@RestController
@RequestMapping("/test")
public class TestController {@Resourceprivate Service2Client service2Client;@GetMapping("/msg")public String test(@RequestParam("msg") String msg) {return "service1:" + msg;}/*** 通过feign调用service2的接口* * @param msg* @return*/@GetMapping("/feign/msg")public String test2(@RequestParam("msg") String msg) {String result = service2Client.test(msg);return "通过feign调用请求返回:" + result;}}
4、测试
总结:我们并没有在service1中引入service2的服务,也实现了调用service2服务中的接口。之后我们想要在微服务中引入其他服务只需写一个某某服务的接口(xxxClient)利用@FeignClient注解指明哪个服务,通过这个接口就可以去调用我们的目标服务的接口。
5.2、服务降级
前面我在@FeignClient注解里加了fallback参数。Fallback是通过Hystrix实现的, 所以需要开启Hystrix,spring boot application.properties文件配置feign.hystrix.enabled=true,这样就开启了Fallback。
service1的nacos中的yml配置:
server:port: 8001feign:hystrix:enabled: true
service2中服务抛出个异常,模拟服务不可用:
package com.javacoding.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/msg")public String test(@RequestParam("msg") String msg) {throw new RuntimeException();
// return "service2:" + msg;}}
测试:
5.3、负载均衡
如果我们service2服务是非常重要的,我们通过会部署好几个防止其中一个崩溃导致服务不可用,那么service1在通过feign调用的时候到底调用了哪一个service2呢?如何把请求进行分配呢?
第一步:先把service2的nacos配置的端口注释掉,改为写在项目bootstrap.yml里面。经过测试发现,如果不写在项目bootstrap.yml里面,后续我们启动两个service2服务的时候会一直用nacos配置里的端口。
spring:application:name: service2cloud:nacos:discovery:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaconfig:server-addr: 114.115.xxx.xx:8848namespace: 900c8c5e-b442-4b61-abf4-90b640e643eaprefix: service2group: DEFAULT_GROUPfile-extension: yaml
server:port: 8003
第二步:改造一下service2中的方法,返回信息中加入端口信息,方便区分请求的是哪个服务
package com.javacoding.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/msg")public String test(@RequestParam("msg") String msg, HttpServletRequest request) {
// throw new RuntimeException();int serverPort = request.getServerPort();return "service2:" + msg + ",端口:" + serverPort;}}
第三步:启动网关、service1、service2服务,然后复制service2更改端口启动,这样service2服务就启动了两个实例。
所有服务启动后nacos里的实例信息如下:
第四步:测试接口
总结:可以看到我们的请求在8003和8004端口来回切换,这就是默认的轮训策略。
拓展:除了默认的轮训还有哪些策略?
通常我们都是自定义的方式来配置我们的负载均衡策略,这样我们会有更多的操作空间。
在service1中我们自定义配置:
package com.javacoding.config;import com.netflix.loadbalancer.RandomRule;
import com.netflix.loadbalancer.RoundRobinRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class FeignBalanceConfig {@Bean@LoadBalancedpublic RestTemplate getResTemplate() {return new RestTemplate();}// @Bean
// public RoundRobinRule getRule() {
// // 轮训
// return new RoundRobinRule();
// }@Beanpublic RandomRule getRule() {//随机策略return new RandomRule();}
}
测试随机策略:
还有其他一些策略,不在一一演示了,配置如下:
@Bean
public AvailabilityFilteringRule getRule() {//首先会过滤掉故障机或者并发链接数超过阈值的服务器.剩余的机器轮询配置return new AvailabilityFilteringRule();
}@Bean
public WeightedResponseTimeRule getRule() {//服务器影响时间越快,则权重越高return new WeightedResponseTimeRule();
}@Bean
public BestAvailableRule getRule() {//最大可用策略,即先过滤出故障服务器后,选择一个当前并发请求数最小的return new BestAvailableRule();
}
ok,以上内容如果都学会并了解那代表你已经可以用springcloud微服务应对90%开发需求了。当然还有Hystrix、Ribbon组件,虽然没讲如何用,但这两个组件Feign都已经很好了集成了,如果你面对的是非常复杂的开发场景,那也可以单独去配置这两个组件(原理都是一样的)。
希望这篇微服务项目可以带你很好的入门~~~