前面介绍了微服务都是通过Eureka找到的,但是在很多开发中为了规范微服务的使用,提供有一个处理控制器Zuul
Zuul其实是一个API网关,类似于设计模式里面的Facade门面模式,他的存在就像是整个微服务的门面,所有的外部客户端访问都需要经过它来进行调度与过滤
新建立一个模块【springcloud-zuul-gateway】
【springcloud-zuul-gateway】 的pom文件如下
springcloud
enjoy
1.0-SNAPSHOT
4.0.0
springcloud-zuul-gateway
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-actuator
【springcloud-zuul-gateway】修改application.yml文件
server:
port: 9501
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://admin:enjoy@eureka1:7001/eureka,http://admin:enjoy@eureka2:7002/eureka,http://admin:enjoy@eureka3:7003/eureka
register-with-eureka: false
spring:
application:
name: springcloud-zuul-gateway
【springcloud-zuul-gateway】 创建启动类
package cn.enjoy;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.web.bind.annotation.RequestMapping;
@SpringBootApplication
@EnableZuulProxy
public class ZuulApp {
public static void main(String[] args) {
SpringApplication.run(ZuulApp.class,args);
}
}
发现启动报错,其实这是因为zuul目前对springboot2.1.2支持并不好,这也是zuul最近一直被人诟病的地方。
【springcloud】修改父工程pom文件,降低springboot版本为2.0.7.RELEASE
org.springframework.boot
spring-boot-dependencies
2.0.7.RELEASE
pom
import
重新启动ZuulApp
正常访问用户服务:http://localhost:8090/users/get/1
使用zuul代理访问用户服务:http://localhost:9501/springcloud-provider-users/users/get/1
前面以及简单的使用了zuul,但你会发现访问地址还必须知道程序的名称,如果不知道这个名称是无法访问的,但如果让用户知道了这名称,那么使用zuul就是去它的实际意义的,我们可以通过名称直接调用
既然是使用代理,那么代理的功能就是不能让用户看到真实的操作,屏蔽真实的调用地址,这个时候就需要自己增加zuul的路由规则配置了。
【springcloud-zuul-gateway】修改application.yml配置文件,增加路由配置
zuul:
routes:
springcloud-provider-users: /users-proxy/**
这个时候就可以通过/users-proxy 来访问springcloud-provider-users服务
http://localhost:9501/users-proxy/users/get/1
但是还会发现,虽然现在以及开启了路由访问的支持,但依然通过应用程序的名称还是能访问
http://localhost:9501/springcloud-provider-users/users/get/1
【springcloud-zuul-gateway】修改application.yml文件,忽略掉用户服务的名称
zuul:
routes:
springcloud-provider-users: /users-proxy/**
ignored-services:
springcloud-provider-users
做完后,就可以进行代理的安全使用,但真实情况下,一般会有很多微服务,如果完全按照上面的配置方式会非常的麻烦,所有最加到的做法是可以采用一个通配符“*”的模式来统一完成。
【springcloud-zuul-gateway】修改application.yml文件
zuul:
routes:
springcloud-provider-users: /users-proxy/**
ignored-services:
"*"
除开上面这一种访问模式以外,在zuul中还有另外一种配置方式
【springcloud-zuul-gateway】修改application.yml文件
zuul:
routes:
users.path: /users-proxy/**
users.serviceId: springcloud-provider-users
ignored-services:
"*"
其中在配置文件中出现的users其实是一个逻辑名称,这个名称主要作用是将path与serviceId绑定在一起
【springcloud-zuul-gateway】如果说不想通过eureka进行访问,对于zuul来说也是可以实现的,但是在真实的开发环境中,基本不会使用
zuul:
routes:
users:
path: /users-proxy/**
serviceId: springcloud-provider-users
users2:
path: /users2-proxy/**
url: http://localhost:8090/
ignored-services:
"*"
访问:http://localhost:9501/users2-proxy/users/get/1
【springcloud-zuul-gateway】 设置公共前缀
zuul:
routes:
users:
path: /users-proxy/**
serviceId: springcloud-provider-users
users2:
path: /users2-proxy/**
url: http://localhost:8090/
ignored-services:
"*"
prefix: /enjoy-api
一旦设置了公共前缀,所以的访问路径都要在前面加上前缀
http://localhost:9501/enjoy-api/users-proxy/users/get/1
http://localhost:9501/enjoy-api/users2-proxy/users/get/1
其实zuul的功能本质上就是一个代理操作,类似于nginx,但是在真实的使用中,所有的微服务一点都有增加的认证信息,那么就必须在其访问之前追加认证的头部操作,这样的功能需要通过zuul的过去操作完成。
【springcloud-zuul-gateway】 修改application.yml配置,增加产品微服务
zuul:
routes:
users:
path: /users-proxy/**
serviceId: springcloud-provider-users
users2:
path: /users2-proxy/**
url: http://localhost:8090/
product:
path: /product-proxy/**
serviceId: springcloud-provider-product
ignored-services:
"*"
prefix: /enjoy-api
这样直接访问:http://localhost:9501/enjoy-api/product-proxy/prodcut/get/1
这样是访问不到的
【springcloud-zuul-gateway】追加过滤处理
package cn.enjoy.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import java.nio.charset.Charset;
import java.util.Base64;
public class AuthorizedRequestFilter extends ZuulFilter{
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext() ; // 获取当前请求的上下文
String auth = "admin:enjoy"; // 认证的原始信息
byte[] encodedAuth = Base64.getEncoder()
.encode(auth.getBytes(Charset.forName("US-ASCII"))); // 进行一个加密的处理
String authHeader = "Basic " + new String(encodedAuth);
currentContext.addZuulRequestHeader("Authorization", authHeader);
return null;
}
}
其中filterType为过滤的类型
在进行Zuul过滤的时候可以设置其过滤执行的位置,那么此时有如下几种类型:
【springcloud-zuul-gateway】建立一个配置程序类
package cn.enjoy.config;
import cn.enjoy.filter.AuthorizedRequestFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZuulConfig {
@Bean
public AuthorizedRequestFilter getAuthorizedRequestFilter() {
return new AuthorizedRequestFilter() ;
}
}
这个时候访问:
http://localhost:9501/enjoy-api/product-proxy/prodcut/get/1
http://localhost:9501/enjoy-api/users-proxy/users/get/1
这两个服务都能正常访问了。
作为所有接口的统一门面,zuul也是可以进行加密访问的
【springcloud-zuul-gateway】修改pom文件,增加安全访问模块
org.springframework.boot
spring-boot-starter-security
【springcloud-zuul-gateway】修改application.yml配置文件,增加用户配置
spring:
application:
name: springcloud-zuul-gateway
security:
user:
name: admin
password: enjoy
再访问http://localhost:9501/enjoy-api/users-proxy/users/get/1
这个时候就需要输入用户名密码了
前面学习feign的时候确实已经知道,他其实是去eureka中获取服务地址的,如果想使用feign来访问zuul,首先就应该让zuul注册到eureka中
【springcloud-zuul-gateway】 修改application.yml文件
eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://admin:enjoy@eureka1:7001/eureka,http://admin:enjoy@eureka2:7002/eureka,http://admin:enjoy@eureka3:7003/eureka
instance:
instance-id: microcloud-zuul-gateway
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
【springcloud-service】现在所有的服务要通过zuul的代理进行访问,新增接口
package cn.enjoy.service;
import cn.enjoy.feign.FeignClientConfig;
import cn.enjoy.service.fallback.IProductClientServiceFallbackFactory;
import cn.enjoy.service.fallback.IZUUlClientServiceallbackFactory;
import cn.enjoy.vo.Product;
import cn.enjoy.vo.Users;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@FeignClient(name = "MICROCLOUD-ZUUL-GATEWAY",configuration = FeignClientConfig.class,
fallbackFactory = IZUUlClientServiceallbackFactory.class)
public interface IZUUlClientService {
@RequestMapping("/enjoy-api/product-proxy/prodcut/get/{id}")
public Product getProduct(@PathVariable("id")long id);
@RequestMapping("/enjoy-api/product-proxy/prodcut/list")
public List listProduct() ;
@RequestMapping("/enjoy-api/product-proxy/prodcut/add")
public boolean addPorduct(Product product) ;
@RequestMapping("/enjoy-api/users-proxy/users/get/{name}")
public Users getUsers(@PathVariable("name")String name);
}
新增IZUUlClientServiceallbackFactory,在Zuul由于出现网络问题失去联系后进行容错处理
package cn.enjoy.service.fallback;
import cn.enjoy.service.IProductClientService;
import cn.enjoy.service.IZUUlClientService;
import cn.enjoy.vo.Product;
import cn.enjoy.vo.Users;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class IZUUlClientServiceallbackFactory implements FallbackFactory {
@Override
public IZUUlClientService create(Throwable throwable) {
return new IZUUlClientService() {
@Override
public Product getProduct(long id) {
Product product = new Product();
product.setProductId(999999L);
product.setProductName("feign-zuulName");
product.setProductDesc("feign-zuulDesc");
return product;
}
@Override
public List listProduct() {
return null;
}
@Override
public boolean addPorduct(Product product) {
return false;
}
@Override
public Users getUsers(String name) {
Users user = new Users();
user.setSex("F");
user.setAge(17);
user.setName("zuul-fllback:"+name);
return user;
}
};
}
}
【springcloud-consumer-hystrix】 修改ConsumerProductController,增加一个新的方法,访问接口
package cn.enjoy.controller;
import cn.enjoy.service.IProductClientService;
import cn.enjoy.service.IZUUlClientService;
import cn.enjoy.vo.Product;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/consumer")
public class ConsumerProductController {
@Resource
private IProductClientService iProductClientService;
@Resource
private IZUUlClientService izuUlClientService;
@RequestMapping("/product/get")
public Object getProduct(long id) {
return iProductClientService.getProduct(id);
}
@RequestMapping("/product/list")
public Object listProduct() {
return iProductClientService.listProduct();
}
@RequestMapping("/product/add")
public Object addPorduct(Product product) {
return iProductClientService.addPorduct(product);
}
@RequestMapping("/product/getProductAndUser")
public Object getProductAndUser(long id) {
Map result = new HashMap();
result.put("product",izuUlClientService.getProduct(id));
result.put("user",izuUlClientService.getUsers(id+""));
return result;
}
}
依次启动eureka,user服务,product服务,zuul服务,customerhystrix服务
在地址栏输入:
http://localhost/consumer/product/getProductAndUser?id=1
关闭zuul服务
http://localhost/consumer/product/getProductAndUser?id=1
发现服务降级已经开启
zuul是一个代理服务,但如果被代理的服务突然断了,这个时候zuul上面会有出错信息,例如,停止product服务
访问:http://localhost:9501/enjoy-api/product-proxy/prodcut/get/1
现在服务的调用方已经做了处理,不会出现这样的错误信息,但一般来说,对于zuul本身代理方,也应该进行zuul的降级处理
修改【springcloud-zuul-gateway】建立fallback回退处理类
package cn.enjoy.fallback;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
@Component
public class ProviderFallback implements FallbackProvider {
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders() ;
headers.set("Content-Type", "text/html; charset=UTF-8");
return headers;
}
@Override
public InputStream getBody() throws IOException {
// 响应体
return new ByteArrayInputStream("产品微服务不可用,请稍后再试。".getBytes());
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.BAD_REQUEST.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
访问:http://localhost:9501/enjoy-api/product-proxy/prodcut/get/1
getRoute:方法可以返回服务的ID,比如‘springcloud-provider-product’,如果需要匹配全部适应 “*”