Analyse et résolution des défaillances de connexion causées par MySQL utilisant replicationconnection

InfoQ 2022-06-23 12:42:52 阅读数:511

analyseetsolutiondesfaillances
MySQLSéparation lecture - écriture de la base de données,Est l'un des moyens couramment utilisés pour améliorer la qualité du Service,Et pour les solutions techniques,Il existe de nombreux cadres ou solutions open source matures,Par exemple:sharding-jdbc、springDansAbstractRoutingDatasource、MySQL-RouterAttendez.,Etmysql-jdbcDansReplicationConnectionPeut également supporter.Cet article ne fait pas trop d'analyse sur la sélection technique de la séparation lecture - écriture,C'est juste que l'exploration est utiliséedruidComme source de données、UnionReplicationConnectionLors de la séparation lecture - écriture,Cause de la défaillance de la connexion,Et trouver une solution simple et efficace.

Contexte du problème

Pour des raisons historiques,.Une exception de défaillance de connexion s'est produite pour plusieurs services,Les principales erreurs sont les suivantes::

null
Il n'est pas difficile de voir à partir du Journal,Ceci est dû au fait que la connexion n'a pas été connectée avecMySQLInteraction côté serveur,Le serveur a fermé la connexion,Scénario typique de défaillance de la connexion.

Les principales configurations impliquées sont les suivantes:

jdbcConfiguration

jdbc:mysql:replication://master_host:port,slave_host:port/database_name

druidConfiguration

testWhileIdle=true(C'est - à - dire:, Vérification de la connexion inactive activée );timeBetweenEvictionRunsMillis=6000L(C'est - à - dire:, Pour les scénarios qui obtiennent des connexions , Si une connexion est inactive plus longtemps que 1Minutes, Sera vérifié ,Si la connexion n'est pas valide, Sera abandonné et récupéré ).

Annexe:DruidDataSource.getConnectionDirectMoyenne,La logique de traitement est la suivante:

if (testWhileIdle) {
 final DruidConnectionHolder holder = poolableConnection.holder;
 long currentTimeMillis = System.currentTimeMillis();
 long lastActiveTimeMillis = holder.lastActiveTimeMillis;
 long lastExecTimeMillis = holder.lastExecTimeMillis;
 long lastKeepTimeMillis = holder.lastKeepTimeMillis;

 if (checkExecuteTime
 && lastExecTimeMillis != lastActiveTimeMillis) {
 lastActiveTimeMillis = lastExecTimeMillis;
 }

 if (lastKeepTimeMillis > lastActiveTimeMillis) {
 lastActiveTimeMillis = lastKeepTimeMillis;
 }

 long idleMillis = currentTimeMillis - lastActiveTimeMillis;

 long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;

 if (timeBetweenEvictionRunsMillis <= 0) {
 timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
 }

 if (idleMillis >= timeBetweenEvictionRunsMillis
 || idleMillis < 0 // unexcepted branch
 ) {
 boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
 if (!validate) {
 if (LOG.isDebugEnabled()) {
 LOG.debug(&quot;skip not validate connection.&quot;);
 }

 discardConnection(poolableConnection.holder);
 continue;
 }
 }
}

mysql Configuration des paramètres de temporisation

wait_timeout=3600(3600Secondes,C'est - à - dire:: Si une connexion n'interagit pas avec le serveur depuis plus d'une heure , La connexion sera utilisée par le serveur kill).C'est évident., Basé sur la configuration ci - dessus ,Comme d'habitude,Ça n'aurait pas dû arriver“The last packet successfully received from server was xxx,xxx,xxx milliseconds ago”La question de.(Bien sûr., L'intervention manuelle a été exclue. kill Possibilité de déconnecter la base de données ).

Quand“C'est logique.” L'expérience n'explique pas le problème. , Il est souvent nécessaire de sauter d'une liaison empirique qui peut flotter à la surface , Une enquête. .Alors, Quelle est la vraie raison du problème? ?

Causes profondes

Lorsqu'il est utilisédruidGérer les sources de données,Unionmysql-jdbcMésozoïqueReplicationConnectionLors de la séparation lecture - écriture,ReplicationConnection Existe réellement dans l'objet Proxy masterEtslaves Deux ensembles de connexions ,druid Lors de l'essai de connexion , Seuls ceux - ci peuvent être détectés masterConnexion,Si unslave Connexion non utilisée depuis longtemps , Peut causer des problèmes de connexion .

Analyse des causes

mysql-jdbcMoyenne, Processus de connexion piloté par la base de données

Unioncom.mysql.jdbc.DriverCode source,C'est facile à voir.mysql-jdbc Le processus principal pour obtenir une connexion est le suivant: :

null
Pour“jdbc:mysql:replication://” Configuré au début jdbc-url,Adoptionmysql-jdbc Connexion obtenue ,En fait, c'est unReplicationConnectionObjet proxy pour,Par défaut,“jdbc:mysql:replication://”Le premier aprèshostEtportCorrespondant àmasterConnexion,Par la suitehostEtportCorrespondant àslavesConnexion, Et pour plus d'un slave Scénario configuré , équilibrage de la charge par défaut avec des politiques aléatoires .

ReplicationConnectionObjet Proxy,UtiliserJDKProxy Dynamic generated,Parmi euxInvocationHandlerRéalisation concrète,- Oui.ReplicationConnectionProxy,Les codes clés sont les suivants::

public static ReplicationConnection createProxyInstance(List<String> masterHostList, Properties masterProperties, List<String> slaveHostList,
 Properties slaveProperties) throws SQLException {
 ReplicationConnectionProxy connProxy = new ReplicationConnectionProxy(masterHostList, masterProperties, slaveHostList, slaveProperties);
 return (ReplicationConnection) java.lang.reflect.Proxy.newProxyInstance(ReplicationConnection.class.getClassLoader(), INTERFACES_TO_PROXY, connProxy);
 }

ReplicationConnectionProxyUne partie importante de

À propos des agents de connexion à la base de données ,ReplicationConnectionProxy Les principales composantes sont les suivantes: :

null
ReplicationConnectionProxyExistemasterConnectionEtslavesConnection Deux objets réellement connectés ,currentConnetion(Connexion actuelle)On peut passer àmastetConnectionOuslavesConnection, Le mode de commutation peut être défini par readOnlyRéalisation.Dans la logique opérationnelle, Le cœur de la séparation lecture - écriture ,En termes simples:UtiliserReplicationConnectionLors de la séparation lecture - écriture,Fais - en un.“ParamètresconnectionDereadOnlyPropriété”aopC'est tout..Basé surReplicationConnectionProxy, Acquis dans la logique opérationnelle ConnectionObjet Proxy, Quelle est la logique principale de l'accès à la base de données? ?

ReplicationConnection Processus de traitement des objets Proxy

Pour la logique d'entreprise,AcquisConnectionExemple,- Oui.ReplicationConnectionObjet Proxy, L'objet proxy passe par ReplicationConnectionProxyEtReplicationMySQLConnection Coopération mutuelle pour compléter le traitement de l'accès à la base de données ,Parmi euxReplicationConnectionProxyEn cours de réalisation InvocationHandlerEn même temps, Joue également un rôle dans la gestion des connexions , La logique de base est la suivante: :

null
PourprepareStatement Attendez la logique conventionnelle ,ConnectionMySQConnection Obtenir la connexion actuelle pour le traitement ( C'est là que se concentre le traitement normal de la séparation de la lecture et de l'écriture. );En ce moment, Points saillants pingInternalMéthodes, Il est également géré pour obtenir la connexion actuelle ,Et ensuite exécuterpingInternalLogique.

Pourping() Cette logique particulière , La description est relativement simple , Mais le sens du sujet reste le même ,C'est - à - dire::C'est exact.masterConnexion etsleves Toutes les connexions doivent être faites ping()Traitement.

Dans la figure,pingInternalProcessus etdruidDeMySQ Le contrôle de connexion concerne ,EtpingTraitement spécial pour, C'est la clé de la solution. .

druid Paire de sources de données MySQVérification des connexions

druid- Oui.MySQL La classe d'implémentation par défaut pour la vérification de connexion est MySqlValidConnectionChecker, La logique de base est la suivante: :

public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
 if (conn.isClosed()) {
 return false;
 }

 if (usePingMethod) {
 if (conn instanceof DruidPooledConnection) {
 conn = ((DruidPooledConnection) conn).getConnection();
 }

 if (conn instanceof ConnectionProxy) {
 conn = ((ConnectionProxy) conn).getRawObject();
 }

 if (clazz.isAssignableFrom(conn.getClass())) {
 if (validationQueryTimeout <= 0) {
 validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
 }

 try {
 ping.invoke(conn, true, validationQueryTimeout * 1000);
 } catch (InvocationTargetException e) {
 Throwable cause = e.getCause();
 if (cause instanceof SQLException) {
 throw (SQLException) cause;
 }
 throw e;
 }
 return true;
 }
 }

 String query = validateQuery;
 if (validateQuery == null || validateQuery.isEmpty()) {
 query = DEFAULT_VALIDATION_QUERY;
 }

 Statement stmt = null;
 ResultSet rs = null;
 try {
 stmt = conn.createStatement();
 if (validationQueryTimeout > 0) {
 stmt.setQueryTimeout(validationQueryTimeout);
 }
 rs = stmt.executeQuery(query);
 return true;
 } finally {
 JdbcUtils.close(rs);
 JdbcUtils.close(stmt);
 }

}

Utilisé dans le service correspondant mysql-jdbc(5.1.45Édition),Non réglé“druid.mysql.usePingMethod” Dans le cas des propriétés du système ,Par défautusePingMethodPourtrue,Comme suit:

public MySqlValidConnectionChecker(){
try {
 clazz = Utils.loadClass(&quot;com.mysql.jdbc.MySQLConnection&quot;);
 if (clazz == null) {
 clazz = Utils.loadClass(&quot;com.mysql.cj.jdbc.ConnectionImpl&quot;);
 }

 if (clazz != null) {
 ping = clazz.getMethod(&quot;pingInternal&quot;, boolean.class, int.class);
 }

 if (ping != null) {
 usePingMethod = true;
 }
 } catch (Exception e) {
 LOG.warn(&quot;Cannot resolve com.mysql.jdbc.Connection.ping method. Will use 'SELECT 1' instead.&quot;, e);
 }

 configFromProperties(System.getProperties());
}

@Override
public void configFromProperties(Properties properties) {
 String property = properties.getProperty(&quot;druid.mysql.usePingMethod&quot;);
 if (&quot;true&quot;.equals(property)) {
 setUsePingMethod(true);
 } else if (&quot;false&quot;.equals(property)) {
 setUsePingMethod(false);
 }
}

En même temps,Comme vous pouvez le voir,MySqlValidConnectionCheckerDanspingLa méthode utiliseMySQLConnectionDanspingInternalMéthodes,Et cette méthode,Face vers le HautReplicationConnectionAnalyse de,Quand on appellepingInternalHeure, Vérifier uniquement la connexion actuelle . Le moment d'effectuer la vérification de la connexion est le suivant: DrduiDatasourceLors de l'obtention de la connexion, Non défini pour le moment readOnlyPropriétés, Connexion vérifiée ,En fait, c'est justeReplicationConnectionProxyDansmasterConnexion.

En outre,Si ça passe“druid.mysql.usePingMethod”Paramètres de propriétéusePingMeghodPourfalse, En fait, il peut aussi causer des problèmes de connexion ,Parce que:Quand on passevalideQuery(Par exemple“select 1”) Lors de la vérification de la connexion ,On y va.ReplicationConnection Logique de requête normale dans , La connexion correspondante est toujours masterConnexion.

Questions supplémentaires
:ping Pourquoi utiliser la méthode “pingInternal”,Au lieu deping?Raisons:pingInternal Les paramètres de contrôle tels que le temps d'arrêt sont réservés .

La solution

Ajuster la version dépendante

Utilisé dans le Service mysql-jdbcLa version est5.1.45,druidLa version est1.1.20. Comprendre d'autres dépendances de version élevée , Le problème persiste .

Modifier la mise en œuvre de la séparation lecture - écriture

La charge de travail de la modification réside principalement dans la configuration des sources de données et aopAjustement, Mais une régression globale est nécessaire pour vérifier le coût , Compte tenu de l'importance générale des services liés à cette question, , Pas d'ajustement important pour le moment .

Développementmysql-jdbcDrive

D'après l'originalReplicationConnectionLa fonction de,DéveloppementpingInternal Ajustement à la normale ping, Intégration de l'original Driver Développer de nouveaux Driver.Faisabilité du programme, Mais le coût de la modification n'est pas petit .

Basé surdruid,DéveloppementMySQLVérification de la connexion

Résoudre les problèmes simplement et efficacement , Sélectionner l'extension MySqlValidConnectionChecker,Et dansdruid Ajouter la configuration correspondante à la source de données .L'extension est la suivante::

public class MySqlReplicationCompatibleValidConnectionChecker extends MySqlValidConnectionChecker {


 private static final Log LOG = LogFactory.getLog(MySqlValidConnectionChecker.class);
 /**
 * 
 */
 private static final long serialVersionUID = 1L;

 @Override
 public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {

 if (conn.isClosed()) {
 return false;
 }

 if (conn instanceof DruidPooledConnection) {
 conn = ((DruidPooledConnection) conn).getConnection();
 }

 if (conn instanceof ConnectionProxy) {
 conn = ((ConnectionProxy) conn).getRawObject();
 }

 if (conn instanceof ReplicationConnection) {

 try {
 ((ReplicationConnection) conn).ping();
 LOG.info(&quot;validate connection success: connection=&quot; + conn.toString());
 return true;
 } catch (SQLException e) {
 LOG.error(&quot;validate connection error: connection=&quot; + conn.toString(), e);
 throw e;
 }

 }

 return super.isValidConnection(conn, validateQuery, validationQueryTimeout);
 }

}

ReplicatoinConnection.ping() Dans la logique de mise en œuvre ,Ça va être pour toutmasterEtslavesConnexion en courspingFonctionnement,Chaqueping Les actions sont appelées à LoadBalancedConnectionProxy.doPingTraitement,Et ici, Configurable dans la base de données urlParamètres intermédiairesloadBalancePingTimeoutDélai de réglage de la propriété.



Transfert au Centre R & D et à la plate - forme d'échange d'apprentissage technologique des partenaires de l'industrie,Partager régulièrement des expériences pratiques de première ligne et des sujets techniques de pointe dans l'industrie.Attention au numéro public「Technologie de rotation」,Diverses pratiques de produits secs,Bienvenue à partager~
Copyright:Cet article est[InfoQ]Établi,Veuillez apporter le lien original pour réimprimer,remercier。 https://fra.fheadline.com/2022/174/202206231217468145.html