本篇继续简述Netflix版SpringCloud之feign & zuul。
一些基本组件概念理解:
feign: feign默认集成了ribbon, 也可以实现负载均衡
zuul: 路由网关控制服务器
下面简单演示下以上组件的用法。
准备工作:启动eureka服务注册中心(参见 Netflix SpringCloud-Eureka)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--访问http://localhost:9001/hystrix 在请求地址栏输入:http://localhost:9001/hystrix.stream, 点击Monitor Stream可以监控service层的方法--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId></dependency>
eureka: client: serviceUrl: defaultZone: http://peer1:8888/eureka/,http://peer2:8889/eureka/server: port: 9001spring: application: name: routing-feign zipkin: base-url: http://localhost:9100## Feign是自带断路器的,在D版本的Spring Cloud中,它没有默认打开。需要在配置文件中配置打开它feign: hystrix: enabled: true
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.feign.EnableFeignClients;import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;@SpringBootApplication@EnableDiscoveryClient@EnableFeignClients@EnableHystrixDashboard // 断路仪表盘@EnableCircuitBreaker // 开启断路器public class RoutingFeignApplication { public static void main(String[] args) { SpringApplication.run(RoutingFeignApplication.class, args); }}
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;/** * 在controller层,对外暴露一个”/hello”的API接口, * 通过定义的Feign客户端HelloService来消费服务 */@RestControllerpublic class HelloController { @Autowired HelloService helloService; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String sayHi(@RequestParam String name){ return helloService.sayHelloFromClientOne(name); }}
import org.springframework.cloud.netflix.feign.FeignClient;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.RequestParam;/** * 定义一个feign接口,通过@ FeignClient(“服务名”),来指定调用哪个服务 * fallback 指定服务断路处理 */@FeignClient(value = "eureka-client", fallback = HelloServiceHystrixImpl.class)public interface HelloService { @RequestMapping(value = "/hello", method = RequestMethod.GET) String sayHelloFromClientOne(@RequestParam(value = "name") String name);}
feign hystrix 断路处理:
import org.springframework.stereotype.Component;@Componentpublic class HelloServiceHystrixImpl implements HelloService { @Override public String sayHelloFromClientOne(String name) { return "sorry " + name; }}
(1)服务注册中心eureka-server集群,端口为8888,8889
eureka-client工程启动两个实例,端口分别为8887,8886
启动server-zipkin,端口为9100,
启动routing-feign, 端口为9001

(2)访问routing-feign服务:
http://localhost:9001/hello?name=bruce


反复刷新访问以上地址,可以看到会交替请求eureka-client两个实例,
即通过feign, 实现了服务的负载均衡。然后再看下zipkin服务链路:


feign hystrix 断路仪表盘监控:

调取服务后,监控断路仪表盘:

当把eureka-client所有节点的服务都停掉后,再次请求,发现已有断路处理响应:


<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency>
eureka: client: serviceUrl: defaultZone: http://peer1:8888/eureka/,http://peer2:8889/eureka/server: port: 9002spring: application: name: routing-zuulzuul: routes: api-a: path: /api-ribbon/** serviceId: routing-ribbon api-b: path: /api-feign/** serviceId: routing-feign
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.discovery.EnableDiscoveryClient;import org.springframework.cloud.netflix.zuul.EnableZuulProxy;/** * Zuul是路由控制服务器,feign(ribbon)可以实现负载均衡 * 访问 http://localhost:9002/api-feign/hello?name=bruce * 根据yml中配置的zuul路由规则, * 以 /api-ribbon/ 开头的请求都转发给routing-ribbon服务 * 以 /api-feign/ 开头的请求都转发给routing-feign服务 */@SpringBootApplication@EnableDiscoveryClient@EnableZuulProxypublic class RoutingZuulApplication { public static void main(String[] args) { SpringApplication.run(RoutingZuulApplication.class, args); }}
import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;/** * http://localhost:9002/api-ribbon/hello?name=bruce * token is empty * * http://localhost:9002/api-ribbon/hello?name=bruce&token=11 * hello bruce,I am from port:8887*/@Componentpublic class MyFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(MyFilter.class); /** * filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下: * pre:路由之前 * routing:路由之时 * post:路由之后 * error:发送错误调用 * filterOrder:过滤的顺序 * shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。 * run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。 * @return */ @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){ log.error("response writer error"); } return null; } log.info("ok"); return null; }}
(1)服务注册中心eureka-server集群,端口为8888,8889
eureka-client工程启动两个实例,端口分别为8887,8886
启动routing-ribbon, 端口为9000,
启动routing-feign, 端口为9001,
启动routing-zuul,端口为9002,
启动server-zipkin,端口为9100

(2)请求访问:http://localhost:9002/api-feign/hello?name=bruce
由于未提供token,会被zuul filter拦截

(3)加上请求令牌token后,多次刷新请求,负载均衡:


(4)请求地址中,api-feign 换作 api-ribbon后,
请求访问:http://localhost:9002/api-feign/hello?name=bruce
多次刷新请求,负载均衡:


由上测试,可见zuul实现了简单的路由控制转发功能。
以上简单演示集成了ribbon的feign,实现负载均衡的效果,以及feign整合了hystrix的断路处理机制。此外还有zuul的路由网关控制功能。
netflix下的springcloud, 已不再维护,目前只抽取了一些比较典型的功能加以温习,其他的不再赘述。后面抽空会简述下SpringCloud Alibaba。