一、搭建eureka服务中心
要注意cloud版本和boot版本的对应关系,否则服务都启动不了
本项目使用版本如下:
spring-boot :2.3.1.RELEASE spring-cloud:Hoxton.SR7
构建springBoot项目,引入eureka服务的依赖
这里只需要引入这一个就ok
配置application文件
采用yml配置一定要注意格式;
defaultZone 后端口后尽量别在加,可能导致404(因为这个坑搞了好久)
# 指定服务名称
spring:
application:
name: eureka1
# 端口自定义
server:
port: 8752
# 指定当前eureka客户端的注册地址,也就是eureka服务的提供方,当前配置的服务的注册服务方
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false #不把自己(eureka-server)注册到eureka上(不做高可用的情况下)
fetch-registry: false #不从eureka上来获取服务的注册信息(因为本身就是注册中心,消费者就需要获取提供者的信息)
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port} # 后边再加一层(如/eureka)刚问时会404
启动类上添加@EnableEurekaServer
添加@EnableEurekaServer,表明标注类是一个Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
访问eureka注册中心管理面
二、搭建一个springboot微服务工程,用于提供服务
搭建SpringBootService,这里是各个微服务的业务逻辑,将其注册到注册中心去,其作为服务提供者,也就是服务端
spring-boot :2.3.1.RELEASE spring-cloud:Hoxton.SR7
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka-client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>
启动类上添加@EnableDiscoveryClient注解
也可以使用@EnableEurekaClient,添加后就会被EurekaService扫描到,注册到注册中心
@SpringBootApplication
@EnableDiscoveryClient // @EnableEurekaClient只适用于Eureka作为注册中心;@EnableDiscoveryClient 可以是其他注册中心
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置application.yml
TransportException: Cannot execute request on any known server
server:
port: 8760
# eureka-client
eureka:
client:
service-url:
defaultZone: http://localhost:8752/eureka # 服务地址/eureka (/eureka一定要加,否则启动直接报错TransportException: Cannot execute request on any known server)
# application-name
spring:
application:
name: ServiceProvider1
启动微服务
注意,要先启动注册服务,否则启动微服务时会报错RedirectingEurekaHttpClient : Request execution error…
从控制台打印日志可以看到,注册中心发现并已注册微服务SpringBootService1:8760
再次查看注册中心
可以看到,SpringBootService1确实已被注册管理
微服务也可以正常访问
http://localhost:8760/users/query/all
我这里boot用的我之前练习的项目,所以直接能从数据库中获取到数据
但是我们一般不直接调用所需的微服务(服务提供者),而是经过注册中心获取所需的服务提供者列表(为一个列表,此列表包含了能提供相应服务的服务器),他们也许是个集群,因此server会返回一个 ip+端口号的表,服务消费者通过相应算法访问这表上的不同服务器,这些服务器提供的是相同的服务,这种在服务消费者一方挑选服务器为自己服务的方式是一种客户端的负载均衡。在cloud中ribbon就是一种负载均衡的客户端
三、搭建基于ribbon的客户端,用于消费服务
同二搭建boot微服务工程,引入相关的依赖(复制一份上个工程代码,修改下即可)
pom.xml
在上个pom上新增ribobon依赖即可
<!-- ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
application.xml
server:
port: 8770
# eureka-client
eureka:
client:
service-url:
defaultZone: http://localhost:8752/eureka # 服务地址/eureka (/eureka一定要加,否则启动直接报错TransportException: Cannot execute request on any known server)
# application-name
spring:
application:
name: ServiceConsumer1
定义启动类
@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
定义controller类来编写ribbon的代码
其实在这之前和服务提供类一样,这一步是关键,我们需要使用restTemplate+ribbon 来访问服务提供者;
这里我们使用自动注入并直接调用restTemplate对象的方式来调用服务提供者,以达到微服务之间的通信;
首先,需要定义一个@Bean来产生一个restTemplate Bean对象,交给Spring容器管理;
添加@LoadBalanced后才能让RestTemplate具有负载均衡的能力;
@Configuration
public class Beans {
@Bean // @Bean是放在方法的注释,它很明确地告诉被注释的方法,你给我产生一个Bean,然后交给Spring容器
@LoadBalanced // 实现负载匀衡; 使用restTemplate时就可以直接使用服务名访问
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
接着我们就来写下控制层的代码
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/hello")
public String hello() {
// 方式1:根据服务名 获取服务列表 根据算法选取某个服务 并访问某个服务的网络位置
ServiceInstance serviceProvider = loadBalancerClient.choose("ServiceProvider1");
String url = "http://" + serviceProvider.getHost() + ":" + serviceProvider.getPort() +
"/users/query/all";
String response = new RestTemplate().getForObject(url, String.class);
return response + "\t" + url;
// 方式2:可以直接使用服务名访问
// return restTemplate.getForObject("http://ServiceProvider1/users/query/all", String.class);
}
}
启动测试
依次启动注册中心,服务提供者、服务消费者三个服务
我们通过注册中心可以看到,2个微服务都被成功注册
浏览器访问服务消费方rest接口,可以看到成功从服务提供端获取了数据,因为只有一个服务端节点,所以ribbon只会将请求转发到8760端口的节点
看到这里,大伙可能有个疑问上边只有一个单节点服务提供者(端口8760),每次访问的都是这一个节点,这怎么能体现负载均衡呢?别急,我们接着就来构造下整个微服务的多实例来再看看
四、构建多实例的服务提供者
构建ServiceProvider1多实例
端口:8061
到此处,我们就构造好了一个ServiceProvider1服务的1个实例,端口为8061
启动项目,查看注册中心
我们可以看到,8061的节点已被注册,那接下来我们来测试下负载均衡
负载均衡测试
可以看到,通过消费端访问服务端时,连续请求时,ribbon负载均衡策略默认是轮询,也就是会将请求循环转发给每一个可用的服务
在微服务架构中多层服务之间会相互调用(就像消费端调用服务端),如果其中有一层服务故障了,可能会导致一层服务或者多层服务故障,从而导致整个系统故障。这种现象被称为服务雪崩效应, 熔断就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过断路器直接将此请求链路断开,接下来我们来看看
五、使用熔断器Hystrix
Hystrix熔断器可以是服务端也可以是客户端(消费端)的,而服务端的熔断器我们一般通过整合Feign实现,这个后边再说
Hystrix使用说明,参数配置
熔断器可视化界面hystrix dashboard
客户端熔断器
pom中新增依赖
注意导包时spring-cloud-starter-netflix-hystrix,不是spring-cloud-starter-hystrix否则可能会找不到某些注解
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
修改启动类,添加注解@EnableHystrix 开启Hystrix的熔断器功能
@SpringBootApplication
@EnableDiscoveryClient // @EnableEurekaClient只适用于Eureka作为注册中心;@EnableDiscoveryClient 可以是其他注册中心
@EnableHystrix // 开启Hystrix的熔断器功能
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
修改下controller代码,我们让客户端请求服务端时,如果超时100ms,则直接回调fallbackMethod,而不会一致阻塞,占用线程资源
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private LoadBalancerClient loadBalancerClient;
@Autowired
private RestTemplate restTemplate;
private static String urlMsg = null;
@HystrixCommand(commandProperties = {
// 设置单线程请求的超时时间,如果超时,回调方法,而不会一直阻塞
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="100")
}, fallbackMethod = "fallBackHello")
@RequestMapping("/hello")
public String hello() {
// 方式1:根据服务名 获取服务列表 根据算法选取某个服务 并访问某个服务的网络位置
ServiceInstance serviceProvider = loadBalancerClient.choose("ServiceProvider1");
String url = "http://" + serviceProvider.getHost() + ":" + serviceProvider.getPort() +
"/users/query/all";
urlMsg = url;
String response = new RestTemplate().getForObject(url, String.class);
return response + "\t" + url;
// 方式2:可以直接使用服务名访问
// return restTemplate.getForObject("http://ServiceProvider1/users/query/all", String.class);
}
// 熔断策略的回调方法
public String fallBackHello() {
return urlMsg + " 当前请求人数较多,请稍后再试...";
}
}
熔断测试
依次启动4个服务,使用http://localhost:8770/consumer/hello 访问时,可以正常访问到服务端8760和8061 2个节点
这时候,如果我们把8760服务停掉,再来看下
当ribbon转发请求到8760节点时,因为服务故障,所以调用该服务的请求快速失败,而不是线程等待。
而对于转发到8061节点的请求则正常返回
这时候我们再启动8760的服务,又可以正常访问了
六、整合Feign
在上边我们搭建Ribbon的时候,消费端是通过RestTemplate去调用服务端的一个接口的,如果调用每一个接口都采用这种方式,这种模板代码看起来很臃肿,为了消除这种弊端,往往在微服务间的调用我们采用Feign注解的方式映射到对应到请求地址,这样就像我们平时调用自己写的方法一样容易,它让微服务之间的调用变得更简单了,类似controller调用service
而Feign整合后,也可以作为负载均衡和熔断器使用,不必再引入Ribbon和Hystrix的依赖
接下来,我们就在服务端的调用之间来引入feign来简单实现下…
在这之前,同二我们再创建一个ServiceProvider2
pom.xml
ServiceProvider1新增fegin依赖
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
修改ServiceProvider1启动类
ServiceProvider1启动类上新增@EnableFeignClients注解
@SpringBootApplication
@EnableDiscoveryClient // @EnableEurekaClient只适用于Eureka作为注册中心;@EnableDiscoveryClient 可以是其他注册中心
@EnableFeignClients // 启用feign客户端
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}
ServiceProvider1 定义Feign API
接口中映射具体请求的服务以及请求绝对路径
@FeignClient(name = "ServiceProvider2") // 被调用服务(service2)yml中定义的application.name
public interface ServiceAToBFeignApi {
@GetMapping("/service2/hello") // 具体的请求路径
String getHelloService2();
}
ServiceProvider1定义controller类
注入Feign API,直接调用接口即可
@RestController
@RequestMapping("/service1")
public class ServiceAToBController {
@Autowired
private ServiceAToBFeignApi serviceAToBFeignApi;
@GetMapping("/hello")
public String helloService1() {
return serviceAToBFeignApi.getHelloService2();
}
}
修改ServiceConsumer1的controller代码
只需要将之前代码中url改变部分为/service1/hello,ribbon还使用的是restTemplate去请求(当然我们也可以使用feign去代替)
@RequestMapping("/hello")
public String hello() {
// 方式1:根据服务名 获取服务列表 根据算法选取某个服务 并访问某个服务的网络位置
ServiceInstance serviceProvider = loadBalancerClient.choose("ServiceProvider1");
String url = "http://" + serviceProvider.getHost() + ":" + serviceProvider.getPort() +
"/service1/hello";
urlMsg = url;
String response = new RestTemplate().getForObject(url, String.class);
return url + "\t" + response;
// 方式2:可以直接使用服务名访问
// return restTemplate.getForObject("http://ServiceProvider1/users/query/all", String.class);
}
ServiceProvider2定义controller类
@RestController
@RequestMapping("/service2")
public class HelloController {
@GetMapping("/hello")
public String helloService2(){
return "service1调用service2";
}
}
启动测试
依次来启动服务,查看注册中心,可以看到新增的ServiceProvider2已经注册
直接通过ribbon消费者端来访问,成功获取到ServiceProvider2的返回结果
http://localhost:8770/consumer/hello
七、网关gateway
当我们的整个cloud构架中微服务越来越多的时候,我们就需要使用网关来作为统一入口,它会对前端的每个请求做识别过滤,然后再动态的将请求路由到不同的后端集群中
持续更新中…