SpringCloud微服务技术栈.黑马跟学

Posted 心向阳光的天域

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud微服务技术栈.黑马跟学相关的知识,希望对你有一定的参考价值。

SpringCloud微服务技术栈.黑马跟学 九

今日目标

1.分布式事务问题

1.1.本地事务

本地事务,也就是传统的单机事务。在传统数据库事务中,必须要满足四个原则:

1.2.分布式事务

分布式事务,就是指不是在单个服务或单个数据库架构下,产生的事务,例如:

  • 跨数据源的分布式事务
  • 跨服务的分布式事务
  • 综合情况

在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成。例如电商行业中比较常见的下单付款案例,包括下面几个行为:

  • 创建新订单
  • 扣减商品库存
  • 从用户账户余额扣除金额

完成上面的操作需要访问三个不同的微服务和三个不同的数据库。

订单的创建、库存的扣减、账户扣款在每一个服务和数据库内是一个本地事务,可以保证ACID原则。

但是当我们把三件事情看做一个"业务",要满足保证“业务”的原子性,要么所有操作全部成功,要么全部失败,不允许出现部分成功部分失败的现象,这就是分布式系统下的事务了。

此时ACID难以满足,这是分布式事务要解决的问题

1.3.演示分布式事务问题

我们通过一个案例来演示分布式事务的问题:
1)创建数据库,名为seata_demo,然后导入课前资料提供的SQL文件:

2)导入课前资料提供的微服务:

微服务结构如下:

其中:
seata-demo:父工程,负责管理项目依赖

  • account-service:账户服务,负责管理用户的资金账户。提供扣减余额的接口
  • storage-service:库存服务,负责管理商品库存。提供扣减库存的接口
  • order-service:订单服务,负责管理订单。创建订单时,需要调用account-service和storage-service

3)启动nacos、所有微服务
4)测试下单功能,发出Post请求:
先尝试扣减成功的情况
postMan输入

http://localhost:8082/order?userId=user202103032042012&commodityCode=100202003032041&count=2&money=200


点击send后发现返回200

查看数据库,订单表,产生数量是2的订单

库存表库存从10变为了8

个人余额从1000变成了800

再尝试扣减失败的情况
请求如下:

在postman中访问

http://localhost:8082/order?userId=user202103032042012&commodityCode=100202003032041&count=10&money=200

如图:

点击send之后,发现报了500

看下数据库,个人账户从800竟然变成了600,这明显是有问题的

测试发现,当库存不足时,如果余额已经扣减,并不会回滚,出现了分布式事务问题。

分布式服务的事务问题
在分布式系统下,一个业务跨越多个服务或数据源,每个服务都是一个分支事务, 要保证所有分支事务最终状态一致,这样的事务就是分布式事务。

2.理论基础

解决分布式事务问题,需要一些分布式系统的基础知识作为理论指导。

2.1.CAP定理

1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标。

  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance (分区容错性)


它们的第一个字母分别是 C、A、P。

Eric Brewer 说,这三个指标不可能同时做到。这个结论就叫做 CAP 定理。

2.1.1.一致性

Consistency(一致性):用户访问分布式系统中的任意节点,得到的数据必须一致。

比如现在包含两个节点,其中的初始数据是一致的:

当我们修改其中一个节点的数据时,两者的数据产生了差异:

要想保住一致性,就必须实现node01 到 node02的数据 同步:

2.1.2.可用性

Availability (可用性):用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。

如图,有三个节点的集群,访问任何一个都可以及时得到响应:

当有部分节点因为网络故障或其它原因无法访问时,代表节点不可用:

解决方案:在node3节点没恢复网络之前,所有访问node3的请求全都阻塞住,等到网络恢复后,再进行数据同步和请求响应。
问题:在网络恢复前,无法保证可用性,响应都阻塞了。这说明了,想要保证一致性,却牺牲了可用性。

2.1.3.分区容错

Partition(分区):因为网络故障或其它原因导致分布式系统中的部分节点与其它节点失去连接,形成独立分区。

Tolerance(容错):在集群出现分区时,整个系统也要持续对外提供服务

2.1.4.矛盾

在分布式系统中,系统间的网络不能100%保证健康,一定会有故障的时候,而服务有必须对外保证服务。因此Partition Tolerance不可避免。

当节点接收到新的数据变更时,就会出现问题了:

如果此时要保证一致性,就必须等待网络恢复,完成数据同步后,整个集群才对外提供服务,服务处于阻塞状态,不可用。

如果此时要保证可用性,就不能等待网络恢复,那node01、node02与node03之间就会出现数据不一致。

也就是说,在P一定会出现的情况下,A和C之间只能实现一个。

总结:
简述CAP定理内容?
● 分布式系统节点通过网络连接,一定会出现分区问题§
● 当分区出现时,系统的一致性©和可用性(A)就无法同时满足

思考: elasticsearch集群 是CP还是AP?
答:CP
ES集群出现分区时,故障节点会被剔除集群,数据分片会重新分配到其它节点,保证数据一致。因此是低可用性,高一致性,属于CP

2.2.BASE理论

BASE理论是对CAP的一种解决思路,包含三个思想:

  • Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
  • **Soft State(软状态):**在一定时间内,允许出现中间状态,比如临时的不一致状态。
  • Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。

2.3.解决分布式事务的思路

分布式事务最大的问题是各个子事务的一致性问题,因此可以借鉴CAP定理和BASE理论,有两种解决思路:

  • AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致

  • CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。

但不管是哪一种模式,都需要在子系统事务之间互相通讯,协调事务状态,也就是需要一个事务协调者(TC)

这里的子系统事务,称为分支事务;有关联的各个分支事务在一起称为全局事务

总结:
简述BASE理论三个思想:
● 基本可用
● 软状态
● 最终一致
解决分布式事务的思想和模型:

3.初识Seata

Seata是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。

官网地址:http://seata.io/,其中的文档、播客中提供了大量的使用说明、源码分析。

3.1.Seata的架构

Seata事务管理中有三个重要的角色:

  • TC (Transaction Coordinator) - **事务协调者:**维护全局和分支事务的状态,协调全局事务提交或回滚。

  • TM (Transaction Manager) - **事务管理器:**定义全局事务的范围、开始全局事务、提交或回滚全局事务。

  • RM (Resource Manager) - **资源管理器:**管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

整体的架构如图:

Seata基于上述架构提供了四种不同的分布式事务解决方案:

  • XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
  • TCC模式:最终一致的分阶段事务模式,有业务侵入
  • AT模式:最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式
  • SAGA模式:长事务模式,有业务侵入

无论哪种方案,都离不开TC,也就是事务的协调者。

3.2.部署TC服务

参考课前资料提供的文档《 seata的部署和集成.md 》:

seata的部署和集成

一、部署Seata的tc-server

1.下载

首先我们要下载seata-server包,地址在http😕/seata.io/zh-cn/blog/download.html

当然,课前资料也准备好了:

2.解压

在非中文目录解压缩这个zip包,其目录结构如下:

3.修改配置

修改conf目录下的registry.conf文件:

内容如下:

registry 
  # tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
  type = "nacos"

  nacos 
    # seata tc 服务注册到 nacos的服务名称,可以自定义
    application = "seata-tc-server"
    serverAddr = "127.0.0.1:8848"
    group = "DEFAULT_GROUP"
    namespace = ""
    cluster = "SH"
    username = "nacos"
    password = "nacos"
  


config 
  # 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
  type = "nacos"
  # 配置nacos地址等信息
  nacos 
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  

• group = “DEFAULT_GROUP” :与Nacos中分组一致

4.在nacos添加配置

特别注意,为了让tc服务的集群可以共享配置,我们选择了nacos作为统一配置中心。因此服务端配置文件seataServer.properties文件需要在nacos中配好。
格式如下:

DataID: seataServer.properties
Group:DEFAULT_GROUP
配置格式:Properties

配置内容如下:

# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_tc?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000

# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898

其中的数据库地址、用户名、密码都需要修改成你自己的数据库信息。

5.创建数据库表

特别注意:tc服务在管理分布式事务时,需要记录事务相关数据到数据库中,你需要提前创建好这些表。

新建一个名为seata_tc的数据库,运行课前资料提供的sql文件:
这里数据库名称和上面nacos中配置文件的数据库名称一致即可

这些表主要记录全局事务、分支事务、全局锁信息:


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- 分支事务表
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table`  (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` tinyint(4) NULL DEFAULT NULL,
  `client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime(6) NULL DEFAULT NULL,
  `gmt_modified` datetime(6) NULL DEFAULT NULL,
  PRIMARY KEY (`branch_id`) USING BTREE,
  INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- 全局事务表
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table`  (
  `xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `transaction_id` bigint(20) NULL DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `timeout` int(11) NULL DEFAULT NULL,
  `begin_time` bigint(20) NULL DEFAULT NULL,
  `application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT NULL,
  `gmt_modified` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`xid`) USING BTREE,
  INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
  INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;

6.启动TC服务

进入bin目录,运行其中的seata-server.bat即可:

默认端口是8091

启动成功后,seata-server应该已经注册到nacos注册中心了。

打开浏览器,访问nacos地址:http://localhost:8848,然后进入服务列表页面,可以看到seata-tc-server的信息:

点击进去看到详情

二、微服务集成seata

1.引入依赖

首先,我们需要在微服务中引入seata依赖:
参与微服务的所有模块pom.xml都要加上

    <!-- 版本信息 父工程指定即可 -->
    <properties>
        <seata.version>1.4.2</seata.version>
    </properties>
    
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!--版本较低,1.3.0,因此排除-->
        <exclusion>
            <artifactId>seata-spring-boot-starter</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<!--seata starter 采用1.4.2版本-->
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>$seata.version</version>
</dependency>

2.修改配置文件

需要修改application.yml文件,添加一些配置:
把所有服务模块的application.yml文件都修改一下

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    # 参考tc服务自己的registry.conf中的配置
    type: nacos
    nacos: # tc
      server-addr: 127.0.0.1:8848
      namespace: ""
      group: DEFAULT_GROUP
      application: seata-tc-server # tc服务在nacos中的服务名称
      username: nacos
      password: nacos
  tx-service-group: seata-demo # 事务组,根据这个获取tc服务的cluster名称
  service:
    vgroup-mapping: # 事务组与TC服务cluster的映射关系
      seata-demo: SH

配置好之后,重启服务即可,看到seata中显示RM register success即成功了

三、TC服务的高可用和异地容灾

1.模拟异地容灾的TC集群

计划启动两台seata的tc服务节点:

节点名称ip地址端口号集群名称
seata127.0.0.18091SH
seata2127.0.0.18092HZ

之前我们已经启动了一台seata服务,端口是8091,集群名为SH。

现在,将seata目录复制一份,起名为seata2

修改seata2/conf/registry.conf内容如下:

registry 
  # tc服务的注册中心类,这里选择nacos,也可以是eureka、zookeeper等
  type = "nacos"

  nacos 
    # seata tc 服务注册到 nacos的服务名称,可以自定义
    application = "seata-tc-server"
    serverAddr = "127.0.0.1:8848"
    group = "DEFAULT_GROUP"
    namespace = ""
    cluster = "HZ"
    username = "nacos"
    password = "nacos"
  


config 
  # 读取tc服务端的配置文件的方式,这里是从nacos配置中心读取,这样如果tc是集群,可以共享配置
  type = "nacos"
  # 配置nacos地址等信息
  nacos 
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  

进入seata2/bin目录,然后运行命令:

seata-server.bat -p 8092

打开nacos控制台,查看服务列表:

点进详情查看:

2.将事务组映射配置到nacos

接下来,我们需要将tx-service-group与cluster的映射关系都配置到nacos配置中心。

新建一个配置:

配置的内容如下:

# 事务组映射关系
service.vgroupMapping.seata-demo=SH

service.enableDegrade=false
service.disableGlobalTransaction=false
# 与TC服务的通信配置
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableClientBatchSendRequest=false
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
# RM配置
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=false
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
# TM配置
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000

# undo日志配置
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
client.log.exceptionRate=100

3.微服务读取nacos配置

接下来,需要修改每一个微服务的application.yml文件,让微服务读取nacos中的client.properties文件:

seata:
  config:
    type: nacos
    nacos:
      server-addr: 127.0.0.1:8848
      username: nacos
      password: nacos
      group: SEATA_GROUP
      data-id: client.properties

重启微服务,现在微服务到底是连接tc的SH集群,还是tc的HZ集群,都统一由nacos的client.properties来决定了。

3.3.微服务集成Seata

我们以order-service为例来演示。

3.3.1.引入依赖

首先,在order-service中引入依赖:

<!--seata-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <exclusions>
        <!--版本较低,1.3.0,因此排除--> 
        <exclusion>
            <artifactId>seata-spring-boot-starter</artifactId>
            <groupId>io.seata</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <!--seata starter 采用1.4.2版本-->
    <version>$seata.version</version>
</dependency>
3.3.2.配置TC地址

在order-service中的application.yml中,配置TC服务信息,通过注册中心nacos,结合服务名称获取TC地址:

seata:
  registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
      server-addr: 127.0.0.1:8848 # nacos地址
      namespace: "" # namespace,默认为空
      group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
      application: seata-tc-server # seata服务名称
      username: nacos
      password: nacos
  tx-service-group: seata-demo # 事务组名称
  service:
    vgroup-mapping: # 事务组与cluster的映射关系
      seata-demo: SH

微服务如何根据这些配置寻找TC的地址呢?

我们知道注册到Nacos中的微服务,确定一个具体实例需要四个信息:

  • namespace:命名空间
  • group:分组
  • application:服务名
  • cluster:集群名

以上四个信息,在刚才的yaml文件中都能找到:

namespace为空,就是默认的public

结合起来,TC服务的信息就是:public@DEFAULT_GROUP@seata-tc-server@SH,这样就能确定TC服务集群了。然后就可以去Nacos拉取对应的实例信息了。

3.3.3.其它服务

其它两个微服务也都参考order-service的步骤来做,完全一样。

4.动手实践

下面我们就一起学习下Seata中的四种不同的事务模式。

4.1.XA模式

XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。

4.1.1.两阶段提交

XA是规范,目前主流数据库都实现了这种规范,实现的原理都是基于两阶段提交。

正常情况:

异常情况:

一阶段:

  • 事务协调者通知每个事物参与者执行本地事务
  • 本地事务执行完成后报告事务执行状态给事务协调者,此时事务不提交,继续持有数据库锁

二阶段:

  • 事务协调者基于一阶段的报告来判断下一步操作
    • 如果一阶段都成功,则通知所有事务参与者,提交事务
    • 如果一阶段任意一个参与者失败,则通知所有事务参与者回滚事务

4.1.2.Seata的XA模型

Seata对原始的XA模式做了简单的封装和改造,以适应自己的事务模型,基本架构如图:

RM一阶段的工作:

​ ① 注册分支事务到TC

​ ② 执行分支业务sql但不提交

​ ③ 报告执行状态到TC

TC二阶段的工作:

  • TC检测各分支事务执行状态

    a.如果都成功,通知所有RM提交事务

    b.如果有失败,通知所有RM回滚事务

RM二阶段的工作:

  • 接收TC指令,提交或回滚事务

4.1.3.优缺点

XA模式的优点是什么?

  • 事务的强一致性,满足ACID原则。
  • 常用数据库都支持,实现简单,并且没有代码侵入

XA模式的缺点是什么?

  • 因为一阶段需要锁定数据库资源,等待二阶段结束才释放,性能较差
  • 依赖关系型数据库实现事务

4.1.4.实现XA模式

Seata的starter已经完成了XA模式的自动装配,实现非常简单,步骤如下:

1)修改application.yml文件(每个参与事务的微服务),开启XA模式:

seata:
  data-source-proxy-mode: XA

2)给发起全局事务的入口方法添加@GlobalTransactional注解:

本例中是OrderServiceImpl中的create方法.

3)重启所有服务并测试
重启order-service,再次测试,发现无论怎样,三个微服务都能成功回滚。
先看下数据库目前的状态
个人账户余额600

库存数量8

用postman发送订单,成功的

点击send

查看数据库,个人账户余额400

库存数量6

没问题,然后我们尝试一下失败的情况

点击send

查看数据库,个人账户余额不变

库存也不变

事务的一致性得到了保障

4.2.AT模式

AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。

4.2.1.Seata的AT模型

基本流程图:

阶段一RM的工作:

  • 注册分支事务
  • 记录undo-log(数据快照)
  • 执行业务sql并提交
  • 报告事务状态

阶段二提交时RM的工作:

  • 删除undo-log即可

阶段二回滚时RM的工作:

  • 根据undo-log恢复数据到更新前
  • 再删除undo-log

4.2.2.流程梳理

我们用一个真实的业务来梳理下AT模式的原理。

比如,现在又一个数据库表,记录用户余额:

idmoney
1100

其中一个分支业务要执行的SQL为:

update tb_account set money = money - 10 where id = 1

AT模式下,当前分支事务执行流程如下:
一阶段:
1)TM发起并注册全局事务到TC
2)TM调用分支事务
3)分支事务准备执行业务SQL
4)RM拦截业务SQL,根据where条件查询原始数据,形成快照。


    "id": 1, "money": 100

5)RM执行业务SQL,提交本地事务,释放数据库锁。此时 money = 90
6)RM报告本地事务状态给TC

二阶段:

1)TM通知TC事务结束
2)TC检查分支事务状态
​ a)如果都成功,则立即删除快照
​ b)如果有分支事务失败,需要回滚。读取快照数据("id": 1, "money": 100),将快照恢复到数据库。此时数据库再次恢复为100

流程图:

4.2.3.AT与XA的区别

简述AT模式与XA模式最大的区别是什么?

  • XA模式一阶段不提交事务,锁定资源;AT模式一阶段直接提交,不锁定资源。
  • XA模式依赖数据库机制实现回滚;AT模式利用数据快照实现数据回滚。
  • XA模式强一致;AT模式最终一致

4.2.4.脏写问题

在多线程并发访问AT模式的分布式事务时,有可能出现脏写问题,如图:

解决思路就是引入了全局锁的概念。在释放DB锁之前,先拿到全局锁。避免同一时刻有另外一个事务来操作当前数据。

4.2.5.优缺点

AT模式的优点:

  • 一阶段完成直接提交事务,释放数据库资源,性能比较好
  • 利用全局锁实现读写隔离
  • 没有代码侵入,框架自动完成回滚和提交

AT模式的缺点:

  • 两阶段之间属于软状态,属于最终一致
  • 框架的快照功能会影响性能,但比XA模式要好很多

4.2.6.实现AT模式

AT模式中的快照生成、回滚等动作都是由框架自动完成,没有任何代码侵入,因此实现非常简单。
只不过,AT模式需要一个表来记录全局锁、另一张表来记录数据快照undo_log。

1)导入数据库表,记录全局锁

导入课前资料提供的Sql文件:seata-at.sql,其中lock_table导入到TC服务关联的数据库,undo_log表导入到微服务关联的数据库:
lock_table表:放入seata_tc数据库
undo_log表:放入seata_demo数据库

添加后如下:

2)修改application.yml文件,将事务模式修改为AT模式即可:

seata:
  data-source-proxy-mode: AT # 默认就是AT

3)重启所有微服务并测试
我们用postman直接测试失败的情况,初始情况余额是400,库存是6


点击send,发现报错500

查看余额,还是400没问题


查看库存还是6没问题

4.3.TCC模式

TCC模式与AT模式非常相似,每阶段都是独立事务,不同的是TCC通过人工编码来实现数据恢复。需要实现三个方法:

  • Try:资源的检测和预留;
  • Confirm:完成资源操作业务;要求 Try 成功 Confirm 一定要能成功。
  • Cancel:预留资源释放,可以理解为try的反向操作。

4.3.1.流程分析

举例,一个扣减用户余额的业务。假设账户A原来余额是100,需要余额扣减30元。

  • 阶段一( Try ):检查余额是否充足,如果充足则冻结金额增加30元,可用余额扣除30

初识余额:

余额充足,可以冻结:

此时,总金额 = 冻结金额 + 可用金额,数量依然是100不变。事务直接提交无需等待其它事务。

  • 阶段二(Confirm):假如要提交(Confirm),则冻结金额扣减30

确认可以提交,不过之前可用金额已经扣减过了,这里只要清除冻结金额就好了:

此时,总金额 = 冻结金额 + 可用金额 = 0 + 70 = 70元

  • 阶段二(Canncel):如果要回滚(Cancel),则冻结金额扣减30,可用余额增加30

需要回滚,那么就要释放冻结金额,恢复可用金额:

4.3.2.Seata的TCC模型

Seata中的TCC模型依然延续之前的事务架构,如图:

4.3.3.优缺点

TCC模式的每个阶段是做什么的?

  • Try:资源检查和预留
  • Confirm:业务执行和提交
  • Cancel:预留资源的释放

TCC的优点是什么?

  • 一阶段完成直接提交事务,释放数据库资源,性能好
  • 相比AT模型,无需生成快照,无需使用全局锁,性能最强
  • 不依赖数据库事务,而是依赖补偿操作,可以用于非事务型数据库

TCC的缺点是什么?

  • 有代码侵入,需要人为编写try、Confirm和Cancel接口,太麻烦
  • 软状态,事务是最终一致
  • 需要考虑Confirm和Cancel的失败情况,做好幂等处理

4.3.4.事务悬挂和空回滚

1)空回滚

当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚

如图:

执行cancel操作时,应当判断try是否已经执行,如果尚未执行,则应该空回滚。

2)业务悬挂

对于已经空回滚的业务,之前被阻塞的try操作恢复,继续执行try,就永远不可能confirm或cancel ,事务一直处于中间状态,这就是业务悬挂

执行try操作时,应当判断cancel是否已经执行过了,如果已经执行,应当阻止空回滚后的try操作,避免悬挂

4.3.5.实现TCC模式

解决空回滚和业务悬挂问题,必须要记录当前事务状态,是在try、还是cancel?

1)思路分析

这里我们定义一张分支事务表:
在seata-demo中执行

CREATE TABLE `account_freeze_tbl` (
  `xid` varchar(128) NOT NULL,
  `user_id
        
                

SpringCloud概述及微服务技术栈的使用

1、SpringCloud的简介

SpringCloud是一系列框架的有序集合。它利用SpringBoot的开发便利性巧妙地简化了分布式系统基础设置的开发,如服务发现与注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署。SpringCloud并没有重复制造轮子,它只是将目前各家公司开发比较成熟、经得起考研的服务框架组合起来,通过SpringBoot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者流下了一套简单易懂、易部署和易维护的分布式系统开发工具。

1.1、SpringCloud中的五大核心组件

Spring Cloud的本质是在SpringBoot的基础上,增加了一堆微服务相关的规范,并对应用上下文(ApplicationContext)进行功能增强,既然SpringCloud是规范,那么就需要去实现,目前Spring Cloud规范已有Spring官方,Spring Cloud Netflix,Spring Cloud Alibaba等是实现。 通过组件化的方式,Spring Cloud将这些实现整合到一起构成全家桶式的微服务技术栈。

SpringCloud Netflix组件

组件名称作用
Eureka服务注册中心
Ribbon客户端负载均衡
Feign声明式服务端调用(基于Ribbon,将调用方式RestTemplate,改为service接口调用)
Hystrix客户端容错报保护(熔断降级服务)
ZuulAPI服务网关

Spring Cloud Alibaba组件

组件名称作用
Nacos服务注册中心
Sentinel客户端容错保护

Spring Cloud原生及其他组件

组件作用
Consul(Eureka替代者)服务注册中心
Config分布式配置中心
Gateway(Zuul替代者)API服务网关
Sleuth分布式链路追踪

1.2、SpringCloud的架构


从上图可以看出SpringCloud各个组件的相互配合,合作支持了一套完整的微服务架构。

  • 注册中心: 负责服务的注册与发现,很好的将个服务连接起来
  • 断路器: 负责监控服务之间的调用情况,连续多次的失败,将进行熔断降级保护
  • API网关: 负责转发所有对外的请求和服务
  • 配置中心: 提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
  • 链路追踪技术: 可以将所有的数据记录下来,方便我们进行后续分析
  • 各个组件又提供了功能完善的dashboard监控平台,可以当便的监控各组件的运行状况

1.3、微服务与微服务架构

微服务:

强调的是服务的大小,它关注的是某个一个点,是具体解决某一个问题/提供落地式对应服务的一个服务应用,狭义的看,可以看做是IDEA中的一个个微服务工程或者Moudle模块。IDEA工具里面使用Maven开发的一个个独立的Moudel,它具体是使用SpringBoot开发的一个小模块,专业的事情交给专业的模块来做,一个模块就做一件事情,强调的是一个个个体,每个个体完成一个具体的任务或者功能。

微服务架构:

一种新的架构形式,Martin Fowler于2014年提出。
微服务架构是一种架构模式,它提倡将单一应用程序划分成一组小的服务,服务之间相互协调互相配合,为用户提供最终价值,每个服务运行在其独立的进程中,服务与服务之间采用轻量级的通信机制(如HTTP协议)互相协作,每个服务都围绕着具体的业务进行构建,并且能够被独立的部署到生产环境中,另外,应尽量避免统一的,集中式的服务管理机制,对具体的一个服务而言,应根据业务上下文,选择合适的语言、工具(如maven)对其进行统一构建。

1.4、微服务技术栈概括

微服务技术条目技术支持
服务开发Spring、SpringBoot、SpringMVC
服务配置与管理Netflix公司的Archaius、阿里的Diamond等
服务注册与发现Eureka、Consul、Zookeeper
服务调用Rset、RPC、gRPC
服务熔断器Hystrix、Envoy等
负载均衡Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具)Feign等
消息队列RabbitMQ、ActiveMQ、Kafka等
服务配置中心管理SpringCLoudConfig、Chef等
服务路由(API网关)Zuul等
服务监控Zabbix、Nagios、Metrics、Specatator等
全链路追踪Zipkin、Brave、Dapper等
数据流操作开发包SpringCloud Stream(封装与Redis、Rabbit、Kafka等发送接收消息)
时间消息总栈SpringCloud Bus
服务部署Docker、OpenStack、Kuberneters等

1.5、为什么选择SpringCloud作为微服务架构?

选型一依据

  • 整体解决方案和框架成熟度
  • 社区热度
  • 可维护性
  • 学习曲线

当前各大IT公司用的微服务架构有哪些?

  • 阿里:dubbo+HFS
  • 京东:JFS
  • 新浪:Motan
  • 当当网:DubboX

1.6、SpringCloud与SpringBoot的关系

  • SpringBoot专注于快速方便的开发出当个个体微服务
  • SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务,整合并管理起来,为各个微服务之间提供:配置管理、服务发现、断路器、路由、代理、事件总栈、决策竞选、分布式会话等等集成服务
  • SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系

1.7、Dubbo和SpringCloud技术选型

1、分布式+服务治理Dubbo

  • 目前成熟的互联网架构,应用服务化拆分+消息中间件

2、Dubbo与SpringCloud对比
可以看一下社区活跃度:DubboSpringCloud

DubboSpringCloud
服务注册中心ZookeeperSpring Cloud Netflix Eureka
服务调用方式RPCREST API
服务监控Dubbo-monitorSpringBoot Admin
断路器不完善SpringCloud Netflix Hystrix
服务网关Spring Cloud Netflix Zuul
分布式配置Spring Cloud Config
服务追踪Spring Cloud Sleuth
消息总栈Spring Cloud Bus
数据流Spring Cloud Stream
批量任务Spring Cloud Task

两者最大的区别在于通信方式:
SpringCloud抛弃了Dubbo的RPC通信,采用的是基于轻量级HTTP协议的REST API方式。
严格来说,这两种方式各有优劣,在性能上RPC要优于REST,但是在灵活度上REST相比RPC是更灵活,服务提供方和调用方只需要一致契约,不存在代码级别的强依赖,这个优点在当下强调快速演化的微服务环境下,显得更加合适。

二者解决的问题域不同:Dubbo的定位是一款RPC框架,而SpringCloud的目标是微服务架构下的一站式解决方案。

1.8、SpringCloud可以做什么?

  • Distributed/Versioned configuration 分布式/版本控制系统
  • Service registration and discovery 服务注册与发现
  • Routing 路由
  • Service-to-service calls 服务到服务之间的调用
  • Load balancing 负载均衡策略
  • Circuit Breakers 断路器
  • Distributed messaging 分布式消息管理

1.9、SpringCloud官网下载

SpringCloud官网

SpringCloud没有采用数字编号的方式命名版本号,而是采用了伦敦地铁站的名称,同时根据字母表的顺序来对应版本时间顺序:

  • 最早的Realse版本:Angel,
  • 第二个Realse版本:Brixton,
  • 然后依次是Camden、Dalston、Edgware,
  • 目前最新的是Hoxton SR4 CURRENT GA通用稳定版。

1.10、SpringCloud版本选择

大版本说明

SpringBootSpringCloud关系
1.2XAngel版本兼容SpringBoot1.2X
1.3XBrixton版本(布里克斯顿)兼容SpringBoot1.3X,也兼容SpringBoot1.4X
1.4XCamden版本(卡姆登)兼容SpringBoot1.4X,也兼容SpringBoot1.5X
1.5XDalston版本(多尔斯顿)兼容SpringBoot1.5X,不兼容SpringBoot2.0X
1.5XEdgware版本(埃奇韦尔)兼容SpringBoot1.5X,不兼容SpringBoot2.0X
2.0XFinchley版本(芬奇利)不兼容SpringBoot1.5X ,兼容SpringBoot2.0X
2.1XGreenwich版本(格林威治)

实际开发版本关系

spring-boot-starter-parentspring-cloud-dependencies
版本号发布日期版本号发布日期
1.5.2.RELEASE2017-03Dalston.RC12017-x
1.5.9.RELEASE2017-11Edgware.RELEASE2017-11
1.5.16.RELEASE2018-04Edgware.SR52018-10
1.5.20.RELEASE2018-09Edgware.SR52018-10
2.0.2.RELEASE2018-05Fomchiey.BULD-SNAPSHOT2018-x
2.0.6.RELEASE2018-10Fomchiey-SR22018-10
2.1.4.RELEASE2019-04Greenwich.SR12019-03

2、基于Eureka注册中心的案例搭建与分析

SpringCloud系列(一)、服务注册中心Eureka基础【详细教程】

3、Eureka的替换方案Consul

Eureka的闭源影响

在Euraka的GitHub上,宣布Eureka 2.x闭源。近这意味着如果开发者继续使用作为 2.x 分支上现有工作repo 一部分发布的代码库和工件,风险则将自负。

Eureka的替换方案如下:

  • ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等
  • Consul是近几年比较流行的服务发现工具,工作中用到,简单了解一下。consul的三个主要应用场景:服务发现、服务隔离、服务配置
  • Nacos是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

3.1、Consul概述


Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,

  • 内置了服务注册与发现框 架
  • 分布一致性协议实现
  • 健康检查
  • Key/Value 存储
  • 多数据中心方案

不再需要依赖其它工具(比如 ZooKeeper 等),使用起来也较 为简单。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。

3.2、Consul的优势

  • 使用Raft算法来保证一致性,比复杂的Paxoa算法更直接,相比较而言,Zookeeper采用的是Paxos,而etcd使用的则是Raft
  • 支持多数据中心,内外网的服务采用不同的端口监听。多数据中心集群可以避免单数据中心的单点故障,而其部署则需要考虑网络延迟,分片等情况,zookeeper和etcd均不提供多数据中心功能的支持
  • 支持健康检测,etcd不提供此功能
  • 支持HTTP和DNS协议接口。 zookeeper的继承较为复杂,etcd只支持http协议
  • 官方提供web管理界面,etcd无此功能
  • 综合比较,Consul作为服务注册和配置管理,比较值得关注和研究

Consul的特征:

  • 服务发现
  • 多数据中心
  • key/value存储
  • 健康检测

3.3、Consul与Eureka的区别

Consul具有强一致性(CP):

  • 服务注册相比Eureka会稍慢一些,因为Consul的Raft协议要求必须过半数的节点都写入成功才认为注册成功
  • Leader挂点后,重新选举期间整个Consul不可用,保证了强一致性,但牺牲了可用性

Eureka保证高可用性和最终一致性(AP):

  • 服务注册相对要快,因为不需要等注册信息replicate到其他节点上,也不保证注册信息是否replicate成功
  • 当数据出现不一致时,虽然A、B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时,如果请求A查不到,但请求B可以查到(但内容不一定一致),如此保证了高可用性,但是牺牲了一致性

开发语言和使用:

  • Eureka就是个servlet程序,跑在servlet容器中
  • Consul则是go编写而成,安装启动即可

3.4、Consul的下载与安装

访问 Consul 官网下载 Consul 的最新版本,Consul 需要单独安装,我这里是consul1.5x。根据不同的系统类型选择不同的安装包,从下图也可以看出 Consul 支持所有主流系统。

在Linux虚拟机在中安装Consul服务:

## 从官网下载最新版本的Consul服务
wget https://releases.hashicorp.com/consul/1.5.3/consul_1.5.3_linux_amd64.zip
##使用unzip命令解压
unzip consul_1.5.3_linux_amd64.zip
##将解压好的consul可执行命令拷贝到/usr/local/bin目录下
cp consul /usr/local/bin
##测试一下
consul

启动Consul服务:

##已开发者模式快速启动,-client指定客户端可以访问的ip地址
[root@node01 ~]# consul agent -dev -client=0.0.0.0
==> Starting Consul agent...
     Version: 'v1.5.3'
     Node ID: '49ed9aa0-380b-3772-a0b6-b0c6ad561dc5'
    Node name: 'node01'
   Datacenter: 'dc1' (Segment: '<all>')
     Server: true (Bootstrap: false)
   Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
  Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
     Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false,
Auto-Encrypt-TLS: false

启动成功之后访问: http://IP地址:8500 ,可以看到 Consul 的管理界面:

我们此处暂时先使用windows下的版本启动Consul服务:从官网下载windows版本的zip,解压后免安装:

输入启动Consu命令:

#-client=0.0.0.0 是为了开放所有ip访问
consul agent -dev -client=0.0.0.0

启动之后,访问地址栏:localhost:8500

3.5、Consul的K/V存储

可以参照Consul提供的KV存储的 API完成基于Consul的数据存储

含义请求路径请求方式
查看keyv1/kv/:keyGET
保存或更新v1/kv/:keyPUT
删除v1/kv/:keyDELETE
  • key值中可以带/, 可以看做是不同的目录结构。
  • value的值经过了base64_encode加密,获取到数据后base64_decode解密才能获取到原始值。数据不能大于512Kb
  • 不同数据中心的kv存储系统是独立的,使用dc=?参数指定。

3.6、基于Consul的服务注册案例

工程配置仍然和Eureka保持一致(可做参考):
SpringCloud系列(一)、服务注册中心Eureka基础【详细教程】

ebuy-consul-parent(父模块)
---ebuy-consul-product(商品微服务)
---ebuy-consul-order(订单微服务)

修改商品和订单微服务模块的pom文件:

	<!--SpringCloud提供的基于Consul的服务发现-->
	<dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
	<!--actuator用于心跳检查-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

配置服务注册
ebuy-consul-product的application.yml

server:
  port: 9011 #端口号

spring:
  application:
    name: ebuy-product #商品模块服务名称
  datasource:
    username: root #数据库用户名
    password: root #数据库密码
    driver-class-name: com.mysql.jdbc.Driver #mysql加载驱动
    url: jdbc:mysql://localhost:3306/ebuy?useUnicode=true&characterEncoding=utf8
  cloud:
    consul:
      host: 127.0.0.1 #指定consul服务地址
      port: 8500 #指定consul服务端口号
      discovery:
        register: true #是否注册
        instance-id: ${spring.application.name}-1 #指定实例id名
        server-name: ${spring.application.name} #服务实例名称
        port: ${server.port} #服务实例端口号
        health-check-path: /actuator/health  #健康检测路径
        health-check-interval: 15s #指定健康检测时间间隔
        prefer-ip-address: true #开启ip地址注册
        ip-address: ${spring.cloud.client.ip-address} #实例请求ip

#mybatis相关配置
mybatis:
  type-aliases-package: com.ebuy.product.pojo  #mybatis简化pojo实体类别名
  mapper-locations: com/ebuy/product/mapper/*.xml #mapper映射文件路径

#打印日志
logging:
  level:
    com.ebuy: DEBUG #日志级别

ebuy-consul-order的application.yml

server:
  port: 9013 #端口号
  address: 127.0.0.1
  tomcat:
    max-threads: 10 #最大线程数(默认为200台)

spring:
  application:
    name: ebuy-order #服务名
  cloud:
    consul:
      host: 127.0.0.1 #指定consul服务地址
      port: 8500 #指定consul服务端口号
      discovery:
        register: true #是否注册
        instance-id: ${spring.application.name}-1 #指定实例id名
        server-name: ${spring.application.name} #服务实例名称
        port: ${server.port} #服务实例端口号
        health-check-path: /actuator/health  #健康检测路径
        health-check-interval: 15s #指定健康检测时间间隔
        prefer-ip-address: true #开启ip地址注册
        ip-address: ${spring.cloud.client.ip-address} #实例请求ip
        health-check-url: http://${server.address}:${server.port}/**/health
        health-check-critical-timeout: 30s #check失败后,多少秒剔除该服务

#打印日志
logging:
  level:
    com.ebuy: DEBUG

其中 spring.cloud.consul 中添加consul的相关配置:

  • host:表示Consul的Server的请求地址
  • port:表示Consul的Server的端口
  • discovery:服务注册与发现的相关配置
    • instance-id : 实例的唯一id(推荐必填),spring cloud官网文档的推荐,为了保证生成一个唯一的id ,也可以换成${spring.application.name}:${spring.cloud.client.ipAddress}
    • prefer-ip-address:开启ip地址注册
    • ip-address:当前微服务的请求ip

启动两个微服务:查看Consul监控中心

基于微服务的发现:
由于SpringCloud对Consul进行了封装。对于在消费者端获取服务提供者信息和Eureka是一致的。同样使用 DiscoveryClient完成调用获取微服务实例信息,其余用法基本都和Eureka保持一致。

4、Ribbon:基于客户端服务调用(负载均衡)

经过以上的学习,已经实现了服务的注册和服务发现。当启动某个服务的时候,可以通过HTTP的形式将信息注册到注册中心,并且可以通过SpringCloud提供的工具获取注册中心的服务列表。但是服务之间的调用还存在很多的问题,如何更加方便的调用微服务,多个微服务的提供者如何选择,如何负载均衡等。

4.1、什么是Ribbon?

Ribbon是Netflix发布的一个负载均衡器,有助于控制HTTP和TCP客户端行为,在SpringCloud中,Eureka一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从Eureka或者Consul中读取到的服务信息,在调用服务节点提供的服务时,会合理的进行负载,默认为轮询策略。

在SpringCloud中可以将注册信息和Ribbon配合使用,Ribbon自动的从注册中心获取服务提供者的列表信息,并基于内置的负载均衡算法,请求服务。

4.2、Ribbon的主要作用

客户端服务调用:

  • 基于Ribbon实现服务调用,是通过拉取到的所有服务列表组成(服务名:请求路径)的一种映射关系,借助于RestTemplate最终实现调用。

负载均衡:

  • 当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址。

4.3、Ribbon的关键组件

  • ServerList:可以响应客户端的特定服务的服务器列表。
  • ServerListFilter:可以动态获得的具有所需特征的候选服务器列表的过滤器。
  • ServerListUpdater:用于执行动态服务器列表更新。
  • Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
  • Ping:客户端用于快速检查服务器当时是否处于活动状态。
  • LoadBalancer:负载均衡器,负责负载均衡调度的管理。

4.4、工程改造

上述讲解了Consul替代Eureka,此处我们暂时先将注册中心改为Eureka注册,并配置两台注册中心集群:
application.yml(将8000注册到9000)互相注册,application.yml(将9000注册到8000)即可

server:
  port: 8000 #端口号

spring:
  application:
    name: eureka-server #eurekaServer服务名

eureka:
  #instance:
    #hostname: 127.0.0.1 #服务器ip地址
  client:
    register-with-eureka: true #是否将自己注册到注册中心
    #fetch-registry: false #是否从注册中心获取服务列表
    serviceUrl: #配置暴露给Eureka Client的请求地址
      defaultZone: http://127.0.0.1:9000/eureka/
  server:
    enable-self-preservation: false #关闭自我保护机制(一旦发现有网络不稳定的服务,直接剔除)
    eviction-interval-timer-in-ms: 4000 #剔除时间间隔,单位:毫秒
    #wait-time-in-ms-when-sync-empty: 5

服务提供者和消费者:修改application.yml文件中注册中心配置:

server:
  port: 90XX #端口号
  address: 127.0.0.1
  tomcat:
    max-threads: 10 #最大线程数(默认为200台)

spring:
  application:
    name: ebuy-XXXX #服务名
#  cloud:
#    consul:
#      host: 127.0.0.1 #指定consul服务地址
#      port: 8500 #指定consul服务端口号
#      discovery:
#        register: true #是否注册
#        instance-id: ${spring.application.name}-1 #指定实例id名
#        server-name: ${spring.application.name} #服务实例名称
#        port: ${server.port} #服务实例端口号
#        health-check-path: /actuator/health  #健康检测路径
#        health-check-interval: 15s #指定健康检测时间间隔
#        prefer-ip-address: true #开启ip地址注册
#        ip-address: ${spring.cloud.client.ip-address} #实例请求ip
#        health-check-url: http://${server.address}:${server.port}/**/health
#        health-check-critical-timeout: 30s #check失败后,多少秒剔除该服务

#使用eureka注册中心
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8000/eureka/,http://127.0.0.1:9000/eureka/
  instance:
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
    prefer-ip-address: true   #使用ip地址注册(在注册中心显示名字以ip地址显示)
    lease-expiration-duration-in-seconds: 10 #eureka client发送心跳给eureka server服务端后,续约到期时间(默认为90秒)
    lease-renewal-interval-in-seconds: 5 #发送心跳续约时间间隔

#打印日志
logging:
  level:
    com.ebuy: DEBUG

4.5、服务调用Ribbon高级,什么是负载均衡?

在搭建网站时,如果节点的web服务性能和可靠性都无法达到要求,或者是在使用外网服务时,经常担心被人攻击,一不小心就会有打开外网端口的情况,通常这个时候加入负载均衡就能有效的解决服务访问问题。

负载均衡是一种基础的网络服务,其原理是通过运行在前面的负载均衡服务,按照指定的负载均衡算法,将流量分配到后端服务集群上,从而为系统提供并行扩展的能力。

负载均衡的应用场景包括流量包、转发规则以及后端服务,由于服务有内外网个例,健康检查等功能,能够有效提高系统的安全性和可靠性。

4.6、客户端负载均衡和服务端负载均衡

客户端负载均衡:

  • 客户端从注册中心会获取到一个服务提供者的服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡,即在客户端就进行负载均衡算法分配。

服务端负载均衡:

  • 先发送请求到负载均衡服务器或软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行服务在均衡算法分配

4.7、基于Ribbon实现负载均衡

首先要搭载多态服务器,上述已经搭建好Eureka注册中心的集群,然后再搭建两台ebuy-product和一台ebuy-order即可,如下:

1、服务提供者ebuy-product

服务提供者:修改ebuy-product模块下的ProductController#findById()方法:

@RestController
@RequestMapping("/product")
public class ProductController {

	/**
	 * 回去客户端ip地址
	 */
	@Value("${spring.cloud.client.ip-address}")
	private String ip;

	/**
	 * 获取客户端的端口号
	 */
	@Value("${server.port}")
	private String port;

	@Autowired
	private EasybuyProductService productService;

	@RequestMapping(value = "/{id}",method = RequestMethod.GET)
	public EasybuyProduct findById(@PathVariable Long id) {
		EasybuyProduct product = productService.selectByPrimaryKey(id);
		product.setEpDescription("调用ebuy-product服务,ip:"+ip+",服务提供者端口:"+port);
 		return product;
	}
}

ebuy-product服务提供者启动两台:9011,9012
ebuy-order服务消费者启动一台:9013

2、服务消费者ebuy-order

然后在ebuy-order的EbuyOrderApplication启动类处,创建RestTemplate方法,并添加@LoadBalanced注解实现与Ribbon搭配的负载均衡:

@SpringBootApplication
@EnableEurekaClient  //开启Eureka客户端服务注册
@EnableDiscoveryClient  //开启服务发现
public class EbuyOrderApplication {

    /**
     * @Bean 配置RestTemplate交给spring管理
     * @LoadBalanced 实现负载均衡(Ribbon原理)
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

    public static void main(String[] args) {
        SpringApplication.run(EbuyOrderApplication.class, args);
    }
}

在ebuy-order服务模块的OrderController下添加下单方法:

	@Autowired
    RestTemplate restTemplate;

    
    @RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
    public EasybuyProduct findById(@PathVariable Long id) {
        EasybuyProduct easybuyProduct=new EasybuyProduct();
        //easybuyProduct=restTemplate.getForObject("http://127.0.0.1:9011/product/"+id,EasybuyProduct.class);<

以上是关于SpringCloud微服务技术栈.黑马跟学的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud微服务技术栈.黑马跟学

从零开始学微服务07.微服务常用的开发框架

从零开始学微服务07.微服务常用的开发框架

学微服务之前,技术栈需要先分类

学微服务之前,技术栈需要先分类

从零开始学微服务06.微服务架构的建设思路

(c)2006-2024 SYSTEM All Rights Reserved IT常识