|
15 | 15 | */
|
16 | 16 | package com.oceanbase.odc.service.db.session;
|
17 | 17 |
|
| 18 | +import static com.oceanbase.odc.core.shared.constant.DialectType.OB_MYSQL; |
| 19 | + |
18 | 20 | import java.sql.Connection;
|
19 | 21 | import java.sql.Statement;
|
| 22 | +import java.util.ArrayList; |
20 | 23 | import java.util.List;
|
21 | 24 | import java.util.Map;
|
22 | 25 | import java.util.Objects;
|
|
40 | 43 |
|
41 | 44 | import com.google.common.collect.Lists;
|
42 | 45 | import com.oceanbase.odc.common.util.StringUtils;
|
| 46 | +import com.oceanbase.odc.common.util.VersionUtils; |
43 | 47 | import com.oceanbase.odc.core.authority.util.SkipAuthorize;
|
44 | 48 | import com.oceanbase.odc.core.datasource.SingleConnectionDataSource;
|
45 | 49 | import com.oceanbase.odc.core.session.ConnectionSession;
|
46 | 50 | import com.oceanbase.odc.core.session.ConnectionSessionConstants;
|
47 | 51 | import com.oceanbase.odc.core.session.ConnectionSessionUtil;
|
48 | 52 | import com.oceanbase.odc.core.shared.Verify;
|
| 53 | +import com.oceanbase.odc.core.shared.constant.DialectType; |
49 | 54 | import com.oceanbase.odc.core.shared.exception.InternalServerError;
|
50 | 55 | import com.oceanbase.odc.core.shared.model.OdcDBSession;
|
51 | 56 | import com.oceanbase.odc.core.sql.execute.model.JdbcGeneralResult;
|
@@ -78,6 +83,13 @@ public class DefaultDBSessionManage implements DBSessionManageFacade {
|
78 | 83 | + "(?<port>[0-9]{1,5})\\*/.*";
|
79 | 84 | private static final Pattern SERVER_PATTERN = Pattern.compile(SERVER_REGEX);
|
80 | 85 | private static final ConnectionMapper CONNECTION_MAPPER = ConnectionMapper.INSTANCE;
|
| 86 | + private static final String GLOBAL_CLIENT_SESSION_OB_PROXY_VERSION_NUMBER = "4.2.3"; |
| 87 | + private static final String GLOBAL_CLIENT_SESSION_OB_VERSION_NUMBER = "4.2.5"; |
| 88 | + private static final String ORACLE_MODEL_KILL_SESSION_WITH_BLOCK_OB_VERSION_NUMBER = "4.2.1.0"; |
| 89 | + private static final byte GLOBAL_CLIENT_SESSION_PROXY_ID_MIN = 0; |
| 90 | + private static final short GLOBAL_CLIENT_SESSION_PROXY_ID_MAX = 8191; |
| 91 | + private static final byte GLOBAL_CLIENT_SESSION_ID_VERSION = 2; |
| 92 | + |
81 | 93 |
|
82 | 94 | @Autowired
|
83 | 95 | private ConnectSessionService sessionService;
|
@@ -198,33 +210,230 @@ private List<KillSessionResult> doKillSessionOrQuery(
|
198 | 210 | @SkipAuthorize("odc internal usage")
|
199 | 211 | public List<JdbcGeneralResult> executeKillSession(ConnectionSession connectionSession, List<SqlTuple> sqlTuples,
|
200 | 212 | String sqlScript) {
|
| 213 | + List<JdbcGeneralResult> results = executeKillCommands(connectionSession, sqlTuples, sqlScript); |
| 214 | + if (connectionSession.getDialectType() == DialectType.OB_MYSQL |
| 215 | + || connectionSession.getDialectType() == DialectType.OB_ORACLE) { |
| 216 | + return processResults(connectionSession, results); |
| 217 | + } |
| 218 | + return results; |
| 219 | + } |
| 220 | + |
| 221 | + private List<JdbcGeneralResult> executeKillCommands(ConnectionSession connectionSession, List<SqlTuple> sqlTuples, |
| 222 | + String sqlScript) { |
201 | 223 | List<JdbcGeneralResult> results =
|
202 | 224 | connectionSession.getSyncJdbcExecutor(ConnectionSessionConstants.BACKEND_DS_KEY)
|
203 | 225 | .execute(new OdcStatementCallBack(sqlTuples, connectionSession, true, null, false));
|
204 | 226 | if (results == null) {
|
205 | 227 | log.warn("Execution of the kill session command failed with unknown error, sql={}", sqlScript);
|
206 | 228 | throw new InternalServerError("Unknown error");
|
207 | 229 | }
|
208 |
| - return results.stream().map(jdbcGeneralResult -> { |
209 |
| - SqlTuple sqlTuple = jdbcGeneralResult.getSqlTuple(); |
| 230 | + return results; |
| 231 | + } |
| 232 | + |
| 233 | + /** |
| 234 | + * process the execution result after the first kill commands. if the result contains unknown thread |
| 235 | + * id exception, try to use other solutions to execute the kill commands. |
| 236 | + * |
| 237 | + * @param connectionSession |
| 238 | + * @param results |
| 239 | + * @return |
| 240 | + */ |
| 241 | + private List<JdbcGeneralResult> processResults(ConnectionSession connectionSession, |
| 242 | + List<JdbcGeneralResult> results) { |
| 243 | + Boolean isDirectedOBServer = isObServerDirected(connectionSession); |
| 244 | + String obProxyVersion = getObProxyVersion(connectionSession, isDirectedOBServer); |
| 245 | + String obVersion = ConnectionSessionUtil.getVersion(connectionSession); |
| 246 | + boolean isEnabledGlobalClientSession = |
| 247 | + isGlobalClientSessionEnabled(connectionSession, obProxyVersion, obVersion); |
| 248 | + boolean isSupportedOracleModeKillSession = isOracleModeKillSessionSupported(obVersion, connectionSession); |
| 249 | + List<JdbcGeneralResult> finalResults = new ArrayList<>(); |
| 250 | + for (JdbcGeneralResult jdbcGeneralResult : results) { |
210 | 251 | try {
|
211 | 252 | jdbcGeneralResult.getQueryResult();
|
212 | 253 | } catch (Exception e) {
|
213 |
| - if (StringUtils.contains(e.getMessage(), "Unknown thread id")) { |
214 |
| - try { |
215 |
| - log.info("Kill query/session Unknown thread id error, try direct connect observer"); |
216 |
| - directLinkServerAndExecute(sqlTuple.getExecutedSql(), |
217 |
| - connectionSession); |
218 |
| - return JdbcGeneralResult.successResult(sqlTuple); |
219 |
| - } catch (Exception e1) { |
220 |
| - log.warn("Failed to direct connect observer {}", e1.getMessage()); |
221 |
| - } |
| 254 | + if (isUnknownThreadIdError(e)) { |
| 255 | + jdbcGeneralResult = handleUnknownThreadIdError(connectionSession, |
| 256 | + jdbcGeneralResult, isDirectedOBServer, |
| 257 | + isEnabledGlobalClientSession, isSupportedOracleModeKillSession); |
222 | 258 | } else {
|
223 |
| - log.warn("Failed to execute sql in kill session scenario, sqlTuple={}", sqlTuple, e); |
| 259 | + log.warn("Failed to execute sql in kill session scenario, sqlTuple={}", |
| 260 | + jdbcGeneralResult.getSqlTuple(), e); |
224 | 261 | }
|
225 | 262 | }
|
| 263 | + finalResults.add(jdbcGeneralResult); |
| 264 | + } |
| 265 | + return finalResults; |
| 266 | + } |
| 267 | + |
| 268 | + /** |
| 269 | + * Check whether client directed oceanbase database server. If an exception occurs or the version |
| 270 | + * does not support, return null. |
| 271 | + * |
| 272 | + * @param connectionSession |
| 273 | + * @return |
| 274 | + */ |
| 275 | + private Boolean isObServerDirected(ConnectionSession connectionSession) { |
| 276 | + String sql = (connectionSession.getDialectType() == OB_MYSQL) |
| 277 | + ? "select PROXY_SESSID from oceanbase.gv$ob_processlist where ID =(select connection_id());" |
| 278 | + : "select PROXY_SESSID from gv$ob_processlist where ID =(select sys_context('userenv','sid') from dual);"; |
| 279 | + try { |
| 280 | + List<String> proxySessids = connectionSession.getSyncJdbcExecutor(ConnectionSessionConstants.BACKEND_DS_KEY) |
| 281 | + .query(sql, (rs, rowNum) -> rs.getString("PROXY_SESSID")); |
| 282 | + if (proxySessids != null && proxySessids.size() == 1) { |
| 283 | + return proxySessids.get(0) == null; |
| 284 | + } |
| 285 | + } catch (Exception e) { |
| 286 | + log.warn("Failed to obtain the PROXY_SESSID: {}", e.getMessage()); |
| 287 | + } |
| 288 | + // Return null if the version is not supported or an exception occurs |
| 289 | + return null; |
| 290 | + } |
| 291 | + |
| 292 | + /** |
| 293 | + * Get the OBProxy version number. If an exception occurs or the version does not support, return |
| 294 | + * null. |
| 295 | + * |
| 296 | + * @param connectionSession |
| 297 | + * @param isDirectedOBServer |
| 298 | + * @return |
| 299 | + */ |
| 300 | + private String getObProxyVersion(ConnectionSession connectionSession, Boolean isDirectedOBServer) { |
| 301 | + if (Boolean.TRUE.equals(isDirectedOBServer)) { |
| 302 | + return null; |
| 303 | + } |
| 304 | + try { |
| 305 | + return connectionSession.getSyncJdbcExecutor(ConnectionSessionConstants.BACKEND_DS_KEY) |
| 306 | + .queryForObject("select proxy_version()", String.class); |
| 307 | + } catch (Exception e) { |
| 308 | + log.warn("Failed to obtain the OBProxy version number: {}", e.getMessage()); |
| 309 | + return null; |
| 310 | + } |
| 311 | + } |
| 312 | + |
| 313 | + private boolean isGlobalClientSessionEnabled(ConnectionSession connectionSession, String obProxyVersion, |
| 314 | + String obVersion) { |
| 315 | + // verification version requirement |
| 316 | + if (StringUtils.isBlank(obProxyVersion) |
| 317 | + || StringUtils.isBlank(obVersion) |
| 318 | + || VersionUtils.isLessThan(obProxyVersion, GLOBAL_CLIENT_SESSION_OB_PROXY_VERSION_NUMBER) |
| 319 | + || VersionUtils.isLessThan(obVersion, GLOBAL_CLIENT_SESSION_OB_VERSION_NUMBER)) { |
| 320 | + return false; |
| 321 | + } |
| 322 | + try { |
| 323 | + Integer proxyId = getOBProxyConfig(connectionSession, "proxy_id"); |
| 324 | + Integer clientSessionIdVersion = getOBProxyConfig(connectionSession, "client_session_id_version"); |
| 325 | + |
| 326 | + return proxyId != null |
| 327 | + && proxyId >= GLOBAL_CLIENT_SESSION_PROXY_ID_MIN |
| 328 | + && proxyId <= GLOBAL_CLIENT_SESSION_PROXY_ID_MAX |
| 329 | + && clientSessionIdVersion != null |
| 330 | + && clientSessionIdVersion == GLOBAL_CLIENT_SESSION_ID_VERSION; |
| 331 | + } catch (Exception e) { |
| 332 | + log.warn("Failed to determine if global client session is enabled: {}", e.getMessage()); |
| 333 | + return false; |
| 334 | + } |
| 335 | + } |
| 336 | + |
| 337 | + /** |
| 338 | + * Gets the value of OBProxy's configuration variable If an exception occurs or the version does not |
| 339 | + * support, return null. |
| 340 | + * |
| 341 | + * @param connectionSession |
| 342 | + * @param configName |
| 343 | + * @return |
| 344 | + */ |
| 345 | + private Integer getOBProxyConfig(ConnectionSession connectionSession, String configName) { |
| 346 | + try { |
| 347 | + return connectionSession.getSyncJdbcExecutor(ConnectionSessionConstants.BACKEND_DS_KEY) |
| 348 | + .query("show proxyconfig like '" + configName + "';", |
| 349 | + rs -> rs.next() ? rs.getInt("value") : null); |
| 350 | + } catch (Exception e) { |
| 351 | + log.warn("Failed to obtain the value of OBProxy's configuration variable: {}", e.getMessage()); |
| 352 | + return null; |
| 353 | + } |
| 354 | + } |
| 355 | + |
| 356 | + private boolean isUnknownThreadIdError(Exception e) { |
| 357 | + return StringUtils.containsIgnoreCase(e.getMessage(), "Unknown thread id"); |
| 358 | + } |
| 359 | + |
| 360 | + private JdbcGeneralResult handleUnknownThreadIdError(ConnectionSession connectionSession, |
| 361 | + JdbcGeneralResult jdbcGeneralResult, Boolean isDirectedOBServer, |
| 362 | + boolean isEnabledGlobalClientSession, boolean isSupportedOracleModeKillSession) { |
| 363 | + if (Boolean.TRUE.equals(isDirectedOBServer)) { |
| 364 | + log.info("The current connection mode is directing observer, return error result directly"); |
| 365 | + return jdbcGeneralResult; |
| 366 | + } |
| 367 | + if (isEnabledGlobalClientSession) { |
| 368 | + log.info("The OBProxy has enabled the global client session, return error result directly"); |
| 369 | + return jdbcGeneralResult; |
| 370 | + } |
| 371 | + if (isSupportedOracleModeKillSession) { |
| 372 | + return tryKillSessionByAnonymousBlock(connectionSession, jdbcGeneralResult, |
| 373 | + jdbcGeneralResult.getSqlTuple()); |
| 374 | + } |
| 375 | + return tryKillSessionViaDirectConnectObServer(connectionSession, jdbcGeneralResult, |
| 376 | + jdbcGeneralResult.getSqlTuple()); |
| 377 | + } |
| 378 | + |
| 379 | + private boolean isOracleModeKillSessionSupported(String obVersion, ConnectionSession connectionSession) { |
| 380 | + return StringUtils.isNotBlank(obVersion) && |
| 381 | + VersionUtils.isGreaterThanOrEqualsTo(obVersion, ORACLE_MODEL_KILL_SESSION_WITH_BLOCK_OB_VERSION_NUMBER) |
| 382 | + && |
| 383 | + connectionSession.getDialectType() == DialectType.OB_ORACLE; |
| 384 | + } |
| 385 | + |
| 386 | + /** |
| 387 | + * Try to kill session by using anonymous code blocks. If successful, return a successful |
| 388 | + * jdbcGeneralResult, otherwise return the original jdbcGeneralResult. |
| 389 | + * |
| 390 | + * @param connectionSession |
| 391 | + * @param jdbcGeneralResult |
| 392 | + * @param sqlTuple |
| 393 | + * @return |
| 394 | + */ |
| 395 | + private JdbcGeneralResult tryKillSessionByAnonymousBlock(ConnectionSession connectionSession, |
| 396 | + JdbcGeneralResult jdbcGeneralResult, SqlTuple sqlTuple) { |
| 397 | + log.info("Kill query/session Unknown thread id error, try anonymous code blocks"); |
| 398 | + String executedSql = sqlTuple.getExecutedSql(); |
| 399 | + if (executedSql != null && executedSql.endsWith(";")) { |
| 400 | + executedSql = executedSql.substring(0, executedSql.length() - 1); |
| 401 | + } |
| 402 | + String anonymousCodeBlock = "BEGIN\n" |
| 403 | + + "EXECUTE IMMEDIATE '" |
| 404 | + + executedSql |
| 405 | + + "';\n" |
| 406 | + + "END;"; |
| 407 | + try { |
| 408 | + connectionSession.getSyncJdbcExecutor(ConnectionSessionConstants.BACKEND_DS_KEY) |
| 409 | + .execute(anonymousCodeBlock); |
| 410 | + return JdbcGeneralResult.successResult(sqlTuple); |
| 411 | + |
| 412 | + } catch (Exception e) { |
| 413 | + log.warn("Failed to kill session by using anonymous code blocks: {}", e.getMessage()); |
226 | 414 | return jdbcGeneralResult;
|
227 |
| - }).collect(Collectors.toList()); |
| 415 | + } |
| 416 | + } |
| 417 | + |
| 418 | + /** |
| 419 | + * Try to kill session by direct connect observer. If successful, return a successful |
| 420 | + * jdbcGeneralResult, otherwise return the original jdbcGeneralResult. |
| 421 | + * |
| 422 | + * @param connectionSession |
| 423 | + * @param jdbcGeneralResult |
| 424 | + * @param sqlTuple |
| 425 | + * @return |
| 426 | + */ |
| 427 | + private JdbcGeneralResult tryKillSessionViaDirectConnectObServer(ConnectionSession connectionSession, |
| 428 | + JdbcGeneralResult jdbcGeneralResult, SqlTuple sqlTuple) { |
| 429 | + try { |
| 430 | + log.info("Kill query/session Unknown thread id error, try direct connect observer"); |
| 431 | + directLinkServerAndExecute(sqlTuple.getExecutedSql(), connectionSession); |
| 432 | + return JdbcGeneralResult.successResult(sqlTuple); |
| 433 | + } catch (Exception e) { |
| 434 | + log.warn("Failed to direct connect observer {}", e.getMessage()); |
| 435 | + return jdbcGeneralResult; |
| 436 | + } |
228 | 437 | }
|
229 | 438 |
|
230 | 439 | private void directLinkServerAndExecute(String sql, ConnectionSession session)
|
|
0 commit comments