Spring Cloud微服务实战:手把手带你整合eureka&zuul&feign&hystrix(附源码)

Spring Cloud微服务实战:手把手带你整合eureka&zuul&feign&hystrix(附源码)

Spring Cloud简介

Spring Cloud是一个基于Spring Boot实现的微服务架构开发工具。它为微服务架构中涉及的配置管理、服务治理、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。

Spring Cloud包含了多个子项目,比如Spring Cloud Config、Spring Cloud Netflix、Spring Cloud Bus、Spring Cloud Stream、Spring Cloud Zookeeper等等。

本文介绍基于Spring Boot 2.0.5版本,Spring Cloud Finchley.SR1版本的微服务搭建,包括eureka&zuul&feign&hystrix的整合。

最终项目结构

文末附源码地址。

服务注册发现模块

该模块对应本次搭建项目中的cloud-eureka,eureka作为服务发现注册中心首先搭建,因为后面的服务都要注册到上面。当然服务发现还可以用zookeeper、consul等等,最近阿里也启动了新的服务发现开源项目Nacos,各种服务注册发现中间件真是层出不穷。

首先使用idea生成多模块maven主工程,新建一个空白标准的maven project(不要选择Create from archetype选项)

在主工程上新建module,选择Spring Initializr

输入cloud-eureka服务注册中心模块信息

选择Cloud Discovery中的Eureka Server依赖

生成的pom文件部分配置:

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</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>${spring-cloud.version}</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>
复制代码

启动类,加上@EnableEurekaServer注解:

@SpringBootApplication
@EnableEurekaServer
public class CloudEurekaApplication {

	public static void main(String[] args) {
		SpringApplication.run(CloudEurekaApplication.class, args);
	}
}
复制代码

默认情况下服务注册中心会将自己作为客户端注册到Eureka Server,所以需要禁用它的客户端注册行为,配置文件application.properties添加如下配置:

#端口号.
server.port=8070
#关闭自我保护.
eureka.server.enable-self-preservation=false
#清理服务器时间间隔[5s]
eureka.server.eviction-interval-timer-in-ms=5000

#主机名.
eureka.instance.hostname=localhost
#是否将自己作为客户端注册到Eureka Server[当前模块只是作为Eureka Server服务端所以设为false]
eureka.client.register-with-eureka=false
#是否从Eureka Server获取注册信息[当前是单点的Eureka Server所以不需要同步其它节点的数据]
eureka.client.fetch-registry=false

#Eureka Server[查询注册服务]地址.
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka
复制代码

启动工程访问:http://localhost/8070/ ,可看到如下界面,其中还没有服务实例

客服端模块(服务提供者)

该模块对应本次搭建项目中的cloud-provider,其作为服务提供者客户端在注册中心进行注册。搭建过程和cloud-eureka类似,在主工程上新建module并选择Spring Initializr即可,唯一区别是依赖选择Cloud Discovery中的Eureka Discovery:

pom文件依赖配置如下:

<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.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>
复制代码

启动类,加上@EnableDiscoveryClient,表示其作为服务发现客户端

@SpringBootApplication
@EnableDiscoveryClient
public class CloudProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(CloudProviderApplication.class, args);
    }
}
复制代码

application.properties添加如下配置:

#应用名称.
spring.application.name=cloud-provider
#应用端口号.
server.port=8080
#Eureka Server服务器地址.
eureka.client.serviceUrl.defaultZone=http://localhost:8070/eureka/
复制代码

通过spring.application.name指定微服务服务提供者的名称,后续使用该名称便可以访问该服务。
eureka.client.serviceUrl.defaultZone指定服务注册中心地址。

启动该工程,再次访问:http://localhost/8070/ , 可以看到出现了启动的CLOUD-PROVIDER服务:

定义MyController类,使用Rest风格请求,添加info方法如下:

@RestController
public class MyController {

    @RequestMapping(value = "/info", method = RequestMethod.GET)
    public String info() {
        try {
            //休眠2秒,测试超时服务熔断[直接关闭服务提供者亦可]
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello, cloud-provider";
    }
}
复制代码

访问:http://127.0.0.1:8080/info , 返回信息如下

声明式服务调用组件Feign及服务熔断组件Hystrix整合

新建服务消费者模块,该模块对应本次搭建项目中的cloud-consumer。同样,新建过程和上述模块类似,这里不再赘述。本模块将通过Feign组件调用上一个模块服务的info方法,并通过Hystrix实现服务调用失败时的服务熔断。

maven依赖配置:

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-openfeign</artifactId>
	</dependency>

	<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.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>
复制代码

启动类,加上@EnableFeignClients和@EnableEurekaClient

@SpringBootApplication
@EnableFeignClients //调用者启动时,打开Feign开关
@EnableEurekaClient
public class CloudConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudConsumerApplication.class, args);
    }
}
复制代码

application.properties添加如下配置:

#应用名称.
spring.application.name=cloud-consumer
#端口号.
server.port=8081
#Eureka Server服务器地址.
eureka.client.serviceUrl.defaultZone=http://localhost:8070/eureka/

#高版本spring-cloud-openfeign请求分为两层,先ribbon控制,后hystrix控制.
#ribbon请求处理的超时时间.
ribbon.ReadTimeout=5000
#ribbon请求连接的超时时间
ribbon.ConnectionTimeout=5000

##设置服务熔断超时时间[默认1s]
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000

#开启Hystrix以支持服务熔断[高版本默认false关闭],如果置为false,则请求超时交给ribbon控制.
#feign.hystrix.enabled=true
复制代码

定义服务接口类InfoClient,作为调用远程服务的本地入口:

//1.name为被调用的服务应用名称.
//2.InfoFallBack作为熔断实现,当请求cloud-provider失败时调用其中的方法.
//3.feign配置.
@FeignClient(name = "cloud-provider", fallback = InfoFallBack.class, configuration = MyFeignConfig.class)
public interface InfoClient {

    //被请求微服务的地址
    @RequestMapping("/info")
    String info();
}

复制代码

定义熔断类InfoFallBack,如果远程服务无法成功请求,则调用指定的本地逻辑方法:

@Component
public class InfoFallBack implements InfoClient {
    @Override
    public String info() {
        return "fallback info";
    }
}
复制代码

定义个性化的feign配置类MyFeignConfig:

@Configuration
public class MyFeignConfig {

    /**
     * feign打印日志等级
     * @return
     */
    @Bean
    Logger.Level feignLoggerLeval(){
        return Logger.Level.FULL;
    }
}
复制代码

定义服务调用类ConsumerController,通过本地方法入口调用远程服务:

@RestController
@Configuration
public class ConsumerController {

    @Autowired
    InfoClient infoClient;

    @RequestMapping(value = "/consumerInfo", method = RequestMethod.GET)
    public String consumerInfo(){
        return infoClient.info();
    }
}
复制代码

启动工程,访问:http://127.0.0.1:8081/consumerInfo , 成功调用远程服务:

服务熔断测试,application.properties配置修改如下:

  1. feign.hystrix.enabled=true注释打开,开启Hystrix以支持服务熔断,这边高版本默认为false
  2. 关闭cloud-provider服务或者去除ribbon请求处理超时时间及服务熔断超时时间的配置

重新启动cloud-consumer服务,再次访问,服务熔断成功调用了本地的方法:

服务网关组件Zuul整合

该组件提供了智能路由以及访问过滤等功能。新建服务网关模块cloud-zuul,过程和以上同样类似,这里省略。

maven依赖配置:

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>
复制代码

启动类,加上@EnableZuulProxy和@EnableEurekaClient注解:

@SpringBootApplication
@EnableZuulProxy //开启网关Zuul
@EnableEurekaClient
public class CloudZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(CloudZuulApplication.class, args);
    }
}
复制代码

application.properties添加如下配置:

#应用名称.
spring.application.name=cloud-zuul
#应用端口号.
server.port=8071
#Eureka Server服务器地址.
eureka.client.serviceUrl.defaultZone=http://localhost:8070/eureka/

#通过指定URL配置了Zuul路由,则配置以下两个超时时间.
#zuul.host.connect-timeout-millis=5000
#zuul.host.socket-timeout-millis=5000

#zuul使用服务发现的方式[通过serviceId路由服务],得配置ribbon的超时时间.
#官网文档已说明:http://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_zuul_timeouts
#ribbon请求处理的超时时间.
ribbon.ReadTimeout=5000
#ribbon请求连接的超时时间.
ribbon.ConnectionTimeout=5000

##设置服务熔断超时时间[默认1s]
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000

#只要访问以/api/开头的多层目录都可以路由到服务名为cloud-provider的服务上.
zuul.routes.cloud-provider=/api/**
复制代码

注意zuul.routes.cloud-provider表示要访问的服务以何种路径方式路由。

定义网关过滤器AccessFilter,根据过滤器的不同生命周期在调用服务时调用过滤器中的方法逻辑。

/**
 * 服务网关过滤器
 */
@Component
public class AccessFilter extends ZuulFilter {

    /**
     * 返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型:
     *  pre:可以在请求被路由之前调用
     *  route:在路由请求时候被调用
     *  post:在route和error过滤器之后被调用
     *  error:处理请求时发生错误时被调用
     * @return
     */
    @Override
    public String filterType() {
        return "pre"; //前置过滤器
    }

    @Override
    public int filterOrder() {
        return 0; //过滤器的执行顺序,数字越大优先级越低
    }

    @Override
    public boolean shouldFilter() {
        return true;//是否执行该过滤器,此处为true,说明需要过滤
    }

    /**
     * 过滤器具体逻辑
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        System.out.println(String.format("%s demoFilter request to %s", request.getMethod(), request.getRequestURL().toString()));
        String username = request.getParameter("username");// 获取请求的参数
        if(!StringUtils.isEmpty(username)&&username.equals("bright")){//当请求参数username为“bright”时通过
            ctx.setSendZuulResponse(true);// 对该请求进行路由
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess", true);// 设值,让下一个Filter看到上一个Filter的状态
            return null;
        }else{
            ctx.setSendZuulResponse(false);// 过滤该请求,不对其进行路由
            ctx.setResponseStatusCode(401);// 返回错误码
            ctx.setResponseBody("{\"result\":\"username is not correct!\"}");// 返回错误内容
            ctx.set("isSuccess", false);
            return null;
        }
    }
}
复制代码

启动该工程,访问:http://127.0.0.1:8071/api/info , 成功执行网关过滤器中的方法逻辑,请求被过滤,没有调用远程服务返回了设置的错误内容:

访问:http://127.0.0.1:8071/api/info?username=bright ,执行网关过滤器中的方法逻辑,请求参数合法,所以请求没有被过滤成功调用了远程服务:

项目源码

欢迎微信扫码关注微信公众号:后端开发者中心,不定期推送服务端各类技术文章。

转载于:https://juejin.im/post/5bb2f424e51d450e7b174a3f

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值