- 本博客纯属个人总结,非原创。
- 喜欢技术交流的,可关注博主,武汉有后端开发群,可支持内推,了解武汉行情等。
前沿
优惠卷平台项目的整体功能和模块,以及每个功能点的技术选型和背后的依据。
搭建一个简化版的营销优惠计算系统,实现
- 优惠卷模版的创建,
- 用户领取优惠卷,
- 下单核销优惠卷
- 订单价格计算等功能。
实现一个领取优惠卷的功能,首先是要创建一个营销规则模版,这个模版就像一个模具一样,通过这个模具来铸造,并最终发放到用户手中。
.
使用模版的好处是可以对优惠卷规则做一层抽象,比如满减类,打折类,这些优惠卷是具体的优惠金额不同,但是玩法类似。我们把相类似的玩法功能抽象成一个模版,就可以简化具体优惠卷的创建和核销流程。
整个项目划分了优惠卷模版服务,计算服务,用户服务,平台类组件
优惠卷模版服务:模版规则是创建优惠卷的前置条件,每种类型的模版都是一个计算公式,这个公式约定了优惠计算的方式,模版服务实现了模版规则的创建,克隆,分页查找等功能,另外还可以在项目中定义满减,随机立减,满折,晚间双倍优惠等多种卷模版类型。
计算服务:根据用户购物车中的商品信息和优惠卷信息,来计算当前订单优惠后的价格,另外,如果用户有多张优惠卷,还提供了”优惠金额试算“,帮助用户挑选最省钱的优惠卷。
用户服务:暴露给外部用户使用的接口,它依赖于模版服务+计算服务,为了完成底层逻辑,主要业务场景是用户领卷,订单价格试算,下单核销,订单金额等功能。
平台类组件:包括一些与业务无关的中心化组件。
从整体来看,模版服务和计算服务是基础服务,用户服务是对用户开放的接口,他依赖于模版服务+计算服务来完成业务逻辑,而平台类组件提供了横向的微服务特性支持,比如微服务网关,链路追踪等功能,整体图如下:
SpringCloud实战项目全景规划
- 第一阶段:搭建基础的微服务功能,实现微服务之间的通信;
- 第二阶段:为各个模块构建服务容错,分布式配置中心,分布式链路追踪能力;
- 第三阶段:进一步实现微服务网关,消息驱动和分布式事务;
第一阶段:实现微服务通信
将用户微服务,优惠卷模版服务和订单计算服务拆分为独立部署的业务系统,通过注册中心来实现服务实现和服务发现。让各个微服务可以互相调用,涉及到的技术:nacos注册中心,loadbalance负载均衡组件,openFeign服务间调用。
- nacos注册中心:是注册中心组件,负责收集所有服务节点的地址信息,并维护服务注册表,所有服务上线后都会向他汇报状态。
- loadbalance负载均衡:在客户端发送服务调用的时候,他会负责从nacos的注册表中挑选一台目标服务器。
- openFeign:基于HTTP的远程服务调用,让我们就像本地接口一样方便地发起远程服务调用。
为什么选择nacos+loadbalance作为选型方案。
第一:早期是eureka+ribbon作为服务治理+负载均衡组件,但netfix正退出springcloud的历史舞台,eureka+Ribbon已经进入不维护状态(断更),在考虑技术选型时,选择后劲更大,更新最活跃的nacos和loadbalance组件。
服务治理:服务治理的重点是搭建基础的跨服务调用功能,把用户服务,计算服务,订单服务改造成可以独立启动的微服务,并借助nacos的服务发现功能,通过Webflux组件中的webClient实现基于HTTP的跨服务间的调用。
负载均衡
简化服务调用:使用OpenFeign组件对用户服务进行改造,将原先复杂的WebClient调用替换为简洁的OpenFeign调用。
第二阶段:服务容错,链路追踪,分布式配置
利用服务容错提高微服务架构的可用性,搭建全链路的分布式链路追踪能力,实现统一的配置管理和动态属性推送,涉及的组件:nacos config,Sentinel,Sleuth+Zipkin+Elk。
在微服务架构中,
服务容错:保证高可用的重要手段,推荐使用:Sentinel作为容错组件,可以实现降级,熔断,容量整形等多种服务容错途径。
后期会搭建:Sentinel Dashboard控制台,通过控制台将降级规则和流量整形规则应用到业务埋点中。
链路追踪:推荐使用Sleuth组件,线上异常排查全靠它,使用全局唯一标记将跨微服务调用链上的各个环节全部串联起来,结合Zipkin组件实现调用链的可视化检索,将调用链上各个阶段的请求按顺序显示在页面上,目前业界主流的ELK组合(es + logstash + kibana)作为日志检索系统。
“分布式配置”:使用Nacos Config组件,实现配置项的远程获取和动态推送。
第三阶段:统一流量入口,消息驱动,分布式事务
流量入口:采用Gateway,担任路由转发,同时它提供丰富的谓词组合实现复杂的路由判断逻辑,可以在网关层定义拦截器,做特殊处理。
消息驱动:采用Stream
分布式事务:Seata组件,保证微服务环境下的事务一致性,Seata分布式事务解决方案有两种,第一:没有代码侵入的Seata AT方案,第二:补偿性的Seata TCC方案。
项目细节
1:对于境内电商行业来说,金额往往是以分为单位的,这样我们可以直接使用 Long 类型参与金额的计算,比如 100 就代表 100 分,也就是一块钱。这比使用 Double 到处转换 BigDecimal 省了很多事儿。
2:尽可能减少级联配置,用单表查询取而代之,如果一个查询需要 join 好几张表,最好的做法就通过重构业务逻辑来简化 DB 查询的复杂度。
3:在做大型应用架构的时候,我们通常会把计算密集型服务与 IO/ 存储密集型服务分割开来,这样做的一个主要原因是提高资源利用率。
比如有两个:计算密集型的微服务A,IO密集型微服务B,大促流量来时,如果微服务A的压力比较大,我们可以调配高性能CPU和内存去定向扩容A集群,如果微服务B压力吃紧,我们可以定向分配资源给B。
计算型:不吃网络IO和磁盘空间,主要占用CPU,内存等资源。
微服务组件
服务治理
解决的首要任务就是:服务注册+服务发现,通过这两项技术,我们能让服务之间发起面对面的直接调用。
服务A怎么知道服务B中每台机器的地址呢?
我们需要搭建一个中心化的服务注册中心,服务B要将自己的信息添加到注册中心里,服务A就能从注册中心获取到服务B的所有节点。
服务B集群向注册中心发起注册,将自己的信息注册到注册中心,这个过程就是服务注册。
每隔一段时间,服务A就会从注册中心中获取服务B集群的服务列表,这个过程叫服务发现。
最后服务A根据本地负载均衡策略,从服务列表中选取某一个服务B的节点,发起服务调用。
注册中心是一个中心化的信息管理者,所有的微服务节点在启动后都会将自己的地址信息添加注册到注册中心,在服务注册的过程中,有两个关键信息很重要。
- 服务名称:服务名称通常默认是spring.application.name属性,在服务注册过程中我们必须将应用服务名上报到注册中心,这样其他服务才能根据服务名找到对应的节点列表。
- 地址信息:包括服务节点的ip地址和端口。
容错机制
所有的服务都要在注册中心注册,而且每个节点都需要每隔一段时间向注册中心同步自己当前的状态,简称:心跳。
如果节点持续发送心跳信息,则一切正常,如果注册中心有一段时间未收到心跳包,则视为下线,该服务从服务列表中剔除。
当服务节点关闭或重启的时,会发送一条**“服务下线”**指令给注册中心,也视为下线。
nacos体系架构
三个核心:领域模式,数据模型,基本架构。
领域模型
Nacos的领域模型描述了服务与实例之间的边界和层级关系。Nacos的服务领域模型是以“服务”为维度来构建的。
这个服务并不是指集群中的单个服务器,而是指微服务的服务名称。
服务,集群,实例是Nacos的核心概念。
-
服务
在服务的基础上,可以配置元数据和服务保护阈值(0~1)等信息,当服务的健康实例数/总比例数是小于保护阈值时,说明当前服务还能提供的机器已经不多了,Nacos会开启服务保护模式,不再主动剔除服务实例。 -
集群
一个服务是由多个服务实例组成的,在每个服务实例启动的时候,可以设置它所属的集群,可以配置元数据,还可以为持久化节点设置健康检查模式。 -
实例
实例对应的就是服务节点,我们可以通过Nacos控制台查看每个实例的IP地址和端口,编辑实例的元数据信息,修改它的上线/下线状态等。
上述都存在元数据,它是包含了服务描述信息和自定义标签的数据集合。
数据模型
- Namespace
命名空间,他是最顶层的数据结构,用来区分开发,预发,生产环境。
默认情况下所有的服务都部署到一个叫做“public”的公共命令空间。 - Group
在命名空间之下有一个分组结构。
默认情况下所有的服务都属于"default_group"这个分组,不同分组间的微服务是相互隔离的。 - Data ID
在分组之下,就是具体的微服务了,比如订单服务,商品服务等。
Nacos的基础架构
Nacos的两大核心:Naming Service和Config Service
一致性
Nacos的一致性保证Nacos集群中各个节点之间的数据一致性。
- 侧重一致性的Raft协议,基于集群中选举出来的Leader节点进行数据写入。
- 临时节点的Distro协议,他是最终一致性的分布式一致性协议。
自动装配原理
在早期SpringCloud版本中,需要在启动类加上@EnableDiscoveryClient注解,才能开启服务治理功能。
在新版本中,我们只需要通过配置项就可以开启。
- NacosDiscoveryAutoConfiguration
服务发现的自动装配器。
主要是:加载Nacos配置项,申明NacosService Discovery类用作服务发现。
源码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled //默认是true,可通过配置项制为false
@ConditionalOnNacosDiscoveryEnable //默认是true,可通过配置项制为false
public class NacosDiscoveryAutoConfiguration {
//@ConfigurationProperties("spring.cloud.nacos.discovery")
//通过上面的注解,读取配置文件中nacos相关的配置项封装进NacosDiscoveryProperties类
@Bean
@ConditionalOnMissingBean
public NacosDiscoveryProperties nacosProperties() {
return new NacosDiscoveryProperties();
}
//如下面代码NacosServiceDiscovery类
@Bean
@ConditionalOnMissingBean
public NacosServiceDiscovery nacosServiceDiscovery(
NacosDiscoveryProperties discoveryProperties,
NacosServiceManager nacosServiceManager) {
return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager);
}
}
//返回值:根据服务名称获取所有已注册的服务,返回所有服务的服务名称,一般是给调用方使用的,
//提供服务发现功能。
public class NacosServiceDiscovery {
// 封装了Nacos配置项的类
private NacosDiscoveryProperties discoveryProperties;
// 另一个自动装配器声明的核心服务治理类
private NacosServiceManager nacosServiceManager;
// 根据服务名称获取所有已注册服务
public List<ServiceInstance> getInstances(String serviceId) throws NacosException {
String group = discoveryProperties.getGroup();
List<Instance> instances = namingService().selectInstances(serviceId, group,
true);
return hostToServiceInstanceList(instances, serviceId);
}
// 返回所有服务的服务名称
public List<String> getServices() throws NacosException {
String group = discoveryProperties.getGroup();
ListView<String> services = namingService().getServicesOfServer(1,
Integer.MAX_VALUE, group);
return services.getData();
}
// 省略部分代码...
}
-
NacosServiceAutoConfiguration
声明核心服务治理类:NacosServiceManager。
主要是通过serviceId,group等一系列获取已注册的服务列表。 -
NacosServiceRegistryAutoConfiguration
Nacos服务注册的自动装配器。
关闭服务,服务下线:主要靠注解@PreDestroy实现
NacosServiceRegistryAutoConfiguration->NacosAutoServiceRegistration->AbstractAutoServiceRegistration.destroy()
Nacos服务发现底层实现
Nacos Client通过一种主动轮训的机制,从nacos server获取服务注册信息,包括地址列表,group分组,cluster名称等一系列数据。
Nacos Client会开启一个本地的定时任务,每间隔一段时间,就尝试从Nacos server查询服务注册表,并将最新的注册信息更新到本地。
负责拉取服务的任务是UpdateTask类,它实现了Runable方法,Nacos以开启线程的方式调用了UpdateTask类中的run方法,触发本地的服务发现查询请求。
首先在 spring.factory 文件中配置了 NacosDiscoveryClientConfiguration 类,用于 springboot 的初始化时的自动装配。在 NacosDiscoveryClientConfiguration 类中会向 spring 容器中添加 NacosWatch 这个 bean。顺着 NacosWatch 的 start 方法一路往下,就能看到:NacosWatch#start ->NacosNamingService#subscribe -> HostReactor#subscribe -> HostReactor#addTask;UpdateTask 就是作为 task 添加到定时执行队列里的。