上两章节,介绍了下关于注册中心-Eureka的使用及高可用的配置示例,本章节开始,来介绍下服务和服务之间如何进行服务调用的,同时会讲解下几种不一样方式的服务调用。html
在SpringCloud
体系中,咱们知道服务之间的调用是经过http
协议进行调用的。而注册中心的主要目的就是维护这些服务的服务列表。咱们知道,在Spring
中,提供了RestTemplate
。RestTemplate
是Spring
提供的用于访问Rest服务的客户端。而在SpringCloud
中也是使用此服务进行服务调用的。java
同时在微服务中,通常上服务都不会进行单点部署的,都会至少部署2台及以上的。如今咱们有了注册中心进行服务列表的维护,就须要一个客户端负载均衡来进行动态服务的调用。git
因此开始示例前,咱们先来大体了解下关于负载均衡
和RestTemplate
的相关知识点。其实后面实例的Ribbon
和Feign
最后的调用都是基于RestTemplate
的。使用比较简单~github
负载均衡(Load Balance)是分布式系统架构设计中必须考虑的因素之一,它一般是指,将请求/数据**【均匀】分摊**到多个操做单元上执行,负载均衡的关键在于【均匀】。web
实现负载均衡的方式有不少种,这里简单介绍下几种方式,并未过多深刻。算法
注意:如下部份内容转至几种负载均衡技术的实现。spring
1.HTTP重定向负载均衡编程
根据用户的http请求计算出一个真实的web服务器地址,并将该web服务器地址写入http重定向响应中返回给浏览器,由浏览器从新进行访问设计模式
优缺点:实现起来很简单,而缺点也显而易见了:请求两次才能完成一次访问;性能差;重定向服务器会成为瓶颈api
2.DNS域名解析负载均衡
在DNS服务器上配置多个域名对应IP的记录。例如一个域名
www.baidu.com
对应一组web服务器IP地址,域名解析时通过DNS服务器的算法将一个域名请求分配到合适的真实服务器上。
优缺点:加快访问速度,改善性能。同时因为DNS解析是多级解析,每一级DNS均可能化缓存记录A,当某一服务器下线后,该服务器对应的DNS记录A可能仍然存在,致使分配到该服务器的用户访问失败,并且DNS负载均衡采用的是简单的轮询算法,不能区分服务器之间的差别,不能反映服务器当前运行状态。
3.反向代理负载均衡
反向代理处于web服务器这边,反向代理服务器提供负载均衡的功能,同时管理一组web服务器,它根据负载均衡算法将请求的浏览器访问转发到不一样的web服务器处理,处理结果通过反向服务器返回给浏览器。
优缺点:实现简单,可利用反向代理缓存资源(这是最经常使用的了)及改善网站性能。同时由于是全部请求和响应的中转站,因此反向代理服务器可能成为瓶颈。
以上仅仅是部分实现方式,还有好比IP负载均衡
、数据链路层负载均衡
等等,这些可能涉及到相关网络方面的知识点了,不是很了解,你们有兴趣能够自行搜索下吧。
实现负载均衡也又区分客户端和服务端之分,Ribbon
就是基于客户端的负载均衡。 客户端负载均衡:
服务端负载均衡:
服务端实现负载均衡方式有不少,好比:硬件F5
、Nginx
、HA Proxy
等等,这些应该实施相关人员应该比较熟悉了,本人可能也就对Nginx
了解下,⊙﹏⊙‖∣
RestTemplate
是Spring
提供的用于访问Rest服务
的客户端,RestTemplate
提供了多种便捷访问远程Http服务的方法,可以大大提升客户端的编写效率。
简单来讲,RestTemplate
采用了模版设计
的设计模式,将过程当中与特定实现相关的部分委托给接口,而这个接口的不一样实现定义了接口的不一样行为,因此能够很容易的使用不一样的第三方http服务,如okHttp
、httpclient
等。
RestTemplate
定义了不少的与REST资源交互,这里简单介绍下一些经常使用的请求方式的使用。
在URL上执行特定的HTTP方法,返回包含对象的ResponseEntity
。其余的如GET
、POST
等方法底层都是基于此方法的。
如:
RequestEntity requestEntity = RequestEntity.get(new URI(uri)).build(); ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
RequestEntity<User> requestEntity = RequestEntity.post(new URI(uri)).body(user); ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);
get请求能够分为两类:
getForEntity()
和getForObject()
.
// 1-getForObject() User user1 = this.restTemplate.getForObject(uri, User.class); // 2-getForEntity() ResponseEntity<User> responseEntity1 = this.restTemplate.getForEntity(uri, User.class); HttpStatus statusCode = responseEntity1.getStatusCode(); HttpHeaders header = responseEntity1.getHeaders(); User user2 = responseEntity1.getBody();
其余的方法都大同小异了,能够根据实际的业务需求进行调用。
简单示例:
// 1-postForObject() User user1 = this.restTemplate.postForObject(uri, user, User.class); // 2-postForEntity() ResponseEntity<User> responseEntity1 = this.restTemplate.postForEntity(uri, user, User.class);
关于postForLocation()
,用的比较少,做用是返回新建立资源的URI,前面介绍的二者是返回资源自己,也就是结果集了。
关于其余的请求类型相关用法,这里就不详细阐述了,都是相似的。能够查看下此文章:详解 RestTemplate 操做,讲的蛮详细了。
特别说明:系列教程为了方便,github上分别建立了一个单体的Eureka
注册中心和高可用的Eureka
注册中心,无特殊说明,都是使用单体的Eureka
注册中心进行服务注册与发现的,工程名为:spring-cloud-eureka-server
,端口号为:1000。服务提供方工程名为:spring-cloud-eureka-client
,应用名称为:eureka-client
,端口号为:2000,提供了一个接口:http://127.0.0.1:2000/hello
spring-cloud-eureka-server示例:spring-cloud-eureka-server
spring-cloud-eureka-client示例:spring-cloud-eureka-client
此类是实现客户端负载均衡的关键。自己它是个接口类,位于spring-cloud-commons
包下,此包包含了大量的服务治理相关的抽象接口,好比已经介绍过的DiscoveryClient
、ServiceRegistry
以及LoadBalancerClient实例
等等。
首先,咱们使用最原生的方式去获取调用服务接口。
建立个工程:spring-cloud-eureka-consumer
0.引入pom文件依赖。
<!-- 客户端依赖 --> <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-web</artifactId> </dependency>
1.配置文件添加相关注册中心等信息。
## 服务名称 spring.application.name=eureka-consumer ## 端口号 server.port=8008 #指定注册中心地址 eureka.client.service-url.defaultZone=http://127.0.0.1:1000/eureka # 启用ip配置 这样在注册中心列表中看见的是以ip+端口呈现的 eureka.instance.prefer-ip-address=true # 实例名称 最后呈现地址:ip:2000 eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
2.编写启动类,加入@EnableDiscoveryClient
,申明为一个客户端应用,同时申明一个RestTemplate
,最后是使用RestTemplate
来完成rest服务调用的。
@SpringBootApplication @EnableDiscoveryClient @Slf4j public class EurekaConsumerApplication { public static void main(String[] args) throws Exception { SpringApplication.run(EurekaConsumerApplication.class, args); log.info("spring-cloud-eureka-consumer启动!"); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
3.编写一个调用类,调用spring-cloud-eureka-client
服务提供者提供的服务。
/** * 访问客户端示例 * @author oKong * */ @RestController @Slf4j public class DemoController { @Autowired LoadBalancerClient loadBalancerClient; @Autowired RestTemplate restTemplate; @GetMapping("/hello") public String hello(String name) { ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client"); String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/hello?name=" + name; log.info("url地址为:{}", url); return restTemplate.getForObject(url, String.class); } }
4.启动应用,访问:http://127.0.0.1:8008/hell0?name=oKong ,能够看见控制台输出了利用LoadBalancerClient
的choose
方法,获取到了对应eureka-client
服务ID的服务地址。
最后经过范围对应的http地址进行服务请求:
最后浏览器上能够看见,进行了正确的访问了:
此时,切换到服务提供者spring-cloud-eureka-client
控制台,能够看见日志输出:
此时咱们已经调用成功了,经过LoadBalancerClient
获取到了服务提供者实际服务地址,最后进行调用。
你们能够建立多个的spring-cloud-eureka-client
服务提供者,再去调用下,能够看见会调用不一样的服务地址的。
Spring Cloud Ribbon
是一个基于Http和TCP的客服端负载均衡工具,它是基于Netflix Ribbon
实现的。与Eureka
配合使用时,Ribbon
可自动从Eureka Server (注册中心)
获取服务提供者地址列表,并基于负载均衡
算法,经过在客户端中配置ribbonServerList
来设置服务端列表去轮询访问以达到均衡负载的做用。
上小节,简单的使用LoadBalancerClient
进行了服务实例获取最后调用,也说了其实LoadBalancerClient
是个接口类。而Ribbon
实现了此接口,对应实现类为:RibbonLoadBalancerClient
.
如今咱们来看下,使用Ribbon
的方式如何进行更加优雅的方式进行服务调用。
建立一个工程:spring-cloud-eureka-consumer-ribbon
(其实这个工程和spring-cloud-eureka-consumer
是差很少的,只是有些许不一样。)
0.加入pom依赖
<!-- 客户端依赖 --> <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-web</artifactId> </dependency>
1.配置文件修改,添加注册中心等相关信息。
spring.application.name=eureka-consumer-ribbon server.port=8018 #指定注册中心地址 eureka.client.service-url.defaultZone=http://127.0.0.1:1000/eureka # 启用ip配置 这样在注册中心列表中看见的是以ip+端口呈现的 eureka.instance.prefer-ip-address=true # 实例名称 最后呈现地址:ip:2000 eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
2.编写启动类,加入@EnableDiscoveryClient
,同时申明一个RestTemplate
,这里和原先不一样,就在于加入了@LoadBalanced
注解进行修饰RestTemplate
类,稍后会大体讲解下是如何进行实现的。
@SpringBootApplication @EnableDiscoveryClient @Slf4j public class EurekaConsumerRibbonApplication { public static void main(String[] args) throws Exception { SpringApplication.run(EurekaConsumerRibbonApplication.class, args); log.info("spring-cloud-eureka-consumer-ribbon启动!"); } //添加 @LoadBalanced 使其具有了使用LoadBalancerClient 进行负载均衡的能力 @Bean @LoadBalanced public RestTemplate restTemplage() { return new RestTemplate(); } }
3.编写测试类,进行服务调用。
/** * ribbon访问客户端示例 * @author oKong * */ @RestController @Slf4j public class DemoController { @Autowired RestTemplate restTemplate; @GetMapping("/hello") public String hello(String name) { //直接使用服务名进行访问 log.info("请求参数name:{}", name); return restTemplate.getForObject("http://eureka-client/hello?name=" + name, String.class); } }
能够看见,能够直接注入RestTemplate
,经过服务名直接调用.
4.启动应用,访问:http://127.0.0.1:8018/hello?name=oKong ,能够看见调用成功:
控制台输出:
能够从以上示例中,能够看出,咱们就加了一个
@LoadBalanced
注解修饰RestTemplate
bean类,就实现了服务的调用。如今来简单看看具体是如何实现的。
首先,咱们看看此注解的代码说明:
从注释能够看出,该注解用来给RestTemplate作标记,以使用负载均衡的客户端LoadBalancerClient
。
如今来看一眼相同包下的类的状况,能够看到有个LoadBalancerAutoConfiguration
,字面意思能够知道这是一个自动配置类,此类就是咱们要找的关键类了。
LoadBalancerAutoConfiguration
,此类不长,一百来行,这里就不贴了。
简单说明下: 首先,此类生效的条件是
@ConditionalOnClass(RestTemplate.class) @ConditionalOnBean(LoadBalancerClient.class)
RestTemplate
类必须存在于当前工程的环境中。LoadBalancerClient
的实现Bean。该自动化配置类中,主要作了几件事情:
@LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList();
同时为其每一个对象经过调用RestTemplateCustomizer
添加了一个LoadBalancerInterceptor
和RetryLoadBalancerInterceptor
拦截器(有生效条件),其为ClientHttpRequestInterceptor
接口的实现类,ClientHttpRequestInterceptor
是RestTemplate
的请求拦截器
RetryLoadBalancerInterceptor拦截器
LoadBalancerInterceptor拦截器
咱们主要看下LoadBalancerInterceptor
:
能够看见,最后是实现了ClientHttpRequestInterceptor
接口的实现类执行execute
方法进行.
从继承关系里,此实现类就是RibbonLoadBalancerClient
类了。
RibbonLoadBalancerClient
类:
简单来讲:最后仍是经过loadBalancerClient.choose()
获取到服务实例,最经过拼凑http地址来进行最后的服务调用。
整体来讲,就是经过为加入@LoadBalanced
注解的RestTemplate
添加一个请求拦截器,在请求前经过拦截器获取真正的请求地址,最后进行服务调用。
里面的细节就不阐述了,毕竟源码分析不是很在行呀,你们能够跟踪进去一探究竟吧。
友情提醒:若被@LoadBalanced
注解的RestTemplate
访问正常的服务地址,如http://127.0.0.1:8080/hello
时,是会提示没法找到此服务的。
具体缘由:serverid
必须是咱们访问的服务名称
,当咱们直接输入ip
的时候获取的server
是null
,就会抛出异常。
此时,如果须要调用非注册中心的服务,能够建立一个不被@LoadBalanced
注解的RestTemplate
,同时指定bean的名称,使用时,使用@Qualifier
指定name注入此RestTemplate
。
@Bean("normalRestTemplage") public RestTemplate normalRestTemplage() { return new RestTemplate(); } //使用 @Autowired @Qualifier("normalRestTemplage") RestTemplate normalRestTemplate; @GetMapping("/ip") public String ip(String name) { //直接使用服务名进行访问 log.info("使用ip请求,请求参数name:{}", name); return normalRestTemplate.getForObject("http://127.0.0.1:2000/hello?name=" + name, String.class); }
目前还未进行过自定义负载均衡,这里就简单的举例下,上次整理ppt时有讲过一些,但未深刻了解过⊙﹏⊙‖∣,
能够从继承关系看出,是经过继承IRule
来实现的。
可继承ClientConfigEnabledRoundRobinRule,来实现本身负载均衡策略。
从上一章节,咱们知道,当咱们要调用一个服务时,须要知道服务名和api地址,这样才能进行服务调用,服务少时,这样写以为没有什么问题,但当服务一多,接口参数不少时,上面的写法就显得不够优雅了。因此,接下来,来讲说一种更好更优雅的调用服务的方式:Feign。
Feign
是Netflix
开发的声明式、模块化的HTTP客户端。Feign
可帮助咱们更好更快的便捷、优雅地调用HTTP API
。
在Spring Cloud
中,使用Feign
很是简单——建立一个接口,并在接口上添加一些注解。Feign
支持多种注释,例如Feign自带的注解或者JAX-RS注解等 Spring Cloud对Feign进行了加强,使Feign支持了Spring MVC注解,并整合了Ribbon和 Eureka,从而让Feign 的使用更加方便。只须要经过建立接口并用注解来配置它既可完成对Web服务接口的绑定。
建立个spring-cloud-eureka-consumer-ribbon
工程项目。
0.加入feigin
依赖
<!-- feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- eureka客户端依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- rest api --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
1.配置文件
spring.application.name=eureka-consumer-feign server.port=8028 #指定注册中心地址 eureka.client.service-url.defaultZone=http://127.0.0.1:1000/eureka # 启用ip配置 这样在注册中心列表中看见的是以ip+端口呈现的 eureka.instance.prefer-ip-address=true # 实例名称 最后呈现地址:ip:2000 eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
2.建立启动类,加入注解@EnableFeignClients
,开启feign
支持。
@SpringBootApplication @EnableFeignClients @Slf4j public class EurekaConsumerFeignApplication { public static void main(String[] args) throws Exception { SpringApplication.run(EurekaConsumerFeignApplication.class, args); log.info("spring-cloud-eureka-consumer-feign启动"); } }
3.建立一个接口类IHelloClient
,加入注解@FeignClient
来指定这个接口所要调用的服务名称。
@FeignClient(name="eureka-client") public interface IHelloClient { /** * 定义接口 * @param name * @return */ @RequestMapping(value="/hello", method=RequestMethod.GET) public String hello(@RequestParam("name") String name); }
4.建立一个demo控制层,引入此接口类。
/** * feign 示例 * @author OKong * */ @RestController @Slf4j public class DemoController { @Autowired IHelloClient helloClient; @GetMapping("/hello") public String hello(String name) { log.info("使用feign调用服务,参数name:{}", name); return helloClient.hello(name); } }
5.启动应用,访问:http://127.0.0.1:8028/hello?name=oKong-feign
是否是很简单,和调用本地服务是同样的了!
Feign
支持继承,但不支持多继承。使用继承,可将一些公共操做分组到一些父类接口中,从而简化Feign的开发。
因此在实际开发中,调用服务接口时,可直接按接口类和实现类进行编写,调用方引入接口依赖,继承一个本地接口,这样接口方法默认都是定义好的,也少了不少编码量。用起来就更爽了,就是有点依赖性,对方服务修改后须要同步更新下,但这个团队内部约定下问题不大的
这里简单实例下,建立一个spring-cloud-eureka-client-api
工程。
0.加入依赖,注意此依赖的做用范围:
<!--api接口依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>provided</scope> </dependency>
1.编写一个接口类IHellpApi
:
public interface IHelloApi { //定义提供者服务名 public static final String SERVICE_NAME = "eureka-client"; /** * 定义接口 * @param name * @return */ @RequestMapping(value="/hello", method=RequestMethod.GET) public String hello(@RequestParam("name") String name); }
修改spring-cloud-eureka-client
工程
0.引入api依赖
<!-- 导入接口依赖 --> <dependency> <groupId>cn.lqdev.learning</groupId> <artifactId>spring-cloud-eureka-client-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
1.建立一个HelloApiImpl
类,实现IHelloApi
:
/** * 使用接口方式进行接口编写 * @author oKong * */ @RestController @Slf4j public class HelloApiImpl implements IHelloApi { @Override public String helloApi(@RequestParam("name") String name) { log.info("[spring-cloud-eureka-client]服务[helloApi]被调用,参数name值为:{}", name); return name + ",helloApi调用!"; } }
此时,HelloApiImpl
是个控制层也是个接口实现类了。
修改spring-cloud-eureka-consumer-feign
工程。 0.引入api依赖
<!-- 导入接口依赖 --> <dependency> <groupId>cn.lqdev.learning</groupId> <artifactId>spring-cloud-eureka-client-api</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
1.一样建立一个接口,使其继承IHelloApi
:
/** * 直接继承接口 * @author Okong * */ @FeignClient(name = IHelloApi.SERVICE_NAME) public interface HelloApi extends IHelloApi{ }
小技巧:能够在IHelloApi
定义一个服务名变量,如:SERVICE_NAME,这样让提供者进行变量的赋值,能够避免一些没必要要的交流成本的,如有变化,服务调用方也无需关心的。一切都是约定编程!
2.修改下DemoController
类,注入HelloApi
:
@Autowired HelloApi helloApi; @GetMapping("hello2") public String hello2(String name) { log.info("使用feign继承方式调用服务,参数name:{}", name); return helloApi.helloApi(name); }
3.分别启动各服务,访问:http://127.0.0.1:8028/hello2?name=oKong-api
使用起来没啥差异的,同样的调用,但对于调用方而言,能够无需去理会具体细节了,照着接口方法去传参就行了。
这种方式,和原来的dubbo
调用的方式是相似的,简单方便。你们能够把接口和实体放入一个包中,调用者和提供者都进行依赖便可。
在使用Feign
时,会遇见一些问题,为了不没必要要的错误,如下这些须要额外注意下。
本章节主要讲解了下服务消费者如何利用原生、ribbon、fegin三种方式进行服务调用的,其实每种调用方式都是使用
ribbon
来进行调用的,只是有些进行了加强,是的使用起来更简单高效而已。对于其原理的实现,本文未进行详细阐述,你们能够谷歌想相关知识,跟踪下源码了解下,本人也还没有深刻研究过,仍是停留在使用阶段,以后有时间了看一看,有啥心得再来分享吧。此时若服务上线下线,调用者调用可能会出现短暂的调用异常,最多见的就是找不到服务,此时服务容错保护就排上用场了,因此下一章节,就来讲说关于服务容错保护
相关知识点~
目前互联网上大佬都有分享
SpringCloud
系列教程,内容可能会相似,望多多包涵了。原创不易,码字不易,还但愿你们多多支持。若文中有错误之处,还望提出,谢谢。
499452441
lqdevOps
我的博客:http://blog.lqdev.cn
源码示例:https://github.com/xie19900123/spring-cloud-learning
原文地址:http://blog.lqdev.cn/2018/09/21/SpringCloud/chapter-four/