假设有2个微服务A和B分别在端点http:// localhost:8181 /和http:// localhost:8282 /上运行,若是想要在A服务中调用B服务,那么咱们须要在A服务中键入B服务的url,这个url是负载均衡器分配给咱们的,包括负载平衡后的IP地址,那么很显然,B服务与这个URL硬编码耦合在一块儿了,若是咱们使用了服务自动注册机制,就可使用B服务的逻辑ID,而不是使用特定IP地址和端口号来调用服务。java
咱们可使用Netflix Eureka Server建立Service Registry服务器,并将咱们的微服务同时做为Eureka客户端,这样一旦咱们启动微服务,它将自动使用逻辑服务ID向Eureka Server注册。而后,其余微服务(一样也是Eureka客户端)就可使用服逻辑务ID来调用REST端点服务了。git
Spring Cloud使用Load Balanced RestTemplate建立Service Registry并发现其余服务变得很是容易。github
除了使用Netflix Eureka Server做为服务发现,也可使用Zookeeper,可是根据CAP定理,在须要P网络分区容忍性状况下,强一致性C和高可用性A只能选择一个,Zookeeper是属于CP,而Eureka是属于AP,在服务发现方面,高可用性才是更重要,不然没法完成服务之间调用,而服务信息是否一致则不是最重要,A服务发现B服务时,B服务信息没有及时更新,可能发生调用错误,可是调用错误总比没法链接到服务注册中心要强。不然,服务注册中心就成为整个系统的单点故障,存在极大的单点风险,这是咱们为何须要分布式系统的首要缘由。web
让咱们使用Netflix Eureka建立一个Service Registry,它只是一个带有Eureka Server启动器的SpringBoot应用程序。spring
使用Intellij的Idea开发工具是很是容易启动Spring cloud的:数据库
能够从https://start.spring.io/网址,选择相应组件便可。bash
因为咱们须要创建一个注册服务器,所以选择Eureka Server组件便可,经过这些自动工具其实是能自动生成Maven的配置:服务器
org.springframework.cloud网络
spring-cloud-starter-netflix-eureka-server并发
咱们须要给SpringBoot启动类添加**@EnableEurekaServer**注释,以使咱们的SpringBoot应用程序成为基于Eureka Server的Service Registry。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class ServiceRegistryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRegistryApplication.class, args);
}
}
默认状况下,每一个Eureka服务器也是Eureka客户端,客户端必定会须要一个服务器URL来定位,不然就会不断报错,因为咱们只有一个Eureka Server节点(独立模式),咱们将经过在application.properties文件中配置如下属性来禁用此客户端行为。
SpringCloud有properties和YAML两种配置方式,这两种配置方式其实只是形式不一样,properties配置信息格式是a.b.c,而YAML则是a:b:c:,二者本质是同样的,只须要其中一个便可,这里以properties为案例:
spring.application.name=jdon-eureka-server
server.port=1111
eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
复制代码
如今运行ServiceRegistryApplication并访问http:// localhost:1111,若是不能访问,说明没有正常启动,请检查三个环节:pom.xml是否配置正确?须要Eureka和配置
SpringBoot的注释@EnableEurekaServer是否增长了?
最后,application.properties是否配置?
SpringCloud其实很是简单,约定大于配置,默认只要配置服务器端口就能够了,而后是一条注释**@EnableEurekaServer**,就能启动Eurek服务器了。
服务器准备好后,咱们就要准备服务生产者,向服务器里面注册本身,服务消费者则是从服务器中发现注册的服务而后调用。
服务生产者其实首先是Eureka的客户端,生产者将本身注册到前面启动的服务器当中,引若是是idea的导航,选择CloudDiscovery的EurekaDiscovery,若是是 Maven则引入包依赖是:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
这样,spring-cloud-starter-netflix-eureka-client这个jar包就放入咱们系统的classpath,为了可以正常使用这个jar包,还须要配置,只须要在application.properties中配置eureka.client.service-url.defaultZone属性便可自动注册Eureka Server:
eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
当咱们的服务在Eureka Server注册时,它会持续发送必定时间间隔的心跳。若是Eureka服务器没有从任何服务的实例接收到心跳,它将认为这个服务实例已经关闭并从本身的池中剔除它。
以上是服务生产者注册服务的过程,比较简单,为了使咱们的服务生产者能的演示代码够运行起来,咱们还须要新建一个服务生产者代码:
@RestController
public class ProducerService {
@GetMapping("/pengproducer")
public String sayHello(){
return "hello world";
}
}
复制代码
这段代码是将服务暴露成RESTful接口,@RestController是声明Rest接口,/pengproducer是REST的访问url,经过get方式可以得到字符串:hello world
由于REST属于WEB的一种接口,所以须要在pom.xml中引入Web包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
复制代码
而后在application.properties中加入有关REST接口的配置:
spring.application.name=PengProducerService
server.port=2111
复制代码
指定咱们的生产者服务的名称是PengProducerService,REST端口开在2111。
如今能够在idea中启动咱们的应用了,这样咱们启动这个项目,就能够在http://127.0.0.1:2111/ 访问这个REST服务。同时,由于咱们以前已经启动了注册服务器,访问http://localhost:1111/你会发现PengProducerService出如今服务列表中:
上面启动应用服务是在idea编辑器中,咱们还能够经过命令行启动咱们的服务生产者:
java -jar -Dserver.port=2112 producer-0.0.1-SNAPSHOT.jar
这个是在端口2112开启咱们的服务端点了。如今再问http://localhost:1111/,你会看到可用节点Availability Zones下面已经从(1)变为(2),如今咱们的服务生产者已经有两个实例在运行,当服务的消费者访问这个两个实例时,它能够根据负载平衡策略好比轮询访问其中一个服务生产者实例。
总结一下,为了让服务生产者注册到Euraka服务器中,只须要两个步骤:
请注意,spring-cloud-starter-netflix-eureka-client包是Spring Cloud升级后最新的包名,原来是spring-cloud-starter-eureka,里面没有netflix,这是过去版本,Spring Boot 1.5之后都是加入了netflix的,见Spring Cloud Edgware Release Notes
另外,这里不须要在SpringBoot主代码中再加入@enablediscoveryclient 或 @enableeurekaclient,只要eureka的client包在maven中配置,也就会出如今系统的classpath中,这样就会默认自动注册到eureka服务器中了。
这部分源码下载:百度网盘。
下面咱们准备访问这个服务生产者PengProducerService的消费者服务:
上个章节咱们已经启动了两个服务生产者实例,如何经过负载平衡从两个中选择一个访问呢?这时就须要Ribbon,为了使用Ribbon,咱们须要使用@LoadBalanced元注解,那么这个注解放在哪里呢?通常有两个DiscoveryClient 和 RestTemplate,这两个的区别是:
1. DiscoveryClient能够得到服务提供者(生产者)的多个实例集合,能让你手工决定选择哪一个实例,这里负载平衡的策略好比round robin轮询就不会派上,实际就没有使用Ribbon:
List<ServiceInstance> instances=discoveryClient.getInstances("PengProducerService");
ServiceInstance serviceInstance=instances.get(0);
复制代码
2.RestTemplate则是使用Ribbon的负载平衡策略,使用@LoadBalanced注释resttemplate并使用zuul代理服务器做为边缘服务器。那么对zuul边缘服务器的任何请求将默认使用Ribbon进行负载平衡,而resttemplate将以循环方式路由请求。这部分代码以下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.client.RestTemplate;
@Controller
public class ConsumerService {
@Autowired
private RestTemplate restTemplate;
public String callProducer() {
ResponseEntity<String> result =
this.restTemplate.getForEntity(
"http://PengProducerService/pengproducer",
String.class,
"");
if (result.getStatusCode() == HttpStatus.OK) {
System.out.printf(result.getBody() + " called in callProducer");
return result.getBody();
} else {
System.out.printf(" is it empty");
return " empty ";
}
}
}
复制代码
RestTemplate是自动注射进这个控制器,在这控制器,咱们调用了服务生产者http://PengProducerService/pengproducer,而后得到其结构。
这个控制器的调用咱们能够在SpringBoot启动函数里调用:
@SpringBootApplication
public class ConsumerApplication {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(ConsumerApplication
.class, args);
ConsumerService consumerService = ctx.getBean(ConsumerService.class);
System.out.printf("final result RestTemplate=" + consumerService
.callProducer() + " \n");
}
}
复制代码
注意到@LoadBalanced是标注在RestTemplate上,而RestTemplate是被注入到ConsumerService中的,这样经过调用RestTemplate对象实际就是得到负载平衡后的服务实例。这个能够经过咱们的服务提供者里面输出hashcode来分辨出来,启动两个服务提供者实例,每次运行ConsumerService,应该是依次打印出不一样的hashcode:
hello world1246528978 called in callProducerfinal result RestTemplate=hello world1246528978
再次运行结果:
hello world1179769159 called in callProducerfinal result RestTemplate=hello world1179769159
hellow world后面的哈希值不一样,可见是来自不一样的服务提供者实例。
若是系统基于https进行负载平衡,那么只须要两个步骤:
1.application.properties中激活ribbon的https:
ribbon.IsSecure=true
2.代码中RestTemplate初始化时传入ClientHttpRequestFactory对象:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
CloseableHttpClient httpClient = HttpClientUtil.getHttpClient();
HttpComponentsClientHttpRequestFactory clientrequestFactory = new HttpComponentsClientHttpRequestFactory();
clientrequestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(clientrequestFactory);
return restTemplate;
}
复制代码
这部分源码下载:百度网盘
上篇是使用Ribbon实现对多个服务生产者实例使用负载平衡的方式进行消费,在调用服务生产者时,返回的是字符串类型,若是返回是各类本身定义的对象,这些对象传递到消费端是经过JSON方式,那么咱们的消费者须要使用Feign来访问各类Json对象。
须要注意的是:Feign = Eureka +Ribbon + RestTemplate,也就是说,使用Feign访问服务生产者,无需前面章节那么关于负载平衡的代码了,前面咱们使用RestTemplate进行负载平衡访问,代码仍是挺复杂
如今咱们开始Feign的实现:首先咱们在服务的生产者那边进行修改,让咱们生产者项目变得接近实战中项目,增长领域层、服务层和持久层。
假设新增Article领域模型对象,咱们就须要仓储保存,这里咱们使用Spring默认约定,使用JPA访问h2数据库,将Article经过JPA保存到h2数据库中:
要启用JPA和h2数据库,首先只要配置pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
复制代码
Article领域模型对象做为须要持久的实体对象:配置实体@Entity和@Id主键便可:
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String body;
private Date startDate;
复制代码
而后咱们创建一个空的Article仓储接口便可:
@Repository
public interface ArticleRep extends JpaRepository<Article,Long> {
}
复制代码
这样,关于Article的CRUD实现就已经有了,不须要本身再编写任何SQL语句。这样咱们编写一个Service就能够提供Article对象的CRUD方法,这里只编写插入和查询批量两个方法:
@Service
public class ArticleService {
@Autowired
ArticleRep articleRep;
public List<Article> getAllArticles(){
return articleRep.findAll();
}
public void insertArticle(Article article){
articleRep.save(article);
}
}
复制代码
咱们在REST接口中暴露这两种方法:
@RestController
public class ProducerService {
@Autowired
ArticleService articleService;
@GetMapping("/articles")
public List<Article> getAllArticles(){
return articleService.getAllArticles();
}
@GetMapping("/article")
public void publishArticle(@RequestBody Article article){
articleService.insertArticle(article);
}
复制代码
上面服务的生产者提供了两个REST url,咱们在消费者这边使用/articles以得到全部文章:
@FeignClient(name="PengProducerService")
public interface ConsumerService {
@GetMapping("/articles")
List<Article> getAllArticles();
}
复制代码
这是咱们消费者的服务,调用生产者 /articles,这是一个接口,无需实现,注意须要标注FeignClient,其中写入name或value微服务生产者的application.properties配置:
spring.application.name=PengProducerService
固然,这里会直接耦合PengProducerService这个名称,咱们之后能够经过配置服务器更改,这是后话。
而后须要在应用Application代码加入@EnableFeignClients:
@SpringBootApplication
@EnableFeignClients
public class FeignconsumerApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(FeignconsumerApplication
.class, args);
ConsumerService consumerService = context.getBean(ConsumerService
.class);
System.out.printf("#############all articles ok" + consumerService
.getAllArticles());
}
复制代码
在FeignconsumerApplication咱们调用了前面接口ConsumerService,而ConsumerService则经过负载平衡调用另一个生产者微服务,若是咱们给那个生产者服务加入一些Articles数据,则这里就能返回这些数据:
#############all articles ok[com.example.feignconsumer.domain.Article@62b475e2, com.example.feignconsumer.domain.Article@e9474f]
说明调用成功。
在调试过程当中,曾经出现错误:
Load balancer does not have available server for client:PengProducerService
常常排查是因为生产者项目中pom.xml导入的是spring-cloud-starter-netflix-eureka-client,改成pring-cloud-starter-netflix-eureka-server就能够了,这是SpringBoot 2.0发现的一个问题。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
复制代码
本章的代码下载:百度网盘
经过这个项目学习,咱们如同蚕丝剥茧层层搞清楚了Spring Cloud的微服务之间同步调用方式,发现基于REST/JSON的调用代码最少,也是最方便,Feign封装了Ribbon负载平衡和Eureka服务器访问以及REST格式处理。