热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

RocketMQ事务消费和顺序消费详解,小票

http:www.cnblogs.com520playboyp6750023.html一、RocketMq有3中消息类型1.普通消费2.顺序消费3.事务消费顺序消费场景在网购的时

http://www.cnblogs.com/520playboy/p/6750023.html


一、RocketMq有3中消息类型

1.普通消费

2. 顺序消费

3.事务消费

  • 顺序消费场景

在网购的时候,我们需要下单,那么下单需要假如有三个顺序,第一、创建订单 ,第二:订单付款,第三:订单完成。也就是这个三个环节要有顺序,这个订单才有意义。RocketMQ可以保证顺序消费。

  • rocketMq实现顺序消费的原理

 produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一个线程去消费消息

注意:是把把消息发到同一个队列(queue),不是同一个topic,默认情况下一个topic包括4个queue

单个节点(Producer端1个、Consumer端1个)

1、Producer.java 

package order; import java.util.List; import com.alibaba.rocketmq.client.exception.MQBrokerException;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
import com.alibaba.rocketmq.client.producer.MessageQueueSelector;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.common.message.Message;
import com.alibaba.rocketmq.common.message.MessageQueue;
import com.alibaba.rocketmq.remoting.exception.RemotingException; /** * Producer,发送顺序消息 */
public class Producer { public static void main(String[] args) { try { DefaultMQProducer producer &#61; new DefaultMQProducer("order_Producer"); producer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876"); producer.start(); // String[] tags &#61; new String[] { "TagA", "TagB", "TagC", "TagD", // "TagE" }; for (int i &#61; 1; i <&#61; 5; i&#43;&#43;) { Message msg &#61; new Message("TopicOrderTest", "order_1", "KEY" &#43; i, ("order_1 " &#43; i).getBytes()); SendResult sendResult &#61; producer.send(msg, new MessageQueueSelector() { public MessageQueue select(List mqs, Message msg, Object arg) { Integer id &#61; (Integer) arg; int index &#61; id % mqs.size(); return mqs.get(index); } }, 0); System.out.println(sendResult); } producer.shutdown(); } catch (MQClientException e) { e.printStackTrace(); } catch (RemotingException e) { e.printStackTrace(); } catch (MQBrokerException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } }
}

2、Consumer.java


package order;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerOrderly;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
import com.alibaba.rocketmq.common.message.MessageExt; /** * 顺序消息消费&#xff0c;带事务方式&#xff08;应用可控制Offset什么时候提交&#xff09; */
public class Consumer1 { public static void main(String[] args) throws MQClientException { DefaultMQPushConsumer consumer &#61; new DefaultMQPushConsumer("order_Consumer"); consumer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876"); /** * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
* 如果非第一次启动&#xff0c;那么按照上次消费的位置继续消费
*/ consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("TopicOrderTest", "*"); consumer.registerMessageListener(new MessageListenerOrderly() { AtomicLong consumeTimes &#61; new AtomicLong(0); public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { // 设置自动提交 context.setAutoCommit(true); for (MessageExt msg : msgs) { System.out.println(msg &#43; ",内容&#xff1a;" &#43; new String(msg.getBody())); } try { TimeUnit.SECONDS.sleep(5L); } catch (InterruptedException e) { e.printStackTrace(); } ; return ConsumeOrderlyStatus.SUCCESS; } }); consumer.start(); System.out.println("Consumer1 Started."); } }


结果如下图所示&#xff1a;

这个五条数据被顺序消费了

  • 多个节点&#xff08;Producer端1个、Consumer端2个&#xff09;

Producer.java

package order; import java.util.List; import com.alibaba.rocketmq.client.exception.MQBrokerException;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.client.producer.DefaultMQProducer;
import com.alibaba.rocketmq.client.producer.MessageQueueSelector;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.common.message.Message;
import com.alibaba.rocketmq.common.message.MessageQueue;
import com.alibaba.rocketmq.remoting.exception.RemotingException; /** * Producer&#xff0c;发送顺序消息 */
public class Producer { public static void main(String[] args) { try { DefaultMQProducer producer &#61; new DefaultMQProducer("order_Producer"); producer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876"); producer.start(); // String[] tags &#61; new String[] { "TagA", "TagB", "TagC", "TagD", // "TagE" }; for (int i &#61; 1; i <&#61; 5; i&#43;&#43;) { Message msg &#61; new Message("TopicOrderTest", "order_1", "KEY" &#43; i, ("order_1 " &#43; i).getBytes()); SendResult sendResult &#61; producer.send(msg, new MessageQueueSelector() { public MessageQueue select(List mqs, Message msg, Object arg) { Integer id &#61; (Integer) arg; int index &#61; id % mqs.size(); return mqs.get(index); } }, 0); System.out.println(sendResult); } for (int i &#61; 1; i <&#61; 5; i&#43;&#43;) { Message msg &#61; new Message("TopicOrderTest", "order_2", "KEY" &#43; i, ("order_2 " &#43; i).getBytes()); SendResult sendResult &#61; producer.send(msg, new MessageQueueSelector() { public MessageQueue select(List mqs, Message msg, Object arg) { Integer id &#61; (Integer) arg; int index &#61; id % mqs.size(); return mqs.get(index); } }, 1); System.out.println(sendResult); } for (int i &#61; 1; i <&#61; 5; i&#43;&#43;) { Message msg &#61; new Message("TopicOrderTest", "order_3", "KEY" &#43; i, ("order_3 " &#43; i).getBytes()); SendResult sendResult &#61; producer.send(msg, new MessageQueueSelector() { public MessageQueue select(List mqs, Message msg, Object arg) { Integer id &#61; (Integer) arg; int index &#61; id % mqs.size(); return mqs.get(index); } }, 2); System.out.println(sendResult); } producer.shutdown(); } catch (MQClientException e) { e.printStackTrace(); } catch (RemotingException e) { e.printStackTrace(); } catch (MQBrokerException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } }
}


Consumer1.java

/** * 顺序消息消费&#xff0c;带事务方式&#xff08;应用可控制Offset什么时候提交&#xff09; */
public class Consumer1 { public static void main(String[] args) throws MQClientException { DefaultMQPushConsumer consumer &#61; new DefaultMQPushConsumer("order_Consumer"); consumer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876"); /** * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
* 如果非第一次启动&#xff0c;那么按照上次消费的位置继续消费
*/ consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("TopicOrderTest", "*"); /** * 实现了MessageListenerOrderly表示一个队列只会被一个线程取到 *&#xff0c;第二个线程无法访问这个队列 */ consumer.registerMessageListener(new MessageListenerOrderly() { AtomicLong consumeTimes &#61; new AtomicLong(0); public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { // 设置自动提交 context.setAutoCommit(true); for (MessageExt msg : msgs) { System.out.println(msg &#43; ",内容&#xff1a;" &#43; new String(msg.getBody())); } try { TimeUnit.SECONDS.sleep(5L); } catch (InterruptedException e) { e.printStackTrace(); } ; return ConsumeOrderlyStatus.SUCCESS; } }); consumer.start(); System.out.println("Consumer1 Started."); } }


Consumer2.java

/** * 顺序消息消费&#xff0c;带事务方式&#xff08;应用可控制Offset什么时候提交&#xff09; */
public class Consumer2 { public static void main(String[] args) throws MQClientException { DefaultMQPushConsumer consumer &#61; new DefaultMQPushConsumer("order_Consumer"); consumer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876"); /** * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
* 如果非第一次启动&#xff0c;那么按照上次消费的位置继续消费
*/ consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("TopicOrderTest", "*"); /** * 实现了MessageListenerOrderly表示一个队列只会被一个线程取到 *&#xff0c;第二个线程无法访问这个队列 */ consumer.registerMessageListener(new MessageListenerOrderly() { AtomicLong consumeTimes &#61; new AtomicLong(0); public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { // 设置自动提交 context.setAutoCommit(true); for (MessageExt msg : msgs) { System.out.println(msg &#43; ",内容&#xff1a;" &#43; new String(msg.getBody())); } try { TimeUnit.SECONDS.sleep(5L); } catch (InterruptedException e) { e.printStackTrace(); } ; return ConsumeOrderlyStatus.SUCCESS; } }); consumer.start(); System.out.println("Consumer2 Started."); } }


先启动Consumer1和Consumer2&#xff0c;然后启动Producer&#xff0c;Producer会发送15条消息
Consumer1消费情况如图&#xff0c;都按照顺序执行了




Consumer2消费情况如图&#xff0c;都按照顺序执行了

二、事务消费

这里说的主要是分布式事物。下面的例子的数据库分别安装在不同的节点上。

事物消费需要先说说什么是事务。比如说&#xff1a;我们跨行转账&#xff0c;从工商银行转到建设银行&#xff0c;也就是我从工商银行扣除1000元之后&#xff0c;我的建设银行也必须加1000元。这样才能保证数据的一致性。假如工商银行转1000元之后&#xff0c;建设银行的服务器突然宕机&#xff0c;那么我扣除了1000&#xff0c;但是并没有在建设银行给我加1000&#xff0c;就出现了数据的不一致。因此加1000和减1000才行&#xff0c;减1000和减1000必须一起成功&#xff0c;一起失败。

再比如&#xff0c;我们进行网购的时候&#xff0c;我们下单之后&#xff0c;订单提交成功&#xff0c;仓库商品的数量必须减一。但是订单可能是一个数据库&#xff0c;仓库数量可能又是在另个数据库里面。有可能订单提交成功之后&#xff0c;仓库数量服务器突然宕机。这样也出现了数据不一致的问题。

使用消息队列来解决分布式事物&#xff1a;

现在我们去外面饭店吃饭&#xff0c;很多时候都不会直接给了钱之后直接在付款的窗口递饭菜&#xff0c;而是付款之后他会给你一张小票&#xff0c;你拿着这个小票去出饭的窗口取饭。这里和我们的系统类似&#xff0c;提高了吞吐量。即使你到第二个窗口&#xff0c;师傅告诉你已经没饭了&#xff0c;你可以拿着这个凭证去退款&#xff0c;即使中途由于出了意外你无法到达窗口进行取饭&#xff0c;但是只要凭证还在&#xff0c;可以将钱退给你。这样就保证了数据的一致性。

如何保证凭证&#xff08;消息&#xff09;有2种方法&#xff1a;

1、在工商银行扣款的时候&#xff0c;余额表扣除1000&#xff0c;同时记录日志&#xff0c;而且这2个表是在同一个数据库实例中&#xff0c;可以使用本地事物解决。然后我们通知建设银行需要加1000给该用户&#xff0c;建设银行收到之后给我返回已经加了1000给用户的确认信息之后&#xff0c;我再标记日志表里面的日志为已经完成。

2、通过消息中间件

原文地址&#xff1a;http://www.jianshu.com/p/453c6e7ff81c

 

RocketMQ第一阶段发送Prepared消息时&#xff0c;会拿到消息的地址&#xff0c;第二阶段执行本地事物&#xff0c;第三阶段通过第一阶段拿到的地址去访问消息&#xff0c;并修改消息的状态。

细心的你可能又发现问题了&#xff0c;如果确认消息发送失败了怎么办&#xff1f;RocketMQ会定期扫描消息集群中的事物消息&#xff0c;如果发现了Prepared消息&#xff0c;它会向消息发送端(生产者)确认&#xff0c;Bob的钱到底是减了还是没减呢&#xff1f;如果减了是回滚还是继续发送确认消息呢&#xff1f;RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

例子&#xff1a;

Consumer.java

 

package transaction; import java.util.List; import com.alibaba.rocketmq.client.consumer.DefaultMQPushConsumer;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import com.alibaba.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import com.alibaba.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.common.consumer.ConsumeFromWhere;
import com.alibaba.rocketmq.common.message.MessageExt; /** * Consumer&#xff0c;订阅消息 */
public class Consumer { public static void main(String[] args) throws InterruptedException, MQClientException { DefaultMQPushConsumer consumer &#61; new DefaultMQPushConsumer("transaction_Consumer"); consumer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876"); consumer.setConsumeMessageBatchMaxSize(10); /** * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费
* 如果非第一次启动&#xff0c;那么按照上次消费的位置继续消费
*/ consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET); consumer.subscribe("TopicTransactionTest", "*"); consumer.registerMessageListener(new MessageListenerConcurrently() { public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { try { for (MessageExt msg : msgs) { System.out.println(msg &#43; ",内容&#xff1a;" &#43; new String(msg.getBody())); } } catch (Exception e) { e.printStackTrace(); return ConsumeConcurrentlyStatus.RECONSUME_LATER;// 重试
} return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;// 成功
} }); consumer.start(); System.out.println("transaction_Consumer Started."); }
}

Producer.java


  

package transaction; import com.alibaba.rocketmq.client.exception.MQClientException;
import com.alibaba.rocketmq.client.producer.SendResult;
import com.alibaba.rocketmq.client.producer.TransactionCheckListener;
import com.alibaba.rocketmq.client.producer.TransactionMQProducer;
import com.alibaba.rocketmq.common.message.Message; /** * 发送事务消息例子 * */
public class Producer { public static void main(String[] args) throws MQClientException, InterruptedException { TransactionCheckListener transactionCheckListener &#61; new TransactionCheckListenerImpl(); TransactionMQProducer producer &#61; new TransactionMQProducer("transaction_Producer"); producer.setNamesrvAddr("192.168.100.145:9876;192.168.100.146:9876;192.168.100.149:9876;192.168.100.239:9876"); // 事务回查最小并发数 producer.setCheckThreadPoolMinSize(2); // 事务回查最大并发数 producer.setCheckThreadPoolMaxSize(2); // 队列数 producer.setCheckRequestHoldMax(2000); producer.setTransactionCheckListener(transactionCheckListener); producer.start(); // String[] tags &#61; new String[] { "TagA", "TagB", "TagC", "TagD", "TagE" // }; TransactionExecuterImpl tranExecuter &#61; new TransactionExecuterImpl(); for (int i &#61; 1; i <&#61; 2; i&#43;&#43;) { try { Message msg &#61; new Message("TopicTransactionTest", "transaction" &#43; i, "KEY" &#43; i, ("Hello RocketMQ " &#43; i).getBytes()); SendResult sendResult &#61; producer.sendMessageInTransaction(msg, tranExecuter, null); System.out.println(sendResult); Thread.sleep(10); } catch (MQClientException e) { e.printStackTrace(); } } for (int i &#61; 0; i <100000; i&#43;&#43;) { Thread.sleep(1000); } producer.shutdown(); }
}

TransactionExecuterImpl .java --执行本地事务

package transaction; import com.alibaba.rocketmq.client.producer.LocalTransactionExecuter;
import com.alibaba.rocketmq.client.producer.LocalTransactionState;
import com.alibaba.rocketmq.common.message.Message; /** * 执行本地事务 */
public class TransactionExecuterImpl implements LocalTransactionExecuter { // private AtomicInteger transactionIndex &#61; new AtomicInteger(1); public LocalTransactionState executeLocalTransactionBranch(final Message msg, final Object arg) { System.out.println("执行本地事务msg &#61; " &#43; new String(msg.getBody())); System.out.println("执行本地事务arg &#61; " &#43; arg); String tags &#61; msg.getTags(); if (tags.equals("transaction2")) { System.out.println("&#61;&#61;&#61;&#61;&#61;&#61;我的操作&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#xff0c;失败了 -进行ROLLBACK"); return LocalTransactionState.ROLLBACK_MESSAGE; } return LocalTransactionState.COMMIT_MESSAGE; // return LocalTransactionState.UNKNOW;
}
}



TransactionCheckListenerImpl--未决事务&#xff0c;服务器回查客户端(目前已经被阉割啦)

package transaction; import com.alibaba.rocketmq.client.producer.LocalTransactionState;
import com.alibaba.rocketmq.client.producer.TransactionCheckListener;
import com.alibaba.rocketmq.common.message.MessageExt; /** * 未决事务&#xff0c;服务器回查客户端 */
public class TransactionCheckListenerImpl implements TransactionCheckListener { // private AtomicInteger transactionIndex &#61; new AtomicInteger(0); //在这里&#xff0c;我们可以根据由MQ回传的key去数据库查询&#xff0c;这条数据到底是成功了还是失败了。 public LocalTransactionState checkLocalTransactionState(MessageExt msg) { System.out.println("未决事务&#xff0c;服务器回查客户端msg &#61;" &#43; new String(msg.getBody().toString())); // return LocalTransactionState.ROLLBACK_MESSAGE; return LocalTransactionState.COMMIT_MESSAGE; // return LocalTransactionState.UNKNOW;
}
}


producer端&#xff1a;发送数据到MQ&#xff0c;并且处理本地事物。这里模拟了一个成功一个失败。Consumer只会接收到本地事物成功的数据。第二个数据失败了&#xff0c;不会被消费。



Consumer只会接收到一个&#xff0c;第二个数据不会被接收到


推荐阅读
  • 我正在使用 Ruby on Rails 构建个人网站。总体而言,RoR 是一个非常出色的工具,它提供了丰富的功能和灵活性,使得创建自定义页面变得既高效又便捷。通过利用其强大的框架和模块化设计,我可以轻松实现复杂的功能,同时保持代码的整洁和可维护性。此外,Rails 的社区支持也非常强大,为开发过程中遇到的问题提供了丰富的资源和解决方案。 ... [详细]
  • C#编程指南:实现列表与WPF数据网格的高效绑定方法 ... [详细]
  • Java 8 引入了 Stream API,这一新特性极大地增强了集合数据的处理能力。通过 Stream API,开发者可以更加高效、简洁地进行集合数据的遍历、过滤和转换操作。本文将详细解析 Stream API 的核心概念和常见用法,帮助读者更好地理解和应用这一强大的工具。 ... [详细]
  • 在处理UVA11987问题时,关键在于实现并查集结构以支持删除操作。特别地,当需要删除某个节点时,如果该节点不是根节点,则处理相对简单;然而,若删除的是根节点,则需要进行额外的处理来维护集合的连通性。本文将详细介绍如何通过优化并查集算法,确保在删除根节点时仍能高效地维护数据结构的完整性和查询效率。 ... [详细]
  • 本文详细探讨了Java集合框架的使用方法及其性能特点。首先,通过关系图展示了集合接口之间的层次结构,如`Collection`接口作为对象集合的基础,其下分为`List`、`Set`和`Queue`等子接口。其中,`List`接口支持按插入顺序保存元素且允许重复,而`Set`接口则确保元素唯一性。此外,文章还深入分析了不同集合类在实际应用中的性能表现,为开发者选择合适的集合类型提供了参考依据。 ... [详细]
  • 使用 MyEclipse 和 TestNG 测试框架在 Java 中高效进行单元测试
    通过MyEclipse集成TestNG测试框架,可以在Java开发中高效地进行单元测试。本文介绍了在JDK 1.8.0_121和MyEclipse 10.0离线环境下配置和使用TestNG的具体步骤,帮助开发者提高测试效率和代码质量。 ... [详细]
  • 使用cpphttplib构建HTTP服务器以处理带有查询参数的URL请求 ... [详细]
  • 在《PHP应用性能优化实战指南:从理论到实践的全面解析》一文中,作者分享了一次实际的PHP应用优化经验。文章回顾了先前进行的一次优化项目,指出即使系统运行时间较长后出现的各种问题和性能瓶颈,通过采用一些通用的优化策略仍然能够有效解决。文中不仅详细阐述了优化的具体步骤和方法,还结合实例分析了优化前后的性能对比,为读者提供了宝贵的参考和借鉴。 ... [详细]
  • Java 中优先级队列的轮询方法详解与应用 ... [详细]
  • 本文深入探讨了 MXOTDLL.dll 在 C# 环境中的应用与优化策略。针对近期公司从某生物技术供应商采购的指纹识别设备,该设备提供的 DLL 文件是用 C 语言编写的。为了更好地集成到现有的 C# 系统中,我们对原生的 C 语言 DLL 进行了封装,并利用 C# 的互操作性功能实现了高效调用。此外,文章还详细分析了在实际应用中可能遇到的性能瓶颈,并提出了一系列优化措施,以确保系统的稳定性和高效运行。 ... [详细]
  • BZOJ1034 详细解析与算法优化
    本文深入解析了BZOJ1034问题,并提出了优化算法。通过借鉴广义田忌赛马的贪心策略,当己方当前最弱的马优于对方最弱的马时进行匹配;同样地,若己方当前最强的马优于对方最强的马,也进行匹配。此方法在保证胜率的同时,有效提升了算法效率。 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 本文详细探讨了C语言中`extern`关键字的简易编译方法,并深入解析了预编译、`static`和`extern`的综合应用。通过具体的代码示例,介绍了如何在不同的文件之间共享变量和函数声明,以及这些关键字在编译过程中的作用和影响。文章还讨论了预编译过程中宏定义的使用,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 深入解析Gradle中的Project核心组件
    在Gradle构建系统中,`Project` 是一个核心组件,扮演着至关重要的角色。通过使用 `./gradlew projects` 命令,可以清晰地列出当前项目结构中包含的所有子项目,这有助于开发者更好地理解和管理复杂的多模块项目。此外,`Project` 对象还提供了丰富的配置选项和生命周期管理功能,使得构建过程更加灵活高效。 ... [详细]
  • 题目描述:小K不幸被LL邪教洗脑,洗脑程度之深使他决定彻底脱离这个邪教。在最终离开前,他计划再进行一次亚瑟王游戏。作为最后一战,他希望这次游戏能够尽善尽美。众所周知,亚瑟王游戏的结果很大程度上取决于运气,但通过合理的策略和算法优化,可以提高获胜的概率。本文将详细解析洛谷P3239 [HNOI2015] 亚瑟王问题,并提供具体的算法实现方法,帮助读者更好地理解和应用相关技术。 ... [详细]
author-avatar
mobiledu2502936427
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有