Skip to content

Commit 29d49fa

Browse files
authored
feat(kill session): adapt global client session and using block in oracle model in kill session (#2978)
* adapt global client session and oracle model pl * modify according to pr commit * modify code according to pr * modify code format * modify code format * modify code format * add judgment of directing observer * modify code format * Check whether the directly connected sql matches the oracle and mysql modes * Modify the code according to pr * modify code format * add comment for main method * modify according to pr * Optimize readability
1 parent 743c034 commit 29d49fa

File tree

2 files changed

+223
-14
lines changed

2 files changed

+223
-14
lines changed

server/odc-server/src/main/java/com/oceanbase/odc/server/web/controller/v2/ConnectSessionController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public SuccessResponse<Boolean> killQuery(@PathVariable String sessionId) {
209209
* kill session
210210
*
211211
* @param req
212-
* @return
212+
* @return kill result
213213
*/
214214
@ApiOperation(value = "kill session", notes = "终止会话接口")
215215
@RequestMapping(value = "/sessions/killSession", method = RequestMethod.POST)

server/odc-service/src/main/java/com/oceanbase/odc/service/db/session/DefaultDBSessionManage.java

Lines changed: 222 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
*/
1616
package com.oceanbase.odc.service.db.session;
1717

18+
import static com.oceanbase.odc.core.shared.constant.DialectType.OB_MYSQL;
19+
1820
import java.sql.Connection;
1921
import java.sql.Statement;
22+
import java.util.ArrayList;
2023
import java.util.List;
2124
import java.util.Map;
2225
import java.util.Objects;
@@ -40,12 +43,14 @@
4043

4144
import com.google.common.collect.Lists;
4245
import com.oceanbase.odc.common.util.StringUtils;
46+
import com.oceanbase.odc.common.util.VersionUtils;
4347
import com.oceanbase.odc.core.authority.util.SkipAuthorize;
4448
import com.oceanbase.odc.core.datasource.SingleConnectionDataSource;
4549
import com.oceanbase.odc.core.session.ConnectionSession;
4650
import com.oceanbase.odc.core.session.ConnectionSessionConstants;
4751
import com.oceanbase.odc.core.session.ConnectionSessionUtil;
4852
import com.oceanbase.odc.core.shared.Verify;
53+
import com.oceanbase.odc.core.shared.constant.DialectType;
4954
import com.oceanbase.odc.core.shared.exception.InternalServerError;
5055
import com.oceanbase.odc.core.shared.model.OdcDBSession;
5156
import com.oceanbase.odc.core.sql.execute.model.JdbcGeneralResult;
@@ -78,6 +83,13 @@ public class DefaultDBSessionManage implements DBSessionManageFacade {
7883
+ "(?<port>[0-9]{1,5})\\*/.*";
7984
private static final Pattern SERVER_PATTERN = Pattern.compile(SERVER_REGEX);
8085
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+
8193

8294
@Autowired
8395
private ConnectSessionService sessionService;
@@ -198,33 +210,230 @@ private List<KillSessionResult> doKillSessionOrQuery(
198210
@SkipAuthorize("odc internal usage")
199211
public List<JdbcGeneralResult> executeKillSession(ConnectionSession connectionSession, List<SqlTuple> sqlTuples,
200212
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) {
201223
List<JdbcGeneralResult> results =
202224
connectionSession.getSyncJdbcExecutor(ConnectionSessionConstants.BACKEND_DS_KEY)
203225
.execute(new OdcStatementCallBack(sqlTuples, connectionSession, true, null, false));
204226
if (results == null) {
205227
log.warn("Execution of the kill session command failed with unknown error, sql={}", sqlScript);
206228
throw new InternalServerError("Unknown error");
207229
}
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) {
210251
try {
211252
jdbcGeneralResult.getQueryResult();
212253
} 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);
222258
} 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);
224261
}
225262
}
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());
226414
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+
}
228437
}
229438

230439
private void directLinkServerAndExecute(String sql, ConnectionSession session)

0 commit comments

Comments
 (0)