Seata源码—7.Seata TCC模式的事务处理一
大纲
1.Seata TCC分布式事务案例配置
2.Seata TCC案例服务提供者启动分析
3.@TwoPhaseBusinessAction注解扫描源码
4.Seata TCC案例分布式事务入口分析
5.TCC核心注解扫描与代理创建入口源码
6.TCC动态代理拦截器TccActionInterceptor
7.Action拦截处理器ActionInterceptorHandler
8.Seata TCC分布式事务的注册提交回滚处理源码
1.Seata TCC分布式事务案例配置
(1)位于seata-samples的tcc模块下的Demo工程
(2)Demo工程的配置文件
(3)Demo工程运行说明
(1)位于seata-samples的tcc模块下的Demo工程
dubbo-tcc-sample模块主要演示了TCC模式下分布式事务的提交和回滚。该Demo中一个分布式事务内会有两个TCC事务参与者,这两个TCC事务参与者分别是TccActionOne和TccActionTwo。分布式事务提交则两者均提交,分布式事务回滚则两者均回滚。
这两个TCC事务参与者均是Dubbo远程服务。一个应用作为服务提供方,会实现这两个TCC参与者,并将它们发布成Dubbo服务。另外一个应用作为事务发起方,会订阅Dubbo服务,然后调用编排TCC参与者,执行远程Dubbo服务。
TccActionOne接口定义如下:
public interface TccActionOne {@TwoPhaseBusinessAction(name = "DubboTccActionOne", commitMethod = "commit", rollbackMethod = "rollback")public boolean prepare(BusinessActionContext actionContext, @BusinessActionContextParameter(paramName = "a") int a);public boolean commit(BusinessActionContext actionContext);public boolean rollback(BusinessActionContext actionContext);
}
TccActionTwo接口定义如下:
public interface TccActionTwo {@TwoPhaseBusinessAction(name = "DubboTccActionTwo", commitMethod = "commit", rollbackMethod = "rollback")public boolean prepare(BusinessActionContext actionContext,@BusinessActionContextParameter(paramName = "b") String b,@BusinessActionContextParameter(paramName = "c", index = 1) List list);public boolean commit(BusinessActionContext actionContext);public boolean rollback(BusinessActionContext actionContext);
}
(2)Demo工程的配置文件
一.seata-tcc.xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"default-autowire="byName"><!-- fescar bean scanner --><bean class="io.seata.spring.annotation.GlobalTransactionScanner"><constructor-arg value="tcc-sample"/><constructor-arg value="my_test_tx_group"/></bean><bean id="tccActionOneImpl" class="io.seata.samples.tcc.dubbo.action.impl.TccActionOneImpl"/><bean id="tccActionTwoImpl" class="io.seata.samples.tcc.dubbo.action.impl.TccActionTwoImpl"/><bean id="tccTransactionService" class="io.seata.samples.tcc.dubbo.service.TccTransactionService"/>
</beans>
二.seata-dubbo-provider.xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd" default-autowire="byName"><dubbo:application name="tcc-sample"><dubbo:parameter key="qos.enable" value="false"/></dubbo:application><!--使用 zookeeper 注册中心暴露服务,注意要先开启 zookeeper--><dubbo:registry address="zookeeper://127.0.0.1:2181"/><dubbo:protocol name="dubbo" port="-1"/><dubbo:provider timeout="10000" threads="10" threadpool="fixed" loadbalance="roundrobin"/><!-- 第一个TCC 参与者服务发布 --><dubbo:service interface="io.seata.samples.tcc.dubbo.action.TccActionOne" ref="tccActionOneImpl"/><!-- 第二个TCC 参与者服务发布 --><dubbo:service interface="io.seata.samples.tcc.dubbo.action.TccActionTwo" ref="tccActionTwoImpl"/>
</beans>
三.seata-dubbo-reference.xml
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://code.alibabatech.com/schema/dubbohttp://code.alibabatech.com/schema/dubbo/dubbo.xsd" default-autowire="byName"><dubbo:application name="tcc-sample-reference"><dubbo:parameter key="qos.enable" value="false"/></dubbo:application><!--使用 zookeeper 注册中心暴露服务,注意要先开启 zookeeper--><dubbo:registry address="zookeeper://127.0.0.1:2181"/><dubbo:protocol name="dubbo" port="-1"/><dubbo:provider timeout="10000" threads="10" threadpool="fixed" loadbalance="roundrobin"/><!-- 第一个TCC参与者 服务订阅 --><dubbo:reference id="tccActionOne" interface="io.seata.samples.tcc.dubbo.action.TccActionOne" check="false" lazy="true"/><!-- 第二个TCC参与者 服务订阅 --><dubbo:reference id="tccActionTwo" interface="io.seata.samples.tcc.dubbo.action.TccActionTwo" check="false" lazy="true"/>
</beans>
(3)Demo工程运行指南
一.启动Seata Server
二.启动Dubbo服务应用
运行DubboTccProviderStarter。该应用会发布Dubbo服务,并且实现了两个TCC参与者。
public class TccProviderStarter extends AbstractStarter {public static void main(String[] args) throws Exception {new TccProviderStarter().start0(args);}@Overrideprotected void start0(String[] args) throws Exception {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"spring/seata-tcc.xml", "spring/seata-dubbo-provider.xml"});new ApplicationKeeper().keep();}
}public class TccActionOneImpl implements TccActionOne {@Overridepublic boolean prepare(BusinessActionContext actionContext, int a) {String xid = actionContext.getXid();System.out.println("TccActionOne prepare, xid:" + xid + ", a:" + a);return true;}@Overridepublic boolean commit(BusinessActionContext actionContext) {String xid = actionContext.getXid();System.out.println("TccActionOne commit, xid:" + xid + ", a:" + actionContext.getActionContext("a"));ResultHolder.setActionOneResult(xid, "T");return true;}@Overridepublic boolean rollback(BusinessActionContext actionContext) {String xid = actionContext.getXid();System.out.println("TccActionOne rollback, xid:" + xid + ", a:" + actionContext.getActionContext("a"));ResultHolder.setActionOneResult(xid, "R");return true;}
}public class TccActionTwoImpl implements TccActionTwo {@Overridepublic boolean prepare(BusinessActionContext actionContext, String b, List list) {String xid = actionContext.getXid();System.out.println("TccActionTwo prepare, xid:" + xid + ", b:" + b + ", c:" + list.get(1));return true;}@Overridepublic boolean commit(BusinessActionContext actionContext) {String xid = actionContext.getXid();System.out.println("TccActionTwo commit, xid:" + xid + ", b:" + actionContext.getActionContext("b") + ", c:" + actionContext.getActionContext("c"));ResultHolder.setActionTwoResult(xid, "T");return true;}@Overridepublic boolean rollback(BusinessActionContext actionContext) {String xid = actionContext.getXid();System.out.println("TccActionTwo rollback, xid:" + xid + ", b:" + actionContext.getActionContext("b") + ", c:" + actionContext.getActionContext("c"));ResultHolder.setActionTwoResult(xid, "R");return true;}
}
三.启动事务应用
运行TccConsumerStarter。该应用会订阅Dubbo服务,发起分布式事务,调用上述两个TCC参与者,内含TCC事务提交场景和TCC事务回滚场景的演示。
public class TccConsumerStarter extends AbstractStarter {static TccTransactionService tccTransactionService = null;public static void main(String[] args) throws Exception {new TccConsumerStarter().start0(args);}@Overrideprotected void start0(String[] args) throws Exception {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(new String[]{"spring/seata-tcc.xml", "spring/seata-dubbo-reference.xml"});tccTransactionService = (TccTransactionService) applicationContext.getBean("tccTransactionService");//分布式事务提交demotransactionCommitDemo();//分布式事务回滚demotransactionRollbackDemo();}private static void transactionCommitDemo() throws InterruptedException {String txId = tccTransactionService.doTransactionCommit();System.out.println(txId);Assert.isTrue(StringUtils.isNotEmpty(txId), "事务开启失败");System.out.println("transaction commit demo finish.");}private static void transactionRollbackDemo() throws InterruptedException {Map map = new HashMap(16);try {tccTransactionService.doTransactionRollback(map);Assert.isTrue(false, "分布式事务未回滚");} catch (Throwable t) {Assert.isTrue(true, "分布式事务异常回滚");}String txId = (String) map.get("xid");System.out.println(txId);System.out.println("transaction rollback demo finish.");}
}public class TccTransactionService {private TccActionOne tccActionOne;private TccActionTwo tccActionTwo;//提交分布式事务@GlobalTransactionalpublic String doTransactionCommit() {//第一个TCC事务参与者boolean result = tccActionOne.prepare(null, 1);if (!result) {throw new RuntimeException("TccActionOne failed.");}List list = new ArrayList();list.add("c1");list.add("c2");//第二个TCC事务参与者result = tccActionTwo.prepare(null, "two", list);if (!result) {throw new RuntimeException("TccActionTwo failed.");}return RootContext.getXID();}//回滚分布式事务@GlobalTransactionalpublic String doTransactionRollback(Map map) {//第一个TCC事务参与者boolean result = tccActionOne.prepare(null, 1);if (!result) {throw new RuntimeException("TccActionOne failed.");}List list = new ArrayList();list.add("c1");list.add("c2");//第二个TCC事务参与者result = tccActionTwo.prepare(null, "two", list);if (!result) {throw new RuntimeException("TccActionTwo failed.");}map.put("xid", RootContext.getXID());throw new RuntimeException("transacton rollback");}public void setTccActionOne(TccActionOne tccActionOne) {this.tccActionOne = tccActionOne;}public void setTccActionTwo(TccActionTwo tccActionTwo) {this.tccActionTwo = tccActionTwo;}
}
2.Seata TCC案例服务提供者启动分析
添加了@TwoPhaseBusinessAction注解的接口发布成Dubbo服务:
3.@TwoPhaseBusinessAction注解扫描源码
(1)全局事务注解扫描器的wrapIfNecessary()方法扫描Spring Bean
(2)TCCBeanParserUtils的isTccAutoProxy()方法判断是否要创建TCC动态代理
(1)全局事务注解扫描器的wrapIfNecessary()方法扫描Spring Bean
全局事务注解扫描器GlobalTransactionScanner会在调用initClient()方法初始化Seata Client客户端后,通过wrapIfNecessary()方法扫描Spring Bean中含有@TwoPhaseBusinessAction注解的方法。
//AbstractAutoProxyCreator:Spring的动态代理自动创建者
//ConfigurationChangeListener:关注配置变更事件的监听器
//InitializingBean:Spring Bean初始化回调
//ApplicationContextAware:用来感知Spring容器
//DisposableBean:支持可抛弃Bean
public class GlobalTransactionScanner extends AbstractAutoProxyCreatorimplements ConfigurationChangeListener, InitializingBean, ApplicationContextAware, DisposableBean {...//InitializingBean接口的回调方法//Spring容器启动和初始化完毕后,会调用如下的afterPropertiesSet()方法进行回调@Overridepublic void afterPropertiesSet() {//是否禁用了全局事务,默认是falseif (disableGlobalTransaction) {if (LOGGER.isInfoEnabled()) {LOGGER.info("Global transaction is disabled.");}ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (ConfigurationChangeListener)this);return;}//通过CAS操作确保initClient()初始化动作仅仅执行一次if (initialized.compareAndSet(false, true)) {//initClient()方法会对Seata Client进行初始化,比如和Seata Server建立长连接//seata-samples的tcc模块的seata-tcc.xml配置文件里都配置了GlobalTransactionScanner这个Bean//而GlobalTransactionScanner这个Bean伴随着Spring容器的初始化完毕,都会回调其初始化逻辑initClient()initClient();}}//initClient()是核心方法,负责对Seata Client客户端进行初始化private void initClient() {if (LOGGER.isInfoEnabled()) {LOGGER.info("Initializing Global Transaction Clients ... ");}if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {LOGGER.warn("...", DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP);}if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));}//对于Seata Client来说,最重要的组件有两个://一个是TM,即Transaction Manager,用来管理全局事务//一个是RM,即Resource Manager,用来管理各分支事务的数据源//init TM//TMClient.init()会对客户端的TM全局事务管理器进行初始化TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);if (LOGGER.isInfoEnabled()) {LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);}//init RM//RMClient.init()会对客户端的RM分支事务资源管理器进行初始化RMClient.init(applicationId, txServiceGroup);if (LOGGER.isInfoEnabled()) {LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);}if (LOGGER.isInfoEnabled()) {LOGGER.info("Global Transaction Clients are initialized. ");}//注册Spring容器被销毁时的回调钩子,释放TM和RM两个组件的一些资源registerSpringShutdownHook();}//The following will be scanned, and added corresponding interceptor://添加了如下注解的方法会被扫描到,然后方法会添加相应的拦截器进行拦截//TM://@see io.seata.spring.annotation.GlobalTransactional // TM annotation//Corresponding interceptor://@see io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalTransaction(MethodInvocation, AspectTransactional) // TM handler//GlobalLock://@see io.seata.spring.annotation.GlobalLock // GlobalLock annotation//Corresponding interceptor://@see io.seata.spring.annotation.GlobalTransactionalInterceptor# handleGlobalLock(MethodInvocation, GlobalLock) // GlobalLock handler//TCC mode://@see io.seata.rm.tcc.api.LocalTCC // TCC annotation on interface//@see io.seata.rm.tcc.api.TwoPhaseBusinessAction // TCC annotation on try method//@see io.seata.rm.tcc.remoting.RemotingParser // Remote TCC service parser//Corresponding interceptor://@see io.seata.spring.tcc.TccActionInterceptor // the interceptor of TCC mode@Override//由于GlobalTransactionScanner继承了Spring提供的AbstractAutoProxyCreator,//所以Spring会把Spring Bean传递给GlobalTransactionScanner.wrapIfNecessary()进行判断;//让GlobalTransactionScanner来决定是否要根据Bean的Class、或者Bean的方法是否有上述注解,//从而决定是否需要针对Bean的Class创建动态代理,来对添加了注解的方法进行拦截;protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {//do checkersif (!doCheckers(bean, beanName)) {return bean;}try {synchronized (PROXYED_SET) {if (PROXYED_SET.contains(beanName)) {return bean;}interceptor = null;//check TCC proxy//判断传递进来的Bean是否是TCC动态代理//服务启动时会把所有的Bean传递进来这个wrapIfNecessary(),检查这个Bean是否需要创建TCC动态代理if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {//init tcc fence clean task if enable useTccFenceTCCBeanParserUtils.initTccFenceCleanTask(TCCBeanParserUtils.getRemotingDesc(beanName), applicationContext);//TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCCinterceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (ConfigurationChangeListener)interceptor);} else {//获取目标class的接口Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);//existsAnnotation()方法会判断Bean的Class或Method是否添加了@GlobalTransactional等注解if (!existsAnnotation(new Class[]{serviceInterface}) && !existsAnnotation(interfacesIfJdk)) {return bean;}if (globalTransactionalInterceptor == null) {//构建一个GlobalTransactionalInterceptor,即全局事务注解的拦截器globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (ConfigurationChangeListener)globalTransactionalInterceptor);}interceptor = globalTransactionalInterceptor;}LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.getClass().getName());if (!AopUtils.isAopProxy(bean)) {//如果这个Bean并不是AOP代理//接下来会基于Spring的AbstractAutoProxyCreator创建针对目标Bean接口的动态代理//这样后续调用到目标Bean的方法,就会调用到TccActionInterceptor拦截器bean = super.wrapIfNecessary(bean, beanName, cacheKey);} else {AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));int pos;for (Advisor avr : advisor) {//Find the position based on the advisor's order, and add to advisors by pospos = findAddSeataAdvisorPosition(advised, avr);advised.addAdvisor(pos, avr);}}PROXYED_SET.add(beanName);return bean;}} catch (Exception exx) {throw new RuntimeException(exx);}}...
}
(2)TCCBeanParserUtils的isTccAutoProxy()方法判断是否要创建TCC动态代理
public class TCCBeanParserUtils {private TCCBeanParserUtils() {}//is auto proxy TCC beanpublic static boolean isTccAutoProxy(Object bean, String beanName, ApplicationContext applicationContext) {boolean isRemotingBean = parserRemotingServiceInfo(bean, beanName);//get RemotingBean descriptionRemotingDesc remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);//is remoting beanif (isRemotingBean) {if (remotingDesc != null && remotingDesc.getProtocol() == Protocols.IN_JVM) {//LocalTCC//创建一个local tcc代理return isTccProxyTargetBean(remotingDesc);} else {//sofa:reference / dubbo:reference, factory beanreturn false;}} else {if (remotingDesc == null) {//check FactoryBeanif (isRemotingFactoryBean(bean, beanName, applicationContext)) {remotingDesc = DefaultRemotingParser.get().getRemotingBeanDesc(beanName);return isTccProxyTargetBean(remotingDesc);} else {return false;}} else {return isTccProxyTargetBean(remotingDesc);}}}...//is TCC proxy-bean/target-bean: LocalTCC , the proxy bean of sofa:reference/dubbo:referencepublic static boolean isTccProxyTargetBean(RemotingDesc remotingDesc) {if (remotingDesc == null) {return false;}//check if it is TCC beanboolean isTccClazz = false;//针对我们的class拿到一个接口classClass<?> tccInterfaceClazz = remotingDesc.getInterfaceClass();//获取我们的接口里定义的所有的方法Method[] methods = tccInterfaceClazz.getMethods();TwoPhaseBusinessAction twoPhaseBusinessAction;//遍历所有的方法for (Method method : methods) {//获取的方法是否加了@TwoPhaseBusinessAction注解twoPhaseBusinessAction = method.getAnnotation(TwoPhaseBusinessAction.class);if (twoPhaseBusinessAction != null) {isTccClazz = true;break;}}if (!isTccClazz) {return false;}short protocols = remotingDesc.getProtocol();//LocalTCCif (Protocols.IN_JVM == protocols) {//in jvm TCC bean , AOPreturn true;}//sofa:reference / dubbo:reference, AOPreturn remotingDesc.isReference();}...
}
4.Seata TCC案例分布式事务入口分析
TccTransactionService作为分布式事务的入口,其提交事务和回滚事务的接口都会被添加上@GlobalTransactional注解。
所以应用启动时,TccTransactionService的Bean就会被GlobalTransactionScanner扫描,然后其下添加了@GlobalTransactional注解的接口就会被创建动态代理。
在TccTransactionService的提交分布式事务的接口中,会先后调用TccActionOne和TccActionTwo两个Dubbo服务。并且在调用两个Dubbo服务时,会通过ApacheDubboTransactionPropagationFilter传递xid。
public class TccTransactionService {private TccActionOne tccActionOne;private TccActionTwo tccActionTwo;//提交分布式事务@GlobalTransactionalpublic String doTransactionCommit() {//第一个TCC事务参与者boolean result = tccActionOne.prepare(null, 1);if (!result) {throw new RuntimeException("TccActionOne failed.");}List list = new ArrayList();list.add("c1");list.add("c2");//第二个TCC事务参与者result = tccActionTwo.prepare(null, "two", list);if (!result) {throw new RuntimeException("TccActionTwo failed.");}return RootContext.getXID();}//回滚分布式事务@GlobalTransactionalpublic String doTransactionRollback(Map map) {//第一个TCC事务参与者boolean result = tccActionOne.prepare(null, 1);if (!result) {throw new RuntimeException("TccActionOne failed.");}List list = new ArrayList();list.add("c1");list.add("c2");//第二个TCC事务参与者result = tccActionTwo.prepare(null, "two", list);if (!result) {throw new RuntimeException("TccActionTwo failed.");}map.put("xid", RootContext.getXID());throw new RuntimeException("transacton rollback");}public void setTccActionOne(TccActionOne tccActionOne) {this.tccActionOne = tccActionOne;}public void setTccActionTwo(TccActionTwo tccActionTwo) {this.tccActionTwo = tccActionTwo;}
}
相关文章:

Seata源码—7.Seata TCC模式的事务处理一
大纲 1.Seata TCC分布式事务案例配置 2.Seata TCC案例服务提供者启动分析 3.TwoPhaseBusinessAction注解扫描源码 4.Seata TCC案例分布式事务入口分析 5.TCC核心注解扫描与代理创建入口源码 6.TCC动态代理拦截器TccActionInterceptor 7.Action拦截处理器ActionIntercept…...

【语法】C++的map/set
目录 平衡二叉搜索树 set insert() find() erase() swap() map insert() 迭代器 erase() operator[] multiset和multimap 在之前学习的STL中,string,vector,list,deque,array都是序列式容器,它们的…...
【FAQ】HarmonyOS SDK 闭源开放能力 —Live View Kit (3)
1.问题描述: 通过Push Kit创建实况窗之后,再更新实况窗失败,平台查询提示“实况窗端更新失败,通知未创建或已经过期”。 解决方案: 通过Push Kit更新实况窗内容的过程是自动更新的。客户端在创建本地实况窗后&#…...

vue vite textarea标签按下Shift+Enter 换行输入,只按Enter则提交的实现思路
注意input标签不能实现,需要用textarea标签 直接看代码 <template><textareav-model"message"keydown.enter"handleEnter"placeholder"ShiftEnter 换行,Enter 提交"></textarea> </template>&l…...
MySQL多线程备份工具mysqlpump详解!
MySQLPUMP备份工具详解 1. 概述 MySQLPump 是 MySQL 5.7 引入的一个客户端备份工具,用于替代传统的 mysqldump 工具。它提供了并行处理、进度状态显示、更好的压缩支持等新特性,能够更高效地执行 MySQL 数据库备份操作。 2. 主要特性 并行处理&#x…...
创建信任所有证书的HttpClient:Java 实现 HTTPS 接口调用,等效于curl -k
在 Java 生态中,HttpClient 和 Feign 都是调用第三方接口的常用工具,但它们的定位、设计理念和使用场景有显著差异。以下是详细对比: DIFF1. 定位与抽象层级 特性HttpClientFeign层级底层 HTTP 客户端库(处理原始请求/响应&#…...
Redisson分布式集合原理及应用
Redisson是一个用于Redis的Java客户端,它简化了复杂的数据结构和分布式服务的使用。 适用场景对比 数据结构适用场景优点RList消息队列、任务队列、历史记录分布式共享、阻塞操作、分页查询RMap缓存、配置中心、键值关联数据支持键值对、分布式事务、TTLRSet去重集…...

深入理解 PlaNet(Deep Planning Network):基于python从零实现
引言:基于模型的强化学习与潜在动态 基于模型的强化学习(Model-based Reinforcement Learning)旨在通过学习环境动态的模型来提高样本效率。这个模型可以用来进行规划,让智能体在不需要与真实环境进行每一次决策交互的情况下&…...
精益数据分析(75/126):用户反馈的科学解读与试验驱动迭代——Rally的双向验证方法论
精益数据分析(75/126):用户反馈的科学解读与试验驱动迭代——Rally的双向验证方法论 在创业的黏性阶段,用户反馈是优化产品的重要依据,但如何避免被表面反馈误导?如何将反馈转化为可落地的迭代策略&#x…...

仿腾讯会议——视频发送接收
1、 添加音频模块 2、刷新图片,触发重绘 3、 等比例缩放视频帧 4、 新建视频对象 5、在中介者内定义发送视频帧的函数 6、完成发送视频的函数 7、 完成开启/关闭视频 8、绑定视频的信号槽函数 9、 完成开启/关闭视频 10、 完成发送视频 11、 完成刷新图片显示 12、完…...

从3.7V/5V到7.4V,FP6291在应急供电智能门锁中的应用
在智能家居蓬勃发展的当下,智能门锁以其便捷、安全的特性,成为现代家庭安防的重要组成部分。在智能门锁电量耗尽的情况下,应急电源外接移动电源(USB5V输入) FP6291升压到7.4V供电可应急开锁。增强用户在锁具的安全性、…...
java后端-海外登录(谷歌/FaceBook)
前言 由于最近公司的项目要在海外运行,因此需要对接海外的登录,目前就是谷歌和facebook两种,后面支付也是需要的,后续再进行书写 谷歌登录 这个相对比较容易,而且只提供给安卓即可,废话就不多说了,直接贴解决方案 引入maven依赖 <dependency> <groupId>com.go…...

【人工智障生成日记1】从零开始训练本地小语言模型
🎯 从零开始训练本地小语言模型:MiniGPT TinyStories(4090Ti) 🧭 项目背景 本项目旨在以学习为目的,从头构建一个完整的本地语言模型训练管线。目标是: ✅ 不依赖外部云计算✅ 完全本地运行…...

Selenium-Java版(frame切换/窗口切换)
frame切换/窗口切换 前言 切换到frame 原因 解决 切换回原来的主html 切换到新的窗口 问题 解决 回到原窗口 法一 法二 示例 前言 参考教程:Python Selenium Web自动化 2024版 - 自动化测试 爬虫_哔哩哔哩_bilibili 上期文章:Sel…...

一文深度解析:Pump 与 PumpSwap 的协议机制与技术差异
在 Solana 链上,Pump.fun 和其延伸产品 PumpSwap 构成了 meme coin 发行与流通的两大核心场景。从初期的游戏化发行模型,到后续的自动迁移与交易市场,Pump 系列协议正在推动 meme coin 从“爆发性投机”走向“协议化运营”。本文将从底层逻辑…...

星云智控v1.0.0产品发布会圆满举行:以创新技术重构物联网监控新生态
星云智控v1.0.0产品发布会圆满举行:以创新技术重构物联网监控新生态 2024年5月15日,成都双流蛟龙社区党群服务中心迎来了一场备受业界瞩目的发布会——优雅草科技旗下”星云智控v1.0.0”物联网AI智控系统正式发布。本次发布会吸引了包括沃尔沃集团、新希…...

SpringBoot(一)--- Maven基础
目录 前言 一、初始Maven 1.依赖管理 2.项目构建 3.统一项目结构 二、IDEA集成Maven 1.Maven安装 2.创建Maven项目 2.1全局设置 2.2 创建SpringBoot项目 2.3 常见问题 三、单元测试 1.JUnit入门 2.断言 前言 Maven 是一款用于管理和构建Java项目的工具ÿ…...

基于FPGA控制电容阵列与最小反射算法的差分探头优化设计
在现代高速数字系统测试中,差分探头的信号完整性直接影响测量精度。传统探头存在阻抗失配导致的信号反射问题,本文提出一种通过FPGA动态控制电容阵列,结合最小反射算法的优化方案,可实时调整探头等效容抗,将信号反射损…...

kakfa 基本了解
部署结构 Kafka 使用zookeeper来协商和同步,但是kafka 从版本3.5正式开始deprecate zookeeper, 同时推荐使用自带的 kraft. 而从4.0 开始则不再支持 zookeeper。 所以 kafka 是有control plane 和 data plane 的。 data plane 就是broker,control plane…...
基于Browser Use + Playwright 实现AI Agent操作Web UI自动化
Browser Use是什么 Browser Use是一个开源项目官网:Browser Use - Enable AI to control your browser,专为大语言模型(LLM)设计的只能浏览器工具,能够让AI像人类一样自然的浏览和操作网页,支持多标签页管…...

Origin绘制多因子柱状点线图
多因子柱状点线图是一种结合柱状图和点线图的复合图表,常用于同时展示多个因子(变量)在不同分组下的分布和趋势变化。 适用场景: (1)比较多个因子在不同分组中的数值大小(柱状图)&a…...

Web漏洞扫描服务的特点与优势:守护数字时代的安全防线
在数字化浪潮中,Web应用程序的安全性已成为企业业务连续性和用户信任的核心要素。随着网络攻击手段的不断升级,Web漏洞扫描服务作为一种主动防御工具,逐渐成为企业安全体系的标配。本文将从特点与优势两方面,解析其价值与应用场景…...
iOS 直播技术及优化
iOS直播技术的实现和优化涉及多个技术环节,需结合协议选择、编解码方案、播放器技术及性能调优等多方面。 一、核心技术实现 协议选择与传输优化 HLS(HTTP Live Streaming):苹果官方推荐,基于HTTP分片传输,…...

抛弃传统P2P技术,EasyRTC音视频基于WebRTC打造教育/会议/远程巡检等场景实时通信解决方案
一、方案背景 随着网络通信发展,实时音视频需求激增。传统服务器中转方式延迟高、资源消耗大,WebP2P技术由此兴起。EasyRTC作为高性能实时通信平台,集成WebP2P技术,实现低延迟、高效率音视频通信,广泛应用于教育、医疗…...

俄罗斯军总参情报局APT28组织瞄准援乌后勤供应链发起全球网络攻击
2025年5月,由美国、英国、欧盟和北约网络安全与情报机构联合发布的最新网络安全公告披露,俄罗斯军总参情报局(GRU)第85特别服务中心第26165部队(又称APT28、Fancy Bear、Forest Blizzard和BlueDelta)正持续…...

杰发科技AC7801——PWM获取固定脉冲个数
测试通道6 在初始化时候打开通道中断 void PWM1_GenerateFrequency(void) {PWM_CombineChConfig combineChConfig[1]; //组合模式相关结构体PWM_IndependentChConfig independentChConfig[2];//独立模式相关结构体PWM_ModulationConfigType pwmConfig; //PWM模式相关结构体PWM…...

MacBookPro上macOS安装第三方应用报错解决方案:遇到:“无法打开“XXX”,因为无法确定(验证)开发者身份?怎么解决
MacBook Pro 上 macOS 安装第三方应用报错解决方案 —— 彻底搞定「无法打开“XXX”,因为无法确定开发者身份」 适用系统:macOS Catalina 10.15 ~ macOS Sonoma 14.x 适用机型:Intel / Apple Silicon 全系 MacBook Pro 文章目录 **MacBook P…...
MVC和MVVM架构的区别
MVC和MVVM都是前端开发中常用的设计模式,都是为了解决前端开发中的复杂性而设计的,而MVVM模式则是一种基于MVC模式的新模式。 MVC(Model-View-Controller)的三个核心部分:模型、视图、控制器相较于MVVM(Model-View-ViewModel)的三个核心部分…...

RAG(Retrieval-Augmented-Generation)检索增强生成
什么是RAG(检索增强生成)? RAG是一种AI框架,结合传统的数据检索技术和LLM(大语言模型)的优势,通过将外部数据和LLM生成语言技能集合,对LLM的输出进行优化,使输出更准确、…...

黑马点评前端Nginx启动失败问题解决记录
Nginx启动失败问题解决记录 问题描述 在学习黑马Redis课程时,启动黑马点评Nginx前端项目发现: 无法访问8080端口检查Windows端口占用情况(无占用)结论:Nginx服务未成功启动 错误日志分析 在nginx安装目录下的logs…...