Spring Cloud是基于Spring Boot之上主要用于分布式架构。对于分布式架构提供了很多管理配置 例如:服务注册与发现(Eureka)、消费者(Ribbon and Feign)、断路器(Hystrix)、路由网关(Zuul)、分布式配置中心(Config)以及高可用的服务注册中心。对于Dubbo+Zookeeper分布式架构要简单很多。下面介绍下各配置的相关内容:
一、注册中心(Eureka Server and Client):
前面提到eureka是spring boot的注册中心,server client客户端把每个服务都注册eureka server的配置文件中,eureka server接收客户端转递的IP和端口进行注册。Eureka client每30秒向Eureka server发送心跳进行续约,如果超过90秒说明该服务不进行续约就会从注册中心扣除(shutdown)。Eureka Client在启动注册的时候系统默认需要40秒的时间来进行注册。
代码示例 eg:
Eureka Server配置:
1、需要在pom.xml文件增加eureka server依赖:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
2、在配置文件中application.yml中增加服务地址:
registerWithEureka和fetchRegistry两个参数表示false表示这是一个服务注册,如果没有声明表示是一个客户端。
#服务端端口
server:
port: 8761
#配置服务端口URL路径 共客户端使用
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3、在主程序类中增加注释@EnableEurekaServer注释:
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
Eureka Client配置 :
1、新建一个spring boot项目然后一样加入eureka server依赖如上。
2、在客户端配置文件中加入端口和Url:
下面eureka.client参数没有registerWithEureka和fetchRegistry两个参数说明这是配置的一个客户端,其中增加了defaultZone默认地址指向的就是注册中心Eureka Server的地址。
#1客户端端口
server:
port: 8763
#客户端配置
eureka:
instance:
hostname: localhost #声明
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8761/eureka/ #这里就是服务端暴露的地址
#应用程序名字用于调用
spring:
application:
name: eureka-client #项目名称(无论多少客户端多少个 名称要保持一样,例如 project2项目的properties 也是这个名称只是端口不一样)
3、在主程序类中增加@EnableEurekaEclient客户端声明:
@EnableEurekaClient //eureka客户端声明
@SpringBootApplication
@RestController //页面返回内容
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
@Value("${server.port}") //得到服务端端口 8762
String port;
@RequestMapping("/hi") //访问路径
public String home(@RequestParam String name) {
return "hi "+name+",i am from port:" +port;
}
}
配置多个客户端只需要提供指向注册中心的url地址及可,以上就是Eureka Server和Eureka Client的简单配置。
二、服务消费者 (Rest + Ribbon Or Feign)
Spring Cloud提供两种服务调用的方式一种是rest+ribbon,另一种feign,在微服务中服务与服务之间是相对独立的基于http restul协议 (http restuls:基于Web应用,客户端将消息封装成一个包给服务器,服务器进行解析然后将消息返回给客户端) 。
Ribbon是一个负载均衡客户端(负载均衡: 当大量服务访问同一个应用,会产生并发和死锁等情况,ribbon会根据当前服务器良好的节点进行相应的服务进行响应)。
基于服务消费(rest + ribbon) Code eg:
1、新建一个spring cloud项目增加ribbon依赖:
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
2、在配置文件如下:
#3客户端端口
server:
port: 8764
#客户端配置
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:8761/eureka/
#应用程序名字用于调用
spring:
application:
name: service-ribbon
3、主程序配置类:
@EnableHystrix是后面的熔断处理加上去也可以,如果要用需要加下依赖:
@EnableDiscoveryClient //向服务中心注册(用于消费)
@EnableEurekaClient
@SpringBootApplication
@EnableHystrix //开启断路功能
public class EurekaServiceRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceRibbonApplication.class, args);
}
@Bean //声明一个Bean
@LoadBalanced //设置负载均衡的功能
RestTemplate restTemplate() {
return new RestTemplate();
}
}
其中@LoadBalanced 就表示设置了一个负载均衡的功能。
4、增加一个Service
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod="error") //能过HystrixCommand实现熔断,当方法出现错误返回一个回调
public String hiService(String name) {
return restTemplate.getForObject("http://EUREKA-CLIENT/hi?name="+name,String.class);
}
public String error(String name) {
return "Request "+name+" is Dad error!!";
}
}
其中引用一个restTemplate模版然后调用.getForObject方法,参数是对哪一个url进行负载均衡,EUREKA-CLIENT是之前两个客户端的应用程序名称。
5、再增加一个control类:
@RestController
public class Control {
@Autowired
HelloService helloService;
@RequestMapping(value = "/hi")
public String hi(@RequestParam String name){
return helloService.hiService(name);
}
}
运行Eureka server ->Eureka Client 1 --->Eureka Client 2 --->Eureka-Service-Ribbon 运行http://localhost:8764/hi?name=miku
就会显示
hi miku,i am from port:8762
再次刷新会出现
hi miku,i am from port:8763
可以看到会出现两个不同的端口,这就是实现了负载均衡,访问不同web端不同的服务。此时结构如下:
Service-hi 8762、Service-hi 8763、Service-ribbon 8764都向Eureka Server注册了服务 ribbon项目使用restTemplate开启了负载均衡通过ribbon指向两个客户端相同的方法(hi方法)加载。达到两个不同的客户端访问。
基于服务消费(Feign) Code eg:
Feign是一个声明式的伪Http客户端,使用Feign直接在接口在增加注解,它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。feign默认集成了ribbon。
1、新建一个工程service-feign增加以下依赖:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-openfeign
2、在配置文件增加如下:
server:
port: 8765
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: service-feign
#在Fegin中开记路由断路功能
feign:
hystrix:
enabled: true
3、主程序增加如下注解:
@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
@EnableFeignClients
public class SericeFeignApplication {
public static void main(String[] args) {
SpringApplication.run(SericeFeignApplication.class, args);
}
}
4、增加一个接口:
@FeignClient(value =("EUREKA-CLIENT"),fallback = SchedualServiceHiHystric.class)
public interface SchedualServiceHi {
@RequestMapping(value = "/hi",method = RequestMethod.GET)
String sayHiFromClientOne(@RequestParam(value = "name") String name);
}
以上代码中通过@FeignClient注解两个参数 value指明哪个客户端的应用程序明,fallback如果出现错误调用哪个类。然后通过sayHiFromClientOne指明哪个url参数访问。这样就开启了负载均衡。
5、增加一个control进行转路:
@RestController
public class HiController {
@Autowired
SchedualServiceHi schedualServiceHi;
@GetMapping(value = "/hi")
public String sayHi(@RequestParam String name) {
return schedualServiceHi.sayHiFromClientOne( name );
}
}
6、增加一个断路处理的类:
@Component
public class SchedualServiceHiHystric implements SchedualServiceHi {
@Override
public String sayHiFromClientOne(String name) {
// TODO Auto-generated method stub
return "sorry "+name;
}
}
运行Eureka server ->Eureka Client 1 --->Eureka Client 2 --->Service-Feign运行http://localhost:8764/hi?name=miku
刷新 页面同样也会显示两个不同的端口进行访问。
比较两种不同的消费服务rest+ribbon通过一个Bean 调用 testTemplate 类 使用@LoadBalanced注解,而Feign则是通过在接口上进行用@FeignClient声明来开启负载均衡。
三、断路器(Hystrix)
在微服务架构中服务与服务之间可以进行相互调用,Spring Could项目通过restTemplate+ribbon和Feign进行服用调用 ,在容量的请求数据当中,我们并不能保证每个服务都可能100%调用,对某个请求可能需要大量的访问,很有可能消耗selevt容器大量的资源直到崩溃,为此spring cloud架构中提出了断路器Hystrix处理,在某方法遇到发生错误的时候可以返回一个处理。底层的服务中如果出现故障,会导致连锁故障,当对特定的服务达到一个阀值(5秒20次)断路器将会把打开,调用一个错误处理方法fallback。
基于ribbon实现断路器 Code eg:
1、在前面的Ribbon工程增加Hystrix依赖:
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2、在主程序中增加@EnableHystrix注释开启断路器功能
@EnableDiscoveryClient //向服务中心注册(用于消费)
@EnableEurekaClient
@SpringBootApplication
@EnableHystrix //开启断路功能
public class EurekaServiceRibbonApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServiceRibbonApplication.class, args);
}
@Bean //声明一个Bean
@LoadBalanced //设置负载均衡的功能
RestTemplate restTemplate() {
return new RestTemplate();
}
}
3、在前面的工程server当中看到如下:
@Service
public class HelloService {
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod="error") //能过HystrixCommand实现熔断,当方法出现错误返回一个回调
public String hiService(String name) {
return restTemplate.getForObject("http://EUREKA-CLIENT/hi?name="+name,String.class);
}
public String error(String name) {
return "Request "+name+" is Dad error!!";
}
}
代码中hiServevic方法通过@HystrixCommand注解声明一个断路器,通过参数fallbackMethod指定一个方法名称进行处理,应该方法如果在请求中方法停止了或者关闭就会跳转该方法返回一个固定值进行反馈。
2、在Feign中增加断路器 Code eg:
在前面的Servce-Feign工程看到如下:
@FeignClient(value =("EUREKA-CLIENT"),fallback = SchedualServiceHiHystric.class)
public interface SchedualServiceHi {
@RequestMapping(value = "/hi",method = RequestMethod.GET)
String sayHiFromClientOne(@RequestParam(value = "name") String name);
}
在Serivce中声明了接口SchedualServiceHi 并使用@FeignClient注释两个参数 第一个参数指明客户端的程序名称,第二数是断路器的回调方法,该参数指向一个SchedualServiceHiHystric类,该类如下:
@Component
public class SchedualServiceHiHystric implements SchedualServiceHi {
@Override
public String sayHiFromClientOne(String name) {
// TODO Auto-generated method stub
return "sorry "+name;
}
}
应该实现了SchedualServiceHi接口并使用了sayHiFromClientOne方法同样也是返回一个固定值进行反馈。
两者区别ribbon是基于方法上而feign是基于类,两者调用形式不同。如何测试同样运行Eureka Server->Eureka Client->Eureka-ribbon 启用服务,通过url:http://localhost:8765?name=miku运行 可以正常显示端口和名字,现在停止Eureka Client,再次访问url可以看见我们使用的错误信息。
四、智能路由(Zuul)
在spring could架构中要客户端发送请求,先经过负载均衡(ribbon ->loadbance or feigin->feignClient)、然后到路由zuul、再到服务器集群Eureka Client----->Eureka Server,所用的配置文件都集成在服务管理。路由器的主要功能是路由转发和过滤器。
基于Ribbon实现Zuul Code eg:
1、新建工程service-zuul在配置文件pom.xml增加如下依赖:
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.cloud
spring-cloud-starter-netflix-zuul
2、在配置文件applcation中增加如下:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
server:
port: 8766
spring:
application:
name: service-zuul
#路由网关配置(通过请求路径转发到不同的服务)
zuul:
routes:
api-a:
path: /api-a/**
serviceId: service-ribbon
api-b:
path: /api-b/**
serviceId: service-feign
其上通过zuul.routes.api-a ....参数指定两个不同的Eureka Client工程,path请求转以的路径,serviceId指向的就是工程名称。
3、在主程序中增加:
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableZuulProxy //开启路由网关功能
public class ServiceZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceZuulApplication.class, args);
}
}
通过@EnableZuulProxy开启路由功能。
4、增加一个路由处理过滤理:
@Component
public class MyFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@Override
public boolean shouldFilter() {
// TODO Auto-generated method stub
return true;
}
@Override
public Object run() throws ZuulException {
// TODO Auto-generated method stub
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){}
return null;
}
log.info("ok");
return null;
}
@Override
public String filterType() {
// TODO Auto-generated method stub
return "pre"; //参数中四种类型pre,routing,post,error错误调用
}
@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}
}
该类继承了一个路由过滤器ZuulFilter类,提供了几个方法:
filterTye方法指定一个字符串的返回类型,提供了几个参数:
pre:路由之前
routiong:路由之时
post:路由之后
error:发送错误调用
filterOrder方法:过滤的顺序
shouldfilter:逻辑判断,是否要过滤,本文true,永远过滤。
run:对请求的逻辑处理
在run方法通过判断是否带有参数token如果为空,则返回错误提示信息,如果包含则通过路由到服务中心。
访问:http://localhost:8766/api-a/hi?name=forezp ;网页显示:
token is empty
访问 http://localhost:8766/api-a/hi?name=miku&token=22 ;
网页显示:
hi miku,i am from port:8762
以上就是通过在配置文件中指定需要路由转发的Eureka Client服务,然后继承一个Zuulfilter过滤器,在实现方法通过run方法指定对请求的url进行不同的处理,来控制路由的转发,通过路由器可以进行一些访问的安全控制。
五、分布式配置中心(Spring Cloud Config)
在一个大项目中有多的配置文件还有一些测试环境test,正式环境pe,本地仓库,远程仓库如对一个文件需要进行频繁的修改难以对文件有效的管理,spring cloud提供了config配置中心服务专门对配置文件进行有效果的管理,当Eureka Servers注册的时候Config Server就加载配置文件地址通过负载均衡发送到server服务。
Spring Cloud Config Code eg:
1、新建spring boot 工程名为config-server增加如下依赖:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-config-server
2、配置文件application.yml上,指定服务端口为8889,加上作为服务注册中心的基本配置
spring.application.name=config-server
server.port=8888
#spring.cloud.config.server.git.uri=https://github.com/forezp/SpringcloudConfig/
spring.cloud.config.server.git.uri=https://github.com/lookloop21/Spring-Boot-Rersource
spring.cloud.config.server.git.searchPaths=
spring.cloud.config.label=master
spring.cloud.config.server.git.username=
spring.cloud.config.server.git.password=
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
3、在主程序增加如下:
@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
@EnableEurekaClient
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
4、在增加工程config-client 加入以下依赖:
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-config
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
5、在配置文件增加如下代码:
spring.application.name=config-client
spring.cloud.config.label=master
spring.cloud.config.profile=dev
spring.cloud.config.uri= http://localhost:8888/
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
spring.cloud.config.discovery.enabled=true
spring.cloud.config.discovery.serviceId=config-server
server.port=8882
其中通过serviceId指向config-server工程
6、在主程序增加如下代码:
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@RestController
@RefreshScope
public class ConfigClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class, args);
}
@Value("${foo}")
String foo;
@RequestMapping(value = "/hi")
public String hi(){
return foo;
}
}
其中@RefreshScope指定文件可以动态修改,不需要停止服务再启动。
访问http://localhost:8882/hi,浏览器显示:
foo version 6666 for miku
以上是通过git远程仓库实现配置访问,在config-server中配置文件指定远程仓库的配置文件url、账号、密码等相关信息,config-client配置文件中读取config-server 仓库配置文件的相关信息。通过spring cloud config配置中心可以对远程或者本地文件进行有效的管理。
总结:以上就Spring Cloud对项目管理提供的一些服务和支持还有很多包括消息总线,断路器监控等,文中主要对Eureka Server--Client服务注册与发现、消费服务Ribbon + Feign、断路器Hystrix、路由网关Zuul、文件配置中心Config Server-Clients进行了简单说明。以上就是开发过程常用到的基础服务。更多的服务需要读者查看相应的文档,不足之处下方留言。
代码地址:https://github.com/lookloop21/Spring-Boot-Rersource spring-could-code.zip文件