第十五章_Redis与MySQL数据双写一致性工程落地案例

news2024/11/19 15:38:37

复习+面试题

采用双检加锁策略 

多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它

其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。

后面的线程进来发现已经有缓存了,就直接走缓存。 

canal

是什么

canal [kə'næl],中文翻译为 水道/管道/沟渠/运河,主要用途是用于 MySQL 数据库增量日志数据的订阅、消费和解析,是阿里巴巴开发并开源的,采用Java语言开发;

历史背景是早期阿里巴巴因为杭州和美国双机房部署,存在跨机房数据同步的业务需求,实现方式主要是基于业务 trigger(触发器) 获取增量变更。从2010年开始,阿里巴巴逐步尝试采用解析数据库日志获取增量变更进行同步,由此衍生出了canal项目;

官网地址

一句话

canal [kə'næl],译意为水道/管道/沟渠,主要用途是基于 MySQL​数据库增量日志解析,提供增量数据订阅和消费

能干嘛

  • 数据库镜像
  • 数据库实时备份
  • 索引构建和实时维护(拆分异构索引、倒排索引等)
  • 业务 cache 刷新
  • 带业务逻辑的增量数据处理

去哪下

官网下载地址

工作原理,面试回答

传统MySQL主从复制工作原理 

MySQL的主从复制将经过如下步骤:

1、当 master 主服务器上的数据发生改变时,则将其改变写入二进制事件日志文件中;

2、salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进行探测,探测其是否发生过改变,

如果探测到 master 主服务器的二进制事件日志发生了改变,则开始一个 I/O Thread 请求 master 二进制事件日志;

3、同时 master 主服务器为每个 I/O Thread 启动一个dump  Thread,用于向其发送二进制事件日志;

4、slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件中;

5、salve 从服务器将启动 SQL Thread 从中继日志中读取二进制日志,在本地重放,使得其数据和主服务器保持一致;

6、最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒;

canal工作原理

mysql-canal-redis双写一致性Coding

java案例,来源出处

mysql

1 查看mysql版本

SELECT VERSION();

mysql5.7.28

2 当前的主机二进制日志

show master status;

3 查看SHOW VARIABLES LIKE 'log_bin';

4 开启 MySQL的binlog写入功能

D:\devSoft\mysql\mysql5.7.28(自己安装目录)目录下打开 

最好提前备份

my.ini

mysql

log-bin=mysql-bin #开启 binlog
binlog-format=ROW #选择 ROW 模式
server_id=1    #配置MySQL replaction需要定义,不要和canal的 slaveId重复
ROW模式 除了记录sql语句之外,还会记录每个字段的变化情况,能够清楚的记录每行数据的变化历史,但会占用较多的空间。
STATEMENT模式只记录了sql语句,但是没有记录上下文信息,在进行数据恢复的时候可能会导致数据的丢失情况;
MIX模式比较灵活的记录,理论上说当遇到了表结构变更的时候,就会记录为statement模式。当遇到了数据更新或者删除情况下就会变为row模式;

window        my.ini

linux             my.cnf

5 重启mysql

再次查看SHOW VARIABLES LIKE 'log_bin';

6 授权canal连接MySQL账号

mysql默认的用户在mysql库的user表里

SELECT * FROM mysql.`user`;

默认没有canal账户,此处新建+授权

DROP USER IF EXISTS 'canal'@'%';
CREATE USER 'canal'@'%' IDENTIFIED BY 'canal';  
GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal';  
FLUSH PRIVILEGES;
SELECT * FROM mysql.user;

canal服务端 

1.下载

GitHub地址

下载Linux版本:canal.deployer-1.1.6.tar.gz

注意发布时间+版本,2022.8.11后发布的才用

2.解压

解压后整体放入/mycanal路径下

3.配置

修改/mycanal/conf/example路径下instance.properties文件

instance.properties

换成自己的mysql主机master的IP地址 

换成自己的在mysql新建的canal账户

4. 启动

/opt/mycanal/bin路径下执行./startup.sh

5.查看

判断canal是否启动成功

查看 server 日志

查看 样例example 的日志

canal客户端(Java编写业务程序) 

SQL脚本

1 随便选个数据库,以你自己为主,本例bigdata,按照下面建表

CREATE TABLE `t_user` (

  `id` bigint(20) NOT NULL AUTO_INCREMENT,

  `userName` varchar(100) NOT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4;

2 建module

canal_demo02

3 改POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu.canal</groupId>
    <artifactId>canal_demo02</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.14</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <junit.version>4.12</junit.version>
        <log4j.version>1.2.17</log4j.version>
        <lombok.version>1.16.18</lombok.version>
        <mysql.version>5.1.47</mysql.version>
        <druid.version>1.1.16</druid.version>
        <mapper.version>4.1.5</mapper.version>
        <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
    </properties>

    <dependencies>
        <!--canal-->
        <dependency>
            <groupId>com.alibaba.otter</groupId>
            <artifactId>canal.client</artifactId>
            <version>1.1.0</version>
        </dependency>

        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!--SpringBoot与Redis整合依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!--SpringBoot与AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

        <!--Mysql数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--SpringBoot集成druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!--mybatis和springboot整合-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.boot.version}</version>
        </dependency>

        <!--通用基础配置junit/devtools/test/log4j/lombok/hutool-->
        <!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.2.3</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>${log4j.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <optional>true</optional>
        </dependency>

        <!--persistence-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
            <version>1.0.2</version>
        </dependency>

        <!--通用Mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper</artifactId>
            <version>${mapper.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.8.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4 写YML

server.port=5555

# ========================alibaba.druid=====================
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/bigdata?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.test-while-idle=false

5 主启动

package com.test.canal;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @auther admin
 */
@SpringBootApplication
public class CanalDemo02App {

     //本例不要启动CanalDemo02App实例

}

6 业务类

RedisUtils

package com.atguigu.canal.util;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @auther admin
 */
public class RedisUtils {

    public static final String  REDIS_IP_ADDR = "192.168.111.185";

    public static final String  REDIS_PWD = "111111";

    public static JedisPool jedisPool;

    static {
        JedisPoolConfig jedisPoolConfig=new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPool=new JedisPool(jedisPoolConfig, REDIS_IP_ADDR, 6379, 10000, REDIS_PWD);
    }

    public static Jedis getJedis() throws Exception {
        if(jedisPool != null){
            return jedisPool.getResource();
        }

        throw new Exception("Jedispool is not ok");
    }

}

RedisCanalClientExample

package com.test.canal.biz;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.test.canal.util.RedisUtils;
import redis.clients.jedis.Jedis;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @auther admin
 */
public class RedisCanalClientExample {

    public static final Integer _60SECONDS = 60;

    public static final String  REDIS_IP_ADDR = "192.168.111.185";

    private static void redisInsert(List<Column> columns) {
        JSONObject jsonObject = new JSONObject();

        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            jsonObject.put(column.getName(),column.getValue());
        }

        if(columns.size() > 0) {
            try(Jedis jedis = RedisUtils.getJedis()) {
                jedis.set(columns.get(0).getValue(), jsonObject.toJSONString());
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static void redisDelete(List<Column> columns) {
        JSONObject jsonObject = new JSONObject();

        for (Column column : columns) {
            jsonObject.put(column.getName(), column.getValue());
        }

        if(columns.size() > 0) {
            try(Jedis jedis = RedisUtils.getJedis()) {
                jedis.del(columns.get(0).getValue());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    private static void redisUpdate(List<Column> columns) {
        JSONObject jsonObject = new JSONObject();

        for (Column column : columns) {
            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
            jsonObject.put(column.getName(), column.getValue());
        }

        if(columns.size() > 0) {
            try(Jedis jedis = RedisUtils.getJedis()) {
                jedis.set(columns.get(0).getValue(), jsonObject.toJSONString());
                System.out.println("---------update after: "+jedis.get(columns.get(0).getValue()));
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void printEntry(List<Entry> entrys) {
        for (Entry entry : entrys) {
            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
                continue;
            }

            RowChange rowChage = null;

            try {
                //获取变更的row数据
                rowChage = RowChange.parseFrom(entry.getStoreValue());
            } catch (Exception e) {
                throw new RuntimeException("ERROR ## parser of eromanga-event has an error,data:" + entry.toString(),e);
            }

            //获取变动类型
            EventType eventType = rowChage.getEventType();
            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
                    entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
                    entry.getHeader().getSchemaName(), entry.getHeader().getTableName(), eventType));

            for (RowData rowData : rowChage.getRowDatasList()) {
                if (eventType == EventType.INSERT) {
                    redisInsert(rowData.getAfterColumnsList());
                } else if (eventType == EventType.DELETE) {
                    redisDelete(rowData.getBeforeColumnsList());
                } else {//EventType.UPDATE
                    redisUpdate(rowData.getAfterColumnsList());
                }
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("---------O(∩_∩)O哈哈~ initCanal() main方法-----------");

        //=================================
        // 创建链接canal服务端
        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(REDIS_IP_ADDR,
                11111), "example", "", "");

        int batchSize = 1000;
        //空闲空转计数器
        int emptyCount = 0;

        System.out.println("---------------------canal init OK,开始监听mysql变化------");

        try {
            connector.connect();
            //connector.subscribe(".*\\..*");
            connector.subscribe("bigdata.t_user");
            connector.rollback();
            int totalEmptyCount = 10 * _60SECONDS;

            while (emptyCount < totalEmptyCount) {
                System.out.println("我是canal,每秒一次正在监听:"+ UUID.randomUUID().toString());
                // 获取指定数量的数据
                Message message = connector.getWithoutAck(batchSize); 
                long batchId = message.getId();
                int size = message.getEntries().size();

                if (batchId == -1 || size == 0) {
                    emptyCount++;

                    try { 
                        TimeUnit.SECONDS.sleep(1); 
                    } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                    }
                } else {
                    //计数器重新置零
                    emptyCount = 0;
                    printEntry(message.getEntries());
                }

                // 提交确认
                connector.ack(batchId);

                // 处理失败, 回滚数据
                // connector.rollback(batchId); 
            }

            System.out.println("已经监听了"+totalEmptyCount+"秒,无任何消息,请重启重试......");
        } finally {
            connector.disconnect();
        }
    }
}

题外话

java程序下connector.subscribe配置的过滤正则

关闭资源代码简写

try-with-resources释放资源

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

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

相关文章

Redis内幕揭秘:探索Redis基础知识及应用场景,挖掘出高效的缓存技术

Redis 是一个开源的内存数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。以下是 Redis 的发展史&#xff1a; 2009 年&#xff1a;Salvatore Sanfilippo 开始编写 Redis。2010 年&#xff1a;Redis 发布了 1.0 版本。这个版本包含了许多常用的数据结构&…

在陌生人社交场景 挖呀挖呀挖……

在陌生人社交场景 挖呀挖呀挖&#xff0c; 找可靠的大品牌&#xff08;网易&#xff09;&#xff0c;享最优惠的价~ &#x1f604; 对于“深挖”娱乐社交领域需求的开发者来说&#xff0c;陌生人社交是毋庸置疑最受青睐的场景。尤其是对渴望交流、敢于表达自我的年轻人来说&…

10-HTML-表单标签

标签描述<form>定义供用户输入的 HTML 表单。<input>定义输入控件。<textarea>定义多行的文本输入控件。<button>定义按钮。<select>定义选择列表&#xff08;下拉列表&#xff09;。<optgroup>定义选择列表中相关选项的组合。<option&…

【Rust日报】2023-05-10 llm - 使用Rust在CPU上运行大模型

llm - 使用Rust在CPU上运行大模型 这是一套工具。目前支持这些模型&#xff1a; GPT-2GPT-JLLaMA: LLaMA, Alpaca, Vicuna, Koala, GPT4All v1, GPT4-X, WizardGPT-NeoX: GPT-NeoX, StableLM, Dolly v2 (partial, not the same tensor names?)BLOOM: BLOOMZ https://github.co…

多优先级(笔记)

目录 支持多优先级的方法通用方法优化方法1、修改任务控制块2、修改xTaskCerateStactic()修改 prvInitialiseNewTask() 函数prvAddTaskToReadyList()初始化任务列表prvAddTaskToReadyList()vTaskStartScheduler()vTaskDelay()vTaskSwitchContext()xTaskIncrementTick() 实验实验…

如何成就一个伟大的公司?

任何事物都不及“伟大”那样简单&#xff0c;事实上&#xff0c;能够“简单”便是伟大。最优秀的模式&#xff0c;往往是最简单的东西。 那么&#xff0c;如今作为一名创业者&#xff0c;要如何才能更好的发展自己的公司&#xff0c;把带领的团队打造成为一个伟大的公司呢&…

MySQL基础(二十八)索引优化与查询优化

都有哪些维度可以进行数据库调优?简言之: 索引失效、没有充分利用到索引——索引建立关联查询太多JOIN (设计缺陷或不得已的需求)——SQL优化服务器调优及各个参数设置(缓冲、线程数等)———调整my.cnf。数据过多――分库分表 关于数据库调优的知识点非常分散。不同的DBMS&…

基于Vue3 + ts + echarts(版本5.X)实现中国地图下钻、地图打点、地图热力图功能

写在前面&#xff1a; 实现效果图 1.比较重要的部分用红字标出 2.安装echats: npm install echarts --save 3.由于echarts5版本的已经没有自带地图数据了&#xff0c;所以地图数据需要到专门的GEO数据网站中下载。这里提供一个阿里的下载地址&#xff1a;DataV.GeoAtl…

云原生环境下的安全实践:保护应用程序和数据的关键策略

文章目录 云原生环境下的安全实践&#xff1a;保护应用程序和数据的关键策略一.安全措施和实践1. 身份和访问管理&#xff1a;2. 容器安全&#xff1a;3. 网络安全&#xff1a;4. 日志和监控&#xff1a;5. 持续集成和持续交付&#xff08;CI/CD&#xff09;安全&#xff1a;6.…

【学习笔记-myabtis】使用mybtis对接pgsql的postgis插件,获取地理字段Geometry信息

使用mybtis对接pgsql的postgis插件&#xff0c;获取地理字段geometry信息 参考资料&#xff1a; Mybatis 自定义TypeHandler - 邓维-java - 博客园 1、如何使用typehandler ​ 相信大家用Mybatis这个框架至少一年以上了吧&#xff0c;有没有思考过这样一个问题&#xff1a;数据…

xxl-job 是什么?

xxl-job 是什么&#xff1f; XXL-JOB 是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 设计思想 是将调度行为抽象形成 调度中心 平台&#xff0c;平台本身不承担业务逻辑&#xff0c;而是负责发起 调度请求 后&#xff0c;由 执…

右下角任务栏出现广告持续闪烁解决方法

&#x1f466;&#x1f466;一个帅气的boy&#xff0c;你可以叫我Love And Program &#x1f5b1; ⌨个人主页&#xff1a;Love And Program的个人主页 &#x1f496;&#x1f496;如果对你有帮助的话希望三连&#x1f4a8;&#x1f4a8;支持一下博主 右下角任务栏出现广告持…

【K8S】【Jenkins】【CI/CD】【一】交付CI/CD工具至k8s 【待写】

1 安装Harbor镜像仓库&#xff08;之前已部署 &#xff0c;略&#xff09; 可参考之前的《Kubernetes业务迁移.pdf》 网站-账号密码 http://gitlab.oldxu.net:30080/users/sign_in &#xff08; root/ admin12345 &#xff09; http://sonar.oldxu.net:30080/ …

Type-C显示器是什么,Type-C显示器的5大优势

在显示器领域内&#xff0c;USB Type-C接口还处于发展阶段&#xff0c;目前已经在新推出的一些高端显示器和旗舰显示器中有配置。USB Type-C接口的出现&#xff0c;将会形成以显示器为核心的桌面解决方案&#xff0c;用户可以把任何笔记本、手机、平板等等的画面转移到一台大屏…

使用sklearn,报错Library not loaded: @rpath/libgfortran.3.dylib

因为需要使用sklearn&#xff0c;去做一些数据分析&#xff0c;所以使用conda命令进行安装 conda install scikit-learn 在安装完成之后&#xff0c;导入&#xff0c;并使用拟合优度R2函数评估&#xff0c;发生如下报错&#xff1b; import sklearn as sk r2 sk.metrics.r2_sc…

怎么安全快速地创建Windows7文件差异备份任务?

​什么是差异备份&#xff1f; 差异备份是什么呢&#xff1f;简单来说&#xff0c;差异备份就是一种数据备份类型&#xff0c;它会帮助我们备份自上次完整备份以来已更改的全部文件。 举个例子&#xff0c;假如我们在星期一进行了一次完整备份&#xff0c;那么星…

有数·智享未来 | 新华三重磅发布绿洲平台3.0

5月10日&#xff0c;紫光股份旗下新华三集团以“有数智享未来”为主题&#xff0c;成功举办绿洲平台3.0新品发布会。全新一代绿洲平台实现内核进阶&#xff0c;以五大技术能力升级、五大行业方案沉淀、六类服务能力保障&#xff0c;三位一体构筑更领先的用数底座、更落地的用数…

ASEMI代理LT8471IFE#PBF原装ADI车规级LT8471IFE#PBF

编辑&#xff1a;ll ASEMI代理LT8471IFE#PBF原装ADI车规级LT8471IFE#PBF 型号&#xff1a;LT8471IFE#PBF 品牌&#xff1a;ADI/亚德诺 封装&#xff1a;TSSOP-20 批号&#xff1a;2023 引脚数量&#xff1a;20 工作温度&#xff1a;-40C~125C 安装类型&#xff1a;表面…

Protobuf:一种轻量级、高效的数据交换格式,附Java与Python数据交换示例

目录 下载安装Protobuf定义数据格式Java代码序列化Python反序列化 Protobuf&#xff08;Protocol Buffers&#xff09;是由 Google 开发的一种轻量级、高效的数据交换格式 官方文档&#xff1a;https://protobuf.dev/overview/GitHub&#xff1a;https://github.com/protocolb…

佩戴舒适的蓝牙耳机有哪些?蓝牙耳机佩戴舒适度排名

随着技术的成熟&#xff0c;真无线耳机这个市场竞争也越来越激烈&#xff0c;伴随着TWS真无线耳机快速发展&#xff0c;许多耳机品牌凭借着优质的产品抓住了时代机遇&#xff0c;各家无论是手机厂商还是耳机品牌争相布局真无线耳机市场&#xff0c; 下面笔者整理了几款佩戴舒适…