您好,欢迎访问一九零五行业门户网

Mysql源码学习――Connection Manager_MySQL

bitscn.com
1.连接的线程数
mysql支持单线程和多线程两种连接线程数。如果是单线程,则在同一时刻,只能有一个connection连接到mysql,
其他的连接会被挂起。如果是多线程,则同一时刻可以支持多个connection同时连接到服务器。
可以通过设置服务器的启动参数来设定连接的线程数:
mysqld.exe --thread-handling=no-threads
mysqld.exe --thread-handling=one-thread-per-connection
服务器如何通过参数来选择使用哪种方式的呢?且看服务器中的分支代码:
#ifdef embedded_library
  one_thread_scheduler(&thread_scheduler);
#else
  if (global_system_variables.thread_handling
      scheduler_one_thread_per_connection)
    one_thread_per_connection_scheduler(&thread_scheduler);
  else if (global_system_variables.thread_handling == scheduler_no_threads)
    one_thread_scheduler(&thread_scheduler);
  else
    pool_of_threads_scheduler(&thread_scheduler);  /* purecov: tested */
#endif
这段代码出现在get_options函数中,此函数是根据传入的服务器参数,设置相应参数的模式。由这段代码可以看出,如果定义了embedded_library宏定义(估计应该是嵌入式使用),则调用one_thread_scheduler,即使用单线程。如果参数小于等于scheduler_one_thread_per_connection,则调用one_thread_per_connection_scheduler,即每个连接一个线程,即多线程。 至于global_system_variables.thread_handling是如何进行设置的呢?其实就是根据我们传递给服务器的参数--thread-handling进行设置的,参数的设置统一在函数get_options中,其调用mysqld_get_one_option,其中有个分支,代码如下:
case opt_thread_handling:
  {
    global_system_variables.thread_handling=
      find_type_or_exit(argument, &thread_handling_typelib, opt->name)-1;
    break;
  }
对参数初始化有兴趣的可以具体的看下get_options这个函数,这里就不详细讲解了。 我们来看下one_thread_scheduler和one_thread_per_connection_scheduler的源代码,看下他们都做了些什么?
void one_thread_scheduler(scheduler_functions* func)
{
  func->max_threads= 1;
#ifndef embedded_library
  func->add_connection= handle_connection_in_main_thread;
#endif
  func->init_new_connection_thread= init_dummy;
  func->end_thread= no_threads_end;
}
void one_thread_per_connection_scheduler(scheduler_functions* func)
{
  func->max_threads= max_connections;
  func->add_connection= create_thread_to_handle_connection;
  func->end_thread= one_thread_per_connection_end;
}
原来就是设置了一个结构体中scheduler_functions的参数,只不过这些参数是一些函数指针罢了,也就是说在具体的调用中,
只需要调用add_connection或end_thread即可,不需要知道到底是调用了哪个函数,这大概就是一种变形的多态性吧。
2.初始化网络配置
网络配置比较简单,就是设置端口,创建套接字,绑定端口,监听端口。实现全部集中在network_init函数中,由于这个函数确
实没什么好讲的,如果对socket不熟悉的话,可以去网上搜索下相关知识。这里直接给出相应的伪代码:
network_init
{
    set_ports; //设置端口号,#define mysql_port            3306
    socket;//创建套接字
    bind;       //绑定端口号
    listen;//监听端口号
}
3.连接的方式
进程间通信的方式不止是socket,还有其他很多方式。mysql支持三种连接方式:namepipe、socket和shared memory,
即命名管道、套接字和共享内存的方式。这三种方式是可以共存的。默认只使用套接字。
tcp/ip套接字方式是mysql在任何平台下都提供的连接方式,也是网络中使用得最多的一种方式。这种方式在tcp/ip连接上建立
一个基于网络的连接请求,一般情况下客户端在一台服务器上,而mysql实例在另一台服务器上,这两台机器通过一个tcp/ip网络连接。
例如,我可以在windows服务器下请求一台远程linux服务器下的mysql实例。
在windows 2000、windows xp、windows 2003和windows vista以及在此之后的windows操作系统中,如果两个需要通
信的进程在同一台服务器上,那么可以使用命名管道,sql server数据库默认安装后的本地连接也使用命名管道。在mysql数据库中,
需在配置文件中启用--enable-named-pipe选项。在mysql 4.1之后的版本中,mysql还提供了共享内存的连接方式,在配置文件中
添加--shared-memory。如果想使用共享内存的方式,在连接时,mysql客户端还必须使用-protocol=memory选项。
启动时可以通过下面的参数进行设置。
mysqld.exe --enable-named-pipe
mysqld.exe --shared-memory
除了在启动时进行参数设置外,也可以通过修改my.ini文件进行设置。我们来看下源码中选择连接方式的分支函数handle_connections_methods:
handle_connections_methods()
{
if (hpipe != invalid_handle_value)
{
       handler_count++;
       if (pthread_create(&hthread,&connection_attrib,
               handle_connections_namedpipes, 0))
       {
      sql_print_warning(can't create thread to handle named pipes);
      handler_count--;
    }
}
    if (have_tcpip && !opt_disable_networking)
  {
    handler_count++;
    if (pthread_create(&hthread,&connection_attrib,
               handle_connections_sockets, 0))
    {
      sql_print_warning(can't create thread to handle tcp/ip);
      handler_count--;
    }
  }
if (opt_enable_shared_memory)
  {
    handler_count++;
    if (pthread_create(&hthread,&connection_attrib,
               handle_connections_shared_memory, 0))
    {
      sql_print_warning(can't create thread to handle shared memory);
      handler_count--;
    }
  }
}
由于对于namepipe和memory share的通信方式不太了解,这里只研究socket的通信方式。从代码中可以看出,handle_connections_sockets便是socket的设置,我们就来看下它。
4.socket管理创建新线程socket管理其实比较简单,直接给出其伪代码:
handle_connections_sockets
{
    select; //监视socket文件描述符
    new_socket = accept;//处理到来的客户端连接
    thd = new thd;创建thd类
    vio_tmp = vio_new(new_socket,vio_type_tcpip, 0);   //初始化vio结构体
    my_net_init(&thd->net, vio_tmp);//初始化thd的net结构体
    create_new_thread(thd);//为这个连接创建一个新的线程,如果是单线程模式的话,就不会创建一个新线程
}
首先是select函数进行监视socket端口,如果监控到有连接,则通过accept函数接受客户端的连接,然后新建一个thd类,将连接参数全部设置到thd类的参数上,最后调用create_new_thread函数,这个函数便是重点。 我们进入这个函数,看下做了啥。
create_new_thread
{
    ++connection_count;//全局连接数自增
    thread_count++;    //全局线程数自增
    thread_scheduler.add_connection(thd);//真正创建线程
}
so easy,首先将全局连接数+1,全局线程数+1,然后调用add_connection函数,这个函数就是我们在上面第一步设置连接的
线程数中,one_thread_scheduler和one_thread_per_connection_scheduler中设置的一个参数。这两者的区别便是是否创建了
一个新的线程来处理到来的连接。one_thread_scheduler是单线程方式,木有新建线程。我们重点研究one_thread_per_connection_scheduler,其设置的add_connection函数为create_thread_to_handle_connection:
create_thread_to_handle_connection(thd *thd)
{
    thread_created++;
    threads.append(thd); // 创建线程数自增,并加入到threads链表上
    pthread_create(&thd->real_id,&connection_attrib,
                              handle_one_connection,
                              (void*) thd);//这就是真正创建线程的地方了,函数便是handle_one_connection
}
可见,最后调用了pthread_create函数,这个函数便是创建一个新的线程,新线程的处理函数为handle_one_connection.
5.新线程处理流程
新线程处理函数为handle_one_connection,到此位置,一个新的connection被一个新创建的线程所单独处理。我们看下其中
是如何进行处理的。
handle_one_connection(void *arg)
{
    for (;;)
    {
        lex_start(thd); //初始化词法分析结构体
        login_connection(thd); //用户认证,失败报错
        prepare_new_connection_state(thd* thd);//initialize thd to handle queries
         while (!net->error && net->vio != 0 &&   //循环处理command
           !(thd->killed == thd::kill_connection))
        {
            if (do_command(thd))
              break;                   //处理失败跳出
          }
          end_connection(thd);         //关闭连接
          close_connection(thd, 0, 1);
         thread_scheduler.end_thread(thd,1);//结束线程
                  return 0;
    }
}
首先进行了词法分析结构体的初始化,然后进行用户认证,认证成功后通过do_command循环执行客户端发过来的命令。
6.总结
整个connection manager的流程十分清晰,单线程的连接一般很少使用,大多使用多线程方式。多线程连接中其实还涉及到线程缓冲
池的概念,即如果一个连接断开后,其所创建的线程不会被销毁掉,而是放到缓冲池中,等待下一个新的connection到来时,首先去线程
缓冲池查找是否有空闲的线程,有的话直接使用,木有的话才去创建新的线程来管理这个connection。这些属于thread manage的内容,
下节进行thread manage的学习。
ps. 上周六迎来第一个双休,结果还是被叫道公司改bug,我忍。不过,boss,前几个月的加班费啥时候发啊?不准拖欠民工工资…
踏着落叶,追寻着我的梦想。转载请注明出处
摘自 心中无码 bitscn.com
其它类似信息

推荐信息