一、springcloud介绍
1.1、
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下2种:
-
RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表
-
Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。springcloud就是基于http。
1.2、springcloud核心组件
五大重要组件
服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon/Feign
服务网关——Netflix Zuul
断路器——Netflix Hystrix
分布式配置——Spring Cloud Config
1.3、快速案例
1)parent
<?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>father</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>../springcloud-study/common</module> </modules> <packaging>pom</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.SR2</version> <type>pom</type> <!-- import表示将spring-cloud-dependencies包中的依赖信息导入--> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.9.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </project>
2)provider(springboot工程)
package com.example.provider.web; import org.example.domain.Dog; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class DogController { @RequestMapping("/dog") public Dog getDog(){ return new Dog("肥松", 2); } }
3)consumer(springboot工程)
package com.example.consumer.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class SpringCloudConfig { @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
package com.example.consumer.web; import org.example.domain.Dog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class RemoteDogController { @Autowired private RestTemplate restTemplate; @RequestMapping("/dog/remote") public Dog getDogRemote() { //1.准备provider的主机加端口 String addr = "http://localhost:8989"; //2.远程调用接口地址 String api = "/provider/dog"; //Dog.class 返回类型 return restTemplate.getForObject(addr+api, Dog.class); // return new Dog("didi", 11); } }
4)common(实体类必须要有无参数构造)
package org.example.domain; public class Dog { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Dog(String name, Integer age) { this.name = name; this.age = age; } public Dog() { } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
二、eureka-注册中心
Eureka是netflix的一个子模块,也是核心模块之一,Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移
服务注册与发现对于微服务架构来说是非常重要的,有了服务发现和注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务,而不需要修改服务调用的配置文件了。
2.1、eureka服务端
1)创建springboot web项目,添加依赖
<!--Eureka服务端支持:此依赖需放在其他依赖下面,否则可能启动不了--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)在主启动类添加注解
3)配置
server.port=5000 eureka.instance.hostname=localhost #实例是否在eureka服务器上注册自己的信息以供其他服务发现,默认为true eureka.client.register-with-eureka=false #此客户端是否获取eureka服务器注册表上的注册信息,默认为true eureka.client.fetch-registry=false #与Eureka注册服务中心的通信zone和url地址 eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
4)启动服务,登录 http://localhost:5000 查看
2.2、provider客户端
1)添加依赖
<!--Eureka服务端支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)配置
server.port=8989 server.servlet.context-path=/provider spring.application.name=provider01 //添加服务名称,方便调用 eureka.client.service-url.defaultZone=http://localhost:5000/eureka
2.3、consumer客户端
1)添加依赖
<!--Eureka服务端支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)配置
server.servlet.context-path=/consumer spring.application.name=consumer01 eureka.client.serviceUrl.defaultZone=http://localhost:5000/eureka
3)在配置类添加注解
4)修改调用方式
package com.example.consumer.web; import org.example.domain.Dog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class RemoteDogController { @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/ribbon/dog/remote",produces = "application/json;charset=utf-8") public Dog getDogRemote() { //1.准备provider的主机加端口 // String addr = "http://localhost:8989"; String addr = "http://provider01"; //2.远程调用接口地址 String api = "/provider/dog"; //Dog.class 返回类型 return restTemplate.getForObject(addr+api, Dog.class); // return new Dog("didi", 11); } }
三、Feign负载均衡
前面的可以发现当我们通过RestTemplate调用其它服务的API时,所需要的参数须在请求的URL中进行拼接,如果参数少的话或许我们还可以忍受,一旦有多个参数的话,这时拼接请求字符串就会效率低下,并且显得好傻
Feign是以接口方式进行调用,而不是通过RestTemplate来调用,feign底层还是ribbon,它进行了封装
3.1、common模块(声明接口)
1)添加依赖
<!--Eureka服务端支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)创建接口(该接口不需要实现)
package org.example.feignconsumer.service; import org.example.domain.Dog; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.RequestMapping; //@FeignClient注解表示当前接口和一个Provider对应,注解value属性指定要调用的Provider微服务名称 @FeignClient(value = "provider01") public interface DogService { //远程调用的接口方法 //要求@RequestMapping注解映射的地址一致 //要求方法声明一致 //用来获取请求参数的@RequestParam 、 @PathVariable 、@RequestBody不能省略,要两边一致 @RequestMapping(value = "/dog") public Dog getDog(); }
3.2、provider(提供服务)
1)添加依赖
<!--Eureka服务端支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.example</groupId> <artifactId>common</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
2)配置
server.port=7000 spring.application.name=provider01 eureka.client.service-url.defaultZone=http://localhost:5000/eureka
3)controller
package com.example.provider.web; import org.example.domain.Dog; import org.springframework.http.HttpRequest; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; @RestController public class DogController { // @RequestMapping(value = "/dog",produces = "application/json;charset=utf-8") // public Dog getDog(HttpServletRequest request){ // // int serverPort = request.getServerPort(); // // return new Dog("肥松 "+serverPort, 2); // } @RequestMapping(value = "/dog") //要与service中的方法保持一致 public Dog getDog(){ return new Dog("肥松", 2); } }
3.3、consumer(消费服务)
1)添加依赖
<dependency> <groupId>org.example</groupId> <artifactId>common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--Eureka服务端支持--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)配置
server.servlet.context-path=/consumer spring.application.name=consumer01 eureka.client.serviceUrl.defaultZone=http://localhost:5000/eureka
3)controller
package org.example.feignconsumer.controller; import org.example.domain.Dog; import org.example.feignconsumer.service.DogService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class TestController { //需保证包与common一致(org.example.feignconsumer),否则找不到service @Autowired private DogService dogService; @RequestMapping("/test") public Dog test01(){ return dogService.getDog(); } }
4)主启动类添加注解
@EnableFeignClients
四、Hystrix熔断
4.1、熔断(provider端措施)
1)在provider加入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)在provider主启动类加入注解
//开启断路器功能
@EnableCircuitBreaker
3)修改controller
package com.example.provider.web; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.example.domain.Dog; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class DogController { @HystrixCommand(fallbackMethod = "getDogBackup") @RequestMapping(value = "/dog") public Dog getDog(@RequestParam("signal") String signal) throws InterruptedException { if("bang".equals(signal)) throw new RuntimeException(); if ("sleep".equals(signal)) Thread.sleep(5000); return new Dog("肥松", 2); } //当发生故障时。执行此方法返回 public Dog getDogBackup(@RequestParam("signal") String signal){ return new Dog(signal, 0); } }
4.2、降级(consumer端措施)
服务降级处理是在客户端(consumer端)实现完成的,与服务端(provider)无关,当某个consumer访问一个provider却迟迟得不到响应时,执行预先设定好的一个解决方案,而不是一直等待。
实现步骤:
1)common中添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)common中创建factory类
package org.example.feignconsumer.factory; import feign.hystrix.FallbackFactory; import org.example.domain.Dog; import org.example.feignconsumer.service.DogService; import org.springframework.stereotype.Component; /** * 1.实现consumer端服务降级功能 * 2.实现FallbackFactory接口时要传入@FeignClient注解标记的接口类型 * 3.在create方法中返回的@FeignClient注解标记的接口类型的对象,当provider调用失败后,会执行这个对象的对应方法 */ @Component public class MyFallBackFactory implements FallbackFactory<DogService> { @Override public DogService create(Throwable throwable) { return new DogService() { @Override public Dog getDog(String signal) { return new Dog("降级:"+throwable.getMessage(), 10); } }; } }
3)consumer配置添加
feign.hystrix.enabled=true
4)common中service添加注解
//@FeignClient注解表示当前接口和一个Provider对应,注解value属性指定要调用的Provider微服务名称 @FeignClient(value = "provider01", fallbackFactory = MyFallBackFactory.class) public interface DogService {
五、hystrix监控
5.1、provider端
1)添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
2)添加配置
management.endpoints.web.exposure.include=hystrix.stream
5.2、新建dashboard工程
1)添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> <version>2.2.3.RELEASE</version> </dependency>
2)主启动类添加注解
@EnableHystrixDashboard
3)配置
server.port=8000 spring.application.name=dashboard
4)登录
http://localhost:8000/hystrix
六、ZUUL网关
6.1、使用zuul
1)创建zuul工程
2)添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> <version>2.2.3.RELEASE</version> </dependency>
3)配置
server.port=9000 spring.application.name=zuul eureka.client.serviceUrl.defaultZone=http://localhost:5000/eureka
4)添加注解
//启用zuul代理 @EnableZuulProxy @SpringBootApplication public class GatewayApplication
5)访问
6.2、其他配置
1)使用指定地址代替微服务名称
2)让用户不能通过微服务名称访问
3)给访问路径加统一前缀
6.3、zuulfilter
package org.example.gateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class MyZuulFilter extends ZuulFilter { /** * 返回过滤器类型 * pre表示在目标微服务前执行 * @return */ @Override public String filterType() { String type = "pre"; return type; } @Override public int filterOrder() { return 0; } /** * 判断当前请求是否要进行过滤 * 要过滤:返回true,继续执行run方法 * 不过滤:返回false,直接放行 * @return */ @Override public boolean shouldFilter() { //获取request对象 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); String signal = request.getParameter("signal"); return signal.equals("bang"); } @Override public Object run() throws ZuulException { System.out.println("run方法执行了。。。。。。。。。。"); //当前实现忽略这个方法的返回值,所以返回null,不做特殊处理 return null; } }
七、SpringCloudAlibaba实战案例
项目结构:
7.1、注册中心
7.1.1、运行Nacos注册中心
1、Nacos下载和安装
2、Windows启动Nacos
启动:startup.cmd -m standalone
3、访问
用户名密码:nacos/nacos
7.1.2、服务发现
1、引入依赖
<!--服务发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
2、添加服务配置信息
#spring: cloud: nacos: discovery: server-addr: localhost:8848 # nacos服务地址
3、启动微服务
7.2、OpenFeign
需求
7.2.1、校验手机号是否注册
1、UserInfoController
@ApiOperation("校验手机号是否注册") @GetMapping("/checkMobile/{mobile}") public boolean checkMobile(@PathVariable String mobile){ return userInfoService.checkMobile(mobile); }
2、UserInfoService
boolean checkMobile(String mobile);
实现:
@Override public boolean checkMobile(String mobile) { QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("mobile", mobile); Integer count = baseMapper.selectCount(queryWrapper); return count > 0; }
7.2.2、OpenFeign的引入
1、引入依赖
<!--服务调用--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2、启动类添加注解
@EnableFeignClients
7.2.3、接口的远程调用
1、CoreUserInfoClient
@FeignClient(value = "service-core") public interface CoreUserInfoClient { @GetMapping("/api/core/userInfo/checkMobile/{mobile}") boolean checkMobile(@PathVariable String mobile); }
2、ApiSmsController
@Resource private CoreUserInfoClient coreUserInfoClient;
在获取验证码方法中调用远程方法校验手机号是否存在
//手机号是否注册 boolean result = coreUserInfoClient.checkMobile(mobile); System.out.println("result = " + result); Assert.isTrue(result == false, ResponseEnum.MOBILE_EXIST_ERROR); //生成验证码 .....
7.2.4、超时控制
feign: client: config: default: connectTimeout: 10000 #连接超时配置 readTimeout: 600000 #执行超时配置
7.2.5、OpenFeign日志
1、作用
2、日志级别
- NONE:默认级别,不显示日志
- BASIC:仅记录请求方法、URL、响应状态及执行时间
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应头信息
- FULL:除了HEADERS中定义的信息之外,还有请求和响应正文及元数据信息
3、配置日志bean
@Configuration public class OpenFeignConfig { @Bean Logger.Level feignLoggerLevel(){ return Logger.Level.FULL; } }
4、开启日志
logging:
level:
com.atguigu.srb.sms.client.CoreUserInfoClient: DEBUG #以什么级别监控哪个接口
5、修改logback日志级别
<!-- 开发环境和测试环境 --> <springProfile name="dev,test"> <logger name="com.example" level="DEBUG"> <appender-ref ref="CONSOLE" /> </logger> </springProfile>
6、查看日志输出
7.3、Sentinel熔断
1、service-base中引入sentinel依赖
<!--服务容错--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2、开启Sentinel支持
#开启Feign对Sentinel的支持 #feign: sentinel: enabled: true
3、创建容错类(降级方式)
@Service @Slf4j public class CoreUserInfoClientFallback implements CoreUserInfoClient { @Override public boolean checkMobile(String mobile) { log.error("远程调用失败,服务熔断"); return false; } }
4、指定熔断类
@FeignClient(value = "service-core", fallback = CoreUserInfoClientFallback.class) public interface CoreUserInfoClient {
7.4、网关
不使用网关时:
使用网关时:
7.4.1、创建模块service-gateway
1、创建模块
2、配置pom
<dependencies> <!-- 网关 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--服务注册--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> </dependencies>
3、配置application.yml
server: port: 80 # 服务端口 spring: profiles: active: dev # 环境设置 application: name: service-gateway # 服务名 cloud: nacos: discovery: server-addr: localhost:8848 # nacos服务地址 gateway: discovery: locator: enabled: true # gateway可以发现nacos中的微服务,并自动生成转发路由
4、logback.xml
5、创建启动类
@SpringBootApplication @EnableDiscoveryClient public class ServiceGatewayApplication { public static void main(String[] args) { SpringApplication.run(ServiceGatewayApplication.class, args); } }
6、启动网关
nginx.exe -s stop
7、测试自动路由转发
7.4.2、路由配置
1、基本配置
#spring: # cloud: # gateway: routes: - id: service-core uri: lb://service-core #lb负载均衡,当有多个相同service采用默认轮询访问 predicates: - Path=/*/core/** #当访问路径有core时自动找到service-core服务,这样访问路径就可以去掉service-core - id: service-sms uri: lb://service-sms predicates: - Path=/*/sms/** - id: service-oss uri: lb://service-oss predicates: - Path=/*/oss/**
2、测试路由转发
7.4.3、跨域配置
1、配置文件
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); //是否允许携带cookie config.addAllowedOrigin("*"); //可接受的域,是一个具体域名或者*(代表任意域名) config.addAllowedHeader("*"); //允许携带的头 config.addAllowedMethod("*"); //允许访问的方式 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsWebFilter(source); } }
2、删除后端跨域配置