1.场景描述
可以设想一个比较常见的分布式事务场景,商品上架操作,该操作涉及到商品模块的Service服务中的上架操作,同时必须要满足在solr中建立商品的索引方便前台搜索以及生成商品的静态化页面,在上架操作中发送了一条消息,消息接收方搜索工程以及静态化工程分别提供建立索引的服务以及生成静态化的服务。该两个服务本身无状态且独立,构成一个完整的事务。如下图所示
2.商品上架代码以及问题引出
@Autowired
private JmsTemplate jmsTemplate;
@Autowired
private ProductDao productDao;
/**
* 商品上架
* 在solr索引库建立索引信息
* 生成静态化页面
* @param ids 商品id集合
*/
@Transactional
public void isShow(Long[] ids) {
// 校验 防止空指针异常
if (null == ids || ids.length == 0) {
return;
}
Product product = new Product();
// 设置商品状态为上架
product.setIsShow(true);
for (final Long id : ids) {
product.setId(id);
// 数据库中更新商品状态为上架
productDao.updateByPrimaryKeySelective(product);
// 在solr索引库建立索引信息 生成静态化页面
// 发送消息
jmsTemplate.send(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
// 传递商品的id
return session.createTextMessage(String.valueOf(id));
}
});
}
商品上架,同时也是消息的发送方。首先要明确消息中间件是在分布式文件系统中完成消息的发送和接受的基础软件,而消息中间件是异步的,因此消息发送方并不知道自己发送的消息会被谁接受,也不能正确的接收到消息接收方接收到消息之后的状态。这就意味着一旦消息接收方出现了异常比如说服务器的宕机或者断电断网等因素导致了不能正确的建立索引以及生成静态化页面。事务不能得到保证,商品还是上架了。那么怎么当静态化工程或者搜索工程的一方出现异常的时候马上的进行事务的回滚,让商品上架失败呢?
3.解决方案
①在数据库建立日志表
CREATE TABLE `pdbs_pdata_logs` (
`pdata_id` int(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`pdata_product_id` bigint(20) DEFAULT NULL COMMENT '商品id',
`pdata_is_solr` tinyint(1) DEFAULT NULL COMMENT '是否建立solr索引',
`pdata_is_freemarker` tinyint(1) DEFAULT NULL COMMENT '是否生成静态化页面',
PRIMARY KEY (`ts_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
②分布式事务解决方案
public void isShow(Long[] ids) {
// 在此处执行以下sql
// insert into pdbs_pdata_logs ('pdata_product_id') values(id1),(id2),(id3).....
if (null == ids || ids.length == 0) {
return;
}
Product product = new Product();
product.setIsShow(true);
for (final Long id : ids) {
product.setId(id);
productDao.updateByPrimaryKeySelective(product);
// 在静态化工程 和搜索工程中 分别执行以下SQL
// update pdbs_pdata_logs t set t.pdata_is_freemarker =1 where t.pdata_product_id= ?
// update pdbs_pdata_logs t set t.pdata_is_solr=1 where t.pdata_product_id= ?
jmsTemplate.send(new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
// 传递商品的id
return session.createTextMessage(String.valueOf(id));
}
});
}
// 执行以下sql
//select COUNT(1) from pdbs_pdata_logs t where (ISNULL(t.pdata_is_freemarker) or ISNULL(t.pdata_is_solr)) AND t.pdata_product_id in(id1,id2,....);
// 如果count > 0的话 这说明 静态化工程或者搜索工程有一方有异常或者没有正确的执行 上架失败。抛出一个运行时异常 spring 捕捉到运行时异常则会进行事务的回滚 这样在一定的方面 解决了分布式事务的一部分问题
}
分布式事务,本质上是对多个数据库的事务进行统一控制,按照控制力度可以分为:不控制、部分控制和完全控制。不控制就是不引入分布式事务,部分控制就是各种变种的两阶段提交,包括上面提到的消息事务+最终一致性、TCC模式,而完全控制就是完全实现两阶段提交。部分控制的好处是并发量和性能很好,缺点是数据一致性减弱了,完全控制则是牺牲了性能,保障了一致性,具体用哪种方式,最终还是取决于业务场景。作为技术人员,一定不能忘了技术是为业务服务的,不要为了技术而技术,针对不同业务进行技术选型也是一种很重要的能力!