zoukankan      html  css  js  c++  java
  • spring cloud项目01:服务注册与发现

    Java 8

    spring boot 2.5.2

    spring cloud 2020.0.3

    ---

    目录

    EurekaServer项目(standalone)

    EurekaClient项目

    使用Eureka注册中心注册的服务

            方法1:使用 RestTemplate

            方式2:使用OpenFeign

            断路器的一个问题

    EurekaServer项目(standalone)

    建立 Eureka Server项目,仅添加 spring-cloud-starter-netflix-eureka-server 依赖包:

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

    给应用添加注解 @EnableEurekaServer:

    1 @EnableEurekaServer
    2 @SpringBootApplication
    3 public class ServiceRegistrationAndDiscoveryServiceApplication {
    4 
    5     public static void main(String[] args) {
    6         SpringApplication.run(ServiceRegistrationAndDiscoveryServiceApplication.class, args);
    7     }
    8 
    9 }

    添加配置:

    server.port=8761

    启动应用:出现了一些异常信息,服务在8761端口启动

    o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8761 (http)
    
    iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. 
    You can switch to using Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath. c.n.d.s.t.d.RedirectingEurekaHttpClient : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/},
    exception=java.net.ConnectException: Connection refused: no further information stacktrace=com.sun.jersey.api.client.ClientHandlerException:
    java.net.ConnectException: Connection refused: no further information ... Caused by: java.net.ConnectException: Connection refused: no further information c.n.d.s.t.d.RetryableEurekaHttpClient : Request execution failed with message: java.net.ConnectException: Connection refused: no further information com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/DESKTOP-BDNTQQ3:8761 - was unable to refresh its cache!
    This periodic background refresh will be retried in 30 seconds. status = Cannot execute request on any known server stacktrace =
    com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server com.netflix.discovery.DiscoveryClient : Initial registry fetch from primary servers failed com.netflix.discovery.DiscoveryClient : Using default backup registry implementation which does not do anything. c.n.eureka.cluster.PeerEurekaNodes : Adding new peer nodes [http://localhost:8761/eureka/] c.n.eureka.cluster.PeerEurekaNodes : Replica node URL: http://localhost:8761/eureka/ c.n.eureka.DefaultEurekaServerContext : Initialized com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/DESKTOP-BDNTQQ3:8761: registering service... o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8761 (http) with context path '' .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8761

    可以访问 http://localhost:8761/ :

    发现注册了一个 UNKNOWN 应用,点击 Status下的超链接,返回:

    将链接 desktop-bdntqq3改为 localhost,仍然如此。

    根据官网指南,Eureka Server会 注册其本身,因此,添加下面的配置 1)禁止其注册自身 和 2)禁止获取配置:

    eureka.client.register-with-eureka=false
    eureka.client.fetch-registry=false

    启动成功,没有报错,访问 http://localhost:8761/ ,没有发现注册服务:

    检查 spring-cloud-starter-netflix-eureka-server 包的结构:原来,它已经包含了 spring-boot-starter-web/actuator、还包含了 spring-cloud-netflix-eureka-client 等包,难怪它会提供Web接口,也会注册自己呢。

    不过,其中 actuator 的 info接口 不能正常访问,需要添加下面的配置:

    management.endpoints.web.exposure.include=info,health

    这样,/actuator/info 端点 就会暴露出来了。

    EurekaClient项目

    Eureka Server建立好了,接下来,通过Eureka Client注册服务。

    在 https://start.spring.io/ 建立项目——web-first,依赖包:

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

    上面建立了一个 Web服务。

    导入Eclipse,启动(启动前,,Eureka Server已经启动)。部分日志如下:

    iguration$LoadBalancerCaffeineWarnLogger : Spring Cloud LoadBalancer is currently working with the default cache. You can switch to using 
    Caffeine cache, by adding it and org.springframework.cache.caffeine.CaffeineCacheManager to the classpath. com.netflix.discovery.DiscoveryClient : Initializing Eureka in region us-east-1 c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration ... com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1626934699656 with initial instances count: 1 o.s.c.n.e.s.EurekaServiceRegistry : Registering application UNKNOWN with eureka with status UP com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1626934699658, current=UP, previous=STARTING] com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/DESKTOP-BDNTQQ3: registering service... com.netflix.discovery.DiscoveryClient : DiscoveryClient_UNKNOWN/DESKTOP-BDNTQQ3 - registration status: 204

    访问Eureka Server的 http://localhost:8761/ :

    注册成功,但是,Application 为 UNKNOWN。在Status中,超链接的名字为 电脑的 计算机名,地址为 http://desktop-bdntqq3:8080/actuator/info ,但是,访问失败——需要配置hosts才可以,

    其中的 /actuator/info 像是 actuator的端点,但是,项目没有引入 actuator,因此无法访问。

    疑问

    1)没有任何配置,怎么就注册成功了呢?一定是 依赖包spring-cloud-starter-netflix-eureka-client 自动做了一些事情;

    2)注册中心,Status下的超链接怎么不能访问,要怎么配置?

    解决Application为UNKNOWN的问题

    在web-first项目中添加配置:

    spring.application.name=web-first

    再次启动,部分日志:

    com.netflix.discovery.DiscoveryClient    : DiscoveryClient_WEB-FIRST/DESKTOP-BDNTQQ3:web-first: registering service...
    
    com.netflix.discovery.DiscoveryClient    : DiscoveryClient_WEB-FIRST/DESKTOP-BDNTQQ3:web-first - registration status: 204

    注册中心显示:

    解决注册中心下Status超链接地址为主机名称的问题

    在web-first项目中添加下面的配置:

    eureka.instance.prefer-ip-address=true

    再次启动项目,此时,注册中心下Status的超链接现实为IP地址:

    http://192.168.125.197:8080/actuator/info

    但这个接口 还是无法访问。

    更改注册中心的端口

    更改Eureka Server注册服务地址为 8769,再次启动 服务器、客户端两个服务,此时,客户端 web-first项目 无法注册成功,启动时报错:

    c.n.d.s.t.d.RedirectingEurekaHttpClient  : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}, 
    exception=I/O error on GET request for "http://localhost:8761/eureka/apps/": Connect to localhost:8761 [localhost/127.0.0.1,
    localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException:
    Connect to localhost:8761 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect
    stacktrace=org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8761/eureka/apps/":
    Connect to localhost:8761 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect; nested exception is
    org.apache.http.conn.HttpHostConnectException: Connect to localhost:8761 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1]
    failed: Connection refused: connect

    web-first项目 启动了,但是,注册失败。启动后,仍然会 每隔30秒 执行一次注册:

    [nfoReplicator-0] c.n.d.s.t.d.RedirectingEurekaHttpClient  : Request execution error. endpoint=DefaultEndpoint{ serviceUrl='http://localhost:8761/eureka/}, 
    exception=I/O error on POST request for "http://localhost:8761/eureka/apps/WEB-FIRST": Connect to localhost:8761 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1]
    failed: Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException: Connect to localhost:8761
    [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect stacktrace=org.springframework.web.client.ResourceAccessException:
    I/O error on POST request for "http://localhost:8761/eureka/apps/WEB-FIRST": Connect to localhost:8761 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] failed:
    Connection refused: connect; nested exception is org.apache.http.conn.HttpHostConnectException: Connect to localhost:8761 [localhost/127.0.0.1,
    localhost/0:0:0:0:0:0:0:1] failed: Connection refused: connect [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_WEB-FIRST/DESKTOP-BDNTQQ3:web-first - registration failed Cannot execute
    request on any known server [freshExecutor-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_WEB-FIRST/DESKTOP-BDNTQQ3:web-first - was unable to refresh its cache!
    This periodic background refresh will be retried in 30 seconds. status = Cannot execute request on any known server stacktrace =
    com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server

    启动期间的线程时 main,项目启动后,使用异步线程执行注册了。

    解决这个问题的方法是,添加下面的配置:

    eureka.client.service-url.defaultZone=http://localhost:8769/eureka/

    再次启动web-first项目,没有报错,注册成功。

    注:在注册中心中,有一个 DS Replicas 项目,其下为 localhost 超链接,其仍然指向 8761端口:http://localhost:8761/eureka/

    页面上还有其它现实 8761端口的地方。

    因此,此时需要修改Eureka Server的配置,同 web-first的 新增配置:

    eureka.client.service-url.defaultZone=http://localhost:8769/eureka/

    再次启动 Eureka Server,服务启动成功:,上面的 DS Replicas 下 超链接正常了:

    http://localhost:8769/eureka/

    特别提醒:

    配置中的  defaultZone 必须是 驼峰格式,不能写成 default-zone,否则,无效。

    因为,eureka.client.service-url 是一个 Map类型。

    更多细节还需 深挖。

    服务器的 /eureka 端点 不能访问

    虽然配置了 /eureka 端点,但是,其不能访问。

    可以通过 /eureka/apps 来访问 注册到注册中心的 应用:

    /eureka/apps/ + 服务名称 还可以访问 服务的信息:

    更进一步:

    /eureka/apps/ + 服务名称  + / + instance名称 还可以访问实例详情,略。

    启动两个同名的服务

    上面注册 WEB-FIRST 服务的一个实例,接下来使用 端口9090 启动一个 web-first服务。

    -Dserver.port=9090

    启动后的注册中心页面:

    注:同一台主机,端口需要不同;如果是不同主机运行,则无妨。一般来说,同一个 application 的多个实例 是 运行在不同主机上。

    更多配置,TODO 

    使用Eureka注册中心注册的服务

    在web-first新建接口:

     1 @RestController
     2 class HelloController {
     3     // GET请求
     4     @GetMapping(path="getTime")
     5     public String getTime() {
     6         return new Date().toGMTString();
     7     }
     8     // POST请求
     9     @PostMapping(path="updateUser")
    10     public String updateUser(@RequestBody User u) {
    11         System.out.println("参数 User u = " + u);
    12         return "OK";
    13     }
    14     
    15 }
    16 
    17 class User {
    18     private String name;
    19     private Integer age;
    20     // getter, setter, toString...
    21     
    22 }

    新建Web项目 web-second,注册到注册中心,pom.xml同 web-first。

    修改 web-second 的配置:端口为 10000

    server.port=10000
    
    spring.application.name=web-second
    
    eureka.instance.prefer-ip-address=true
    eureka.client.service-url.defaultZone=http://localhost:8769/eureka/

    启动 web-second。注册中心页面现实多了 WEB-SECOND 服务:

    接下来,通过 WEB-SECOND 项目调用 WEB-FIRST 的两个接口。

    方法1:使用 RestTemplate

     1 @Configuration
     2 class MyConfiguration {
     3 
     4     @LoadBalanced
     5     @Bean
     6     RestTemplate restTemplate() {
     7         return new RestTemplate();
     8     }
     9     
    10 }
    11 
    12 @Component
    13 class MyRunner implements CommandLineRunner {
    14 
    15     @Autowired
    16     private RestTemplate restTemplate;
    17     
    18     @Override
    19     public void run(String... args) throws Exception {
    20         String results = restTemplate.getForObject("http://web-first/getTime",
    21                 String.class);
    22         System.out.println("GET请求 results=" + results);
    23         
    24         User user = new User();
    25         user.setName("web-second");
    26         user.setAge(1);
    27         ResponseEntity<String> rt = restTemplate.postForEntity("http://web-first/updateUser", user, String.class);
    28         System.out.println("POST请求 rt=" + rt + ", rt.body=" + rt.getBody());
    29     }
    30     
    31 }
    32 
    33 class User {
    34     private String name;
    35     private Integer age;
    36     // getter, setter, toString...
    37 }

    测试结果:调用成功。

    GET请求 results=22 Jul 2021 08:27:11 GMT
    POST请求 rt=<200,OK,[Content-Type:"text/plain;charset=UTF-8", Content-Length:"2", Date:"Thu, 22 Jul 2021 08:27:11 GMT", Keep-Alive:"timeout=60", 
    Connection:"keep-alive"]>, boty=OK

    增加一个 WEB-FIRST 服务——端口9090,再多次执行 WEB-SECOND 中的调用,检查是否有 负载均衡:

    修改 WEB-SECOND 的代码:每隔2秒执行一轮,共10轮。

     1     @Override
     2     public void run(String... args) throws Exception {
     3         
     4         for (int i=0; i<10; i++) {
     5             String results = restTemplate.getForObject("http://web-first/getTime",
     6                     String.class);
     7             System.out.println("GET请求 results=" + results);
     8             
     9             User user = new User();
    10             user.setName("web-second");
    11             user.setAge(i);
    12             ResponseEntity<String> rt = restTemplate.postForEntity("http://web-first/updateUser", user, String.class);
    13             System.out.println("POST请求 rt=" + rt + ", rt.body=" + rt.getBody());
    14             
    15             System.out.println();
    16             
    17             // 每隔2秒执行一轮
    18             TimeUnit.SECONDS.sleep(2);
    19         }
    20     }

    检查 两个WEB-FIRST 服务的日志,是否 均衡地 收到了请求?NO!所有请求由一个服务器处理了。

    更正WEB-FIRST的测试程序:GET、POST都输出日志(之前只有 updateUser输出了信息,误判 没有做负载均衡)

    @RestController
    class HelloController {
        
        @Autowired
        private HttpServletRequest req;
        
        @GetMapping(path="getTime")
        public String getTime(Integer i) {
            System.out.println("GET url=" + req.getRequestURL());
            System.out.println("GET 参数i=" + i + ", url=" + req.getRequestURL());
            return new Date().toGMTString();
        }
        
        @PostMapping(path="updateUser")
        public String updateUser(@RequestBody User u) {
            System.out.println("POST url=" + req.getRequestURL());
            System.out.println("POST 参数 User u = " + u); 
            return "OK";
        }
        
    }

    启动两个WEB-FIRST服务,启动后,再启动WEB-SECOND服务。检查 WEB-FIRST服务 的日志,有做负载均衡了:其中的GET、POST请求分别由 两个 WEB-FIRST服务 处理。

    在启动一台WEB-FIRST服务,就可以看到明显的负载均衡现象了:

    注:

    参考 SPRING CLOUD文档的 “Spring RestTemplate as a Load Balancer Client” 一节实现。

    “Spring WebClient as a Load Balancer Client”  一节还介绍了 使用 WebClient 访问,类似 RestTemplate。

    方式2:使用OpenFeign

    feign 翻译:

    vt.  假装,伪装; 捏造(借口、理由等); 装作; 创造或虚构;
    vi.  假装; 装作; 作假; 佯作;
    
    变形 过去分词: feigned 过去式: feigned 现在分词: feigning 第三人称单数: feigns

    Feign是一个 Web服务客户端,它让Web服务客户端的编写变得很容易。

    注:本节参考 spring cloud文档的 “Spring Cloud OpenFeign” 一章。

    添加依赖包:

    		<!-- openfeign -->
    		<dependency>
    			<groupId>org.springframework.cloud</groupId>
    			<artifactId>spring-cloud-starter-openfeign</artifactId>
    		</dependency>

    启动类添加注解:

    1 @EnableFeignClients
    2 @SpringBootApplication
    3 public class WebSecondApplication {
    4 ...
    5 }

    定义FeignClient访问 WEB-FIRST 的接口:

     1 @FeignClient(value="web-first") // WEB-FIRST 服务名
     2 public interface WebFirstFeignClient {
     3 
     4     @GetMapping(value="/getTime")
     5     public String getTime(@RequestParam Integer i); // @RequestParam 必须 6     
     7     @PostMapping(value="/updateUser")
     8     public String updateUser(User user);
     9     
    10 }
    11 
    12 @Component
    13 class MyRunner2 implements CommandLineRunner {
    14 
    15     @Autowired
    16     private WebFirstFeignClient client;
    17     
    18     @Override
    19     public void run(String... args) throws Exception {
    20 
    21         for (int i=0; i<10; i++) {
    22             System.out.println("GET 返回:" + client.getTime(i));
    23             
    24             User user = new User();
    25             user.setName("lib");
    26             user.setAge(i);
    27             System.out.println("POST 返回:" + client.updateUser(user));
    28         }
    29         
    30     }
    31     
    32 }

    启动WEB-SECOND服务,调用成功。

    疑问

    WEB-FIRST 服务 挂了,此时,执行WEB-SECOND会怎样呢?启动失败,报错:

    Caused by: feign.FeignException$ServiceUnavailable: [503] during [GET] to [http://web-first/getTime?i=0] 
    [WebFirstFeignClient#getTime(Integer)]: [Load balancer does not contain an instance for the service web-first]

    WEB-SECOND服务没有成功启动

    怎么避免 WEB-FIRST 挂掉 导致 消费方 WEB-SECOND 启动不了的情况呢?

    进一步配置 @FeignClient ,添加断路器:

    feign.circuitbreaker.enabled=true

    导入依赖包spring-cloud-starter-circuitbreaker-resilience4j

    <!-- resilience4j -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
    </dependency>

    添加resilience4j 的 Customize bean配置:

    @Bean
    public Customizer<Resilience4JCircuitBreakerFactory> defaultCustomizer() {
    	return factory -> factory.configureDefault(id -> new
    			Resilience4JConfigBuilder(id)
    			.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(4
    					)).build())
    			.circuitBreakerConfig(CircuitBreakerConfig.ofDefaults())
    			.build());
    }

    添加 WebFirstFeignFallback:

    @Component
    public class WebFirstFeignFallback implements WebFirstFeignClient {
    
        @Override
        public String getTime(Integer i) {
            return "getTime熔断";
        }
    
        @Override
        public String updateUser(User user) {
            return "updateUser熔断";
        }
    }

    配置 @FeignClient :

    1 @FeignClient(value="web-first", fallback = WebFirstFeignFallback.class)
    2 public interface WebFirstFeignClient {
    3 ...    
    4 }

    此时,启动 WEB-SECOND 服务:服务启动成功,日志输出如下,调用接口返回 WebFirstFeignFallback 中的内容。

    2021-07-22 18:34:41.167  WARN 19484 --- [oundedElastic-1] o.s.c.l.core.RoundRobinLoadBalancer      : No servers available for service: web-first
    2021-07-22 18:34:41.167  WARN 19484 --- [pool-1-thread-1] .s.c.o.l.FeignBlockingLoadBalancerClient : 
    Load balancer does not contain an instance for the service web-first GET 返回:getTime熔断 2021-07-22 18:34:41.169 WARN 19484 --- [oundedElastic-1] o.s.c.l.core.RoundRobinLoadBalancer : No servers available for service: web-first 2021-07-22 18:34:41.169 WARN 19484 --- [pool-1-thread-1] .s.c.o.l.FeignBlockingLoadBalancerClient :
    Load balancer does not contain an instance for the service web-first POST 返回:updateUser熔断

    为什么 不使用spring-cloud-starter-hystrix 呢?因为版本啊!

    本文的spring boot是2.5.2,而在 2.5.0之后就不支持 spring-cloud-starter-hystrix,需要使用 resilience4j 的断路器。

    除了resilience4j,还有 Sentinel、Spring Retry 两种。

    更多详情,请看 spring cloud官方文档之 “ Spring Cloud Circuit Breaker -> Configuring Resilience4J Circuit Breakers”一章。

    注:Alibaba Sentinel 是面向云原生微服务的流量控制,熔断降级组件,监控保护你的微服务。

    参考资料:

    1、官方-Service Registration and Discovery

    2、SpringCloud之Eureka注册中心原理及其搭建

    3、FeignClient超时配置

    4、Resilience4j+Feign实现熔断,fallback

    5、Spring Cloud Feign(第四篇)之Fallback

    6、

  • 相关阅读:
    洛谷P1261 服务器储存信息问题
    洛谷P2110 欢总喊楼记
    洛谷P2482 [SDOI2010]猪国杀
    洛谷P2756 飞行员配对方案问题
    洛谷P2763 试题库问题
    洛谷P2774 方格取数问题
    Huffman编码
    SA后缀数组
    KMP
    LCA
  • 原文地址:https://www.cnblogs.com/luo630/p/15043547.html
Copyright © 2011-2022 走看看