在前面学习ribbon,feign的时候,向api提供者发起请求的时候,实际用的是http://提供者服务名称这形式,如果提供者api自身进行了集群,没有使用类似nginx的反向代理,而消费者又没有连接eureka的话,那就麻烦了。spring cloud中的zuul其中的一个功能就担任了反向代理的功能,还能连接eureka进行服务发现。
如果使用nginx对提供者集群进行反向代理,架构类似如下
如果消费者服务集群新增或减少或ip端口变更,那对应的nginx也要变更,然后重启。
如果使用zuul+eureka,则消费者服务集群变更的时候,zuul重eureka中获取到最新的消费者集群信息。
当然也可以只弄一个zuul服务代理多个服务集群。
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务
路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,
为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集
群主体能够具备更高的可复用性和可测试性。
使用spring boot搭建zuul服务
pom.xml
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-zuul
org.springframework.retry
spring-retry
org.springframework.cloud
spring-cloud-dependencies
Dalston.RELEASE
pom
import
org.springframework.boot
spring-boot-maven-plugin
package com.fei.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@SpringCloudApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableZuulProxy 是zuul代理生效,@SpringCloudApplication 里面集合了springboot,eureka客户端,断路器hystrix
/**
* @author Spencer Gibb
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
logging.config=classpath:logback.xml
logging.path=d:/logs
##tomcat set###
# eureka的默认端口是8761
server.port=8888
server.session-timeout=60
###########
spring.application.name=zuul-server
#像eureka服务注册信息时,使用ip地址,默认使用hostname
eureka.instance.preferIpAddress=true
eureka.instance.instance-id=${spring.cloud.client.ipAddress}:${server.port}
eureka.client.serviceUrl.defaultZone=http://01.server.eureka:8081/eureka/
#开启健康检查(需要spring-boot-starter-actuator依赖)
eureka.client.healthcheck.enabled=true
#租期到期时间,默认90秒
eureka.instance.lease-expiration-duration-in-seconds=30
#租赁更新时间间隔,默认30,即30秒发送一次心跳
eureka.instance.lease-renewal-interval-in-seconds=10
#启动负载均衡的重试机制,默认false
spring.cloud.loadbalancer.retry.enabled=true
#Hystrix是否启用超时时间
hystrix.command.default.execution.timeout.enabled=true
#Hystrix断路器的超时时间,默认是1s,断路器的超时时间需要大于ribbon的超时时间,不然不会触发重试。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
#ribbon请求连接的超时时间
ribbon.ConnectTimeout=250
#请求处理的超时时间
ribbon.ReadTimeout=1000
#对所有请求操作都进行重试
ribbon.OkToRetryOnAllOperations=true
#对当前服务的重试次数(第一次分配给9082的时候,如果404,则再重试MaxAutoRetries次,如果还是404,则切换到其他服务MaxAutoRetriesNextServer决定)
ribbon.MaxAutoRetries=0
#切换服务的次数(比如本次请求分配给9082处理,发现404,则切换分配给9081处理,如果还是404,则返回404给客户端)
ribbon.MaxAutoRetriesNextServer=1
##反向代理配置,类似nginx
#####用户管理服务#####
zuul.routes.user-api.path=/user-api/**
#当stripPrefix=true,http://127.0.0.1:7081/user-api/user/find =>http://127.0.0.1:7081/user/find
#当stripPrefix=false,http://127.0.0.1:7081/user-api/user/find =>http://127.0.0.1:7081/user-api/user/find
zuul.routes.user-api.stripPrefix=true
#eureka中对应的服务名称
zuul.routes.user-api.serviceId=api-user-server
#重试,默认false
zuul.routes.user-api.retryable=true
#ribbo负载均衡策略配置,默认是依次轮询
#api-user-server.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
#####积分管理服务#####
zuul.routes.integral-api.path=/integral-api/**
zuul.routes.integral-api.stripPrefix=true
zuul.routes.integral-api.serviceId=API-INTEGRAL
重试机制pom.xml必须有spring-retry,同时spring.cloud.loadbalancer.retry.enabled=true,具体的某个服务zuul.routes.user-api.retryable=true,其中的user-api是路由名称,可自行自定义。
前面学习已经搭建有eureka服务了127.0.0.1:8081,eureka-api提供者api-user-server,这里就不重复了,完整代码见github.
启动eureka服务127.0.0.1:8081,启动zuul服务127.0.0.1:8888,eureka-api提供者中的UserProvider.java
package com.fei.springcloud.provider;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserProvider {
@GetMapping(value="/find/{id}")
public String find(@PathVariable("id") String id,HttpServletRequest request){
long startTime = System.currentTimeMillis();
System.out.println("服务端端口:"+request.getLocalPort()+" 接收到请求。。。。");
//实际项目中,这里可以使用JSONObject,返回json字符串
//为了便于测试消费者app的负载均衡,返回服务端端口
// try {
// Thread.sleep(5000);//休眠5秒钟,测试网关超时
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
long endTime = System.currentTimeMillis();
String s = "张三"+" 服务端端口:"+request.getLocalPort()+" 耗时毫秒=" + (endTime - startTime);
System.out.println("服务端端口:"+request.getLocalPort()+" 返回数据。。。。。耗时毫秒=" + (endTime - startTime));
return s;
}
}
修改端口为9081启动,然后修改端口为9082启动,也就是127.0.0.1:9081,127.0.0.1:9082.
浏览器请求http://127.0.0.1:8888/user-api/user/find/1,不停刷新,显示的信息来源9081,9082轮询。说明代理起作用了。
zuul的配置文件,把重试配置关闭,同时停止9082。重启zuul,然后刷新http://127.0.0.1:8888/user-api/user/find/1,发现信息来源9081时正常,请求到9082时就报错了,zuul没有再次把请求放到9081去。停止zuul,修改配置文件,把重试配置开启,启动zuul,稍等十几秒后再刷http://127.0.0.1:8888/user-api/user/find/1,发现不报错了,说明但分配请求到9082时,发现404,立刻转发请求到9081去了。
经测试,当请求时404时才会触发重试。测试例子,api中的代码加入Thread.slepp(5000),模拟api的处理时间超过了zuul中配置的超时时间,超时引起的错误不会触发重试。
完整代码