不同的连接
事务:数据库操作的最小单元
分布式事务:跨越connect的事务
CAP理论
1.一旦出现了分区的情况下,需要保证数据的一致性,就要牺牲数据的可用性
2.一旦出现分区的情况下,如果保证集群的可用性,就牺牲了一致性;
满足CA-->单体应用
追求单体事务的完成度
XA/2PC
根据第一个阶段的返回结果
缺陷
1.第一阶段待修改的数据会被加上排它锁,导致在没有释放锁的时候其他执行会等待
2.无法保证数据的强一致性
(也就是导入一个事务管理器,让管理器管理)
定义:遵循BASE理论,最终一致性;与刚性不同,允许这一定时间内,不同节点数据不一致,但是要求数据最终一致;
TCC事务(相当于人工回滚)
服务化的二阶段编程模型() :XP/2PC是数据库的两阶段,TCC是业务的二阶段
try里是一个执行,confirm接口再执行
如果try失败了,那么执行 Cancel
也就是把业务分为了两个阶段,数据库是无法感知的,没有数据库阻塞
如果confirm失败了,会不断重试达到最终一致性
(业务二段,类似回滚)
(单项目操作变为事务+两张表+定时操作)
变成了本地的事务,(本地消息表),让1与2变成了事务,让定时任务发送消息来进行库存服务
库存服务方面
如果库存扣减失败,由于本地事务不会发送库存扣减的消息,余额方会不断重试,最终一致性;
现在存在的问题:扣减成功,但是发消息失败,会导致重试之后扣除两次库存
接口幂等性:
一个事件,在相同条件下,做一次与多次没有区别
对库存扣减实现接口幂等性
再添加一张表(建立扣减库存与扣减库存记录表的本地事务关系)
总结:
1.利用本地消息表,把扣除余额与插入消息表变为事务,(对此可以保证如果扣除失败,不插入,定时扫描不到就不发送消息)成功
1.1如果成功,定时任务扫描表格,发现没有具有事务没有完成的任务,会不断发送消息,进行扣减库存(保证最终一致性)
2.如果余额扣减成功,并发送消息,库存表执行扣减库存(如果失败,余额会不断重试发送),为了防止出现扣库存但是没有成功发送消息(得到一个扣减库存的记录表),利用这个表可以记录自身扣减的行为防止多次扣减库存如果二者都成功,再发回扣减库存成功消息给余额,余额表修改本地消息记录;
==>完成事务
思想:1.可以把方法通过数据库的加表事务变成事务方法
2.定时方法,重试的思想
消费失败RMQ会重试16次 4小时
④的消息丢失时,⑤会回查15次,如果回查都失败,默认本地事务失败了
1.上游失败执行4回滚,丢弃消息
2.下游执行失败会重试16次
3.上游消息丢失,会回查15次
如何保证?(记下来,要考)
==MQ事务消息
提供者:
多一个监听器
监听器里执行事务的方法
1,根据返回值来返回成功或者失败(来告诉mq的执行结果)
方法2.用来丢失消息的时候检查事务状态
消费者:
秒杀业务的提供者:
package com.cskaoyan.mq;
import com.alibaba.fastjson.JSON;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
/**
* @author lopper
* @creat ${year}-12-1810:48
*/
@Component
public class CreatProducer {
TransactionMQProducer transactionMQProducer;
//注入一个mapper
@PostConstruct
public void innit() {
transactionMQProducer = new TransactionMQProducer("creat_order_producer");
transactionMQProducer.setNamesrvAddr("127.0.0.1:9876");
//设置监听器
transactionMQProducer.setTransactionListener(new TransactionListener() {
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
//注入mapper 扣减库存 arg_>来自msg传递的参数_>此时是一个map
Map map = (Map) o;
Long psId = map.get("psId");
Long proId = map.get("ProId");
//mapper执行本地事务 返回值rows
if (false){
//影响行数小于1,表示商品没有库存,秒杀失败了
//本地缓存,利用事务key执行缓存,利用redis,表明每次事务是否成功
return LocalTransactionState.ROLLBACK_MESSAGE;
}
//本地缓存,利用事务key执行缓存,利用redis,表明每次事务是否成功 为了帮助回查
return LocalTransactionState.COMMIT_MESSAGE;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
//从redis里面拿到上一步存入的值,以供消息丢失的时候回查
//根据拿到的值 返回不同结果 成功
//注意有可能执行顺序,查询结果是空,返回unknown
return null;
}
});
try {
transactionMQProducer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
/**
* 返回值代表,消息发送成功,并且本地事务也执行成功了
* @param map
* @return
*/
public boolean sendMQMsgTX(Map map,Long psId,Long ProId) {
HashMap paramMap = new HashMap<>();
paramMap.put("psId",psId);
paramMap.put("ProId",ProId);
String jsonString = JSON.toJSONString(map);
Message message = new Message();
message.setTopic("creat_order");
message.setBody(jsonString.getBytes());
TransactionSendResult sendResult = null;
try {
sendResult = transactionMQProducer.sendMessageInTransaction(message, paramMap);
} catch (MQClientException e) {
e.printStackTrace();
}
if (sendResult!=null&&sendResult.getLocalTransactionState().equals(LocalTransactionState.COMMIT_MESSAGE)){
return true;
}
return false;
}
}
package com.cskaoyan.mq;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author lopper
* @creat ${year}-12-1811:21
*/
@Component
public class CreateConsumer {
DefaultMQPushConsumer consumer;
@PostConstruct
public void init() {
consumer = new DefaultMQPushConsumer("creat_order_consumer");
consumer.setNamesrvAddr("127.0.0.1:9876");
consumer.setMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
//调用订单的事务
MessageExt msg = list.get(0);
byte[] body = msg.getBody();
String jsonStr = new String(body);
//根据传入的强转
Map map = JSON.parseObject(jsonStr, Map.class);
//调用方法,传入对象
//根据调用结果返回消费成功或失败
return null;
}
});
try {
consumer.subscribe("creat_order","*");
consumer.start();
} catch (MQClientException e) {
e.printStackTrace();
}
}
}
通过频道,得知等待的队列是否完成;wait->notify
可重入锁:同一个线程可以对方法多次加锁,加锁次数的计数器:计数器加一,当加锁次数为0的时候锁释放;
===