Java监听mysql的binlog详解(mysql-binlog-connector)

  • 1. 需求概述
  • 2. 技术选型
  • 3. 方案设计
  • 3.环境准备
    • 3.1 查看是否开启binlog
    • 3.2 mysql开启binlog
  • 4.代码实现
    • 4.1 创建springboot项目
    • 4.2 引入依赖
    • 4.3 监听实现
  • 5.实现验证

1. 需求概述

业务开发中经常需要根据一些数据变更实现相对应的操作。例如,一些用户注销自己的账户,系统可以给用户自动发短信确认,这时有两种解决方案,一种是耦合到业务系统中,当用户执行注销操作的时候,执行发短信的操作,既是是通过MQ也是要耦合业务代码的,第二种方案基于数据库层面的操作,通过监听binlog实现自动发短信操作,这样就可以与业务系统解耦。
本篇主要介绍基于mysql-binlog-connector实现对数据库的监听,并集成springboot的方案。

2. 技术选型

基于binlog实现数据同步的方案有两种:
一种是mysql-binlog-connector,另一种是ali的canal。
mysql-binlog-connector:是通过引入依赖jar包实现,需要自行实现解析,但是相对轻量。
canal:是数据同步中间件,需要单独部署维护,功能强大,支持数据库及MQ的同步,维护成本高。
根据实际业务场景,按需索取,业务量小,业务简单,轻量可以通过mysql-binlog-connector,业务量大,逻辑复杂,有专门的运维团队,可以考虑canal,比较经过阿里高并发验证,相对稳定。

Canal监听mysql的binlog日志实现数据同步:https://blog.csdn.net/m0_37583655/article/details/119517336
Java监听mysql的binlog详解(mysql-binlog-connector):https://blog.csdn.net/m0_37583655/article/details/119148470

3. 方案设计

1.支持对不同数据库,不同表的配置监听。
2.封装细节数据库,对外提供统一监听。
3.讲结果集封装位方便操作数据结构。
5.讲监听信息统一放入阻塞队列。
6.实现多线程消费。

3.环境准备

3.1 查看是否开启binlog

1.正常开启状态
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+
1 row in set (0.02 sec)
mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       154 |
+------------------+-----------+
1 row in set (0.09 sec)2.权限不足情况
mysql> show binary logs;
1227 - Access denied; you need (at least one of) the SUPER, REPLICATION CLIENT privilege(s) for this operation3.未开启状态(默认情况下是不开启的)
mysql> show binary logs;
ERROR 1381 - You are not using binary logging

3.2 mysql开启binlog

1.开启binlog

修改mysql配置文件my.ini。添加下配置:
log_bin=mysql-bin
binlog-format=Row

注意:
1.该文件默认不允许修改,需要右键“管理员取得所有权”之后才能保存修改。
2.切记不要修改错配置文件。需要注意的是图中的my.ini位于:C:\ProgramData\MySQL\MySQL Server 5.7,而不是位于:C:\Program Files\MySQL\MySQL Server 5.7。

2. 重启mysql服务
直接找到服务重启即可
我的电脑->(右键)管理->服务与应用程序->服务->MYSQL->开启(停止、重启动)
命令方式:
net stop mysql
net start mysql

3.binlog文件路径
若指定为绝对路径,则为指定路径:
log_bin=C:\mysql-binlog\mysql-bin

若不指定绝对路径则默认当前目录下Data文件夹下:log_bin=mysql-bin

binlog文件:mysql-bin.000001

4.代码实现

4.1 创建springboot项目

createProject->Spring Initializer

4.2 引入依赖

<?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><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.zrj</groupId><artifactId>binlog</artifactId><version>0.0.1-SNAPSHOT</version><name>binlog</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.4</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.5.4</version><scope>compile</scope></dependency><!-- https://mvnrepository.com/artifact/args4j/args4j --><dependency><groupId>args4j</groupId><artifactId>args4j</artifactId><version>2.33</version></dependency><!-- https://mvnrepository.com/artifact/com.google.guava/guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>28.1-jre</version></dependency><!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version></dependency><!-- https://mvnrepository.com/artifact/com.github.shyiko/mysql-binlog-connector-java --><dependency><groupId>com.github.shyiko</groupId><artifactId>mysql-binlog-connector-java</artifactId><version>0.17.0</version></dependency><!-- https://mvnrepository.com/artifact/com.google.code.gson/gson --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>

4.3 监听实现

application.yml

spring:datasource:username: rootpassword: 123456url: jdbc:mysql://localhost:3306/INFORMATION_SCHEMA?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTCdriver-class-name: com.mysql.jdbc.Driverhikari:pool-name: Retail_HikariCP #连接池名称minimum-idle: 10 #最小空闲连接数量idle-timeout: 120000 #空闲连接存活最大时间,默认600000(10分钟)maximum-pool-size: 20 #连接池最大连接数,默认是10auto-commit: true  #此属性控制从池返回的连接的默认自动提交行为,默认值:truemax-lifetime: 1800000 #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟connection-timeout: 30000 #数据库连接超时时间,默认30秒,即30000connection-test-query: SELECT 1mybatis:mapper-locations: classpath:mapper/*Mapper.xmltype-aliases-package: com.zrj.util#showSql
logging:level:com:example:mapper : debug# binlog listener
binlog:columns: # 订阅binlog数据库连接信息,ip,端口,用户密码(用户必须要有权限)host: 192.168.52.8port: 3306username: flink_slavepasswd: flink_slavedb: tour_dev # 监听数据库table: search_store_logo,search_store_mating

BinLogConstants

package com.zrj.util.binlog;import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;/*** 监听配置信息** @author zrj* @since 2021/7/27**/
@Data
@Component
public class BinLogConstants {@Value("${binlog.datasource.host}")private String host;@Value("${binlog.datasource.port}")private int port;@Value("${binlog.datasource.username}")private String username;@Value("${binlog.datasource.passwd}")private String passwd;@Value("${binlog.db}")private String db;@Value("${binlog.table}")private String table;public static final int consumerThreads = 5;public static final long queueSleep = 1000;}

Colum

package com.zrj.util.binlog;import lombok.Data;/*** 字段属性对象** @author zrj* @since 2021/7/27**/
@Data
public class Colum {public int inx;public String colName; // 列名public String dataType; // 类型public String schema; // 数据库public String table; // 表public Colum(String schema, String table, int idx, String colName, String dataType) {this.schema = schema;this.table = table;this.colName = colName;this.dataType = dataType;this.inx = idx;}
}

Conf

package com.zrj.util.binlog;import lombok.AllArgsConstructor;
import lombok.Data;/*** 数据库配置** @author zrj* @since 2021/7/27**/
@Data
@AllArgsConstructor
public class Conf {private String host;private int port;private String username;private String passwd;
}

BinLogItem

package com.zrj.util.binlog;import com.github.shyiko.mysql.binlog.event.EventType;
import com.google.common.collect.Maps;
import lombok.Data;import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;import static com.github.shyiko.mysql.binlog.event.EventType.isDelete;
import static com.github.shyiko.mysql.binlog.event.EventType.isWrite;/*** binlog对象** @author zrj* @since 2021/7/26**/
@Data
public class BinLogItem implements Serializable {private static final long serialVersionUID = 5503152746318421290L;private String dbTable;private EventType eventType;private Long timestamp = null;private Long serverId = null;// 存储字段-之前的值之后的值private Map<String, Serializable> before = null;private Map<String, Serializable> after = null;// 存储字段--类型private Map<String, Colum> colums = null;/*** 新增或者删除操作数据格式化*/public static BinLogItem itemFromInsertOrDeleted(Serializable[] row, Map<String, Colum> columMap, EventType eventType) {if (null == row || null == columMap) {return null;}if (row.length != columMap.size()) {return null;}// 初始化ItemBinLogItem item = new BinLogItem();item.eventType = eventType;item.colums = columMap;item.before = Maps.newHashMap();item.after = Maps.newHashMap();Map<String, Serializable> beOrAf = Maps.newHashMap();columMap.entrySet().forEach(entry -> {String key = entry.getKey();Colum colum = entry.getValue();beOrAf.put(key, row[colum.inx]);});// 写操作放after,删操作放beforeif (isWrite(eventType)) {item.after = beOrAf;}if (isDelete(eventType)) {item.before = beOrAf;}return item;}/*** 更新操作数据格式化*/public static BinLogItem itemFromUpdate(Map.Entry<Serializable[], Serializable[]> mapEntry, Map<String, Colum> columMap, EventType eventType) {if (null == mapEntry || null == columMap) {return null;}// 初始化ItemBinLogItem item = new BinLogItem();item.eventType = eventType;item.colums = columMap;item.before = Maps.newHashMap();item.after = Maps.newHashMap();Map<String, Serializable> be = Maps.newHashMap();Map<String, Serializable> af = Maps.newHashMap();columMap.entrySet().forEach(entry -> {String key = entry.getKey();Colum colum = entry.getValue();be.put(key, mapEntry.getKey()[colum.inx]);af.put(key, mapEntry.getValue()[colum.inx]);});item.before = be;item.after = af;return item;}}

BinLogUtils

package com.ennova.tour.search.core.service.binlog;import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.ennova.tour.search.core.service.enums.CategoryEnum;
import com.ennova.tour.search.dal.mapper.ext.SearchStoreLogoExtMapper;
import com.ennova.tour.search.dal.po.mbg.SearchStoreLogo;
import com.github.shyiko.mysql.binlog.event.EventType;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;
import java.sql.*;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;import static com.github.shyiko.mysql.binlog.event.EventType.isDelete;
import static com.github.shyiko.mysql.binlog.event.EventType.isUpdate;
import static com.github.shyiko.mysql.binlog.event.EventType.isWrite;/*** 监听工具** @author zrj* @since 2021/7/27**/
@Slf4j
@Component
public class BinLogUtils {private static BinLogUtils binLogUtils;@Resourceprivate SearchStoreLogoExtMapper searchStoreLogoExtMapper;@PostConstructpublic void init() {binLogUtils = this;binLogUtils.searchStoreLogoExtMapper = this.searchStoreLogoExtMapper;}/*** 拼接dbTable*/public static String getdbTable(String db, String table) {return db + "-" + table;}/*** 获取columns集合*/public static Map<String, Colum> getColMap(Conf conf, String db, String table) throws ClassNotFoundException {try {Class.forName("com.mysql.jdbc.Driver");// 保存当前注册的表的colum信息Connection connection = DriverManager.getConnection("jdbc:mysql://" + conf.getHost() + ":" + conf.getPort(), conf.getUsername(), conf.getPasswd());// 执行sqlString preSql = "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, ORDINAL_POSITION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? and TABLE_NAME = ?";PreparedStatement ps = connection.prepareStatement(preSql);ps.setString(1, db);ps.setString(2, table);ResultSet rs = ps.executeQuery();Map<String, Colum> map = new HashMap<>(rs.getRow());while (rs.next()) {String schema = rs.getString("TABLE_SCHEMA");String tableName = rs.getString("TABLE_NAME");String column = rs.getString("COLUMN_NAME");int idx = rs.getInt("ORDINAL_POSITION");String dataType = rs.getString("DATA_TYPE");if (column != null && idx >= 1) {map.put(column, new Colum(schema, tableName, idx - 1, column, dataType)); // sql的位置从1开始}}ps.close();rs.close();return map;} catch (SQLException e) {log.error("load db conf error, db_table={}:{} ", db, table, e);}return null;}/*** 根据table获取code** @param table* @return java.lang.Integer*/public static Integer getCodeByTable(String table) {if (StrUtil.isEmpty(table)) {return null;}return CategoryEnum.getCodeByTab(table);}public static String getMsgByTab(String table) {if (StrUtil.isEmpty(table)) {return null;}return CategoryEnum.getMsgByTab(table);}/*** 根据DBTable获取table** @param dbTable* @return java.lang.String*/public static String getTable(String dbTable) {if (StrUtil.isEmpty(dbTable)) {return "";}String[] split = dbTable.split("-");if (split.length == 2) {return split[1];}return "";}/*** 将逗号拼接字符串转List** @param str* @return*/public static List<String> getListByStr(String str) {if (StrUtil.isEmpty(str)) {return Lists.newArrayList();}return Arrays.asList(str.split(","));}/*** 根据操作类型获取对应集合** @param binLogItem* @return*/public static Map<String, Serializable> getOptMap(BinLogItem binLogItem) {// 获取操作类型EventType eventType = binLogItem.getEventType();if (isWrite(eventType) || isUpdate(eventType)) {return binLogItem.getAfter();}if (isDelete(eventType)) {return binLogItem.getBefore();}return null;}/*** 获取操作类型** @param binLogItem* @return*/public static Integer getOptType(BinLogItem binLogItem) {// 获取操作类型EventType eventType = binLogItem.getEventType();if (isWrite(eventType)) {return 1;}if (isUpdate(eventType)) {return 2;}if (isDelete(eventType)) {return 3;}return null;}/*** 根据storeId获取imgUrl*/public static String getImgUrl(Long storeId) {if (storeId == null) {return "";}//获取urlSearchStoreLogo searchStoreLogo = new SearchStoreLogo();searchStoreLogo.setStoreId(storeId);List<SearchStoreLogo> searchStoreLogos = binLogUtils.searchStoreLogoExtMapper.selectList(searchStoreLogo);if (CollectionUtil.isNotEmpty(searchStoreLogos)) {SearchStoreLogo storeLogo = searchStoreLogos.get(0);if (storeLogo != null) {return storeLogo.getStoreLogo();}}return "";}/*** 格式化date** @param date* @return java.util.Date*/public static Date getDateFormat(Date date) {if (date == null) {return null;}String dateFormat = "yyyy-MM-dd HH:mm:ss";String strDate = DateUtil.format(date, dateFormat);if (StrUtil.isEmpty(strDate)) {return null;}Date formatDate = DateUtil.parse(strDate, dateFormat);return formatDate;}
}

BinLogListener

package com.zrj.util.binlog;/*** BinLogListener监听器** @author zrj* @since 2021/7/26**/
@FunctionalInterface
public interface BinLogListener {void onEvent(BinLogItem item);
}

MysqlBinLogListener

package com.zrj.util.binlog;import com.github.shyiko.mysql.binlog.BinaryLogClient;
import com.github.shyiko.mysql.binlog.event.*;
import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import lombok.extern.slf4j.Slf4j;
import org.kohsuke.args4j.Option;import javax.annotation.Resource;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.*;import static com.github.shyiko.mysql.binlog.event.EventType.*;
import static com.zrj.util.binlog.BinLogUtils.getColMap;
import static com.zrj.util.binlog.BinLogUtils.getdbTable;/*** 数据库监听器** @author zrj* @since 2021/7/26**/
@Slf4j
public class MysqlBinLogListener implements BinaryLogClient.EventListener {@Option(name = "-binlog-consume_threads", usage = "the thread num of consumer")private int consumerThreads = BinLogConstants.consumerThreads;private BinaryLogClient parseClient;private BlockingQueue<BinLogItem> queue;private final ExecutorService consumer;// 存放每张数据表对应的listenerprivate Multimap<String, BinLogListener> listeners;private Conf conf;private Map<String, Map<String, Colum>> dbTableCols;private String dbTable;/*** 监听器初始化** @param conf*/public MysqlBinLogListener(Conf conf) {BinaryLogClient client = new BinaryLogClient(conf.getHost(), conf.getPort(), conf.getUsername(), conf.getPasswd());EventDeserializer eventDeserializer = new EventDeserializer();//eventDeserializer.setCompatibilityMode(//序列化//        EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG,//        EventDeserializer.CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY//);client.setEventDeserializer(eventDeserializer);this.parseClient = client;this.queue = new ArrayBlockingQueue<>(1024);this.conf = conf;this.listeners = ArrayListMultimap.create();this.dbTableCols = new ConcurrentHashMap<>();this.consumer = Executors.newFixedThreadPool(consumerThreads);}/*** 监听处理** @param event*/@Overridepublic void onEvent(Event event) {EventType eventType = event.getHeader().getEventType();if (eventType == EventType.TABLE_MAP) {TableMapEventData tableData = event.getData();String db = tableData.getDatabase();String table = tableData.getTable();dbTable = getdbTable(db, table);}// 只处理添加删除更新三种操作if (isWrite(eventType) || isUpdate(eventType) || isDelete(eventType)) {if (isWrite(eventType)) {WriteRowsEventData data = event.getData();for (Serializable[] row : data.getRows()) {if (dbTableCols.containsKey(dbTable)) {BinLogItem item = BinLogItem.itemFromInsertOrDeleted(row, dbTableCols.get(dbTable), eventType);item.setDbTable(dbTable);queue.add(item);}}}if (isUpdate(eventType)) {UpdateRowsEventData data = event.getData();for (Map.Entry<Serializable[], Serializable[]> row : data.getRows()) {if (dbTableCols.containsKey(dbTable)) {BinLogItem item = BinLogItem.itemFromUpdate(row, dbTableCols.get(dbTable), eventType);item.setDbTable(dbTable);queue.add(item);}}}if (isDelete(eventType)) {DeleteRowsEventData data = event.getData();for (Serializable[] row : data.getRows()) {if (dbTableCols.containsKey(dbTable)) {BinLogItem item = BinLogItem.itemFromInsertOrDeleted(row, dbTableCols.get(dbTable), eventType);item.setDbTable(dbTable);queue.add(item);}}}}}/*** 注册监听** @param db       数据库* @param table    操作表* @param listener 监听器* @throws Exception*/public void regListener(String db, String table, BinLogListener listener) throws Exception {String dbTable = getdbTable(db, table);// 获取字段集合Map<String, Colum> cols = getColMap(conf, db, table);// 保存字段信息dbTableCols.put(dbTable, cols);// 保存当前注册的listenerlisteners.put(dbTable, listener);}/*** 开启多线程消费** @throws IOException*/public void parse() throws IOException {parseClient.registerEventListener(this);for (int i = 0; i < consumerThreads; i++) {consumer.submit(() -> {while (true) {if (queue.size() > 0) {try {BinLogItem item = queue.take();String dbtable = item.getDbTable();listeners.get(dbtable).forEach(binLogListener -> binLogListener.onEvent(item));} catch (InterruptedException e) {e.printStackTrace();}}Thread.sleep(BinLogConstants.queueSleep);}});}parseClient.connect();}}

TourBinLogListener

package com.zrj.util.binlog;import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.List;/*** 乐游监听器* SpringBoot启动成功后的执行业务线程操作* CommandLineRunner去实现此操作* 在有多个可被执行的业务时,通过使用 @Order 注解,设置各个线程的启动顺序(value值由小到大表示启动顺序)。* 多个实现CommandLineRunner接口的类必须要设置启动顺序,不让程序启动会报错!** @author zrj* @since 2021/7/27**/
@Slf4j
@Component
@Order(value = 1)
public class TourBinLogListener implements CommandLineRunner {@Resourceprivate BinLogConstants binLogConstants;@Overridepublic void run(String... args) throws Exception {log.info("初始化配置信息:" + binLogConstants.toString());// 初始化配置信息Conf conf = new Conf(binLogConstants.getHost(), binLogConstants.getPort(), binLogConstants.getUsername(), binLogConstants.getPasswd());// 初始化监听器MysqlBinLogListener mysqlBinLogListener = new MysqlBinLogListener(conf);// 获取table集合List<String> tableList = BinLogUtils.getListByStr(binLogConstants.getTable());if (CollectionUtil.isEmpty(tableList)) {return;}// 注册监听tableList.forEach(table -> {log.info("注册监听信息,注册DB:" + binLogConstants.getDb() + ",注册表:" + table);try {mysqlBinLogListener.regListener(binLogConstants.getDb(), table, item -> {log.info("监听逻辑处理");});} catch (Exception e) {log.error("BinLog监听异常:" + e);}});// 多线程消费mysqlBinLogListener.parse();}
}

5.实现验证

启动应用,操作数据库。

Java监听mysql的binlog详解(mysql-binlog-connector)相关推荐

  1. Oracle11g安装教程、配置实例、监听、客户端程序详解_Windows篇

    Oracle11g安装教程.配置实例.监听.客户端程序详解_Windows篇 文章目录 Oracle11g安装教程.配置实例.监听.客户端程序详解_Windows篇 前言 一.数据库的安装前准备,前提 ...

  2. mysql like escape_详解MySQL like如何查询包含#39;%#39;的字段(ESCAPE用法)

    在SQl like语句中,比如 SELECT * FROM user WHERE username LIKE '%luchi%' SELECT * FROM user WHERE username L ...

  3. 监听器之jp@gc详解

    一.jp@gc - Actiive Threads Over Time 不同时间活动用户数量展示 下面是一个阶梯加压测试的图标   二.jp@gc - Transactions per Second ...

  4. epoll监听文件_epoll使用详解

    epoll介绍 epoll的行为与poll(2)相似,监视多个有IO事件的文件描述符.epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触 ...

  5. bootstrap源码之滚动监听组件scrollspy.js详解

    其实滚动监听使用的情况还是很多的,比如导航居于右侧,当主题内容滚动某一块的时候,右侧导航对应的要高亮. 实现功能 1.当滚动区域内设置的hashkey距离顶点到有效位置时,就关联设置其导航上的指定项 ...

  6. 修改php-fpm监听端口,php-fpm配置详解

    php5.3自带php-fpm /usr/local/php/etc/php-fpm.conf pid = run/php-fpm.pid pid设置,默认在安装目录中的var/run/php-fpm ...

  7. Vue.js中 watch(深度监听-deep)原理以及详解

    handler方法和immediate属性 这里 watch 的一个特点是,最初绑定的时候是不会执行的,要等到 firstName 改变时才执行监听计算.那我们想要一开始就让他最初绑定的时候就执行改怎 ...

  8. vue中监听属性watch使用详解

    深度监听: (1)vue中的watch默认不监测对象内部值的改变(一层). (2)配置deep:true可以监测对象内部值改变(多层). 备注: (1)vue自身可以监测对象内部值的改变,但vue提供 ...

  9. jQuery绑定事件监听bind和移除事件监听unbind用法实例详解

    这里分别采用后bind(eventType,[data],Listener)//data为可选参数,one()该方法绑定的事件触发一次后自动删除,unbind(eventType,Listener), ...

  10. mysql主从复制gtid_详解MySQL主从复制实战 - 基于GTID的复制

    基于GTID的复制 简介 基于GTID的复制是MySQL 5.6后新增的复制方式. GTID (global transaction identifier) 即全局事务ID, 保证了在每个在主库上提交 ...

最新文章

  1. linux内核内存管理的三个阶段分析
  2. asp net code
  3. PHP 销毁指定目录
  4. c语言mysql 学生信息管理系统_学生信息管理系统学生时代小作品源码(C语言版)...
  5. 软件测试基础——理论知识
  6. 案例研究 路由器到路由器EOMPLS---基于端口
  7. grasshop 犀牛5.0下载_神契幻奇谭 v1.129版发布 快来下载神契幻奇谭2020最新官方版...
  8. 怎么使用CAD编辑工具将图纸中的文本对齐
  9. 安装Mendeley后Word中没有出现对应的Mendeley插件
  10. vue 嵌套表格组件_vue+element-ui实现嵌套表格导出
  11. 【实践与问题解决28】最全超分辨率(SR)数据集介绍以及多方法下载链接
  12. epub格式电子书剖析之一:文档构成
  13. yocto系列讲解[理论篇]68 -Yocto版本信息查询稳定版本和EOL版本
  14. 百度地图离线API2.0(含示例,可完全断网访问)
  15. Python超级详细的上台阶楼梯问题,算法运行速度极快,内含计算排列的方法。问题:有n级台阶,每步可以走一级或两级,问有多少种不同的走法。k为传入的参数,默认值为3
  16. 基于SpringBoot开发一套完整的项目(四)准备工作
  17. 面向对象综合训练综合练习
  18. php对接海康api样例
  19. 精仿百思不得姐客户端应用iOS源码
  20. Java学习 - URL短地址压缩算法

热门文章

  1. QUECTEL上海移远4G通讯CAT4模组EC20CEFAG模块串口调试指南之02EC20模组硬件供电和开关机复位操作
  2. Greenplum——基于Greenplum-Spark Connector的Spark脚本开发及遇到的坑
  3. 国内外优秀的垂直搜索引擎
  4. Unix 时间戳 (Unix Timestamp) 与 Windows 时间转换工具
  5. TCP/IP协议分层模型详解
  6. Hexo+valine评论微信通知
  7. CCW 下填Modbus Mapping简单方法
  8. Android RemoteViews 解析
  9. 关系;关系模式;关系数据库
  10. Hive 数据倾斜问题定位排查及解决(实际案例)