源码解读
请求接入流程
PacketCodec
(编解码)FrontendChannelInboundHandler
(业务处理器)是完成这个流程的关键环节。
PacketCodec
public final class PacketCodec extends ByteToMessageCodec<DatabasePacket> {
private final DatabasePacketCodecEngine databasePacketCodecEngine;
@Override
protected void decode(final ChannelHandlerContext context, final ByteBuf in, final List<Object> out) {
int readableBytes = in.readableBytes();
if (!databasePacketCodecEngine.isValidHeader(readableBytes)) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Read from client {} : \n {}", context.channel().id().asShortText(), ByteBufUtil.prettyHexDump(in));
}
databasePacketCodecEngine.decode(context, in, out, readableBytes);
}
@SuppressWarnings("unchecked")
@Override
protected void encode(final ChannelHandlerContext context, final DatabasePacket message, final ByteBuf out) {
databasePacketCodecEngine.encode(context, message, out);
if (log.isDebugEnabled()) {
log.debug("Write to client {} : \n {}", context.channel().id().asShortText(), ByteBufUtil.prettyHexDump(out));
}
}
}
所有请求需要先执行解码,才交给FrontendChannelInboundHandler
;同理所有的响应都需要由编码方法打包伪装成MySQL的包返回。
入口为FrontendChannelInboundHandler
类channelRead
方法,流程如下图所示:
FrontendChannelInboundHandler
public final class FrontendChannelInboundHandler extends ChannelInboundHandlerAdapter {
private final DatabaseProtocolFrontendEngine databaseProtocolFrontendEngine;
private final BackendConnection backendConnection;
private volatile boolean authorized;
public FrontendChannelInboundHandler(final DatabaseProtocolFrontendEngine databaseProtocolFrontendEngine) {
this.databaseProtocolFrontendEngine = databaseProtocolFrontendEngine;
TransactionType transactionType = TransactionType.valueOf(ProxyContext.getInstance().getMetaDataContexts().getProps().getValue(ConfigurationPropertyKey.PROXY_TRANSACTION_TYPE));
backendConnection = new BackendConnection(transactionType);
}
@Override
public void channelActive(final ChannelHandlerContext context) {
ChannelThreadExecutorGroup.getInstance().register(context.channel().id());
backendConnection.setConnectionId(databaseProtocolFrontendEngine.getAuthEngine().handshake(context));
}
@Override
public void channelRead(final ChannelHandlerContext context, final Object message) {
if (!authorized) {
authorized = auth(context, (ByteBuf) message);
return;
}
ProxyStateContext.execute(context, message, databaseProtocolFrontendEngine, backendConnection);
}
private boolean auth(final ChannelHandlerContext context, final ByteBuf message) {
try (PacketPayload payload = databaseProtocolFrontendEngine.getCodecEngine().createPacketPayload(message)) {
AuthenticationResult authResult = databaseProtocolFrontendEngine.getAuthEngine().auth(context, payload);
if (authResult.isFinished()) {
backendConnection.setGrantee(new Grantee(authResult.getUsername(), authResult.getHostname()));
backendConnection.setCurrentSchema(authResult.getDatabase());
}
return authResult.isFinished();
// CHECKSTYLE:OFF
} catch (final Exception ex) {
// CHECKSTYLE:ON
log.error("Exception occur: ", ex);
context.write(databaseProtocolFrontendEngine.getCommandExecuteEngine().getErrorPacket(ex));
}
return false;
}
@Override
public void channelInactive(final ChannelHandlerContext context) {
context.fireChannelInactive();
closeAllResources(context);
}
private void closeAllResources(final ChannelHandlerContext context) {
databaseProtocolFrontendEngine.release(backendConnection);
PrimaryVisitedManager.clear();
backendConnection.closeResultSets();
backendConnection.closeStatements();
backendConnection.closeConnections(true);
backendConnection.closeCalciteExecutor();
ChannelThreadExecutorGroup.getInstance().unregister(context.channel().id());
}
@Override
public void channelWritabilityChanged(final ChannelHandlerContext context) {
if (context.channel().isWritable()) {
backendConnection.getResourceLock().doNotify();
}
}
}
这里核心是去调用了ProxyStateContext
的execute方法:
private static final Map<StateType, ProxyState> STATES = new ConcurrentHashMap<>(3, 1);
static {
STATES.put(StateType.OK, new OKProxyState());
STATES.put(StateType.LOCK, new LockProxyState());
STATES.put(StateType.CIRCUIT_BREAK, new CircuitBreakProxyState());
}
public static void execute(final ChannelHandlerContext context, final Object message,
final DatabaseProtocolFrontendEngine databaseProtocolFrontendEngine, final BackendConnection backendConnection) {
STATES.get(ProxyContext.getInstance().getStateContext().getCurrentState()).execute(context, message, databaseProtocolFrontendEngine, backendConnection);
}
ProxyState
接口有三个实现类:OKProxyState
、LockProxyState
、CircuitBreakProxyState
。
OKProxyState
以OKProxyState为例:
public final class OKProxyState implements ProxyState {
@Override
public void execute(final ChannelHandlerContext context, final Object message, final DatabaseProtocolFrontendEngine databaseProtocolFrontendEngine, final BackendConnection backendConnection) {
boolean supportHint = ProxyContext.getInstance().getMetaDataContexts().getProps().<Boolean>getValue(ConfigurationPropertyKey.PROXY_HINT_ENABLED);
boolean isOccupyThreadForPerConnection = databaseProtocolFrontendEngine.getFrontendContext().isOccupyThreadForPerConnection();
ExecutorService executorService = CommandExecutorSelector.getExecutorService(
isOccupyThreadForPerConnection, supportHint, backendConnection.getTransactionStatus().getTransactionType(), context.channel().id());
executorService.execute(new CommandExecutorTask(databaseProtocolFrontendEngine, backendConnection, context, message));
}
}
此处CommandExecutorTask
是实现了Runnable接口的,实际上executorService
调用的是CommandExecutorTask
的run方法:
public final class CommandExecutorTask implements Runnable {
private final DatabaseProtocolFrontendEngine databaseProtocolFrontendEngine;
private final BackendConnection backendConnection;
private final ChannelHandlerContext context;
private final Object message;
/**
* To make sure SkyWalking will be available at the next release of ShardingSphere,
* a new plugin should be provided to SkyWalking project if this API changed.
*
* @see <a href="https://github.com/apache/skywalking/blob/master/docs/en/guides/Java-Plugin-Development-Guide.md#user-content-plugin-development-guide">Plugin Development Guide</a>
*/
@Override
public void run() {
boolean isNeedFlush = false;
try (PacketPayload payload = databaseProtocolFrontendEngine.getCodecEngine().createPacketPayload((ByteBuf) message)) {
ConnectionStatus connectionStatus = backendConnection.getConnectionStatus();
if (!backendConnection.getTransactionStatus().isInConnectionHeldTransaction()) {
connectionStatus.waitUntilConnectionRelease();
connectionStatus.switchToUsing();
}
isNeedFlush = executeCommand(context, payload, backendConnection);
// CHECKSTYLE:OFF
} catch (final Exception ex) {
// CHECKSTYLE:ON
processException(ex);
} finally {
Collection<SQLException> exceptions = closeExecutionResources();
if (isNeedFlush) {
context.flush();
}
if (!backendConnection.getTransactionStatus().isInConnectionHeldTransaction()) {
exceptions.addAll(backendConnection.closeConnections(false));
}
processClosedExceptions(exceptions);
}
}
}