Wenjun Ruan
2 years ago
committed by
GitHub
23 changed files with 1386 additions and 40 deletions
@ -0,0 +1,42 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!-- |
||||||
|
~ Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
~ contributor license agreements. See the NOTICE file distributed with |
||||||
|
~ this work for additional information regarding copyright ownership. |
||||||
|
~ The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
~ (the "License"); you may not use this file except in compliance with |
||||||
|
~ the License. You may obtain a copy of the License at |
||||||
|
~ |
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
~ |
||||||
|
~ Unless required by applicable law or agreed to in writing, software |
||||||
|
~ distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
~ See the License for the specific language governing permissions and |
||||||
|
~ limitations under the License. |
||||||
|
--> |
||||||
|
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<parent> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler-registry</artifactId> |
||||||
|
<version>dev-SNAPSHOT</version> |
||||||
|
</parent> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
|
||||||
|
<artifactId>dolphinscheduler-registry-all</artifactId> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler-registry-zookeeper</artifactId> |
||||||
|
<version>${project.version}</version> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler-registry-mysql</artifactId> |
||||||
|
<version>${project.version}</version> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
</project> |
@ -0,0 +1,36 @@ |
|||||||
|
# Introduction |
||||||
|
|
||||||
|
This module is the mysql registry plugin module, this plugin will use mysql as the registry center. |
||||||
|
|
||||||
|
# How to use |
||||||
|
|
||||||
|
If you want to set the registry center as mysql, you need to do the below two steps: |
||||||
|
|
||||||
|
1. Initialize the mysql table |
||||||
|
|
||||||
|
You can directly execute the sql script `src/main/resources/mysql_registry_init.sql`. |
||||||
|
|
||||||
|
2. Open the config |
||||||
|
|
||||||
|
You need to set the registry properties in master/worker/api's appplication.yml |
||||||
|
|
||||||
|
```yaml |
||||||
|
registry: |
||||||
|
type: mysql |
||||||
|
term-refresh-interval: 2s |
||||||
|
term-expire-times: 3 |
||||||
|
hikari-config: |
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver |
||||||
|
jdbc-url: jdbc:mysql://127.0.0.1:3306/dolphinscheduler |
||||||
|
username: root |
||||||
|
password: root |
||||||
|
maximum-pool-size: 5 |
||||||
|
connection-timeout: 9000 |
||||||
|
idle-timeout: 600000 |
||||||
|
``` |
||||||
|
|
||||||
|
After do this two steps, you can start your DolphinScheduler cluster, your cluster will use mysql as registry center to |
||||||
|
store server metadata. |
||||||
|
|
||||||
|
NOTE: You need to add `mysql-connector-java.jar` into DS classpath, since this plugin will not bundle this driver in distribution. |
||||||
|
You can get the detail about <a href="https://dolphinscheduler.apache.org/en-us/docs/latest/user_doc/guide/installation/pseudo-cluster.html">Initialize the Database</a> |
@ -0,0 +1,56 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!-- |
||||||
|
~ Licensed to Apache Software Foundation (ASF) under one or more contributor |
||||||
|
~ license agreements. See the NOTICE file distributed with |
||||||
|
~ this work for additional information regarding copyright |
||||||
|
~ ownership. Apache Software Foundation (ASF) licenses this file to you under |
||||||
|
~ the Apache License, Version 2.0 (the "License"); you may |
||||||
|
~ not use this file except in compliance with the License. |
||||||
|
~ You may obtain a copy of the License at |
||||||
|
~ |
||||||
|
~ http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
~ |
||||||
|
~ Unless required by applicable law or agreed to in writing, |
||||||
|
~ software distributed under the License is distributed on an |
||||||
|
~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||||
|
~ KIND, either express or implied. See the License for the |
||||||
|
~ specific language governing permissions and limitations |
||||||
|
~ under the License. |
||||||
|
--> |
||||||
|
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||||
|
<parent> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler-registry-plugins</artifactId> |
||||||
|
<version>dev-SNAPSHOT</version> |
||||||
|
</parent> |
||||||
|
<modelVersion>4.0.0</modelVersion> |
||||||
|
|
||||||
|
<artifactId>dolphinscheduler-registry-mysql</artifactId> |
||||||
|
|
||||||
|
<dependencies> |
||||||
|
<dependency> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler-registry-api</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>org.apache.dolphinscheduler</groupId> |
||||||
|
<artifactId>dolphinscheduler-common</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>com.zaxxer</groupId> |
||||||
|
<artifactId>HikariCP</artifactId> |
||||||
|
</dependency> |
||||||
|
<dependency> |
||||||
|
<groupId>mysql</groupId> |
||||||
|
<artifactId>mysql-connector-java</artifactId> |
||||||
|
</dependency> |
||||||
|
|
||||||
|
<dependency> |
||||||
|
<groupId>org.slf4j</groupId> |
||||||
|
<artifactId>slf4j-api</artifactId> |
||||||
|
</dependency> |
||||||
|
</dependencies> |
||||||
|
|
||||||
|
</project> |
@ -0,0 +1,326 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql; |
||||||
|
|
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.model.DataType; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.model.MysqlRegistryData; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.model.MysqlRegistryLock; |
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
|
||||||
|
import java.sql.Array; |
||||||
|
import java.sql.Connection; |
||||||
|
import java.sql.PreparedStatement; |
||||||
|
import java.sql.ResultSet; |
||||||
|
import java.sql.SQLException; |
||||||
|
import java.sql.SQLIntegrityConstraintViolationException; |
||||||
|
import java.sql.Statement; |
||||||
|
import java.sql.Timestamp; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig; |
||||||
|
import com.zaxxer.hikari.HikariDataSource; |
||||||
|
|
||||||
|
/** |
||||||
|
* Used to CRUD from mysql |
||||||
|
*/ |
||||||
|
public class MysqlOperator implements AutoCloseable { |
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(MysqlOperator.class); |
||||||
|
|
||||||
|
private final HikariDataSource dataSource; |
||||||
|
private final long expireTimeWindow; |
||||||
|
|
||||||
|
public MysqlOperator(MysqlRegistryProperties registryProperties) { |
||||||
|
this.expireTimeWindow = registryProperties.getTermExpireTimes() * registryProperties.getTermRefreshInterval().toMillis(); |
||||||
|
|
||||||
|
HikariConfig hikariConfig = registryProperties.getHikariConfig(); |
||||||
|
hikariConfig.setPoolName("MysqlRegistryDataSourcePool"); |
||||||
|
|
||||||
|
this.dataSource = new HikariDataSource(hikariConfig); |
||||||
|
} |
||||||
|
|
||||||
|
public void healthCheck() throws SQLException { |
||||||
|
String sql = "select 1 from t_ds_mysql_registry_data"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql); |
||||||
|
ResultSet resultSet = preparedStatement.executeQuery();) { |
||||||
|
// if no exception, the healthCheck success
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<MysqlRegistryData> queryAllMysqlRegistryData() throws SQLException { |
||||||
|
String sql = "select id, `key`, data, type, create_time, last_update_time from t_ds_mysql_registry_data"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql); |
||||||
|
ResultSet resultSet = preparedStatement.executeQuery()) { |
||||||
|
List<MysqlRegistryData> result = new ArrayList<>(resultSet.getFetchSize()); |
||||||
|
while (resultSet.next()) { |
||||||
|
MysqlRegistryData mysqlRegistryData = MysqlRegistryData.builder() |
||||||
|
.id(resultSet.getLong("id")) |
||||||
|
.key(resultSet.getString("key")) |
||||||
|
.data(resultSet.getString("data")) |
||||||
|
.type(resultSet.getInt("type")) |
||||||
|
.createTime(resultSet.getTimestamp("create_time")) |
||||||
|
.lastUpdateTime(resultSet.getTimestamp("last_update_time")) |
||||||
|
.build(); |
||||||
|
result.add(mysqlRegistryData); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public long insertOrUpdateEphemeralData(String key, String value) throws SQLException { |
||||||
|
String sql = "INSERT INTO t_ds_mysql_registry_data (`key`, data, type, create_time, last_update_time) VALUES (?, ?, ?, current_timestamp, current_timestamp)" + |
||||||
|
"ON DUPLICATE KEY UPDATE data=?, last_update_time=current_timestamp"; |
||||||
|
// put a ephemeralData
|
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { |
||||||
|
preparedStatement.setString(1, key); |
||||||
|
preparedStatement.setString(2, value); |
||||||
|
preparedStatement.setInt(3, DataType.EPHEMERAL.getTypeValue()); |
||||||
|
preparedStatement.setString(4, value); |
||||||
|
int insertCount = preparedStatement.executeUpdate(); |
||||||
|
ResultSet generatedKeys = preparedStatement.getGeneratedKeys(); |
||||||
|
if (insertCount < 1 || !generatedKeys.next()) { |
||||||
|
throw new SQLException("Insert or update ephemeral data error"); |
||||||
|
} |
||||||
|
return generatedKeys.getLong(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public long insertOrUpdatePersistentData(String key, String value) throws SQLException { |
||||||
|
String sql = "INSERT INTO t_ds_mysql_registry_data (`key`, data, type, create_time, last_update_time) VALUES (?, ?, ?, current_timestamp, current_timestamp)" + |
||||||
|
"ON DUPLICATE KEY UPDATE data=?, last_update_time=current_timestamp"; |
||||||
|
// put a persistent Data
|
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { |
||||||
|
preparedStatement.setString(1, key); |
||||||
|
preparedStatement.setString(2, value); |
||||||
|
preparedStatement.setInt(3, DataType.PERSISTENT.getTypeValue()); |
||||||
|
preparedStatement.setString(4, value); |
||||||
|
int insertCount = preparedStatement.executeUpdate(); |
||||||
|
ResultSet generatedKeys = preparedStatement.getGeneratedKeys(); |
||||||
|
if (insertCount < 1 || !generatedKeys.next()) { |
||||||
|
throw new SQLException("Insert or update persistent data error"); |
||||||
|
} |
||||||
|
return generatedKeys.getLong(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void deleteEphemeralData(String key) throws SQLException { |
||||||
|
String sql = "DELETE from t_ds_mysql_registry_data where `key` = ? and type = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setString(1, key); |
||||||
|
preparedStatement.setInt(2, DataType.EPHEMERAL.getTypeValue()); |
||||||
|
preparedStatement.execute(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void deleteEphemeralData(long ephemeralNodeId) throws SQLException { |
||||||
|
String sql = "DELETE from t_ds_mysql_registry_data where `id` = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setLong(1, ephemeralNodeId); |
||||||
|
preparedStatement.execute(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void deletePersistentData(String key) throws SQLException { |
||||||
|
String sql = "DELETE from t_ds_mysql_registry_data where `key` = ? and type = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setString(1, key); |
||||||
|
preparedStatement.setInt(2, DataType.PERSISTENT.getTypeValue()); |
||||||
|
preparedStatement.execute(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void clearExpireLock() { |
||||||
|
String sql = "delete from t_ds_mysql_registry_lock where last_term < ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setTimestamp(1, |
||||||
|
new Timestamp(System.currentTimeMillis() - expireTimeWindow)); |
||||||
|
int i = preparedStatement.executeUpdate(); |
||||||
|
if (i > 0) { |
||||||
|
logger.info("Clear expire lock, size: {}", i); |
||||||
|
} |
||||||
|
} catch (Exception ex) { |
||||||
|
logger.warn("Clear expire lock from mysql registry error", ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void clearExpireEphemeralDate() { |
||||||
|
String sql = "delete from t_ds_mysql_registry_data where last_update_time < ? and type = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setTimestamp(1, new Timestamp(System.currentTimeMillis() - expireTimeWindow)); |
||||||
|
preparedStatement.setInt(2, DataType.EPHEMERAL.getTypeValue()); |
||||||
|
int i = preparedStatement.executeUpdate(); |
||||||
|
if (i > 0) { |
||||||
|
logger.info("clear expire ephemeral data, size:{}", i); |
||||||
|
} |
||||||
|
} catch (Exception ex) { |
||||||
|
logger.warn("Clear expire ephemeral data from mysql registry error", ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public MysqlRegistryData getData(String key) throws SQLException { |
||||||
|
String sql = "SELECT id, `key`, data, type, create_time, last_update_time FROM t_ds_mysql_registry_data WHERE `key` = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setString(1, key); |
||||||
|
try (ResultSet resultSet = preparedStatement.executeQuery()) { |
||||||
|
if (!resultSet.next()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return MysqlRegistryData.builder() |
||||||
|
.id(resultSet.getLong("id")) |
||||||
|
.key(resultSet.getString("key")) |
||||||
|
.data(resultSet.getString("data")) |
||||||
|
.type(resultSet.getInt("type")) |
||||||
|
.createTime(resultSet.getTimestamp("create_time")) |
||||||
|
.lastUpdateTime(resultSet.getTimestamp("last_update_time")) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public List<String> getChildren(String key) throws SQLException { |
||||||
|
String sql = "SELECT `key` from t_ds_mysql_registry_data where `key` like ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setString(1, key + "%"); |
||||||
|
try (ResultSet resultSet = preparedStatement.executeQuery()) { |
||||||
|
List<String> result = new ArrayList<>(resultSet.getFetchSize()); |
||||||
|
while (resultSet.next()) { |
||||||
|
String fullPath = resultSet.getString("key"); |
||||||
|
if (fullPath.length() > key.length()) { |
||||||
|
result.add(StringUtils.substringBefore(fullPath.substring(key.length() + 1), "/")); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public boolean existKey(String key) throws SQLException { |
||||||
|
String sql = "SELECT 1 FROM t_ds_mysql_registry_data WHERE `key` = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setString(1, key); |
||||||
|
try (ResultSet resultSet = preparedStatement.executeQuery()) { |
||||||
|
if (resultSet.next()) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Try to acquire the target Lock, if cannot acquire, return null. |
||||||
|
*/ |
||||||
|
public MysqlRegistryLock tryToAcquireLock(String key) throws SQLException { |
||||||
|
String sql = "INSERT INTO t_ds_mysql_registry_lock (`key`, lock_owner, last_term, last_update_time, create_time) VALUES (?, ?, current_timestamp, current_timestamp, current_timestamp)"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { |
||||||
|
preparedStatement.setString(1, key); |
||||||
|
preparedStatement.setString(2, MysqlRegistryConstant.LOCK_OWNER); |
||||||
|
preparedStatement.executeUpdate(); |
||||||
|
try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) { |
||||||
|
if (resultSet.next()) { |
||||||
|
long newLockId = resultSet.getLong(1); |
||||||
|
return getLockById(newLockId); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} catch (SQLIntegrityConstraintViolationException e) { |
||||||
|
// duplicate exception
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public MysqlRegistryLock getLockById(long lockId) throws SQLException { |
||||||
|
String sql = "SELECT `id`, `key`, lock_owner, last_term, last_update_time, create_time FROM t_ds_mysql_registry_lock WHERE id = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setLong(1, lockId); |
||||||
|
try (ResultSet resultSet = preparedStatement.executeQuery()) { |
||||||
|
if (resultSet.next()) { |
||||||
|
return MysqlRegistryLock.builder() |
||||||
|
.id(resultSet.getLong("id")) |
||||||
|
.key(resultSet.getString("key")) |
||||||
|
.lockOwner(resultSet.getString("lock_owner")) |
||||||
|
.lastTerm(resultSet.getTimestamp("last_term")) |
||||||
|
.lastUpdateTime(resultSet.getTimestamp("last_update_time")) |
||||||
|
.createTime(resultSet.getTimestamp("create_time")) |
||||||
|
.build(); |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// release the lock
|
||||||
|
public boolean releaseLock(long lockId) throws SQLException { |
||||||
|
String sql = "DELETE FROM t_ds_mysql_registry_lock WHERE id = ?"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
preparedStatement.setLong(1, lockId); |
||||||
|
int i = preparedStatement.executeUpdate(); |
||||||
|
return i > 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public boolean updateEphemeralDataTerm(Collection<Long> ephemeralDateIds) throws SQLException { |
||||||
|
String sql = "update t_ds_mysql_registry_data set `last_update_time` = current_timestamp() where `id` IN (?)"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
Array idArray = connection.createArrayOf("bigint", ephemeralDateIds.toArray()); |
||||||
|
preparedStatement.setArray(1, idArray); |
||||||
|
return preparedStatement.executeUpdate() > 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public boolean updateLockTerm(List<Long> lockIds) throws SQLException { |
||||||
|
String sql = "update t_ds_mysql_registry_lock set `last_term` = current_timestamp and `last_update_time` = current_timestamp where `id` IN (?)"; |
||||||
|
try (Connection connection = dataSource.getConnection(); |
||||||
|
PreparedStatement preparedStatement = connection.prepareStatement(sql)) { |
||||||
|
Array idArray = connection.createArrayOf("bigint", lockIds.toArray()); |
||||||
|
preparedStatement.setArray(1, idArray); |
||||||
|
return preparedStatement.executeUpdate() > 0; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() throws Exception { |
||||||
|
if (!dataSource.isClosed()) { |
||||||
|
try (HikariDataSource closedDatasource = this.dataSource) { |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,177 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql; |
||||||
|
|
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.task.EphemeralDateManager; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.task.RegistryLockManager; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.task.SubscribeDataManager; |
||||||
|
import org.apache.dolphinscheduler.registry.api.ConnectionListener; |
||||||
|
import org.apache.dolphinscheduler.registry.api.Registry; |
||||||
|
import org.apache.dolphinscheduler.registry.api.RegistryException; |
||||||
|
import org.apache.dolphinscheduler.registry.api.SubscribeListener; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
import java.time.Duration; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
import javax.annotation.PostConstruct; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* This is one of the implementation of {@link Registry}, with this implementation, you need to rely on mysql database to |
||||||
|
* store the DolphinScheduler master/worker's metadata and do the server registry/unRegistry. |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
@ConditionalOnProperty(prefix = "registry", name = "type", havingValue = "mysql") |
||||||
|
public class MysqlRegistry implements Registry { |
||||||
|
|
||||||
|
private static Logger LOGGER = LoggerFactory.getLogger(MysqlRegistry.class); |
||||||
|
|
||||||
|
private final EphemeralDateManager ephemeralDateManager; |
||||||
|
private final SubscribeDataManager subscribeDataManager; |
||||||
|
private final RegistryLockManager registryLockManager; |
||||||
|
private final MysqlOperator mysqlOperator; |
||||||
|
|
||||||
|
public MysqlRegistry(MysqlRegistryProperties mysqlRegistryProperties) { |
||||||
|
this.mysqlOperator = new MysqlOperator(mysqlRegistryProperties); |
||||||
|
mysqlOperator.clearExpireLock(); |
||||||
|
mysqlOperator.clearExpireEphemeralDate(); |
||||||
|
this.ephemeralDateManager = new EphemeralDateManager(mysqlRegistryProperties, mysqlOperator); |
||||||
|
this.subscribeDataManager = new SubscribeDataManager(mysqlRegistryProperties, mysqlOperator); |
||||||
|
this.registryLockManager = new RegistryLockManager(mysqlRegistryProperties, mysqlOperator); |
||||||
|
LOGGER.info("Initialize Mysql Registry..."); |
||||||
|
} |
||||||
|
|
||||||
|
@PostConstruct |
||||||
|
public void start() { |
||||||
|
LOGGER.info("Starting Mysql Registry..."); |
||||||
|
// start a mysql connect check
|
||||||
|
ephemeralDateManager.start(); |
||||||
|
subscribeDataManager.start(); |
||||||
|
registryLockManager.start(); |
||||||
|
LOGGER.info("Started Mysql Registry..."); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean subscribe(String path, SubscribeListener listener) { |
||||||
|
// new a schedule thread to query the path, if the path
|
||||||
|
subscribeDataManager.addListener(path, listener); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void unsubscribe(String path) { |
||||||
|
subscribeDataManager.removeListener(path); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void addConnectionStateListener(ConnectionListener listener) { |
||||||
|
// check the current connection
|
||||||
|
ephemeralDateManager.addConnectionListener(listener); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String get(String key) { |
||||||
|
// get the key value
|
||||||
|
return subscribeDataManager.getData(key); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void put(String key, String value, boolean deleteOnDisconnect) { |
||||||
|
try { |
||||||
|
if (deleteOnDisconnect) { |
||||||
|
// when put a ephemeralData will new a scheduler thread to update it
|
||||||
|
ephemeralDateManager.insertOrUpdateEphemeralData(key, value); |
||||||
|
} else { |
||||||
|
mysqlOperator.insertOrUpdatePersistentData(key, value); |
||||||
|
} |
||||||
|
} catch (Exception ex) { |
||||||
|
throw new RegistryException(String.format("put key:%s, value:%s error", key, value), ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void delete(String key) { |
||||||
|
try { |
||||||
|
mysqlOperator.deleteEphemeralData(key); |
||||||
|
mysqlOperator.deletePersistentData(key); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RegistryException(String.format("Delete key: %s error", key), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Collection<String> children(String key) { |
||||||
|
try { |
||||||
|
return mysqlOperator.getChildren(key); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RegistryException(String.format("Get key: %s children error", key), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean exists(String key) { |
||||||
|
try { |
||||||
|
return mysqlOperator.existKey(key); |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RegistryException(String.format("Check key: %s exist error", key), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean acquireLock(String key) { |
||||||
|
try { |
||||||
|
registryLockManager.acquireLock(key); |
||||||
|
return true; |
||||||
|
} catch (RegistryException e) { |
||||||
|
throw e; |
||||||
|
} catch (Exception e) { |
||||||
|
throw new RegistryException(String.format("Acquire lock: %s error", key), e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean releaseLock(String key) { |
||||||
|
registryLockManager.releaseLock(key); |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Duration getSessionTimeout() { |
||||||
|
throw new UnsupportedOperationException("Not support session timeout at Mysql Registry"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() { |
||||||
|
LOGGER.info("Closing Mysql Registry..."); |
||||||
|
// remove the current Ephemeral node, if can connect to mysql
|
||||||
|
try (EphemeralDateManager closed1 = ephemeralDateManager; |
||||||
|
SubscribeDataManager close2 = subscribeDataManager; |
||||||
|
RegistryLockManager close3 = registryLockManager; |
||||||
|
MysqlOperator closed4 = mysqlOperator) { |
||||||
|
} catch (Exception e) { |
||||||
|
LOGGER.error("Close Mysql Registry error", e); |
||||||
|
} |
||||||
|
LOGGER.info("Closed Mysql Registry..."); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql; |
||||||
|
|
||||||
|
import org.apache.dolphinscheduler.common.utils.NetUtils; |
||||||
|
import org.apache.dolphinscheduler.common.utils.OSUtils; |
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass; |
||||||
|
|
||||||
|
@UtilityClass |
||||||
|
public final class MysqlRegistryConstant { |
||||||
|
|
||||||
|
public static final long LOCK_ACQUIRE_INTERVAL = 1_000; |
||||||
|
|
||||||
|
public static final String LOCK_OWNER = NetUtils.getHost() + "_" + OSUtils.getProcessID(); |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql; |
||||||
|
|
||||||
|
import java.time.Duration; |
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
|
||||||
|
import com.zaxxer.hikari.HikariConfig; |
||||||
|
|
||||||
|
import lombok.Data; |
||||||
|
|
||||||
|
@Data |
||||||
|
@Configuration |
||||||
|
@ConditionalOnProperty(prefix = "registry", name = "type", havingValue = "mysql") |
||||||
|
@ConfigurationProperties(prefix = "registry") |
||||||
|
public class MysqlRegistryProperties { |
||||||
|
|
||||||
|
/** |
||||||
|
* Used to schedule refresh the ephemeral data/ lock. |
||||||
|
*/ |
||||||
|
private Duration termRefreshInterval = Duration.ofSeconds(2); |
||||||
|
/** |
||||||
|
* Used to calculate the expire time, |
||||||
|
* e.g. if you set 2, and latest two refresh error, then the ephemeral data/lock will be expire. |
||||||
|
*/ |
||||||
|
private int termExpireTimes = 3; |
||||||
|
private HikariConfig hikariConfig; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,33 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql.model; |
||||||
|
|
||||||
|
public enum DataType { |
||||||
|
EPHEMERAL(1), |
||||||
|
PERSISTENT(2), |
||||||
|
; |
||||||
|
private final int typeValue; |
||||||
|
|
||||||
|
DataType(int typeValue) { |
||||||
|
this.typeValue = typeValue; |
||||||
|
} |
||||||
|
|
||||||
|
public int getTypeValue() { |
||||||
|
return typeValue; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql.model; |
||||||
|
|
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
@Data |
||||||
|
@Builder |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
public class MysqlRegistryData { |
||||||
|
|
||||||
|
private long id; |
||||||
|
private String key; |
||||||
|
private String data; |
||||||
|
private int type; |
||||||
|
private Date createTime; |
||||||
|
private Date lastUpdateTime; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql.model; |
||||||
|
|
||||||
|
import java.util.Date; |
||||||
|
|
||||||
|
import lombok.AllArgsConstructor; |
||||||
|
import lombok.Builder; |
||||||
|
import lombok.Data; |
||||||
|
import lombok.NoArgsConstructor; |
||||||
|
|
||||||
|
@Data |
||||||
|
@Builder |
||||||
|
@NoArgsConstructor |
||||||
|
@AllArgsConstructor |
||||||
|
public class MysqlRegistryLock { |
||||||
|
|
||||||
|
private long id; |
||||||
|
/** |
||||||
|
* The lock key. |
||||||
|
*/ |
||||||
|
private String key; |
||||||
|
/** |
||||||
|
* acquire lock host. |
||||||
|
*/ |
||||||
|
private String lockOwner; |
||||||
|
/** |
||||||
|
* The last term, if the (currentTime - lastTerm) > termExpire time, the lock will be expired. |
||||||
|
*/ |
||||||
|
private Date lastTerm; |
||||||
|
/** |
||||||
|
* The lock last update time. |
||||||
|
*/ |
||||||
|
private Date lastUpdateTime; |
||||||
|
/** |
||||||
|
* The lock create time. |
||||||
|
*/ |
||||||
|
private Date createTime; |
||||||
|
} |
@ -0,0 +1,162 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql.task; |
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull; |
||||||
|
|
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.MysqlOperator; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.MysqlRegistryProperties; |
||||||
|
import org.apache.dolphinscheduler.registry.api.ConnectionListener; |
||||||
|
import org.apache.dolphinscheduler.registry.api.ConnectionState; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder; |
||||||
|
|
||||||
|
/** |
||||||
|
* This thread is used to check the connect state to mysql. |
||||||
|
*/ |
||||||
|
public class EphemeralDateManager implements AutoCloseable { |
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(EphemeralDateManager.class); |
||||||
|
|
||||||
|
private final MysqlOperator mysqlOperator; |
||||||
|
private final MysqlRegistryProperties registryProperties; |
||||||
|
private final List<ConnectionListener> connectionListeners = Collections.synchronizedList(new ArrayList<>()); |
||||||
|
private final Set<Long> ephemeralDateIds = Collections.synchronizedSet(new HashSet<>()); |
||||||
|
private final ScheduledExecutorService scheduledExecutorService; |
||||||
|
|
||||||
|
public EphemeralDateManager(MysqlRegistryProperties registryProperties, MysqlOperator mysqlOperator) { |
||||||
|
this.registryProperties = registryProperties; |
||||||
|
this.mysqlOperator = checkNotNull(mysqlOperator); |
||||||
|
this.scheduledExecutorService = Executors.newScheduledThreadPool( |
||||||
|
1, |
||||||
|
new ThreadFactoryBuilder().setNameFormat("EphemeralDateTermRefreshThread").setDaemon(true).build()); |
||||||
|
} |
||||||
|
|
||||||
|
public void start() { |
||||||
|
this.scheduledExecutorService.scheduleWithFixedDelay( |
||||||
|
new EphemeralDateTermRefreshTask(mysqlOperator, connectionListeners, ephemeralDateIds), |
||||||
|
registryProperties.getTermRefreshInterval().toMillis(), |
||||||
|
registryProperties.getTermRefreshInterval().toMillis(), |
||||||
|
TimeUnit.MILLISECONDS); |
||||||
|
} |
||||||
|
|
||||||
|
public void addConnectionListener(ConnectionListener connectionListener) { |
||||||
|
connectionListeners.add(connectionListener); |
||||||
|
} |
||||||
|
|
||||||
|
public long insertOrUpdateEphemeralData(String key, String value) throws SQLException { |
||||||
|
long ephemeralId = mysqlOperator.insertOrUpdateEphemeralData(key, value); |
||||||
|
ephemeralDateIds.add(ephemeralId); |
||||||
|
return ephemeralId; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() throws SQLException { |
||||||
|
ephemeralDateIds.clear(); |
||||||
|
connectionListeners.clear(); |
||||||
|
scheduledExecutorService.shutdownNow(); |
||||||
|
for (Long ephemeralDateId : ephemeralDateIds) { |
||||||
|
mysqlOperator.deleteEphemeralData(ephemeralDateId); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Use this task to refresh ephemeral term and check the connect state.
|
||||||
|
static class EphemeralDateTermRefreshTask implements Runnable { |
||||||
|
private final List<ConnectionListener> connectionListeners; |
||||||
|
private final Set<Long> ephemeralDateIds; |
||||||
|
private final MysqlOperator mysqlOperator; |
||||||
|
private ConnectionState connectionState; |
||||||
|
|
||||||
|
private EphemeralDateTermRefreshTask(MysqlOperator mysqlOperator, |
||||||
|
List<ConnectionListener> connectionListeners, |
||||||
|
Set<Long> ephemeralDateIds) { |
||||||
|
this.mysqlOperator = checkNotNull(mysqlOperator); |
||||||
|
this.connectionListeners = checkNotNull(connectionListeners); |
||||||
|
this.ephemeralDateIds = checkNotNull(ephemeralDateIds); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
try { |
||||||
|
ConnectionState currentConnectionState = getConnectionState(); |
||||||
|
if (currentConnectionState == connectionState) { |
||||||
|
// no state change
|
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if (connectionState == ConnectionState.CONNECTED) { |
||||||
|
if (currentConnectionState == ConnectionState.DISCONNECTED) { |
||||||
|
connectionState = ConnectionState.DISCONNECTED; |
||||||
|
triggerListener(ConnectionState.DISCONNECTED); |
||||||
|
} |
||||||
|
} else if (connectionState == ConnectionState.DISCONNECTED) { |
||||||
|
if (currentConnectionState == ConnectionState.CONNECTED) { |
||||||
|
connectionState = ConnectionState.CONNECTED; |
||||||
|
triggerListener(ConnectionState.RECONNECTED); |
||||||
|
} |
||||||
|
} else if (connectionState == null) { |
||||||
|
connectionState = currentConnectionState; |
||||||
|
triggerListener(connectionState); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
LOGGER.error("Mysql Registry connect state check task execute failed", e); |
||||||
|
connectionState = ConnectionState.DISCONNECTED; |
||||||
|
triggerListener(ConnectionState.DISCONNECTED); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private ConnectionState getConnectionState() { |
||||||
|
try { |
||||||
|
if (ephemeralDateIds.isEmpty()) { |
||||||
|
mysqlOperator.healthCheck(); |
||||||
|
} else { |
||||||
|
updateEphemeralDateTerm(); |
||||||
|
} |
||||||
|
mysqlOperator.clearExpireEphemeralDate(); |
||||||
|
return ConnectionState.CONNECTED; |
||||||
|
} catch (Exception ex) { |
||||||
|
return ConnectionState.DISCONNECTED; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void updateEphemeralDateTerm() throws SQLException { |
||||||
|
if (!mysqlOperator.updateEphemeralDataTerm(ephemeralDateIds)) { |
||||||
|
LOGGER.warn("Update mysql registry ephemeral data: {} term error", ephemeralDateIds); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void triggerListener(ConnectionState connectionState) { |
||||||
|
for (ConnectionListener connectionListener : connectionListeners) { |
||||||
|
connectionListener.onUpdate(connectionState); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,137 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql.task; |
||||||
|
|
||||||
|
import org.apache.dolphinscheduler.common.thread.ThreadUtils; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.MysqlOperator; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.MysqlRegistryConstant; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.MysqlRegistryProperties; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.model.MysqlRegistryLock; |
||||||
|
import org.apache.dolphinscheduler.registry.api.RegistryException; |
||||||
|
|
||||||
|
import java.sql.SQLException; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder; |
||||||
|
|
||||||
|
import lombok.AccessLevel; |
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
|
||||||
|
public class RegistryLockManager implements AutoCloseable { |
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RegistryLockManager.class); |
||||||
|
|
||||||
|
private final MysqlOperator mysqlOperator; |
||||||
|
private final MysqlRegistryProperties registryProperties; |
||||||
|
private final Map<String, MysqlRegistryLock> lockHoldMap; |
||||||
|
private final ScheduledExecutorService lockTermUpdateThreadPool; |
||||||
|
|
||||||
|
public RegistryLockManager(MysqlRegistryProperties registryProperties, MysqlOperator mysqlOperator) { |
||||||
|
this.registryProperties = registryProperties; |
||||||
|
this.mysqlOperator = mysqlOperator; |
||||||
|
this.lockHoldMap = new ConcurrentHashMap<>(); |
||||||
|
this.lockTermUpdateThreadPool = Executors.newSingleThreadScheduledExecutor( |
||||||
|
new ThreadFactoryBuilder().setNameFormat("MysqlRegistryLockTermRefreshThread").setDaemon(true).build()); |
||||||
|
} |
||||||
|
|
||||||
|
public void start() { |
||||||
|
lockTermUpdateThreadPool.scheduleWithFixedDelay( |
||||||
|
new LockTermRefreshTask(lockHoldMap, mysqlOperator), |
||||||
|
registryProperties.getTermRefreshInterval().toMillis(), |
||||||
|
registryProperties.getTermRefreshInterval().toMillis(), |
||||||
|
TimeUnit.MILLISECONDS); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Acquire the lock, if cannot get the lock will await. |
||||||
|
*/ |
||||||
|
public void acquireLock(String lockKey) throws RegistryException { |
||||||
|
// maybe we can use the computeIf absent
|
||||||
|
lockHoldMap.computeIfAbsent(lockKey, key -> { |
||||||
|
MysqlRegistryLock mysqlRegistryLock; |
||||||
|
try { |
||||||
|
while ((mysqlRegistryLock = mysqlOperator.tryToAcquireLock(lockKey)) == null) { |
||||||
|
logger.debug("Acquire the lock {} failed try again", key); |
||||||
|
// acquire failed, wait and try again
|
||||||
|
ThreadUtils.sleep(MysqlRegistryConstant.LOCK_ACQUIRE_INTERVAL); |
||||||
|
} |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RegistryException("Acquire the lock error", e); |
||||||
|
} |
||||||
|
return mysqlRegistryLock; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public void releaseLock(String lockKey) { |
||||||
|
MysqlRegistryLock mysqlRegistryLock = lockHoldMap.get(lockKey); |
||||||
|
if (mysqlRegistryLock != null) { |
||||||
|
try { |
||||||
|
// the lock is unExit
|
||||||
|
mysqlOperator.releaseLock(mysqlRegistryLock.getId()); |
||||||
|
lockHoldMap.remove(lockKey); |
||||||
|
} catch (SQLException e) { |
||||||
|
throw new RegistryException(String.format("Release lock: %s error", lockKey), e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() { |
||||||
|
lockTermUpdateThreadPool.shutdownNow(); |
||||||
|
for (Map.Entry<String, MysqlRegistryLock> lockEntry : lockHoldMap.entrySet()) { |
||||||
|
releaseLock(lockEntry.getKey()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This task is used to refresh the lock held by the current server. |
||||||
|
*/ |
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) |
||||||
|
static class LockTermRefreshTask implements Runnable { |
||||||
|
private final Map<String, MysqlRegistryLock> lockHoldMap; |
||||||
|
private final MysqlOperator mysqlOperator; |
||||||
|
|
||||||
|
public void run() { |
||||||
|
try { |
||||||
|
if (lockHoldMap.isEmpty()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
List<Long> lockIds = lockHoldMap.values() |
||||||
|
.stream() |
||||||
|
.map(MysqlRegistryLock::getId) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
if (!mysqlOperator.updateLockTerm(lockIds)) { |
||||||
|
logger.warn("Update the lock: {} term failed.", lockIds); |
||||||
|
} |
||||||
|
mysqlOperator.clearExpireLock(); |
||||||
|
} catch (Exception e) { |
||||||
|
logger.error("Update lock term error", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,156 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.apache.dolphinscheduler.plugin.registry.mysql.task; |
||||||
|
|
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.MysqlOperator; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.MysqlRegistryProperties; |
||||||
|
import org.apache.dolphinscheduler.plugin.registry.mysql.model.MysqlRegistryData; |
||||||
|
import org.apache.dolphinscheduler.registry.api.Event; |
||||||
|
import org.apache.dolphinscheduler.registry.api.SubscribeListener; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
import java.util.concurrent.Executors; |
||||||
|
import java.util.concurrent.ScheduledExecutorService; |
||||||
|
import java.util.concurrent.TimeUnit; |
||||||
|
import java.util.function.Function; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder; |
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor; |
||||||
|
|
||||||
|
/** |
||||||
|
* Used to refresh if the subscribe path has been changed. |
||||||
|
*/ |
||||||
|
public class SubscribeDataManager implements AutoCloseable { |
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(SubscribeDataManager.class); |
||||||
|
|
||||||
|
private final MysqlOperator mysqlOperator; |
||||||
|
private final MysqlRegistryProperties registryProperties; |
||||||
|
private final Map<String, List<SubscribeListener>> dataSubScribeMap = new ConcurrentHashMap<>(); |
||||||
|
private final ScheduledExecutorService dataSubscribeCheckThreadPool; |
||||||
|
private final Map<String, MysqlRegistryData> mysqlRegistryDataMap = new ConcurrentHashMap<>(); |
||||||
|
|
||||||
|
public SubscribeDataManager(MysqlRegistryProperties registryProperties, MysqlOperator mysqlOperator) { |
||||||
|
this.registryProperties = registryProperties; |
||||||
|
this.mysqlOperator = mysqlOperator; |
||||||
|
this.dataSubscribeCheckThreadPool = Executors.newScheduledThreadPool( |
||||||
|
1, |
||||||
|
new ThreadFactoryBuilder().setNameFormat("MysqlRegistrySubscribeDataCheckThread").setDaemon(true).build()); |
||||||
|
} |
||||||
|
|
||||||
|
public void start() { |
||||||
|
dataSubscribeCheckThreadPool.scheduleWithFixedDelay( |
||||||
|
new RegistrySubscribeDataCheckTask(dataSubScribeMap, mysqlOperator, mysqlRegistryDataMap), |
||||||
|
registryProperties.getTermRefreshInterval().toMillis(), |
||||||
|
registryProperties.getTermRefreshInterval().toMillis(), |
||||||
|
TimeUnit.MILLISECONDS); |
||||||
|
} |
||||||
|
|
||||||
|
public void addListener(String path, SubscribeListener subscribeListener) { |
||||||
|
dataSubScribeMap.computeIfAbsent(path, k -> new ArrayList<>()).add(subscribeListener); |
||||||
|
} |
||||||
|
|
||||||
|
public void removeListener(String path) { |
||||||
|
dataSubScribeMap.remove(path); |
||||||
|
} |
||||||
|
|
||||||
|
public String getData(String path) { |
||||||
|
MysqlRegistryData mysqlRegistryData = mysqlRegistryDataMap.get(path); |
||||||
|
if (mysqlRegistryData == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return mysqlRegistryData.getData(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() { |
||||||
|
dataSubscribeCheckThreadPool.shutdownNow(); |
||||||
|
dataSubScribeMap.clear(); |
||||||
|
} |
||||||
|
|
||||||
|
@RequiredArgsConstructor |
||||||
|
static class RegistrySubscribeDataCheckTask implements Runnable { |
||||||
|
|
||||||
|
private final Map<String, List<SubscribeListener>> dataSubScribeMap; |
||||||
|
private final MysqlOperator mysqlOperator; |
||||||
|
private final Map<String, MysqlRegistryData> mysqlRegistryDataMap; |
||||||
|
|
||||||
|
@Override |
||||||
|
public void run() { |
||||||
|
// query the full data from database, and update the mysqlRegistryDataMap
|
||||||
|
try { |
||||||
|
Map<String, MysqlRegistryData> currentMysqlDataMap = mysqlOperator.queryAllMysqlRegistryData() |
||||||
|
.stream() |
||||||
|
.collect(Collectors.toMap(MysqlRegistryData::getKey, Function.identity())); |
||||||
|
// find the different
|
||||||
|
List<MysqlRegistryData> addedData = new ArrayList<>(); |
||||||
|
List<MysqlRegistryData> deletedData = new ArrayList<>(); |
||||||
|
List<MysqlRegistryData> updatedData = new ArrayList<>(); |
||||||
|
for (Map.Entry<String, MysqlRegistryData> entry : currentMysqlDataMap.entrySet()) { |
||||||
|
MysqlRegistryData newData = entry.getValue(); |
||||||
|
MysqlRegistryData oldData = mysqlRegistryDataMap.get(entry.getKey()); |
||||||
|
if (oldData == null) { |
||||||
|
addedData.add(newData); |
||||||
|
} else { |
||||||
|
if (!entry.getValue().getLastUpdateTime().equals(oldData.getLastUpdateTime())) { |
||||||
|
updatedData.add(newData); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
for (Map.Entry<String, MysqlRegistryData> entry : mysqlRegistryDataMap.entrySet()) { |
||||||
|
if (!currentMysqlDataMap.containsKey(entry.getKey())) { |
||||||
|
deletedData.add(entry.getValue()); |
||||||
|
} |
||||||
|
} |
||||||
|
mysqlRegistryDataMap.clear(); |
||||||
|
mysqlRegistryDataMap.putAll(currentMysqlDataMap); |
||||||
|
// trigger listener
|
||||||
|
for (Map.Entry<String, List<SubscribeListener>> entry : dataSubScribeMap.entrySet()) { |
||||||
|
String subscribeKey = entry.getKey(); |
||||||
|
List<SubscribeListener> subscribeListeners = entry.getValue(); |
||||||
|
triggerListener(addedData, subscribeKey, subscribeListeners, Event.Type.ADD); |
||||||
|
triggerListener(deletedData, subscribeKey, subscribeListeners, Event.Type.REMOVE); |
||||||
|
triggerListener(updatedData, subscribeKey, subscribeListeners, Event.Type.UPDATE); |
||||||
|
} |
||||||
|
} catch (Exception e) { |
||||||
|
LOGGER.error("Query data from mysql registry error"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void triggerListener(List<MysqlRegistryData> dataList, |
||||||
|
String subscribeKey, |
||||||
|
List<SubscribeListener> subscribeListeners, |
||||||
|
Event.Type type) { |
||||||
|
for (MysqlRegistryData data : dataList) { |
||||||
|
if (data.getKey().startsWith(subscribeKey)) { |
||||||
|
subscribeListeners.forEach(subscribeListener -> |
||||||
|
subscribeListener.notify(new Event(data.getKey(), data.getKey(), data.getData(), type))); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
/* |
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||||
|
* contributor license agreements. See the NOTICE file distributed with |
||||||
|
* this work for additional information regarding copyright ownership. |
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||||
|
* (the "License"); you may not use this file except in compliance with |
||||||
|
* the License. You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS = 0; |
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `t_ds_mysql_registry_data`; |
||||||
|
CREATE TABLE `t_ds_mysql_registry_data` |
||||||
|
( |
||||||
|
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key', |
||||||
|
`key` varchar(200) NOT NULL COMMENT 'key, like zookeeper node path', |
||||||
|
`data` varchar(200) NOT NULL COMMENT 'data, like zookeeper node value', |
||||||
|
`type` tinyint(4) NOT NULL COMMENT '1: ephemeral node, 2: persistent node', |
||||||
|
`last_update_time` timestamp NULL COMMENT 'last update time', |
||||||
|
`create_time` timestamp NULL COMMENT 'create time', |
||||||
|
PRIMARY KEY (`id`), |
||||||
|
unique (`key`) |
||||||
|
) ENGINE = InnoDB |
||||||
|
DEFAULT CHARSET = utf8; |
||||||
|
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `t_ds_mysql_registry_lock`; |
||||||
|
CREATE TABLE `t_ds_mysql_registry_lock` |
||||||
|
( |
||||||
|
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key', |
||||||
|
`key` varchar(200) NOT NULL COMMENT 'lock path', |
||||||
|
`lock_owner` varchar(100) NOT NULL COMMENT 'the lock owner, ip_processId', |
||||||
|
`last_term` timestamp NOT NULL COMMENT 'last term time', |
||||||
|
`last_update_time` timestamp NULL COMMENT 'last update time', |
||||||
|
`create_time` timestamp NULL COMMENT 'lock create time', |
||||||
|
PRIMARY KEY (`id`), |
||||||
|
unique (`key`) |
||||||
|
) ENGINE = InnoDB |
||||||
|
DEFAULT CHARSET = utf8; |
44
dolphinscheduler-registry/dolphinscheduler-registry-api/src/main/java/org/apache/dolphinscheduler/registry/api/RegistryProperties.java → dolphinscheduler-registry/dolphinscheduler-registry-plugins/dolphinscheduler-registry-zookeeper/src/main/java/org/apache/dolphinscheduler/plugin/registry/zookeeper/ZookeeperRegistryProperties.java
44
dolphinscheduler-registry/dolphinscheduler-registry-api/src/main/java/org/apache/dolphinscheduler/registry/api/RegistryProperties.java → dolphinscheduler-registry/dolphinscheduler-registry-plugins/dolphinscheduler-registry-zookeeper/src/main/java/org/apache/dolphinscheduler/plugin/registry/zookeeper/ZookeeperRegistryProperties.java
Loading…
Reference in new issue