서버 개발 시리즈 2

9400 단어
title: 서버 개발 시리즈 2 date: 2017-9-13 11:27:46
3주 동안의 미친 야근을 거친 후에 서버 개발 리듬을 마침내 놓을 수 있게 되었고, 짬이 나면 외적인 각도로 이번 프로젝트를 잘 볼 수 있게 되었다.

swoole 누드 쓰기 tcp 서버 사용하기


'스위스 군도'(익숙한 틀) 가 없는 상황에서 나체 swoole는 다음과 같이 변했다.
  • swoole tcp 서버 기본 골격
  • 
    require_once __DIR__ . '/../vendor/autoload.php'; // composer autoload
    require_once __DIR__ . '/config.php'; //  
    
    //---------------------server
    $serv = new swoole_server("0.0.0.0", 9999);
    $serv->set([
        'worker_num'            => 4,
        'task_worker_num'       => 8,
    //    'daemonize'             => true,
        'pid'                   => __DIR__ . '/server.pid',
        'log_file'              => __DIR__ . '/../log/swoole.log',
    
        //  
        'open_length_check'     => 1,       //  
        'package_length_type'   => 'N',     //  
        'package_length_offset' => 4,       //  N 
        'package_body_offset'   => 8,       //  N 
        'package_max_length'    => 2000000, //  
    ]);
    
    // swoole table  ,  
    //$swooleTable = new swoole_table('100000'); //   10w  
    //$swooleTable->column('auth', swoole_table::TYPE_INT, '1');
    //$swooleTable->create();
    //$serv->table = $swooleTable;
    
    $serv->zoneId = $config['zone_id']; //  
    $serv->userinfo = []; //  
    
    //   onWorkerStart   redis/mysql  
    $serv->on('workerStart', 'onWorkerStart');
    
    // mysql  
    $serv->on('task', 'onTask');
    $serv->on('finish', 'onFinish');
    
    $serv->on('connect', 'onConnect');
    $serv->on('receive', 'onReceive'); //  
    $serv->on('close', 'onClose');
    
    $serv->start();
    
  • 메시지 처리
  • 여기에는 논리를 분리하기 위해 많은 function 가 사용되었다
    function onReceive(swoole_server $serv, $fd, $from_id, $data)
    {
        //  decode()  ,   autoload psr-4  
        $data = decode($data);
        //  
        if ($data['msg_type'] == 0) {
            function_msg0($serv, $fd, $data);
        } else if ($data['msg_type'] == 1) {
            function_msg1($serv, $fd, $data);
        }
    }
    
  • onWorkerStart 콜백에서 redis/mysql 연결을 만들어야 합니다
  • 이 wiki를 참고할 수 있습니다: 1개의 redis나 mysql 연결을 사용할 수 있는지 여부입니다. 다음 프로세스가 시작될 때 2개의 redis 연결을 초기화합니다. 하나는 worker, 하나는 cache
    function onWorkerStart(swoole_server $serv, $id){
        //   worker  
        if ($id < $serv->setting['worker_num']) {
            // cache
            $cache = new \Redis();
            $cache->connect($config['cache']['host'], $config['cache']['port'], $config['cache']['timeout']);
            $cache->auth($config['cache']['auth']);
            $serv->cache = $cache;
    
            // pub
            $pub = new \Redis();
            $pub->connect($config['pub_sub']['host'], $config['pub_sub']['port'], $config['pub_sub']['timeout']);
            $pub->auth($config['pub_sub']['auth']);
            $serv->pub = $pub;
        }
    }
    
    //  
    $serv->cache->set('key1', 'value1');
    $serv->pub->publish('topic1', 'data1');
    
  • mysql 연결 탱크
  • 비록 제목은 연결 탱크라고 하지만, 여기는 사실 mysql 연결 대상만 실례화했을 뿐, 원리는 유사하다
    function onTask($serv, $task_id, $from_id, $data) {
        static $link = null;
        if ($link == null) {
            $link = mysqli_connect($config['mysql']['host'], $config['mysql']['user'], $config['mysql']['password'], $config['mysql']['database']);
            if (!$link) {
                $link = null;
                return;
            }
        }
        //  ,   sql  ,  
        list($queryType, $sql) = explode('|', $data); 
        $result = $link->query($sql);
        if ($result) {
            if ($queryType == 'select') {
                $result = $result->fetch_all(MYSQLI_ASSOC);
            } else if ($queryType == 'insert') {
                $result = mysqli_insert_id($link);
            }
            return $result;
        }
    }
    
    function onFinish($serv, $data)
    {
        //
    }
    
    //  
    $res = $serv->taskWait('select|select name from user where id=xxx'); //  
    

    구독 시 밟힌 구덩이 사용하기


    swoole에서 구독을 실현하려면 이 블로그를 참고하십시오. Redis에서 구독 메시지를 WebSocket 클라이언트로 전달하는 방법
    onWorkerStart 콜백 함수에서 시작해야 함pub/sub
    function onWorkerStart(swoole_server $serv, $id){
    //        if ($id == 0) { //   sub
                $sub = new swoole_redis(); // swoole_redis  
                $sub->on('message', function (swoole_redis $redis, $result) use ($serv, $config) {
                    if ($result[0] == 'message') {
                        list($userId, $status) = explode(':', $result[2]);
                        //   fd,   client  
                        $userFd = $serv->userinfo[$userId]['fd'] ?? 0;
                        if ($userId && $userFd && in_array($status, [0, 1, 2])) {
                            foreach ($serv->connections as $fd) {
                                if ($userFd == $fd) { //  
    
                                    //  
    
                                    //   client  
                                    $serv->send($fd, encode('foo'));
                                    break;
                                }
                            }
                        }
                    }
                });
                $sub->connect($config['pub_sub']['host'], $config['pub_sub']['port'], function (swoole_redis $redis, $result) use ($config) {
                    $redis->auth($config['pub_sub']['auth'], function (swoole_redis $redis, $result) use ($config) {
                        $redis->subscribe('game_result_'. $config['zone_id']);
                    });
                });
    //        }
        }
    }
    

    코드를 자세히 보면'하나의sub만 시작합니다'라는 주석이 있습니다. 이것은 업무에 의해 결정됩니다. 구독 메시지를 받을 때 특정한 사용자에게만 전달할 수 있습니다.그러나 onWorkerStart 콜백 함수에서 시작할 수 없습니다.왜냐하면 우리는 먼저 swoole의 프로세스 모델을 이해해야 한다.
  • 서버가 시작되면 redis sub 프로세스가 먼저 시작됩니다
  • master프로세스 시작master프로세스 및 manager라인
  • reactor 라인, tcp 연결과 tcp 데이터의 송수신을 관리하는 데 사용
  • reactor프로세스는 manager프로세스와 worker프로세스를 관리하는 데 사용되며, 위의 task_worker구성에 따라
  • worker_num/task_worker_num프로세스처리worker라인에서 전송된 데이터, 업무 논리를 처리한 후 reactor라인에 전송, reactor라인에서 사용자
  • 에게 전송
  • reactor프로세스는 소모된 작업을 worker프로세스에 전송하고, task_worker프로세스가 처리된 후 촉발task_worker이벤트 리셋
  • 그래서 우리는 사용onFinish에 근거하여 우리가 현재 $work_id < $serv->setting['worker_num'] 프로세스인지 worker 프로세스인지 판단할 수 있다
    첫 번째 버전을 쓸 때 저는 업무 수요에 따라 task_worker 프로세스에서만 redis sub를 열 수 있도록 제한했습니다. 그러나 문제는 바로 왔습니다. 현재 사용자의 fd가 반드시 $work_id = 0 프로세스에 있는 것은 아닙니다. 그러면 다음 코드가 효력을 잃게 됩니다.
    foreach ($serv->connections as $fd) { // $serv   worker  
        if ($userFd == $fd) {
            // do something
        }
    }
    

    그러나 $work_id = 0의 제한을 가하지 않으면 우리가worker 프로세스를 얼마나 열었는지,sub가 얼마나 많은지, 메시지의 중복 구독, 중복 업무 논리 처리를 초래할 수 있다.
    이럴 때 swoole가 제공하는 Process 프로세스 관리 모듈을 알아야 합니다. 저희는 단독으로 하나의 프로세스를 만들어서sub를 유지하면 됩니다.

    swoole를 사용하여 서버에서 발견한 문제를 나체로 쓰기.


    분명히 위의 업무 논리가 복잡하지 않고 사용하는 서비스도 많지 않지만 전체적으로 개발된 불편함은 매우 뚜렷하다.
  • 개발 환경과 테스트 환경의 구축: swoole 컴파일,redis/mysql 설치
  • 프로필 관리: 빠른 개발 시 업무에 쓰고, 최적화 시 뽑기$work_id = 0 프로필
  • 서비스 배치: 공식적인wiki리 시스템ddaemonize 방안을 시도하기 시작했는데 그 결과 대량의 좀비 프로세스가 발생했다
  • 연결 탱크: 더 높은 성능이 필요하면 연결 탱크가 필요합니다. redis든 mysql
  • 이든
  • 프로토콜 처리: 우리는 config.php의 사용자 정의 프로토콜을 사용하여 프로토콜과 업무를 분리하는 것이 좋은 디자인이다
  • 학습 비용: 공식적인wiki는 첫 번째 읽으면 대체적인 이미지가 생기고 두 번째 읽으면서wiki의 예를 실현하며 세 번째 업무 수요에 따라wiki의 해당 부분을 자세히 읽는다.그러나wiki는 1400페이지에 가까워도 새로운 문제가 생길 것이다.

  • 비교해 보면 php의 웹 프레임워크가 이렇게 많은데 MVC가 크게 유행하고 있는데 php의 서버 프레임워크도 있어서 위의 공통된 문제를 해결할 수 있습니까?
    여기서 swoole distribution을 추천합니다. 재구성할 때 이 프레임워크를 선택했습니다. 장점을 간단하게 말씀드리겠습니다.
  • docker 설정 개발 환경,docker for window는 디렉터리 마운트를 통해 열 업데이트가 불가능합니다(물론 해결 방안도 있습니다)
  • 다른 서비스(systemd supervisor)에 의존하지 않고 서비스화 배치
  • Pack 모듈 솔루션 프로토콜 분석
  • 고전적인 MVC 구조, 루트만 조금만 수정하면 controller와 모델에서 업무 논리를 쓸 수 있다
  • 자체 연결 풀, 구성 파일 수정
  • 맞아요. 그리고 협정
  • $value = yield $this->redis_pool->getCoroutine()->get('key1');
    
  • 맞아요. 그리고 Process
  • namespace app\Process;
    
    use Server\Components\Process\Process;
    
    class MyProcess extends Process
    {
        public function start($process)
        {
            parent::start($process);
    
            //   redis sub  
        }
    
        //   controller   rpc  
        public function getData()
        {
            return '123';
        }
    }
    

    마지막에 쓰다


    확실히 이전에 서버를 쓴 적이 없어서'탁상공론'의 단계에 머물렀는데, 진정으로 썼을 때'이 일은 정말 힘들다'는 것을 발견했다.그러나 그 몇 년 동안 당신이 읽은 책, 브러시 기술 블로그, 참가한 기술 대회는 어쨌든 유용합니다. 입문 장소에 가로막힌 것은 언어가 아니라 이 분야의'기초'입니다. 이런 것들을 통해 얻을 수 있습니다. 부족한 것은 스스로 그것을 체계화해야 합니다.물론 그 다음은 코딩 + protobuf이 일선 프로그래머들에게 계속 유용할 것이다.

    좋은 웹페이지 즐겨찾기