ソケット、redis、壊れた卵について少し

4月1日の昼食後、金曜日に働きたくはありません。突然、技術者がなんらかの冗談を言うでしょう。 したがって、私は何かについて書くことにしました。

Unixソケット、mysql、php、およびredisの1つの記事で、habrのオープンスペースについては、一度に無差別に叫びました。 1つの記事ですべてについて説明するのではなく、ソケットについて少し説明し、redisについて少し説明します。

質問は次のとおりです。UnixまたはTCPソケットよりも高速なのは何ですか?

ただし、気にする価値のない質問は常に議論されており、同じ記事のアンケートに回答しなかった場合は回答しません。回答者のほぼ半数は、TCPソケットを使用する方が優れている/信頼できる/安定していると考えています。

AF_UNIXをすでに選択している人は、これ以上読むことはできません。



理論からの簡単な絞り込みから始めましょう。

ソケットは、ローカルまたはネットワークで使用するクライアント/サーバーシステムの開発を可能にするプロセス間通信インターフェイスの1つです。 UNIXソケットを(一方で)比較することを検討しているので、同じマシン内でIPCについて話し続けます。

名前付きパイプとは異なり、ソケットを使用する場合、クライアントとサーバーの違いがトレースされます。 ソケットメカニズムを使用すると、多くのクライアントが接続するサーバーを作成できます。



サーバー側の相互作用の実装方法:

- ソケットシステムコールはソケットを作成しますが、このソケットは他のプロセスと共有できません。

-ソケットが呼び出されます。 ローカルAF_UNIXドメインソケット( AF_LOCAL )の場合、アドレスはファイル名で指定されます。 ネットワークソケットAF_INETは、IP /ポートに従って名前が付けられます。

-システムコールリッスン(intソケット、intバックログ)は着信接続をキューに入れます。 2番目のバックログパラメーターは、このキューの長さを決定します。

-サーバーは、 accept呼び出しを使用してこれらの接続を受け入れます。これにより、名前付きソケットとは異なる新しいソケットが作成されます。 この新しいソケットは、この特定のクライアントと対話するためにのみ使用されます。



クライアントの観点から見ると、接続はやや単純です。

- ソケットが呼び出されます。

-そして、名前付きサーバーソケットをアドレスとして使用して接続します。



2番目のパラメーターがこのソケットで使用されるデータ交換のタイプを決定するintソケット(intドメイン、int型、intプロトコル)の呼び出しを詳しく見てみましょう。 比較では、可能な値SOCK_STREAMを考慮します。これは、信頼性のある、順序付けられた、双方向のバイトストリームです。 つまり、次の形式のソケット

sockfd = socket(AF_UNIX、SOCK_STREAM、0);

そして

sockfd = socket(AF_INET、SOCK_STREAM、0);



AF_UNIXドメインのソケット構造は単純です。

struct sockaddr_un { unsigned char sun_len; /* sockaddr len including null */ sa_family_t sun_family; /* AF_UNIX */ char sun_path[104]; /* path name (gag) */ };
      
      





AF_INETドメインはもう少し複雑です。

 struct sockaddr_in { uint8_t sin_len; sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; char sin_zero[8]; };
      
      





完了に追加費用が発生します。 特に、解決のコスト( gethostbyname )および/またはどちらの側で卵を壊すかを見つけるコスト( htons )である可能性があります。



また、 AF_INETドメインのソケットは、localhostにアクセスしているにもかかわらず、ローカルシステムで動作することを知りません。 したがって、生産性を向上させるために、ネットワークスタックのメカニズムをバイパスすることはありません。 このようにして、コンテキストスイッチング、ACK、TCPフロー制御、ルーティング、大規模なパケット分割などに対して「支払い」ます。 つまり、ローカルインターフェイスで作業しているという事実にもかかわらず、「本格的なTCP作業」です。



AF_UNIXソケットは、同じシステム内で動作することを「認識」します。 IPヘッダーの設定、ルーティング、チェックサム計算などの労力を回避します。 さらに、 AF_UNIXドメインはファイルシステムをアドレススペースとして使用するため、ファイルのアクセス許可を使用してそれらへのアクセスを制御するという形でボーナスが得られます。 したがって、プロセスへのソケットへのアクセスを大きな労力なしで制限できます。また、セキュリティ保護段階のコストは負担しません。



実際に理論を確認しましょう。

サーバー側を書くのが面倒なので、同じredis-serverを使用します。 その機能はこれに優れており、同時にそれに対する告発が公正であったかどうかを確認します。 クライアントパーツは独自にスケッチします。 複雑度O(1)の最も単純なINCRコマンドを実行します。

ソケットの作成は、意図的にループ内に配置されます。

TCPクライアント:

AF_INET
 #include <stdio.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #include <string.h> int main(int argc, char *argv[]) { int sockfd, portno, n; struct sockaddr_in serv_addr; struct hostent *server; char buffer[256]; if (argc < 4) { fprintf(stderr,"usage %s hostname port count_req\n", argv[0]); exit(0); } portno = atoi(argv[2]); int i=0; int ci = atoi(argv[3]); for(i; i < ci; i++) { sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("ERROR opening socket"); exit(1); } server = gethostbyname(argv[1]); if (server == NULL) { fprintf(stderr,"ERROR, no such host\n"); exit(0); } bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length); serv_addr.sin_port = htons(portno); if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { perror("ERROR connecting"); exit(1); } char str[] = "*2\r\n$4\r\nincr\r\n$3\r\nfoo\r\n"; int len = sizeof(str); bzero(buffer, len); memcpy ( buffer, str, len ); n = write(sockfd, buffer, strlen(buffer)); if (n < 0) { perror("ERROR writing to socket"); exit(1); } bzero(buffer,256); n = read(sockfd, buffer, 255); if (n < 0) { perror("ERROR reading from socket"); exit(1); } printf("%s\n",buffer); close(sockfd); } return 0; }
      
      







UNIXクライアント:

AF_UNIX
 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <string.h> int main(int argc, char *argv[]) { int sockfd, portno, n; struct sockaddr_un serv_addr; struct hostent *server; char buffer[256]; if (argc < 1) { fprintf(stderr,"usage %s count_req\n", argv[0]); exit(0); } int i=0; int ci = atoi(argv[1]); for(i; i < ci; i++) { sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (sockfd < 0) { perror("ERROR opening socket"); exit(1); } bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, "/tmp/redis.sock"); if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { perror("ERROR connecting"); exit(1); } char str[] = "*2\r\n$4\r\nincr\r\n$3\r\nfoo\r\n"; int len = sizeof(str); bzero(buffer, len); memcpy ( buffer, str, len ); n = write(sockfd, buffer, strlen(buffer)); if (n < 0) { perror("ERROR writing to socket"); exit(1); } bzero(buffer,256); n = read(sockfd, buffer, 255); if (n < 0) { perror("ERROR reading from socket"); exit(1); } printf("%s\n",buffer); close(sockfd); } return 0; }
      
      







1つのクライアントでのテスト:

 # redis-cli set foo 0 ; time ./redistcp 127.0.0.1 6379 1000000 > /dev/null ; redis-cli get foo OK 2.108u 21.991s 1:13.75 32.6% 9+158k 0+0io 0pf+0w "1000000" # redis-cli set foo 0 ; time ./redisunix 1000000 > /dev/null ; redis-cli get foo OK 0.688u 9.806s 0:36.90 28.4% 4+151k 0+0io 0pf+0w "1000000"
      
      







そして今、20の並列クライアントがそれぞれ500,000のリクエストを送信しています。

TCPの場合: 6:12.86

 # redis-cli info Commandstats cmdstat_set:calls=1,usec=5,usec_per_call=5.00 cmdstat_incr:calls=10000000,usec=24684314,usec_per_call=2.47
      
      





UNIXの場合: 4:11.23

 # redis-cli info Commandstats cmdstat_set:calls=1,usec=8,usec_per_call=8.00 cmdstat_incr:calls=10000000,usec=22258069,usec_per_call=2.23
      
      







したがって、全体として、TCPソケットを支持する議論は、アプリケーションのモビリティと単純なスケーリングの可能性にすぎません。 ただし、同じマシン内で作業する必要がある場合は、UNIXソケットが優先されます。 したがって、TCPソケットとUNIXソケットの間の選択は、まず第一に、移植性とパフォーマンスの間の選択です。



これについては、Unixソケットを愛することを提案し、鈍い終わりの質問はリリプートとブレフスクの住民に任せます。



All Articles