Author:Eric
Version:9.0.0
文章目录
一、引言
在分布式环境下,传统的一些技术会失败,比如传统的synchronized或者lock锁,以及创建数据库的事务,无法保证ACID,还有定时任务也可能会出现重复执行的问题。
二、分布式锁介绍
由于传统的锁是基于Tomcat服务器内部的,搭建了集群之后,导致锁失效,使用分布式锁来处理。
分布式锁介绍 |
---|
![]() |
三、分布式锁解决方案【重点
】
3.1 搭建环境
创建SpringBoot
编写抢购的业务
@RestController
public class SecondKillController {
//1. 准备商品的库存
public static Map<String,Integer> itemStock = new HashMap<>();
//2. 准备商品的订单
public static Map<String,Integer> itemOrder = new HashMap<>();
static{
itemStock.put("牙刷",10000);
itemOrder.put("牙刷",0);
}
@GetMapping("/kill")
public String kill(String item) throws InterruptedException {
//1. 减库存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品库存数不足!!!";
}
Thread.sleep(100);
itemStock.put(item,stock - 1);
//2. 创建订单
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item) + 1);
//3. 返回信息
return "抢购成功!!!" + item + ": 剩余库存数为: " + itemStock.get(item) + ",订单数为: " + itemOrder.get(item);
}
}
网页访问地址:http:localhost:8080/kill?item=牙刷
下载ab压力测试
ab -n 请求数 -c 并发数 访问的路径
测试,通过手动快速刷新或ab压力器,库存数和订单数不一致
测试 |
---|
![]() |
3.2 Zookeeper实现分布式锁原理
Zookeeper实现分布式锁原理 |
---|
![]() |
3.3 Zookeeper实现分布式互斥锁
导入依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.1</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
编写配置类
@Configuration
public class ZkConfig {
@Bean
public CuratorFramework cf(){
RetryPolicy retryPolicy = new ExponentialBackoffRetry(3000,2);
CuratorFramework curatorFramework = CuratorFrameworkFactory.builder()
.connectString("192.168.199.109:2181,192.168.199.109:2182,192.168.199.109:2183")
.retryPolicy(retryPolicy)
.build();
curatorFramework.start();
return curatorFramework;
}
}
在业务代码中添加分布式锁
//InterProcessMutex基于Zookeeper实现了分布式互斥锁
InterProcessMutex lock = new InterProcessMutex(cf,"/lock");
//...加锁
lock.acquire(); //不限时等待,
lock.acquire(1,TimeUnit.SECONDS); //限时等待,指定排队多久就放弃获取锁资源
//----------------业务逻辑代码------------------------
//1. 减库存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品库存数不足!!!";
}
Thread.sleep(100);
itemStock.put(item,stock - 1);
//2. 创建订单
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item) + 1);
//------------------------------
// 释放锁
lock.release();
3.4 Redis实现分布式锁原理
Redis实现分布式锁原理 |
---|
![]() |
3.5 Redis实现分布式锁
导入依赖,添加配置文件
# redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
# 配置文件
spring:
redis:
host: 192.168.199.109
port: 6379
编写工具类
@Component
public class RedisLockUtil {
@Autowired
private StringRedisTemplate redisTemplate;
public boolean lock(String key,String value,int second){
return redisTemplate.opsForValue().setIfAbsent(key,value,second, TimeUnit.SECONDS);
}
public void unlock(String key){
redisTemplate.delete(key);
}
}
修改业务逻辑代码
@GetMapping("/redis/kill")
public String redisKill(String item) throws Exception {
//...加锁
if(lock.lock(item,System.currentTimeMillis() + "",1)){
//---------------- 业务代码。。。------------
//1. 减库存
Integer stock = itemStock.get(item);
if(stock <= 0){
return "商品库存数不足!!!";
}
Thread.sleep(100);
itemStock.put(item,stock - 1);
//2. 创建订单
Thread.sleep(100);
itemOrder.put(item,itemOrder.get(item) + 1);
//---------------------------------
// 释放锁
lock.unlock(item);
}
}
测试
四、分布式任务介绍
分布式任务介绍 |
---|
![]() |
五、分布式任务解决方案【重点
】
5.1 Elastic-Job介绍
官网:http://elasticjob.io/index_zh.html
http://shardingsphere.apache.org/elasticjob/index_zh.html
由当当网基于Quartz + Zookeeper的二次开放产品
基于Zookeeper分布式锁,保证只有一个服务去执行定时任务。
基于Zookeeper实现了注册中心,自动帮助我们去调度指定的服务执行定时任务。
基于Zookeeper实现了注册中心,基于心跳的方式,自动去检测服务的健康情况。
5.2 Elastic-Job实现分布式任务
创建SpringBoot工程
导入依赖
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>2.1.5</version>
</dependency>
分布式调度的注册中心,用zookeeper实现,需要配置Zookeeper信息
// 注册中心
@Bean
public CoordinatorRegistryCenter center(){
CoordinatorRegistryCenter regCenter = new ZookeeperRegistryCenter(
new ZookeeperConfiguration("192.168.199.109:2181,192.168.199.109:2182,192.168.199.109:2183", "elastic-job-demo"));
regCenter.init();
return regCenter;
}
创建指定的定时任务
@Component
public class MyElasticJob implements SimpleJob {
@Override
public void execute(ShardingContext context) {
switch (context.getShardingItem()) {
case 0:
System.out.println("执行0任务!!");
break;
case 1:
System.out.println("执行1任务!!");
break;
case 2:
System.out.println("执行2任务!!");
break;
// case n: ...
}
}
}
配置执行的周期,并且开始调度任务
javaSimpleJob表示作业的名称,0/5 * * * *设置作业调度的间隔,3表示作业的分片。shardingItemParameters(“0=A,1=B,2=C”)表示设置分片的序列号和个性化参数。
// 执行任务调度信息
@Bean
public SpringJobScheduler scheduler(MyElasticJob job,CoordinatorRegistryCenter center){
// 定义作业(任务)核心配置
JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.
newBuilder("demoSimpleJob", "0/10 * * * * ?", 3)//每隔10秒执行一次,cron表达式,3个分片
.shardingItemParameters("0=A,1=B,2=C").build();
// 定义SIMPLE类型配置,简单任务
SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig, MyElasticJob.class.getCanonicalName());//执行任务类的名称
// 定义Lite作业根配置
LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build();
// 定义SpringJobScheduler
SpringJobScheduler scheduler = new SpringJobScheduler(job,center,simpleJobRootConfig);
scheduler.init();
return scheduler;
}
同一个项目多个端口配置
测试
六、分布式事务介绍
6.1分布式事务介绍
分布式事务介绍 |
---|
![]() |
6.2 Base理论
CAP理论,C:一致性,A:可用性,P:分区容错性。分布式环境下,三者取其二。
Eureka:AP,保证了可用性,舍弃了一致性。
Zookeeper:CP,每一个节点必须能够找到Master才能对外提供服务,舍弃了可用性。
Base理论,BA:基本可用,S:中间状态,E:最终一致性。
基于CAP理论演化而来的,是对CAP定理中一致性和可用性的一个权衡结果。
核心思想:我们无法做到强一致性,但是每一个应用都可以根据自身的业务特点,采用一些适当的方式来权衡,最终达到一致性。
BA:分布式系统中因为一个原因,导致出现了一些问题,允许损失掉部分服务的可用性,保证我核心功能的高可用。
S:允许系统之间存在一个中间状态,并不会影响正常的去使用整个系统,允许数据的同步存在延迟。
E:系统中所有的数据副本经过一定时间后,最终能够达到一致的状态,不需要保证系统数据强一致性。
七、分布式事务解决方案【重点
】
7.1 2PC两段提交
两段提交分为两个阶段:
第一个阶段是准备阶段,参与者(服务1,服务2…)需要开启事务,执行SQL,保证数据库中已经存在相应的数据。参与者会向TransactionManager准备OK。
第二个阶段当TransactionManager收到了所有的参与者的通知之后,向所有的参与者发送Commit请求。
问题1:执行的性能是很低的。一般是传统事务的10倍以上。
问题2:TransactionManager是没有超时时间的。
问题3:TransactionManager存在单点故障的问题
2PC两段提交 |
---|
![]() |
7.2 3PC三段提交
三段提交在二段提交的基础上,引入了超时时间机制,并且在二段提交的基础上,又多了一个步骤,在提交事务之前,再询问一下,数据库的日志信息,是否已经完善。
Undo日志记录某数据被修改前的值,可以用来在事务失败时进行rollback;Redo日志记录某数据块被修改后的值,可以用来恢复未写入data file(数据库存放数据的文件)的已成功事务更新的数据。
3PC三段提交 |
---|
![]() |
7.3 TCC机制
TCC(Try 尝试,Confirm 确认,Cancel 取消),和你的业务代码切合在一起。
- Try:尝试去预执行具体业务代码。 下单订ing。。。
- try成功了:Confirm:再次执行Confirm的代码。
- try失败了:Cancel:再次执行Cancel的代码。
TCC其实采用的是补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作;分为三个阶段:
1.Try 阶段主要是对业务系统做检测及资源预留
2.Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
3.Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放
事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。
TCC |
---|
![]() |
7.4 MQ分布式事务
RabbitMQ在发送消息时,confirm机制,可以保证消息发送到MQ服务中,消费者有手动ack机制,保证消费到MQ中的消息,完成最终一致性。
MQ分布式事务 |
---|
![]() |
7.5 LCN实现分布式事务
基于三段提交和TCC实现的
创建一个协调者工程,创建两个服务(参与者)
协调者:添加依赖
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
协调者:编写配置文件
springboot的LCN项目有yml文件,需要加一个空的application.properties文件即可,不然数据库的信息会丢失不能识别而报错
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///lcn?serverTimezone=UTC
username: root
password: 123
redis:
host: 192.168.206.142
port: 6379
#协调端口
tx-lcn:
manager:
port: 8070
协调者:添加注解
@EnableTransactionManagerServer//协调者
协调者:准备表
# 创建表
CREATE TABLE `t_tx_exception` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`transaction_state` tinyint(4) NULL DEFAULT NULL,
`registrar` tinyint(4) NULL DEFAULT NULL,
`remark` varchar(4096) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
Order服务: 添加依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Order服务: 编写配置文件
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///lcn?serverTimezone=UTC
username: root
password: 123
#协调者和参与者的通信
tx-lcn:
client:
manager-address: localhost:8070
Order服务: 启动类添加注解
@EnableDistributedTransaction
@MapperScan("com.qf.mapper")
Order服务:controller
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/create")
public String create(){
orderService.createOrder();
return "创建订单成功";
}
}
Order服务:Service层添加注解,启动类需要配置@Bean创建RestTemplate对象
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private OrderMapper orderMapper;
@Override
@Transactional //事务
@LcnTransaction //
public void createOrder() {
//减库存,调用另一个项目
restTemplate.getForObject("http://localhost:8082/item", String.class);
// int i=1/0;
//创建订单,当前项目
orderMapper.createOrder();
}
}
Order服务:mapper接口
public interface OrderMapper {
@Insert("insert into t_order(id,name,money)values(1,'张三疯',22)")
void createOrder();
}
item服务:配置文件,
server:
port: 8082
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///lcn?serverTimezone=UTC
username: root
password: 123
tx-lcn:
client:
manager-address: localhost:8070
item服务:启动类添加注解
@EnableDistributedTransaction
@MapperScan(basePackages = "com.qf.mapper")
item服务:Controller层
@RestController
public class ItemController {
@Autowired
private ItemService itemService;
@GetMapping("/item")
public String item(){
itemService.updateStock();
return null;
}
}
item服务:Service层
@Service
public class ItemServiceImpl implements ItemService {
@Autowired
private ItemMapper itemMapper;
@Override
@Transactional
@LcnTransaction
public void updateStock() {
itemMapper.updateStock();
}
}
item服务:mapper层
public interface ItemMapper {
@Update("update t_item set stock = stock-1 where id = 1")
void updateStock();
}
测试,异常后,事务回滚
测试 |
---|
![]() |