SpringCloud
1、架构的演变
- 集中式架构
- 垂直式架构
- 分布式服务
- 流动计算架构(SOA)
- 微服务
2、服务调用方式
2.1 RPC和HTTP
- RPC(Remote Produce Call,远程过程调用)
自定义数据格式,基于原生TCP通信,速度快、效率高。Dubbo。
- HTTP
是一种网络传输协议,基于TCP,规定了数据传输的格式。SpringCloud。
优点:对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。
缺点:消息封装臃肿。
2.2 HTTP客户端工具
- HttpClient
- OKHttp
- URLConnection
2.3 Spring的RestTemplate
Spring提供了一个RestTemplate模板工具类,对基于HTTP的客户端进行了封装,并且实现了对象与JSON的序列化和反序列化。
3、SpringCloud
- 后台硬。
- 技术强。
- 群众基础好。
- 使用方便。
3.1 简介
SpringCloud将现在非常流行的一些技术整合到一起,实现了注入:配置管理、服务发现、智能路由、负载均衡、熔断器、控制总线、集群状态等功能。
主要涉及的组件包括:
- Eureka:服务治理组件,包含服务注册中心、服务注册与发现机制的实现(服务治理、服务注册/发现)。
- Zuul:网关组件,提供智能路由、访问过滤功能。
- Ribbon:客户端负载均衡的服务调用组件(客户端负载)
- Feign:远程调用,基于Ribbon和Hystrix的声明式服务调用组件(声明式服务调用)。
- Hystrix:容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟,为故障提供强大的容错能力(熔断、断路器、容错)。
4、调用远程服务(RestTemplate)
①依赖(web starter)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
②配置端口
server: port: 80
③注入RestTemplate
package com.xianhuii; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @SpringBootApplication public class XianhuiiServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiServiceConsumerApplication.class, args); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
④使用RestTemplate
package com.xianhuii.controller; import com.xianhuii.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/consumer/user") public class UserController { @Autowired private RestTemplate restTemplate; public User queryUserById(@RequestParam("id") Long id) { return this.restTemplate.getForObject("http://localhost:8081/user"+id, User.class); } }
5、Eureka:注册中心
- 注册中心
①导入依赖(Eureka Server)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
②编写配置
server: port: 10086 spring: application: name: xianhuii-eureka # 微服务的名称 eureka: client: service-url: defaultZone: http://localhost:${server.port}/eureka # 注册中心的地址(本微服务地址)
③添加注解(@EnableEurekaServer)
package com.xianhuii.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer // 启用Eureka服务端(注册中心) @SpringBootApplication public class XianhuiiEurekaApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiEurekaApplication.class, args); } }
-服务提供方
①导入依赖(Eureka Client)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
②编写配置
server: port: 8081 spring: application: name: server-provider # 微服务的名称 eureka: client: service-url: defaultZone: http://localhost:10086/eureka # 注册中心地址
③添加注解(@EnableDiscoveryClient)
package com.xianhuii.service; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient // 启用Eureka客户端(服务提供者) @SpringBootApplication public class XianhuiiServiceProviderApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiServiceProviderApplication.class, args); } }
-服务消费方
①导入依赖(Eureka Client)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
②编写配置
server: port: 80 spring: application: name: server-consumer eureka: client: service-url: defaultZone: http://localhost:10086/eureka
③添加注解(@EnableDiscoveryClient)
package com.xianhuii; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class XianhuiiServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiServiceConsumerApplication.class, args); } }
④使用DiscoveryClient(了解)
package com.xianhuii.controller; import com.xianhuii.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController @RequestMapping("/consumer/user") public class UserController { @Autowired private RestTemplate restTemplate; @Autowired private DiscoveryClient discoveryClient; // 包含了所有服务信息 public User queryUserById(@RequestParam("id") Long id) { List<ServiceInstance> instances = discoveryClient.getInstances("service-provider"); ServiceInstance instance = instances.get(0); return this.restTemplate.getForObject("http://" + instance.getHost() + ":" + instance.getPort()+ "/user" + id, User.class); } }
6、Eureka集群
-注册中心(相互注册)
server: port: 10086 spring: application: name: xianhuii-eureka # 作为微服务的名称,注入到Eureka容器 eureka: client: service-url: defaultZone: http://localhost:10087/eureka,http://localhost:10088/eureka # 注册中心相互注册
7、Eureka提高开发效率的技巧
-服务提供方
eureka: instance: lease-expiration-duration-in-seconds: 90 # 服务续约时间间隔(心跳时间),默认30s lease-renewal-interval-in-seconds: 30 # 服务失效时间,默认90s
-服务消费方
eureka: client: registry-fetch-interval-seconds: 5 # 服务备份间隔时间,默认30s
-注册中心
eureka: server: eviction-interval-timer-in-ms: 1000 # 失效剔除时间,默认60*1000ms enable-self-preservation: false # 关闭自我保护机制,默认开启
8、Ribbon:负载均衡
-服务消费方
①导入依赖(eureka-client-start中包含了ribbon)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
②编写配置(可不编写,使用默认的轮询算法)
service-provider: # 服务提供方的服务id ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 随机算法
③开启注解(RestTemplate上添加@LoadBalanced)
package com.xianhuii; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication public class XianhuiiServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiServiceConsumerApplication.class, args); } @LoadBalanced // 开启负载均衡 @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
④使用(根据服务提供方的服务名进行远程调用)
package com.xianhuii.controller; import com.xianhuii.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/consumer/user") public class UserController { @Autowired private RestTemplate restTemplate; public User queryUserById(@RequestParam("id") Long id) { return this.restTemplate.getForObject("http://service-provider/user/" + id, User.class); } }
9、Hystrix:熔断器
- 线程隔离。
- 服务降级。
-服务消费方(服务降级)
①导入依赖(hystrix-starter)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
②编写配置(可不编写)
③开启注解(@EnableCircuitBreaker)
package com.xianhuii; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableCircuitBreaker @EnableDiscoveryClient @SpringBootApplication public class XianhuiiServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiServiceConsumerApplication.class, args); } @LoadBalanced // 开启负载均衡 @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
④编写局部的熔断方法(@HystrixCommand)
package com.xianhuii.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController @RequestMapping("/consumer/user") public class UserController { @Autowired private RestTemplate restTemplate; @HystrixCommand(fallbackMethod = "queryUserByIdFallback") @GetMapping public String queryUserById(@RequestParam("id") Long id) { return this.restTemplate.getForObject("http://service-provider/user/" + id, String.class); } // 熔断方法:返回值、形参需要一致 public String queryUserByIdFallback(Long id) { return "服务正忙,请稍后再试!"; } }
④编写全局的熔断方法(@DefaultProperties、@HystrixCommand )
package com.xianhuii.controller; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @DefaultProperties(defaultFallback = "fallbackMethod") // 指明全局的熔断方法 @RestController @RequestMapping("/consumer/user") public class UserController { @Autowired private RestTemplate restTemplate; @HystrixCommand // 声明指定的熔断方法 @GetMapping public String queryUserById(@RequestParam("id") Long id) { return this.restTemplate.getForObject("http://service-provider/user/" + id, String.class); } // 熔断方法:返回值一致,形参为空 public String fallbackMethod() { return "服务正忙,请稍后再试!"; } }
组合注解(@SpringCloudApplication)
package com.xianhuii; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; //@EnableCircuitBreaker //@EnableDiscoveryClient //@SpringBootApplication @SpringCloudApplication // 以上三个注解的组合 public class XianhuiiServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiServiceConsumerApplication.class, args); } @LoadBalanced // 开启负载均衡 @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
-服务消费方(服务熔断)
- 熔断状态:Closed、Open、Half Open。
10、Feign:集成Ribbon、Hystrix
-服务消费方(基本使用)
①导入依赖(openfeign-starter)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
②编写配置(默认即可)
③开启注解(@EnableFeignClients )
package com.xianhuii; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableFeignClients // 开启Feign组件 @SpringCloudApplication public class XianhuiiServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiServiceConsumerApplication.class, args); } }
④根据服务提供方Controller定义接口
服务提供方Controller(UserController)
package com.xianhuii.service.controller; import com.xianhuii.service.pojo.User; import com.xianhuii.service.service.UserService; import org.springframework.beans.factory.annotation.Autowired; 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 UserController { @Autowired private UserService userService; @GetMapping("/{id}") public User queryUserById(@PathVariable("id") Long id) { return this.userService.queryUserById(id); } }
服务消费方定义的接口(@FeignClient)
package com.xianhuii.client; import com.xianhuii.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Component @FeignClient("service-provider") // 指定服务提供方的服务名 public interface UserClient { // 根据服务提供方编写方法 @GetMapping("user/{id}") User queryUserById(@PathVariable("id") Long id); }
⑤在服务消费方中使用该接口进行远程调用
package com.xianhuii.controller; import com.xianhuii.client.UserClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/consumer/user") public class UserController { @Autowired private UserClient userClient; @GetMapping public String queryUserById(@RequestParam("id") Long id) { return this.userClient.queryUserById(id).toString(); } }
-服务消费方(熔断器)
①编写配置(默认关闭熔断功能,需要手动开启)
feign: hystrix: enabled: true
②编写熔断方法(实现接口)
package com.xianhuii.client; import com.xianhuii.pojo.User; import org.springframework.stereotype.Component; @Component public class UserClientFallback implements UserClient { // 熔断方法 @Override public User queryUserById(Long id) { User user = new User(); user.setUserName("服务器正忙,请稍后再试!"); return user; } }
③指定熔断方法(@FeignClient(fallback = UserClientFallback.class))
package com.xianhuii.client; import com.xianhuii.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Component @FeignClient(value = "service-provider", fallback = UserClientFallback.class) public interface UserClient { // 根据服务提供方编写方法 @GetMapping("user/{id}") User queryUserById(@PathVariable("id") Long id); }
11、Zuul:网关
-Zuul模块
①导入依赖(zuul-starter、eureka-client)
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
②编写配置
server: port: 10010 spring: application: name: xianhuii-zuul eureka: client: service-url: defaultZone: http://localhost:10086/eureka zuul: routes: service-provider: /service-provider/** # 服务名: 映射路径(默认) # 相当于: # service-provider: # serviceId: service-provider # 服务的Id # path: /service-provider/** # 映射路径 service-consumer: /consumer/** prefix: /api # zuul网关前缀,推荐使用/api ignored-services: * # 隐藏原有内部路径
③开启注解(@EnableZuulProxy)
package com.xianhuii.zuul; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; @EnableDiscoveryClient // 启用Eureka客户端 @EnableZuulProxy // 开启Zuul网关 @SpringBootApplication public class XianhuiiZuulApplication { public static void main(String[] args) { SpringApplication.run(XianhuiiZuulApplication.class, args); } }
-自定义Zuul过滤器
继承ZuulFilter
package com.xianhuii.zuul.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; @Component public class LoginFilter extends ZuulFilter { // 过滤器的类型:pre、route、post、error @Override public String filterType() { return "pre"; } // 执行顺序:返回值越小,优先级越高 @Override public int filterOrder() { return 10; } // 是否执行run方法 @Override public boolean shouldFilter() { return true; } // 过滤器的业务逻辑 // 返回值为null:代表该过滤器什么都不做 @Override public Object run() throws ZuulException { // 初始化context RequestContext context = RequestContext.getCurrentContext(); // 获取request对象 HttpServletRequest request = context.getRequest(); // 获取参数 String token = request.getParameter("token"); // 判断 if (StringUtils.isBlank(token)) { // 拦截:不转发请求 context.setSendZuulResponse(false); // 设置响应状态码:401身份为认证 context.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED); // 设置响应体 context.setResponseBody("request error!"); } return null; } }