PowerDNS-架构分析

//这部分是有关后端查询的部分。假设后端用的是mysql,可以看到如果没有缓存的话那就是直接对数据库的查询
class gMySQLFactory : public BackendFactory
{
    DNSBackend *make(const string &suffix="")
    {
        return new gMySQLBackend(d_mode,suffix);
    }
    //构造函数
    gMySQLBackend::gMySQLBackend
    {
        reconnect();
        {
            setDB(new SMySQL())
        }
    }
}
class SMySQL()
{
    SMySQL::SMySQL()
    {
        connect();
        {
            //注意它在连接的时候会有retry机制,这个就是和真正的mysql建立连接
            mysql_real_connect()
        }
    }
    //所有的query操作都会转化为mysql的execute操作
    execute()
    {
        调用mysql_query
    }
}




class UDPNameserver
{
    //构造函数,构造的时候监听好ipv4或者ipv6的端口
    UDPNameserver(true)
    {
        //依据监听的套接字去调用不同的函数,ipv4或者ipv6
        if(!::arg()["local-address"].empty())
            bindIPv4()
            {
                //这里解析它要监听的地址(包括IP和port)
                vector<string>locals;
                stringtok(locals,::arg()["local-address"]," ,");

                for(;i=locals.begin();i!=locals.end();++i)
                {
                    s=socket(AF_INET,SOCK_DGRAM,0);
                    //如果可重用的话,会把每一个socket设置成SO_REUSEPORT属性,这样的话,多个线程可以监听同一个端口了,内核帮你做选择,你不需要锁了, 对于udp来说,这个时候的端口就是可以接收数据的
                    #ifdef SO_REUSEPORT
                    if( d_can_reuseport )
                        if( setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)))
                              d_can_reuseport = false;
                     #endif
                    bind(s, (sockaddr*)&locala, locala.getSocklen())
                    d_sockets.push_back(s);
                    struct pollfd pfd;
                    pfd.fd = s;
                    pfd.events = POLLIN;
                    pfd.revents = 0;
                    d_rfds.push_back(pfd);
                }
            }
        if(!::arg()["local-ipv6"].empty())
            bindIPv6();
    }
}
class TCPNameserver
{
    //获取很多配置文件的时间
for(vector<string>::const_iteratorladdr=locals.begin();laddr!=locals.end();++laddr) {
    //通过local-address获取到它要监听的地址,注意这里是写的几个就监听几个端口,当然如果写的是一样的话,那就可能会去监听同一个端口了
    vector<string>locals;
    stringtok(locals,::arg()["local-address"]," ,");

    for(;laddr=locals.begin();laddr!=locals.end();++laddr)
    {
        int s=socket(AF_INET,SOCK_STREAM,0);
        //为监听socket设置SO_REUSEPORT选项
        bind()
        listen(s,128);
        d_sockets.push_back(s);
        struct pollfd pfd;
        memset(&pfd, 0, sizeof(pfd));
        pfd.fd = s;
        pfd.events = POLLIN;
        d_prfds.push_back(pfd);
    }
}
class UeberBackend
{
    //它的构造函数比较特殊
    UeberBackend::UeberBackend(const string &pname)
    {
        backends=BackendMakers().all(pname=="key-only");
        
    }
    
    void go()
    {
        //唤醒条件变量d_cond,通知睡眠在上面的线程
        pthread_mutex_lock(&d_mut);
        d_go=true;
        pthread_cond_broadcast(&d_cond);
        pthread_mutex_unlock(&d_mut);
    }
    loadmodule(const string &name)
    {
        //打开动态连接库
        void *dlib=dlopen(name.c_str(), RTLD_NOW);
    }
}
class BackendMakers
{
   
    all()
    {
        if(metadataOnly)
            //这个就是加载的模块,make就是每个模块自定义的函数,这里假定用了mysql作为后端,那就去看gmysql的make函数
            made = d_repository[i->first]->makeMetadataOnly(i->second);
            {
                return this->make(suffix);
            }
    }
    launch
    {
        for(vector<string>::const_iterator i=parts.begin();i!=parts.end();++i)
        {
            const string &part=*i;
            加载到d_repository中
            load(module);
            
            d_repository[module]->declareArguments(name);
            d_instances.push_back(make_pair(module,name));   
        }
    }
    load
    {
        //继续调用它
        UeberBackend::loadmodule
    }
}
class Distributor
{
    //如果是一个线程的话就调用single这个
    void create(int n)
    {
        if( n == 1 )
            return new SingleThreadDistributor<Answer,Question,Backend>();
        else
            return new MultiThreadDistributor<Answer,Question,Backend>( n );
    }
}
class PacketHandler
{
    void question()
    {
        //这个是lua的逻辑,也就是说如果指定lua作为查询逻辑的话,那就直接用lua查询就可以了
        if(d_pdl)
        {
            ret=d_pdl->prequery(p);
            if(ret)
              return ret;
        }
        return doQuestion(p);
    }
    //否则才去后端查询
    void doQuestion
    {
        //这个是真正的去查询后端了
        
    }
}
//这个是distributor创造出来的单实例,这种情况
class SingleThreadDistributor
{
    //只是new一个后端实例
    SingleThreadDistributor()
    {
        //对于dns权威来说,它的Backend就是PacketHandler
        b=new Backend;
    }
    //callback就是那个sendout函数
    void question(Question* q, callback_t callback)
    {
        Answer *a;
        bool allowRetry=true;
        retry:
            try 
            {
                if (!b)
                {
                  allowRetry=false;
                  b=new Backend;
                }
                //调用上面实例化出来的b去查询question,然后把结果复制给answer a 
                a=b->question(q); // a can be NULL!
             }
             紧接着俩次catch
             {
                   看情况要么返回要么retry
             }
         //相当于调用sendout函数发送a,每个线程自己去发送数据
         callback(a);
    }
}
class MultiThreadDistributo(n)
{
      //构造函数
      MultiThreadDistributo(int n) 
      {
          //获取queue的大小
          d_num_threads=n;
          d_overloadQueueLength=::arg().asNum("overload-queue-length");
          d_maxQueueLength=::arg().asNum("max-queue-length");

          //创建管道
          for(int i=0; i < n; ++i) {
              int fds[2];
              if(pipe(fds) < 0)
                 unixDie("Creating pipe");
              d_pipes.push_back({fds[0],fds[1]});
          }

          //创建线程,
          for(int i=0;i<n;i++) {
              pthread_create(&tid,0,&makeThread,static_cast<void *>(this));
              Utility::usleep(50000); // we've overloaded mysql in the past :-)
          }
       }

        //
        makeThread()
        {
            MultiThreadDistributor *us=static_cast<MultiThreadDistributor *>(p);
            int ournum=us->d_running++;
            //相当于每一个分发线程实例化一个packethandler出来
            Backend *b=new Backend();
            int queuetimeout=::arg().asNum("queue-limit");
            //这里for死循环,分发问题给后端,同时
            for(;;)
            {
                //它的内部封装的是一个question和一个callback,从 pipe的一个端口读放到QD中
                QuestionData* QD;
                if(read(us->d_pipes[ournum].first, &QD, sizeof(QD)) != sizeof(QD))
                
                //如果包太多的时候对于这种超时很多的包就不处理了
                if(queuetimeout && QD->Q->d_dt.udiff()>queuetimeout*1000)
                {
                    delete;
                    continue
                }
                --us->d_queued;
                Answer *a;
                //下面这个是和后端交互的唯一的地方,这里有一个retry,如果失败了就会反复的去请求
                retry:
                  try 
                  {
                        if (!b) {
                              allowRetry=false;
                              b=new Backend();
                        }
                        //去调用b的question的方法,
                        a=b->question(QD->Q);
                        delete QD->Q;
                  }
                  catch(const PDNSException &e) 
                  {
                       delete b;
                       b=NULL;
                       if (!allowRetry) {
                           L<<Logger::Error<<"Backend error: "<<e.reason<<endl;
                           //如果失败了而且不许重试,那就直接返回错误了
                           a=QD->Q->replyPacket();

                           a->setRcode(RCode::ServFail);
                           S.inc("servfail-packets");
                           S.ringAccount("servfail-queries",QD->Q->qdomain.toLogString());

                           delete QD->Q;
                       }
                       else 
                       {//这里允许重试,就是再问一次后端
                           L<<Logger::Notice<<"Backend error (retry once): "<<e.reason<<endl;
                           goto retry;
                       }
                  }  
                  // 调用callback,就是那个sendout
                  QD->callback(a);  
            }
        }
        //多线程的question函数
        question()
        {
            q=new Question(*q);
            //该结构里封装了question和sendout函数
            auto QD=new QuestionData();
            QD->Q=q;
            QD->callback=callback;
            auto ret = QD->id = nextid++
            //这里把问题通过pipe发给那些线程
            write(d_pipes[QD->id % d_pipes.size()].second, &QD, sizeof(QD)) != sizeof(QD)
            d_queued++
            if(d_queued > d_maxQueueLength)
            {
                throw DistributorFatal();
            }
        }
}
// 解析从load-modules参数出来的module
loadModules()
{
    vector<string>modules;
    stringtok(modules,::arg()["load-modules"],", ");
    for(vector<string>::const_iterator i=modules.begin();i!=modules.end();++i)
    {
        const string &module=*I;
        //进行模块的加载
        res=UeberBackend::loadmodule(module);
    }
}
int receive(DNSPacket *prefilled)
{
    vector<struct pollfd> rfds= d_rfds;
    for(auto &pfd :  rfds) {
        pfd.events = POLLIN;
        pfd.revents = 0;
    }
    //这里遍历所有的rfds,反复调用直到本次数据全部接收完整
    err = poll(&rfds[0], rfds.size(), -1);
    //调用recvmsg接收数据
    for(auto &pfd :  rfds)
    {
        if(pfd.revents & POLLIN)
        {
          sock=pfd.fd;
          if((len=recvmsg(sock, &msgh, 0)) < 0 )
            Utility::sock_t sock=-1;
          //注意它这里处理完一个fd以后就break了,也就是说一个线程只处理一个fd,而不管是哪个fd
          break
        }
    }
    //设置socket的一些信息,本地和远端的地址信息
    DNSPacket *packet;
    packet->setSocket(sock);
    packet->setRemote(&remote);
    
    //parse包
    if(packet->parse(mesg, (size_t) len)<0)
    {
        //如果支持edns协议的话就获取相关的信息
        //复制要查询的域名
        //填充dns的一些报文,比如qtype,qclass
    }
    最终如果解析正确的话就把这个packet返回去
}
//接收来自Nameserver的questions,同时hands them to the Distributor for further processing
qthread()
{
        DNSPacket *P;
        //分发器,那个数字是分发线程的个数
        //注意有这么一个typedef,所以DNSDistributor中的bankend就是PacketHandler,那对于别的分发器可能会有别的bankend
        typedef Distributor<DNSPacket,DNSPacket,PacketHandler> DNSDistributor;
        DNSDistributor *distributor = DNSDistributor::Create(::arg().asNum("distributor-threads", 1)); // the big dispatcher!,这里会依据单线程还是多线程会去创造不同的distributor实例,如果是单线程的话只是开了一个后端实例,如果是多线程的话,则直接开了n个线程,每个线程开一个Backend后端实例,

        //注意这里是有多少个receive线程,就有多少个distributor实例
        g_distributors[num] = distributor;
        //声明dnspacket实例
        DNSPacket question(true);
        DNSPacket cached(false);

        //读取udp配置参数
        //声明一个server
        shared_ptr<UDPNameserver> NS;
        //从g_udpReceivers获取到对应的udpserver
        if( number != NULL && N->canReusePort() )
        {
            NS = g_udpReceivers[num];
        }
        else
        {
            NS = N;//N就是创建的唯一的那个监听地址的socket,如果设置了SO_REUSEPORT那就创建多个线程
        }
        //该线程的作用就是循环接收包
        for(;;) {
            if(!(P=NS->receive(&question))) { // receive a packet         inline
            //这里调用receive函数
                 continue;                    // packet was broken, try again
        // 依据返回的p开始统计信息
        numreceived++;
        //中间会有一个cache的判断,如果命中了则直接返回
        
        //如果后端负载过重,那么直接丢弃
        //if(distributor->isOverloaded())
        {
            overloadDrops++;
              continue;    
        }
        //否则的话则把包丢给distributor去查询,注意单distributor和多的有不同的处理方法
        distributor->question(P, &sendout);
        //sendout是一个函数
        void sendout(DNSPacket* a)
        {
          if(!a)
            return;
            //就是上面的那个N,第一个udpserver来发送数据
          N->send(a);

          int diff=a->d_dt.udiff();
          avg_latency=(int)(0.999*avg_latency+0.001*diff);
          delete a;
        }
    }
}

AuthPacketCache PC;//!< This is the main PacketCache, shared across all threads
AuthQueryCache QC;
mainthread()
{
    //设置一些用户的属性,dnspacket的属性
    //设置DNSPacket的一些属性,比如dnspacket的包的大小
    //设置cache的一些属性
    PC.setTTL(::arg().asNum("cache-ttl"));
    PC.setMaxEntries(::arg().asNum("max-packet-cache-entries"));
    QC.setMaxEntries(::arg().asNum("max-cache-entries"));
    //设置webserver的一些属性
    AuthWebServer webserver;
    //设置解析器的属性
    
    DynListener *dl;
    //创建线程,
    dl->go();
    pthread_t qtid;
    //启动webserver
    if(::arg().mustDo("webserver") || ::arg().mustDo("api"))
        webserver.go();
    if(::arg().mustDo("slave") || ::arg().mustDo("master") || !::arg()["forward-notify"].empty())
        Communicator.go();

    //依据receiver-threads创建线程,这几个线程就负责接收数据包
    unsigned int max_rthreads= ::arg().asNum("receiver-threads", 1);
    g_distributors.resize(max_rthreads);
    for(unsigned int n=0; n < max_rthreads; ++n)
        pthread_create(&qtid,0,qthread, reinterpret_cast<void *>(n)); // receives packets

    //主线程
    for(;;) {
        sleep(1800);
        try {
          doSecPoll(false);
        }
        catch(...){}
  }
}

在main函数中
main():
//读取配置文件,设置日志等相关的信息

//通过这个配置可以在一个单机上加载不同的pdns实例,也就是不同的pdns指定不同的配置文件
if(::arg()["config-name"]!="")
      s_programname+="-"+::arg()["config-name"];
//保护模式下创建某个实例

// 这个函数是加载各个指定的模块,解析从load-modules参数出来的module
loadModules()
//选择某个后端模块启动,这里选择某个后端登陆的时候,保证只选择一个,就是加载动态库文件而已
BackendMakers().launch(::arg()["launch"])
//监听控制台socket

//唤醒所有沉睡在某个条件变量上的线程
UeberBackend::go();
//声明一个UDPNameserver,注意在这里第一次创建的时候,d_additional_socket是false
N=std::make_shared<UDPNameserver>()
g_udpReceivers.push_back(N);
// 获取该指令配置的线程的个数,创建多个UDPNameserver,即内部创建多个socket监听同一个地址+端口
//这里还没有创建线程来着
size_t rthreads = ::arg().asNum("receiver-threads", 1);
for (size_t idx = 1; idx < rthreads; idx++) {
        try {
              // 为实例申请空间
              g_udpReceivers[idx] = std::make_shared<UDPNameserver>(true);
        }
        catch(const PDNSException& e) {
          L<<Logger::Error<<"Unable to reuse port, falling back to original bind"<<endl;
          break;
        }
}
//如果没有禁用tcp的话,那就创建 tcp nameserver,注意对于该监听socket来说没有对socket设置SO_REUSEPORT属性,也就是不可以有多个线程监听同一个地址
if(!::arg().mustDo("disable-tcp"))
      TN=new TCPNameserver;
    
}
mainthread()

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值