📜 ⬆️ ⬇️

A little bit about sockets, redis and broken eggs

I don’t want to work on Friday after the first of April lunch - all of a sudden the technician will throw out some joke. Therefore I decided to write about something.
Not so long ago, in the open spaces of Habr in one article, Unix-sockets, mysql, php and redis immediately deflated. We will not talk about everything in one article; we’ll dwell on sockets and a little on redis.
So the question is: which is faster than Unix or TCP sockets?
A question that is not worth a damn, however, it is constantly mutable and would not write if not the survey in the article itself, according to which almost half of the respondents believe that it is better / more reliable / more stable to use TCP sockets.
Those who already chooses AF_UNIX, you can not continue to read.

Let's start with a brief extract of the theory.
A socket is one of the interfaces for interprocess communication, which allows developing client-server systems for local or network use. Since we are considering comparing (on the one hand) Unix sockets, in the following we will talk about IPC within one machine.
Unlike named pipes, when using sockets there is a difference between the client and the server. The socket mechanism allows you to create a server to which many clients connect.

How server interaction is implemented:
- the socket system call creates a socket, but this socket cannot be shared with other processes;
- the socket is called. For local sockets of the AF_UNIX domain ( AF_LOCAL ), the address will be specified by the file name. Network sockets AF_INET are named according to their ip / port;
- the system call listen (int socket, int backlog) forms a queue of incoming connections. The second parameter backlog determines the length of this queue;
- the server accepts these connections by calling accept , which creates a new socket that is different from the named socket. This new socket is used only to interact with this particular client.

From the client’s point of view, the connection is somewhat simpler:
- the socket is called;
- and connect , using the server's named socket as the address.
')
Let's take a closer look at the int socket (int domain, int type, int protocol) call , the second parameter of which determines the type of data exchange used with this socket. In our comparison, we will consider its possible value SOCK_STREAM , which is a reliable, ordered bi-directional byte stream. That is, the view sockets are involved in the review.
sockfd = socket (AF_UNIX, SOCK_STREAM, 0);
and
sockfd = socket (AF_INET, SOCK_STREAM, 0);

The socket structure in the AF_UNIX domain is simple:
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) */ }; 

In the AF_INET domain is somewhat more complicated:
 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]; }; 

and we will incur additional costs for its completion. In particular, this may be the cost of resolving ( gethostbyname ) and / or figuring out which side to break eggs ( htons ).

Also, the sockets in the AF_INET domain, despite being accessed by localhost, “do not know” that they are running on the local system. Thus, they do not make any effort to bypass the mechanisms of the network stack to increase performance. Thus, we “pay” for context switching, ACK, TCP flow control, routing, splitting large packets, etc. That is, this is a “full TCP job” despite the fact that we are working on the local interface.

In turn, the AF_UNIX sockets "realize" that they work within the same system. They avoid efforts to install ip-headers, routing, calculation of checksums, etc. In addition, once in the AF_UNIX domain, the file system is used as the address space, we get a bonus in the form of the ability to use file access rights and control access to them. Thus, we can, without significant efforts, restrict access to sockets to processes and, again, do not incur costs for the security security stages.

Let's test the theory in practice.
I'm too lazy to write the server part, so I will use the same redis-server. Its functionality is great for this and at the same time we will check whether the charges against him were fair. Client parts we outline our own. We will execute the simplest INCR command with O (1) complexity.
Creating sockets is intentionally placed inside loops.
TCP client:
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 client:
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; } 


We test with one client:
 # 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" 


And now for twenty parallel clients sending 500,000 requests each.
for 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 

for 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 


Thus, in general, only mobility of the application and the possibility of simple scaling can serve as arguments in favor of TCP sockets. But if you need to work within the same machine, then the choice, of course, in favor of UNIX-sockets. Therefore, the choice between TCP and UNIX sockets is, first of all, the choice between portability and performance.

On this, I propose to love Unix sockets, and leave the questions of dullness to the residents of Liliput and Blefusku.

Source: https://habr.com/ru/post/280668/


All Articles