一、网络通信和服务
网络通信是整个MySql的基本服务,包括在此基础上衍生的其它相关服务,构成了Mysql客户端和服务端完成交互的主要方式。主要的功能包括:
1、网络初始化和服务初始化:包括参数、服务端和监听等。
2、网络交互模块:数据的收发和控制等。
3、交互协议模块:包含UNIX SOCKET套接字协议、TCP/IP协议,管道和共享内存(Share Memory)协议四种
这三大块基本就覆盖了网络通信和服务的主要的内容。
二、主要流程
主要的流程基本如下图:
网络通信的流程相对Redis来说要简单不少,此处暂时先忽略分布式通信的相关任务。
三、源码
仍然回到前面文章中的Main->mysqld_main函数:
bool my_init() {
char *str;
......
//线程及参数初始化
if (my_thread_global_init()) return true;
if (my_thread_init()) return true;
/* $HOME is needed early to parse configuration files located in ~/ */
if ((home_dir = getenv("HOME")) != nullptr)
home_dir = intern_filename(home_dir_buff, home_dir);
{
DBUG_TRACE;
DBUG_PROCESS(my_progname ? my_progname : "unknown");
#ifdef _WIN32
my_win_init();
#endif
MyFileInit();
......
return false;
}
}
static void my_win_init() {
DBUG_TRACE;
......
win_init_registry();
win32_init_tcp_ip();
MyWinfileInit();
}
static bool win32_init_tcp_ip() {
if (win32_have_tcpip()) {
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
/* Be a good citizen: maybe another lib has already initialised
sockets, so dont clobber them unless necessary */
if (WSAStartup(wVersionRequested, &wsaData)) {
/* Load failed, maybe because of previously loaded
incompatible version; try again */
WSACleanup();
if (!WSAStartup(wVersionRequested, &wsaData)) have_tcpip = 1;
} else {
if (wsaData.wVersion != wVersionRequested) {
/* Version is no good, try again */
WSACleanup();
if (!WSAStartup(wVersionRequested, &wsaData)) have_tcpip = 1;
} else
have_tcpip = 1;
}
}
return (0);
}
如果写过Windows平台的网络开发的,对最后这个函数的调用相关是非常清楚的。这下就明白了吧。接着向下:
void notify_connect() {
#ifndef _WIN32
const char *sockstr = getenv("NOTIFY_SOCKET");
if (sockstr == nullptr) {
#ifdef WITH_SYSTEMD_DEBUG
sql_print_warning(
"NOTIFY_SOCKET not set in environment. sd_notify messages will not be "
"sent!");
#endif /* WITH_SYSTEMD_DEBUG */
return;
}
size_t sockstrlen = strlen(sockstr);
size_t sunpathlen = sizeof(sockaddr_un::sun_path) - 1;
if (sockstrlen > sunpathlen) {
std::cerr << "Error: NOTIFY_SOCKET too long" << std::endl;
LogErr(SYSTEM_LEVEL, ER_SYSTEMD_NOTIFY_PATH_TOO_LONG, sockstr, sockstrlen,
sunpathlen);
return;
}
//UDP通信初始化
NotifyGlobals::socket = socket(AF_UNIX, SOCK_DGRAM, 0);
sockaddr_un addr;
socklen_t addrlen;
memset(&addr, 0, sizeof(sockaddr_un));
addr.sun_family = AF_UNIX;
if (sockstr[0] != '@') {
strcpy(addr.sun_path, sockstr);
addrlen = offsetof(struct sockaddr_un, sun_path) + sockstrlen + 1;
} else { // Abstract namespace socket
addr.sun_path[0] = '\0';
strncpy(&addr.sun_path[1], sockstr + 1, strlen(sockstr) - 1);
addrlen = offsetof(struct sockaddr_un, sun_path) + sockstrlen;
}
int ret = -1;
do {
ret = connect(NotifyGlobals::socket,
reinterpret_cast<const sockaddr *>(&addr), addrlen);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
char errbuf[512];
LogErr(WARNING_LEVEL, ER_SYSTEMD_NOTIFY_CONNECT_FAILED, sockstr,
my_strerror(errbuf, sizeof(errbuf) - 1, errno));
NotifyGlobals::socket = -1;
}
#endif /* not defined _WIN32 */
}
再其下,是大量的相关持久化的参数的初始化和配置,接下来是通过宏定义控制的相关功能接口的配置。下来是认证初始化:
void mysql_audit_initialize() {
#ifdef HAVE_PSI_INTERFACE
init_audit_psi_keys();
#endif
mysql_mutex_init(key_LOCK_audit_mask, &LOCK_audit_mask, MY_MUTEX_INIT_FAST);
memset(mysql_global_audit_mask, 0, sizeof(mysql_global_audit_mask));
}
bool Srv_session::module_init() {
if (srv_session_THRs_initialized) return false;
srv_session_THRs_initialized = true;
THR_stack_start_address = nullptr;
THR_srv_session_thread = nullptr;
server_session_list.init();
server_session_threads.init();
return false;
}
下来又是一批的锁、信号量和相关的初始化,包括自定义的一些字符串函数等。对整个应用线程的栈空间进行设置和自定义。
static void set_ports() {
char *env;
if (!mysqld_port &&
!opt_disable_networking) { // Get port if not from commandline
mysqld_port = MYSQL_PORT;
/*
if builder specifically requested a default port, use that
(even if it coincides with our factory default).
only if they didn't do we check /etc/services (and, failing
on that, fall back to the factory default of 3306).
either default can be overridden by the environment variable
MYSQL_TCP_PORT, which in turn can be overridden with command
line options.
*/
#if MYSQL_PORT_DEFAULT == 0
struct servent *serv_ptr;
if ((serv_ptr = getservbyname("mysql", "tcp")))
mysqld_port = ntohs((u_short)serv_ptr->s_port); /* purecov: inspected */
#endif
if ((env = getenv("MYSQL_TCP_PORT")))
mysqld_port = (uint)atoi(env); /* purecov: inspected */
}
if (!mysqld_unix_port) {
#ifdef _WIN32
mysqld_unix_port = (char *)MYSQL_NAMEDPIPE;
#else
mysqld_unix_port = MYSQL_UNIX_ADDR;
#endif
if ((env = getenv("MYSQL_UNIX_PORT")))
mysqld_unix_port = env; /* purecov: inspected */
}
}
int delegates_init() {
alignas(Trans_delegate) static char place_trans_mem[sizeof(Trans_delegate)];
alignas(Binlog_storage_delegate) static char
place_storage_mem[sizeof(Binlog_storage_delegate)];
alignas(Server_state_delegate) static char
place_state_mem[sizeof(Server_state_delegate)];
alignas(Binlog_transmit_delegate) static char
place_transmit_mem[sizeof(Binlog_transmit_delegate)];
alignas(Binlog_relay_IO_delegate) static char
place_relay_io_mem[sizeof(Binlog_relay_IO_delegate)];
transaction_delegate = new (place_trans_mem) Trans_delegate;
if (!transaction_delegate->is_inited()) {
LogErr(ERROR_LEVEL, ER_RPL_TRX_DELEGATES_INIT_FAILED);
return 1;
}
binlog_storage_delegate = new (place_storage_mem) Binlog_storage_delegate;
if (!binlog_storage_delegate->is_inited()) {
LogErr(ERROR_LEVEL, ER_RPL_BINLOG_STORAGE_DELEGATES_INIT_FAILED);
return 1;
}
server_state_delegate = new (place_state_mem) Server_state_delegate;
binlog_transmit_delegate = new (place_transmit_mem) Binlog_transmit_delegate;
if (!binlog_transmit_delegate->is_inited()) {
LogErr(ERROR_LEVEL, ER_RPL_BINLOG_TRANSMIT_DELEGATES_INIT_FAILED);
return 1;
}
binlog_relay_io_delegate = new (place_relay_io_mem) Binlog_relay_IO_delegate;
if (!binlog_relay_io_delegate->is_inited()) {
LogErr(ERROR_LEVEL, ER_RPL_BINLOG_RELAY_DELEGATES_INIT_FAILED);
return 1;
}
return 0;
}
继续查找和网络相关部分,密钥缓存和SSL加密通信:
bool process_key_caches(process_key_cache_t func) {
I_List_iterator<NAMED_ILINK> it(key_caches);
NAMED_ILINK *element;
while ((element = it++)) {
KEY_CACHE *key_cache = (KEY_CACHE *)element->data;
func(element->name, key_cache);
}
return false;
}
static void init_ssl() {
#if !defined(__sun)
#if defined(HAVE_PSI_MEMORY_INTERFACE)
static PSI_memory_info all_openssl_memory[] = {
{&key_memory_openssl, "openssl_malloc", 0, 0,
"All memory used by openSSL"}};
mysql_memory_register("mysqld_openssl", all_openssl_memory,
(int)array_elements(all_openssl_memory));
#endif /* defined(HAVE_PSI_MEMORY_INTERFACE) */
int ret = CRYPTO_set_mem_functions(my_openssl_malloc, my_openssl_realloc,
my_openssl_free);
if (ret == 0)
LogErr(WARNING_LEVEL, ER_SSL_MEMORY_INSTRUMENTATION_INIT_FAILED,
"CRYPTO_set_mem_functions");
#endif /* !defined(__sun) */
ssl_start();
}
void init_max_user_conn(void) {
hash_user_connections =
new collation_unordered_map<std::string, unique_ptr_my_free<user_conn>>(
system_charset_info, key_memory_user_conn);
}
最后看重点:
//第1740行有相关监听器和接收器的定义
static Connection_acceptor<Mysqld_socket_listener> *mysqld_socket_acceptor =
nullptr;
#ifdef _WIN32
static Named_pipe_listener *named_pipe_listener = NULL;
Connection_acceptor<Named_pipe_listener> *named_pipe_acceptor = NULL;
Connection_acceptor<Shared_mem_listener> *shared_mem_acceptor = NULL;
#ifdef _WIN32
int win_main(int argc, char **argv)
#else
int mysqld_main(int argc, char **argv)
#endif
{
......
if (init_ssl_communication()) unireg_abort(MYSQLD_ABORT_EXIT);
if (network_init()) unireg_abort(MYSQLD_ABORT_EXIT);
......
#if defined(_WIN32)
if (mysqld_socket_acceptor != nullptr)
mysqld_socket_acceptor->check_and_spawn_admin_connection_handler_thread();
setup_conn_event_handler_threads();
#else
mysql_mutex_lock(&LOCK_socket_listener_active);
// Make it possible for the signal handler to kill the listener.
socket_listener_active = true;
mysql_mutex_unlock(&LOCK_socket_listener_active);
if (opt_daemonize) {
if (nstdout != nullptr) {
// Show the pid on stdout if deamonizing and connected to tty
fprintf(nstdout, "mysqld is running as pid %lu\n", current_pid);
fclose(nstdout);
nstdout = nullptr;
}
mysqld::runtime::signal_parent(pipe_write_fd, 1);
}
mysqld_socket_acceptor->check_and_spawn_admin_connection_handler_thread();
mysqld_socket_acceptor->connection_event_loop();
......
}
看看网络初始化:
static bool network_init(void) {
if (opt_initialize) return false;
#ifdef HAVE_SYS_UN_H
std::string const unix_sock_name(mysqld_unix_port ? mysqld_unix_port : "");
#else
std::string const unix_sock_name("");
#endif
std::list<Bind_address_info> bind_addresses_info;
if (!opt_disable_networking || unix_sock_name != "") {
if (my_bind_addr_str != nullptr &&
check_bind_address_has_valid_value(my_bind_addr_str,
&bind_addresses_info)) {
LogErr(ERROR_LEVEL, ER_INVALID_VALUE_OF_BIND_ADDRESSES, my_bind_addr_str);
return true;
}
Bind_address_info admin_address_info;
if (!opt_disable_networking) {
if (my_admin_bind_addr_str != nullptr &&
check_admin_address_has_valid_value(my_admin_bind_addr_str,
&admin_address_info)) {
LogErr(ERROR_LEVEL, ER_INVALID_ADMIN_ADDRESS, my_admin_bind_addr_str);
return true;
}
/*
Port 0 is interpreted by implementations of TCP protocol
as a hint to find a first free port value to use and bind to it.
On the other hand, the option mysqld_admin_port can be assigned
the value 0 if a user specified a value that is out of allowable
range of values. Therefore, to avoid a case when an operating
system binds admin interface to am arbitrary selected port value,
set it explicitly to the value MYSQL_ADMIN_PORT in case it has value 0.
*/
if (mysqld_admin_port == 0) mysqld_admin_port = MYSQL_ADMIN_PORT;
}
Mysqld_socket_listener *mysqld_socket_listener = new (std::nothrow)
Mysqld_socket_listener(bind_addresses_info, mysqld_port,
admin_address_info, mysqld_admin_port,
admin_address_info.address.empty()
? false
: listen_admin_interface_in_separate_thread,
back_log, mysqld_port_timeout, unix_sock_name);
if (mysqld_socket_listener == nullptr) return true;
mysqld_socket_acceptor = new (std::nothrow)
Connection_acceptor<Mysqld_socket_listener>(mysqld_socket_listener);
if (mysqld_socket_acceptor == nullptr) {
delete mysqld_socket_listener;
mysqld_socket_listener = nullptr;
return true;
}
if (mysqld_socket_acceptor->init_connection_acceptor())
return true; // mysqld_socket_acceptor would be freed in unireg_abort.
if (report_port == 0) report_port = mysqld_port;
if (!opt_disable_networking) assert(report_port != 0);
}
#ifdef _WIN32
// Create named pipe
if (opt_enable_named_pipe) {
std::string pipe_name = mysqld_unix_port ? mysqld_unix_port : "";
named_pipe_listener = new (std::nothrow) Named_pipe_listener(&pipe_name);
if (named_pipe_listener == NULL) return true;
named_pipe_acceptor = new (std::nothrow)
Connection_acceptor<Named_pipe_listener>(named_pipe_listener);
if (named_pipe_acceptor == NULL) {
delete named_pipe_listener;
named_pipe_listener = NULL;
return true;
}
if (named_pipe_acceptor->init_connection_acceptor())
return true; // named_pipe_acceptor would be freed in unireg_abort.
}
// Setup shared_memory acceptor
if (opt_enable_shared_memory) {
std::string shared_mem_base_name =
shared_memory_base_name ? shared_memory_base_name : "";
Shared_mem_listener *shared_mem_listener =
new (std::nothrow) Shared_mem_listener(&shared_mem_base_name);
if (shared_mem_listener == NULL) return true;
shared_mem_acceptor = new (std::nothrow)
Connection_acceptor<Shared_mem_listener>(shared_mem_listener);
if (shared_mem_acceptor == NULL) {
delete shared_mem_listener;
shared_mem_listener = NULL;
return true;
}
if (shared_mem_acceptor->init_connection_acceptor())
return true; // shared_mem_acceptor would be freed in unireg_abort.
}
#endif // _WIN32
return false;
}
分为WIN和LINUX平台的创建,看其中一个即可,重点其实就在sql/conn_handler目录下的几个文件,这里使用了模板所以有一些跳转失效:
static inline bool spawn_admin_thread(MYSQL_SOCKET admin_socket,
const std::string &network_namespace) {
initialize_thread_context();
admin_thread_arg_t *arg_for_admin_socket_thread =
new (std::nothrow) admin_thread_arg_t(admin_socket, network_namespace);
if (arg_for_admin_socket_thread == nullptr) return true;
int ret = mysql_thread_create(
key_thread_handle_con_admin_sockets, &admin_socket_thread_id,
&admin_socket_thread_attrib, admin_socket_thread,
(void *)arg_for_admin_socket_thread);
(void)my_thread_attr_destroy(&admin_socket_thread_attrib);
if (ret) {
LogErr(ERROR_LEVEL, ER_CANT_CREATE_ADMIN_THREAD, errno);
return true;
}
wait_for_admin_thread_started();
return false;
}
bool Mysqld_socket_listener::check_and_spawn_admin_connection_handler_thread()
const {
if (m_use_separate_thread_for_admin) {
if (spawn_admin_thread(m_admin_interface_listen_socket,
m_admin_bind_address.network_namespace))
return true;
}
return false;
}
bool Mysqld_socket_listener::setup_listener() {
/*
It's matter to add a socket for admin connection listener firstly,
before listening sockets for other connection types be added.
It is done in order to check availability of new incoming connection
on admin interface with higher priority than on other interfaces..
*/
if (!m_admin_bind_address.address.empty()) {
TCP_socket tcp_socket(m_admin_bind_address.address,
m_admin_bind_address.network_namespace,
m_admin_tcp_port, m_backlog, m_port_timeout);
MYSQL_SOCKET mysql_socket = tcp_socket.get_listener_socket();
if (mysql_socket.fd == INVALID_SOCKET) return true;
m_admin_interface_listen_socket = mysql_socket;
if (!m_use_separate_thread_for_admin) {
m_socket_vector.emplace_back(mysql_socket, Socket_type::TCP_SOCKET,
&m_admin_bind_address.network_namespace,
Socket_interface_type::ADMIN_INTERFACE);
}
}
// Setup tcp socket listener
if (m_tcp_port) {
for (const auto &bind_address_info : m_bind_addresses) {
TCP_socket tcp_socket(bind_address_info.address,
bind_address_info.network_namespace, m_tcp_port,
m_backlog, m_port_timeout);
MYSQL_SOCKET mysql_socket = tcp_socket.get_listener_socket();
if (mysql_socket.fd == INVALID_SOCKET) return true;
m_socket_vector.emplace_back(mysql_socket, Socket_type::TCP_SOCKET,
&bind_address_info.network_namespace,
Socket_interface_type::DEFAULT_INTERFACE);
}
}
#if defined(HAVE_SYS_UN_H)
// Setup unix socket listener
if (m_unix_sockname != "") {
Unix_socket unix_socket(&m_unix_sockname, m_backlog);
MYSQL_SOCKET mysql_socket = unix_socket.get_listener_socket();
if (mysql_socket.fd == INVALID_SOCKET) return true;
Listen_socket s(mysql_socket, Socket_type::UNIX_SOCKET);
m_socket_vector.push_back(s);
m_unlink_sockname = true;
}
#endif /* HAVE_SYS_UN_H */
setup_connection_events(m_socket_vector);
return false;
}
const Listen_socket *Mysqld_socket_listener::get_listen_socket() const {
/*
In case admin interface was set up, then first check whether an admin socket
ready to accept a new connection. Doing this way provides higher priority
to admin interface over other listeners.
*/
#ifdef HAVE_POLL
uint start_index = 0;
if (!m_admin_bind_address.address.empty() &&
!m_use_separate_thread_for_admin) {
if (m_poll_info.m_fds[0].revents & POLLIN) {
return &m_socket_vector[0];
} else
start_index = 1;
}
for (uint i = start_index; i < m_socket_vector.size(); ++i) {
if (m_poll_info.m_fds[i].revents & POLLIN) {
return &m_socket_vector[i];
}
}
#else // HAVE_POLL
if (!m_admin_bind_address.address.empty() &&
!m_use_separate_thread_for_admin &&
FD_ISSET(mysql_socket_getfd(m_admin_interface_listen_socket),
&m_select_info.m_read_fds)) {
return &m_socket_vector[0];
}
for (const auto &socket_element : m_socket_vector) {
if (FD_ISSET(mysql_socket_getfd(socket_element.m_socket),
&m_select_info.m_read_fds)) {
return &socket_element;
}
}
#endif // HAVE_POLL
return nullptr;
;
}
Channel_info *Mysqld_socket_listener::listen_for_connection_event() {
#ifdef HAVE_POLL
int retval = poll(&m_poll_info.m_fds[0], m_socket_vector.size(), -1);
#else
m_select_info.m_read_fds = m_select_info.m_client_fds;
int retval = select((int)m_select_info.m_max_used_connection,
&m_select_info.m_read_fds, 0, 0, 0);
#endif
if (retval < 0 && socket_errno != SOCKET_EINTR) {
/*
select(2)/poll(2) failed on the listening port.
There is not much details to report about the client,
increment the server global status variable.
*/
++connection_errors_query_block;
if (!select_errors++ && !connection_events_loop_aborted())
LogErr(ERROR_LEVEL, ER_CONN_SOCKET_SELECT_FAILED, socket_errno);
}
if (retval < 0 || connection_events_loop_aborted()) return nullptr;
/* Is this a new connection request ? */
const Listen_socket *listen_socket = get_listen_socket();
/*
When poll/select returns control flow then at least one ready server socket
must exist. Check that get_ready_socket() returns a valid socket.
*/
assert(listen_socket != nullptr);
MYSQL_SOCKET connect_sock;
#ifdef HAVE_SETNS
/*
If a network namespace is specified for a listening socket then set this
network namespace as active before call to accept().
It is not clear from manuals whether a socket returned by a call to
accept() borrows a network namespace from a server socket used for
accepting a new connection. For that reason, assign a network namespace
explicitly before calling accept().
*/
std::string network_namespace_for_listening_socket;
if (listen_socket->m_socket_type == Socket_type::TCP_SOCKET) {
network_namespace_for_listening_socket =
(listen_socket->m_network_namespace != nullptr
? *listen_socket->m_network_namespace
: std::string(""));
if (!network_namespace_for_listening_socket.empty() &&
set_network_namespace(network_namespace_for_listening_socket))
return nullptr;
}
#endif
if (accept_connection(listen_socket->m_socket, &connect_sock)) {
#ifdef HAVE_SETNS
if (!network_namespace_for_listening_socket.empty())
(void)restore_original_network_namespace();
#endif
return nullptr;
}
#ifdef HAVE_SETNS
if (!network_namespace_for_listening_socket.empty() &&
restore_original_network_namespace())
return nullptr;
#endif
#ifdef HAVE_LIBWRAP
if ((listen_socket->m_socket_type == Socket_type::TCP_SOCKET) &&
check_connection_refused_by_tcp_wrapper(connect_sock)) {
return nullptr;
}
#endif // HAVE_LIBWRAP
Channel_info *channel_info = nullptr;
if (listen_socket->m_socket_type == Socket_type::UNIX_SOCKET)
channel_info = new (std::nothrow) Channel_info_local_socket(connect_sock);
else
channel_info = new (std::nothrow) Channel_info_tcpip_socket(
connect_sock, (listen_socket->m_socket_interface ==
Socket_interface_type::ADMIN_INTERFACE));
if (channel_info == nullptr) {
(void)mysql_socket_shutdown(connect_sock, SHUT_RDWR);
(void)mysql_socket_close(connect_sock);
connection_errors_internal++;
return nullptr;
}
#ifdef HAVE_SETNS
if (listen_socket->m_socket_type == Socket_type::TCP_SOCKET &&
!network_namespace_for_listening_socket.empty())
static_cast<Channel_info_tcpip_socket *>(channel_info)
->set_network_namespace(network_namespace_for_listening_socket);
#endif
return channel_info;
}
没有一个好的源码查看工具,看起代码来有点不方便,浪费了不少时间。其实看到最后会发现,所有的工作重新回归到了最简朴的网络通信,当然这其中为了抽象使用了一些抽象类,把最初提到的四种通信方法形成了四个类来实现。这也是一个很好的方式,但比起来前面的Redis来说,封装的要简单不少。
这个文件夹下,对通信进行了以下几个封装:
channel_info:连接通道管理器,其实就是客户端的相关管理
connection_acceptor:网络通信接收器,负责接收连接消息
connection_handler:连接句柄,客户处理机制
connection_handler_impl:连接管理机制的接口
connection_handler_manager:连接管理器
connection_handler_*_thread:连接需要的线程处理器
源码之前,了无秘密。
四、总结
网络通信在MySql中的重要性并不如Redis中对网络通信的重要性,毕竟做为一个关系型数据库,不可能承受几十上百万的直接压力,更多的是对数据的存储和分析的应用。不过随着MySql的发展,以后会不会在分布式的道路上走得更远,或者说走出另外一条路,这也未为可知。毕竟,变化是永恒的,而未来是无法预测的。
但是网络通信做为基础设施,是无论如何无法回避的,这在任何一个场景下都一样。学习要强干弱枝,分清主次。