Seata解析-数据源代理DataSourceProxy详解

2023-11-01

本文基于seata 1.3.0版本

前面通过十多篇文章详细介绍了TC端。从这篇文章开始介绍RM。
RM是资源管理器,资源指的就是数据库,RM主要与分支事务有关。RM会处理业务数据。
在《Seata解析-seata部署启动初体验》中,使用了类DataSourceProxy创建数据源代理。这里DataSourceProxy代理的就是业务数据库的数据源。因此本文从DataSourceProxy开始,开启分析RM的旅程。
下图是DataSourceProxy的继承结构:
在这里插入图片描述
DataSourceProxy实现了Resource接口,说明DataSourceProxy自身也是一种资源,可以被资源管理器ResourceManager管理。抽象类AbstractDataSourceProxy对DataSource提供了一部分很简单的方法实现,重要方法都是在DataSourceProxy中实现的。
下面具体看一下DataSourceProxy中方法实现。

一、构造方法实现

DataSourceProxy提供了两个构造方法:

	public DataSourceProxy(DataSource targetDataSource) {
        this(targetDataSource, DEFAULT_RESOURCE_GROUP_ID);
    }
	//第二个参数在1.3.0版本里面没有使用,或者说设置这个值没有意义
	//所以这里使用默认值即可。
    public DataSourceProxy(DataSource targetDataSource, String resourceGroupId) {
        super(targetDataSource);
        init(targetDataSource, resourceGroupId);
    }
    private void init(DataSource dataSource, String resourceGroupId) {
        this.resourceGroupId = resourceGroupId;
        try (Connection connection = dataSource.getConnection()) {
            //jdbcUrl是我们自己配置的数据库连接
            jdbcUrl = connection.getMetaData().getURL();
            //从url中分析当前使用的是什么数据库,可能是oracle、mysql等
            //默认使用druid中的JdbcUtils.getDbType()分析
            //分析规则很简单,通过对url前缀匹配得到,比如mysql数据库连接前缀是“jdbc:mysql:”
            dbType = JdbcUtils.getDbType(jdbcUrl);
            if (JdbcConstants.ORACLE.equals(dbType)) {
                userName = connection.getMetaData().getUserName();
            }
        } catch (SQLException e) {
            throw new IllegalStateException("can not init dataSource", e);
        }
        //资源管理器:DefaultResourceManager
        //DataSourceProxy实现了Resource接口,因此本类就是一个资源
        //下面的代码向资源管理器管理注册本类
        DefaultResourceManager.get().registerResource(this);
        //ENABLE_TABLE_META_CHECKER_ENABLE的作用:是否开启定时任务,用于定时将表结构缓存在本地内存
        //默认1分钟运行一次。
        //缓存的表结构在RM保存记录快照时使用,如果内存中没有缓存,会实时查询数据库。
        if (ENABLE_TABLE_META_CHECKER_ENABLE) {
            tableMetaExcutor.scheduleAtFixedRate(() -> {
                try (Connection connection = dataSource.getConnection()) {
                    TableMetaCacheFactory.getTableMetaCache(DataSourceProxy.this.getDbType())
                        .refresh(connection, DataSourceProxy.this.getResourceId());
                } catch (Exception ignore) {
                }
            }, 0, TABLE_META_CHECKER_INTERVAL, TimeUnit.MILLISECONDS);
        }
    }

init()是DataSourceProxy的一个重要方法,其主要完成下面三个任务:

  1. 判断使用的是什么数据库,比如mysql,oracle;
  2. 向资源管理器注册DataSourceProxy,之所以可以注册,是因为DataSourceProxy实现了Resource接口,注册的主要工作是找到与事务分组对应的TC集群,并与集群中的每台机器建立连接;
  3. 判断是否启动定时任务,定时任务的作用是缓存数据库表结构,表结构在RM保存数据快照的时候使用。

第三个任务在介绍RM保存快照的时候在详细说明。下面重点看一下第二个任务,也就是下面这行代码所做的事情:

//将DataSourceProxy注册到默认资源管理器DefaultResourceManager中
DefaultResourceManager.get().registerResource(this);

上面代码调用了DefaultResourceManager的registerResource方法:

	//入参是DataSourceProxy对象
	public void registerResource(Resource resource) {
        //resource.getBranchType()返回AT,
        //从这里可以看出DataSourceProxy只适用于AT模式
        //getResourceManager()返回的是DataSourceManager,
        //DataSourceManager也是用于AT模式下
        getResourceManager(resource.getBranchType()).registerResource(resource);
    }

DefaultResourceManager相当于一个路由类,它有一个Map属性resourceManagers,里面保存了每种模式对应的资源管理器,我们使用的是AT模式,因此getResourceManager方法从resourceManagers中取出AT模式的资源管理器,也就是DataSourceManager对象,然后调用DataSourceManager的registerResource方法。

	public void registerResource(Resource resource) {
        DataSourceProxy dataSourceProxy = (DataSourceProxy) resource;
        //dataSourceProxy.getResourceId()返回的是我们在程序中设置的数据库连接,
        //不过如果连接中有“?”,它会把问号后面的内容去掉
        //比如:在应用程序中设置数据库连接为jdbc:mysql://localhost:3306/test?characterEncoding=utf8
        //dataSourceProxy.getResourceId()实际返回jdbc:mysql://localhost:3306/test
        dataSourceCache.put(dataSourceProxy.getResourceId(), dataSourceProxy);
        super.registerResource(dataSourceProxy);
    }

上面代码的最后调用父类的registerResource方法:

	public void registerResource(Resource resource) {
        //向TC注册资源
        //下面代码首先获得RmNettyRemotingClient实例,
        //这里获取时,实例其实已经创建完毕,创建是在另一个初始化过程中完成的
        //后面的文章详细介绍这个过程
        //现在只需要知道RmNettyRemotingClient已经启动客户端Netty,
        //并且创建了连接池,不过连接池中还没有连接
        RmNettyRemotingClient.getInstance().registerResource(resource.getResourceGroupId(), resource.getResourceId());
    }

上面方法最后又去调用实例RmNettyRemotingClient的registerResource方法:

	public void registerResource(String resourceGroupId, String resourceId) {
        //启动的时候,因为RM还没有建立与服务端的连接,所以下面的if判断是true
        //getClientChannelManager()返回NettyClientChannelManager对象,
        //NettyClientChannelManager就是上面方法提到的连接池,它管理与TC的连接,
        //该连接池是在RmNettyRemotingClient的构造方法中创建的,但是创建时不会建立与TC的连接
        if (getClientChannelManager().getChannels().isEmpty()) {
        	//下面reconnect方法的入参是分组事务名,也就是配置文件中spring.cloud.alibaba.seata.tx-service-group的值
        	//reconnect方法用于创建与TC的连接
            getClientChannelManager().reconnect(transactionServiceGroup);
            return;
        }
        synchronized (getClientChannelManager().getChannels()) {
            for (Map.Entry<String, Channel> entry : getClientChannelManager().getChannels().entrySet()) {
                String serverAddress = entry.getKey();
                Channel rmChannel = entry.getValue();
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("will register resourceId:{}", resourceId);
                }
                sendRegisterMessage(serverAddress, rmChannel, resourceId);
            }
        }
    }

NettyClientChannelManager的reconnect方法根据事务分组从注册中心找到提供服务的TC集群,并获得集群中每台机器的地址,接着创建与每台机器的连接。下面具体看一下代码:

    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;
        }
        //如果集群中没有机器提供服务,那么打印出日志,seata有一个定时任务,
        //每过一段时间会重新查看集群中是否有机器
        if (CollectionUtils.isEmpty(availList)) {
            String serviceGroup = RegistryFactory.getInstance()
                                                 .getServiceGroup(transactionServiceGroup);
            LOGGER.error("no available service '{}' found, please make sure registry config correct", serviceGroup);
            return;
        }
        //遍历每台机器
        for (String serverAddress : availList) {
            try {
                //建立与TC的连接
                acquireChannel(serverAddress);
            } catch (Exception e) {
                LOGGER.error("{} can not connect to {} cause:{}",FrameworkErrorCode.NetConnect.getErrCode(), serverAddress, e.getMessage(), e);
            }
        }
    }
    private List<String> getAvailServerList(String transactionServiceGroup) throws Exception {
        //根据服务分组名首先获得集群名,然后根据集群名查询得到提供服务的TC端机器列表
        //seata允许TC端多机部署,将TC端的多台机器分为一个集群,并给集群一个名字,一个服务分组对应一个集群
        List<InetSocketAddress> availInetSocketAddressList = RegistryFactory.getInstance()
                                                                            .lookup(transactionServiceGroup);
        if (CollectionUtils.isEmpty(availInetSocketAddressList)) {
            return Collections.emptyList();
        }
        //将availInetSocketAddressList中地址转化为IP:PORT的形式
        return availInetSocketAddressList.stream()
                                         .map(NetUtil::toStringAddress)
                                         .collect(Collectors.toList());
    }
    Channel acquireChannel(String serverAddress) {
        //channels是一个Map<String, Channel>对象,通过该属性可以看出,每个TC端,RM只维持一个连接
        //在RM启动的时候,channels里面没有任何连接,所以channelToServer=null
        Channel channelToServer = channels.get(serverAddress);
        if (channelToServer != null) {
            //getExistAliveChannel()检查当前连接是否可用,如果不可用返回null
            channelToServer = getExistAliveChannel(channelToServer, serverAddress);
            if (channelToServer != null) {
                return channelToServer;
            }
        }
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("will connect to " + serverAddress);
        }
        channelLocks.putIfAbsent(serverAddress, new Object());
        //这个位置做了并发控制,每次只能一个线程创建与TC的连接
        synchronized (channelLocks.get(serverAddress)) {
            return doConnect(serverAddress);
        }
    }
    private Channel doConnect(String serverAddress) {
        Channel channelToServer = channels.get(serverAddress);
        if (channelToServer != null && channelToServer.isActive()) {
            return channelToServer;
        }
        Channel channelFromPool;
        try {
            //poolKeyFunction.apply调用的是RmNettyRemotingClient.getPoolKeyFunction()方法
            //该方法创建RegisterRMRequest对象和NettyPoolKey对象,
            //RegisterRMRequest对象在建立与TC连接后,会把该对象发送到TC进行注册
            NettyPoolKey currentPoolKey = poolKeyFunction.apply(serverAddress);
            NettyPoolKey previousPoolKey = poolKeyMap.putIfAbsent(serverAddress, currentPoolKey);
            if (previousPoolKey != null && previousPoolKey.getMessage() instanceof RegisterRMRequest) {
                RegisterRMRequest registerRMRequest = (RegisterRMRequest) currentPoolKey.getMessage();
                ((RegisterRMRequest) previousPoolKey.getMessage()).setResourceIds(registerRMRequest.getResourceIds());
            }
            //调用NettyPoolableFactory的makeObject方法创建与TC的连接
            channelFromPool = nettyClientKeyPool.borrowObject(poolKeyMap.get(serverAddress));
            channels.put(serverAddress, channelFromPool);
        } catch (Exception exx) {
            LOGGER.error("{} register RM failed.",FrameworkErrorCode.RegisterRM.getErrCode(), exx);
            throw new FrameworkException("can not register RM,err:" + exx.getMessage());
        }
        return channelFromPool;
    }

上面的代码根据事务分组查找机器的逻辑是:首先从file.conf文件查找“service.vgroupMapping.事务分组”的配置,该配置就是TC集群的名字,如果注册中心使用的是zk,该配置也是zk上的路径,所以接下来,seata访问zk,查找路径:/registry/zk/TC集群名下的value,这个value就是机器列表。得到TC的机器列表后,下面就要与TC建立连接,建立连接其实是委托给连接池去完成了,这里不介绍如何委托过去的,有兴趣的可以看一下Apache的GenericKeyedObjectPool。连接池创建连接时最终是调用的NettyPoolableFactory的makeObject方法:

	public Channel makeObject(NettyPoolKey key) {
        InetSocketAddress address = NetUtil.toInetSocketAddress(key.getAddress());
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("NettyPool create channel to " + key);
        }
        //通过Netty的客户端获得与TC的连接
        Channel tmpChannel = clientBootstrap.getNewChannel(address);
        long start = System.currentTimeMillis();
        Object response;
        Channel channelToServer = null;
        if (key.getMessage() == null) {
            throw new FrameworkException("register msg is null, role:" + key.getTransactionRole().name());
        }
        try {
            //向TC发送RegisterRMRequest请求
            //也就是向TC注册RM
            response = rpcRemotingClient.sendSyncRequest(tmpChannel, key.getMessage());
            if (!isRegisterSuccess(response, key.getTransactionRole())) {
                //如果TC返回失败,下面的方法会构建失败信息,并抛出异常
                rpcRemotingClient.onRegisterMsgFail(key.getAddress(), tmpChannel, response, key.getMessage());
            } else {
                //如果成功,则将与TC的连接注册到NettyClientChannelManager的channels属性中
                channelToServer = tmpChannel;
                rpcRemotingClient.onRegisterMsgSuccess(key.getAddress(), tmpChannel, response, key.getMessage());
            }
        } catch (Exception exx) {
            if (tmpChannel != null) {
                tmpChannel.close();
            }
            throw new FrameworkException(
                "register " + key.getTransactionRole().name() + " error, errMsg:" + exx.getMessage());
        }
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("register success, cost " + (System.currentTimeMillis() - start) + " ms, version:" + getVersion(
                response, key.getTransactionRole()) + ",role:" + key.getTransactionRole().name() + ",channel:"
                + channelToServer);
        }
        return channelToServer;
    }

到这里DataSourceProxy的注册流程全部结束,可以看到注册最终是建立与TC的连接,然后发送RegisterRMRequest请求注册RM。
注册的流程相对还是比较复杂的。
最后在说一点是如何定义TC属于哪个集群?
在TC的register.conf文件中,在配置registry下的属性时,可以看到部分注册中心有cluster的设置,这个cluster就表示了当前TC实例属于哪个集群。TC启动后也会在注册中心对应的目录下添加自己本机的IP地址。这样RM就可以从注册中心找到TC了。

二、getConnection()方法

DataSourceProxy中除了init方法之外,我们最后再看一下getConnection()方法:

	public ConnectionProxy getConnection() throws SQLException {
        Connection targetConnection = targetDataSource.getConnection();
        return new ConnectionProxy(this, targetConnection);
    }
    @Override
    public ConnectionProxy getConnection(String username, String password) throws SQLException {
        Connection targetConnection = targetDataSource.getConnection(username, password);
        return new ConnectionProxy(this, targetConnection);
    }

DataSourceProxy对getConnection重载了,getConnection的作用是返回数据库连接,从上面代码可以看出seata使用ConnectionProxy对真实的数据库连接进行了代理,所以上层应用使用的都是连接的代理对象。关于代理对象后面的文章会详细介绍。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Seata解析-数据源代理DataSourceProxy详解 的相关文章

  • 使用 Android 发送 HTTP Post 请求

    我一直在尝试从 SO 和其他网站上的大量示例中学习 但我无法弄清楚为什么我编写的示例不起作用 我正在构建一个小型概念验证应用程序 它可以识别语音并将其 文本 作为 POST 请求发送到 node js 服务器 我已确认语音识别有效 并且服务
  • Final字段的线程安全

    假设我有一个 JavaBeanUser这是从另一个线程更新的 如下所示 public class A private final User user public A User user this user user public void
  • 加速代码 - 3D 数组

    我正在尝试提高我编写的一些代码的速度 我想知道从 3d 整数数组访问数据的效率如何 我有一个数组 int cube new int 10 10 10 我用价值观填充其中 然后我访问这些值数千次 我想知道 由于理论上所有 3d 数组都存储在内
  • Spark 1.3.1 上的 Apache Phoenix(4.3.1 和 4.4.0-HBase-0.98)ClassNotFoundException

    我正在尝试通过 Spark 连接到 Phoenix 并且在通过 JDBC 驱动程序打开连接时不断收到以下异常 为简洁起见 下面是完整的堆栈跟踪 Caused by java lang ClassNotFoundException org a
  • 列出jshell中所有活动的方法

    是否有任何命令可以打印当前 jshell 会话中所有新创建的方法 类似的东西 list但仅适用于方法 您正在寻找命令 methods all 它会打印所有方法 包括启动 JShell 时添加的方法 以及失败 被覆盖或删除的方法 对于您声明的
  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • 如何为俚语和表情符号构建正则表达式 (regex)

    我需要构建一个正则表达式来匹配俚语 即 lol lmao imo 等 和表情符号 即 P 等 我按照以下示例进行操作http www coderanch com t 497238 java java Regular Expression D
  • 在 postgres 查询中使用列表

    我有一个动态列表 list a b c d 所以长度可能会改变 我想在查询中比较这些列表值 select from student where name in all the list values 我想将列表值传递到此查询中 我怎样才能做
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • 如何将 pfx 文件转换为 jks,然后通过使用 wsdl 生成的类来使用它来签署传出的肥皂请求

    我正在寻找一个代码示例 该示例演示如何使用 PFX 证书通过 SSL 访问安全 Web 服务 我有证书及其密码 我首先使用下面提到的命令创建一个 KeyStore 实例 keytool importkeystore destkeystore
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • 如何在NiFi中映射流文件中的列数据?

    我有 csv 文件 其结构如下 Alfreds Centro Ernst Island Bacchus Germany Mexico Austria UK Canada 01 02 03 04 05 现在我必须将这些数据移入数据库 如下所示
  • 总是使用 Final?

    我读过 将某些东西做成最终的 然后在循环中使用它会带来更好的性能 但这对一切都有好处吗 我有很多地方没有循环 但我将 Final 添加到局部变量中 它会使速度变慢还是仍然很好 还有一些地方我有一个全局变量final 例如android Pa
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • Google App Engine 如何预编译 Java?

    App Engine 对应用程序的 Java 字节码使用 预编译 过程 以增强应用程序在 Java 运行时环境中的性能 预编译代码的功能与原始字节码相同 有没有详细的信息这是做什么的 我在一个中找到了这个谷歌群组消息 http groups
  • 在mockito中使用when进行模拟ContextLoader.getCurrentWebApplicationContext()调用。我该怎么做?

    我试图在使用 mockito 时模拟 ContextLoader getCurrentWebApplicationContext 调用 但它无法模拟 here is my source code Mock org springframewo
  • SQL 国家字符 (NCHAR) 数据类型的真正用途是什么?

    也CHAR CHARACTER and VARCHAR CHARACTER VARYING SQL 提供了NCHAR NATIONAL CHARACTER and NVARCHAR NATIONAL CHARACTER VARYING 类型
  • 玩!框架:运行“h2-browser”可以运行,但网页不可用

    当我运行命令时activator h2 browser它会使用以下 url 打开浏览器 192 168 1 17 8082 但我得到 使用 Chrome 此网页无法使用 奇怪的是它以前确实有效 从那时起我唯一改变的是JAVA OPTS以启用
  • 如何修复 JNLP 应用程序中的“缺少代码库、权限和应用程序名称清单属性”?

    随着最近的 Java 更新 许多人都遇到了缺少 Java Web Start 应用程序的问题Codebase Permissions and Application name体现属性 尽管有资源可以帮助您完成此任务 但我找不到任何资源综合的
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview

随机推荐

  • IDEA中POM 项目parent中的dependencyManagement中的依赖版本号报红

    现象 IDEA中作为管理依赖的parent项目的pom文件中 在dependencyManagement中的dependency 如果指定的版本在本地仓库不存在 并且在子项目中也未引用的时候 会报红 疑惑 只是引用了很常见的依赖 并且版本官
  • 如何编写一个含有抄底信号的副图指标

    如果你作为通达信软件源代码的程序维护员 如何编写一个含有抄底提示的副图指标 请看下面的的示例教程 python语言 python 导入所需的库 import talib 计算移动平均线 def moving average data per
  • 【哈佛积极心理学笔记】第6讲 乐观主义

    第6讲 乐观主义 How can we create consciously and subconsciously a positive environment where we actually can take out the most
  • 小白学习一周 Linux命令

    文件系统管理相关命令 clear 清屏 pwd 打印当前工作目录 tmp 打开文件夹 cd 改变当前工作目录 mkdir 创建一个新文件夹 mkdir 在根目录下创建一个新文件夹 mkdir p 套娃创建文件夹 rmdir 删除当前目录下的
  • 图像数据处理 pytorch

    coding utf 8 Transfer Learning Tutorial Author Sasank Chilamkurthy
  • 双非计算机学硕报录比竟然有28:1?深圳大学20考研居然如此爆炸!

    深圳大学是一所双非大学 计算机学科评估B 软件工程学科评估没有 由于计算机实力在双非中很强 而且地处广东深圳 是信息行业和互联网行业比较发达的地区 因此深圳大学很受考生欢迎 但是深圳大学也很难考 深圳大学基本所有计算机相关专业都考408 这
  • 【Git】(一)基本操作

    读完本文后 您会了解 1 如何在本地配置GIT环境 2 环境配置成功后 如何从远端下载一个已有仓库到本地 1 配置全局用户名 邮箱 git config global user name username git config global
  • LeetCode 0198. House Robber

    问题简析 作为职业小偷 我要去打家劫舍 但是注意如果两家相邻房子在同一夜被打劫了 则会触发警报 现在给定一个非负整数构成的数列 代表连续的若干房屋中的财产数量 计算一晚上最多能偷多少钱 例如 nums 1 2 3 1 最大值为1 3 4 n
  • 论文End To End speech里一种seq2seq

    在End To End speech里介绍了一种更好的seq2seq的模型 效果我没有实际比较过 但是思路值得学习 接下来分享下 我的理解 虽然这篇论文讲的是Text To speech 但是主要模型架构使用的是seq2seq 主要的改进也
  • 高效真实的云渲染算法

    高效真实的云渲染算法 转 原文链接 http www cnblogs com effulgent archive 2008 10 06 1305029 html 原文 Realistic and Fast Cloud Rendering N
  • 飞书与IAI国际广告奖,协同实现国内营销史上的创新“云终审”

    防疫时期 线下营销活动暂停或无限延期 转型线上迫在眉睫 而想要高效地进行线上远程办公 一套实用而全面的线上协同工具不可或缺 今天的主角 IAI国际广告奖 是由中国传媒大学广告学院与IAI国际广告研究所联合主办的中国大型广告作品案例评选活动
  • 随笔:MySQL 查询事务状态字段说明

    今天一个朋友想查看一下的MySQL层事务提交状态经历的过程 比如我们常说的prapare flush sync commit 几个阶段 但是找了一下发现视乎没有视图可以看到一共看了3个地方 information schema INNODB
  • Elasticsearch实战(十二)---搜索推荐 match_phrase_prefix及fuzzy错误拼写模糊查询

    Elasticsearch实战 搜索推荐 match phrase prefix 文章目录 Elasticsearch实战 搜索推荐 match phrase prefix 1 搜索推荐场景 1 1 准备数据 2 搜索推荐实现 2 1 ma
  • nginx报404 not found错误解决

    一般报404错误都是因为nginx做了伪静态 去除了框架的index php 访问某域名时 去掉index php目录时达到效果一样 如 www test1 index php test2跟www test1 test2效果一致 在vhos
  • stm32flash碰到hex文件出错,读取超慢, 占用内存超多的问题解决

    这个问题是因为sdcc生成的hex的每行的地址并不是排序的好的 有些高的地址在前面 低的地址在后面 这样的话 stm32flash这个hex c并不能处理这个情况 里面有一个逻辑是用来填补0xff的 当后面的地址比前面大 一减得负数 但是变
  • TCP协议详解(三次握手,传输数据,四次挥手)

    首先来了解一下什么是TCP 传输控制协议 简单点来讲TCP它是一种网络通信协议 旨在通过internet发送数据包 TCP是OSI层中的传输层协议 第四层 用于通过传输和确保通过支持网络和internet传递消息来在远程计算机之间创建连接
  • React Native环境及项目配置搭建

    安装RN环境卡了我好久 在网上搜了很多都不全遇到很多坎儿 时至今日我终于装好了 打算写一个详细过程造福大众 也算是对自己总结更深层的记忆 1 首先看官网 React 注意要点 必须要有node javaJDK和AndroidStudio 再
  • iOS 4层结构(iOS技术概要)—— Media 层(二)

    Media层 媒体层提供了图形 音频和视频技术支持 以达到移动设备上极佳的多媒体体验 一 图形技术 高品质图形是iOS应用程序非常重要的一部分 最简单 和最有效 的方法来创建一个应用程序是使用预渲染图片与UI标准控件结合实现系统绘制 然而
  • 如何用git将本地文件放到github上

    1 在github上新建一个仓库 2 使用如下命令操作 前提 本地已安装git 使用git Bash运行如下代码 git init 使本地文件夹成为一个本地git仓库 运行后文件夹下会生成一个 git文件夹 git add 将本地文件夹添加
  • Seata解析-数据源代理DataSourceProxy详解

    本文基于seata 1 3 0版本 前面通过十多篇文章详细介绍了TC端 从这篇文章开始介绍RM RM是资源管理器 资源指的就是数据库 RM主要与分支事务有关 RM会处理业务数据 在 Seata解析 seata部署启动初体验 中 使用了类Da