目录

1、背景

最近在开发的过程中遇到这么一个问题,当产生某种类型的工单后,需要实时通知到另外的系统,由另外的系统进行数据的研判操作。 由于某种原因, 像向消息队列中推送工单消息、或直接调用另外系统的接口、或者部署Cannal 等都不可行,因此此处使用 mysql-binlog-connector-java 这个库来完成数据库binlog的监听,从而通知到另外的系统。

2、mysql-binlog-connector-java简介

mysql-binlog-connector-java是一个Java库,通过它可以实现mysql binlog日志的监听和解析操作。它提供了一系列可靠的方法,使开发者通过监听数据库的binlog日志,来实时获取数据库的变更信息,比如:数据的插入更新删除等操作。

github地址 https://github.com/osheroff/mysql-binlog-connector-java

3、准备工作

1、验证数据库是否开启binlog

mysql> show variables like '%log_bin%';
+---------------------------------+------------------------------------+
| Variable_name | Value |
+---------------------------------+------------------------------------+
| log_bin | ON |
| log_bin_basename | /usr/local/mysql/data/binlog |
| log_bin_index | /usr/local/mysql/data/binlog.index |
| log_bin_trust_function_creators | OFF |
| log_bin_use_v1_row_events | OFF |
| sql_log_bin | ON |
+---------------------------------+------------------------------------+

log_bin 的值为 ON 时,表示开启了binlog

2、开启数据库的binlog

# 修改 my.cnf 配置文件
[mysqld]
#binlog日志的基本文件名,需要注意的是启动mysql的用户需要对这个目录(/usr/local/var/mysql/binlog)有写入的权限
log_bin=/usr/local/var/mysql/binlog/mysql-bin
# 配置binlog日志的格式
binlog_format = ROW
# 配置 MySQL replaction 需要定义,不能和已有的slaveId 重复
server-id=1

3、创建具有REPLICATION SLAVE权限的用户

CREATE USER binlog_user IDENTIFIED BY 'binlog#Replication2024!';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'binlog_user'@'%';
FLUSH PRIVILEGES;

4、事件类型 eventType 解释

注意:不同的mysql版本事件类型可能不同,我们本地是mysql8

TABLE_MAP: 在表的 insert、update、delete 前的事件,用于记录操作的数据库名和表名。
EXT_WRITE_ROWS: 插入数据事件类型,即 insert 类型
EXT_UPDATE_ROWS: 插入数据事件类型,即 update 类型
EXT_DELETE_ROWS: 插入数据事件类型,即 delete 类型 ROTATE: 当mysqld切换到新的二进制日志文件时写入。当发出一个FLUSH LOGS 语句。或者当前二进制日志文件超过max_binlog_size。

1、TABLE_MAP 的注意事项

一般情况下,当我们向数据库中执行insertupdatedelete事件时,一般会先有一个TABLE_MAP事件发出,通过这个事件,我们就知道当前操作的是那个数据库和表。 但是如果我们操作的表上存在触发器时,那么可能顺序就会错乱,导致我们获取到错误的数据库名和表名。

2、获取操作的列名

此处以 EXT_UPDATE_ROWS 事件为列,当我们往数据库中update一条记录时,触发此事件,事件内容为:

Event{header=EventHeaderV4{timestamp=1727498351000, eventType=EXT_UPDATE_ROWS, serverId=1, headerLength=19, dataLength=201, nextPosition=785678, flags=0}, data=UpdateRowsEventData{tableId=264, includedColumnsBeforeUpdate={0, 1, 2, 3, 4, 5, 6, 7}, includedColumns={0, 1, 2, 3, 4, 5, 6, 7}, rows=[
{before=[1, zhangsan, 张三-update, 0, [B@7b720427, [B@238552f, 1727524798000, 1727495998000], after=[1, zhangsan, 张三-update, 0, [B@21dae489, [B@2c0fff72, 1727527151000, 1727498351000]}
]}}

从上面的语句中可以看到includedColumnsBeforeUpdateincludedColumns这2个字段表示更新前的列名和更新后的列名,但是这个时候展示的数字,那么如果展示具体的列名呢? 可以通过information_schema.COLUMNS获取。

5、监听binlog的position

1、从最新的binlog位置开始监听

默认情况下,就是从最新的binlog位置开始监听。

BinaryLogClient client = new BinaryLogClient(hostname, port, username, password);

2、从指定的位置开始监听

BinaryLogClient client = new BinaryLogClient(hostname, port, username, password);
// binlog的文件名
client.setBinlogFilename("");
// binlog的具体位置
client.setBinlogPosition(11);

3、断点续传

这个指的是,当我们的 mysql-binlog-connector-java 程序宕机后,如果数据发生了binlog的变更,我们应该从程序上次宕机的位置的position进行监听,而不是程序重启后从最新的binlog position位置开始监听。默认情况下mysql-binlog-connector-java程序没有为我们实现,需要我们自己去实现。大概的实现思路为:

  1. 监听 ROTATE事件,可以获取到最新的binlog文件名和位置。
  2. 记录每个事件的position的位置。

6、创建表和准备测试数据

CREATE TABLE `binlog_demo`
(
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_name` varchar(64) DEFAULT NULL COMMENT '用户名',
`nick_name` varchar(64) DEFAULT NULL COMMENT '昵称',
`sex` tinyint DEFAULT NULL COMMENT '性别 0-女 1-男 2-未知',
`address` text COMMENT '地址',
`ext_info` json DEFAULT NULL COMMENT '扩展信息',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` timestamp NULL DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_username` (`user_name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='测试binlog' -- 0、删除数据
truncate table binlog_demo; -- 1、添加数据
insert into binlog_demo(user_name, nick_name, sex, address, ext_info, create_time, update_time)
values ('zhangsan', '张三', 1, '地址', '[
"aaa",
"bbb"
]', now(), now()); -- 2、修改数据
update binlog_demo
set nick_name = '张三-update',
sex = 0,
address = '地址-update',
ext_info = '{
"ext_info": "扩展信息"
}',
create_time = now(),
update_time = now()
where user_name = 'zhangsan'; -- 3、删除数据
delete
from binlog_demo
where user_name = 'zhangsan';

4、功能实现

通过mysql-binlog-connector-java库,当数据库中的表数据发生变更时,进行监听

1、从最新的binlog位置开始监听

1、引入jar包

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!-- 监听 mysql binlog -->
<dependency>
<groupId>com.zendesk</groupId>
<artifactId>mysql-binlog-connector-java</artifactId>
<version>0.29.2</version>
</dependency>
</dependencies>

2、监听binlog数据

package com.huan.binlog;

import com.github.shyiko.mysql.binlog.BinaryLogClient;
import com.github.shyiko.mysql.binlog.event.Event;
import com.github.shyiko.mysql.binlog.event.EventType;
import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.concurrent.TimeoutException; /**
* 初始化 binary log client
*
* @author huan.fu
* @date 2024/9/22 - 16:23
*/
@Component
public class BinaryLogClientInit { private static final Logger log = LoggerFactory.getLogger(BinaryLogClientInit.class); private BinaryLogClient client; @PostConstruct
public void init() throws IOException, TimeoutException {
/**
* # 创建用户
* CREATE USER binlog_user IDENTIFIED BY 'binlog#Replication2024!';
* GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'binlog_user'@'%';
* FLUSH PRIVILEGES;
*/
String hostname = "127.0.0.1";
int port = 3306;
String username = "binlog_user";
String password = "binlog#Replication2024!";
// 创建 BinaryLogClient客户端
client = new BinaryLogClient(hostname, port, username, password);
// 这个 serviceId 不可重复
client.setServerId(12); // 反序列化配置
EventDeserializer eventDeserializer = new EventDeserializer();
eventDeserializer.setCompatibilityMode(
// 将日期类型的数据反序列化成Long类型
EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG
); client.setEventDeserializer(eventDeserializer);
client.registerEventListener(new BinaryLogClient.EventListener() {
@Override
public void onEvent(Event event) {
EventType eventType = event.getHeader().getEventType();
log.info("接收到事件类型: {}", eventType);
log.warn("接收到的完整事件: {}", event);
log.info("============================");
}
});
client.registerLifecycleListener(new BinaryLogClient.AbstractLifecycleListener() {
@Override
public void onConnect(BinaryLogClient client) {
log.info("客户端连接到 mysql 服务器 client: {}", client);
} @Override
public void onCommunicationFailure(BinaryLogClient client, Exception ex) {
log.info("客户端和 mysql 服务器 通讯失败 client: {}", client);
} @Override
public void onEventDeserializationFailure(BinaryLogClient client, Exception ex) {
log.info("客户端序列化失败 client: {}", client);
} @Override
public void onDisconnect(BinaryLogClient client) {
log.info("客户端断开 mysql 服务器链接 client: {}", client);
}
});
// client.connect 在当前线程中进行解析binlog,会阻塞当前线程
// client.connect(xxx) 会新开启一个线程,然后在这个线程中解析binlog
client.connect(10000);
} @PreDestroy
public void destroy() throws IOException {
client.disconnect();
}
}

3、测试



从上图中可以看到,我们获取到了更新后的数据,但是具体更新了哪些列名这个我们是不清楚的。

2、获取数据更新具体的列名

此处以更新数据为例,大体的实现思路如下:

  1. 通过监听 TABLE_MAP 事件,用于获取到 insertupdatedelete语句操作前的数据库
  2. 通过查询 information_schema.COLUMNS 表获取 某个表在某个数据库中具体的列信息(比如:列名、列的数据类型等操作)。

2.1 新增common-dbutils依赖用于操作数据库

<!-- 操作数据库 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.8.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

2.2 监听TABLE_MAP事件,获取数据库和表名

  1. 定义2个成员变量databasetableName用于接收数据库和表名。
/**
* 数据库
*/
private String database;
/**
* 表名
*/
private String tableName;
  1. 监听TABLE_MAP事件,获取数据库和表名
// 成员变量 - 数据库名
private String database;
// 成员变量 - 表名
private String tableName; client.registerEventListener(new BinaryLogClient.EventListener() {
@Override
public void onEvent(Event event) {
EventType eventType = event.getHeader().getEventType();
log.info("接收到事件类型: {}", eventType);
log.info("============================"); if (event.getData() instanceof TableMapEventData) {
TableMapEventData eventData = (TableMapEventData) event.getData();
database = eventData.getDatabase();
tableName = eventData.getTable();
log.info("获取到的数据库名: {} 和 表名为: {}", database, tableName);
}
}
});

2.3 编写工具类获取表的列名和位置信息

/**
* 数据库工具类
*
* @author huan.fu
* @date 2024/10/9 - 02:39
*/
public class DbUtils { public static Map<String, String> retrieveTableColumnInfo(String database, String tableName) throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/temp_work", "binlog_user", "binlog#Replication2024!"); QueryRunner runner = new QueryRunner();
Map<String, String> columnInfoMap = runner.query(
connection,
"select a.COLUMN_NAME,a.ORDINAL_POSITION from information_schema.COLUMNS a where a.TABLE_SCHEMA = ? and a.TABLE_NAME = ?",
resultSet -> {
Map<String, String> result = new HashMap<>();
while (resultSet.next()) {
result.put(resultSet.getString("ORDINAL_POSITION"), resultSet.getString("COLUMN_NAME"));
}
return result;
},
database,
tableName
);
connection.close();
return columnInfoMap;
} public static void main(String[] args) throws SQLException {
Map<String, String> stringObjectMap = DbUtils.retrieveTableColumnInfo("temp_work", "binlog_demo");
System.out.println(stringObjectMap);
}
}

2.4 以更新语句为例获取 更新的列名和对应的值

1、编写java代码获取更新后的列和值信息

client.registerEventListener(new BinaryLogClient.EventListener() {
@Override
public void onEvent(Event event) {
EventType eventType = event.getHeader().getEventType();
log.info("接收到事件类型: {}", eventType);
log.warn("接收到的完整事件: {}", event);
log.info("============================"); // 通过 TableMap 事件获取 数据库名和表名
if (event.getData() instanceof TableMapEventData) {
TableMapEventData eventData = (TableMapEventData) event.getData();
database = eventData.getDatabase();
tableName = eventData.getTable();
log.info("获取到的数据库名: {} 和 表名为: {}", database, tableName);
} // 监听更新事件
if (event.getData() instanceof UpdateRowsEventData) {
try {
// 获取表的列信息
Map<String, String> columnInfo = DbUtils.retrieveTableColumnInfo(database, tableName);
// 获取更新后的数据
UpdateRowsEventData eventData = ((UpdateRowsEventData) event.getData());
// 可能更新多行数据
List<Map.Entry<Serializable[], Serializable[]>> rows = eventData.getRows(); for (Map.Entry<Serializable[], Serializable[]> row : rows) {
// 更新前的数据
Serializable[] before = row.getKey();
// 更新后的数据
Serializable[] after = row.getValue();
// 保存更新后的一行数据
Map<String, Serializable> afterUpdateRowMap = new HashMap<>();
for (int i = 0; i < after.length; i++) {
// 因为 columnInfo 中的列名的位置是从1开始,而此处是从0开始
afterUpdateRowMap.put(columnInfo.get((i + 1) + ""), after[i]);
}
log.info("监听到更新的数据为: {}", afterUpdateRowMap);
}
} catch (Exception e) {
log.error("监听更新事件发生了异常");
}
} // 监听插入事件
if (event.getData() instanceof WriteRowsEventData) {
log.info("监听到插入事件");
} // 监听删除事件
if (event.getData() instanceof DeleteRowsEventData) {
log.info("监听到删除事件");
}
}
});

2、执行更新语句

update binlog_demo
set nick_name = '张三-update11',
-- sex = 0,
-- address = '地址-update1',
-- ext_info = '{"ext_info":"扩展信息"}',
-- create_time = now(),
update_time = now()
where user_name = 'zhangsan';

3、查看监听到更新数据信息

3、自定义序列化字段

从下图中可知,针对 text 类型的字段,默认转换成了byte[]类型,那么怎样将其转换成String类型呢?

此处针对更新语句来演示

3.1 自定义更新数据text类型字段的反序列

注意:断点跟踪源码发现text类型的数据映射成了blob类型,因此需要重写 deserializeBlob 方法

public class CustomUpdateRowsEventDataDeserializer extends UpdateRowsEventDataDeserializer {
public CustomUpdateRowsEventDataDeserializer(Map<Long, TableMapEventData> tableMapEventByTableId) {
super(tableMapEventByTableId);
} @Override
protected Serializable deserializeBlob(int meta, ByteArrayInputStream inputStream) throws IOException {
byte[] bytes = (byte[]) super.deserializeBlob(meta, inputStream);
if (null != bytes && bytes.length > 0) {
return new String(bytes, StandardCharsets.UTF_8);
}
return null;
}
}

3.2 注册更新数据的反序列

注意: 需要通过 EventDeserializer 来进行注册

// 反序列化配置
EventDeserializer eventDeserializer = new EventDeserializer(); Field field = EventDeserializer.class.getDeclaredField("tableMapEventByTableId");
field.setAccessible(true);
Map<Long, TableMapEventData> tableMapEventByTableId = (Map<Long, TableMapEventData>) field.get(eventDeserializer);
eventDeserializer.setEventDataDeserializer(EventType.EXT_UPDATE_ROWS, new CustomUpdateRowsEventDataDeserializer(tableMapEventByTableId)
.setMayContainExtraInformation(true));

3.3 更新text类型的字段,看输出的结果

4、只订阅感兴趣的事件

// 反序列化配置
EventDeserializer eventDeserializer = new EventDeserializer();
eventDeserializer.setCompatibilityMode(
// 将日期类型的数据反序列化成Long类型
EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG
);
// 表示对 删除事件不感兴趣 ( 对于DELETE事件的反序列化直接返回null )
eventDeserializer.setEventDataDeserializer(EventType.EXT_DELETE_ROWS, new NullEventDataDeserializer());

对于不感兴趣的事件直接使用NullEventDataDeserializer,可以提高程序的性能。

5、断点续传

当binlog的信息发生变更时,需要保存起来,下次程序重新启动时,读取之前保存好的binlog信息。

5.1 binlog信息持久化

此处为了模拟,将binlog的信息保存到文件中。

/**
* binlog position 的持久化处理
*
* @author huan.fu
* @date 2024/10/11 - 12:54
*/
public class FileBinlogPositionHandler { /**
* binlog 信息实体类
*/
public static class BinlogPositionInfo {
/**
* binlog文件的名字
*/
public String binlogName;
/**
* binlog的位置
*/
private Long position;
/**
* binlog的server id的值
*/
private Long serverId;
} /**
* 保存binlog信息
*
* @param binlogName binlog文件名
* @param position binlog位置信息
* @param serverId binlog server id
*/
public void saveBinlogInfo(String binlogName, Long position, Long serverId) {
List<String> data = new ArrayList<>(3);
data.add(binlogName);
data.add(position + "");
data.add(serverId + "");
try {
Files.write(Paths.get("binlog-info.txt"), data);
} catch (IOException e) {
throw new RuntimeException(e);
}
} /**
* 获取 binlog 信息
*
* @return BinlogPositionInfo
*/
public BinlogPositionInfo retrieveBinlogInfo() {
try {
List<String> lines = Files.readAllLines(Paths.get("binlog-info.txt"));
BinlogPositionInfo info = new BinlogPositionInfo();
info.binlogName = lines.get(0);
info.position = Long.parseLong(lines.get(1));
info.serverId = Long.parseLong(lines.get(2));
return info;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

5.2、构建BinaryLogClient时,传递已存在的binlog信息

// 设置 binlog 信息
FileBinlogPositionHandler fileBinlogPositionHandler = new FileBinlogPositionHandler();
FileBinlogPositionHandler.BinlogPositionInfo binlogPositionInfo = fileBinlogPositionHandler.retrieveBinlogInfo();
if (null != binlogPositionInfo) {
log.info("获取到了binlog 信息 binlogName: {} position: {} serverId: {}", binlogPositionInfo.binlogName,
binlogPositionInfo.position, binlogPositionInfo.serverId);
client.setBinlogFilename(binlogPositionInfo.binlogName);
client.setBinlogPosition(binlogPositionInfo.position);
client.setServerId(binlogPositionInfo.serverId);
}

5.3 更新binlog信息

// FORMAT_DESCRIPTION(写入每个二进制日志文件前的描述事件) HEARTBEAT(心跳事件)这2个事件不进行binlog位置的记录
if (eventType != EventType.FORMAT_DESCRIPTION && eventType != EventType.HEARTBEAT) {
// 当有binlog文件切换时产生
if (event.getData() instanceof RotateEventData) {
RotateEventData eventData = event.getData();
// 保存binlog position 信息
fileBinlogPositionHandler.saveBinlogInfo(eventData.getBinlogFilename(), eventData.getBinlogPosition(), event.getHeader().getServerId());
} else {
// 非 rotate 事件,保存位置信息
EventHeaderV4 header = event.getHeader();
FileBinlogPositionHandler.BinlogPositionInfo info = fileBinlogPositionHandler.retrieveBinlogInfo();
long position = header.getPosition();
long serverId = header.getServerId();
fileBinlogPositionHandler.saveBinlogInfo(info.binlogName, position, serverId);
}
}

5.4 演示

  1. 启动程序
  2. 修改 address 的值为 地址-update2
  3. 停止程序
  4. 修改address的值为地址-offline-update
  5. 启动程序,看能否收到 上一步修改address的值为地址-offline-update的事件

5、参考地址

  1. github地址 - https://github.com/osheroff/mysql-binlog-connector-java
  2. maven仓库地址https://mvnrepository.com/artifact/com.zendesk/mysql-binlog-connector-java/0.29.2
  3. TABLE_MAP事件顺序问题. - https://github.com/shyiko/mysql-binlog-connector-java/issues/67
  4. dbutils的官网 - https://commons.apache.org/proper/commons-dbutils/examples.html

在Java程序中监听mysql的binlog的更多相关文章

  1. 小程序中监听textarea或者input输入的值动态改变data中数组的对象的值

    Page({ data: { todoLists:[ { detail:"", date:"", location:"", priority ...

  2. 监听MySQL的binlog日志工具分析:Canal

    Canal是阿里巴巴旗下的一款开源项目,利用Java开发.主要用途是基于MySQL数据库增量日志解析,提供增量数据订阅和消费,目前主要支持MySQL. GitHub地址:https://github. ...

  3. 20180530利用Maxwell组件实时监听Mysql的binlog日志

    转自:https://blog.csdn.net/qq_30921461/article/details/78320750 http://kafka.apache.org/quickstart htt ...

  4. 在java程序中使用JDBC连接mysql数据库

    在java程序中我们时常会用到数据库中的数据或操作数据库中的数据,如果java程序没有和我们得数据库连接,就不能实现在java程序中直接操作数据库.使用jdbc就能将java程序和数据库连起来,此时我 ...

  5. 监听mysql是否挂了

    监听mysql是否挂了,如果挂了就重启mysql 方式一: #!/bin/bash pgrep -x mysqld &> /dev/null if [ $? -ne 0 ] then e ...

  6. Android-服务中监听电源键和Home键的广播、在锁屏下仍然工作的方法

    Android-服务中监听电源键和Home键的广播  http://blog.csdn.net/u014657752/article/details/49512485 Android开发之如何监听让服 ...

  7. SQL函数TIMEDIFF在Java程序中使用报错的问题分析

    需求背景 (读者可略过)司机每天从早到晚都会去到不同的自动售货机上补货,而且补货次数和路线等也是因人而异,补货依据是由系统优化并指派.但是目前系统还无法实施有效指挥和优良的补货策略,司机的补货活动因此 ...

  8. Linux上从Java程序中调用C函数

    原则上来说,"100%纯Java"的解决方法是最好的,但有些情况下必须使用本地方法.特别是在以下三种情况: 需要访问Java平台无法访问的系统特性和设备: 通过基准测试,发现Jav ...

  9. 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案

    方案特点: 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案,简化软件开发流程,减少各应用系统相同模块的重复开发工作,提高系统稳定性和可靠性. 基于HTTP协议的开发接口 使用特点在网页 ...

  10. Vue 为什么在 HTML 中监听事件?

    为什么在 HTML 中监听事件? 你可能注意到这种事件监听的方式违背了关注点分离(separation of concern)传统理念.不必担心,因为所有的 Vue.js 事件处理方法和表达式都严格绑 ...

随机推荐

  1. 【Docker】01 概述

    什么是Docker? 一个开源的应用容器引擎 由Go语言开发而成,遵循Apache2.0开源协议 允许开发者打包自己的应用或者依赖包组件到一个轻量级可移植的容器中 Docker容器采用沙箱机制,相互之 ...

  2. 【Spring Data JPA】07 Specifications动态查询

    [前言说明] 针对CRUD种的查询,因为我们的查询总是具有各种各样的筛选条件 为了我们的程序能够更加适应筛选条件的变化,SpringDataJpa提供了Specifications这种解决方案 Spe ...

  3. 【转载】 机器人真·涨姿势了:比肩人类抓取能力,上海交大、非夕科技联合提出全新方法AnyGrasp

    原文地址: https://developer.aliyun.com/article/822654 ================================================= ...

  4. 武汉市委郭元强书记、盛阅春代市长会见白鲸开源CEO郭炜等嘉宾代表

    2024年6月14日,第二届软件创新发展大会在中国武汉举行.大会云集了来自全国的书数百位院士.专家.知名软件企业负责人,包括中国工程院院士倪光南.中国科学院院士陈十一.国家工业信息安全发展研究中心总工 ...

  5. 由浅深入理解java多线程,java并发,synchronized实现原理及线程锁机制

    由浅深入理解java多线程,java并发,synchronized实现原理及线程锁机制 目录 由浅深入理解java多线程,java并发,synchronized实现原理及线程锁机制 一,线程的生命周期 ...

  6. Orleans初体验

    Orleans: 是一个跨平台框架,用于构建可靠且可缩放的分散式应用. 分布式应用定义为跨多个进程的应用,通常使用对等通信来超越硬件边界. 从单个本地服务器扩展到了云中数千个分布式.高度可用的应用. ...

  7. Linux基础优化与常用软件包说明

    1.安装常用工具 1.1CentOS(7) 1.1.1 是否联网 ping qq.com 1.1.2 配置yum源(安装软件的软件仓库) 默认情况下yum下载软件的时候是从随机地址下载. 配置yum从 ...

  8. SQLserver 数据库自定义函数

    起源 最近项目开发上使用的SQLserver数据库是2008版本,由于08版本的数据是没有字符串合并(STRING_AGG)这个函数(2017版本及以上支持)的,只有用stuff +for xml p ...

  9. C 语言多文件编译

    C 语言中的多文件编程通常涉及将代码分散在几个不同的源文件(.c 文件)和头文件(.h 文件)中.这么做可以帮助你组织大型项目,提高代码的重用性,便于团队合作,分离接口和实现,以及加快编译时间.下面是 ...

  10. Linux 网络设备命名规则

    在 Linux 系统中,网络接口的命名规则已经经历了几次重要变化,特别是从传统的以 eth 和 wlan 开头的名称,转变到更现代.更具描述性的命名方式.以下是这些变化的概述: 1. 传统命名约定 在 ...