目录
引言
- 微服务是一种框架风格,按照业务板块来划分应用代码,使单个应用的职责更清晰,相互之间可以做到独立升级迭代
微服务框架所包含的技术栈
面试题
- 微服务中常用的组件有哪些?
回答:
- 首先微服务是有多个的,微服务之间进行相互调用便需要用到 OpenFeign 组件
- 其次使用 Nacos 组件作为注册中心,使得每个服务均可注册自己的服务,从而有效地对这些服务进行管理
- 随着服务数量的增加,不可避免地会产生加载不均的问题,此时我们便需要用到 LoadBalancer 组件来实现负载均衡,确保每个服务能被公平且合理地调用
- 至此微服务集群便形成了,如果对外提供服务,并非随便就可以访问的,所以在微服务群前面便就需要使用 Geteway 网关作为入口
- 访问流量激增,引起服务器雪崩这显然是不被允许的,此处我们便可使用 Sentinel 组件进行限流、熔断降级保护
- 除此之外,分布式系统一般都会引发分布式事务问题,此时便需要用到 Seata 组件提供高性能和简单易用的事务服务
微服务架构演变
单体架构
- 将业务的所有功能集中在一个项目中开发,打成一个包部署
- 适合小型项目
总结:
- 优 ——> 架构简单、部署成本低
- 缺 ——> 耦合度高
分布式架构
- 根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务
- 适合大型互联网项目
总结:
- 优 ——> 降低服务耦合度、利于服务升级拓展
分布式框架需要考虑的问题:
- 服务拆分的粒度如何?
- 服务集群地址如何维护?
- 服务之间如何实现远程调用?
- 服务健康状态如何感知?
微服务架构
- 微服务是一种经过良好架构设计的分布式架构方案
特征:
- 面向服务 ——> 微服务对外暴露业务接口
- 隔离性强 ——> 服务调用做好隔离、容错、降级,避免出现级联问题
- 单一职责 ——> 微服务拆分粒度更小,每一个服务均对应唯一的业务能力,避免重复业务开发
- 自治 ——> 团队独立、技术独立、数据独立、部署独立
总结:
- 优点 ——> 拆分粒度更小、服务更独立、耦合度更低
- 缺点 ——> 框架非常复杂、运维、监控、部署难度提高
微服务技术对比
- 国内最知名微服务技术框架的就是 SpringCloud 和 阿里巴巴的 Dubbo
Dubbo SpringCloud SpringCloudAlibaba 注册中心 zookeeper、Redis Eureka、Consul Nacos、Eureka 服务器远程调用 Dubbo协议 Feign(http协议) Dubbo、Feign 配置中心 无 SpringCloudConfig SpringCloudConfig、Nacos 服务网关 无 SpringCloudGateway、Zuul SpringCloudGateway、Zuul 服务监管和保护 dubbo-admin,功能弱 Hystrix Sentinel
认识 SpringCloud
- SpringCloud 集成了各种微服务功能组件
- SpringCloud 给予 SpringBoot 实现了各种组件的自动装配,达到了开箱即用体验的效果
SpringBoot 版本兼容关系
服务拆分和远程调用
服务拆分注意事项
- 不同微服务,不要重复开发相同业务
- 微服务数据独立,不要访问其他微服务的数据库
- 微服务可以将自己的业务暴露为接口,供其他微服务调用
实例理解
- 此处我们将该服务拆分成了 订单模块 和 用户模块
- 每个微服务均有属于自己的数据库
- 由于在不同的数据库中,因此只能通过订单 id 查询订单数据,或者通过用户 id 查询用户数据,无法交叉访问
问题:
- 如何在根据订单 id 查询订单的时,将该订单所属用户的用户信息一并返回呢?
- 即如何在 订单模块 中远程调用 用户模块 中的接口?
远程调用
- 注册 RestTemplate
- 在 order-service 的 OrderApplication 中(SpringBoot 启动类)注册 RestTemplate
package cn.itcast.order; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; //@MapperScan 注解的作用相当于在指定包下的所有 mapper 接口上都加上了 @mapper 注解 @MapperScan("cn.itcast.order.mapper") @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } /** * 将 RestTemplate 对象注入到 spring 容器中 * @return */ @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
- 远程调用 RestTemplate
- 在 order-service 中,通过 RestTemplate 的 getForObject 方法构造 get 请求并发送
- user-service 收到请求并返回对应数据,最后 getForObject 方法收到数据并反序列化为指定类型
package cn.itcast.order.service; import cn.itcast.order.mapper.OrderMapper; import cn.itcast.order.pojo.Order; import cn.itcast.order.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Service public class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; public Order queryOrderById(Long orderId) { // 1、查询订单 Order order = orderMapper.findById(orderId); // 2、利用 RestTemplate 发起 http 请求,查询用户 // a) url 路径 String url = "http://localhost:8081/user/" + order.getUserId(); // b) 发起 http 请求,实现远程调用 // get请求:getForObject // post请求:postForObject // 第一个参数是 url, 第二个参数是请求后响应的参数类型(自动的反序列化) User user = restTemplate.getForObject(url, User.class); // 3、封装 User 到 Order order.setUser(user); // 4、返回 return order; } }
运行结果:
引入问题
- 此处利用 RestTemplate 发送的 http 请求时,无疑是将 url 给直接写死了
- 如果为了应对更多并发,我们的 user-service 可能会被部署为多实例,形成一个集群
- 此时如果还是采取硬编码的形式来写的话,我们又该写那个 user-service 的代码呢?
- 如果永远写的都是 8081 端口,那另外两个 user-service 存在的意义是什么呢?
解决方案:
- 使用 Eureka 来解决上述问题