之前一直在做一些针对APM的深入测试,其中就涉及到了对分布式为服务系统的分布式追踪的测试。所以,有必要了解一下现在比较流行的微服务框架,并进行一些实地测试,之前转了一篇文章:Dubbo和Spring Cloud微服务架构的比较,已经让我们对dubbo和spring cloud有了一定的了解。因为elastic APM目前只能对基于HTTP的API进行追踪,因此对于dubbo这种基于自有协议进行RPC的微服务框架目前不适合我们的测试范围,并且公司里面的微服务框架也是采用的spring cloud,所以,我们先研究一下spring cloud。这篇文章里,我使用的是最新的Greenwich.RELEASE
版本。
这篇文章里,我将描述如何在Spring boot/cloud项目中使用Netflix Ribbon进行客户端负载平衡。学习构建基于微服务的应用程序,使用ribbon作为客户端负载均衡器和eureka作为注册表服务。了解我们如何在负载均衡器下动态添加新的微服务实例。
服务器端负载平衡主要的应用场景是针对单一的大型应用程序,该场景下,我们在负载均衡器后面部署有限数量的应用程序实例。我们将war/ear文件部署到多个服务器实例中,这些实例基本上是部署了相同应用程序的服务器池,我们在其前面放置了一个负载均衡器。
负载均衡器具有公共IP和DNS。客户端使用该公共IP / DNS发出请求。负载均衡器决定将请求转发到哪个内部应用程序服务器。它主要使用round robin或者固定session的算法。我们称之为服务器端负载平衡。
主要是服务器端负载平衡是一种手动操作,我们需要手动添加/删除实例到负载均衡器才能工作。因此,通常情况下,这会让我们失去当今微服务架构中强调的随需应变性和可扩展性。无法做到在任何新实例被调整时,可以自动发现和配置。
另一个问题是服务器端负载平衡的故障转移策略(通常是出问题后再切换流量到另一个服务器)难以向客户端提供无缝体验。
最后,我们需要一个单独的服务器来托管负载均衡器实例,这会对成本和维护产生影响。
为了克服传统负载均衡的问题,客户端负载均衡问世。它们作为内置组件驻留在应用程序中,并与应用程序捆绑在一起,因此我们不必将它们部署在单独的服务器中。
现在让我们想象一下。在微服务架构中,我们将不得不开发许多微服务,并且每个微服务可能在生态系统中具有多个实例。为了克服这个问题的复杂性,我们已经有一个流行的解决方案 —— 使用服务发现模式。在spring boot应用程序中,我们在服务发现领域有几个选项,如eureka,consoul,zookeeper等。
在eureka的场景下,如果一个微服务想要与另一个微服务通信,它通常使用discovery client查找服务注册表,并且Eureka服务器将该目标微服务的所有实例返回给服务调用者。然后,服务调用者负责选择发送请求的实例。在这里,客户端负载平衡(ribbon)开始加入,并自动处理这种情况的复杂性,并以负载平衡的方式委托给适当的实例。请注意,我们可以指定要使用的负载平衡算法。(如果不使用ribbon,eureka的discovery client会使用默认的round robin算法)
Spring Cloud系列的Netflix ribbon提供了客户端负载均衡的功能,可以与服务注册表组件一起设置客户端负载平衡。Spring boot具有非常好的方式,可以轻松配置ribbon的客户端负载均衡器。它提供以下功能:
要下载ribbon,请移步maven central。以下是在Maven中添加依赖项的示例:
<dependency>
<groupId>com.netflix.ribbongroupId>
<artifactId>ribbonartifactId>
<version>2.3.0version>
dependency>
我们将创建以下组件,并了解整个生态系统如何在分布式环境中进行协调。
我们将使用Spring boot创建一个简单的微服务,并将暴露一个简单的REST endpoint。创建一个名为ribbon-server
的spring boot项目,包含spring-boot-web
和ribbon
、eureka discovery
依赖项。
为此,我们需要访问 https://start.spring.io/ 输入对应的maven依赖项,然后下载包含骨架项目的zip文件。然后解压,并在IDE中导入。
实现一个 Rest Controller 并暴露一个 Rest Endpoint:
package com.example.ribbonserver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyRestController {
@Autowired
Environment environment;
@GetMapping("/")
public String health() {
return "I am Ok";
}
@GetMapping("/backend")
public String backend() {
System.out.println("Inside MyRestController::backend...");
String serverPort = environment.getProperty("local.server.port");
System.out.println("Port : " + serverPort);
return "Hello form Backend!!! " + " Host : localhost " + " :: Port : " + serverPort;
}
}
将此服务注册到eureka,我们需要在应用程序类中添加@EnableDiscoveryClient
注解。我们还需要在应用程序propererty文件中添加以下条目。
package com.example.ribbonserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonServerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonServerApplication.class, args);
}
}
application.properites:
spring.application.name=server
server.port = 9090
eureka.client.serviceUrl.defaultZone= http://${registry.host:localhost}:${registry.port:8761}/eureka/
eureka.client.healthcheck.enabled= true
eureka.instance.leaseRenewalIntervalInSeconds= 1
eureka.instance.leaseExpirationDurationInSeconds= 2
创建服务发现服务器,这也很容易。我们需要创建一个如上所述的Spring引导项目,并将Eureka Server作为依赖项,并执行以下配置。
在spring boot应用程序类中添加@EnableEurekaServer
注解,并在应用程序属性文件中添加以下配置。
package com.example.ribboneurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class RibbonEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonEurekaServerApplication.class, args);
}
}
application.properties:
spring.application.name= ${springboot.app.name:eureka-serviceregistry}
server.port = ${server-port:8761}
eureka.instance.hostname= ${springboot.app.name:eureka-serviceregistry}
eureka.client.registerWithEureka= false
eureka.client.fetchRegistry= false
eureka.client.serviceUrl.defaultZone: http://${registry.host:localhost}:${server.port}/eureka/
按照之前的方法创建另一个名为ribbon-client的项目,并依赖spring-cloud-starter-netflix-ribbon
在应用程序类中,添加两个注释@RibbonClient
和@EnableDiscoveryClient
以启用ribbon和Eureka客户端以进行服务注册。
package com.example.ribbonclient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@EnableDiscoveryClient
@SpringBootApplication
@RibbonClient(name = "server", configuration = RibbonConfiguration.class)
public class RibbonClientApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonClientApplication.class, args);
}
}
在application.properties
,我们需要做以下配置。这里禁用了server.ribbon.listOfServers
,我们可以启用它来手动将服务器添加到此负载均衡器的服务器列表中。我们将在测试部分进行检查。其他属性是不言自明的。
spring.application.name=client
server.port=8888
eureka.client.serviceUrl.defaultZone= http://${registry.host:localhost}:${registry.port:8761}/eureka/
eureka.client.healthcheck.enabled= true
eureka.instance.leaseRenewalIntervalInSeconds= 1
eureka.instance.leaseExpirationDurationInSeconds= 2
server.ribbon.eureka.enabled=true
#server.ribbon.listOfServers=localhost:9090,localhost:9091,localhost:9092
server.ribbon.ServerListRefreshInterval=1000
#logging.level.root=TRACE
现在我们需要为ribbon创建一个配置类,以确定负载平衡的算法和health check的方法。我们现在将使用Ribbon提供的默认值,但通过配置类,我们可以很容易地覆盖它们并添加我们的自定义逻辑。
package com.example.ribbonclient;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AvailabilityFilteringRule;
import com.netflix.loadbalancer.IPing;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.PingUrl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class RibbonConfiguration {
@Autowired
IClientConfig config;
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
@Bean
public IRule ribbonRule(IClientConfig config) {
return new AvailabilityFilteringRule();
}
}
通过命令mvn clean install
构建所有项目,并检查构建是否成功。然后一一启动。
首先是Eureka,然后是后端微服务(provider),最后是消费者端微服务。
要启动每个微服务,我们将使用java -jar -Dserver.port=XXXX target/YYYYY.jar
命令。
为此,我们需要为此使用不同的端口,以以下方式在特定端口中启动服务。
java -jar -Dserver.port=XXXX target/YYYYY.jar
。我们将在9090,9091和9092三个端口上创建此服务的3个实例。
现在转到http://localhost:8761/浏览器并检查eureka服务器是否正在运行,所有微服务都被注册了所需数量的实例
在消费端微服务中,我们使用RestTemplate调用后端微服务。使用@LoadBalanced
注释启用Rest tempate
作为客户端负载均衡器。
现在转到浏览器并打开客户端微服务rest endpoint: http://localhost:8888/client/frontend, 看看响应来自哪一个后端实例。
在我们的代码中,后端服务器会返回它的运行端口,我们也在消费端微服务响应中显示它。尝试刷新这个URL几次并注意后端服务器的端口不断变化,这意味着客户端负载平衡正在运行。现在尝试添加更多的后端服务器实例并检查它是否也在eureka服务器中注册并最终在ribbon中被加入负载均衡列表,因为一旦将在eureka中注册,birron将自动向新实例发送请求。
转到消费端微服务application.properties文件并启用它。
server.ribbon.listOfServers=localhost:9090,localhost:9091,localhost:9092
server.ribbon.eureka.enabled=false
现在测试客户端URL。您只能从已注册的实例获得响应(这里是9090,9091,9092)。现在,如果您在不同端口启动后端微服务的新实例,除非你在listOfServers中添加实例的url,否则Ribbon不会向新实例发送请求。
如果你在测试时遇到困难,我建议你在所有应用程序中删除所有与eureka相关的配置,并停止eureka服务器再重启。