自我总结式的学习:
我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。
eureka 尤里卡 服务注册中心,为server,其他服务可以当作client,需要在Application.java里加两个基础注解@EnableDiscoveryClient和@SpringBootApplication;server端加两个@EnableEurekaServer和
@SpringBootApplication注解;
接口里这段代码获取服务client实例:
@Autowired
private DiscoveryClient client;
如何使用eureka?
1.pom.xml中加入spring-cloud-starter-eureka依赖,
2.在application.properties中配置参数eureka.client.serviceUrl.defaultZone以指定服务注册中心的位置,详细内容如下:
# 配置服务注册中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
3.在应用主类中,新增@EnableDiscoveryClient注解,用来将config-server注册到上面配置的服务注册中心上去。
4.启动该应用,并访问http://localhost:1111/,可以在Eureka Server的信息面板中看到config-server已经被注册了。
ribbon 瑞本 客户端的负载均衡,Application里两个基本注解@SpringBootApplication和@EnableDiscoveryClient;
Application里加一段代码
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
来实现负载均衡,接口里的方法写:
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
feign 费因 服务端的负载均衡,feign中Application里加注解 @SpringBootApplication、@EnableDiscoveryClient、@EnableFeignClients;在接口中添加@FeignClient("compute-service")注解
hystrix 海思垂克斯 断路器,使用断路器调用注册服务的接口,如果注册的服务不可用,将调用断路器预设好的回调函数,返回一个正常提示错误的页面。
feign结合hystrix:
@FeignClient(value = "compute-service", fallback = ComputeClientHystrix.class)
调用断路器的回调函数,ComputeClientHystrix实现ComputeClient(接口Interface),实现接口的方法,一旦接口的方法执行中出现问题,就执行断路器的实现方法。
ribbon结合hystrix:
Application里加注解:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
接口:
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return computeService.addService();
}
service:
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "addServiceFallback")
public String addService() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
public String addServiceFallback() {
return "error";
}
config 康菲个 配置中心,并不属于能注册到eureka的服务,
server端:
Application.java文件添加@EnableConfigServer注解,开启Config Server
@EnableConfigServer
@SpringBootApplication
spring.cloud.config.server.git.uri:配置git仓库位置
spring.cloud.config.server.git.searchPaths:配置仓库路径下的相对搜索位置,可以配置多个
spring.cloud.config.server.git.username:访问git仓库的用户名
spring.cloud.config.server.git.password:访问git仓库的用户密码
到这里,使用一个通过Spring Cloud Config实现,并使用git管理内容的配置中心已经完成了,启动该应用,成功后开始下面的内容。
Spring Cloud Config也提供本地存储配置的方式。我们只需要设置属性spring.profiles.active=native,Config Server会默认从应用的src/main/resource目录下检索配置文件。也可以通过spring.cloud.config.server.native.searchLocations=file:F:/properties/属性来指定配置文件的位置。虽然Spring Cloud Config提供了这样的功能,但是为了支持更好的管理内容和版本控制的功能,还是推荐使用git的方式。
client端:
1.URL与配置文件的映射关系如下: 1)application是应用的名字 2)profile是环境 3)label是分支名
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties
2.创建bootstrap.properties配置,来指定config server
spring.application.name=didispace
spring.cloud.config.profile=dev
spring.cloud.config.label=master
spring.cloud.config.uri=http://localhost:7001/
server.port=7002
注:
spring.application.name:对应前配置文件中的{application}部分
spring.cloud.config.profile:对应前配置文件中的{profile}部分
spring.cloud.config.label:对应前配置文件的git分支
spring.cloud.config.uri:配置中心的地址
这里需要格外注意:上面这些属性必须配置在bootstrap.properties中,config部分内容才能被正确加载。因为config的相关配置会先于application.properties,而bootstrap.properties的加载也是先于application.properties。
3.通过@Value("${from}")绑定配置服务中配置的from属性。
config注册服务实现高可用
1.pom.xml中加入spring-cloud-starter-eureka依赖,
2.在application.properties中配置参数eureka.client.serviceUrl.defaultZone以指定服务注册中心的位置,详细内容如下:
# 配置服务注册中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
3.在应用主类中,新增@EnableDiscoveryClient注解,用来将config-server注册到上面配置的服务注册中心上去。
4.启动该应用,并访问http://localhost:1111/,可以在Eureka Server的信息面板中看到config-server已经被注册了。
通过eureka.client.serviceUrl.defaultZone参数指定服务注册中心,用于服务的注册与发现,再将spring.cloud.config.discovery.enabled参数设置为true,开启通过服务来访问Config Server的功能,最后利用spring.cloud.config.discovery.serviceId参数来指定Config Server注册的服务名。这里的spring.application.name和spring.cloud.config.profile如之前通过URI的方式访问时候一样,用来定位Git中的资源。
config通过与git关联,支持更好的管理内容和版本控制的功能,修改git的配置文件后,/refresh就可以刷新了
重新启动config-clinet,访问一次http://localhost:7002/from,可以看到当前的配置值
修改Git仓库config-repo/didispace-dev.properties文件中from的值
再次访问一次http://localhost:7002/from,可以看到配置值没有改变
通过POST请求发送到http://localhost:7002/refresh,我们可以看到返回内容如下,代表from参数的配置内容被更新了
再次访问一次http://localhost:7002/from,可以看到配置值已经是更新后的值了
该功能还可以同Git仓库的Web Hook功能进行关联,当有Git提交变化时,就给对应的配置主机发送/refresh请求来实现配置信息的实时更新。但是,当我们的系统发展壮大之后,维护这样的刷新清单也将成为一个非常大的负担,而且很容易犯错,那么有什么办法可以解决这个复杂度呢?后续我们将继续介绍如何通过Spring Cloud Bus来实现以消息总线的方式进行通知配置信息的变化,完成集群上的自动化更新。
Zuul zing,服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。
Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
使用zuul:
1.引入依赖spring-cloud-starter-zuul、spring-cloud-starter-eureka,如果不是通过指定serviceId的方式,eureka依赖不需要,但是为了对服务集群细节的透明性,还是用serviceId来避免直接引用url的方式吧。
2.应用主类使用@EnableZuulProxy注解开启Zuul
(这里用了@SpringCloudApplication注解,之前没有提过,通过源码我们看到,它整合了@SpringBootApplication、@EnableDiscoveryClient、@EnableCircuitBreaker,主要目的还是简化配置。)
3.application.properties中配置Zuul应用的基础信息,如:应用名、服务端口等。
一、服务路由
通过服务路由的功能,我们在对外提供服务的时候,只需要通过暴露Zuul中配置的调用地址就可以让调用方统一的来访问我们的服务,而不需要了解具体提供服务的主机信息了。
在Zuul中提供了两种映射方式:
1)通过url直接映射,我们可以如下配置:
# routes to url
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:2222/
该配置,定义了,所有到Zuul的中规则为:/api-a-url/**的访问都映射到http://localhost:2222/上,也就是说当我们访问http://localhost:5555/api-a-url/add?a=1&b=2的时候,Zuul会将该请求路由到:http://localhost:2222/add?a=1&b=2上。
其中,配置属性zuul.routes.api-a-url.path中的api-a-url部分为路由的名字,可以任意定义,但是一组映射关系的path和url要相同,下面讲serviceId时候也是如此。
通过url映射的方式对于Zuul来说,并不是特别友好,Zuul需要知道我们所有为服务的地址,才能完成所有的映射配置。
2)而实际上,我们在实现微服务架构时,服务名与服务实例地址的关系在eureka server中已经存在了,所以只需要将Zuul注册到eureka server上去发现其他服务,我们就可以实现对serviceId的映射。例如,我们可以如下配置:
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=service-A
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=service-B
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
总结:
尝试通过服务网关来访问service-A和service-B,根据配置的映射关系,分别访问下面的url
http://localhost:5555/api-a/add?a=1&b=2:通过serviceId映射访问service-A中的add服务
http://localhost:5555/api-b/add?a=1&b=2:通过serviceId映射访问service-B中的add服务
http://localhost:5555/api-a-url/add?a=1&b=2:通过url映射访问service-A中的add服务
推荐使用serviceId的映射方式,除了对Zuul维护上更加友好之外,serviceId映射方式还支持了断路器,对于服务故障的情况下,可以有效的防止故障蔓延到服务网关上而影响整个系统的对外服务
二、服务过滤
在完成了服务路由之后,我们对外开放服务还需要一些安全措施来保护客户端只能访问它应该访问到的资源。所以我们需要利用Zuul的过滤器来实现我们对外服务的安全控制。
在服务网关中定义过滤器只需要继承ZuulFilter抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。
自定义过滤器的实现,需要继承ZuulFilter,需要重写实现下面四个方法:
filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
pre:可以在请求被路由之前调用
routing:在路由请求时候被调用
post:在routing和error过滤器之后被调用
error:处理请求时发生错误时被调用
filterOrder:通过int值来定义过滤器的执行顺序
shouldFilter:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。
run:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)对返回body内容进行编辑等。
在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,我们只需要在应用主类中增加如下内容:
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
最后,总结一下为什么服务网关是微服务架构的重要部分,是我们必须要去做的原因:
1.不仅仅实现了路由功能来屏蔽诸多服务细节,更实现了服务级别、均衡负载的路由。
2.实现了接口权限校验与微服务业务逻辑的解耦。通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注业务逻辑的处理。
3.实现了断路器,不会因为具体微服务的故障而导致服务网关的阻塞,依然可以对外服务。
高可用服务注册中心 -- eureka实现高可用
1.创建application-peer1.properties,作为peer1服务中心的配置,并将serviceUrl指向peer2
spring.application.name=eureka-server
server.port=1111
eureka.instance.hostname=peer1
eureka.client.serviceUrl.defaultZone=http://peer2:1112/eureka/
2.创建application-peer2.properties,作为peer2服务中心的配置,并将serviceUrl指向peer1
spring.application.name=eureka-server
server.port=1112
eureka.instance.hostname=peer2
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/
3.在/etc/hosts文件中添加对peer1和peer2的转换
127.0.0.1 peer1
127.0.0.1 peer2
4.通过spring.profiles.active属性来分别启动peer1和peer2
java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer1
java -jar eureka-server-1.0.0.jar --spring.profiles.active=peer2
此时访问peer1的注册中心:http://localhost:1111/,如下图所示,我们可以看到registered-replicas中已经有peer2节点的eureka-server了。同样地,访问peer2的注册中心:http://localhost:1112/,能看到registered-replicas中已经有peer1节点,并且这些节点在可用分片(available-replicase)之中。我们也可以尝试关闭peer1,刷新http://localhost:1112/,可以看到peer1的节点变为了不可用分片(unavailable-replicas)。
-- 客户端 注册服务
1.我们以Chapter1-1-1中的compute-service为基础,修改application.properties配置文件:
spring.application.name=compute-service
server.port=2222
eureka.client.serviceUrl.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/
2.下面,我们启动该服务,通过访问http://localhost:1111/和http://localhost:1112/,可以观察到compute-service同时被注册到了peer1和peer2上。若此时断开peer1,由于compute-service同时也向peer2注册,因此在peer2上其他服务依然能访问到compute-service,从而实现了高可用的服务注册中心。
假设我们有3个注册中心,我们将peer1、peer2、peer3各自都将serviceUrl指向另外两个节点。换言之,peer1、peer2、peer3是两两互相注册的。启动三个服务注册中心,并将compute-service的serviceUrl指向peer1并启动,可以获得如下图所示的集群效果。
通过上面的实验,我们可以得出下面的结论来指导我们搭建服务注册中心的高可用集群:
两两注册的方式可以实现集群中节点完全对等的效果,实现最高可用性集群,任何一台注册中心故障都不会影响服务的注册与发现。
消息总线
spring中集成junit测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
测试类
@Test
测试方法
pom.xml具体配置:
<!-- springboot父工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- spring boot对单元测试的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 对eureka的支持,客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- eureka服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- 断路器支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- ribbon 客户端负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<!-- springboot 对web的支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- feign 服务端负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!-- config服务的支持 config服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- config客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<!-- springboot版本 -->
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
springboot以jar包形式启动,启动项目指定环境:
java -jar xxx.jar --spring.profiles.active=test
@Resource配置文件自动刷新
1.在类上加@RefreshScope注解。
2.引入配置@Value。