SpringCloud服务发现注册Eureka +Ribbon

作者: 程序员北游 | 来源:发表于2019-04-19 16:59 被阅读15次

    喜欢的朋友可以关注下专栏:Java架构技术进阶。里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦。

    在本期将学习以下知识点:

    • 什么是服务注册和发现?
    • 基于Eureka的注册服务器
    • 服务生产者
    • 结合Ribbon服务消费者
    • 结合Feign的服务生产者和消费者

    什么是服务注册和发现

    假设有2个微服务A和B分别在端点http:// localhost:8181 /和http:// localhost:8282 /上运行,如果想要在A服务中调用B服务,那么我们需要在A服务中键入B服务的url,这个url是负载均衡器分配给我们的,包括负载平衡后的IP地址,那么很显然,B服务与这个URL硬编码耦合在一起了,如果我们使用了服务自动注册机制,就可以使用B服务的逻辑ID,而不是使用特定IP地址和端口号来调用服务。

    我们可以使用Netflix Eureka Server创建Service Registry服务器,并将我们的微服务同时作为Eureka客户端,这样一旦我们启动微服务,它将自动使用逻辑服务ID向Eureka Server注册。然后,其他微服务(同样也是Eureka客户端)就可以使用服逻辑务ID来调用REST端点服务了。

    Spring Cloud使用Load Balanced RestTemplate创建Service Registry并发现其他服务变得非常容易。

    除了使用Netflix Eureka Server作为服务发现,也可以使用Zookeeper,但是根据CAP定理,在需要P网络分区容忍性情况下,强一致性C和高可用性A只能选择一个,Zookeeper是属于CP,而Eureka是属于AP,在服务发现方面,高可用性才是更重要,否则无法完成服务之间调用,而服务信息是否一致则不是最重要,A服务发现B服务时,B服务信息没有及时更新,可能发生调用错误,但是调用错误总比无法连接到服务注册中心要强。否则,服务注册中心就成为整个系统的单点故障,存在极大的单点风险,这是我们为什么需要分布式系统的首要原因。

    基于Eureka的注册服务器

    让我们使用Netflix Eureka创建一个Service Registry,它只是一个带有Eureka Server启动器的SpringBoot应用程序。

    使用Intellij的Idea开发工具是非常容易启动Spring cloud的:

    可以从https://start.spring.io/网址,选择相应组件即可。

    由于我们需要建立一个注册服务器,因此选择Eureka Server组件即可,通过这些自动工具实际上是能自动生成Maven的配置:

    <dependency>

    <groupId>org.springframework.cloud</groupId>
    
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
    

    </dependency>

    我们需要给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则引入包依赖是:

    <dependency>

    <groupId>org.springframework.cloud</groupId>
    
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    

    </dependency>

    这样,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服务器中,只需要两个步骤:

    • 1. 引入spring-cloud-starter-netflix-eureka-client包
    • 2. 配置Eurake服务器的地址

    请注意,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,为了使用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;
    }
    

    这部分源码下载:百度网盘

    结合Feign的服务生产者和消费者

    上篇是使用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接口中暴露这两种方法:

    • 1. get /articles是查询所有对象
    • 2. post /article是新增
    @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格式处理。

    喜欢的朋友可以关注下专栏:Java架构技术进阶。里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦。
    如果你对技术提升很感兴趣,欢迎1~5年的工程师可以加入我的Java进阶之路来交流学习:908676731。里面都是同行,有资源共享,还有大量面试题以及解析。欢迎一到五年的工程师加入,合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!

    相关文章

      网友评论

        本文标题:SpringCloud服务发现注册Eureka +Ribbon

        本文链接:https://www.haomeiwen.com/subject/vfhcgqtx.html