上篇中,主要了解了Spring Cloud的一些简单组件以及他们的功能。
现在动手简单的使用一下Spring Cloud的一些组件。
服务发现:(Eureka)
前面提到,Eureka分为服务端和客户端,服务端是服务注册中心,而客户端是提供服务的。
创建服务注册中心(服务端)
首先在pom文件中加入以下依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在Application文件中增加@EnableEurekaServer注解,表明这是服务端
@EnableEurekaServer
@SpringBootApplication
public class SpringcloudEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudEurekaServerApplication.class, args);
}
}
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为。在application.properties或yml中禁止客户端注册行为,否则会报错。
server.port=1111
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
服务端就写好了,可以访问localhost:1111进行访问。可以看到,这时候是没有服务的。
接下来创建客户端
创建客户端,并在注册中心中注册自己。
还是首先添加依赖:这里和上面不同的地方只有对Eureka的依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.8.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.SR7</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在Application文件中加入@EnableDiscoveryClient注解,表明这是一个客户端。
@EnableDiscoveryClient
@SpringBootApplication
public class SpringcloudEurekaServiceApplication {
public static void main(String[] args) {
SpringApplication.run(SpringcloudEurekaServiceApplication.class, args);
}
}
然后我们需要向注册中心中注册自己,只需要在application.properties或yml文件中增加配置即可:
spring.application.name=compute-service
server.port=2222
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
defaultZone指定了服务注册中心的位置。
注册了之后我们就可以在客户端编写客户端提供的服务了。比如这里提供了add加法服务。
@RestController
public class ComputeController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value = "/add" ,method = RequestMethod.GET)
public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
ServiceInstance instance = client.getLocalServiceInstance();
Integer r = a + b;
logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
return r;
}
}
在提供的服务中,只需要使用DiscoveryClient就可以得到关于调用者的信息。
客户端也完成了,这个时候可以再次访问localhost:1111。
可以发现,客户端已经在服务端上注册了。
我们看一下架构图:
很显然,工作流程是这样的:Service Consumer -> Proxy Server -> Client(Load Balancer) -> Servie Discovery -> Target Service
我们编写好了上图的红色的部分,接下来应该对服务进行负载均衡了,也就是对客户端的负载均衡,我们使用Ribbon来完成这件事。
首先我们启动服务端和两个客户端,客户端分别开在2222和2223端口。即:
开启了两个服务,接下来我们使用Ribbon进行负载均衡。
新建一个项目。
在pom文件中添加如下依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
使用@EnableDiscoveryClient注解在将其注册在注册中心。并且提供一个返回RestTemplate的bean实例,使用@LoadBalanced注解对这个实例进行负载均衡这样,使用这个实例去获取服务的时候,就会帮我们进行负载均衡了。
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
}
在application.properties中注册:
spring.application.name=ribbon-consumer
server.port=3333
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
创建了bean实例,我们就可以通过@Autowired来获取这个RestTemplate了,并且使用这个实例去调用服务。
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
}
启动这个项目,并且访问多次localhost:3333/add可以发现确实进行了负载均衡。
在微服务架构中,服务被拆分成一个一个的单元,各个单元之间相互依赖调用。如果某个单元由于网络原因或者别的单元故障奔溃,那么会直接导致调用方对外的服务也发生延迟。若此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,最终导致自身服务的瘫痪,这样的结构相对于传统的web开发模式来说更加可怕,为了解决这个问题,我们可以采用断路器。
在分布式架构中,当某个服务单元发生故障,使用断路器返回一个错误的相应,而不是进行长时间的等待,这样就不会使得线程被长时间占用,导致服务器的瘫痪。
Spring Cloud提供了Hystrix这么一个组件来实现断路器的功能。
首先启动Eureka服务端和客户端。
对Ribbon的项目进行以下修改:
首先在pom文件中增加以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
在application中增加@EnableCircuitBreaker注解,表明使用断路器。
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class RibbonApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
}
Hystrix使用消息队列的方式,如果连接的服务崩溃,则异步回调某个方法进行处理。
新增一个Service类,在这个类中定义好如何访问,以及失败之后的返回等。
@Service
public class ComputeService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "addServiceFallback")
public String addService() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
public String addServiceFallback() {
return "error";
}
}
这里重点是@HystrixCommand注解:表明该方法为hystrix包裹,可以对依赖服务进行隔离、降级、快速失败、快速重试等等hystrix相关功能
列举几个属性:
1. fallbackMethod 降级方法
2. commandProperties 普通配置属性,可以配置HystrixCommand对应属性,例如采用线程池还是信号量隔离、熔断器熔断规则等等
3. ignoreExceptions 忽略的异常,默认HystrixBadRequestException不计入失败
4. groupKey() 组名称,默认使用类名称
5. commandKey 命令名称,默认使用方法名
最后修改一下Controller即可:
@RestController
public class ConsumerController {
@Autowired
private ComputeService computeService;
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return computeService.addService();
}
}
对于断路器,针对生产环境,,Netflix还给我们准备了一个非常好用的运维工具, 那就是Hystrix Dashboard和Turbine.可以帮助我们更好的观察。
当客户端关闭时访问:
客户端启动时访问:
成功!