Spring微服务实战--注解版(第四章)

第四章 服务发现

在任何分布式的架构中,想要调用某个服务,必须知道服务所在机器的物理地址。其实这就是服务发现的概念。服务发现对于微服务的作用不言而喻,它也正式成为了微服务研究的其中一个主题,原因主要有:服务发现可以实现服务实例的水平伸缩,也就是可以增添服务实例,其次,服务发现提供了一种弹性的应用程序,说的直白点,就是可以对发生故障的实例进行删除或者绕过它。
绕过文中的通过域名解析或者负载均衡实现服务发现的方法。我们来了解下基于云的应用程序实现一个健壮的服务发现机制。首先需要了解几个概念:

  • 服务注册—服务如何使用服务发现代理进行注册
  • 服务地址的客户端查找
  • 跨节点共享服务信息
  • 服务如何将其健康状况回传给代理

我们从下面一个图简单地了解下服务发现的架构
在这里插入图片描述
这是一种较好的方案,可是美中不足,可以发现服务客户端完全依赖于服务发现引擎来查找和调用服务,这种方法比较脆弱。而更健壮的方法是使用客户端负载均衡,客户端负载均衡,很明显地,是在客户端本地实现的负载均衡,客户端将服务实例的IP缓存在本地,客户端缓存将定期使用服务发现层进行刷新缓存。
好了,前面介绍的都是理论知识,我们接下来看下,服务发现和服务路由的实现框架,首先来看使用SpringCloud和Eureka进行服务发现,使用ribbon实现负载均衡。前面的章节介绍了一个许可证服务,接下来我们让许可证服务调用组织服务,而组织服务我们将会向注册中心注册两个实例,使用ribbon实现许可证服务本地的负载均衡,来调用组织服务。还是简单画张图
在这里插入图片描述
接下来的话很重要

  • 随着服务的启动,许可证和组织服务将通过Eureka进行服务注册,这个注册过程将告诉Eureka每个服务实例的IP和端口,以及服务的服务ID。
  • 当许可证服务调用组织服务时,许可证服务将使用Ribbon库来提供客户端负载均衡,Ribbon将使用Eureka检索服务位置信息,然后在本地进行缓存。
  • Ribbon将定期对对Eureka进行ping,并刷新服务位置的本地缓存
    重要的话说完了,接下来就是实战环节。
    新建一个子工程,表示我们的注册服务,新增的重要依赖就一项如下:

在这里插入图片描述
接下来,看下Eureka服务的配置信息,即application.yml

#Default port is 8761
server:
#Eureka监听的端口
  port: 8761

eureka:
  client:
  #不要使用Eureka服务进行注册,因为其本身就是Eureka
    registerWithEureka: false
    #不要在本地缓存注册表信息
    fetchRegistry: false
  server:
  #Eureka不会马上通知天下其注册的服务,默认会等5分钟,让所有服务都有机会向其注册
  #每次服务注册需要30s的时间,需要连续向Eureka发3次心跳包,每次间隔10s,然后才能使用这个服务
    waitTimeInMsWhenSyncEmpty: 5
  serviceUrl:
    defaultZone: http://localhost:8761
  

看下引导类,只需要一个注解,就可以让我们的服务成为注册服务,
在这里插入图片描述
下面我们启动下这个注册服务。其实Eureka是个可视化界面,我们输入对应的IP和端口就可以看到这个界面
在这里插入图片描述
看到这个界面,也说明Eureka启动正常。现在没有服务向Eureka进行注册,接下来我们实现服务的注册。
在组织服务中,首先需要添加对于Eureka的依赖。依赖项同上。
接下来就是重要的,需要告诉SpringBoot通过Eureka来注册其服务实例。但是它却出奇地简单,因为只需要几个配置项,在application.yml中,如下:

eureka:
  instance:
  #注册服务的IP,而不是机器名称
    preferIpAddress: true
  client:
  #通过Eureka注册服务
    registerWithEureka: true
    #获取注册表的副本,不用每次都调用Eureka来查找服务,每隔30s刷新
    fetchRegistry: true
    serviceUrl:
    #Eureka的列表
        defaultZone: http://localhost:8761/eureka/
        
spring:
  application:
  #服务注册的逻辑名称,每个通过Eureka注册的服务都有两个ID,应用程序ID,也就是前面的逻辑名称
  #用于表示一组服务实例;还有每个实例的实例ID,其是一个随机数
    name: organization

        

接下来启动组织服务。但是在引导类中,要加个注解,不要忘记
在这里插入图片描述
启动成功后,重新刷新Eureka的界面,可以看到有服务注册成功了:
在这里插入图片描述
下面实现,许可证服务对于组织服务的调用,作者介绍了三种模式,
在介绍Feign之前,还是先介绍下基本的服务发现,如下代码所示,核心代码片段如下:

@Component
public class OrganizationDiscoveryClient {

    @Autowired
    private DiscoveryClient discoveryClient;

    public Organization getOrganization(String organizationId) {
        RestTemplate restTemplate = new RestTemplate();
        //获取所有组织服务的实例
        List<ServiceInstance> instances = discoveryClient.getInstances("organizationservice");

        if (instances.size()==0) return null;
        String serviceUri = String.format("%s/v1/organizations/%s",instances.get(0).getUri().toString(), organizationId);
        //实现服务的调用
        ResponseEntity< Organization > restExchange =
                restTemplate.exchange(
                        serviceUri,
                        HttpMethod.GET,
                        null, Organization.class, organizationId);

        return restExchange.getBody();
    }
}

上面的代码基本上不用太多解释,拿到要调用服务的所有实例,取出其中一个,拼装调用的uri,使用Spring的模板类去调用服务。但是上述代码的问题是,一没有实现负载均衡,二写的代码太多了,我们的态度是,注解即所有,哈哈。我们先来解决第一个问题,要想实现负载均衡,只需要在引导类中加一个@LoadBalance的注解来定义RestTemplet,如下所示:

@SpringBootApplication
//这个注解是使SpringBooot可以使用DisCovry和Ribbon库
@EnableDiscoveryClient
@EnableFeignClients
public class Application {

  @LoadBalanced
  @Bean
  public RestTemplate getRestTemplate(){
      return new RestTemplate();
  }

  public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
  }

至于具体的调用,我们这里就不再展示,因为平时可能用的少些,这里只是告诉大家有这么回事,接下来我们介绍Feign客户端调用,Feign是Spring启用Ribbond RestTemplet类的替代方案,核心代码如下:
首先引导类需要加入一个注解@EnableFeignClients,用于说明启用Feign客户端

@SpringBootApplication
//这个注解是使SpringBooot可以使用DisCovry和Ribbon库
@EnableDiscoveryClient
@EnableFeignClients
public class Application {

  @LoadBalanced
  @Bean
  public RestTemplate getRestTemplate(){
      return new RestTemplate();
  }

  public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
  }
}

编写Feign客户端调用,也比较方便,是使用注解来标注接口,以映射Ribbon将要调用的基于Eureka的服务,SpringCloud框架将生成一个动态代理类,用于调用目标REST服务,调用代码如下

    private Organization retrieveOrgInfo(String organizationId, String clientType){
        Organization organization = null;

        switch (clientType) {
            case "feign":
                System.out.println("I am using the feign client");
                organization = organizationFeignClient.getOrganization(organizationId);
                break;
            case "rest":
                System.out.println("I am using the rest client");
                organization = organizationRestClient.getOrganization(organizationId);
                break;
            case "discovery":
                System.out.println("I am using the discovery client");
                organization = organizationDiscoveryClient.getOrganization(organizationId);
                break;
            default:
                organization = organizationRestClient.getOrganization(organizationId);
        }

        return organization;
    }

上面代码的organizationFeignClient的代码如下:

//使用该注解表示要调用的注册在Eureka上的服务,value代表服务的逻辑名称,也就是应用ID
@FeignClient("organizationservice")
public interface OrganizationFeignClient {
    //定义要调用的端点
    @RequestMapping(
            method= RequestMethod.GET,
            value="/v1/organizations/{organizationId}",
            consumes="application/json")
    Organization getOrganization(@PathVariable("organizationId") String organizationId);
}

让我们启动所有服务来实现调用,启动的服务包括配置服务、注册服务、许可证服务、组织服务,其实这已经是一个微服务的概念了,不知道到这里大家有没有感觉。注意的一点是,Eureka服务要先启动,再启动许可证和组织服务,不然会报错,因为他们要注册自己,但是发现,注册服务器居然还没启动,所以会抛异常,异常信息如下:

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server
	at com.netflix.discovery.shared.transport.decorator.RetryableEurekaHttpClient.execute(RetryableEurekaHttpClient.java:111) ~[eureka-client-1.4.12.jar:1.4.12]
	at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) ~[eureka-client-1.4.12.jar:1.4.12]
	at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator$1.execute(EurekaHttpClientDecorator.java:59) ~[eureka-client-1.4.12.jar:1.4.12]
	at com.netflix.discovery.shared.transport.decorator.SessionedEurekaHttpClient.execute(SessionedEurekaHttpClient.java:77) ~[eureka-client-1.4.12.jar:1.4.12]
	at com.netflix.discovery.shared.transport.decorator.EurekaHttpClientDecorator.register(EurekaHttpClientDecorator.java:56) ~[eureka-client-1.4.12.jar:1.4.12]
	at com.netflix.discovery.DiscoveryClient.register(DiscoveryClient.java:815) ~[eureka-client-1.4.12.jar:1.4.12]
	at com.netflix.discovery.InstanceInfoReplicator.run(InstanceInfoReplicator.java:104) [eureka-client-1.4.12.jar:1.4.12]
	at com.netflix.discovery.InstanceInfoReplicator$1.run(InstanceInfoReplicator.java:88) [eureka-client-1.4.12.jar:1.4.12]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_211]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_211]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_211]
	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [na:1.8.0_211]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_211]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_211]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_211]

2020-05-15 22:02:03.511  WARN 8188 --- [nfoReplicator-0] c.n.discovery.InstanceInfoReplicator     : There was a problem with the instance info replicator

还有一个题外话,搞微服务实战,建议大家换台好点的电脑,不然起几个服务会卡爆。
我们通过客户端进行调用,结果如下:
在这里插入图片描述
这个过程并不容易,遇到了很多坑:
1)首先,因为POSTGRESQL相关的依赖没去掉,组织服务一直起不来,因为我们换成了MYSQL数据库
2)注意配置服务器的搜索路径这块,不要忘了对应的配置,修改配置后一定记得重启配置服务器:
在这里插入图片描述
3)比较坑的是,虽然组织服务没有连接到数据库,但是服务启动过程中,并没有报相关的错,而是报空指针异常,报的是查不到数据。
这一节就先到这里了。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值