一、微服务简介
微服务最重要的两点是服务发现和负债均衡,针对这两点springcloud分别提供了Eureka和Ribbon,其中Eureka是springCloud Netflix微服务套件的一部分,它基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。spring Cloud Ribbon是一个基于HTTP和TCP客户端负载均衡工具,它基于Netflix Ribbon实现。
Eureka采用的是典型的C-S架构,在Eureka架构中有3个重要角色,
1:Eureka Server (注册中心服务端)
2:Service Provider(服务提供者客户端)
3:Service Consumer(服务消费者客户端)
对于eureka注册中心来说,服务消费者和服务提供者都是客户端
二、Eureka服务中心搭建
1.创建springboot工程添加如下依赖:
<properties>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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>
使用eureka主要添加spring-cloud-starter-netflix-eureka-server依赖,这里springCloud的版本号为<spring-cloud.version>Greenwich.SR3</spring-cloud.version>,当锁定了springCloud的版本后就锁定了eureka的版本,所以这里不需要写eureka的版本号。而springboot和springCloud是有对应关系的,springCloud版本不可随便给,官网已经给出了对应的关系,如下图:
2.application.yml添加Eureka配置
如下
server:
port: 8084
eureka:
client:
service-url:
defaultZone: http://localhost:8084/eureka/
register-with-eureka: false #单机版建议设置为false,设置false的目的是防止自己注册自己,集群版采用默认的true
fetch-registry: false #单机版建议设置为false,设置false的目的是防止自己发现自己,集群版采用默认的true
因为在默认设置下,eureka服务中心会将自己作为客户端来注册它自己,所以单机版需要将register-with-eureka设置为false,而在集群中由于需要相互注册,所以用true。
3.创建引导类添加@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableEurekaServer注解是用来开启开启eureka服务
4.启动工程,浏览器输入ip端口号
看到如下界面
可以看到Instaances currently registered with Eureka为No instances available,即无微服务注册到进来。
三、搭建服务提供者
1.创建工程
pom.xml文件添加如下:
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<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>
对于spring-cloud-starter-netflix-eureka-client依赖和eureka注册中心一样也要指定springCloud版本。
2.application.yml添加配置,指定注册中心地址
server:
port: 9001
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8084/eureka/ #eureka注册中心地址
spring:
application:
name: helloWorld-course-service #应用名
3.创建引导类添加@EnableEurekaClient注解
@SpringBootApplication
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableEurekaClient注解用于注册服务
4.启动工程
工程启动后,进入注册中心页面,结果如下:
可以看到应用名就是配置文件里的应用名,status中up(1),如果是集群就是不是1了。
4.编写对外接口
@RestController("/helloWorld")
public class HelloWorldController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping("/getId/{id}")
public String getId(@PathVariable("id") Integer id){
logger.info("id = " + id);
return "helloWorld";
}
}
四、搭建服务消费者
对于服务消费者的搭建和服务提供者一样需要制定注册中心地址
1.编写controller调用远程服务
微服务之间的远程调用有3中方式,分别是:RestTemplate、DiscoveryClient、Feign
1.RestTemplate方式调用
对于RestTemplate方式有两种
第一种:
@RestController
@RequestMapping("/consumer")
public class HelloWorldController {
private RestTemplate restTemplate = new RestTemplate();
@RequestMapping("/getHelloWorld")
public String getHelloWorld(){
String result = restTemplate.getForObject("http://localhost:9001/helloWorld/getId/100", String.class);
return result;
}
}
RestTemplate是Spring提供的用于模拟浏览器发送http请求的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。
这种方式简单粗暴,这种方式有弊端,最直接的弊端是,如果服务提供者是一个集群,我们永远只能调用集群中的某一个。没法实现负载均衡。而且这种方式根本没利用Eureka,也就是不使用Eureka也可以使用这种方式。
第二种:
使用@LoadBalanced注解
第一步在restTemplate模板对象上加@LoadBalanced注解
@Configuration
public class ApplicationConfig {
@Bean
@LoadBalanced //使用负载均衡器Ribbon
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
第二步:使用应用名调用
@RestController
@RequestMapping("/consumer")
public class HelloWorldController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/getHelloWorld")
public String getHelloWorld(){
String result = restTemplate.getForObject("http://HELLOWORLD-PROVIDER/helloWorld/getId/200", String.class);
return result;
}
}
这种方式不是以ip+port来调用,而是应用名,这种方式发送的请求都会被ribbon拦截,ribbon从eureka注册中心获取服务列表,然后采用均衡策略进行访问。
2.使用Feign方式调用
Feign 是一种声明式、模板化的 HTTP 客户端,在 Spring Cloud 中使用 Feign,可以做到使用 HTTP请求远程服务时能与调用本地方法一样的编码体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。
第一步:
添加spring-cloud-starter-openfeign依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
feign无需指定version,因为springclould中已经指定了版本
第二步:
添加@EnableFeignClients注解启动feign
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
第三步:
定义远程服务接口
@FeignClient("HELLOWORLD-PROVIDER")
public interface IService {
@RequestMapping("/helloWorld/getId/{id}")
public String getHelloWorld(@PathVariable("id") Integer id);
}
第四步:
实现当前controller层:
@RestController
@RequestMapping("/consumer")
public class HelloWorldController {
@Autowired
private IService service;
@RequestMapping("/getHelloWorld")
public String getHelloWorld(){
String result = service.getHelloWorld(500);
return result;
}
}
启动注册中心,提供者,消费者,浏览器输入http://localhost:9002/consumer/getHelloWorld结果如下
helloWorld500
对于@FeignClient注册,应用名不区分大小写
五、使用nacos作为注册中心
1.依赖版本配置
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.demo</groupId>
<artifactId>springcloud-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>nacos-provider</module>
<module>nacos-consumer</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<lombok.version>1.18.16</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2.yml配置
spring:
application:
name: nacos-consumer-demo
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
username: nacos
password: p5dS2kvnG$AcVMpZ
namespace: 64742345-a882-4954-8604-9616242cd6f9
group: DEFAULT_GROUP
prefix: nacos-consumer
file-extension: yml
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: p5dS2kvnG$AcVMpZ
namespace: 64742345-a882-4954-8604-9616242cd6f9
group: DEFAULT_GROUP
spring.cloud.nacos.discovery为注册中心配置,用于服务发现和注册
spring.cloud.nacos.config为nacos配置中心,nacos配置中心的使用可以参考官方平台什么是 Nacos
3.注解配置
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
@EnableDiscoveryClient,才能进行服务注册
@EnableFeignClients,开启feign调用
六、服务熔断
当消费者调用生产者接口出现报错或超时,可能需要进行对应的处理机制
1.启用服务熔断
以feign调用为例,开启服务熔断配置如下
feign:
hystrix:
#为false时 @FeignClient属性fallback不生效,true为开启服务降级
enabled: true
开启后如果未实现服务熔断降级处理逻辑会报错
2.实现服务熔断
@FeignClient(value = "nacos-provider-demo", path = "/nacos-provider",
// fallback = RemoteProviderServiceFallbackImpl.class,
fallbackFactory = RemoteProviderServiceFallbackFactoryImpl.class)
public interface IRemoteProviderService {
/**
* test
* @param arg
* @return
*/
@GetMapping("/provider/test/{arg}")
String test(@PathVariable(value = "arg")String arg);
/**
* test
* @param arg
* @return
*/
@GetMapping("/provider/test/error/{arg}")
String testError(@PathVariable(value = "arg")String arg);
/**
* testTimeout
* @param arg
* @return
*/
@GetMapping("/provider/test/timeout/{arg}")
String testTimeout(@PathVariable(value = "arg") Long arg);
}
@FeignClient(value = "nacos-provider-demo", path = "/nacos-provider",
// fallback = RemoteProviderServiceFallbackImpl.class,
fallbackFactory = RemoteProviderServiceFallbackFactoryImpl.class)
注解有两个属性,fallback或fallbackFactory(两者选其一),配置上熔断处理的类即可
fallback中的类为IRemoteProviderService 的实现类如下:
@Slf4j
@Component
public class RemoteProviderServiceFallbackImpl implements IRemoteProviderService {
@Override
public String test(String arg) {
log.error("调用服务失败,入参{}", arg);
return "test调用失败";
}
@Override
public String testError(String arg) {
log.error("调用服务失败,入参{}", arg);
return "testError调用失败";
}
@Override
public String testTimeout(Long arg) {
log.error("调用服务失败,入参{}", arg);
return "testTimeout调用失败";
}
}
fallbackFactory编写需要实现FallbackFactory类,如下:
@Component
@Slf4j
public class RemoteProviderServiceFallbackFactoryImpl implements FallbackFactory<IRemoteProviderService> {
@Override
public IRemoteProviderService create(Throwable throwable) {
return new IRemoteProviderService() {
@Override
public String test(String arg) {
log.error("调用服务失败" + throwable.getMessage(), throwable);
return "test调用失败";
}
@Override
public String testError(String arg) {
log.error("调用服务失败:" + throwable.getMessage(), throwable);
return "testError调用失败";
}
@Override
public String testTimeout(Long arg) {
log.error("调用服务失败" + throwable.getMessage(), throwable);
return "testTimeout调用失败";
}
};
}
}
七、服务超时时间配置
超时时间有三个地方配置,feign,hystrix,ribbon,配置不对将不生效或不能达到相应的效果,可参考SpringCloud的各种超时时间配置效果_shutdown hook installed for: nfloadbalancer-pingti-CSDN博客
feign:
hystrix:
#为false时 @FeignClient属性fallback不生效,true为开启服务降级
enabled: true
client:
config:
#设置feign日志级别,default为全局,若需要指定某个微服务则写微服务,需要此配置生效需要配置服务日志级别为debug
default:
logger-level: FULL
connect-timeout: 1000
read-timeout: 3000
#熔断器 Hystrix 的超时时间必须不小于 Feign 设置的超时时间,否则 Feign 设置的超时时间设置的超时时间无效
hystrix:
command:
default:
execution:
timeout:
#是否开启超时熔断
enabled: true
isolation:
thread:
#服务降级超时时间,开启服务降级生效,
timeoutInMilliseconds: 10000
ribbon:
# 指的是建立连接后从服务器读取到可用资源所用的时间
ReadTimeout: 1000
# 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
ConnectTimeout: 1000
- 如果hystrix.command.default.execution.timeout.enabled为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是熔断器hystrix的timeoutInMilliseconds, 此时谁的值小谁生效
- 如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon
- ribbon的ConnectTimeout,配置的是请求服务的超时时间,除非服务找不到,或者网络原因,这个时间才会生效
- ribbon还有MaxAutoRetries对当前实例的重试次数,MaxAutoRetriesNextServer对切换实例的重试次数, 如果ribbon的ReadTimeout超时,或者ConnectTimeout连接超时,会进行重试操作
- 由于ribbon的重试机制,通常熔断的超时时间需要配置的比ReadTimeout长,ReadTimeout比ConnectTimeout长,否则还未重试,就熔断了
- 为了确保重试机制的正常运作,理论上(以实际情况为准)建议hystrix的超时时间为:(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout