Seata客户端的启动过程 学习记录

news2024/11/25 17:23:33

Seata客户端的启动过程

1.自动装配4个配置类

image-20230611160505833

将在SpringBoot启动时往容器中添加4个类

1. 自动配置类 SeataAutoConfiguration

SeataAutoConfiguration将会往容器中添加两个bean

  1. failureHandler 事务处理失败执行器
  2. globalTransactionScanner

failureHandler

failureHandler 实现了 FailureHandler 接口,将在事务处理出现异常时,执行相应阶段失败时的方法,我们可以自定类实现此接口,

在失败时执行对应的方法,比如发送邮件通知。但是要注意的是,自定义的事务失败处理器的beanName必须叫failureHandler,否则GlobalTransactionScanner将不会添加到容器中。

image-20230611161812868

public interface FailureHandler {
    void onBeginFailure(GlobalTransaction tx, Throwable cause);

    void onCommitFailure(GlobalTransaction tx, Throwable cause);

    void onRollbackFailure(GlobalTransaction tx, Throwable originalException);

    void onRollbackRetrying(GlobalTransaction tx, Throwable originalException);

    void onTimeoutRollback(GlobalTransaction tx, Throwable originalException);
}

failureHandler 并且会启动周期任务,去检查事务状态是不是正常完成,否则将继续打印警告信息

 public void onBeginFailure(GlobalTransaction tx, Throwable cause) {
        LOGGER.warn("Failed to begin transaction. ", cause);
    }

    public void onCommitFailure(GlobalTransaction tx, Throwable cause) {
        LOGGER.warn("Failed to commit transaction[" + tx.getXid() + "]", cause);
        this.timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Committed), 10L, TimeUnit.SECONDS);
    }

    public void onRollbackFailure(GlobalTransaction tx, Throwable originalException) {
        LOGGER.warn("Failed to rollback transaction[" + tx.getXid() + "]", originalException);
        this.timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.Rollbacked), 10L, TimeUnit.SECONDS);
    }

    public void onRollbackRetrying(GlobalTransaction tx, Throwable originalException) {
        StackTraceLogger.warn(LOGGER, originalException, "Retrying to rollback transaction[{}]", new String[]{tx.getXid()});
        this.timer.newTimeout(new CheckTimerTask(tx, GlobalStatus.RollbackRetrying), 10L, TimeUnit.SECONDS);
    }

    public void onTimeoutRollback(GlobalTransaction tx, Throwable originalException) {
        StackTraceLogger.warn(LOGGER, originalException, "Transaction timeout rollback[{}]", new String[]{tx.getXid()});
    }

globalTransactionScanner

实现了4个接口,继承了一个父类

image-20230611162354082

  1. AbstractAutoProxyCreator 创建代理类
  2. ConfigurationChangeListener 监听配置文件变更
  3. initializingBean 初始化bean执行回调方法
  4. ApplicationContextAware 获取spring容器
  5. DisposableBean 可丢弃bean

首先看initializingBean 的afterPropertiesSet()回调方法

image-20230611162628536

disableGlobalTransaction 默认是false,走 initClient方法

private void initClient() {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Initializing Global Transaction Clients ... ");
        }
        if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {
            LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, " +
                    "please change your default configuration as soon as possible " +
                    "and we don't recommend you to use default tx-service-group's value provided by seata",
                    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));
        }
        //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(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. ");
        }
        registerSpringShutdownHook();

    }

其中完成了TM与RM的初始化

首先看TM的初始化

image-20230611163340797

创建了TmNettyRemotingClient实列,而TmNettyRemotingClient 的成员变量 instance 则是一个TmNettyRemotingClient 对象,用于客户端与服务端通信的netty客户端,消息处理器为io.seata.core.rpc.netty.AbstractNettyRemotingClient.ClientHandler,同样也是一个双向消息处理器

    @Sharable
    class ClientHandler extends ChannelDuplexHandler {

        @Override
        public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
            if (!(msg instanceof RpcMessage)) {
                return;
            }
            processMessage(ctx, (RpcMessage) msg);
        }

        @Override
        public void channelWritabilityChanged(ChannelHandlerContext ctx) {
            synchronized (lock) {
                if (ctx.channel().isWritable()) {
                    lock.notifyAll();
                }
            }
            ctx.fireChannelWritabilityChanged();
        }
        
        ......
        
        }

调用了TmNettyRemotingClient.init方法

   @Override
    public void init() {
        // registry processor
        registerProcessor();
        if (initialized.compareAndSet(false, true)) {
            super.init();
            if (io.seata.common.util.StringUtils.isNotBlank(transactionServiceGroup)) {
                getClientChannelManager().reconnect(transactionServiceGroup);
            }
        }
    }

registerProcessor() 注册消息处理器,同样封装成Pair放入map中

super.registerProcessor(MessageType.TYPE_SEATA_MERGE_RESULT, onResponseProcessor, null);
        super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN_RESULT, onResponseProcessor, null);
        super.registerProcessor(MessageType.TYPE_GLOBAL_COMMIT_RESULT, onResponseProcessor, null);
        super.registerProcessor(MessageType.TYPE_GLOBAL_REPORT_RESULT, onResponseProcessor, null);
        super.registerProcessor(MessageType.TYPE_GLOBAL_ROLLBACK_RESULT, onResponseProcessor, null);
        super.registerProcessor(MessageType.TYPE_GLOBAL_STATUS_RESULT, onResponseProcessor, null);
        super.registerProcessor(MessageType.TYPE_REG_CLT_RESULT, onResponseProcessor, null);
        super.registerProcessor(MessageType.TYPE_BATCH_RESULT_MSG, onResponseProcessor, null);

getClientChannelManager().reconnect(transactionServiceGroup)与服务端建立连接

void reconnect(String transactionServiceGroup) {
        List<String> availList = null;
        try {
            availList = getAvailServerList(transactionServiceGroup);
        } catch (Exception e) {
            LOGGER.error("Failed to get available servers: {}", e.getMessage(), e);
            return;
        }
        if (CollectionUtils.isEmpty(availList)) {
            RegistryService registryService = RegistryFactory.getInstance();
            String clusterName = registryService.getServiceGroup(transactionServiceGroup);

            if (StringUtils.isBlank(clusterName)) {
                LOGGER.error("can not get cluster name in registry config '{}{}', please make sure registry config correct",
                        ConfigurationKeys.SERVICE_GROUP_MAPPING_PREFIX,
                        transactionServiceGroup);
                return;
            }

            if (!(registryService instanceof FileRegistryServiceImpl)) {
                LOGGER.error("no available service found in cluster '{}', please make sure registry config correct and keep your seata server running", clusterName);
            }
            return;
        }
        Set<String> channelAddress = new HashSet<>(availList.size());
        try {
            for (String serverAddress : availList) {
                try {
                    acquireChannel(serverAddress);
                    channelAddress.add(serverAddress);
                } catch (Exception e) {
                    LOGGER.error("{} can not connect to {} cause:{}", FrameworkErrorCode.NetConnect.getErrCode(),
                        serverAddress, e.getMessage(), e);
                }
            }
        } finally {
            if (CollectionUtils.isNotEmpty(channelAddress)) {
                List<InetSocketAddress> aliveAddress = new ArrayList<>(channelAddress.size());
                for (String address : channelAddress) {
                    String[] array = address.split(":");
                    aliveAddress.add(new InetSocketAddress(array[0], Integer.parseInt(array[1])));
                }
                RegistryFactory.getInstance().refreshAliveLookup(transactionServiceGroup, aliveAddress);
            } else {
                RegistryFactory.getInstance().refreshAliveLookup(transactionServiceGroup, Collections.emptyList());
            }
        }
    }

seata服务收到TM注册消息后,会调用io.seata.core.rpc.processor.server.RegTmProcessor#onRegTmMessage,并给客户端返回注册成功的消息

    private void onRegTmMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) {
        RegisterTMRequest message = (RegisterTMRequest) rpcMessage.getBody();
        String ipAndPort = NetUtil.toStringAddress(ctx.channel().remoteAddress());
        Version.putChannelVersion(ctx.channel(), message.getVersion());
        boolean isSuccess = false;
        String errorInfo = StringUtils.EMPTY;
        try {
            if (null == checkAuthHandler || checkAuthHandler.regTransactionManagerCheckAuth(message)) {
                ChannelManager.registerTMChannel(message, ctx.channel());
                Version.putChannelVersion(ctx.channel(), message.getVersion());
                isSuccess = true;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("TM checkAuth for client:{},vgroup:{},applicationId:{} is OK",
                        ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId());
                }
            } else {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("TM checkAuth for client:{},vgroup:{},applicationId:{} is FAIL",
                            ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId());
                }
            }
        } catch (Exception exx) {
            isSuccess = false;
            errorInfo = exx.getMessage();
            LOGGER.error("TM register fail, error message:{}", errorInfo);
        }
        RegisterTMResponse response = new RegisterTMResponse(isSuccess);
        if (StringUtils.isNotEmpty(errorInfo)) {
            response.setMsg(errorInfo);
        }
        remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), response);
        if (isSuccess && LOGGER.isInfoEnabled()) {
            LOGGER.info("TM register success,message:{},channel:{},client version:{}", message, ctx.channel(),
                message.getVersion());
        }
    }

RM的初始化

    public static void init(String applicationId, String transactionServiceGroup) {
        //实例化RM的netty客户端
        RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);
        //设置RM资源管理
        rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());
        //设置RM事务管理器
        rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());
        rmNettyRemotingClient.init();
    }

ResourceManager 用于管理数据库资源

TransactionMessageHandler用来处理RM事务消息

rmNettyRemotingClient.init()方法中注册消息处理器,需要注意的是,此时并不会与seata服务器建立连接,因为此时还比没有管理数据库资源

    public void init() {
        // registry processor
        registerProcessor();
        if (initialized.compareAndSet(false, true)) {
            super.init();

            // Found one or more resources that were registered before initialization
            if (resourceManager != null
                    && !resourceManager.getManagedResources().isEmpty()
                    && StringUtils.isNotBlank(transactionServiceGroup)) {
                getClientChannelManager().reconnect(transactionServiceGroup);
            }
        }
    }

遍历四种模式下的资源管理器,获取其所管理的资源(此时全为空),因此并不会与seata服务端在此时建立连接。 RM与TC建立连接可以看后面创建代理数据源的过程。RM是用来管理代理数据源,当没有数据源的时候,也没必要建立连接,真正向TC注册RM的时机在初始化代理数据源后。image-20230611173320067

seata服务端io.seata.core.rpc.processor.server.RegRmProcessor#onRegRmMessage 返回RM注册成功消息

    private void onRegRmMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) {
        RegisterRMRequest message = (RegisterRMRequest) rpcMessage.getBody();
        String ipAndPort = NetUtil.toStringAddress(ctx.channel().remoteAddress());
        boolean isSuccess = false;
        String errorInfo = StringUtils.EMPTY;
        try {
            if (null == checkAuthHandler || checkAuthHandler.regResourceManagerCheckAuth(message)) {
                ChannelManager.registerRMChannel(message, ctx.channel());
                Version.putChannelVersion(ctx.channel(), message.getVersion());
                isSuccess = true;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("RM checkAuth for client:{},vgroup:{},applicationId:{} is OK", ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId());
                }
            } else {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("RM checkAuth for client:{},vgroup:{},applicationId:{} is FAIL", ipAndPort, message.getTransactionServiceGroup(), message.getApplicationId());
                }
            }
        } catch (Exception exx) {
            isSuccess = false;
            errorInfo = exx.getMessage();
            LOGGER.error("RM register fail, error message:{}", errorInfo);
        }
        RegisterRMResponse response = new RegisterRMResponse(isSuccess);
        if (StringUtils.isNotEmpty(errorInfo)) {
            response.setMsg(errorInfo);
        }
        remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), response);
        if (isSuccess && LOGGER.isInfoEnabled()) {
            LOGGER.info("RM register success,message:{},channel:{},client version:{}", message, ctx.channel(),
                message.getVersion());
        }
    }

2.自动配置类 SeataDataSourceAutoConfiguration

此自动配置类只会往容器中添加一个Bean :SeataAutoDataSourceProxyCreator,来自动创建数据源代理对象

   @Bean({"seataAutoDataSourceProxyCreator"})
    @ConditionalOnMissingBean({SeataAutoDataSourceProxyCreator.class})
    public static SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) {
        return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(), seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
    }

SeataDataSourceAutoConfiguration继承org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator 抽象类,并且重写了父类的wrapIfNecessary方法

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
				//判断当前bean是否是数据源类,不是不需要代理,直接返回
        if (!(bean instanceof DataSource)) {
            return bean;
        } else {
            DataSource origin;
            //判断是否是数据源代理类,如果是,则不需要er 
            if (!(bean instanceof SeataDataSourceProxy)) {
            		//获取缓存代理
                Object enhancer = super.wrapIfNecessary(bean, beanName, cacheKey);
                //如果当前bean就是缓存代理,直接返回
                if (bean == enhancer) {
                    return bean;
                } else {
                		//创建代理数据源
                    origin = (DataSource)bean;
                    SeataDataSourceProxy proxy = this.buildProxy(origin, this.dataSourceProxyMode);
                    DataSourceProxyHolder.put(origin, proxy);
                    return enhancer;
                }
            } else {
                LOGGER.warn("Manually register SeataDataSourceProxy(or its subclass) bean is discouraged! bean name: {}", beanName);
                SeataDataSourceProxy proxy = (SeataDataSourceProxy)bean;
                origin = proxy.getTargetDataSource();
                Object originEnhancer = super.wrapIfNecessary(origin, beanName, cacheKey);
                if (origin == originEnhancer) {
                    return origin;
                } else {
                    DataSourceProxyHolder.put(origin, proxy);
                    return originEnhancer;
                }
            }
        }
    }

调用buildProxy方法将当前数据源封装到DataSourceProxy对象中

    SeataDataSourceProxy buildProxy(DataSource origin, String proxyMode) {
        if (BranchType.AT.name().equalsIgnoreCase(proxyMode)) {
            return new DataSourceProxy(origin);
        } else if (BranchType.XA.name().equalsIgnoreCase(proxyMode)) {
            return new DataSourceProxyXA(origin);
        } else {
            throw new IllegalArgumentException("Unknown dataSourceProxyMode: " + proxyMode);
        }
    }

并且跟进 构造,找到init方法

 private void init(DataSource dataSource, String resourceGroupId) {
        this.resourceGroupId = resourceGroupId;

        try {
            Connection connection = dataSource.getConnection();
            Throwable var4 = null;

            try {
                this.jdbcUrl = connection.getMetaData().getURL();
                this.dbType = JdbcUtils.getDbType(this.jdbcUrl);
                if ("oracle".equals(this.dbType)) {
                    this.userName = connection.getMetaData().getUserName();
                } else if ("mariadb".equals(this.dbType)) {
                    this.dbType = "mysql";
                }

                this.version = this.selectDbVersion(connection);
            } catch (Throwable var14) {
                var4 = var14;
                throw var14;
            } finally {
                if (connection != null) {
                    if (var4 != null) {
                        try {
                            connection.close();
                        } catch (Throwable var13) {
                            var4.addSuppressed(var13);
                        }
                    } else {
                        connection.close();
                    }
                }

            }
        } catch (SQLException var16) {
            throw new IllegalStateException("can not init dataSource", var16);
        }

        this.initResourceId();
        //向TC注册RM
        DefaultResourceManager.get().registerResource(this);
        if (ENABLE_TABLE_META_CHECKER_ENABLE) {
            this.tableMetaExecutor.scheduleAtFixedRate(() -> {
                try {
                    Connection connection = dataSource.getConnection();
                    Throwable var3 = null;

                    try {
                        TableMetaCacheFactory.getTableMetaCache(this.getDbType()).refresh(connection, this.getResourceId());
                    } catch (Throwable var13) {
                        var3 = var13;
                        throw var13;
                    } finally {
                        if (connection != null) {
                            if (var3 != null) {
                                try {
                                    connection.close();
                                } catch (Throwable var12) {
                                    var3.addSuppressed(var12);
                                }
                            } else {
                                connection.close();
                            }
                        }

                    }
                } catch (Exception var15) {
                }

            }, 0L, TABLE_META_CHECKER_INTERVAL, TimeUnit.MILLISECONDS);
        }

        RootContext.setDefaultBranchType(this.getBranchType());
    }

可以看到此时完成了RM向TC的注册

3.自动配置类 SeataHttpAutoConfiguration

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnWebApplication
@ConditionalOnMissingBean({SeataWebMvcConfigurer.class})
@ConditionalOnProperty(
    prefix = "seata.client.http",
    name = {"interceptor-enabled"},
    havingValue = "true",
    matchIfMissing = true
)
@AutoConfigureOrder(Integer.MAX_VALUE)
public class SeataHttpAutoConfiguration {
    public SeataHttpAutoConfiguration() {
    }

    @Bean
    @ConditionalOnClass(
        name = {"jakarta.servlet.http.HttpServletRequest"}
    )
    public JakartaSeataWebMvcConfigurer jakartaSeataWebMvcConfigurer() {
        return new JakartaSeataWebMvcConfigurer();
    }

    @Bean
    @ConditionalOnMissingBean({JakartaSeataWebMvcConfigurer.class})
    public SeataWebMvcConfigurer seataWebMvcConfigurer() {
        return new SeataWebMvcConfigurer();
    }
}
public class SeataWebMvcConfigurer implements WebMvcConfigurerAdapter {
    public SeataWebMvcConfigurer() {
    }

    public void addInterceptors(InterceptorRegistry registry) {
    		//添加拦截器
        registry.addInterceptor(new TransactionPropagationInterceptor());
    }
}

主要是为了在传递xid

image-20230611203840479

4.自动配置类SeataSagaAutoConfiguration

SeataSagaAutoConfiguration主要是为了出来saga模式下的分布式事务,基本不用跳过。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/643562.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

03 表达关系的术语

文章目录 表达关系的术语关联关联的语义表达(6点) 泛化细化(也称为实现)依赖例题 UML 基本关系的一般用法① 模型化简单依赖②模型化单继承③模型化结构关系例题 表达关系的术语 关联 定义&#xff1a;关联是类目之间的结构关系&#xff0c;描述了一组具有相同结构、相同语义…

【MySQL数据库一】MySQL数据库初体验

MySQL数据库初体验 1.数据库基本概念1.1 数据Data1.2 表1.3 数据库1.4 数据库管理系统1.5 数据库系统 2.数据库的发展3.主流的数据库介绍3.1 SQL Server&#xff08;微软公司产品&#xff09;3.2 Oracle &#xff08;甲骨文公司产品&#xff09;3.3 DB2&#xff08;IBM公司产品…

安卓高通机型的基带移植 修改 编译的相关 增加信号 支持5G等【二】

安卓高通机型的基带移植 修改 编译的相关 增加信号 支持5G等【一】 前面分享了这篇帖子&#xff0c;很多友友希望更新下新机型的基带替换方法。今天对其中做一些补充说明。由于安卓机型跨版本幅度较大。有的机型从出厂安卓8有可能官方目前已经更新到安卓12 13等等。所以任何的教…

每天五分钟机器学习:如何确定梯度下降中的学习率?

本文重点 学习率是梯度下降算法中的一个重要参数,它控制着每次迭代中参数的更新幅度,因此学习率的大小直接影响着算法的收敛速度和精度。在实际应用中,如何选择合适的学习率是一个非常重要的问题。 手动调整法 最简单的方法是手动调整学习率。我们可以根据经验或者试错的…

sealos 五年磨一剑——云操作系统正式发布

这是计划的一部分 这是一个宏伟的计划&#xff0c;漫长且有趣。。。 2018 年的一个晚上&#xff0c;我写下了 sealos 第一行代码&#xff0c;当时仓库命名 “kubeinit”&#xff0c;后一想格局太小&#xff0c;我不可能只做一个安装 kubernetes 的事&#xff0c;安装只是计划…

不知道测试什么?这些是你需要知道的软件测试类型和常识

有多少软件测试类型呢? 我们作为测试人员了解很多种不同的软件测试类型&#xff0c;例如功能测试(Functional Test)、非功能测试、自动测试、敏捷测试、以及它们的各种子类型. 尽管在我们的测试过程中会接触很多种测试类型, 或者听说过某些测试类型&#xff0c;但是很少人敢说…

小姐姐们,一起学数据分析

&#x1f496; 亲爱的女生们&#xff0c;听我说&#xff01;数据分析&#xff0c;给你的未来注入浪漫与激情&#xff01;&#x1f496; 嗨&#xff0c;小姐姐们&#x1f46d;&#xff0c; 我想和你们分享一个令人兴奋的话题——数据分析&#xff01;或许你们会觉…

Liunx系统重修

Liunx中以“树”结构的形式来管理文件和文件夹的&#xff01;

ISP下载原理分析

STM32的启动方式&#xff0c;系统存储器启动就是通过ISP下载 ISP简介 ISP下载是指可以通过串行外设&#xff0c;直接将程序下载Flash中&#xff0c;然后自动运行程序的启动方式。 ISP的时候需要用到bootloder(自举程序)&#xff0c;bootloader存储在STM32内部的自举ROM存储器…

webpack踩坑

因为电脑重新装了系统 所以很多环境都需要配置 1.首先去node.js官网下载了node&#xff0c;node下载后对应的npm版本也会自动下载好 2.想要成功跑通项目&#xff0c;还需要下载webpack 全局安装webpack npm -i webpack -Dnpm install webpack-cli -g但一直在报错 查看webpac…

基于Java毕业生就业信息统计系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

2023互联网Java高级工程师1080道面试题(附答案)分享

Java 面试八股文有必要背吗&#xff1f; 我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。现如今&#xff0c;Java 面试的本质就是八股文&#xff0c;把八股文面试题背好&#xff0c;面试才有可能表现好。…

python字符串格式化控制数值经度

python字符串格式化通过占位符拼接演示了字符串格式化拼接的方法 但是大家应该有注意到 我们的 8.70 就变成了 8.700000 那么 我们就需要对浮点数 做一个经度控制 我们先编写如下代码 dom1 110; dom2 1234.1234567; print(dom1) print(dom2)运行结果如下 我们定义了一个整…

【RuoYi-Cloud-Plus】学习笔记 07 - Sentinel(二)Node ProcessorSlotChain 总览与知识整理

文章目录 前言参考目录版本说明学习笔记1、Sentinel 架构图2、Node2.1、入口节点 EntranceNode2.2、链路节点 DefaultNode2.3、簇点 ClusterNode2.4、统计节点 StatisticNode3、Slot Chain3.1、DefaultSlotChainBuilder3.1.1、Slot 排序的实现3.1.2、SPI 文件读取3.2、NodeSele…

Java并发工具之ThreadLocal

一、ThreadLocal 简介 1. ThreadLocal 是什么&#xff1f; ThreadLocal 字面意思是本地线程&#xff0c;其实更准确来说是线程局部变量&#xff0c;线程类 Thread 有个变量叫做 threadLocals&#xff0c;其类型就是ThreadLocal.ThreadLocalMap 类型&#xff0c;他其实不是一个…

回忆童年游戏,完美利用Python制作飞机大战(爷青回~)

名字&#xff1a;阿玥的小东东 学习&#xff1a;python、C/C 博客链接&#xff1a;阿玥的小东东的博客_CSDN博客-python&&c高级知识,过年必备,C/C知识讲解领域博主 目录 pygame包的安装 添加python环境变量 创建飞机大战窗口 飞机照片 将变量及函数抽取出来 添加多…

LeetCode:31. 下一个排列

31. 下一个排列 1&#xff09;题目2&#xff09;思路3&#xff09;代码4&#xff09;结果 1&#xff09;题目 整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。 例如&#xff0c;arr [1,2,3] &#xff0c;以下这些都可以视作 arr 的排列&#xff1a;[1,2,3]、[1…

Nacos负载均衡策略

文章目录 按权重分配流量负载均衡自定义负载均衡策略 按权重分配流量负载均衡 SpringCloud新版本&#xff08;2021.x.x&#xff09;中负载均衡器用LoadBalancer替代了Ribbon&#xff0c;默认只提供了2种负载均衡策略&#xff1a;RandomLoadBalancer 和 RoundRobinLoadBalancer。…

[冷冻电镜]IMOD使用指南

参考教程&#xff1a; Etomo Tuturial for IMOD version 4.11 1. Initial Setup 本教程提供了一个小双轴示例数据集和Etomo的分布指南&#xff0c;更详细的内容参考Tomography Guide。该版本使用1k*1k的图像而不是压缩版本。imodhelp命令可以打开帮助界面&#xff0c;查看各种…

java并发之CAS(Compare and swap)

1. 简介 CAS的底层调用native方法&#xff0c;最终是利用CPU的一个特殊指令&#xff0c;该指令由CPU保证了原子性&#xff0c;而且包含多个操作&#xff0c;比如先比较再更新。 原理&#xff1a; &#xff08;1&#xff09;需要读写的内存值&#xff08;V&#xff09;、原值…