赞
踩
Eureka作为服务注册与发现的组件
2.1 首先创建一个maven主工程。
**2.2 然后创建2个model工程:**一个model工程作为服务注册中心,即Eureka Server,另一个作为Eureka Client。
下面以创建server为例子,详细说明创建过程:
2.3 启动一个服务注册中心,只需要一个注解@EnableEurekaServer,这个注解需要在springboot工程的启动application类上加:
-
- @SpringBootApplication
- @EnableEurekaServer
- public class EurekaServerApplication {
-
- public static void main(String[] args) {
- SpringApplication.run( EurekaServerApplication.class, args );
- }
- }
-
-
**2.4 **eureka是一个高可用的组件,它没有后端缓存,每一个实例注册之后需要向注册中心发送心跳(因此可以在内存中完成),在默认情况下erureka server也是一个eureka client ,必须要指定一个 server。eureka server的配置文件appication.yml:
- server:
- port: 8761
-
- eureka:
- instance:
- hostname: localhost
- client:
- registerWithEureka: false
- fetchRegistry: false
- serviceUrl:
- defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
-
- spring:
- application:
- name: eurka-server
-
通过eureka.client.registerWithEureka:false和fetchRegistry:false来表明自己是一个eureka server.
2.5 eureka server 是有界面的,启动工程,打开浏览器访问:
http://localhost:8761 ,界面如下:
在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate,另一种是feign。在这一篇文章首先讲解下基于ribbon+rest。
Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Feign already uses Ribbon, so if you are using @FeignClient then this section also applies.
ribbon是一个负载均衡客户端,可以很好的控制htt和tcp的一些行为。Feign默认集成了ribbon。
ribbon 已经默认实现了这些配置bean:
IClientConfig ribbonClientConfig: DefaultClientConfigImpl
IRule ribbonRule: ZoneAvoidanceRule
IPing ribbonPing: NoOpPing
ServerList ribbonServerList: ConfigurationBasedServerList
ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter
ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
启动eureka-server 工程;启动service-hi工程,它的端口为8762;将service-hi的配置文件的端口改为8763,并启动,这时你会发现:service-hi在eureka-server注册了2个实例,这就相当于一个小的集群。
重新新建一个spring-boot工程,取名为:service-ribbon;
在它的pom.xml继承了父pom文件,并引入了以下依赖:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.forezp</groupId>
- <artifactId>service-ribbon</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
-
- <name>service-ribbon</name>
- <description>Demo project for Spring Boot</description>
-
-
- <parent>
- <groupId>com.forezp</groupId>
- <artifactId>sc-f-chapter2</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </parent>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
- </dependency>
- </dependencies>
-
-
-
- </project>
-
在工程的配置文件指定服务的注册中心地址为http://localhost:8761/eureka/,程序名称为 service-ribbon,程序端口为8764。配置文件application.yml如下:
- eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8761/eureka/
- server:
- port: 8764
- spring:
- application:
- name: service-ribbon
在工程的启动类中,通过@EnableDiscoveryClient向服务中心注册;并且向程序的ioc注入一个bean: restTemplate;并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。
- @SpringBootApplication
- @EnableEurekaClient
- @EnableDiscoveryClient
- public class ServiceRibbonApplication {
-
- public static void main(String[] args) {
- SpringApplication.run( ServiceRibbonApplication.class, args );
- }
-
- @Bean
- @LoadBalanced
- RestTemplate restTemplate() {
- return new RestTemplate();
- }
-
- }
-
-
写一个测试类HelloService,通过之前注入ioc容器的restTemplate来消费service-hi服务的“/hi”接口,在这里我们直接用的程序名替代了具体的url地址,在ribbon中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的url替换掉服务名,代码如下:
- @Service
- public class HelloService {
-
- @Autowired
- RestTemplate restTemplate;
-
- public String hiService(String name) {
- return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
- }
-
-
- }
-
写一个controller,在controller中用调用HelloService 的方法,代码如下:
-
- @RestController
- public class HelloControler {
-
- @Autowired
- HelloService helloService;
-
- @GetMapping(value = "/hi")
- public String hi(@RequestParam String name) {
- return helloService.hiService( name );
- }
- }
-
-
在浏览器上多次访问http://localhost:8764/hi?name=forezp,浏览器交替显示:
- hi forezp,i am from port:8762
-
- hi forezp,i am from port:8763
ribbon在这里起到了负载均衡的作用。
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
简而言之:
继续用上一节的工程, 启动eureka-server,端口为8761; 启动service-hi 两次,端口分别为8762 、8773.
新建一个spring-boot工程,取名为serice-feign,在它的pom文件引入Feign的起步依赖spring-cloud-starter-feign、Eureka的起步依赖spring-cloud-starter-netflix-eureka-client、Web的起步依赖spring-boot-starter-web,代码如下:
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.forezp</groupId>
- <artifactId>service-feign</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
-
- <name>service-feign</name>
- <description>Demo project for Spring Boot</description>
-
-
- <parent>
- <groupId>com.forezp</groupId>
- <artifactId>sc-f-chapter3</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </parent>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-openfeign</artifactId>
- </dependency>
- </dependencies>
-
- </project>
在工程的配置文件application.yml文件,指定程序名为service-feign,端口号为8765,服务注册地址为http://localhost:8761/eureka/ ,代码如下:
- eureka:
- client:
- serviceUrl:
- defaultZone: http://localhost:8761/eureka/
- server:
- port: 8765
- spring:
- application:
- name: service-feign
在程序的启动类ServiceFeignApplication ,加上@EnableFeignClients注解开启Feign的功能:
- @SpringBootApplication
- @EnableEurekaClient
- @EnableDiscoveryClient
- @EnableFeignClients
- public class ServiceFeignApplication {
-
- public static void main(String[] args) {
- SpringApplication.run( ServiceFeignApplication.class, args );
- }
- }
-
-
-
定义一个feign接口,通过@ FeignClient(“服务名”),来指定调用哪个服务。比如在代码中调用了service-hi服务的“/hi”接口,代码如下:
-
- @FeignClient(value = "service-hi")
- public interface SchedualServiceHi {
- @RequestMapping(value = "/hi",method = RequestMethod.GET)
- String sayHiFromClientOne(@RequestParam(value = "name") String name);
- }
-
-
-
-
在Web层的controller层,对外暴露一个"/hi"的API接口,通过上面定义的Feign客户端SchedualServiceHi 来消费服务。代码如下:
- @RestController
- public class HiController {
-
-
- //编译器报错,无视。 因为这个Bean是在程序启动的时候注入的,编译器感知不到,所以报错。
- @Autowired
- SchedualServiceHi schedualServiceHi;
-
- @GetMapping(value = "/hi")
- public String sayHi(@RequestParam String name) {
- return schedualServiceHi.sayHiFromClientOne( name );
- }
- }
-
-
启动程序,多次访问http://localhost:8765/hi?name=forezp,浏览器交替显示:
- hi forezp,i am from port:8762
-
- hi forezp,i am from port:8763
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以相互调用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证100%可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
为了解决这个问题,业界提出了断路器模型。
Netflix has created a library called Hystrix that implements the circuit breaker pattern. In a microservice architecture it is common to have multiple layers of service calls.
Netflix开源了Hystrix组件,实现了断路器模式,SpringCloud对这一组件进行了整合。 在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图:
较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值(Hystric 是5秒20次) 断路器将会被打开。
断路打开后,可用避免连锁故障,fallback方法可以直接返回一个固定值。
这篇文章基于上一篇文章的工程,首先启动上一篇文章的工程,启动eureka-server 工程;启动service-hi工程,它的端口为8762。
改造serice-ribbon 工程的代码,首先在pox.xml文件中加入spring-cloud-starter-netflix-hystrix的起步依赖:
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
- </dependency>
在程序的启动类ServiceRibbonApplication 加@EnableHystrix注解开启Hystrix:
- @SpringBootApplication
- @EnableEurekaClient
- @EnableDiscoveryClient
- @EnableHystrix
- public class ServiceRibbonApplication {
-
- public static void main(String[] args) {
- SpringApplication.run( ServiceRibbonApplication.class, args );
- }
-
- @Bean
- @LoadBalanced
- RestTemplate restTemplate() {
- return new RestTemplate();
- }
-
- }
-
改造HelloService类,在hiService方法上加上@HystrixCommand注解。该注解对该方法创建了熔断器的功能,并指定了fallbackMethod熔断方法,熔断方法直接返回了一个字符串,字符串为"hi,"+name+",sorry,error!",代码如下:
- @Service
- public class HelloService {
-
- @Autowired
- RestTemplate restTemplate;
-
- @HystrixCommand(fallbackMethod = "hiError")
- public String hiService(String name) {
- return restTemplate.getForObject("http://SERVICE-HI/hi?name="+name,String.class);
- }
-
- public String hiError(String name) {
- return "hi,"+name+",sorry,error!";
- }
-
- }
-
启动:service-ribbon 工程,当我们访问http://localhost:8764/hi?name=forezp,浏览器显示:
hi forezp,i am from port:8762
此时关闭 service-hi 工程,当我们再访问http://localhost:8764/hi?name=forezp,浏览器会显示:
hi ,forezp,orry,error!
这就说明当 service-hi 工程不可用的时候,service-ribbon调用 service-hi的API接口时,会执行快速失败,直接返回一组字符串,而不是等待响应超时,这很好的控制了容器的线程阻塞。防止了线程cpu资源的浪费,同时熔断器结合ribbon联合起到了负载均衡和熔断的效果!
Feign是自带断路器的,在D版本的Spring Cloud之后,它没有默认打开。需要在配置文件中配置打开它,在配置文件加以下代码:
feign.hystrix.enabled=true
基于service-feign工程进行改造,只需要在FeignClient的SchedualServiceHi接口的注解中加上fallback的指定类就行了:
- @FeignClient(value = "service-hi",fallback = SchedualServiceHiHystric.class)
- public interface SchedualServiceHi {
- @RequestMapping(value = "/hi",method = RequestMethod.GET)
- String sayHiFromClientOne(@RequestParam(value = "name") String name);
- }
-
SchedualServiceHiHystric需要实现SchedualServiceHi 接口,并注入到Ioc容器中,代码如下:
- @Component
- public class SchedualServiceHiHystric implements SchedualServiceHi {
- @Override
- public String sayHiFromClientOne(String name) {
- return "sorry "+name;
- }
- }
-
启动四servcie-feign工程,浏览器打开http://localhost:8765/hi?name=forezp,注意此时service-hi工程没有启动,网页显示:
sorry forezp
打开service-hi工程,再次访问,浏览器显示:
- hi forezp,i am from port:8762
-
这证明断路器起到作用了。
在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现、服务消费、负载均衡、断路器、智能路由、配置管理等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个简答的微服务系统如下图:
注意:A服务和B服务是可以相互调用的,作图的时候忘记了。并且配置服务也是注册到服务注册中心的。
在Spring Cloud微服务系统中,一种常见的负载均衡方式是,客户端的请求首先经过负载均衡(zuul、Ngnix),再到达服务网关(zuul集群),然后再到具体的服。,服务统一注册到高可用的服务注册中心集群,服务的所有的配置文件由配置服务管理(下一篇文章讲述),配置服务的配置文件放在git仓库,方便开发人员随时改配置。
Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。
zuul有以下功能:
继续使用上一节的工程。在原有的工程上,创建一个新的工程。
其pom.xml文件如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
-
- <groupId>com.forezp</groupId>
- <artifactId>service-zuul</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
-
- <name>service-zuul</name>
- <description>Demo project for Spring Boot</description>
-
- <parent>
- <groupId>com.forezp</groupId>
- <artifactId>sc-f-chapter5</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- </parent>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
- </dependency>
- </dependencies>
- </project>
-
-
其入口applicaton类加上注解@EnableZuulProxy,开启zuul的功能:
- @SpringBootApplication
- @EnableZuulProxy
- @EnableEurekaClient
- @EnableDiscoveryClient
- public class ServiceZuulApplication {
-
- public static void main(String[] args) {
- SpringApplication.run( ServiceZuulApplication.class, args );
- }
- }
-
-
加上配置文件application.yml加上以下的配置代码:
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ server: port: 8769 spring: application: name: service-zuul zuul: routes: api-a: path: /api-a/** serviceId: service-ribbon api-b: path: /api-b/** serviceId: service-feign
首先指定服务注册中心的地址为http://localhost:8761/eureka/,服务的端口为8769,服务名为service-zuul;以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务;
依次运行这五个工程;打开浏览器访问:http://localhost:8769/api-a/hi?name=forezp ;浏览器显示:
hi forezp,i am from port:8762
打开浏览器访问:http://localhost:8769/api-b/hi?name=forezp ;浏览器显示:
hi forezp,i am from port:8762
这说明zuul起到了路由的作用
zuul不仅只是路由,并且还能过滤,做一些安全验证。继续改造工程;
- @Component
- public class MyFilter extends ZuulFilter {
-
- private static Logger log = LoggerFactory.getLogger(MyFilter.class);
- @Override
- public String filterType() {
- return "pre";
- }
-
- @Override
- public int filterOrder() {
- return 0;
- }
-
- @Override
- public boolean shouldFilter() {
- return true;
- }
-
- @Override
- public Object run() {
- RequestContext ctx = RequestContext.getCurrentContext();
- HttpServletRequest request = ctx.getRequest();
- log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
- Object accessToken = request.getParameter("token");
- if(accessToken == null) {
- log.warn("token is empty");
- ctx.setSendZuulResponse(false);
- ctx.setResponseStatusCode(401);
- try {
- ctx.getResponse().getWriter().write("token is empty");
- }catch (Exception e){}
-
- return null;
- }
- log.info("ok");
- return null;
- }
- }
这时访问:http://localhost:8769/api-a/hi?name=forezp ;网页显示:
token is empty
访问 http://localhost:8769/api-a/hi?name=forezp&token=22 ;
网页显示:
hi forezp,i am from port:8762
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。