SpringCloud
Java 从 0 到架构师目录:【Java从0到架构师】学习记录
分布式、微服务相关概念
微服务(架构设计模式):简单来说微服务就是很小的服务,小到一个服务只对应一个单一的功能,只做一件事。这个服务可以单独部署运行,服务之间可以通过 RPC 来相互交互,每个微服务都是由独立的小团队开发,测试,部署,上线,负责它的整个生命周期。
分布式(系统部署方式): 服务是分散部署在不同的机器上的,一个服务可能负责几个功能,是一种面向 SOA 架构的,服务之间也是通过 RPC 来交互或者是 Webservice 来交互的。逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的。
分布式部署的应用不一定是微服务架构的
- 比如集群部署将相同应用复制到不同服务器上,但是逻辑功能上还是单体应用
在做架构设计的时候,先做逻辑架构,再做物理架构。当拿到需求后,估算最大用户量和并发量后,计算单个应用服务器能否满足需求:
- 如果用户量只有几百人,单体应用就能搞定,即所有的应用部署在一个服务器里
- 如果用户量很大,且某些功能会被频繁访问,建议将应用拆解为多个子系统,各自负责各自的功能,这就是微服务的架构
总结:微服务相比分布式服务来说,它的粒度更小,服务之间耦合度更低,由于每个微服务都由独立的小团队负责,因此它敏捷性更高,分布式服务最后都会向微服务架构演化,这是一种趋势, 不过服务微服务化后带来的挑战也是显而易见的,例如服务粒度小,数量大,后期运维将会很难
微服务框架构选型
选型依据:整体解决方案和框架成熟度、社区热度、可维护性、学习曲线
主流微服务框架:
- SpringCloud
- SpringCloud Alibaba
- 阿里巴巴 Dubbo / HSF
- 京东 JSF
- 新浪微博 Motan
- 当当网 Dubbox
- …
微服务框架对比:
SpringCloud 概述
SpringCloud 是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶
相关资料:
SpringBoot 和 SpringCloud 的关系?
- SpringBoot 专注于快速方便的开发单个个体微服务
- SpringCloud 是关注全局的微服务协调整理治理框架,它将 SpringBoot 开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
- SpringBoot 可以离开 SpringCloud 独立使用开发项目,但是 SpringCloud 离不开 SpringBoot,即 SpringCloud 依赖于 SpringBoot
SpringBoot 版本说明:Spring Cloud 不同其他独立项目,它拥有很多子项目的大项目,所以它是的版本是:版本名 + 版本号 (如
Angel.SR6
)
- 版本名:伦敦的地铁名
- 版本号:SR (Service Release) 是固定的,后面会有一个都给的数字
Brixton.SR5
就是 Brixton 的第 5 个 Release 版本
本次学习使用的版本是 Hoxton SR6,相关文档
服务注册与发现 - Eureka
注册中心的主要功能:
- 提供服务注册
- 提供服务发现
微服务应用和机器越来越多,调用方需要知道接口的网络地址,如果靠配置文件的方式去控制网络地址,对于动态新增机器,URL 地址维护会有很大的问题。注册中心提供服务注册与发现功能,对服务的 URL 地址进行统一的管理。
- 服务提供者 Provider:启动时向注册中心上报自己的网络信息
- 服务消费者 Consumer:启动时向注册中心上报自己的网络信息,拉取 Provider 的网络信息
主流的注册中心:
- zookeeper
- Eureka
- consul
- etd
- nacos
Spring-Cloud Euraka 介绍: SpringCloud Euraka 是 SpringCloud 集合中一个组件,它是对 Euraka 的集成,用于服务注册和发现。Eureka 是 Netflix 中的一个开源框架(2.0 以后闭源),它和 Zookeeper、Consul 一样,都是用于服务注册管理的
案例项目
商品服务:
- 查询商品列表
- 查询商品详情
订单服务:
- 创建订单
案例功能:前台调用订单服务,订单服务远程调用商品服务获取商品详情信息,基于该商品信息创建订单
注册中心 eureka-server 搭建:
- 使用 Spring Initializr 创建 SpringBoot 项目,选择 Cloud Discover -> Eureka Server
- 启动类上贴上
@EnableEurekaServer
注解 - 修改 application.properties 为 application.yml 文件,添加相关配置信息
- 运行测试,打开浏览器输入 http://localhost:8761
以下是参考官网的配置:
# application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
商品服务接口 product-api 搭建:
搭建一个普通的 Maven 项目
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private Long id; // 商品id
private String name; // 商品名称
private int price; // 商品价格
private int stock; // 商品库存
}
商品服务 product-server 搭建:
- 使用 Spring Initializr 创建 SpringBoot 项目,选择 Eureka Discover、Spring Web
- 把 application.properties 修改成 application.yml,并添加配置信息
- 启动测试,会在 Eureka 注册中心控制台页面中看到 product-server 实例
- 启动多个实例,在注册中心管控台页面也可以看到
在 Idea 启动配置中添加-Dserver.port=8082
参数,可以覆盖配置文件中的配置 - 添加 mapper、service、controller 类
Eureka 自我保护机制
Eureka Server 在运行期间会去统计心跳失败比例在 15 分钟之内是否低于 85%,如果低于 85%,Eureka Server 会将这些实例保护起来,让这些实例不会过期,但是在保护期内如果服务刚好这个服务提供者非正常下线了,此时服务消费者就会拿到一个无效的服务实例,此时会调用失败,对于这个问题需要服务消费者端要有一些容错机制,如重试,断路器等。
测试学习环境可以使用:eureka.server.enable-self-preservation=false
关闭保护机制(生产环境不要关闭保护模式)
Eureka客户端与服务器之间的通信
- Register(注册):Eureka 客户端将关于运行实例的信息注册到 Eureka 服务器,注册发生在第一次心跳
- Renew(更新 / 续借):Eureka 客户端需要更新最新注册信息(续借),通过每30秒发送一次心跳。更新通知是为了告诉 Eureka 服务器实例仍然存活。如果服务器在90秒内没有看到更新,它会将实例从注册表中删除。建议不要更改更新间隔,因为服务器使用该信息来确定客户机与服务器之间的通信是否存在广泛传播的问题
- Fetch Registry(抓取注册信息):Eureka 客户端从服务器获取注册表信息并在本地缓存。之后,客户端使用这些信息来查找其他服务。通过在上一个获取周期和当前获取周期之间获取增量更新,这些信息会定期更新(每30秒更新一次)。获取的时候可能返回相同的实例。Eureka客户端自动处理重复信息。
- Cancel(取消):Eureka 客户端在关机时向 Eureka 服务器发送一个取消请求,这将从服务器的实例注册表中删除实例,从而有效地将实例从流量中取出。
微服务调用方式 - Ribbon(负载均衡)
订单服务 order-server 搭建:
server:
port: 8090
spring:
application:
name: order-server
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
使用 Ribbon 来实现远程调用:
- 添加 RestTemplate 的 bean
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
- 通过 restTemplate.getObject 获取远程接口的信息,不需要指定具体的 IP 地址,只需要写上服务的 id(Eureka 中的 service-id)
@Autowired
private RestTemplate restTemplate;
// 商品信息应该通过 productId 从商品服务查询到
// http请求: http://localhost:8081/api/v1/product/find?id=1
//方式: httpClient, RestTemplate , URLConnection
Product product = restTemplate.getForObject(
"http://PRODUCT-SERVER/api/v1/product/find?id=" + productId,
Product.class);
这种做法还是无法避免硬编码,
PRODUCT-SERVER
还是需要自己写,后续使用 Feign 解决该问题
使用 Ribbon 实现负载均衡:在 Ribbon 实现远程调用的基础上,添加 @LoadBalanced
注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
- 配置负载均衡策略:注意,服务的名称需要和代码中的服务名称一致
参考:官方文档
PRODUCT-SERVER:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
微服务调用方式 - Feign
使用 Ribbon 的 RestTemplate 实现远程调用,调用地址会写死在代码里,并不是很好的做法。
使用 Feign 实现远程调用:
- 在 product-api 项目中添加 openfeign 依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
- 在 product-api 项目中添加 ProductFeignApi 接口
/** * @FeignClient 标记为 Feign 接口, name 参数的值是提供服务的 service-id * @RequestMapping 规范请求的 URL * @RequestParam("id") 确定请求的参数 **/ @FeignClient(name = "product-server") public interface ProductFeignApi { @RequestMapping("/api/v1/product/find") Product find(@RequestParam("id") Long id); }
- 在 product-server 项目中添加 ProductFeignApi 的实现类(本质上就是 Controller)
@RestController public class ProductFeignClient implements ProductFeignApi { @Autowired private IProductService productService; @Value("${server.port}") private String port; @Override public Product find(Long id) { Product product = productService.get(id); Product result = new Product(); BeanUtils.copyProperties(product, result); result.setName(result.getName() + ",data from " + port); return result; } }
- 在 order-server 项目中的启动类上贴上
@EnableFeignClients
注解
这个注解会去扫描有@FeignClient
的的接口,并且创建一个代理对象@EnableFeignClients(basePackages = "com.maoge.demo.feign") public class OrderServerApplication { // main }
超时时间、超时重试次数
源码中 options 配置的是 6000ms,但是 Feign 默认加入了 Hystrix,此时是 1 秒超时,我们可以通过修改配置,修改默认超时时间:
新增、删除等业务方法执行多次一定要做幂等性
- 如果没有实现接口幂等性,将重试次数改为 0
以下是在 Fegion 中配置,一般不会配这个:
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
一般会在 Ribbon 中进行配置:
ribbon:
# 在一个服务上的最大重试次数(不包括第一次)
MaxAutoRetries: 2
# 重试之后选择下一个服务器的次数
MaxAutoRetriesNextServer: 1
ConnectTimeout: 1000
ReadTimeout: 1000