├── .travis.yml ├── ChangeLog ├── LICENSE ├── Makefile.am ├── NOTICE ├── README.md ├── conf ├── nutcracker.leaf.yml ├── nutcracker.root.yml └── nutcracker.yml ├── configure.ac ├── contrib ├── Makefile.am ├── yaml-0.1.4.tar.gz └── yaml-0.1.4 │ └── .gitignore ├── m4 └── .gitignore ├── man └── nutcracker.8 ├── notes ├── c-styleguide.txt ├── debug.txt ├── kqueue.pdf ├── memcache.md ├── recommendation.md ├── redis.md └── socket.txt ├── scripts ├── benchmark-mget.py ├── makesrpm.sh ├── multi_get.sh ├── nutcracker.init ├── nutcracker.init.debian ├── nutcracker.spec ├── pipelined_read.sh ├── pipelined_write.sh ├── populate_memcached.sh ├── redis-check.py └── redis-check.sh ├── src ├── Makefile.am ├── event │ ├── Makefile.am │ ├── nc_epoll.c │ ├── nc_event.h │ ├── nc_evport.c │ └── nc_kqueue.c ├── hashkit │ ├── Makefile.am │ ├── nc_crc16.c │ ├── nc_crc32.c │ ├── nc_fnv.c │ ├── nc_hashkit.h │ ├── nc_hsieh.c │ ├── nc_jenkins.c │ ├── nc_ketama.c │ ├── nc_md5.c │ ├── nc_modula.c │ ├── nc_murmur.c │ ├── nc_one_at_a_time.c │ └── nc_random.c ├── nc.c ├── nc_array.c ├── nc_array.h ├── nc_bilibili.c ├── nc_channel.c ├── nc_channel.h ├── nc_client.c ├── nc_client.h ├── nc_conf.c ├── nc_conf.h ├── nc_connection.c ├── nc_connection.h ├── nc_core.c ├── nc_core.h ├── nc_log.c ├── nc_log.h ├── nc_mbuf.c ├── nc_mbuf.h ├── nc_message.c ├── nc_message.h ├── nc_process.c ├── nc_process.h ├── nc_proxy.c ├── nc_proxy.h ├── nc_queue.h ├── nc_rbtree.c ├── nc_rbtree.h ├── nc_request.c ├── nc_response.c ├── nc_server.c ├── nc_server.h ├── nc_shmtx.c ├── nc_shmtx.h ├── nc_signal.c ├── nc_signal.h ├── nc_stats.c ├── nc_stats.h ├── nc_string.c ├── nc_string.h ├── nc_util.c ├── nc_util.h ├── proto │ ├── Makefile.am │ ├── nc_memcache.c │ ├── nc_proto.h │ └── nc_redis.c └── stats.py ├── tests ├── .gitignore ├── README.rst ├── conf │ ├── conf.py │ ├── control.sh │ └── redis.conf ├── lib │ ├── server_modules.py │ └── utils.py ├── log │ └── .gitignore ├── test.py ├── test_memcache │ ├── __init__.py │ └── test_gets.py ├── test_redis │ ├── __init__.py │ ├── common.py │ ├── test_auth.py │ ├── test_basic.py │ ├── test_commands.py │ ├── test_mget_large_binary.py │ ├── test_mget_mset.py │ ├── test_pipeline.py │ └── test_protocol.py └── test_system │ ├── __init__.py │ └── test_reload.py └── travis.sh /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | script: bash ./travis.sh 3 | 4 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2015-22-06 Manju Rajashekhar 2 | * twemproxy: version 0.4.1 release 3 | backend server hostnames are resolved lazily 4 | redis_auth is only valid for a redis pool 5 | getaddrinfo returns non-zero +ve value on error 6 | fix-hang-when-command-only (charsyam) 7 | fix bug crash when get command without key and whitespace (charsyam) 8 | mark server as failed on protocol level transiet failures like -OOM, -LOADING, etc 9 | implemented support for parsing fine grained redis error response 10 | remove redundant conditional judgement in rbtree deletion (leo ma) 11 | fix bug mset has invalid pair (charsyam) 12 | fix bug mset has invalid pair (charsyam) 13 | temp fix a core on kqueue (idning) 14 | support "touch" command for memcached (panmiaocai) 15 | fix redis parse rsp bug (charsyam) 16 | SORT command can take multiple arguments. So it should be part of redis_argn() and not redis_arg0() 17 | remove incorrect assert because client could send data after sending a quit request which must be discarded 18 | allow file permissions to be set for UNIX domain listening socket (ori liveneh) 19 | return error if formatted is greater than mbuf size by using nc_vsnprintf() in msg_prepend_format() 20 | fix req_make_reply on msg_get, mark it as response (idning) 21 | redis database select upon connect (arne claus) 22 | redis_auth (charsyam) 23 | allow null key(empty key) (idning) 24 | fix core on invalid mset like "mset a a a" (idning) 25 | 26 | 2014-18-10 idning 27 | * twemproxy: version 0.4.0 release 28 | mget improve (idning) 29 | many new commands supported: LEX, PFADD, PFMERGE, SORT, PING, QUIT, SCAN... (mattrobenolt, areina, idning) 30 | handle max open file limit(allenlz) 31 | add notice-log and use ms time in log(idning) 32 | fix bug in string_compare (andyqzb) 33 | fix deadlock in sighandler (idning) 34 | 35 | 2013-20-12 Manju Rajashekhar 36 | * twemproxy: version 0.3.0 release 37 | SRANDMEMBER support for the optional count argument (mkhq) 38 | Handle case where server responds while the request is still being sent (jdi-tagged) 39 | event ports (solaris/smartos) support 40 | add timestamp when the server was ejected 41 | support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat) 42 | kqueue (bsd) support (ferenyx) 43 | fix parsing redis response to accept integer reply (charsyam) 44 | 45 | 2013-23-04 Manju Rajashekhar 46 | * twemproxy: version 0.2.4 release 47 | redis keys must be less than mbuf_data_size() in length (fifsky) 48 | Adds support for DUMP/RESTORE commands in Redis (remotezygote) 49 | Use of the weight value in the modula distribution (mezzatto) 50 | Add support to unix socket connections to servers (mezzatto) 51 | only check for duplicate server name and not 'host:port:weight' when 'name' is configured 52 | crc16 hash support added (mezzatto) 53 | 54 | 2013-31-01 Manju Rajashekhar 55 | * twemproxy: version 0.2.3 release 56 | RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon) 57 | EVAL and EVALSHA support (ferenyx) 58 | exit 1 if configuration file is invalid (cofyc) 59 | return non-zero exit status when nutcracker cannot start for some reason 60 | use server names in stats (charsyam) 61 | Fix failure to resolve long FQDN name resolve (conmame) 62 | add support for hash tags 63 | 64 | 2012-18-10 Manju Rajashekhar 65 | * twemproxy: version 0.2.2 release 66 | fix the off-by-one error when calculating redis key length 67 | 68 | 2012-12-10 Manju Rajashekhar 69 | * twemproxy: version 0.2.1 release 70 | don't use buf in conf_add_server 71 | allow an optional instance name for consistent hashing (charsyam) 72 | add --stats-addr=S option 73 | add stats-bind-any -a option (charsyam) 74 | 75 | 2012-12-03 Manju Rajashekhar 76 | * twemproxy: version 0.2.0 release 77 | add -D or --describe-stats command-line argument to print stats description 78 | redis support in twemproxy 79 | setup pre/post splitcopy and pre/post coalesce handlers in msg struct 80 | memcache pre_splitcopy, post_splitcopy, pre_coalesce and post_coalesce handlers 81 | every fragment of a msg vector keeps track of the first/last fragment, number of fragments and fragment owner 82 | set up msg parser handler for memcache connections 83 | refactor parsing code and create header file nc_proto.h 84 | stats_listen should use st->addr as the listening address string 85 | delete stats tracking memcache requests and responses; stats module no longer tracks protocol related stats 86 | 87 | 2012-10-27 Manju Rajashekhar 88 | * twemproxy: version 0.1.20 release 89 | on msg_repair, msg->pos should point to nbuf->pos and not nbuf->last 90 | refactor memcache parsing code into proto directory 91 | add redis option to configuration file 92 | fix macro definition strXcmp error for big endian 93 | fix log_hexdump and loga_hexdump 94 | 95 | 2012-07-31 Manju Rajashekhar 96 | * twemproxy: version 0.1.19 release 97 | close server connection on a stray response (yashh, bmatheny) 98 | 99 | 2012-06-19 Manju Rajashekhar 100 | * twemproxy: version 0.1.18 release 101 | command line option to set mbuf chunk size 102 | 103 | 2012-05-09 Manju Rajashekhar 104 | * twemproxy: version 0.1.17 release 105 | use _exit(0) instead of exit(0) when daemonizing 106 | use loga instead of log_stderr in nc_stacktrace 107 | 108 | 2012-02-09 Manju Rajashekhar 109 | * twemproxy: version 0.1.16 release 110 | twemproxy (aka nutcracker) is a fast and lightweight proxy for memcached protocol. 111 | 112 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure config.h.in config.h.in~ stamp-h.in 2 | 3 | ACLOCAL_AMFLAGS = -I m4 4 | 5 | SUBDIRS = contrib src 6 | 7 | dist_man_MANS = man/nutcracker.8 8 | 9 | 10 | confdir = /etc/ 11 | conf_DATA = ./conf/nutcracker.yml 12 | 13 | 14 | EXTRA_DIST = README.md NOTICE LICENSE ChangeLog conf scripts notes 15 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | twemproxy is a fast and lightweight proxy for memcached protocol 2 | Copyright (C) 2012 Twitter, Inc. 3 | 4 | Portions of twemproxy were inspired from nginx: http://nginx.org/ 5 | 6 | The implementation of generic array (nc_array.[ch]) and red black tree 7 | (nc_rbtree.[ch]) also comes from nginx-0.8.55. 8 | 9 | /* 10 | * Copyright (C) 2002-2010 Igor Sysoev 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND 22 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 | * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 25 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 | * SUCH DAMAGE. 32 | */ 33 | 34 | The generic queue implementation comes from BSD 35 | 36 | /* 37 | * Copyright (c) 1991, 1993 38 | * The Regents of the University of California. All rights reserved. 39 | * 40 | * Redistribution and use in source and binary forms, with or without 41 | * modification, are permitted provided that the following conditions 42 | * are met: 43 | * 1. Redistributions of source code must retain the above copyright 44 | * notice, this list of conditions and the following disclaimer. 45 | * 2. Redistributions in binary form must reproduce the above copyright 46 | * notice, this list of conditions and the following disclaimer in the 47 | * documentation and/or other materials provided with the distribution. 48 | * 3. All advertising materials mentioning features or use of this software 49 | * must display the following acknowledgement: 50 | * This product includes software developed by the University of 51 | * California, Berkeley and its contributors. 52 | * 4. Neither the name of the University nor the names of its contributors 53 | * may be used to endorse or promote products derived from this software 54 | * without specific prior written permission. 55 | * 56 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 57 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 58 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 59 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 60 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 61 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 62 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 63 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 64 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 65 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 66 | * SUCH DAMAGE. 67 | */ 68 | 69 | The implementation of consistent hashing and individual hash algorithms were 70 | borrowed from libmemcached. 71 | 72 | Copyright (c) 2011, Data Differential (http://datadifferential.com/) 73 | Copyright (c) 2007-2010, TangentOrg (Brian Aker) 74 | All rights reserved. 75 | 76 | Redistribution and use in source and binary forms, with or without 77 | modification, are permitted provided that the following conditions are 78 | met: 79 | 80 | * Redistributions of source code must retain the above copyright 81 | notice, this list of conditions and the following disclaimer. 82 | 83 | * Redistributions in binary form must reproduce the above 84 | copyright notice, this list of conditions and the following disclaimer 85 | in the documentation and/or other materials provided with the 86 | distribution. 87 | 88 | * Neither the name of TangentOrg nor the names of its 89 | contributors may be used to endorse or promote products derived from 90 | this software without specific prior written permission. 91 | 92 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 93 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 94 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 95 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 96 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 97 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 98 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 99 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 100 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 101 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 102 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 103 | 104 | The source also includes libyaml (yaml-0.1.4) in contrib/ directory 105 | 106 | Copyright (c) 2006 Kirill Simonov 107 | 108 | Permission is hereby granted, free of charge, to any person obtaining a copy of 109 | this software and associated documentation files (the "Software"), to deal in 110 | the Software without restriction, including without limitation the rights to 111 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 112 | of the Software, and to permit persons to whom the Software is furnished to do 113 | so, subject to the following conditions: 114 | 115 | The above copyright notice and this permission notice shall be included in all 116 | copies or substantial portions of the Software. 117 | 118 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 119 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 120 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 121 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 122 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 123 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 124 | SOFTWARE. 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BILITW (bilibili twemproxy) 2 | 3 | **twemproxy** (pronounced "two-em-proxy"), aka **nutcracker** is a fast and lightweight proxy for [memcached](http://www.memcached.org/) and [redis](http://redis.io/) protocol. It was built primarily to reduce the number of connections to the caching servers on the backend. This, together with protocol pipelining and sharding enables you to horizontally scale your distributed caching architecture. 4 | 5 | **bilitw** (bilibili twemproxy), which introduce multi process of twemproxy(one master and mutli worker), is order to get full use of the CPU cores. 6 | 7 | ## Build 8 | 9 | To build bilitw from source with _debug logs enabled_ and _assertions enabled_: 10 | 11 | $ git clone 12 | $ cd bilitw 13 | $ autoreconf -fvi 14 | $ ./configure CFLAGS="-DGRACEFUL -g -O2" --enable-debug=full 15 | $ make 16 | $ make install 17 | $ nohup bilitw -o /var/log/bilitw.log -v 3 & 18 | 19 | A quick checklist: 20 | 21 | + Use newer version of gcc (older version of gcc has problems) 22 | + Use CFLAGS="-O1" ./configure && make 23 | + Use CFLAGS="-O3 -fno-strict-aliasing" ./configure && make 24 | + `autoreconf -fvi && ./configure` needs `automake` and `libtool` to be installed 25 | 26 | 27 | ## Configuration 28 | stats_duration indicates the interval of statistics printout in log file. 29 | default: 300000 milliseconds -> 5 minutes. 30 | 31 | reload_timeout indicates the timeout for graceful reload, if the timeout reached, old workers will close the client connection. 32 | default: 120000 milliseconds -> 2 minutes 33 | 34 | slow_req_duration is the timeout for slow request value 35 | slow request is the response of redis/memcached exceeds slow_req_duration timeout. 36 | default: not set by default 37 | 38 | **Default under /etc/nutcracker.yml** 39 | 40 | **Block "global" is for global setting of bilitw.** 41 | 42 | global: 43 | stats_duration: 600000 44 | reload_timeout: 60000 45 | slow_req_duration: 5000 46 | stats_file: /tmp/twstats.log 47 | 48 | alpha: 49 | listen: 127.0.0.1:22121 50 | slow_req_duration: 3000 51 | hash: fnv1a_64 52 | distribution: ketama 53 | auto_eject_hosts: true 54 | redis: true 55 | server_retry_timeout: 2000 56 | server_failure_limit: 1 57 | servers: 58 | - 127.0.0.1:6379:1 myserver 59 | - 127.0.0.1:6380:1 myserver1 60 | 61 | 62 | **Graceful Reload Configuration:** 63 | 64 | localhost:~:# ps -ef | grep bilitw 65 | root 19521 19227 0 19:22 pts/0 00:00:00 bilitw master 66 | root 19522 19521 0 19:22 pts/0 00:00:00 bilitw worker 0 67 | root 19523 19521 0 19:22 pts/0 00:00:00 bilitw worker 1 68 | kill -SIGHUP 19521 69 | 70 | ## Unit Test 71 | Dependency: 72 | yum install python 73 | yum install python-redis 74 | 75 | For Redis: 76 | ./tests/test.py 77 | 78 | ## Help 79 | $ bilitw -h 80 | Options: 81 | -h, --help : this help 82 | -V, --version : show version and exit 83 | -t, --test-conf : test configuration for syntax errors and exit 84 | -d, --daemonize : run as a daemon 85 | -D, --describe-stats : print stats description and exit 86 | -v, --verbose=N : set logging level (default: 5, min: 0, max: 11) 87 | -o, --output=S : set logging file (default: stderr) 88 | -c, --conf-file=S : set configuration file (default: /etc/nutcracker.yml) 89 | -s, --stats-port=N : set stats monitoring port (default: 22223) 90 | -a, --stats-addr=S : set stats monitoring ip (default: 0.0.0.0) 91 | -i, --stats-interval=N : set stats aggregation interval in msec (default: 30000 msec) 92 | -p, --pid-file=S : set pid file (default: off) 93 | -m, --mbuf-size=N : set size of mbuf chunk in bytes (default: 16384 bytes) 94 | -n, --worker-num=N : set number of workers (default: number of cpu cores) 95 | -M, --core-mask=N : set cpu core mask that worker process bind to 96 | 97 | bilitw -n 2 -M 12 98 | // 2 means launch 2 workers 99 | // 12 means mask bitmap 0x1100, which tell 2 workers to bind on cpu 2 and cpu 3. 100 | 101 | localhost:~:# ps -ef | grep bilitw 102 | root 19521 19227 0 19:22 pts/0 00:00:00 bilitw master 103 | root 19522 19521 0 19:22 pts/0 00:00:00 bilitw worker 0 104 | root 19523 19521 0 19:22 pts/0 00:00:00 bilitw worker 1 105 | 106 | [2015-12-21 19:20:15.247] nc_process.c:256 set worker 0 affinity to cpu core 2 107 | [2015-12-21 19:20:15.247] nc_process.c:256 set worker 1 affinity to cpu core 3 108 | 109 | ## Observability 110 | 111 | Observability in bilitw is through logs and stats. 112 | 113 | $ bilitw --describe-stats 114 | 115 | pool stats: 116 | client_eof "# eof on client connections" 117 | client_err "# errors on client connections" 118 | client_connections "# active client connections" 119 | server_ejects "# times backend server was ejected" 120 | forward_error "# times we encountered a forwarding error" 121 | fragments "# fragments created from a multi-vector request" 122 | 123 | server stats: 124 | server_eof "# eof on server connections" 125 | server_err "# errors on server connections" 126 | server_timedout "# timeouts on server connections" 127 | server_connections "# active server connections" 128 | requests "# requests" 129 | request_bytes "total request bytes" 130 | responses "# responses" 131 | response_bytes "total response bytes" 132 | in_queue "# requests in incoming queue" 133 | in_queue_bytes "current request bytes in incoming queue" 134 | out_queue "# requests in outgoing queue" 135 | out_queue_bytes "current request bytes in outgoing queue" 136 | 137 | Logging in bilitw is only available when bilitw is built with logging enabled. By default logs are written to stderr. bilitw can also be configured to write logs to a specific file through the -o or --output command-line argument. 138 | 139 | 140 | ## License 141 | Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 142 | -------------------------------------------------------------------------------- /conf/nutcracker.leaf.yml: -------------------------------------------------------------------------------- 1 | leaf: 2 | listen: 127.0.0.1:22121 3 | hash: fnv1a_64 4 | distribution: ketama 5 | auto_eject_hosts: true 6 | server_retry_timeout: 2000 7 | server_failure_limit: 1 8 | servers: 9 | - 127.0.0.1:11212:1 10 | - 127.0.0.1:11213:1 11 | -------------------------------------------------------------------------------- /conf/nutcracker.root.yml: -------------------------------------------------------------------------------- 1 | root: 2 | listen: 127.0.0.1:22120 3 | hash: fnv1a_64 4 | distribution: ketama 5 | preconnect: true 6 | auto_eject_hosts: false 7 | servers: 8 | - 127.0.0.1:22121:1 9 | -------------------------------------------------------------------------------- /conf/nutcracker.yml: -------------------------------------------------------------------------------- 1 | global: 2 | stats_duration: 600000 3 | reload_timeout: 60000 4 | slow_req_duration: 4000 5 | stats_file: /tmp/twstats.log 6 | 7 | alpha: 8 | listen: 127.0.0.1:22121 9 | slow_req_duration: 3000 10 | hash: fnv1a_64 11 | distribution: ketama 12 | auto_eject_hosts: true 13 | redis: true 14 | server_retry_timeout: 2000 15 | server_failure_limit: 1 16 | servers: 17 | - 127.0.0.1:6379:1 myserver 18 | - 127.0.0.1:6380:1 myserver1 19 | 20 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # Define the package version numbers and the bug reporting address 2 | m4_define([NC_MAJOR], 0) 3 | m4_define([NC_MINOR], 4) 4 | m4_define([NC_PATCH], 1) 5 | m4_define([NC_BUGS], [manj@cs.stanford.edu]) 6 | 7 | # Initialize autoconf 8 | AC_PREREQ([2.64]) 9 | AC_INIT([nutcracker], [NC_MAJOR.NC_MINOR.NC_PATCH], [NC_BUGS]) 10 | AC_CONFIG_SRCDIR([src/nc.c]) 11 | AC_CONFIG_AUX_DIR([config]) 12 | AC_CONFIG_HEADERS([config.h:config.h.in]) 13 | AC_CONFIG_MACRO_DIR([m4]) 14 | 15 | # Initialize automake 16 | AM_INIT_AUTOMAKE([1.9 foreign]) 17 | 18 | # Define macro variables for the package version numbers 19 | AC_DEFINE(NC_VERSION_MAJOR, NC_MAJOR, [Define the major version number]) 20 | AC_DEFINE(NC_VERSION_MINOR, NC_MINOR, [Define the minor version number]) 21 | AC_DEFINE(NC_VERSION_PATCH, NC_PATCH, [Define the patch version number]) 22 | AC_DEFINE(NC_VERSION_STRING, "NC_MAJOR.NC_MINOR.NC_PATCH", [Define the version string]) 23 | 24 | # Checks for language 25 | AC_LANG([C]) 26 | 27 | # Checks for programs 28 | AC_PROG_AWK 29 | AC_PROG_CC 30 | AC_PROG_CPP 31 | AC_PROG_CXX 32 | AC_PROG_INSTALL 33 | AC_PROG_LN_S 34 | AC_PROG_MAKE_SET 35 | AC_PROG_RANLIB 36 | AC_PROG_LIBTOOL 37 | 38 | # Checks for typedefs, structures, and compiler characteristics 39 | AC_C_INLINE 40 | AC_TYPE_INT8_T 41 | AC_TYPE_INT16_T 42 | AC_TYPE_INT32_T 43 | AC_TYPE_INT64_T 44 | AC_TYPE_INTMAX_T 45 | AC_TYPE_INTPTR_T 46 | AC_TYPE_UINT8_T 47 | AC_TYPE_UINT16_T 48 | AC_TYPE_UINT32_T 49 | AC_TYPE_UINT64_T 50 | AC_TYPE_UINTMAX_T 51 | AC_TYPE_UINTPTR_T 52 | AC_TYPE_OFF_T 53 | AC_TYPE_PID_T 54 | AC_TYPE_SIZE_T 55 | AC_TYPE_SSIZE_T 56 | 57 | AC_C_BIGENDIAN( 58 | [], 59 | [AC_DEFINE(HAVE_LITTLE_ENDIAN, 1, [Define to 1 if machine is little endian])], 60 | [AC_MSG_ERROR([endianess of this machine is unknown])], 61 | [AC_MSG_ERROR([universial endianess not supported])] 62 | ) 63 | 64 | # Checks for header files 65 | AC_HEADER_STDBOOL 66 | AC_CHECK_HEADERS([fcntl.h float.h limits.h stddef.h stdlib.h string.h unistd.h]) 67 | AC_CHECK_HEADERS([inttypes.h stdint.h]) 68 | AC_CHECK_HEADERS([sys/ioctl.h sys/time.h sys/uio.h]) 69 | AC_CHECK_HEADERS([sys/socket.h sys/un.h netinet/in.h arpa/inet.h netdb.h]) 70 | AC_CHECK_HEADERS([execinfo.h], 71 | [AC_DEFINE(HAVE_BACKTRACE, [1], [Define to 1 if backtrace is supported])], []) 72 | AC_CHECK_HEADERS([sys/epoll.h], [], []) 73 | AC_CHECK_HEADERS([sys/event.h], [], []) 74 | 75 | # Checks for libraries 76 | AC_CHECK_LIB([m], [pow]) 77 | AC_CHECK_LIB([pthread], [pthread_create]) 78 | 79 | # Checks for library functions 80 | AC_FUNC_FORK 81 | AC_FUNC_MALLOC 82 | AC_FUNC_REALLOC 83 | AC_CHECK_FUNCS([dup2 gethostname gettimeofday strerror]) 84 | AC_CHECK_FUNCS([socket]) 85 | AC_CHECK_FUNCS([memchr memmove memset]) 86 | AC_CHECK_FUNCS([strchr strndup strtoul]) 87 | 88 | AC_CACHE_CHECK([if epoll works], [ac_cv_epoll_works], 89 | AC_TRY_RUN([ 90 | #include 91 | #include 92 | #include 93 | int 94 | main(int argc, char **argv) 95 | { 96 | int fd; 97 | 98 | fd = epoll_create(256); 99 | if (fd < 0) { 100 | perror("epoll_create:"); 101 | exit(1); 102 | } 103 | exit(0); 104 | } 105 | ], [ac_cv_epoll_works=yes], [ac_cv_epoll_works=no])) 106 | AS_IF([test "x$ac_cv_epoll_works" = "xyes"], 107 | [AC_DEFINE([HAVE_EPOLL], [1], [Define to 1 if epoll is supported])], []) 108 | 109 | AC_CACHE_CHECK([if kqueue works], [ac_cv_kqueue_works], 110 | AC_TRY_RUN([ 111 | #include 112 | #include 113 | #include 114 | #include 115 | #include 116 | int 117 | main(int argc, char **argv) 118 | { 119 | int fd; 120 | 121 | fd = kqueue(); 122 | if (fd < 0) { 123 | perror("kqueue:"); 124 | exit(1); 125 | } 126 | exit(0); 127 | } 128 | ], [ac_cv_kqueue_works=yes], [ac_cv_kqueue_works=no])) 129 | AS_IF([test "x$ac_cv_kqueue_works" = "xyes"], 130 | [AC_DEFINE([HAVE_KQUEUE], [1], [Define to 1 if kqueue is supported])], []) 131 | 132 | AC_CACHE_CHECK([if event ports works], [ac_cv_evports_works], 133 | AC_TRY_RUN([ 134 | #include 135 | #include 136 | #include 137 | int 138 | main(int argc, char **argv) 139 | { 140 | int fd; 141 | 142 | fd = port_create(); 143 | if (fd < 0) { 144 | perror("port_create:"); 145 | exit(1); 146 | } 147 | exit(0); 148 | } 149 | ], [ac_cv_evports_works=yes], [ac_cv_evports_works=no])) 150 | AS_IF([test "x$ac_cv_evports_works" = "xyes"], 151 | [AC_DEFINE([HAVE_EVENT_PORTS], [1], [Define to 1 if event ports is supported])], []) 152 | 153 | AS_IF([test "x$ac_cv_epoll_works" = "xno" && 154 | test "x$ac_cv_kqueue_works" = "xno" && 155 | test "x$ac_cv_evports_works" = "xno"], 156 | [AC_MSG_ERROR([either epoll or kqueue or event ports support is required])], []) 157 | 158 | AM_CONDITIONAL([OS_LINUX], [test "x$ac_cv_epoll_works" = "xyes"]) 159 | AM_CONDITIONAL([OS_BSD], [test "x$ac_cv_kqueue_works" = "xyes"]) 160 | AM_CONDITIONAL([OS_SOLARIS], [test "x$ac_cv_evports_works" = "xyes"]) 161 | AM_CONDITIONAL([OS_FREEBSD], [test "$(uname -v | cut -c 1-10)" == "FreeBSD 10"]) 162 | 163 | # Package options 164 | AC_MSG_CHECKING([whether to enable debug logs and asserts]) 165 | AC_ARG_ENABLE([debug], 166 | [AS_HELP_STRING( 167 | [--enable-debug=@<:@full|yes|log|no@:>@], 168 | [enable debug logs and asserts @<:@default=no@:>@]) 169 | ], 170 | [], 171 | [enable_debug=no]) 172 | AS_CASE([x$enable_debug], 173 | [xfull], [AC_DEFINE([HAVE_ASSERT_PANIC], [1], 174 | [Define to 1 if panic on an assert is enabled]) 175 | AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled]) 176 | ], 177 | [xyes], [AC_DEFINE([HAVE_ASSERT_LOG], [1], 178 | [Define to 1 if log on an assert is enabled]) 179 | AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled]) 180 | ], 181 | [xlog], [AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled])], 182 | [xno], [], 183 | [AC_MSG_FAILURE([invalid value ${enable_debug} for --enable-debug])]) 184 | AC_MSG_RESULT($enable_debug) 185 | 186 | AC_MSG_CHECKING([whether to disable stats]) 187 | AC_ARG_ENABLE([stats], 188 | [AS_HELP_STRING( 189 | [--disable-stats], 190 | [disable stats]) 191 | ], 192 | [disable_stats=yes], 193 | [disable_stats=no]) 194 | AS_IF([test "x$disable_stats" = xyes], 195 | [], 196 | [AC_DEFINE([HAVE_STATS], [1], [Define to 1 if stats is not disabled])]) 197 | AC_MSG_RESULT($disable_stats) 198 | 199 | # Untar the yaml-0.1.4 in contrib/ before config.status is rerun 200 | AC_CONFIG_COMMANDS_PRE([tar xvfz contrib/yaml-0.1.4.tar.gz -C contrib]) 201 | 202 | # Call yaml-0.1.4 ./configure recursively 203 | AC_CONFIG_SUBDIRS([contrib/yaml-0.1.4]) 204 | 205 | # Define Makefiles 206 | AC_CONFIG_FILES([Makefile 207 | contrib/Makefile 208 | src/Makefile 209 | src/hashkit/Makefile 210 | src/proto/Makefile 211 | src/event/Makefile]) 212 | 213 | # Generate the "configure" script 214 | AC_OUTPUT 215 | -------------------------------------------------------------------------------- /contrib/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = yaml-0.1.4 2 | 3 | EXTRA_DIST = yaml-0.1.4.tar.gz 4 | -------------------------------------------------------------------------------- /contrib/yaml-0.1.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilibili/bilitw/a6c46feef6a0f41c15b11e96938658eec13d41f6/contrib/yaml-0.1.4.tar.gz -------------------------------------------------------------------------------- /contrib/yaml-0.1.4/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # Except me 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /m4/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # Except me 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /man/nutcracker.8: -------------------------------------------------------------------------------- 1 | .TH NUTCRACKER 8 "June 13, 2013" 2 | .SH NAME 3 | nutcracker \- Fast, light-weight proxy for memcached and Redis 4 | .SH SYNOPSIS 5 | .B nutcracker 6 | .RI [ options ] 7 | .SH DESCRIPTION 8 | \fBnutcracker\fP, also known as \fBtwemproxy\fP (pronounced "two-em-proxy"), is 9 | a fast and lightweight proxy for the memcached and Redis protocols. 10 | .PP 11 | It was primarily built to reduce the connection count on backend caching 12 | servers, but it has a number of features, such as: 13 | .IP \[bu] 14 | Maintains persistent server connections to backend servers. 15 | .IP \[bu] 16 | Enables pipelining of requests and responses. 17 | .IP \[bu] 18 | Supports multiple server pools simultaneously. 19 | .IP \[bu] 20 | Shard data automatically across multiple servers. 21 | .IP \[bu] 22 | Supports multiple hashing modes including consistent hashing and 23 | distribution. 24 | .IP \[bu] 25 | High-availability by disabling nodes on failures. 26 | .IP \[bu] 27 | Observability through stats exposed on stats monitoring port. 28 | .SH OPTIONS 29 | .TP 30 | .BR \-h ", " \-\-help 31 | Show usage information and exit. 32 | .TP 33 | .BR \-V ", " \-\-version 34 | Show version and exit. 35 | .TP 36 | .BR \-t ", " \-\-test-conf 37 | Test configuration for syntax errors and exit. 38 | .TP 39 | .BR \-D ", " \-\-describe-stats 40 | Print stats description and exit. 41 | .TP 42 | .BR \-v ", " \-\-verbose=\fIN\fP 43 | Set logging level to \fIN\fP. (default: 5, min: 0, max: 11) 44 | .TP 45 | .BR \-o ", " \-\-output=\fIfilename\fP 46 | Set logging file to \fIfilename\fP. 47 | .TP 48 | .BR \-c ", " \-\-conf-file=\fIfilename\fP 49 | Set configuration file to \fIfilename\fP. 50 | .TP 51 | .BR \-s ", " \-\-stats-port=\fIport\fP 52 | Set stats monitoring port to \fIport\fP. 53 | (default: 22222) 54 | .TP 55 | .BR \-a ", " \-\-stats-addr=\fIaddress\fP 56 | Set stats monitoring IP to \fIaddress\fP. 57 | (default: 0.0.0.0) 58 | .TP 59 | .BR \-i ", " \-\-stats-interval=\fIinterval\fP 60 | Set stats aggregation interval in msec to \fIinterval\fP. 61 | (default: 30000 msec) 62 | .TP 63 | .BR \-m ", " \-\-mbuf-size=\fIsize\fP 64 | Set size of mbuf chunk in bytes to \fIsize\fP. (default: 16384 bytes) 65 | .TP 66 | .BR \-d ", " \-\-daemonize 67 | Run as a daemon. 68 | .TP 69 | .BR \-p ", " \-\-pid-file=\fIfilename\fP 70 | Set pid file to \fIfilename\fP. 71 | .SH SEE ALSO 72 | .BR memcached (8), 73 | .BR redis-server (1) 74 | .br 75 | .SH AUTHOR 76 | nutcracker was written by Twitter, Inc. 77 | -------------------------------------------------------------------------------- /notes/debug.txt: -------------------------------------------------------------------------------- 1 | - strace 2 | strace -o strace.txt -ttT -s 1024 -p `pgrep nutcracker` 3 | 4 | - libyaml (yaml-0.1.4) 5 | 6 | - yaml tokens: 7 | 8 | 0 YAML_NO_TOKEN, 9 | 1 YAML_STREAM_START_TOKEN, 10 | 2 YAML_STREAM_END_TOKEN, 11 | 3 YAML_VERSION_DIRECTIVE_TOKEN, 12 | 4 YAML_TAG_DIRECTIVE_TOKEN, 13 | 5 YAML_DOCUMENT_START_TOKEN, 14 | 6 YAML_DOCUMENT_END_TOKEN, 15 | 7 YAML_BLOCK_SEQUENCE_START_TOKEN, 16 | 8 YAML_BLOCK_MAPPING_START_TOKEN, 17 | 9 YAML_BLOCK_END_TOKEN, 18 | 10 YAML_FLOW_SEQUENCE_START_TOKEN, 19 | 11 YAML_FLOW_SEQUENCE_END_TOKEN, 20 | 12 YAML_FLOW_MAPPING_START_TOKEN, 21 | 13 YAML_FLOW_MAPPING_END_TOKEN, 22 | 14 YAML_BLOCK_ENTRY_TOKEN, 23 | 15 YAML_FLOW_ENTRY_TOKEN, 24 | 16 YAML_KEY_TOKEN, 25 | 17 YAML_VALUE_TOKEN, 26 | 18 YAML_ALIAS_TOKEN, 27 | 19 YAML_ANCHOR_TOKEN, 28 | 20 YAML_TAG_TOKEN, 29 | 21 YAML_SCALAR_TOKEN 30 | 31 | - yaml events 32 | 33 | 0 YAML_NO_EVENT, 34 | 1 YAML_STREAM_START_EVENT, 35 | 2 YAML_STREAM_END_EVENT, 36 | 3 YAML_DOCUMENT_START_EVENT, 37 | 4 YAML_DOCUMENT_END_EVENT, 38 | 5 YAML_ALIAS_EVENT, 39 | 6 YAML_SCALAR_EVENT, 40 | 7 YAML_SEQUENCE_START_EVENT, 41 | 8 YAML_SEQUENCE_END_EVENT, 42 | 9 YAML_MAPPING_START_EVENT, 43 | 10 YAML_MAPPING_END_EVENT 44 | 45 | - sys/queue.h 46 | 47 | queue.h is a generic linked list library adapted from BSD. It has three 48 | macro knobs that are useful for debugging: 49 | 50 | - QUEUE_MACRO_SCRUB nullifies links (next and prev pointers) of deleted 51 | elements and catches cases where we are attempting to do operations 52 | on an element that has already been unlinked. 53 | - QUEUE_MACRO_TRACE keeps track of __FILE__ and __LINE__ of last two 54 | updates to the list data structure. 55 | - QUEUE_MACRO_ASSERT verifies the sanity of list data structure on every 56 | operation. 57 | 58 | - valgrind 59 | valgrind --tool=memcheck --leak-check=yes 60 | 61 | - Core dump 62 | ulimit -c unlimited 63 | 64 | - Generate ENOMEM to test "Out of Memory" 65 | ulimit -m # limit maximum memory size 66 | ulimit -v # limit virtual memory 67 | 68 | - get nutcracker stats 69 | printf "" | socat - TCP:localhost:22222 | tee stats.txt 70 | printf "" | nc localhost 22222 | python -mjson.tool 71 | 72 | - Signalling and Logging 73 | SIGTTIN - To up the log level 74 | SIGTTOU - To down the log level 75 | SIGHUP - To reopen log file 76 | 77 | - Error codes: 78 | http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_2.html 79 | /usr/include/asm-generic/errno-base.h 80 | /usr/include/asm-generic/errno.h 81 | 82 | - epoll (linux) 83 | 84 | union epoll_data { 85 | void *ptr; 86 | int fd; 87 | uint32_t u32; 88 | uint64_t u64; 89 | }; 90 | 91 | struct epoll_event { 92 | uint32_t events; /* epoll events */ 93 | struct epoll_data data; /* user data variable */ 94 | }; 95 | 96 | /* events */ 97 | EPOLLIN = 0x001, 98 | EPOLLPRI = 0x002, 99 | EPOLLOUT = 0x004, 100 | EPOLLERR = 0x008, 101 | EPOLLHUP = 0x010, 102 | EPOLLRDNORM = 0x040, 103 | EPOLLRDBAND = 0x080, 104 | EPOLLWRNORM = 0x100, 105 | EPOLLWRBAND = 0x200, 106 | EPOLLMSG = 0x400, 107 | EPOLLRDHUP = 0x2000, 108 | EPOLLONESHOT = (1 << 30), 109 | EPOLLET = (1 << 31) 110 | 111 | /* opcodes */ 112 | EPOLL_CTL_ADD = 1 /* add a file decriptor to the interface */ 113 | EPOLL_CTL_DEL = 2 /* remove a file decriptor from the interface */ 114 | EPOLL_CTL_MOD = 3 /* change file decriptor epoll_event structure */ 115 | 116 | - kqueue (bsd) 117 | 118 | struct kevent { 119 | uintptr_t ident; /* identifier for this event */ 120 | int16_t filter; /* filter for event */ 121 | uint16_t flags; /* general flags */ 122 | uint32_t fflags; /* filter-specific flags */ 123 | intptr_t data; /* filter-specific data */ 124 | void *udata; /* opaque user data identifier */ 125 | }; 126 | 127 | /* flags / events */ 128 | EV_ADD = 0x0001 /* action - add event to kq (implies enable) */ 129 | EV_DELETE = 0x0002 /* action - delete event from kq */ 130 | EV_ENABLE = 0x0004 /* action - enable event */ 131 | EV_DISABLE = 0x0008 /* action - disable event (not reported) */ 132 | EV_RECEIPT = 0x0040 /* action - force EV_ERROR on success, data == 0 */ 133 | 134 | EV_ONESHOT = 0x0010 /* flags - only report one occurrence */ 135 | EV_CLEAR = 0x0020 /* flags - clear event state after reporting */ 136 | EV_DISPATCH = 0x0080 /* flags - disable event after reporting */ 137 | EV_SYSFLAGS = 0xF000 /* flags - reserved by system */ 138 | EV_FLAG0 = 0x1000 /* flags - filter-specific flag */ 139 | EV_FLAG1 = 0x2000 /* flags - filter-specific flag */ 140 | 141 | EV_EOF = 0x8000 /* returned values - EOF detected */ 142 | EV_ERROR = 0x4000 /* returned values - error, data contains errno */ 143 | 144 | /* filters */ 145 | EVFILT_READ (-1) /* readable */ 146 | EVFILT_WRITE (-2) /* writable */ 147 | EVFILT_AIO (-3) /* attached to aio requests */ 148 | EVFILT_VNODE (-4) /* attached to vnodes */ 149 | EVFILT_PROC (-5) /* attached to struct proc */ 150 | EVFILT_SIGNAL (-6) /* attached to struct proc */ 151 | EVFILT_TIMER (-7) /* timers */ 152 | EVFILT_MACHPORT (-8) /* mach portsets */ 153 | EVFILT_FS (-9) /* filesystem events */ 154 | EVFILT_USER (-10) /* user events */ 155 | EVFILT_VM (-12) /* virtual memory events */ 156 | 157 | EV_CLEAR behaves like EPOLLET because it resets the event after it is 158 | returned; without this flag, the event would be repeatedly returned. 159 | 160 | - poll (unix) 161 | 162 | POLLIN 0x001 /* there is data to read */ 163 | POLLPRI 0x002 /* there is urgent data to read */ 164 | POLLOUT 0x004 /* writing now will not block */ 165 | 166 | POLLRDNORM 0x040 /* normal data may be read */ 167 | POLLRDBAND 0x080 /* priority data may be read */ 168 | POLLWRNORM 0x100 /* writing now will not block */ 169 | POLLWRBAND 0x200 /* priority data may be written */ 170 | 171 | POLLMSG 0x400 172 | POLLREMOVE 0x1000 173 | POLLRDHUP 0x2000 174 | 175 | POLLERR 0x008 /* error condition */ 176 | POLLHUP 0x010 /* hung up */ 177 | POLLNVAL 0x020 /* invalid polling request */ 178 | 179 | - event ports (solaris) 180 | 181 | typedef struct port_event { 182 | int portev_events; /* event data is source specific */ 183 | ushort_t portev_source; /* event source */ 184 | ushort_t portev_pad; /* port internal use */ 185 | uintptr_t portev_object; /* source specific object */ 186 | void *portev_user; /* user cookie */ 187 | } port_event_t; 188 | 189 | /* port sources */ 190 | PORT_SOURCE_AIO 1 191 | PORT_SOURCE_TIMER 2 192 | PORT_SOURCE_USER 3 193 | PORT_SOURCE_FD 4 194 | PORT_SOURCE_ALERT 5 195 | PORT_SOURCE_MQ 6 196 | PORT_SOURCE_FILE 7 197 | -------------------------------------------------------------------------------- /notes/kqueue.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilibili/bilitw/a6c46feef6a0f41c15b11e96938658eec13d41f6/notes/kqueue.pdf -------------------------------------------------------------------------------- /notes/socket.txt: -------------------------------------------------------------------------------- 1 | - int listen(int sockfd, int backlog); 2 | 3 | Linux: The backlog argument defines the maximum length to which the 4 | queue of pending connections for sockfd may grow. If a connection 5 | request arrives when the queue is full, the client may receive an error 6 | with an indication of ECONNREFUSED or, if the underlying protocol 7 | supports retransmission, the request may be ignored so that a later 8 | reattempt at connection succeeds. 9 | 10 | backlog specifies the queue length for completely established sockets 11 | waiting to be accepted, instead of the number of incomplete connection 12 | requests. The maximum length of the queue for incomplete sockets can 13 | be set using /proc/sys/net/ipv4/tcp_max_syn_backlog. 14 | 15 | If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, 16 | then it is silently truncated to that value; the default value in this 17 | file is 128. In kernels before 2.4.25, this limit was a hard coded value, 18 | SOMAXCONN, with the value 128. 19 | 20 | BSD: The backlog argument defines the maximum length the queue of pending 21 | connections may grow to. The real maximum queue length will be 1.5 times 22 | more than the value specified in the backlog argument. A subsequent 23 | listen() system call on the listening socket allows the caller to change 24 | the maximum queue length using a new backlog argument. If a connection 25 | request arrives with the queue full the client may receive an error with 26 | an indication of ECONNREFUSED, or, in the case of TCP, the connection 27 | will be silently dropped. 28 | 29 | The listen() system call appeared in 4.2BSD. The ability to configure 30 | the maximum backlog at run-time, and to use a negative backlog to request 31 | the maximum allowable value, was introduced in FreeBSD 2.2. 32 | 33 | - SO_LINGER (linger) socket option 34 | 35 | This option specifies what should happen when the socket of a type that 36 | promises reliable delivery still has untransmitted messages when it is 37 | closed 38 | 39 | struct linger { 40 | int l_onoff; /* nonzero to linger on close */ 41 | int l_linger; /* time to linger (in secs) */ 42 | }; 43 | 44 | l_onoff = 0 (default), then l_linger value is ignored and close returns 45 | immediately. But if there is any data still remaining in the socket send 46 | buffer, the system will try to deliver the data to the peer 47 | 48 | l_onoff = nonzero, then close blocks until data is transmitted or the 49 | l_linger timeout period expires 50 | a) l_linger = 0, TCP aborts connection, discards any data still remaining 51 | in the socket send buffer and sends RST to peer. This avoids the 52 | TCP's TIME_WAIT state 53 | b) l_linger = nonzero, then kernel will linger when socket is closed. If 54 | there is any pending data in the socket send buffer, the kernel waits 55 | until all the data is sent and acknowledged by peer TCP, or the 56 | linger time expires 57 | 58 | If a socket is set as nonblocking, it will not wait for close to complete 59 | even if linger time is nonzero 60 | 61 | - TIME_WAIT state 62 | 63 | The end that performs active close i.e. the end that sends the first FIN 64 | goes into TIME_WAIT state. After a FIN packet is sent to the peer and 65 | after that peers FIN/ACK arrvies and is ACKed, we go into a TIME_WAIT 66 | state. The duration that the end point remains in this state is 2 x MSL 67 | (maximum segment lifetime). The reason that the duration of the TIME_WAIT 68 | state is 2 x MSL is because the maximum amount of time a packet can wander 69 | around a network is assumed to be MSL seconds. The factor of 2 is for the 70 | round-trip. The recommended value for MSL is 120 seconds, but Berkeley 71 | derived implementations normally use 30 seconds instead. This means a 72 | TIME_WAIT delay is between 1 and 4 minutes. 73 | 74 | For Linux, the TIME_WAIT state duration is 1 minute (net/tcp.h): 75 | #define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT 76 | * state, about 60 seconds */ 77 | 78 | TIME_WAIT state on client, combined with limited number of ephermeral ports 79 | available for TCP connections severely limits the rate at which new 80 | connections to the server can be created. On Linux, by default ephemeral 81 | ports are in the range of 32768 to 61000: 82 | 83 | $ cat /proc/sys/net/ipv4/ip_local_port_range 84 | 32768 61000 85 | 86 | So with a TIME_WAIT state duration of 1 minute, the maximum sustained rate 87 | for any client is ~470 new connections per second 88 | 89 | - TCP keepalive 90 | 91 | TCP keepalive packet (TCP packet with no data and the ACK flag turned on) 92 | is used to assert that connection is still up and running. This is useful 93 | because if the remote peer goes away without closing their connection, the 94 | keepalive probe will detect this and notice that the connection is broken 95 | even if there is no traffic on it. 96 | 97 | Imagine, the following scenario: You have a valid TCP connection established 98 | between two endpoints A and B. B terminates abnormally (think kernel panic 99 | or unplugging of network cable) without sending anything over the network 100 | to notify A that connection is broken. A, from its side, is ready to 101 | receive data, and has no idea that B has gone away. Now B comes back up 102 | again, and while A knows about a connection with B and still thinks that it 103 | active, B has no such idea. A tries to send data to B over a dead 104 | connection, and B replies with an RST packet, causing A to finally close 105 | the connection. So, without a keepalive probe A would never close the 106 | connection if it never sent data over it. 107 | 108 | - There are four socket functions that pass a socket address structure from 109 | the process to the kernel - bind, connect, sendmsg and sendto. These 110 | function are also responsible for passing the length of the sockaddr that 111 | they are passing (socklen_t). 112 | There are five socket functions that pass a socket from the kernel to the 113 | process - accept, recvfrom, recvmsg, getpeername, getsockname. The kernel 114 | is also responsible for returning the length of the sockaddr struct that 115 | it returns back to the userspace 116 | 117 | Different sockaddr structs: 118 | 1. sockaddr_in 119 | 2. sockaddr_in6 120 | 3. sockaddr_un 121 | 122 | Special types of in_addr_t 123 | /* Address to accept any incoming messages */ 124 | #define INADDR_ANY ((in_addr_t) 0x00000000) 125 | 126 | /* Address to send to all hosts */ 127 | #define INADDR_BROADCAST ((in_addr_t) 0xffffffff) 128 | 129 | /* Address indicating an error return */ 130 | #define INADDR_NONE ((in_addr_t) 0xffffffff) 131 | 132 | -------------------------------------------------------------------------------- /scripts/benchmark-mget.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | #file : test_mget.py 4 | #author : ning 5 | #date : 2014-04-01 13:15:48 6 | 7 | import os 8 | import re 9 | import commands 10 | 11 | ports = [ 12 | 4001, # before improve 13 | 4000, # after improve 14 | 2000 # redis 15 | ] 16 | 17 | def system(cmd): 18 | return commands.getoutput(cmd) 19 | 20 | def extra(regex, text): 21 | match = re.search(regex, text, re.DOTALL) 22 | if match: 23 | return match.group(1) 24 | 25 | def testit(): 26 | for mget_size in [10, 100, 1000, 10000]: 27 | for port in ports: 28 | cnt = 100*1000 / mget_size 29 | clients = 50 30 | if mget_size == 10000: 31 | clients = 2 32 | cmd = 'cd /home/ning/xredis/deploy-srcs/redis-2.8.3/src && ./redis-benchmark.%d -n %d -p %d -t mget -r 1000000000 -c %d' % (mget_size, cnt, port, clients) 33 | #print cmd 34 | rst = system(cmd) 35 | 36 | #100.00% <= 2 milliseconds 37 | #28089.89 requests per second 38 | rtime = extra('100.00% <= (\d+) milliseconds', rst) 39 | qps = extra('([\.\d]+) requests per second', rst) 40 | 41 | print 'mget_size=%d on %d: pqs: %s, rtime: %s' % (mget_size, port, qps, rtime) 42 | 43 | testit() 44 | -------------------------------------------------------------------------------- /scripts/makesrpm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #------------------------------------------------------------------------------- 3 | # Create a source RPM package 4 | # Author: Elvin Sindrilaru 2015 5 | #------------------------------------------------------------------------------- 6 | RCEXP='^[0-9]+\.[0-9]+\.[0-9]+\-rc.*$' 7 | 8 | #------------------------------------------------------------------------------- 9 | # Color console messages 10 | #------------------------------------------------------------------------------- 11 | NORMAL=$(tput sgr0) 12 | GREEN=$(tput setaf 2; tput bold) 13 | RED=$(tput setaf 1) 14 | 15 | function green() { 16 | echo -e "$GREEN$*$NORMAL" 17 | } 18 | 19 | function red() { 20 | echo -e "$RED$*$NORMAL" 21 | } 22 | 23 | #------------------------------------------------------------------------------- 24 | # Find a program 25 | #------------------------------------------------------------------------------- 26 | function findProg() 27 | { 28 | for prog in $@; do 29 | if test -x "`which $prog 2>/dev/null`"; then 30 | echo $prog 31 | break 32 | fi 33 | done 34 | } 35 | 36 | #------------------------------------------------------------------------------- 37 | # Print help 38 | #------------------------------------------------------------------------------- 39 | function printHelp() 40 | { 41 | echo "Usage:" 1>&2 42 | echo "${0} [--help] [--source PATH] [--output PATH]" 1>&2 43 | echo " --help prints this message" 1>&2 44 | echo " --source PATH specify the root of the source tree" 1>&2 45 | echo " defaults to ../" 1>&2 46 | echo " --output PATH the directory where the source rpm" 1>&2 47 | echo " should be stored, defaults to ." 1>&2 48 | } 49 | 50 | #------------------------------------------------------------------------------- 51 | # Parse the commandline 52 | #------------------------------------------------------------------------------- 53 | SOURCEPATH="../" 54 | OUTPUTPATH="." 55 | PRINTHELP=0 56 | 57 | while test ${#} -ne 0; do 58 | if test x${1} = x--help; then 59 | PRINTHELP=1 60 | elif test x${1} = x--source; then 61 | if test ${#} -lt 2; then 62 | echo "--source parameter needs an argument" 1>&2 63 | exit 1 64 | fi 65 | SOURCEPATH=${2} 66 | shift 67 | elif test x${1} = x--output; then 68 | if test ${#} -lt 2; then 69 | echo "--output parameter needs an argument" 1>&2 70 | exit 1 71 | fi 72 | OUTPUTPATH=${2} 73 | shift 74 | else 75 | PRINTHELP=1 76 | fi 77 | shift 78 | done 79 | 80 | if test $PRINTHELP -eq 1; then 81 | printHelp 82 | exit 0 83 | fi 84 | 85 | echo "[i] Working on: $SOURCEPATH" 86 | echo "[i] Storing the output to: $OUTPUTPATH" 87 | 88 | #------------------------------------------------------------------------------- 89 | # Check if the source and the output dirs exist 90 | #------------------------------------------------------------------------------- 91 | if test ! -d $SOURCEPATH -o ! -r $SOURCEPATH; then 92 | red "[!] Source path does not exist or is not readable" 1>&2 93 | exit 2 94 | fi 95 | 96 | if test ! -d $OUTPUTPATH -o ! -w $OUTPUTPATH; then 97 | red "[!] Output path does not exist or is not writeable" 1>&2 98 | exit 2 99 | fi 100 | 101 | #------------------------------------------------------------------------------- 102 | # Check if we have all the necassary components 103 | #------------------------------------------------------------------------------- 104 | if test x`findProg rpmbuild` = x; then 105 | red "[!] Unable to find rpmbuild, aborting..." 1>&2 106 | exit 1 107 | fi 108 | 109 | if test x`findProg git` = x; then 110 | red "[!] Unable to find git, aborting..." 1>&2 111 | exit 1 112 | fi 113 | 114 | if test x`findProg awk` = x; then 115 | red "[!] Unable to find awk, aborting..." 1>&2 116 | exit 1 117 | fi 118 | 119 | #------------------------------------------------------------------------------- 120 | # Check if the source is a git repository 121 | #------------------------------------------------------------------------------- 122 | if test ! -d $SOURCEPATH/.git; then 123 | red "[!] I can only work with a git repository" 1>&2 124 | exit 2 125 | fi 126 | 127 | #------------------------------------------------------------------------------- 128 | # Get VERSION from the spec file 129 | #------------------------------------------------------------------------------- 130 | SPEC_FILE="nutcracker.spec" 131 | 132 | if test -e "$SPEC_FILE"; then 133 | VERSION="$(grep "Version:" $SPEC_FILE | awk '{print $2;}')" 134 | else 135 | red "[!] Unable to get version from spec file." 1>&2 136 | exit 4 137 | fi 138 | 139 | echo "[i] Working with version: $VERSION" 140 | 141 | if test x${VERSION:0:1} = x"v"; then 142 | VERSION=${VERSION:1} 143 | fi 144 | 145 | #------------------------------------------------------------------------------- 146 | # Deal with release candidates 147 | #------------------------------------------------------------------------------- 148 | RELEASE=1 149 | 150 | if test x`echo $VERSION | egrep $RCEXP` != x; then 151 | RELEASE=0.`echo $VERSION | sed 's/.*-rc/rc/'` 152 | VERSION=`echo $VERSION | sed 's/-rc.*//'` 153 | fi 154 | 155 | VERSION=`echo $VERSION | sed 's/-/./g'` 156 | echo "[i] RPM compliant version: $VERSION-$RELEASE" 157 | 158 | #------------------------------------------------------------------------------- 159 | # Create a tempdir and copy the files there 160 | #------------------------------------------------------------------------------- 161 | # exit on any error 162 | set -e 163 | 164 | TEMPDIR=`mktemp -d /tmp/nutcracker.srpm.XXXXXXXXXX` 165 | RPMSOURCES=$TEMPDIR/rpmbuild/SOURCES 166 | mkdir -p $RPMSOURCES 167 | mkdir -p $TEMPDIR/rpmbuild/SRPMS 168 | echo "[i] Working in: $TEMPDIR" 1>&2 169 | 170 | #------------------------------------------------------------------------------- 171 | # Make a tarball of the latest commit on the branch 172 | #------------------------------------------------------------------------------- 173 | # no more exiting on error 174 | set +e 175 | 176 | CWD=$PWD 177 | cd $SOURCEPATH 178 | COMMIT=`git log --pretty=format:"%H" -1` 179 | 180 | if test $? -ne 0; then 181 | red "[!] Unable to figure out the git commit hash" 1>&2 182 | exit 5 183 | fi 184 | 185 | git archive --prefix=nutcracker-"$VERSION"/ --format=tar $COMMIT | gzip -9fn > \ 186 | $RPMSOURCES/nutcracker-"$VERSION".tar.gz 187 | 188 | if test $? -ne 0; then 189 | red "[!] Unable to create the source tarball" 1>&2 190 | exit 6 191 | fi 192 | 193 | cd $CWD 194 | 195 | #------------------------------------------------------------------------------- 196 | # Build the source RPM 197 | #------------------------------------------------------------------------------- 198 | echo "[i] Creating the source RPM..." 199 | 200 | # Dirty, dirty hack! 201 | echo "%_sourcedir $RPMSOURCES" >> $TEMPDIR/rpmmacros 202 | rpmbuild --define "_topdir $TEMPDIR/rpmbuild" \ 203 | --define "%_sourcedir $RPMSOURCES" \ 204 | --define "%_srcrpmdir %{_topdir}/SRPMS" \ 205 | --define "_source_filedigest_algorithm md5" \ 206 | --define "_binary_filedigest_algorithm md5" \ 207 | -bs nutcracker.spec > $TEMPDIR/log 208 | if test $? -ne 0; then 209 | red "[!] RPM creation failed" 1>&2 210 | exit 8 211 | fi 212 | 213 | cp $TEMPDIR/rpmbuild/SRPMS/nutcracker*.src.rpm $OUTPUTPATH 214 | rm -rf $TEMPDIR 215 | 216 | green "[i] Done." 217 | -------------------------------------------------------------------------------- /scripts/multi_get.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | port=22123 4 | socatopt="-t 20 -T 20 -b 8193 -d -d " 5 | key="" 6 | keys="" 7 | get_command="" 8 | 9 | # build 10 | for i in `seq 1 512`; do 11 | if [ `expr $i % 2` -eq "0" ]; then 12 | key="foo" 13 | else 14 | key="bar" 15 | fi 16 | key=`printf "%s%d" "${key}" "${i}"` 17 | keys=`printf "%s %s" "${keys}" "${key}"` 18 | done 19 | 20 | get_command="get ${keys}\r\n" 21 | printf "%b" "$get_command" 22 | 23 | # read 24 | for i in `seq 1 16`; do 25 | printf "%b" "${get_command}" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 & 26 | done 27 | -------------------------------------------------------------------------------- /scripts/nutcracker.init: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # chkconfig: - 55 45 4 | # description: Twitter's twemproxy nutcracker 5 | # processname: nutcracker 6 | # config: /etc/sysconfig/nutcracker 7 | 8 | # Source function library. 9 | . /etc/rc.d/init.d/functions 10 | 11 | USER="nobody" 12 | OPTIONS="-d -c /etc/nutcracker/nutcracker.yml" 13 | 14 | if [ -f /etc/sysconfig/nutcracker ];then 15 | . /etc/sysconfig/nutcracker 16 | fi 17 | 18 | # Check that networking is up. 19 | if [ "$NETWORKING" = "no" ] 20 | then 21 | exit 0 22 | fi 23 | 24 | RETVAL=0 25 | prog="nutcracker" 26 | 27 | start () { 28 | echo -n $"Starting $prog: " 29 | #Test the config before start. 30 | daemon --user ${USER} ${prog} $OPTIONS -t >/dev/null 2>&1 31 | RETVAL=$? 32 | if [ $RETVAL -ne 0 ] ; then 33 | echo "Config check fail! Please use 'nutcracker -c /etc/nutcracker/nutcracker.yml' for detail." 34 | echo_failure; 35 | echo; 36 | exit 1 37 | fi 38 | 39 | daemon --user ${USER} ${prog} $OPTIONS 40 | RETVAL=$? 41 | echo 42 | [ $RETVAL -eq 0 ] && touch /var/lock/subsys/${prog} 43 | } 44 | stop () { 45 | echo -n $"Stopping $prog: " 46 | killproc ${prog} 47 | RETVAL=$? 48 | echo 49 | if [ $RETVAL -eq 0 ] ; then 50 | rm -f /var/lock/subsys/${prog} 51 | fi 52 | } 53 | 54 | restart () { 55 | stop 56 | start 57 | } 58 | 59 | 60 | # See how we were called. 61 | case "$1" in 62 | start) 63 | start 64 | ;; 65 | stop) 66 | stop 67 | ;; 68 | status) 69 | status ${prog} 70 | ;; 71 | restart|reload) 72 | restart 73 | ;; 74 | condrestart) 75 | [ -f /var/lock/subsys/nutcracker ] && restart || : 76 | ;; 77 | *) 78 | echo $"Usage: $0 {start|stop|status|restart|reload|condrestart}" 79 | exit 1 80 | esac 81 | 82 | exit $? 83 | 84 | -------------------------------------------------------------------------------- /scripts/nutcracker.init.debian: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: nutcracker 4 | # Required-Start: $network $remote_fs $local_fs 5 | # Required-Stop: $network $remote_fs $local_fs 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Stop/start nutcracker 9 | ### END INIT INFO 10 | 11 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 12 | DESC=nutcracker 13 | NAME=nutcracker 14 | USER=nobody 15 | CONFFILE=/opt/nutcracker/etc/$NAME.yml 16 | LOGFILE=/opt/nutcracker/log/nutcracker.log 17 | DAEMON=/opt/nutcracker/sbin/nutcracker 18 | PIDFILE=/var/run/nutcracker/$NAME.pid 19 | STATSPORT=22222 20 | DAEMON_ARGS="-c $CONFFILE -o $LOGFILE -p $PIDFILE -s $STATSPORT -v 11 -m 2048 -d" 21 | #DAEMON_ARGS="-c $CONFFILE -p $PIDFILE -s $STATSPORT -d" 22 | SCRIPTNAME=/etc/init.d/$NAME 23 | 24 | ulimit -Hn 100000 25 | ulimit -Sn 100000 26 | 27 | [ -x $DAEMON ] || exit 0 28 | 29 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 30 | 31 | . /lib/init/vars.sh 32 | 33 | . /lib/lsb/init-functions 34 | 35 | do_start() 36 | { 37 | mkdir -p /var/run/nutcracker 38 | touch $PIDFILE 39 | chown $USER:$USER -R /var/run/nutcracker 40 | chmod 755 /var/run/nutcracker 41 | 42 | echo -n "Starting ${NAME}: " 43 | start-stop-daemon --start --quiet -m --pidfile $PIDFILE --chuid $USER:$USER --exec $DAEMON -- \ 44 | $DAEMON_ARGS 45 | case "$?" in 46 | 0|1) echo "STARTED." ;; 47 | 2) echo "FAILED." ;; 48 | esac 49 | } 50 | 51 | do_stop() 52 | { 53 | echo -n "Stopping ${NAME}: " 54 | start-stop-daemon --stop --quiet --pidfile $PIDFILE --exec $DAEMON || true 55 | 56 | case "$?" in 57 | 0|1) echo "STOPPED.";; 58 | 2) echo "FAILED." ;; 59 | esac 60 | } 61 | 62 | case "$1" in 63 | start) 64 | do_start 65 | ;; 66 | stop) 67 | do_stop 68 | ;; 69 | status) 70 | status_of_proc -p $PIDFILE "$DAEMON" nutcracker && exit 0 || exit $? 71 | ;; 72 | restart) 73 | do_stop 74 | do_start 75 | ;; 76 | *) 77 | echo "Usage: $SCRIPTNAME {start|stop|status|restart}" >&2 78 | exit 3 79 | ;; 80 | esac 81 | 82 | exit 83 | $RETVAL 84 | -------------------------------------------------------------------------------- /scripts/nutcracker.spec: -------------------------------------------------------------------------------- 1 | Summary: Twitter's nutcracker redis and memcached proxy 2 | Name: nutcracker 3 | Version: 0.4.1 4 | Release: 1 5 | 6 | URL: https://github.com/twitter/twemproxy/ 7 | Source0: %{name}-%{version}.tar.gz 8 | License: Apache License 2.0 9 | Group: System Environment/Libraries 10 | Packager: Tom Parrott 11 | BuildRoot: %{_tmppath}/%{name}-root 12 | 13 | BuildRequires: autoconf 14 | BuildRequires: automake 15 | BuildRequires: libtool 16 | 17 | %description 18 | twemproxy (pronounced "two-em-proxy"), aka nutcracker is a fast and lightweight proxy for memcached and redis protocol. 19 | It was primarily built to reduce the connection count on the backend caching servers. This, together with protocol 20 | pipelining and sharding enables you to horizontally scale your distributed caching architecture. 21 | 22 | %prep 23 | %setup -q 24 | %if 0%{?rhel} == 6 25 | sed -i 's/2.64/2.63/g' configure.ac 26 | %endif 27 | autoreconf -fvi 28 | 29 | %build 30 | 31 | %configure 32 | %__make 33 | 34 | %install 35 | [ %{buildroot} != "/" ] && rm -rf %{buildroot} 36 | 37 | %makeinstall PREFIX=%{buildroot} 38 | 39 | #Install init script 40 | %{__install} -p -D -m 0755 scripts/%{name}.init %{buildroot}%{_initrddir}/%{name} 41 | 42 | #Install example config file 43 | %{__install} -p -D -m 0644 conf/%{name}.yml %{buildroot}%{_sysconfdir}/%{name}/%{name}.yml 44 | 45 | %post 46 | /sbin/chkconfig --add %{name} 47 | 48 | %preun 49 | if [ $1 = 0 ]; then 50 | /sbin/service %{name} stop > /dev/null 2>&1 51 | /sbin/chkconfig --del %{name} 52 | fi 53 | 54 | %clean 55 | [ %{buildroot} != "/" ] && rm -rf %{buildroot} 56 | 57 | %files 58 | %defattr(-,root,root,-) 59 | %if 0%{?rhel} >= 6 60 | /usr/sbin/nutcracker 61 | %else 62 | /usr/bin/nutcracker 63 | %endif 64 | %{_initrddir}/%{name} 65 | %{_mandir}/man8/nutcracker.8.gz 66 | %config(noreplace)%{_sysconfdir}/%{name}/%{name}.yml 67 | 68 | %changelog 69 | * Mon Jun 22 2015 Manju Rajashekhar 70 | - twemproxy: version 0.4.1 release 71 | - backend server hostnames are resolved lazily 72 | - redis_auth is only valid for a redis pool 73 | - getaddrinfo returns non-zero +ve value on error 74 | - fix-hang-when-command-only (charsyam) 75 | - fix bug crash when get command without key and whitespace (charsyam) 76 | - mark server as failed on protocol level transiet failures like -OOM, -LOADING, etc 77 | - implemented support for parsing fine grained redis error response 78 | - remove redundant conditional judgement in rbtree deletion (leo ma) 79 | - fix bug mset has invalid pair (charsyam) 80 | - fix bug mset has invalid pair (charsyam) 81 | - temp fix a core on kqueue (idning) 82 | - support "touch" command for memcached (panmiaocai) 83 | - fix redis parse rsp bug (charsyam) 84 | - SORT command can take multiple arguments. So it should be part of redis_argn() and not redis_arg0() 85 | - remove incorrect assert because client could send data after sending a quit request which must be discarded 86 | - allow file permissions to be set for UNIX domain listening socket (ori liveneh) 87 | - return error if formatted is greater than mbuf size by using nc_vsnprintf() in msg_prepend_format() 88 | - fix req_make_reply on msg_get, mark it as response (idning) 89 | - redis database select upon connect (arne claus) 90 | - redis_auth (charsyam) 91 | - allow null key(empty key) (idning) 92 | - fix core on invalid mset like "mset a a a" (idning) 93 | 94 | * Tue Oct 14 2014 idning 95 | - twemproxy: version 0.4.0 release 96 | - mget improve (idning) 97 | - many new commands supported: LEX, PFADD, PFMERGE, SORT, PING, QUIT, SCAN... (mattrobenolt, areina, idning) 98 | - handle max open file limit(allenlz) 99 | - add notice-log and use ms time in log(idning) 100 | - fix bug in string_compare (andyqzb) 101 | - fix deadlock in sighandler (idning) 102 | 103 | * Fri Dec 20 2013 Manju Rajashekhar 104 | - twemproxy: version 0.3.0 release 105 | - SRANDMEMBER support for the optional count argument (mkhq) 106 | - Handle case where server responds while the request is still being sent (jdi-tagged) 107 | - event ports (solaris/smartos) support 108 | - add timestamp when the server was ejected 109 | - support for set ex/px/nx/xx for redis 2.6.12 and up (ypocat) 110 | - kqueue (bsd) support (ferenyx) 111 | - fix parsing redis response to accept integer reply (charsyam) 112 | 113 | * Tue Jul 30 2013 Tait Clarridge 114 | - Rebuild SPEC to work with CentOS 115 | - Added buildrequires if building with mock/koji 116 | 117 | * Tue Apr 23 2013 Manju Rajashekhar 118 | - twemproxy: version 0.2.4 release 119 | - redis keys must be less than mbuf_data_size() in length (fifsky) 120 | - Adds support for DUMP/RESTORE commands in Redis (remotezygote) 121 | - Use of the weight value in the modula distribution (mezzatto) 122 | - Add support to unix socket connections to servers (mezzatto) 123 | - only check for duplicate server name and not 'host:port:weight' when 'name' is configured 124 | - crc16 hash support added (mezzatto) 125 | 126 | * Thu Jan 31 2013 Manju Rajashekhar 127 | - twemproxy: version 0.2.3 release 128 | - RPOPLPUSH, SDIFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION, SUNIONSTORE, ZINTERSTORE, and ZUNIONSTORE support (dcartoon) 129 | - EVAL and EVALSHA support (ferenyx) 130 | - exit 1 if configuration file is invalid (cofyc) 131 | - return non-zero exit status when nutcracker cannot start for some reason 132 | - use server names in stats (charsyam) 133 | - Fix failure to resolve long FQDN name resolve (conmame) 134 | - add support for hash tags 135 | 136 | * Thu Oct 18 2012 Manju Rajashekhar 137 | - twemproxy: version 0.2.2 release 138 | - fix the off-by-one error when calculating redis key length 139 | 140 | * Fri Oct 12 2012 Manju Rajashekhar 141 | - twemproxy: version 0.2.1 release 142 | - don't use buf in conf_add_server 143 | - allow an optional instance name for consistent hashing (charsyam) 144 | - add --stats-addr=S option 145 | - add stats-bind-any -a option (charsyam) 146 | -------------------------------------------------------------------------------- /scripts/pipelined_read.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | socatopt="-t 4 -T 4 -b 8193 -d -d " 4 | 5 | get_commands="" 6 | 7 | # build 8 | for i in `seq 1 128`; do 9 | if [ `expr $i % 2` -eq "0" ]; then 10 | key="foo" 11 | else 12 | key="bar" 13 | fi 14 | key=`printf "%s%d" "${key}" "${i}"` 15 | 16 | get_command="get ${key}\r\n" 17 | get_commands=`printf "%s%s" "${get_commands}" "${get_command}"` 18 | done 19 | 20 | # read 21 | for i in `seq 1 64`; do 22 | printf "%b" "$get_commands" | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-none,nonblock=1 1> /dev/null 2>&1 & 23 | done 24 | -------------------------------------------------------------------------------- /scripts/pipelined_write.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | socatopt="-t 1 -T 1 -b 16384" 4 | 5 | val=`echo 6^6^6 | bc` 6 | val=`printf "%s" "${val}"` 7 | vallen=`printf "%s" "${val}" | wc -c` 8 | set_command="" 9 | set_commands="" 10 | 11 | # build 12 | for i in `seq 1 64`; do 13 | if [ `expr $i % 2` -eq "0" ]; then 14 | key="foo" 15 | else 16 | key="bar" 17 | fi 18 | key=`printf "%s%d" "${key}" "${i}"` 19 | 20 | set_command="set ${key} 0 0 ${vallen}\r\n${val}\r\n" 21 | set_commands=`printf "%s%s" "${set_commands}" "${set_command}"` 22 | done 23 | 24 | printf "%b" "$set_commands" > /tmp/socat.input 25 | 26 | # write 27 | for i in `seq 1 16`; do 28 | cat /tmp/socat.input | socat ${socatopt} - TCP:localhost:22123,nodelay,shut-down,nonblock=1 & 29 | done 30 | -------------------------------------------------------------------------------- /scripts/populate_memcached.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | port=22123 4 | socatopt="-t 1 -T 1 -b 65537" 5 | 6 | val=`echo 6^6^6 | bc` 7 | val=`printf "%s\r\n" "${val}"` 8 | vallen=`printf "%s" "${val}" | wc -c` 9 | set_command="" 10 | 11 | # build 12 | for i in `seq 1 512`; do 13 | if [ `expr $i % 2` -eq "0" ]; then 14 | key="foo" 15 | else 16 | key="bar" 17 | fi 18 | key=`printf "%s%d" "${key}" "${i}"` 19 | 20 | set_command="set ${key} 0 0 ${vallen}\r\n${val}\r\n" 21 | 22 | printf "%b" "$set_command" | socat ${socatopt} - TCP:localhost:${port},nodelay,shut-down,nonblock=1 & 23 | done 24 | 25 | -------------------------------------------------------------------------------- /scripts/redis-check.py: -------------------------------------------------------------------------------- 1 | import redis 2 | 3 | range=100 4 | factor=32 5 | port=22121 6 | 7 | r = redis.StrictRedis(host='localhost', port=port, db=0) 8 | 9 | # lrange 10 | print [r.lrange('lfoo', 0, x) for x in xrange(1, range)] 11 | print [r.lpush('lfoo', str(x)*factor) for x in xrange(1, range)] 12 | print [r.lrange('lfoo', 0, x) for x in xrange(1, range)] 13 | print r.delete('lfoo') 14 | 15 | # del 16 | print [r.set('foo' + str(x), str(x)*factor) for x in xrange(1, range)] 17 | keys = ['foo' + str(x) for x in xrange(1, range)] 18 | print [r.delete(keys) for x in xrange(1, range)] 19 | 20 | # mget 21 | print [r.set('foo' + str(x), str(x)*100) for x in xrange(1, range)] 22 | keys = ['foo' + str(x) for x in xrange(1, range)] 23 | print [r.mget(keys) for x in xrange(1, range)] 24 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CPPFLAGS = 4 | if !OS_SOLARIS 5 | AM_CPPFLAGS += -D_GNU_SOURCE 6 | endif 7 | AM_CPPFLAGS += -I $(top_srcdir)/src/hashkit 8 | AM_CPPFLAGS += -I $(top_srcdir)/src/proto 9 | AM_CPPFLAGS += -I $(top_srcdir)/src/event 10 | AM_CPPFLAGS += -I $(top_srcdir)/contrib/yaml-0.1.4/include 11 | 12 | AM_CFLAGS = 13 | # about -fno-strict-aliasing: https://github.com/twitter/twemproxy/issues/276 14 | AM_CFLAGS += -fno-strict-aliasing 15 | AM_CFLAGS += -Wall -Wshadow 16 | AM_CFLAGS += -Wpointer-arith 17 | AM_CFLAGS += -Winline 18 | AM_CFLAGS += -Wunused-function -Wunused-variable -Wunused-value 19 | AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value 20 | AM_CFLAGS += -Wconversion -Wsign-compare 21 | AM_CFLAGS += -Wstrict-prototypes -Wmissing-prototypes -Wredundant-decls -Wmissing-declarations 22 | 23 | AM_LDFLAGS = 24 | AM_LDFLAGS += -lm -lpthread -rdynamic 25 | if OS_SOLARIS 26 | AM_LDFLAGS += -lnsl -lsocket 27 | endif 28 | if OS_FREEBSD 29 | AM_LDFLAGS += -lexecinfo 30 | endif 31 | 32 | SUBDIRS = hashkit proto event 33 | 34 | sbin_PROGRAMS = bilitw 35 | 36 | bilitw_SOURCES = \ 37 | nc_core.c nc_core.h \ 38 | nc_connection.c nc_connection.h \ 39 | nc_client.c nc_client.h \ 40 | nc_server.c nc_server.h \ 41 | nc_proxy.c nc_proxy.h \ 42 | nc_message.c nc_message.h \ 43 | nc_request.c \ 44 | nc_response.c \ 45 | nc_mbuf.c nc_mbuf.h \ 46 | nc_conf.c nc_conf.h \ 47 | nc_stats.c nc_stats.h \ 48 | nc_signal.c nc_signal.h \ 49 | nc_rbtree.c nc_rbtree.h \ 50 | nc_log.c nc_log.h \ 51 | nc_string.c nc_string.h \ 52 | nc_array.c nc_array.h \ 53 | nc_util.c nc_util.h \ 54 | nc_channel.c nc_channel.h \ 55 | nc_process.c nc_process.h \ 56 | nc_queue.h \ 57 | nc_bilibili.c 58 | 59 | bilitw_LDADD = $(top_builddir)/src/hashkit/libhashkit.a 60 | bilitw_LDADD += $(top_builddir)/src/proto/libproto.a 61 | bilitw_LDADD += $(top_builddir)/src/event/libevent.a 62 | bilitw_LDADD += $(top_builddir)/contrib/yaml-0.1.4/src/.libs/libyaml.a 63 | -------------------------------------------------------------------------------- /src/event/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CPPFLAGS = -I $(top_srcdir)/src 4 | 5 | AM_CFLAGS = -Wall -Wshadow 6 | AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value 7 | 8 | noinst_LIBRARIES = libevent.a 9 | 10 | noinst_HEADERS = nc_event.h 11 | 12 | libevent_a_SOURCES = \ 13 | nc_epoll.c \ 14 | nc_kqueue.c \ 15 | nc_evport.c 16 | 17 | -------------------------------------------------------------------------------- /src/event/nc_event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_EVENT_H_ 19 | #define _NC_EVENT_H_ 20 | 21 | #include 22 | 23 | #define EVENT_SIZE 1024 24 | 25 | #define EVENT_READ 0x0000ff 26 | #define EVENT_WRITE 0x00ff00 27 | #define EVENT_ERR 0xff0000 28 | 29 | typedef int (*event_cb_t)(void *, uint32_t); 30 | typedef void (*event_stats_cb_t)(void *, void *); 31 | 32 | #ifdef NC_HAVE_KQUEUE 33 | 34 | struct event_base { 35 | int kq; /* kernel event queue descriptor */ 36 | 37 | struct kevent *change; /* change[] - events we want to monitor */ 38 | int nchange; /* # change */ 39 | 40 | struct kevent *event; /* event[] - events that were triggered */ 41 | int nevent; /* # event */ 42 | int nreturned; /* # event placed in event[] */ 43 | int nprocessed; /* # event processed from event[] */ 44 | 45 | event_cb_t cb; /* event callback */ 46 | }; 47 | 48 | #elif NC_HAVE_EPOLL 49 | 50 | struct event_base { 51 | int ep; /* epoll descriptor */ 52 | 53 | struct epoll_event *event; /* event[] - events that were triggered */ 54 | int nevent; /* # event */ 55 | 56 | event_cb_t cb; /* event callback */ 57 | }; 58 | 59 | #elif NC_HAVE_EVENT_PORTS 60 | 61 | #include 62 | 63 | struct event_base { 64 | int evp; /* event port descriptor */ 65 | 66 | port_event_t *event; /* event[] - events that were triggered */ 67 | int nevent; /* # event */ 68 | 69 | event_cb_t cb; /* event callback */ 70 | }; 71 | 72 | #else 73 | # error missing scalable I/O event notification mechanism 74 | #endif 75 | 76 | struct event_base *event_base_create(int size, event_cb_t cb); 77 | void event_base_destroy(struct event_base *evb); 78 | 79 | int event_add_in(struct event_base *evb, struct conn *c); 80 | int event_del_in(struct event_base *evb, struct conn *c); 81 | int event_add_out(struct event_base *evb, struct conn *c); 82 | int event_del_out(struct event_base *evb, struct conn *c); 83 | int event_add_conn(struct event_base *evb, struct conn *c); 84 | int event_del_conn(struct event_base *evb, struct conn *c); 85 | 86 | 87 | 88 | int event_wait(struct event_base *evb, int timeout); 89 | void event_loop_stats(event_stats_cb_t cb, void *arg); 90 | void event_loop_stats_ext(event_stats_cb_t cb, void *arg); 91 | 92 | 93 | int event_add_channel(struct event_base *evb, nc_channel_msg_t* message); 94 | int event_del_channel(struct event_base *evb, nc_channel_msg_t* message); 95 | 96 | 97 | 98 | #endif /* _NC_EVENT_H */ 99 | -------------------------------------------------------------------------------- /src/hashkit/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CPPFLAGS = -I $(top_srcdir)/src 4 | 5 | AM_CFLAGS = -Wall -Wshadow 6 | AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value 7 | 8 | noinst_LIBRARIES = libhashkit.a 9 | 10 | noinst_HEADERS = nc_hashkit.h 11 | 12 | libhashkit_a_SOURCES = \ 13 | nc_crc16.c \ 14 | nc_crc32.c \ 15 | nc_fnv.c \ 16 | nc_hsieh.c \ 17 | nc_jenkins.c \ 18 | nc_ketama.c \ 19 | nc_md5.c \ 20 | nc_modula.c \ 21 | nc_murmur.c \ 22 | nc_one_at_a_time.c \ 23 | nc_random.c 24 | -------------------------------------------------------------------------------- /src/hashkit/nc_crc16.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | static const uint16_t crc16tab[256] = { 21 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 22 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 23 | 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 24 | 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 25 | 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 26 | 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 27 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 28 | 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 29 | 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 30 | 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 31 | 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 32 | 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 33 | 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 34 | 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 35 | 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 36 | 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 37 | 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 38 | 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 39 | 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 40 | 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 41 | 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 42 | 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 43 | 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 44 | 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 45 | 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 46 | 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 47 | 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 48 | 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 49 | 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 50 | 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 51 | 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 52 | 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, 53 | }; 54 | 55 | uint32_t 56 | hash_crc16(const char *key, size_t key_length) 57 | { 58 | uint64_t x; 59 | uint32_t crc = 0; 60 | 61 | for (x=0; x < key_length; x++) { 62 | crc = (crc << 8) ^ crc16tab[((crc >> 8) ^ *key++) & 0x00ff]; 63 | } 64 | 65 | return crc; 66 | } 67 | -------------------------------------------------------------------------------- /src/hashkit/nc_crc32.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * The crc32 functions and data was originally written by Spencer 20 | * Garrett and was gleaned from the PostgreSQL source 21 | * tree via the files contrib/ltree/crc32.[ch] and from FreeBSD at 22 | * src/usr.bin/cksum/crc32.c. 23 | */ 24 | 25 | #include 26 | 27 | static const uint32_t crc32tab[256] = { 28 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 29 | 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 30 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 31 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 32 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 33 | 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 34 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 35 | 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 36 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 37 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 38 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 39 | 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 40 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 41 | 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 42 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 43 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 44 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 45 | 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 46 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 47 | 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 48 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 49 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 50 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 51 | 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 52 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 53 | 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 54 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 55 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 56 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 57 | 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 58 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 59 | 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 60 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 61 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 62 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 63 | 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 64 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 65 | 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 66 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 67 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 68 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 69 | 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 70 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 71 | 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 72 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 73 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 74 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 75 | 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 76 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 77 | 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 78 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 79 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 80 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 81 | 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 82 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 83 | 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 84 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 85 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 86 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 87 | 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 88 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 89 | 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 90 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 91 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, 92 | }; 93 | 94 | /* 95 | * CRC-32 implementation compatible with libmemcached library. Unfortunately 96 | * this implementation does not return CRC-32 as per spec. 97 | */ 98 | uint32_t 99 | hash_crc32(const char *key, size_t key_length) 100 | { 101 | uint64_t x; 102 | uint32_t crc = UINT32_MAX; 103 | 104 | for (x = 0; x < key_length; x++) { 105 | crc = (crc >> 8) ^ crc32tab[(crc ^ (uint64_t)key[x]) & 0xff]; 106 | } 107 | 108 | return ((~crc) >> 16) & 0x7fff; 109 | } 110 | 111 | uint32_t 112 | hash_crc32a(const char *key, size_t key_length) 113 | { 114 | const uint8_t *p = key; 115 | uint32_t crc; 116 | 117 | crc = ~0U; 118 | while (key_length--) { 119 | crc = crc32tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8); 120 | } 121 | 122 | return crc ^ ~0U; 123 | } 124 | -------------------------------------------------------------------------------- /src/hashkit/nc_fnv.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | static uint64_t FNV_64_INIT = UINT64_C(0xcbf29ce484222325); 21 | static uint64_t FNV_64_PRIME = UINT64_C(0x100000001b3); 22 | static uint32_t FNV_32_INIT = 2166136261UL; 23 | static uint32_t FNV_32_PRIME = 16777619; 24 | 25 | uint32_t 26 | hash_fnv1_64(const char *key, size_t key_length) 27 | { 28 | uint64_t hash = FNV_64_INIT; 29 | size_t x; 30 | 31 | for (x = 0; x < key_length; x++) { 32 | hash *= FNV_64_PRIME; 33 | hash ^= (uint64_t)key[x]; 34 | } 35 | 36 | return (uint32_t)hash; 37 | } 38 | 39 | uint32_t 40 | hash_fnv1a_64(const char *key, size_t key_length) 41 | { 42 | uint32_t hash = (uint32_t) FNV_64_INIT; 43 | size_t x; 44 | 45 | for (x = 0; x < key_length; x++) { 46 | uint32_t val = (uint32_t)key[x]; 47 | hash ^= val; 48 | hash *= (uint32_t) FNV_64_PRIME; 49 | } 50 | 51 | return hash; 52 | } 53 | 54 | uint32_t 55 | hash_fnv1_32(const char *key, size_t key_length) 56 | { 57 | uint32_t hash = FNV_32_INIT; 58 | size_t x; 59 | 60 | for (x = 0; x < key_length; x++) { 61 | uint32_t val = (uint32_t)key[x]; 62 | hash *= FNV_32_PRIME; 63 | hash ^= val; 64 | } 65 | 66 | return hash; 67 | } 68 | 69 | uint32_t 70 | hash_fnv1a_32(const char *key, size_t key_length) 71 | { 72 | uint32_t hash = FNV_32_INIT; 73 | size_t x; 74 | 75 | for (x= 0; x < key_length; x++) { 76 | uint32_t val = (uint32_t)key[x]; 77 | hash ^= val; 78 | hash *= FNV_32_PRIME; 79 | } 80 | 81 | return hash; 82 | } 83 | -------------------------------------------------------------------------------- /src/hashkit/nc_hashkit.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_HASHKIT_H_ 19 | #define _NC_HASHKIT_H_ 20 | 21 | #include 22 | #include 23 | 24 | #define HASH_CODEC(ACTION) \ 25 | ACTION( HASH_ONE_AT_A_TIME, one_at_a_time ) \ 26 | ACTION( HASH_MD5, md5 ) \ 27 | ACTION( HASH_CRC16, crc16 ) \ 28 | ACTION( HASH_CRC32, crc32 ) \ 29 | ACTION( HASH_CRC32A, crc32a ) \ 30 | ACTION( HASH_FNV1_64, fnv1_64 ) \ 31 | ACTION( HASH_FNV1A_64, fnv1a_64 ) \ 32 | ACTION( HASH_FNV1_32, fnv1_32 ) \ 33 | ACTION( HASH_FNV1A_32, fnv1a_32 ) \ 34 | ACTION( HASH_HSIEH, hsieh ) \ 35 | ACTION( HASH_MURMUR, murmur ) \ 36 | ACTION( HASH_JENKINS, jenkins ) \ 37 | 38 | #define DIST_CODEC(ACTION) \ 39 | ACTION( DIST_KETAMA, ketama ) \ 40 | ACTION( DIST_MODULA, modula ) \ 41 | ACTION( DIST_RANDOM, random ) \ 42 | 43 | #define DEFINE_ACTION(_hash, _name) _hash, 44 | typedef enum hash_type { 45 | HASH_CODEC( DEFINE_ACTION ) 46 | HASH_SENTINEL 47 | } hash_type_t; 48 | #undef DEFINE_ACTION 49 | 50 | #define DEFINE_ACTION(_dist, _name) _dist, 51 | typedef enum dist_type { 52 | DIST_CODEC( DEFINE_ACTION ) 53 | DIST_SENTINEL 54 | } dist_type_t; 55 | #undef DEFINE_ACTION 56 | 57 | uint32_t hash_one_at_a_time(const char *key, size_t key_length); 58 | void md5_signature(const unsigned char *key, unsigned int length, unsigned char *result); 59 | uint32_t hash_md5(const char *key, size_t key_length); 60 | uint32_t hash_crc16(const char *key, size_t key_length); 61 | uint32_t hash_crc32(const char *key, size_t key_length); 62 | uint32_t hash_crc32a(const char *key, size_t key_length); 63 | uint32_t hash_fnv1_64(const char *key, size_t key_length); 64 | uint32_t hash_fnv1a_64(const char *key, size_t key_length); 65 | uint32_t hash_fnv1_32(const char *key, size_t key_length); 66 | uint32_t hash_fnv1a_32(const char *key, size_t key_length); 67 | uint32_t hash_hsieh(const char *key, size_t key_length); 68 | uint32_t hash_jenkins(const char *key, size_t length); 69 | uint32_t hash_murmur(const char *key, size_t length); 70 | 71 | rstatus_t ketama_update(struct server_pool *pool); 72 | uint32_t ketama_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); 73 | rstatus_t modula_update(struct server_pool *pool); 74 | uint32_t modula_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); 75 | rstatus_t random_update(struct server_pool *pool); 76 | uint32_t random_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash); 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /src/hashkit/nc_hsieh.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * By Paul Hsieh (C) 2004, 2005. Covered under the Paul Hsieh 20 | * derivative license. 21 | * See: http://www.azillionmonkeys.com/qed/weblicense.html for license 22 | * details. 23 | * http://www.azillionmonkeys.com/qed/hash.html 24 | */ 25 | 26 | #include 27 | 28 | #undef get16bits 29 | #if (defined(__GNUC__) && defined(__i386__)) 30 | #define get16bits(d) (*((const uint16_t *) (d))) 31 | #endif 32 | 33 | #if !defined (get16bits) 34 | #define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\ 35 | +(uint32_t)(((const uint8_t *)(d))[0]) ) 36 | #endif 37 | 38 | uint32_t 39 | hash_hsieh(const char *key, size_t key_length) 40 | { 41 | uint32_t hash = 0, tmp; 42 | int rem; 43 | 44 | if (key_length <= 0 || key == NULL) { 45 | return 0; 46 | } 47 | 48 | rem = key_length & 3; 49 | key_length >>= 2; 50 | 51 | /* Main loop */ 52 | for (;key_length > 0; key_length--) { 53 | hash += get16bits (key); 54 | tmp = (get16bits (key+2) << 11) ^ hash; 55 | hash = (hash << 16) ^ tmp; 56 | key += 2*sizeof (uint16_t); 57 | hash += hash >> 11; 58 | } 59 | 60 | /* Handle end cases */ 61 | switch (rem) { 62 | case 3: 63 | hash += get16bits (key); 64 | hash ^= hash << 16; 65 | hash ^= (uint32_t)key[sizeof (uint16_t)] << 18; 66 | hash += hash >> 11; 67 | break; 68 | 69 | case 2: 70 | hash += get16bits (key); 71 | hash ^= hash << 11; 72 | hash += hash >> 17; 73 | break; 74 | 75 | case 1: 76 | hash += (unsigned char)(*key); 77 | hash ^= hash << 10; 78 | hash += hash >> 1; 79 | 80 | default: 81 | break; 82 | } 83 | 84 | /* Force "avalanching" of final 127 bits */ 85 | hash ^= hash << 3; 86 | hash += hash >> 5; 87 | hash ^= hash << 4; 88 | hash += hash >> 17; 89 | hash ^= hash << 25; 90 | hash += hash >> 6; 91 | 92 | return hash; 93 | } 94 | -------------------------------------------------------------------------------- /src/hashkit/nc_modula.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #define MODULA_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ 26 | #define MODULA_POINTS_PER_SERVER 1 27 | 28 | rstatus_t 29 | modula_update(struct server_pool *pool) 30 | { 31 | uint32_t nserver; /* # server - live and dead */ 32 | uint32_t nlive_server; /* # live server */ 33 | uint32_t pointer_per_server; /* pointers per server proportional to weight */ 34 | uint32_t pointer_counter; /* # pointers on continuum */ 35 | uint32_t points_per_server; /* points per server */ 36 | uint32_t continuum_index; /* continuum index */ 37 | uint32_t continuum_addition; /* extra space in the continuum */ 38 | uint32_t server_index; /* server index */ 39 | uint32_t weight_index; /* weight index */ 40 | uint32_t total_weight; /* total live server weight */ 41 | int64_t now; /* current timestamp in usec */ 42 | 43 | now = nc_usec_now(); 44 | if (now < 0) { 45 | return NC_ERROR; 46 | } 47 | 48 | nserver = array_n(&pool->server); 49 | nlive_server = 0; 50 | total_weight = 0; 51 | pool->next_rebuild = 0LL; 52 | 53 | for (server_index = 0; server_index < nserver; server_index++) { 54 | struct server *server = array_get(&pool->server, server_index); 55 | 56 | if (pool->auto_eject_hosts) { 57 | if (server->next_retry <= now) { 58 | server->next_retry = 0LL; 59 | nlive_server++; 60 | } else if (pool->next_rebuild == 0LL || 61 | server->next_retry < pool->next_rebuild) { 62 | pool->next_rebuild = server->next_retry; 63 | } 64 | } else { 65 | nlive_server++; 66 | } 67 | 68 | ASSERT(server->weight > 0); 69 | 70 | /* count weight only for live servers */ 71 | if (!pool->auto_eject_hosts || server->next_retry <= now) { 72 | total_weight += server->weight; 73 | } 74 | } 75 | 76 | pool->nlive_server = nlive_server; 77 | 78 | if (nlive_server == 0) { 79 | ASSERT(pool->continuum != NULL); 80 | ASSERT(pool->ncontinuum != 0); 81 | 82 | log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", 83 | pool->idx, pool->name.len, pool->name.data); 84 | 85 | return NC_OK; 86 | } 87 | log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " 88 | "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, 89 | pool->name.len, pool->name.data); 90 | 91 | continuum_addition = MODULA_CONTINUUM_ADDITION; 92 | points_per_server = MODULA_POINTS_PER_SERVER; 93 | 94 | /* 95 | * Allocate the continuum for the pool, the first time, and every time we 96 | * add a new server to the pool 97 | */ 98 | if (total_weight > pool->nserver_continuum) { 99 | struct continuum *continuum; 100 | uint32_t nserver_continuum = total_weight + MODULA_CONTINUUM_ADDITION; 101 | uint32_t ncontinuum = nserver_continuum * MODULA_POINTS_PER_SERVER; 102 | 103 | continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); 104 | if (continuum == NULL) { 105 | return NC_ENOMEM; 106 | } 107 | 108 | pool->continuum = continuum; 109 | pool->nserver_continuum = nserver_continuum; 110 | /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ 111 | } 112 | 113 | /* update the continuum with the servers that are live */ 114 | continuum_index = 0; 115 | pointer_counter = 0; 116 | for (server_index = 0; server_index < nserver; server_index++) { 117 | struct server *server = array_get(&pool->server, server_index); 118 | 119 | if (pool->auto_eject_hosts && server->next_retry > now) { 120 | continue; 121 | } 122 | 123 | for (weight_index = 0; weight_index < server->weight; weight_index++) { 124 | pointer_per_server = 1; 125 | 126 | pool->continuum[continuum_index].index = server_index; 127 | pool->continuum[continuum_index++].value = 0; 128 | 129 | pointer_counter += pointer_per_server; 130 | } 131 | } 132 | pool->ncontinuum = pointer_counter; 133 | 134 | log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " 135 | "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " 136 | "active points in %"PRIu32" slots", pool->idx, 137 | pool->name.len, pool->name.data, nlive_server, nserver, 138 | pool->nserver_continuum, pool->ncontinuum, 139 | (pool->nserver_continuum + continuum_addition) * points_per_server); 140 | 141 | return NC_OK; 142 | 143 | } 144 | 145 | uint32_t 146 | modula_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) 147 | { 148 | struct continuum *c; 149 | 150 | ASSERT(continuum != NULL); 151 | ASSERT(ncontinuum != 0); 152 | 153 | c = continuum + hash % ncontinuum; 154 | 155 | return c->index; 156 | } 157 | -------------------------------------------------------------------------------- /src/hashkit/nc_murmur.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * "Murmur" hash provided by Austin, tanjent@gmail.com 20 | * http://murmurhash.googlepages.com/ 21 | * 22 | * Note - This code makes a few assumptions about how your machine behaves - 23 | * 24 | * 1. We can read a 4-byte value from any address without crashing 25 | * 2. sizeof(int) == 4 26 | * 27 | * And it has a few limitations - 28 | * 1. It will not work incrementally. 29 | * 2. It will not produce the same results on little-endian and big-endian 30 | * machines. 31 | * 32 | * Updated to murmur2 hash - BP 33 | */ 34 | 35 | #include 36 | 37 | uint32_t 38 | hash_murmur(const char *key, size_t length) 39 | { 40 | /* 41 | * 'm' and 'r' are mixing constants generated offline. They're not 42 | * really 'magic', they just happen to work well. 43 | */ 44 | 45 | const unsigned int m = 0x5bd1e995; 46 | const uint32_t seed = (0xdeadbeef * (uint32_t)length); 47 | const int r = 24; 48 | 49 | 50 | /* Initialize the hash to a 'random' value */ 51 | 52 | uint32_t h = seed ^ (uint32_t)length; 53 | 54 | /* Mix 4 bytes at a time into the hash */ 55 | 56 | const unsigned char * data = (const unsigned char *)key; 57 | 58 | while (length >= 4) { 59 | unsigned int k = *(unsigned int *)data; 60 | 61 | k *= m; 62 | k ^= k >> r; 63 | k *= m; 64 | 65 | h *= m; 66 | h ^= k; 67 | 68 | data += 4; 69 | length -= 4; 70 | } 71 | 72 | /* Handle the last few bytes of the input array */ 73 | 74 | switch(length) { 75 | case 3: 76 | h ^= ((uint32_t)data[2]) << 16; 77 | 78 | case 2: 79 | h ^= ((uint32_t)data[1]) << 8; 80 | 81 | case 1: 82 | h ^= data[0]; 83 | h *= m; 84 | 85 | default: 86 | break; 87 | }; 88 | 89 | /* 90 | * Do a few final mixes of the hash to ensure the last few bytes are 91 | * well-incorporated. 92 | */ 93 | 94 | h ^= h >> 13; 95 | h *= m; 96 | h ^= h >> 15; 97 | 98 | return h; 99 | } 100 | -------------------------------------------------------------------------------- /src/hashkit/nc_one_at_a_time.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* 19 | * HashKit 20 | * Copyright (C) 2009 Brian Aker 21 | * All rights reserved. 22 | * 23 | * Use and distribution licensed under the BSD license. See 24 | * the COPYING file in the parent directory for full text. 25 | */ 26 | 27 | /* 28 | * This has is Jenkin's "One at A time Hash". 29 | * http://en.wikipedia.org/wiki/Jenkins_hash_function 30 | */ 31 | 32 | #include 33 | 34 | uint32_t 35 | hash_one_at_a_time(const char *key, size_t key_length) 36 | { 37 | const char *ptr = key; 38 | uint32_t value = 0; 39 | 40 | while (key_length--) { 41 | uint32_t val = (uint32_t) *ptr++; 42 | value += val; 43 | value += (value << 10); 44 | value ^= (value >> 6); 45 | } 46 | value += (value << 3); 47 | value ^= (value >> 11); 48 | value += (value << 15); 49 | 50 | return value; 51 | } 52 | -------------------------------------------------------------------------------- /src/hashkit/nc_random.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #define RANDOM_CONTINUUM_ADDITION 10 /* # extra slots to build into continuum */ 26 | #define RANDOM_POINTS_PER_SERVER 1 27 | 28 | rstatus_t 29 | random_update(struct server_pool *pool) 30 | { 31 | uint32_t nserver; /* # server - live and dead */ 32 | uint32_t nlive_server; /* # live server */ 33 | uint32_t pointer_per_server; /* pointers per server proportional to weight */ 34 | uint32_t pointer_counter; /* # pointers on continuum */ 35 | uint32_t points_per_server; /* points per server */ 36 | uint32_t continuum_index; /* continuum index */ 37 | uint32_t continuum_addition; /* extra space in the continuum */ 38 | uint32_t server_index; /* server index */ 39 | int64_t now; /* current timestamp in usec */ 40 | 41 | now = nc_usec_now(); 42 | if (now < 0) { 43 | return NC_ERROR; 44 | } 45 | 46 | nserver = array_n(&pool->server); 47 | nlive_server = 0; 48 | pool->next_rebuild = 0LL; 49 | 50 | for (server_index = 0; server_index < nserver; server_index++) { 51 | struct server *server = array_get(&pool->server, server_index); 52 | 53 | if (pool->auto_eject_hosts) { 54 | if (server->next_retry <= now) { 55 | server->next_retry = 0LL; 56 | nlive_server++; 57 | } else if (pool->next_rebuild == 0LL || 58 | server->next_retry < pool->next_rebuild) { 59 | pool->next_rebuild = server->next_retry; 60 | } 61 | } else { 62 | nlive_server++; 63 | } 64 | } 65 | 66 | pool->nlive_server = nlive_server; 67 | 68 | if (nlive_server == 0) { 69 | ASSERT(pool->continuum != NULL); 70 | ASSERT(pool->ncontinuum != 0); 71 | 72 | log_debug(LOG_DEBUG, "no live servers for pool %"PRIu32" '%.*s'", 73 | pool->idx, pool->name.len, pool->name.data); 74 | 75 | return NC_OK; 76 | } 77 | log_debug(LOG_DEBUG, "%"PRIu32" of %"PRIu32" servers are live for pool " 78 | "%"PRIu32" '%.*s'", nlive_server, nserver, pool->idx, 79 | pool->name.len, pool->name.data); 80 | 81 | continuum_addition = RANDOM_CONTINUUM_ADDITION; 82 | points_per_server = RANDOM_POINTS_PER_SERVER; 83 | 84 | /* 85 | * Allocate the continuum for the pool, the first time, and every time we 86 | * add a new server to the pool 87 | */ 88 | if (nlive_server > pool->nserver_continuum) { 89 | struct continuum *continuum; 90 | uint32_t nserver_continuum = nlive_server + RANDOM_CONTINUUM_ADDITION; 91 | uint32_t ncontinuum = nserver_continuum * RANDOM_POINTS_PER_SERVER; 92 | 93 | continuum = nc_realloc(pool->continuum, sizeof(*continuum) * ncontinuum); 94 | if (continuum == NULL) { 95 | return NC_ENOMEM; 96 | } 97 | 98 | srandom((uint32_t)time(NULL)); 99 | 100 | pool->continuum = continuum; 101 | pool->nserver_continuum = nserver_continuum; 102 | /* pool->ncontinuum is initialized later as it could be <= ncontinuum */ 103 | } 104 | 105 | /* update the continuum with the servers that are live */ 106 | continuum_index = 0; 107 | pointer_counter = 0; 108 | for (server_index = 0; server_index < nserver; server_index++) { 109 | struct server *server = array_get(&pool->server, server_index); 110 | 111 | if (pool->auto_eject_hosts && server->next_retry > now) { 112 | continue; 113 | } 114 | 115 | pointer_per_server = 1; 116 | 117 | pool->continuum[continuum_index].index = server_index; 118 | pool->continuum[continuum_index++].value = 0; 119 | 120 | pointer_counter += pointer_per_server; 121 | } 122 | pool->ncontinuum = pointer_counter; 123 | 124 | log_debug(LOG_VERB, "updated pool %"PRIu32" '%.*s' with %"PRIu32" of " 125 | "%"PRIu32" servers live in %"PRIu32" slots and %"PRIu32" " 126 | "active points in %"PRIu32" slots", pool->idx, 127 | pool->name.len, pool->name.data, nlive_server, nserver, 128 | pool->nserver_continuum, pool->ncontinuum, 129 | (pool->nserver_continuum + continuum_addition) * points_per_server); 130 | 131 | return NC_OK; 132 | 133 | } 134 | 135 | uint32_t 136 | random_dispatch(struct continuum *continuum, uint32_t ncontinuum, uint32_t hash) 137 | { 138 | struct continuum *c; 139 | 140 | ASSERT(continuum != NULL); 141 | ASSERT(ncontinuum != 0); 142 | 143 | c = continuum + random() % ncontinuum; 144 | 145 | return c->index; 146 | } 147 | -------------------------------------------------------------------------------- /src/nc.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilibili/bilitw/a6c46feef6a0f41c15b11e96938658eec13d41f6/src/nc.c -------------------------------------------------------------------------------- /src/nc_array.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | 20 | #include 21 | 22 | struct array * 23 | array_create(uint32_t n, size_t size) 24 | { 25 | struct array *a; 26 | 27 | ASSERT(n != 0 && size != 0); 28 | 29 | a = nc_alloc(sizeof(*a)); 30 | if (a == NULL) { 31 | return NULL; 32 | } 33 | 34 | a->elem = nc_alloc(n * size); 35 | if (a->elem == NULL) { 36 | nc_free(a); 37 | return NULL; 38 | } 39 | 40 | a->nelem = 0; 41 | a->size = size; 42 | a->nalloc = n; 43 | 44 | return a; 45 | } 46 | 47 | void 48 | array_destroy(struct array *a) 49 | { 50 | array_deinit(a); 51 | nc_free(a); 52 | } 53 | 54 | rstatus_t 55 | array_init(struct array *a, uint32_t n, size_t size) 56 | { 57 | ASSERT(n != 0 && size != 0); 58 | 59 | a->elem = nc_alloc(n * size); 60 | if (a->elem == NULL) { 61 | return NC_ENOMEM; 62 | } 63 | 64 | a->nelem = 0; 65 | a->size = size; 66 | a->nalloc = n; 67 | 68 | return NC_OK; 69 | } 70 | 71 | void 72 | array_deinit(struct array *a) 73 | { 74 | ASSERT(a->nelem == 0); 75 | 76 | if (a->elem != NULL) { 77 | nc_free(a->elem); 78 | } 79 | } 80 | 81 | uint32_t 82 | array_idx(struct array *a, void *elem) 83 | { 84 | uint8_t *p, *q; 85 | uint32_t off, idx; 86 | 87 | ASSERT(elem >= a->elem); 88 | 89 | p = a->elem; 90 | q = elem; 91 | off = (uint32_t)(q - p); 92 | 93 | ASSERT(off % (uint32_t)a->size == 0); 94 | 95 | idx = off / (uint32_t)a->size; 96 | 97 | return idx; 98 | } 99 | 100 | void * 101 | array_push(struct array *a) 102 | { 103 | void *elem, *new; 104 | size_t size; 105 | 106 | if (a->nelem == a->nalloc) { 107 | 108 | /* the array is full; allocate new array */ 109 | size = a->size * a->nalloc; 110 | new = nc_realloc(a->elem, 2 * size); 111 | if (new == NULL) { 112 | return NULL; 113 | } 114 | 115 | a->elem = new; 116 | a->nalloc *= 2; 117 | } 118 | 119 | elem = (uint8_t *)a->elem + a->size * a->nelem; 120 | a->nelem++; 121 | 122 | return elem; 123 | } 124 | 125 | void * 126 | array_pop(struct array *a) 127 | { 128 | void *elem; 129 | 130 | ASSERT(a->nelem != 0); 131 | 132 | a->nelem--; 133 | elem = (uint8_t *)a->elem + a->size * a->nelem; 134 | 135 | return elem; 136 | } 137 | 138 | void * 139 | array_get(struct array *a, uint32_t idx) 140 | { 141 | void *elem; 142 | 143 | ASSERT(a->nelem != 0); 144 | ASSERT(idx < a->nelem); 145 | 146 | elem = (uint8_t *)a->elem + (a->size * idx); 147 | 148 | return elem; 149 | } 150 | 151 | void * 152 | array_top(struct array *a) 153 | { 154 | ASSERT(a->nelem != 0); 155 | 156 | return array_get(a, a->nelem - 1); 157 | } 158 | 159 | void 160 | array_swap(struct array *a, struct array *b) 161 | { 162 | struct array tmp; 163 | 164 | tmp = *a; 165 | *a = *b; 166 | *b = tmp; 167 | } 168 | 169 | /* 170 | * Sort nelem elements of the array in ascending order based on the 171 | * compare comparator. 172 | */ 173 | void 174 | array_sort(struct array *a, array_compare_t compare) 175 | { 176 | ASSERT(a->nelem != 0); 177 | 178 | qsort(a->elem, a->nelem, a->size, compare); 179 | } 180 | 181 | /* 182 | * Calls the func once for each element in the array as long as func returns 183 | * success. On failure short-circuits and returns the error status. 184 | */ 185 | rstatus_t 186 | array_each(struct array *a, array_each_t func, void *data) 187 | { 188 | uint32_t i, nelem; 189 | 190 | ASSERT(array_n(a) != 0); 191 | ASSERT(func != NULL); 192 | 193 | for (i = 0, nelem = array_n(a); i < nelem; i++) { 194 | void *elem = array_get(a, i); 195 | rstatus_t status; 196 | 197 | status = func(elem, data); 198 | if (status != NC_OK) { 199 | return status; 200 | } 201 | } 202 | 203 | return NC_OK; 204 | } 205 | -------------------------------------------------------------------------------- /src/nc_array.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_ARRAY_H_ 19 | #define _NC_ARRAY_H_ 20 | 21 | #include 22 | 23 | typedef int (*array_compare_t)(const void *, const void *); 24 | typedef rstatus_t (*array_each_t)(void *, void *); 25 | 26 | struct array { 27 | uint32_t nelem; /* # element */ 28 | void *elem; /* element */ 29 | size_t size; /* element size */ 30 | uint32_t nalloc; /* # allocated element */ 31 | }; 32 | 33 | #define null_array { 0, NULL, 0, 0 } 34 | 35 | static inline void 36 | array_null(struct array *a) 37 | { 38 | a->nelem = 0; 39 | a->elem = NULL; 40 | a->size = 0; 41 | a->nalloc = 0; 42 | } 43 | 44 | static inline void 45 | array_set(struct array *a, void *elem, size_t size, uint32_t nalloc) 46 | { 47 | a->nelem = 0; 48 | a->elem = elem; 49 | a->size = size; 50 | a->nalloc = nalloc; 51 | } 52 | 53 | static inline uint32_t 54 | array_n(const struct array *a) 55 | { 56 | return a->nelem; 57 | } 58 | 59 | struct array *array_create(uint32_t n, size_t size); 60 | void array_destroy(struct array *a); 61 | rstatus_t array_init(struct array *a, uint32_t n, size_t size); 62 | void array_deinit(struct array *a); 63 | 64 | uint32_t array_idx(struct array *a, void *elem); 65 | void *array_push(struct array *a); 66 | void *array_pop(struct array *a); 67 | void *array_get(struct array *a, uint32_t idx); 68 | void *array_top(struct array *a); 69 | void array_swap(struct array *a, struct array *b); 70 | void array_sort(struct array *a, array_compare_t compare); 71 | rstatus_t array_each(struct array *a, array_each_t func, void *data); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /src/nc_channel.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _NC_CHANNEL_H_INCLUDED_ 3 | #define _NC_CHANNEL_H_INCLUDED_ 4 | 5 | 6 | #define CHANNEL_BUFFER_MAX_LENGTH 1024 7 | 8 | typedef pid_t nc_pid_t; 9 | typedef struct { 10 | int command; 11 | nc_pid_t pid; 12 | int slot; 13 | int fd; 14 | int len; 15 | } nc_channel_msg_t; 16 | 17 | /* 18 | typedef struct { 19 | int command; 20 | nc_pid_t pid; 21 | } nc_channel_msg_t;*/ 22 | 23 | int nc_write_channel(int s, nc_channel_msg_t *message, size_t size); 24 | int nc_read_channel(int s, nc_channel_msg_t *message, size_t size); 25 | void nc_close_channel(int *fd); 26 | void nc_signal_worker_processes(int signo); 27 | 28 | 29 | #endif /* _NC_CHANNEL_H_INCLUDED_ */ 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/nc_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | void 23 | client_ref(struct conn *conn, void *owner) 24 | { 25 | struct server_pool *pool = owner; 26 | 27 | ASSERT(conn->client && !conn->proxy); 28 | ASSERT(conn->owner == NULL); 29 | 30 | /* 31 | * We use null pointer as the sockaddr argument in the accept() call as 32 | * we are not interested in the address of the peer for the accepted 33 | * connection 34 | */ 35 | conn->family = 0; 36 | conn->addrlen = 0; 37 | conn->addr = NULL; 38 | 39 | pool->nc_conn_q++; 40 | TAILQ_INSERT_TAIL(&pool->c_conn_q, conn, conn_tqe); 41 | 42 | /* owner of the client connection is the server pool */ 43 | conn->owner = owner; 44 | 45 | log_debug(LOG_VVERB, "ref conn %p owner %p into pool '%.*s'", conn, pool, 46 | pool->name.len, pool->name.data); 47 | } 48 | 49 | void 50 | client_unref(struct conn *conn) 51 | { 52 | struct server_pool *pool; 53 | 54 | ASSERT(conn->client && !conn->proxy); 55 | ASSERT(conn->owner != NULL); 56 | 57 | pool = conn->owner; 58 | conn->owner = NULL; 59 | 60 | ASSERT(pool->nc_conn_q != 0); 61 | pool->nc_conn_q--; 62 | TAILQ_REMOVE(&pool->c_conn_q, conn, conn_tqe); 63 | 64 | log_debug(LOG_VVERB, "unref conn %p owner %p from pool '%.*s'", conn, 65 | pool, pool->name.len, pool->name.data); 66 | } 67 | 68 | bool 69 | client_active(struct conn *conn) 70 | { 71 | ASSERT(conn->client && !conn->proxy); 72 | 73 | ASSERT(TAILQ_EMPTY(&conn->imsg_q)); 74 | 75 | if (!TAILQ_EMPTY(&conn->omsg_q)) { 76 | log_debug(LOG_VVERB, "c %d is active", conn->sd); 77 | return true; 78 | } 79 | 80 | if (conn->rmsg != NULL) { 81 | log_debug(LOG_VVERB, "c %d is active", conn->sd); 82 | return true; 83 | } 84 | 85 | if (conn->smsg != NULL) { 86 | log_debug(LOG_VVERB, "c %d is active", conn->sd); 87 | return true; 88 | } 89 | 90 | log_debug(LOG_VVERB, "c %d is inactive", conn->sd); 91 | 92 | return false; 93 | } 94 | 95 | static void 96 | client_close_stats(struct context *ctx, struct server_pool *pool, err_t err, 97 | unsigned eof) 98 | { 99 | stats_pool_decr(ctx, pool, client_connections); 100 | 101 | if (eof) { 102 | stats_pool_incr(ctx, pool, client_eof); 103 | return; 104 | } 105 | 106 | switch (err) { 107 | case EPIPE: 108 | case ETIMEDOUT: 109 | case ECONNRESET: 110 | case ECONNABORTED: 111 | case ENOTCONN: 112 | case ENETDOWN: 113 | case ENETUNREACH: 114 | case EHOSTDOWN: 115 | case EHOSTUNREACH: 116 | default: 117 | stats_pool_incr(ctx, pool, client_err); 118 | break; 119 | } 120 | } 121 | 122 | void 123 | client_close(struct context *ctx, struct conn *conn) 124 | { 125 | rstatus_t status; 126 | struct msg *msg, *nmsg; /* current and next message */ 127 | 128 | ASSERT(conn->client && !conn->proxy); 129 | 130 | client_close_stats(ctx, conn->owner, conn->err, conn->eof); 131 | 132 | if (conn->sd < 0) { 133 | conn->unref(conn); 134 | conn_put(conn); 135 | return; 136 | } 137 | 138 | msg = conn->rmsg; 139 | if (msg != NULL) { 140 | conn->rmsg = NULL; 141 | 142 | ASSERT(msg->peer == NULL); 143 | ASSERT(msg->request && !msg->done); 144 | 145 | log_debug(LOG_INFO, "close c %d discarding pending req %"PRIu64" len " 146 | "%"PRIu32" type %d", conn->sd, msg->id, msg->mlen, 147 | msg->type); 148 | 149 | req_put(msg); 150 | } 151 | 152 | ASSERT(conn->smsg == NULL); 153 | ASSERT(TAILQ_EMPTY(&conn->imsg_q)); 154 | 155 | for (msg = TAILQ_FIRST(&conn->omsg_q); msg != NULL; msg = nmsg) { 156 | nmsg = TAILQ_NEXT(msg, c_tqe); 157 | 158 | /* dequeue the message (request) from client outq */ 159 | conn->dequeue_outq(ctx, conn, msg); 160 | 161 | if (msg->done) { 162 | log_debug(LOG_INFO, "close c %d discarding %s req %"PRIu64" len " 163 | "%"PRIu32" type %d", conn->sd, 164 | msg->error ? "error": "completed", msg->id, msg->mlen, 165 | msg->type); 166 | req_put(msg); 167 | } else { 168 | msg->swallow = 1; 169 | 170 | ASSERT(msg->request); 171 | ASSERT(msg->peer == NULL); 172 | 173 | log_debug(LOG_INFO, "close c %d schedule swallow of req %"PRIu64" " 174 | "len %"PRIu32" type %d", conn->sd, msg->id, msg->mlen, 175 | msg->type); 176 | } 177 | } 178 | ASSERT(TAILQ_EMPTY(&conn->omsg_q)); 179 | 180 | conn->unref(conn); 181 | 182 | status = close(conn->sd); 183 | if (status < 0) { 184 | log_error("close c %d failed, ignored: %s", conn->sd, strerror(errno)); 185 | } 186 | conn->sd = -1; 187 | 188 | conn_put(conn); 189 | } 190 | -------------------------------------------------------------------------------- /src/nc_client.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_CLIENT_H_ 19 | #define _NC_CLIENT_H_ 20 | 21 | #include 22 | 23 | bool client_active(struct conn *conn); 24 | void client_ref(struct conn *conn, void *owner); 25 | void client_unref(struct conn *conn); 26 | void client_close(struct context *ctx, struct conn *conn); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/nc_conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_CONF_H_ 19 | #define _NC_CONF_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | 30 | #define NC_STATS_PATH "/tmp/twstats.log" 31 | 32 | 33 | #define CONF_OK (void *) NULL 34 | #define CONF_ERROR (void *) "has an invalid value" 35 | 36 | #define CONF_ROOT_DEPTH 1 37 | #define CONF_MAX_DEPTH CONF_ROOT_DEPTH + 1 38 | 39 | #define CONF_DEFAULT_ARGS 3 40 | #define CONF_DEFAULT_POOL 8 41 | #define CONF_DEFAULT_SERVERS 8 42 | 43 | #define CONF_UNSET_NUM -1 44 | #define CONF_UNSET_PTR NULL 45 | #define CONF_UNSET_HASH (hash_type_t) -1 46 | #define CONF_UNSET_DIST (dist_type_t) -1 47 | 48 | #define CONF_DEFAULT_HASH HASH_FNV1A_64 49 | #define CONF_DEFAULT_DIST DIST_KETAMA 50 | #define CONF_DEFAULT_TIMEOUT -1 51 | #define CONF_DEFAULT_LISTEN_BACKLOG 65535 52 | #define CONF_DEFAULT_CLIENT_CONNECTIONS 0 53 | #define CONF_DEFAULT_REDIS false 54 | #define CONF_DEFAULT_REDIS_DB 0 55 | #define CONF_DEFAULT_PRECONNECT false 56 | #define CONF_DEFAULT_AUTO_EJECT_HOSTS false 57 | #define CONF_DEFAULT_SERVER_RETRY_TIMEOUT 30 * 1000 /* in msec */ 58 | #define CONF_DEFAULT_SERVER_FAILURE_LIMIT 2 59 | #define CONF_DEFAULT_SERVER_CONNECTIONS 1 60 | #define CONF_DEFAULT_KETAMA_PORT 11211 61 | #define CONF_DEFAULT_TCPKEEPALIVE false 62 | 63 | 64 | // 65 | #define CONF_CHANGE_ADD 1 66 | #define CONF_CHANGE_DEL 2 67 | #define CONF_CHANGE_MOD 3 68 | 69 | struct conf_listen { 70 | struct string pname; /* listen: as "hostname:port" */ 71 | struct string name; /* hostname:port */ 72 | int port; /* port */ 73 | mode_t perm; /* socket permissions */ 74 | struct sockinfo info; /* listen socket info */ 75 | unsigned valid:1; /* valid? */ 76 | }; 77 | 78 | struct conf_server { 79 | struct string pname; /* server: as "hostname:port:weight" */ 80 | struct string name; /* hostname:port or [name] */ 81 | struct string addrstr; /* hostname */ 82 | int port; /* port */ 83 | int weight; /* weight */ 84 | struct sockinfo info; /* connect socket info */ 85 | unsigned valid:1; /* valid? */ 86 | 87 | // 88 | int change; 89 | }; 90 | 91 | struct conf_pool { 92 | struct string name; /* pool name (root node) */ 93 | struct conf_listen listen; /* listen: */ 94 | hash_type_t hash; /* hash: */ 95 | struct string hash_tag; /* hash_tag: */ 96 | dist_type_t distribution; /* distribution: */ 97 | int timeout; /* timeout: */ 98 | int backlog; /* backlog: */ 99 | int client_connections; /* client_connections: */ 100 | int tcpkeepalive; /* tcpkeepalive: */ 101 | int redis; /* redis: */ 102 | struct string redis_auth; /* redis_auth: redis auth password (matches requirepass on redis) */ 103 | int redis_db; /* redis_db: redis db */ 104 | int preconnect; /* preconnect: */ 105 | int auto_eject_hosts; /* auto_eject_hosts: */ 106 | int server_connections; /* server_connections: */ 107 | int server_retry_timeout; /* server_retry_timeout: in msec */ 108 | int server_failure_limit; /* server_failure_limit: */ 109 | 110 | int slow_req_duration; /* slow_req_duration: */ 111 | 112 | struct array server; /* servers: conf_server[] */ 113 | unsigned valid:1; /* valid? */ 114 | 115 | // 116 | int change; 117 | }; 118 | 119 | struct conf { 120 | char *fname; /* file name (ref in argv[]) */ 121 | FILE *fh; /* file handle */ 122 | struct array arg; /* string[] (parsed {key, value} pairs) */ 123 | struct array pool; /* conf_pool[] (parsed pools) */ 124 | uint32_t depth; /* parsed tree depth */ 125 | yaml_parser_t parser; /* yaml parser */ 126 | yaml_event_t event; /* yaml event */ 127 | yaml_token_t token; /* yaml token */ 128 | unsigned seq:1; /* sequence? */ 129 | unsigned valid_parser:1; /* valid parser? */ 130 | unsigned valid_event:1; /* valid event? */ 131 | unsigned valid_token:1; /* valid token? */ 132 | unsigned sound:1; /* sound? */ 133 | unsigned parsed:1; /* parsed? */ 134 | unsigned valid:1; /* valid? */ 135 | 136 | // 137 | uint64_t reload_timeout; 138 | uint64_t slow_req_duration; 139 | uint64_t stats_duration; /* msecs to printout stats */ 140 | char *stats_file; 141 | // todo, cpu stuff 142 | 143 | }; 144 | 145 | struct command { 146 | struct string name; 147 | char *(*set)(struct conf *cf, struct command *cmd, void *data); 148 | int offset; 149 | }; 150 | 151 | #define null_command { null_string, NULL, 0 } 152 | 153 | char *conf_set_string(struct conf *cf, struct command *cmd, void *conf); 154 | char *conf_set_listen(struct conf *cf, struct command *cmd, void *conf); 155 | char *conf_add_server(struct conf *cf, struct command *cmd, void *conf); 156 | char *conf_set_num(struct conf *cf, struct command *cmd, void *conf); 157 | char *conf_set_bool(struct conf *cf, struct command *cmd, void *conf); 158 | char *conf_set_hash(struct conf *cf, struct command *cmd, void *conf); 159 | char *conf_set_distribution(struct conf *cf, struct command *cmd, void *conf); 160 | char *conf_set_hashtag(struct conf *cf, struct command *cmd, void *conf); 161 | 162 | rstatus_t conf_server_each_transform(void *elem, void *data); 163 | rstatus_t conf_pool_each_transform(void *elem, void *data); 164 | 165 | struct conf *conf_create(char *filename); 166 | void conf_destroy(struct conf *cf); 167 | 168 | 169 | rstatus_t conf_pool_master(void *elem, void *data); 170 | 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/nc_connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_CONNECTION_H_ 19 | #define _NC_CONNECTION_H_ 20 | 21 | #include 22 | 23 | typedef rstatus_t (*conn_recv_t)(struct context *, struct conn*); 24 | typedef struct msg* (*conn_recv_next_t)(struct context *, struct conn *, bool); 25 | typedef void (*conn_recv_done_t)(struct context *, struct conn *, struct msg *, struct msg *); 26 | 27 | typedef rstatus_t (*conn_send_t)(struct context *, struct conn*); 28 | typedef struct msg* (*conn_send_next_t)(struct context *, struct conn *); 29 | typedef void (*conn_send_done_t)(struct context *, struct conn *, struct msg *); 30 | 31 | typedef void (*conn_close_t)(struct context *, struct conn *); 32 | typedef bool (*conn_active_t)(struct conn *); 33 | 34 | typedef void (*conn_ref_t)(struct conn *, void *); 35 | typedef void (*conn_unref_t)(struct conn *); 36 | 37 | typedef void (*conn_msgq_t)(struct context *, struct conn *, struct msg *); 38 | typedef void (*conn_post_connect_t)(struct context *ctx, struct conn *, struct server *server); 39 | typedef void (*conn_swallow_msg_t)(struct conn *, struct msg *, struct msg *); 40 | 41 | struct conn { 42 | TAILQ_ENTRY(conn) conn_tqe; /* link in server_pool / server / free q */ 43 | void *owner; /* connection owner - server_pool / server */ 44 | 45 | int sd; /* socket descriptor */ 46 | int family; /* socket address family */ 47 | socklen_t addrlen; /* socket length */ 48 | struct sockaddr *addr; /* socket address (ref in server or server_pool) */ 49 | 50 | struct msg_tqh imsg_q; /* incoming request Q */ 51 | struct msg_tqh omsg_q; /* outstanding request Q */ 52 | struct msg *rmsg; /* current message being rcvd */ 53 | struct msg *smsg; /* current message being sent */ 54 | 55 | conn_recv_t recv; /* recv (read) handler */ 56 | conn_recv_next_t recv_next; /* recv next message handler */ 57 | conn_recv_done_t recv_done; /* read done handler */ 58 | conn_send_t send; /* send (write) handler */ 59 | conn_send_next_t send_next; /* write next message handler */ 60 | conn_send_done_t send_done; /* write done handler */ 61 | conn_close_t close; /* close handler */ 62 | conn_active_t active; /* active? handler */ 63 | conn_post_connect_t post_connect; /* post connect handler */ 64 | conn_swallow_msg_t swallow_msg; /* react on messages to be swallowed */ 65 | 66 | conn_ref_t ref; /* connection reference handler */ 67 | conn_unref_t unref; /* connection unreference handler */ 68 | 69 | conn_msgq_t enqueue_inq; /* connection inq msg enqueue handler */ 70 | conn_msgq_t dequeue_inq; /* connection inq msg dequeue handler */ 71 | conn_msgq_t enqueue_outq; /* connection outq msg enqueue handler */ 72 | conn_msgq_t dequeue_outq; /* connection outq msg dequeue handler */ 73 | 74 | size_t recv_bytes; /* received (read) bytes */ 75 | size_t send_bytes; /* sent (written) bytes */ 76 | 77 | uint32_t events; /* connection io events */ 78 | err_t err; /* connection errno */ 79 | unsigned recv_active:1; /* recv active? */ 80 | unsigned recv_ready:1; /* recv ready? */ 81 | unsigned send_active:1; /* send active? */ 82 | unsigned send_ready:1; /* send ready? */ 83 | 84 | unsigned client:1; /* client? or server? */ 85 | unsigned proxy:1; /* proxy? */ 86 | unsigned connecting:1; /* connecting? */ 87 | unsigned connected:1; /* connected? */ 88 | unsigned eof:1; /* eof? aka passive close? */ 89 | unsigned done:1; /* done? aka close? */ 90 | unsigned redis:1; /* redis? */ 91 | unsigned authenticated:1; /* authenticated? */ 92 | }; 93 | 94 | TAILQ_HEAD(conn_tqh, conn); 95 | 96 | struct context *conn_to_ctx(struct conn *conn); 97 | struct conn *conn_get(void *owner, bool client, bool redis); 98 | struct conn *conn_get_proxy(void *owner); 99 | void conn_put(struct conn *conn); 100 | ssize_t conn_recv(struct conn *conn, void *buf, size_t size); 101 | ssize_t conn_sendv(struct conn *conn, struct array *sendv, size_t nsend); 102 | void conn_init(void); 103 | void conn_deinit(void); 104 | uint32_t conn_ncurr_conn(void); 105 | uint64_t conn_ntotal_conn(void); 106 | uint32_t conn_ncurr_cconn(void); 107 | bool conn_authenticated(struct conn *conn); 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /src/nc_core.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_CORE_H_ 19 | #define _NC_CORE_H_ 20 | 21 | #ifdef HAVE_CONFIG_H 22 | # include 23 | #endif 24 | 25 | #ifdef HAVE_DEBUG_LOG 26 | # define NC_DEBUG_LOG 1 27 | #endif 28 | 29 | #ifdef HAVE_ASSERT_PANIC 30 | # define NC_ASSERT_PANIC 1 31 | #endif 32 | 33 | #ifdef HAVE_ASSERT_LOG 34 | # define NC_ASSERT_LOG 1 35 | #endif 36 | 37 | #ifdef HAVE_STATS 38 | # define NC_STATS 1 39 | #else 40 | # define NC_STATS 0 41 | #endif 42 | 43 | #ifdef HAVE_EPOLL 44 | # define NC_HAVE_EPOLL 1 45 | #elif HAVE_KQUEUE 46 | # define NC_HAVE_KQUEUE 1 47 | #elif HAVE_EVENT_PORTS 48 | # define NC_HAVE_EVENT_PORTS 1 49 | #else 50 | # error missing scalable I/O event notification mechanism 51 | #endif 52 | 53 | #ifdef HAVE_LITTLE_ENDIAN 54 | # define NC_LITTLE_ENDIAN 1 55 | #endif 56 | 57 | #ifdef HAVE_BACKTRACE 58 | # define NC_HAVE_BACKTRACE 1 59 | #endif 60 | 61 | #define NC_OK 0 62 | #define NC_ERROR -1 63 | #define NC_EAGAIN -2 64 | #define NC_ENOMEM -3 65 | 66 | /* reserved fds for std streams, log, stats fd, epoll etc. */ 67 | #define RESERVED_FDS 32 68 | 69 | typedef int rstatus_t; /* return type */ 70 | typedef int err_t; /* error type */ 71 | 72 | struct array; 73 | struct string; 74 | struct context; 75 | struct conn; 76 | struct conn_tqh; 77 | struct msg; 78 | struct msg_tqh; 79 | struct server; 80 | struct server_pool; 81 | struct mbuf; 82 | struct mhdr; 83 | struct conf; 84 | struct stats; 85 | struct instance; 86 | struct event_base; 87 | 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | #include 98 | #include 99 | #include 100 | 101 | #include 102 | #include 103 | #include 104 | #include 105 | #include 106 | #include 107 | #include 108 | 109 | 110 | #include 111 | #include 112 | #include 113 | #include 114 | #include 115 | #include 116 | #include 117 | #include 118 | #include 119 | #include 120 | #include 121 | #include 122 | #include 123 | #include 124 | #include 125 | 126 | 127 | extern cpu_set_t cpu_mask; 128 | extern int cpu_mask_group[]; 129 | 130 | 131 | struct context { 132 | uint32_t id; /* unique context id */ 133 | struct conf *cf; /* configuration */ 134 | struct stats *stats; /* stats */ 135 | 136 | struct array pool; /* server_pool[] */ 137 | struct event_base *evb; /* event base */ 138 | int max_timeout; /* max timeout in msec */ 139 | int timeout; /* timeout in msec */ 140 | 141 | uint32_t max_nfd; /* max # files */ 142 | uint32_t max_ncconn; /* max # client connections */ 143 | uint32_t max_nsconn; /* max # server connections */ 144 | 145 | 146 | #ifdef GRACEFUL 147 | struct array listen_conns; 148 | #endif 149 | 150 | 151 | }; 152 | 153 | 154 | struct env_master { 155 | struct context *ctx; /* active context */ 156 | int log_level; /* log level */ 157 | char *log_filename; /* log filename */ 158 | char *conf_filename; /* configuration filename */ 159 | uint16_t stats_port; /* stats monitoring port */ 160 | int stats_interval; /* stats aggregation interval */ 161 | char *stats_addr; /* stats monitoring addr */ 162 | char hostname[NC_MAXHOSTNAMELEN]; /* hostname */ 163 | size_t mbuf_chunk_size; /* mbuf chunk size */ 164 | pid_t pid; /* process id */ 165 | char *pid_filename; /* pid filename */ 166 | unsigned pidfile:1; /* pid file created? */ 167 | 168 | int worker_processes; 169 | uint64_t cpu_mask; 170 | struct array pool; /* server_pool[] */ 171 | nc_channel_msg_t ctrl_msg; /* for master-worker control message */ 172 | 173 | 174 | // 175 | int64_t reload_timeout; 176 | int64_t slow_req_duration; 177 | int64_t stats_duration; /* msecs to printout stats */ 178 | int64_t reload_time; 179 | 180 | int stats_fd; 181 | // todo, cpu stuff 182 | 183 | }; 184 | 185 | 186 | struct instance { 187 | struct context *ctx; /* active context */ 188 | int log_level; /* log level */ 189 | char *log_filename; /* log filename */ 190 | char *conf_filename; /* configuration filename */ 191 | uint16_t stats_port; /* stats monitoring port */ 192 | int stats_interval; /* stats aggregation interval */ 193 | char *stats_addr; /* stats monitoring addr */ 194 | char hostname[NC_MAXHOSTNAMELEN]; /* hostname */ 195 | size_t mbuf_chunk_size; /* mbuf chunk size */ 196 | pid_t pid; /* process id */ 197 | char *pid_filename; /* pid filename */ 198 | unsigned pidfile:1; /* pid file created? */ 199 | }; 200 | 201 | struct context *core_start(struct instance *nci); 202 | void core_stop(struct context *ctx); 203 | rstatus_t core_core(void *arg, uint32_t events); 204 | rstatus_t core_loop(struct context *ctx); 205 | 206 | #endif 207 | -------------------------------------------------------------------------------- /src/nc_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_LOG_H_ 19 | #define _NC_LOG_H_ 20 | 21 | struct logger { 22 | char *name; /* log file name */ 23 | int level; /* log level */ 24 | int fd; /* log file descriptor */ 25 | int nerror; /* # log error */ 26 | }; 27 | 28 | #define LOG_EMERG 0 /* system in unusable */ 29 | #define LOG_ALERT 1 /* action must be taken immediately */ 30 | #define LOG_CRIT 2 /* critical conditions */ 31 | #define LOG_ERR 3 /* error conditions */ 32 | #define LOG_WARN 4 /* warning conditions */ 33 | #define LOG_NOTICE 5 /* normal but significant condition (default) */ 34 | #define LOG_INFO 6 /* informational */ 35 | #define LOG_DEBUG 7 /* debug messages */ 36 | #define LOG_VERB 8 /* verbose messages */ 37 | #define LOG_VVERB 9 /* verbose messages on crack */ 38 | #define LOG_VVVERB 10 /* verbose messages on ganga */ 39 | #define LOG_PVERB 11 /* periodic verbose messages on crack */ 40 | 41 | #define LOG_MAX_LEN 256 /* max length of log message */ 42 | 43 | /* 44 | * log_stderr - log to stderr 45 | * loga - log always 46 | * loga_hexdump - log hexdump always 47 | * log_error - error log messages 48 | * log_warn - warning log messages 49 | * log_panic - log messages followed by a panic 50 | * ... 51 | * log_debug - debug log messages based on a log level 52 | * log_hexdump - hexadump -C of a log buffer 53 | */ 54 | #ifdef NC_DEBUG_LOG 55 | 56 | #define log_debug(_level, ...) do { \ 57 | if (log_loggable(_level) != 0) { \ 58 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 59 | } \ 60 | } while (0) 61 | 62 | #define log_hexdump(_level, _data, _datalen, ...) do { \ 63 | if (log_loggable(_level) != 0) { \ 64 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 65 | _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ 66 | __VA_ARGS__); \ 67 | } \ 68 | } while (0) 69 | 70 | #else 71 | 72 | #define log_debug(_level, ...) 73 | #define log_hexdump(_level, _data, _datalen, ...) 74 | 75 | #endif 76 | 77 | #define log_stderr(...) do { \ 78 | _log_stderr(__VA_ARGS__); \ 79 | } while (0) 80 | 81 | #define log_safe(...) do { \ 82 | _log_safe(__VA_ARGS__); \ 83 | } while (0) 84 | 85 | #define log_stderr_safe(...) do { \ 86 | _log_stderr_safe(__VA_ARGS__); \ 87 | } while (0) 88 | 89 | #define loga(...) do { \ 90 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 91 | } while (0) 92 | 93 | #define loga_hexdump(_data, _datalen, ...) do { \ 94 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 95 | _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ 96 | __VA_ARGS__); \ 97 | } while (0) \ 98 | 99 | #define log_error(...) do { \ 100 | if (log_loggable(LOG_ALERT) != 0) { \ 101 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 102 | } \ 103 | } while (0) 104 | 105 | #define log_warn(...) do { \ 106 | if (log_loggable(LOG_WARN) != 0) { \ 107 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 108 | } \ 109 | } while (0) 110 | 111 | #define log_panic(...) do { \ 112 | if (log_loggable(LOG_EMERG) != 0) { \ 113 | _log(__FILE__, __LINE__, 1, __VA_ARGS__); \ 114 | } \ 115 | } while (0) 116 | 117 | int log_init(int level, char *filename); 118 | void log_deinit(void); 119 | void log_level_up(void); 120 | void log_level_down(void); 121 | void log_level_set(int level); 122 | void log_stacktrace(void); 123 | void log_reopen(void); 124 | int log_loggable(int level); 125 | void _log(const char *file, int line, int panic, const char *fmt, ...); 126 | void _log_stderr(const char *fmt, ...); 127 | void _log_safe(const char *fmt, ...); 128 | void _log_stderr_safe(const char *fmt, ...); 129 | void _log_hexdump(const char *file, int line, char *data, int datalen, const char *fmt, ...); 130 | int get_logger_fd(); 131 | 132 | #endif 133 | -------------------------------------------------------------------------------- /src/nc_mbuf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_MBUF_H_ 19 | #define _NC_MBUF_H_ 20 | 21 | #include 22 | 23 | typedef void (*mbuf_copy_t)(struct mbuf *, void *); 24 | 25 | struct mbuf { 26 | uint32_t magic; /* mbuf magic (const) */ 27 | STAILQ_ENTRY(mbuf) next; /* next mbuf */ 28 | uint8_t *pos; /* read marker */ 29 | uint8_t *last; /* write marker */ 30 | uint8_t *start; /* start of buffer (const) */ 31 | uint8_t *end; /* end of buffer (const) */ 32 | }; 33 | 34 | STAILQ_HEAD(mhdr, mbuf); 35 | 36 | #define MBUF_MAGIC 0xdeadbeef 37 | #define MBUF_MIN_SIZE 512 38 | #define MBUF_MAX_SIZE 16777216 39 | #define MBUF_SIZE 16384 40 | #define MBUF_HSIZE sizeof(struct mbuf) 41 | 42 | static inline bool 43 | mbuf_empty(struct mbuf *mbuf) 44 | { 45 | return mbuf->pos == mbuf->last ? true : false; 46 | } 47 | 48 | static inline bool 49 | mbuf_full(struct mbuf *mbuf) 50 | { 51 | return mbuf->last == mbuf->end ? true : false; 52 | } 53 | 54 | void mbuf_init(struct instance *nci); 55 | void mbuf_deinit(void); 56 | struct mbuf *mbuf_get(void); 57 | void mbuf_put(struct mbuf *mbuf); 58 | void mbuf_rewind(struct mbuf *mbuf); 59 | uint32_t mbuf_length(struct mbuf *mbuf); 60 | uint32_t mbuf_size(struct mbuf *mbuf); 61 | size_t mbuf_data_size(void); 62 | void mbuf_insert(struct mhdr *mhdr, struct mbuf *mbuf); 63 | void mbuf_remove(struct mhdr *mhdr, struct mbuf *mbuf); 64 | void mbuf_copy(struct mbuf *mbuf, uint8_t *pos, size_t n); 65 | struct mbuf *mbuf_split(struct mhdr *h, uint8_t *pos, mbuf_copy_t cb, void *cbarg); 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/nc_process.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (C) Igor Sysoev 4 | * Copyright (C) Nginx, Inc. 5 | */ 6 | 7 | 8 | #ifndef _NC_PROCESS_H_INCLUDED_ 9 | #define _NC_PROCESS_H_INCLUDED_ 10 | 11 | 12 | 13 | // process role 14 | #define NC_PROCESS_MASTER 1 15 | #define NC_PROCESS_WORKER 2 16 | 17 | 18 | // channel control message 19 | #define NC_CMD_OPEN_CHANNEL 1 20 | #define NC_CMD_CLOSE_CHANNEL 2 21 | #define NC_CMD_QUIT 3 22 | #define NC_CMD_TERMINATE 4 23 | #define NC_CMD_RELOAD 5 24 | #define NC_CMD_RELOAD_DONE 6 25 | #define NC_CMD_REOPEN_REPLY 7 26 | #define NC_CMD_GET_STATS 8 27 | 28 | #define NC_CNT_RELOAD_MAGIC -10 29 | 30 | 31 | #define NC_PROCESS_STATE_RUNNING 0 32 | #define NC_PROCESS_STATE_RELOADING 1 33 | 34 | 35 | typedef pid_t nc_pid_t; 36 | 37 | #define NC_INVALID_PID -1 38 | 39 | typedef void (*nc_spawn_proc_pt) (void *data); 40 | 41 | typedef struct { 42 | nc_pid_t pid; 43 | 44 | 45 | int idxWorker; 46 | 47 | int status; 48 | int channel[2]; 49 | int channel_back[2]; 50 | 51 | nc_spawn_proc_pt proc; 52 | void *data; 53 | char *name; 54 | 55 | unsigned respawn:1; 56 | unsigned just_spawn:1; 57 | unsigned detached:1; 58 | unsigned exiting:1; 59 | unsigned exited:1; 60 | unsigned isNew:1; 61 | } nc_process_t; 62 | 63 | 64 | 65 | 66 | #define NC_MAX_PROCESSES 512 67 | #define NC_PROCESS_MAGIC_JUMP 256 68 | #define NC_PROCESS_NORESPAWN -1 69 | #define NC_PROCESS_JUST_SPAWN -2 70 | #define NC_PROCESS_RESPAWN -3 71 | #define NC_PROCESS_JUST_RESPAWN -4 72 | #define NC_PROCESS_DETACHED -5 73 | 74 | #define nc_getpid getpid 75 | 76 | #ifndef nc_log_pid 77 | #define nc_log_pid nc_pid 78 | #endif 79 | 80 | 81 | nc_pid_t nc_spawn_process( nc_spawn_proc_pt proc, int data, 82 | char *name, int respawn); 83 | 84 | 85 | rstatus_t tw_master_listen(int family, struct sockaddr* addr, socklen_t addrlen, int backlog); 86 | void tw_worker_cycle(void *data); 87 | void nc_process_get_status(void); 88 | 89 | extern nc_pid_t nc_pid; 90 | extern int nc_channel; 91 | extern int nc_process_slot; 92 | extern int nc_worker_channel; 93 | extern int nc_worker_channel_write; 94 | 95 | 96 | extern int nc_last_process; 97 | extern nc_process_t nc_processes[NC_MAX_PROCESSES]; 98 | extern int nc_process_role; 99 | extern int nc_worker_index; 100 | 101 | extern struct env_master env_global; 102 | extern struct instance nci_global; 103 | 104 | extern int nc_reap; 105 | extern int nc_sigio; 106 | extern int nc_sigalarm; 107 | extern int nc_terminate; 108 | extern int nc_quit; 109 | // 110 | extern int nc_reload; 111 | extern int nc_reload_start; 112 | extern int nc_cnt_reload; 113 | extern int nc_get_stats_cmd; 114 | extern int nc_stats_listen_sd; 115 | 116 | 117 | extern int nc_debug_quit; 118 | extern int nc_exiting; 119 | extern int nc_reopen; 120 | extern int nc_daemonized; 121 | 122 | extern int nc_noaccept; 123 | extern int nc_noaccepting; 124 | extern int nc_restart; 125 | 126 | 127 | #endif /* _NGX_PROCESS_H_INCLUDED_ */ 128 | 129 | -------------------------------------------------------------------------------- /src/nc_proxy.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_PROXY_H_ 19 | #define _NC_PROXY_H_ 20 | 21 | #include 22 | 23 | void proxy_ref(struct conn *conn, void *owner); 24 | void proxy_unref(struct conn *conn); 25 | void proxy_close(struct context *ctx, struct conn *conn); 26 | 27 | rstatus_t proxy_each_init(void *elem, void *data); 28 | rstatus_t proxy_each_deinit(void *elem, void *data); 29 | 30 | rstatus_t proxy_init(struct context *ctx); 31 | void proxy_deinit(struct context *ctx); 32 | rstatus_t proxy_recv(struct context *ctx, struct conn *conn); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /src/nc_rbtree.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_RBTREE_ 19 | #define _NC_RBTREE_ 20 | 21 | #define rbtree_red(_node) ((_node)->color = 1) 22 | #define rbtree_black(_node) ((_node)->color = 0) 23 | #define rbtree_is_red(_node) ((_node)->color) 24 | #define rbtree_is_black(_node) (!rbtree_is_red(_node)) 25 | #define rbtree_copy_color(_n1, _n2) ((_n1)->color = (_n2)->color) 26 | 27 | struct rbnode { 28 | struct rbnode *left; /* left link */ 29 | struct rbnode *right; /* right link */ 30 | struct rbnode *parent; /* parent link */ 31 | int64_t key; /* key for ordering */ 32 | void *data; /* opaque data */ 33 | uint8_t color; /* red | black */ 34 | }; 35 | 36 | struct rbtree { 37 | struct rbnode *root; /* root node */ 38 | struct rbnode *sentinel; /* nil node */ 39 | }; 40 | 41 | void rbtree_node_init(struct rbnode *node); 42 | void rbtree_init(struct rbtree *tree, struct rbnode *node); 43 | struct rbnode *rbtree_min(struct rbtree *tree); 44 | void rbtree_insert(struct rbtree *tree, struct rbnode *node); 45 | void rbtree_delete(struct rbtree *tree, struct rbnode *node); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/nc_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_SERVER_H_ 19 | #define _NC_SERVER_H_ 20 | 21 | #include 22 | 23 | /* 24 | * server_pool is a collection of servers and their continuum. Each 25 | * server_pool is the owner of a single proxy connection and one or 26 | * more client connections. server_pool itself is owned by the current 27 | * context. 28 | * 29 | * Each server is the owner of one or more server connections. server 30 | * itself is owned by the server_pool. 31 | * 32 | * +-------------+ 33 | * | |<---------------------+ 34 | * | |<------------+ | 35 | * | | +-------+--+-----+----+--------------+ 36 | * | pool 0 |+--->| | | | 37 | * | | | server 0 | server 1 | ... ... | 38 | * | | | | | |--+ 39 | * | | +----------+----------+--------------+ | 40 | * +-------------+ // 41 | * | | 42 | * | | 43 | * | | 44 | * | pool 1 | 45 | * | | 46 | * | | 47 | * | | 48 | * +-------------+ 49 | * | | 50 | * | | 51 | * . . 52 | * . ... . 53 | * . . 54 | * | | 55 | * | | 56 | * +-------------+ 57 | * | 58 | * | 59 | * // 60 | */ 61 | 62 | typedef uint32_t (*hash_t)(const char *, size_t); 63 | 64 | struct continuum { 65 | uint32_t index; /* server index */ 66 | uint32_t value; /* hash value */ 67 | }; 68 | 69 | struct server { 70 | uint32_t idx; /* server index */ 71 | struct server_pool *owner; /* owner pool */ 72 | 73 | struct string pname; /* hostname:port:weight (ref in conf_server) */ 74 | struct string name; /* hostname:port or [name] (ref in conf_server) */ 75 | struct string addrstr; /* hostname (ref in conf_server) */ 76 | uint16_t port; /* port */ 77 | uint32_t weight; /* weight */ 78 | struct sockinfo info; /* server socket info */ 79 | 80 | uint32_t ns_conn_q; /* # server connection */ 81 | struct conn_tqh s_conn_q; /* server connection q */ 82 | 83 | int64_t next_retry; /* next retry time in usec */ 84 | uint32_t failure_count; /* # consecutive failures */ 85 | }; 86 | 87 | struct server_pool { 88 | uint32_t idx; /* pool index */ 89 | struct context *ctx; /* owner context */ 90 | 91 | struct conn *p_conn; /* proxy connection (listener) */ 92 | uint32_t nc_conn_q; /* # client connection */ 93 | struct conn_tqh c_conn_q; /* client connection q */ 94 | 95 | struct array server; /* server[] */ 96 | uint32_t ncontinuum; /* # continuum points */ 97 | uint32_t nserver_continuum; /* # servers - live and dead on continuum (const) */ 98 | struct continuum *continuum; /* continuum */ 99 | uint32_t nlive_server; /* # live server */ 100 | int64_t next_rebuild; /* next distribution rebuild time in usec */ 101 | 102 | struct string name; /* pool name (ref in conf_pool) */ 103 | struct string addrstr; /* pool address - hostname:port (ref in conf_pool) */ 104 | uint16_t port; /* port */ 105 | struct sockinfo info; /* listen socket info */ 106 | mode_t perm; /* socket permission */ 107 | int dist_type; /* distribution type (dist_type_t) */ 108 | int key_hash_type; /* key hash type (hash_type_t) */ 109 | hash_t key_hash; /* key hasher */ 110 | struct string hash_tag; /* key hash tag (ref in conf_pool) */ 111 | int timeout; /* timeout in msec */ 112 | int backlog; /* listen backlog */ 113 | int redis_db; /* redis database to connect to */ 114 | uint32_t client_connections; /* maximum # client connection */ 115 | uint32_t server_connections; /* maximum # server connection */ 116 | int64_t server_retry_timeout; /* server retry timeout in usec */ 117 | uint32_t server_failure_limit; /* server failure limit */ 118 | 119 | int slow_req_duration; /* slow req duration */ 120 | 121 | struct string redis_auth; /* redis_auth password (matches requirepass on redis) */ 122 | unsigned require_auth; /* require_auth? */ 123 | unsigned auto_eject_hosts:1; /* auto_eject_hosts? */ 124 | unsigned preconnect:1; /* preconnect? */ 125 | unsigned redis:1; /* redis? */ 126 | unsigned tcpkeepalive:1; /* tcpkeepalive? */ 127 | }; 128 | 129 | void server_ref(struct conn *conn, void *owner); 130 | void server_unref(struct conn *conn); 131 | int server_timeout(struct conn *conn); 132 | int server_slow_duration(struct conn *conn); 133 | bool server_active(struct conn *conn); 134 | rstatus_t server_init(struct array *server, struct array *conf_server, struct server_pool *sp); 135 | void server_deinit(struct array *server); 136 | struct conn *server_conn(struct server *server); 137 | rstatus_t server_connect(struct context *ctx, struct server *server, struct conn *conn); 138 | void server_close(struct context *ctx, struct conn *conn); 139 | void server_connected(struct context *ctx, struct conn *conn); 140 | void server_ok(struct context *ctx, struct conn *conn); 141 | 142 | uint32_t server_pool_idx(struct server_pool *pool, uint8_t *key, uint32_t keylen); 143 | struct conn *server_pool_conn(struct context *ctx, struct server_pool *pool, uint8_t *key, uint32_t keylen); 144 | rstatus_t server_pool_run(struct server_pool *pool); 145 | rstatus_t server_pool_preconnect(struct context *ctx); 146 | void server_pool_disconnect(struct context *ctx); 147 | rstatus_t server_pool_init(struct array *server_pool, struct array *conf_pool, struct context *ctx); 148 | void server_pool_deinit(struct array *server_pool); 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /src/nc_shmtx.c: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/nc_shmtx.h: -------------------------------------------------------------------------------- 1 | #ifndef _NC_SHMTX_H_INCLUDED_ 2 | #define _NC_SHMTX_H_INCLUDED_ 3 | 4 | typedef long nc_atomic_int_t; 5 | typedef unsigned long nc_atomic_uint_t; 6 | 7 | 8 | typedef volatile nc_atomic_uint_t nc_atomic_t; 9 | 10 | 11 | #define nc_atomic_cmp_set(lock, old, set) \ 12 | __sync_bool_compare_and_swap(lock, old, set) 13 | 14 | #define nc_atomic_fetch_add(value, add) \ 15 | __sync_fetch_and_add(value, add) 16 | 17 | #define nc_memory_barrier() __sync_synchronize() 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /src/nc_signal.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include 22 | 23 | 24 | 25 | static struct signal signals[] = { 26 | // none defined, extend in the future 27 | { SIGUSR1, "SIGUSR1", 0, signal_handler }, 28 | { SIGUSR2, "SIGUSR2", 0, signal_handler }, 29 | 30 | // log related 31 | { SIGTTIN, "SIGTTIN", 0, signal_handler }, 32 | { SIGTTOU, "SIGTTOU", 0, signal_handler }, 33 | { SIGHUP, "SIGHUP", 0, signal_handler }, 34 | 35 | // zombie process 36 | { SIGCHLD, "SIGCHLD", 0, signal_handler }, 37 | 38 | // fast quit 39 | { SIGINT, "SIGINT", 0, signal_handler }, 40 | { SIGTERM, "SIGTERM", 0, signal_handler}, 41 | // graceful shutdown 42 | { SIGQUIT, "SIGQUIT", 0, signal_handler}, 43 | 44 | // alarm 45 | { SIGALRM, "SIGALRM", 0, signal_handler }, 46 | // graceful shutdown of worker processes 47 | { SIGWINCH, "SIGWINCH", 0, signal_handler}, 48 | 49 | // Segment fault, core dump 50 | { SIGSEGV, "SIGSEGV", (int)SA_RESETHAND, signal_handler }, 51 | 52 | // rst, connection unexpected quit 53 | { SIGPIPE, "SIGPIPE", 0, SIG_IGN }, 54 | //{ SIGIO, "SIGIO", 0, SIG_IGN }, 55 | { 0, NULL, 0, NULL } 56 | }; 57 | 58 | 59 | 60 | 61 | rstatus_t 62 | signal_init(void) 63 | { 64 | struct signal *sig; 65 | 66 | for (sig = signals; sig->signo != 0; sig++) { 67 | rstatus_t status; 68 | struct sigaction sa; 69 | 70 | memset(&sa, 0, sizeof(sa)); 71 | sa.sa_handler = sig->handler; 72 | sa.sa_flags = sig->flags; 73 | sigemptyset(&sa.sa_mask); 74 | 75 | status = sigaction(sig->signo, &sa, NULL); 76 | if (status < 0) { 77 | //log_error(); 78 | log_error("sigaction(%s) failed: %s", sig->signame, 79 | strerror(errno)); 80 | return NC_ERROR; 81 | } 82 | } 83 | 84 | return NC_OK; 85 | } 86 | 87 | 88 | 89 | 90 | void 91 | signal_deinit(void) 92 | { 93 | } 94 | 95 | 96 | 97 | void 98 | signal_handler(int signo) 99 | { 100 | struct signal *sig; 101 | void (*action)(void); 102 | char *actionstr; 103 | bool done; 104 | 105 | for (sig = signals; sig->signo != 0; sig++) { 106 | if (sig->signo == signo) { 107 | break; 108 | } 109 | } 110 | ASSERT(sig->signo != 0); 111 | 112 | actionstr = ""; 113 | action = NULL; 114 | done = false; 115 | 116 | if (nc_process_role == NC_PROCESS_MASTER ) 117 | { 118 | switch (signo) { 119 | case SIGUSR1: 120 | break; 121 | 122 | case SIGUSR2: 123 | break; 124 | 125 | case SIGTTIN: 126 | actionstr = ", up logging level"; 127 | action = log_level_up; 128 | break; 129 | 130 | case SIGTTOU: 131 | actionstr = ", down logging level"; 132 | action = log_level_down; 133 | break; 134 | 135 | case SIGHUP: 136 | actionstr = ", graceful reload configuration"; 137 | //action = log_reopen; 138 | #ifdef GRACEFUL 139 | if (!nc_reload_start) { 140 | log_error("start reload children worker %s ", actionstr); 141 | nc_reload = 1; 142 | } else { 143 | log_error("deny to reload children worker due to children in reload status %s ", actionstr); 144 | } 145 | #endif 146 | break; 147 | 148 | 149 | // control signalling 150 | // best effort 151 | case SIGTERM: 152 | case SIGINT: 153 | done = true; 154 | actionstr = ", fast exiting"; 155 | /*action = restart*/ 156 | nc_terminate = 1; 157 | break; 158 | 159 | case SIGQUIT: 160 | nc_quit = 1; 161 | actionstr = ", graceful shutting down"; 162 | break; 163 | 164 | case SIGWINCH: 165 | //nc_noaccept = 1; 166 | //actionstr = ", stop woring processes, stop accepting connections"; 167 | core_ctx_get_stats(&nci_global); 168 | break; 169 | 170 | case SIGCHLD: 171 | nc_process_get_status(); 172 | break; 173 | 174 | case SIGALRM: 175 | nc_sigalarm = 1; 176 | break; 177 | 178 | 179 | case SIGSEGV: 180 | log_stacktrace(); 181 | actionstr = ", core dumping"; 182 | raise(SIGSEGV); 183 | break; 184 | 185 | default: 186 | NOT_REACHED(); 187 | } 188 | 189 | if (signo!=14) { 190 | log_safe("mastere signal %d (%s) received %s", signo, sig->signame, actionstr); 191 | } 192 | 193 | if (action != NULL) { 194 | action(); 195 | } 196 | /* 197 | if (done) { 198 | exit(1); 199 | }*/ 200 | } 201 | else if (nc_process_role == NC_PROCESS_WORKER) 202 | { 203 | 204 | //log_safe("hello tyosn!"); 205 | switch (signo) { 206 | case SIGUSR1: 207 | break; 208 | 209 | case SIGUSR2: 210 | break; 211 | 212 | case SIGTTIN: 213 | actionstr = ", up logging level"; 214 | action = log_level_up; 215 | break; 216 | 217 | case SIGTTOU: 218 | actionstr = ", down logging level"; 219 | action = log_level_down; 220 | break; 221 | 222 | case SIGHUP: 223 | actionstr = ", graceful reload configuration"; 224 | //action = log_reopen; 225 | #ifdef GRACEFUL 226 | core_reload(&nci_global); 227 | #endif 228 | break; 229 | 230 | 231 | // control signalling 232 | case SIGTERM: 233 | case SIGINT: 234 | done = true; 235 | log_safe("worker %d receive fast terminate signal", nc_worker_index); 236 | 237 | actionstr = ", fast exiting"; 238 | //exit(1); 239 | //ASSERT(0); 240 | raise(SIGSEGV); 241 | break; 242 | 243 | case SIGWINCH: 244 | //nc_debug_quit = 1; 245 | //core_ctx_debug(&nci_global); 246 | case SIGQUIT: 247 | nc_quit = 1; 248 | actionstr = ", graceful shutting down"; 249 | break; 250 | 251 | case SIGSEGV: 252 | log_stacktrace(); 253 | actionstr = ", core dumping"; 254 | raise(SIGSEGV); 255 | break; 256 | 257 | default: 258 | NOT_REACHED(); 259 | } 260 | 261 | //log_safe("worker signal %d (%s) received%s", signo, sig->signame, actionstr); 262 | 263 | if (action != NULL) { 264 | action(); 265 | } 266 | 267 | if (done) { 268 | exit(1); 269 | } 270 | } 271 | else 272 | { 273 | ASSERT(0); 274 | } 275 | 276 | } 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /src/nc_signal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_SIGNAL_H_ 19 | #define _NC_SIGNAL_H_ 20 | 21 | #include 22 | 23 | /* TODO: #ifndef */ 24 | #define TW_SHUTDOWN_SIGNAL QUIT 25 | #define TW_TERMINATE_SIGNAL TERM 26 | 27 | 28 | //#define TW_NOACCEPT_SIGNAL WINCH 29 | //#define TW_RECONFIGURE_SIGNAL HUP 30 | 31 | 32 | struct signal { 33 | int signo; 34 | char *signame; 35 | int flags; 36 | void (*handler)(int signo); 37 | }; 38 | 39 | rstatus_t signal_init(void); 40 | void signal_deinit(void); 41 | void signal_handler(int signo); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/nc_string.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemproxy - A fast and lightweight proxy for memcached protocol. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef _NC_STRING_H_ 19 | #define _NC_STRING_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | struct string { 28 | uint32_t len; /* string length */ 29 | uint8_t *data; /* string data */ 30 | }; 31 | 32 | #define string(_str) { sizeof(_str) - 1, (uint8_t *)(_str) } 33 | #define null_string { 0, NULL } 34 | 35 | #define string_set_text(_str, _text) do { \ 36 | (_str)->len = (uint32_t)(sizeof(_text) - 1);\ 37 | (_str)->data = (uint8_t *)(_text); \ 38 | } while (0); 39 | 40 | #define string_set_raw(_str, _raw) do { \ 41 | (_str)->len = (uint32_t)(nc_strlen(_raw)); \ 42 | (_str)->data = (uint8_t *)(_raw); \ 43 | } while (0); 44 | 45 | void string_init(struct string *str); 46 | void string_deinit(struct string *str); 47 | bool string_empty(const struct string *str); 48 | rstatus_t string_duplicate(struct string *dst, const struct string *src); 49 | rstatus_t string_copy(struct string *dst, const uint8_t *src, uint32_t srclen); 50 | int string_compare(const struct string *s1, const struct string *s2); 51 | 52 | /* 53 | * Wrapper around common routines for manipulating C character 54 | * strings 55 | */ 56 | #define nc_memcpy(_d, _c, _n) \ 57 | memcpy(_d, _c, (size_t)(_n)) 58 | 59 | #define nc_memmove(_d, _c, _n) \ 60 | memmove(_d, _c, (size_t)(_n)) 61 | 62 | #define nc_memchr(_d, _c, _n) \ 63 | memchr(_d, _c, (size_t)(_n)) 64 | 65 | #define nc_strlen(_s) \ 66 | strlen((char *)(_s)) 67 | 68 | #define nc_strncmp(_s1, _s2, _n) \ 69 | strncmp((char *)(_s1), (char *)(_s2), (size_t)(_n)) 70 | 71 | #define nc_strchr(_p, _l, _c) \ 72 | _nc_strchr((uint8_t *)(_p), (uint8_t *)(_l), (uint8_t)(_c)) 73 | 74 | #define nc_strrchr(_p, _s, _c) \ 75 | _nc_strrchr((uint8_t *)(_p),(uint8_t *)(_s), (uint8_t)(_c)) 76 | 77 | #define nc_strndup(_s, _n) \ 78 | (uint8_t *)strndup((char *)(_s), (size_t)(_n)); 79 | 80 | /* 81 | * snprintf(s, n, ...) will write at most n - 1 of the characters printed into 82 | * the output string; the nth character then gets the terminating `\0'; if 83 | * the return value is greater than or equal to the n argument, the string 84 | * was too short and some of the printed characters were discarded; the output 85 | * is always null-terminated. 86 | * 87 | * Note that, the return value of snprintf() is always the number of characters 88 | * that would be printed into the output string, assuming n were limited not 89 | * including the trailing `\0' used to end output. 90 | * 91 | * scnprintf(s, n, ...) is same as snprintf() except, it returns the number 92 | * of characters printed into the output string not including the trailing '\0' 93 | */ 94 | #define nc_snprintf(_s, _n, ...) \ 95 | snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) 96 | 97 | #define nc_scnprintf(_s, _n, ...) \ 98 | _scnprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) 99 | 100 | #define nc_vsnprintf(_s, _n, _f, _a) \ 101 | vsnprintf((char *)(_s), (size_t)(_n), _f, _a) 102 | 103 | #define nc_vscnprintf(_s, _n, _f, _a) \ 104 | _vscnprintf((char *)(_s), (size_t)(_n), _f, _a) 105 | 106 | #define nc_strftime(_s, _n, fmt, tm) \ 107 | (int)strftime((char *)(_s), (size_t)(_n), fmt, tm) 108 | 109 | /* 110 | * A (very) limited version of snprintf 111 | * @param to Destination buffer 112 | * @param n Size of destination buffer 113 | * @param fmt printf() style format string 114 | * @returns Number of bytes written, including terminating '\0' 115 | * Supports 'd' 'i' 'u' 'x' 'p' 's' conversion 116 | * Supports 'l' and 'll' modifiers for integral types 117 | * Does not support any width/precision 118 | * Implemented with simplicity, and async-signal-safety in mind 119 | */ 120 | int _safe_vsnprintf(char *to, size_t size, const char *format, va_list ap); 121 | int _safe_snprintf(char *to, size_t n, const char *fmt, ...); 122 | 123 | #define nc_safe_snprintf(_s, _n, ...) \ 124 | _safe_snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) 125 | 126 | #define nc_safe_vsnprintf(_s, _n, _f, _a) \ 127 | _safe_vsnprintf((char *)(_s), (size_t)(_n), _f, _a) 128 | 129 | static inline uint8_t * 130 | _nc_strchr(uint8_t *p, uint8_t *last, uint8_t c) 131 | { 132 | while (p < last) { 133 | if (*p == c) { 134 | return p; 135 | } 136 | p++; 137 | } 138 | 139 | return NULL; 140 | } 141 | 142 | static inline uint8_t * 143 | _nc_strrchr(uint8_t *p, uint8_t *start, uint8_t c) 144 | { 145 | while (p >= start) { 146 | if (*p == c) { 147 | return p; 148 | } 149 | p--; 150 | } 151 | 152 | return NULL; 153 | } 154 | 155 | #endif 156 | -------------------------------------------------------------------------------- /src/proto/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CPPFLAGS = -I $(top_srcdir)/src 4 | 5 | AM_CFLAGS = -Wall -Wshadow 6 | AM_CFLAGS += -Wno-unused-parameter -Wno-unused-value 7 | 8 | noinst_LIBRARIES = libproto.a 9 | 10 | noinst_HEADERS = nc_proto.h 11 | 12 | libproto_a_SOURCES = \ 13 | nc_memcache.c \ 14 | nc_redis.c 15 | -------------------------------------------------------------------------------- /src/stats.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | #coding=utf-8 3 | import json 4 | import sys 5 | 6 | 7 | class Server: 8 | svrCount = 0 9 | def __init__(self, raw): 10 | self.raw = raw 11 | Server.svrCount += 1 12 | 13 | class ServerPool: 14 | spCount = 0 15 | 16 | def __init__(self, raw): 17 | self.raw = raw 18 | ServerPool.spCount += 1 19 | 20 | class Worker: 21 | wkCount = 0 22 | 23 | def __init__(self, raw): 24 | self.raw = raw 25 | Worker.wkCount += 1 26 | 27 | def parse(self): 28 | 29 | self.cur_connections = 1, 30 | self.service = self.raw['service'] 31 | self.source = self.raw['source'] 32 | self.timstamp = self.raw['timestamp'] 33 | self.total_connections += self.raw['total_connections'] 34 | self.uptime = self.raw['uptime'] 35 | 36 | "uptime": 1192, 37 | "version": "0.4.1" 38 | 39 | 40 | 41 | while 1: 42 | line = sys.stdin.readline() 43 | if not line: 44 | break 45 | 46 | # parse to json 47 | parsed_data = json.loads(line[:-1]) 48 | 49 | parsed_data 50 | 51 | Woker 52 | 53 | print parsed_data['version'] 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.out 3 | *.log 4 | -------------------------------------------------------------------------------- /tests/README.rst: -------------------------------------------------------------------------------- 1 | Python testing facilities for twemproxy, this test suite is based on https://github.com/idning/redis-mgr 2 | 3 | already add to https://travis-ci.org/idning/twemproxy as travis-ci 4 | 5 | see https://github.com/idning/twemproxy/blob/travis-ci/travis.sh 6 | 7 | usage 8 | ===== 9 | 10 | 1. install dependency:: 11 | 12 | pip install nose 13 | pip install git+https://github.com/andymccurdy/redis-py.git@2.10.3 14 | pip install git+https://github.com/idning/python-memcached.git#egg=memcache 15 | 16 | 2. copy binarys to _binaries/:: 17 | 18 | _binaries/ 19 | |-- nutcracker 20 | |-- redis-benchmark 21 | |-- redis-check-aof 22 | |-- redis-check-dump 23 | |-- redis-cli 24 | |-- redis-sentinel 25 | |-- redis-server 26 | |-- memcached 27 | 28 | 3. run:: 29 | 30 | $ nosetests -v 31 | test_del.test_multi_delete_on_readonly ... ok 32 | test_mget.test_mget ... ok 33 | 34 | ---------------------------------------------------------------------- 35 | Ran 2 tests in 4.483s 36 | 37 | OK 38 | 39 | 4. add A case:: 40 | 41 | cp tests/test_del.py tests/test_xxx.py 42 | vim tests/test_xxx.py 43 | 44 | 45 | 46 | variables 47 | ========= 48 | :: 49 | 50 | export T_VERBOSE=9 will start nutcracker with '-v 9' (default:4) 51 | export T_MBUF=512 will start nutcracker whit '-m 512' (default:521) 52 | export T_LARGE=10000 will test 10000 keys for mget/mset (default:1000) 53 | 54 | T_LOGFILE: 55 | 56 | - to put test log on stderr:: 57 | 58 | export T_LOGFILE=- 59 | 60 | - to put test log on t.log:: 61 | 62 | export T_LOGFILE=t.log 63 | 64 | or:: 65 | 66 | unset T_LOGFILE 67 | 68 | 69 | notes 70 | ===== 71 | 72 | - After all the tests. you may got a core because we have a case in test_signal which will send SEGV to nutcracker 73 | 74 | 75 | -------------------------------------------------------------------------------- /tests/conf/conf.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | import os 4 | import sys 5 | 6 | PWD = os.path.dirname(os.path.realpath(__file__)) 7 | WORKDIR = os.path.join(PWD, '../') 8 | 9 | BINARYS = { 10 | 'REDIS_SERVER_BINS' : os.path.join(WORKDIR, '_binaries/redis-*'), 11 | 'REDIS_CLI' : os.path.join(WORKDIR, '_binaries/redis-cli'), 12 | 'MEMCACHED_BINS' : os.path.join(WORKDIR, '_binaries/memcached'), 13 | 'NUTCRACKER_BINS' : os.path.join(WORKDIR, '_binaries/nutcracker'), 14 | } 15 | 16 | -------------------------------------------------------------------------------- /tests/conf/control.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | start() 4 | { 5 | stop 6 | ulimit -c unlimited 7 | 8 | pushd . > /dev/null 9 | 10 | cd `dirname $$0` 11 | ${startcmd} 12 | popd 13 | } 14 | 15 | stop() 16 | { 17 | pkill -9 -f '${runcmd}' 18 | } 19 | 20 | case C"$$1" in 21 | C) 22 | echo "Usage: $$0 {start|stop}" 23 | ;; 24 | Cstart) 25 | start 26 | echo "Done!" 27 | ;; 28 | Cstop) 29 | stop 30 | echo "Done!" 31 | ;; 32 | C*) 33 | echo "Usage: $$0 {start|stop}" 34 | ;; 35 | esac 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tests/lib/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import time 5 | import copy 6 | import thread 7 | import socket 8 | import threading 9 | import logging 10 | import inspect 11 | import argparse 12 | import telnetlib 13 | import redis 14 | import random 15 | import redis 16 | import json 17 | import glob 18 | import commands 19 | 20 | from collections import defaultdict 21 | from argparse import RawTextHelpFormatter 22 | 23 | from string import Template 24 | 25 | PWD = os.path.dirname(os.path.realpath(__file__)) 26 | WORKDIR = os.path.join(PWD, '../') 27 | 28 | def getenv(key, default): 29 | if key in os.environ: 30 | return os.environ[key] 31 | return default 32 | 33 | logfile = getenv('T_LOGFILE', 'log/t.log') 34 | if logfile == '-': 35 | logging.basicConfig(level=logging.DEBUG, 36 | format="%(asctime)-15s [%(threadName)s] [%(levelname)s] %(message)s") 37 | else: 38 | logging.basicConfig(filename=logfile, level=logging.DEBUG, 39 | format="%(asctime)-15s [%(threadName)s] [%(levelname)s] %(message)s") 40 | 41 | logging.info("test running") 42 | 43 | def strstr(s1, s2): 44 | return s1.find(s2) != -1 45 | 46 | def lets_sleep(SLEEP_TIME = 0.1): 47 | time.sleep(SLEEP_TIME) 48 | 49 | def TT(template, args): #todo: modify all 50 | return Template(template).substitute(args) 51 | 52 | def TTCMD(template, args): #todo: modify all 53 | ''' 54 | Template for cmd (we will replace all spaces) 55 | ''' 56 | ret = TT(template, args) 57 | return re.sub(' +', ' ', ret) 58 | 59 | def nothrow(ExceptionToCheck=Exception, logger=None): 60 | def deco_retry(f): 61 | def f_retry(*args, **kwargs): 62 | try: 63 | return f(*args, **kwargs) 64 | except ExceptionToCheck, e: 65 | if logger: 66 | logger.info(e) 67 | else: 68 | print str(e) 69 | return f_retry # true decorator 70 | return deco_retry 71 | 72 | @nothrow(Exception) 73 | def test_nothrow(): 74 | raise Exception('exception: xx') 75 | 76 | def json_encode(j): 77 | return json.dumps(j, indent=4, cls=MyEncoder) 78 | 79 | def json_decode(j): 80 | return json.loads(j) 81 | 82 | #commands dose not work on windows.. 83 | def system(cmd, log_fun=logging.info): 84 | if log_fun: log_fun(cmd) 85 | r = commands.getoutput(cmd) 86 | return r 87 | 88 | def shorten(s, l=80): 89 | if len(s)<=l: 90 | return s 91 | return s[:l-3] + '...' 92 | 93 | def assert_true(a): 94 | assert a, 'assert fail: except true, got %s' % a 95 | 96 | def assert_equal(a, b): 97 | assert a == b, 'assert fail: %s vs %s' % (shorten(str(a)), shorten(str(b))) 98 | 99 | def assert_raises(exception_cls, callable, *args, **kwargs): 100 | try: 101 | callable(*args, **kwargs) 102 | except exception_cls as e: 103 | return e 104 | except Exception as e: 105 | assert False, 'assert_raises %s but raised: %s' % (exception_cls, e) 106 | assert False, 'assert_raises %s but nothing raise' % (exception_cls) 107 | 108 | def assert_fail(err_response, callable, *args, **kwargs): 109 | try: 110 | callable(*args, **kwargs) 111 | except Exception as e: 112 | assert re.search(err_response, str(e)), \ 113 | 'assert "%s" but got "%s"' % (err_response, e) 114 | return 115 | 116 | assert False, 'assert_fail %s but nothing raise' % (err_response) 117 | 118 | if __name__ == "__main__": 119 | test_nothrow() 120 | -------------------------------------------------------------------------------- /tests/log/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /tests/test_memcache/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilibili/bilitw/a6c46feef6a0f41c15b11e96938658eec13d41f6/tests/test_memcache/__init__.py -------------------------------------------------------------------------------- /tests/test_memcache/test_gets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | import os 5 | import sys 6 | import redis 7 | import memcache 8 | 9 | PWD = os.path.dirname(os.path.realpath(__file__)) 10 | WORKDIR = os.path.join(PWD, '../') 11 | sys.path.append(os.path.join(WORKDIR, 'lib/')) 12 | sys.path.append(os.path.join(WORKDIR, 'conf/')) 13 | import conf 14 | 15 | from server_modules import * 16 | from utils import * 17 | 18 | CLUSTER_NAME = 'ntest' 19 | all_mc= [ 20 | Memcached('127.0.0.1', 2200, '/tmp/r/memcached-2200/', CLUSTER_NAME, 'mc-2200'), 21 | Memcached('127.0.0.1', 2201, '/tmp/r/memcached-2201/', CLUSTER_NAME, 'mc-2201'), 22 | ] 23 | 24 | nc_verbose = int(getenv('T_VERBOSE', 4)) 25 | mbuf = int(getenv('T_MBUF', 512)) 26 | large = int(getenv('T_LARGE', 1000)) 27 | 28 | nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, 29 | all_mc, mbuf=mbuf, verbose=nc_verbose, is_redis=False) 30 | 31 | def setup(): 32 | for r in all_mc: 33 | r.deploy() 34 | r.stop() 35 | r.start() 36 | 37 | nc.deploy() 38 | nc.stop() 39 | nc.start() 40 | 41 | def teardown(): 42 | for r in all_mc: 43 | r.stop() 44 | assert(nc._alive()) 45 | nc.stop() 46 | 47 | def getconn(): 48 | host_port = '%s:%s' % (nc.host(), nc.port()) 49 | return memcache.Client([host_port]) 50 | 51 | def test_basic(): 52 | conn = getconn() 53 | conn.set('k', 'v') 54 | assert('v' == conn.get('k')) 55 | 56 | conn.set("key", "1") 57 | for i in range(10): 58 | conn.incr("key") 59 | assert(str(i+2) == conn.get('key')) 60 | 61 | conn.delete("key") 62 | assert(None == conn.get('key')) 63 | 64 | default_kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(10)} 65 | def test_mget_mset(kv=default_kv): 66 | conn = getconn() 67 | conn.set_multi(kv) 68 | keys = sorted(kv.keys()) 69 | 70 | assert(conn.get_multi(keys) == kv) 71 | assert(conn.gets_multi(keys) == kv) 72 | 73 | #del 74 | conn.delete_multi(keys) 75 | #mget again 76 | vals = conn.get_multi(keys) 77 | assert({} == vals) 78 | 79 | def test_mget_mset_large(): 80 | for cnt in range(179, large, 179): 81 | #print 'test', cnt 82 | kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(cnt)} 83 | test_mget_mset(kv) 84 | 85 | def test_mget_mset_key_not_exists(kv=default_kv): 86 | conn = getconn() 87 | conn.set_multi(kv) 88 | 89 | keys = kv.keys() 90 | keys2 = ['x-'+k for k in keys] 91 | keys = keys + keys2 92 | random.shuffle(keys) 93 | 94 | for i in range(2): 95 | #mget to check 96 | vals = conn.get_multi(keys) 97 | for i, k in enumerate(keys): 98 | if k in kv: 99 | assert(kv[k] == vals[k]) 100 | else: 101 | assert(k not in vals) 102 | 103 | #del 104 | conn.delete_multi(keys) 105 | #mget again 106 | vals = conn.get_multi(keys) 107 | assert({} == vals) 108 | 109 | -------------------------------------------------------------------------------- /tests/test_redis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilibili/bilitw/a6c46feef6a0f41c15b11e96938658eec13d41f6/tests/test_redis/__init__.py -------------------------------------------------------------------------------- /tests/test_redis/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | import os 5 | import sys 6 | import redis 7 | 8 | PWD = os.path.dirname(os.path.realpath(__file__)) 9 | WORKDIR = os.path.join(PWD,'../') 10 | sys.path.append(os.path.join(WORKDIR,'lib/')) 11 | sys.path.append(os.path.join(WORKDIR,'conf/')) 12 | 13 | import conf 14 | 15 | from server_modules import * 16 | from utils import * 17 | 18 | CLUSTER_NAME = 'ntest' 19 | nc_verbose = int(getenv('T_VERBOSE', 5)) 20 | mbuf = int(getenv('T_MBUF', 512)) 21 | large = int(getenv('T_LARGE', 1000)) 22 | 23 | all_redis = [ 24 | RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', CLUSTER_NAME, 'redis-2100'), 25 | RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', CLUSTER_NAME, 'redis-2101'), 26 | ] 27 | 28 | nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, 29 | all_redis, mbuf=mbuf, verbose=nc_verbose) 30 | 31 | def setup(): 32 | print 'setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) 33 | for r in all_redis + [nc]: 34 | r.clean() 35 | r.deploy() 36 | r.stop() 37 | r.start() 38 | 39 | def teardown(): 40 | for r in all_redis + [nc]: 41 | assert(r._alive()) 42 | r.stop() 43 | 44 | default_kv = {'kkk-%s' % i : 'vvv-%s' % i for i in range(10)} 45 | 46 | def getconn(): 47 | for r in all_redis: 48 | c = redis.Redis(r.host(), r.port()) 49 | c.flushdb() 50 | 51 | r = redis.Redis(nc.host(), nc.port()) 52 | return r 53 | 54 | -------------------------------------------------------------------------------- /tests/test_redis/test_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | from common import * 5 | 6 | all_redis = [ 7 | RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', 8 | CLUSTER_NAME, 'redis-2100', auth = 'hellopasswd'), 9 | RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', 10 | CLUSTER_NAME, 'redis-2101', auth = 'hellopasswd'), 11 | ] 12 | 13 | nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, 14 | all_redis, mbuf=mbuf, verbose=nc_verbose, 15 | redis_auth = 'hellopasswd') 16 | 17 | nc_badpass = NutCracker('127.0.0.1', 4101, '/tmp/r/nutcracker-4101', CLUSTER_NAME, 18 | all_redis, mbuf=mbuf, verbose=nc_verbose, 19 | redis_auth = 'badpasswd') 20 | nc_nopass = NutCracker('127.0.0.1', 4102, '/tmp/r/nutcracker-4102', CLUSTER_NAME, 21 | all_redis, mbuf=mbuf, verbose=nc_verbose) 22 | 23 | def setup(): 24 | print 'setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) 25 | for r in all_redis + [nc, nc_badpass, nc_nopass]: 26 | r.clean() 27 | r.deploy() 28 | r.stop() 29 | r.start() 30 | 31 | def teardown(): 32 | for r in all_redis + [nc, nc_badpass, nc_nopass]: 33 | assert(r._alive()) 34 | r.stop() 35 | 36 | default_kv = {'kkk-%s' % i : 'vvv-%s' % i for i in range(10)} 37 | 38 | def getconn(): 39 | r = redis.Redis(nc.host(), nc.port()) 40 | return r 41 | 42 | ''' 43 | 44 | cases: 45 | 46 | 47 | redis proxy case 48 | 1 1 test_auth_basic 49 | 1 bad test_badpass_on_proxy 50 | 1 0 test_nopass_on_proxy 51 | 0 0 already tested on other case 52 | 0 1 53 | 54 | ''' 55 | 56 | def test_auth_basic(): 57 | # we hope to have same behavior when the server is redis or twemproxy 58 | conns = [ 59 | redis.Redis(all_redis[0].host(), all_redis[0].port()), 60 | redis.Redis(nc.host(), nc.port()), 61 | ] 62 | 63 | for r in conns: 64 | assert_fail('NOAUTH|operation not permitted', r.ping) 65 | assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') 66 | assert_fail('NOAUTH|operation not permitted', r.get, 'k') 67 | 68 | # bad passwd 69 | assert_fail('invalid password', r.execute_command, 'AUTH', 'badpasswd') 70 | 71 | # everything is ok after auth 72 | r.execute_command('AUTH', 'hellopasswd') 73 | r.set('k', 'v') 74 | assert(r.ping() == True) 75 | assert(r.get('k') == 'v') 76 | 77 | # auth fail here, should we return ok or not => we will mark the conn state as not authed 78 | assert_fail('invalid password', r.execute_command, 'AUTH', 'badpasswd') 79 | 80 | assert_fail('NOAUTH|operation not permitted', r.ping) 81 | assert_fail('NOAUTH|operation not permitted', r.get, 'k') 82 | 83 | def test_nopass_on_proxy(): 84 | r = redis.Redis(nc_nopass.host(), nc_nopass.port()) 85 | 86 | # if you config pass on redis but not on twemproxy, 87 | # twemproxy will reply ok for ping, but once you do get/set, you will get errmsg from redis 88 | assert(r.ping() == True) 89 | assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') 90 | assert_fail('NOAUTH|operation not permitted', r.get, 'k') 91 | 92 | # proxy has no pass, when we try to auth 93 | assert_fail('Client sent AUTH, but no password is set', r.execute_command, 'AUTH', 'anypasswd') 94 | pass 95 | 96 | def test_badpass_on_proxy(): 97 | r = redis.Redis(nc_badpass.host(), nc_badpass.port()) 98 | 99 | assert_fail('NOAUTH|operation not permitted', r.ping) 100 | assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') 101 | assert_fail('NOAUTH|operation not permitted', r.get, 'k') 102 | 103 | # we can auth with bad pass (twemproxy will say ok for this) 104 | r.execute_command('AUTH', 'badpasswd') 105 | # after that, we still got NOAUTH for get/set (return from redis-server) 106 | assert(r.ping() == True) 107 | assert_fail('NOAUTH|operation not permitted', r.set, 'k', 'v') 108 | assert_fail('NOAUTH|operation not permitted', r.get, 'k') 109 | 110 | def setup_and_wait(): 111 | time.sleep(60*60) 112 | -------------------------------------------------------------------------------- /tests/test_redis/test_basic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | from common import * 5 | 6 | def test_setget(): 7 | r = getconn() 8 | 9 | rst = r.set('k', 'v') 10 | assert(r.get('k') == 'v') 11 | 12 | def test_msetnx(): 13 | r = getconn() 14 | 15 | #not supported 16 | keys = default_kv.keys() 17 | assert_fail('Socket closed|Connection closed', r.msetnx,**default_kv) 18 | 19 | def test_null_key(): 20 | r = getconn() 21 | rst = r.set('', 'v') 22 | assert(r.get('') == 'v') 23 | 24 | rst = r.set('', '') 25 | assert(r.get('') == '') 26 | 27 | kv = {'' : 'val', 'k': 'v'} 28 | ret = r.mset(**kv) 29 | assert(r.get('') == 'val') 30 | 31 | def test_ping_quit(): 32 | r = getconn() 33 | assert(r.ping() == True) 34 | 35 | #get set 36 | rst = r.set('k', 'v') 37 | assert(r.get('k') == 'v') 38 | 39 | assert_fail('Socket closed|Connection closed', r.execute_command, 'QUIT') 40 | 41 | def test_slow_req(): 42 | r = getconn() 43 | 44 | kv = {'mkkk-%s' % i : 'mvvv-%s' % i for i in range(500000)} 45 | 46 | pipe = r.pipeline(transaction=False) 47 | pipe.set('key-1', 'v1') 48 | pipe.get('key-1') 49 | pipe.hmset('xxx', kv) 50 | pipe.get('key-2') 51 | pipe.get('key-3') 52 | 53 | assert_fail('timed out', pipe.execute) 54 | 55 | def test_signal(): 56 | #init 57 | nc.cleanlog() 58 | nc.signal('HUP') 59 | 60 | nc.signal('HUP') 61 | nc.signal('TTIN') 62 | nc.signal('TTOU') 63 | nc.signal('SEGV') 64 | 65 | time.sleep(.3) 66 | log = file(nc.logfile()).read() 67 | 68 | assert(strstr(log, 'HUP')) 69 | assert(strstr(log, 'TTIN')) 70 | assert(strstr(log, 'TTOU')) 71 | assert(strstr(log, 'SEGV')) 72 | 73 | #recover 74 | nc.start() 75 | 76 | def test_nc_stats(): 77 | nc.stop() #reset counters 78 | nc.start() 79 | r = getconn() 80 | kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(10)} 81 | for k, v in kv.items(): 82 | r.set(k, v) 83 | r.get(k) 84 | 85 | def get_stat(name): 86 | time.sleep(1) 87 | stat = nc._info_dict() 88 | #pprint(stat) 89 | if name in ['client_connections', 'client_eof', 'client_err', \ 90 | 'forward_error', 'fragments', 'server_ejects']: 91 | return stat[CLUSTER_NAME][name] 92 | 93 | #sum num of each server 94 | ret = 0 95 | for k, v in stat[CLUSTER_NAME].items(): 96 | if type(v) == dict: 97 | ret += v[name] 98 | return ret 99 | 100 | assert(get_stat('requests') == 20) 101 | assert(get_stat('responses') == 20) 102 | 103 | ##### mget 104 | keys = kv.keys() 105 | r.mget(keys) 106 | 107 | #for version<=0.3.0 108 | #assert(get_stat('requests') == 30) 109 | #assert(get_stat('responses') == 30) 110 | 111 | #for mget-improve 112 | assert(get_stat('requests') == 22) 113 | assert(get_stat('responses') == 22) 114 | 115 | def test_issue_323(): 116 | # do on redis 117 | r = all_redis[0] 118 | c = redis.Redis(r.host(), r.port()) 119 | assert([1, 'OK'] == c.eval("return {1, redis.call('set', 'x', '1')}", 1, 'tmp')) 120 | 121 | # do on twemproxy 122 | c = getconn() 123 | assert([1, 'OK'] == c.eval("return {1, redis.call('set', 'x', '1')}", 1, 'tmp')) 124 | 125 | def setup_and_wait(): 126 | time.sleep(60*60) 127 | 128 | -------------------------------------------------------------------------------- /tests/test_redis/test_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | from common import * 5 | 6 | def test_linsert(): 7 | r = getconn() 8 | 9 | r.rpush('mylist', 'Hello') 10 | r.rpush('mylist', 'World') 11 | r.linsert('mylist', 'BEFORE', 'World', 'There') 12 | 13 | rst = r.lrange('mylist', 0, -1) 14 | assert(rst == ['Hello', 'There', 'World']) 15 | 16 | def test_lpush_lrange(): 17 | r = getconn() 18 | 19 | vals = ['vvv-%s' % i for i in range(10) ] 20 | assert([] == r.lrange('mylist', 0, -1)) 21 | 22 | r.lpush('mylist', *vals) 23 | rst = r.lrange('mylist', 0, -1) 24 | 25 | assert(10 == len(rst)) 26 | 27 | def test_hscan(): 28 | r = getconn() 29 | 30 | kv = {'kkk-%s' % i : 'vvv-%s' % i for i in range(10)} 31 | r.hmset('a', kv) 32 | 33 | cursor, dic = r.hscan('a') 34 | assert(str(cursor) == '0') 35 | assert(dic == kv) 36 | 37 | cursor, dic = r.hscan('a', match='kkk-5') 38 | assert(str(cursor) == '0') 39 | assert(dic == {'kkk-5': 'vvv-5'}) 40 | 41 | def test_hscan_large(): 42 | r = getconn() 43 | 44 | kv = {'x'* 100 + 'kkk-%s' % i : 'vvv-%s' % i for i in range(1000)} 45 | r.hmset('a', kv) 46 | 47 | cursor = '0' 48 | dic = {} 49 | while True: 50 | cursor, t = r.hscan('a', cursor, count=10) 51 | for k, v in t.items(): 52 | dic[k] = v 53 | 54 | if '0' == str(cursor): 55 | break 56 | 57 | assert(dic == kv) 58 | 59 | cursor, dic = r.hscan('a', '0', match='*kkk-5*', count=1000) 60 | if str(cursor) == '0': 61 | assert(len(dic) == 111) 62 | else: 63 | assert(len(dic) == 111) 64 | 65 | #again. 66 | cursor, dic = r.hscan('a', cursor, match='*kkk-5*', count=1000) 67 | assert(str(cursor) == '0') 68 | assert(len(dic) == 0) 69 | 70 | def test_zscan(): 71 | r = getconn() 72 | 73 | r.zadd('a', 'a', 1, 'b', 2, 'c', 3) 74 | 75 | cursor, pairs = r.zscan('a') 76 | assert(str(cursor) == '0') 77 | assert(set(pairs) == set([('a', 1), ('b', 2), ('c', 3)])) 78 | 79 | cursor, pairs = r.zscan('a', match='a') 80 | assert(str(cursor) == '0') 81 | assert(set(pairs) == set([('a', 1)])) 82 | 83 | def test_sscan(): 84 | r = getconn() 85 | 86 | r.sadd('a', 1, 2, 3) 87 | 88 | cursor, members = r.sscan('a') 89 | assert(str(cursor) == '0') 90 | assert(set(members) == set(['1', '2', '3'])) 91 | 92 | cursor, members = r.sscan('a', match='1') 93 | assert(str(cursor) == '0') 94 | assert(set(members) == set(['1'])) 95 | 96 | -------------------------------------------------------------------------------- /tests/test_redis/test_mget_large_binary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | from common import * 5 | from test_mget_mset import test_mget_mset as _mget_mset 6 | 7 | #force to use large mbuf, we need to copy the setup/teardown here.. 8 | 9 | mbuf = 64*1024 10 | 11 | nc = NutCracker(nc.host(), nc.port(), '/tmp/r/nutcracker-4100', CLUSTER_NAME, 12 | all_redis, mbuf=mbuf, verbose=nc_verbose) 13 | 14 | def setup(): 15 | print 'special setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) 16 | for r in all_redis + [nc]: 17 | r.deploy() 18 | r.stop() 19 | r.start() 20 | 21 | def teardown(): 22 | for r in all_redis + [nc]: 23 | assert(r._alive()) 24 | r.stop() 25 | 26 | ###################################################### 27 | def test_mget_binary_value(cnt=5): 28 | kv = {} 29 | for i in range(cnt): 30 | kv['kkk-%s' % i] = os.urandom(1024*1024*16+1024) #16M 31 | for i in range(cnt): 32 | kv['kkk2-%s' % i] = '' 33 | _mget_mset(kv) 34 | 35 | -------------------------------------------------------------------------------- /tests/test_redis/test_mget_mset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | from common import * 5 | 6 | def test_mget_mset(kv=default_kv): 7 | r = getconn() 8 | 9 | def insert_by_pipeline(): 10 | pipe = r.pipeline(transaction=False) 11 | for k, v in kv.items(): 12 | pipe.set(k, v) 13 | pipe.execute() 14 | 15 | def insert_by_mset(): 16 | ret = r.mset(**kv) 17 | 18 | #insert_by_mset() #only the mget-imporve branch support this 19 | try: 20 | insert_by_mset() #only the mget-imporve branch support this 21 | except: 22 | insert_by_pipeline() 23 | 24 | keys = kv.keys() 25 | 26 | #mget to check 27 | vals = r.mget(keys) 28 | for i, k in enumerate(keys): 29 | assert(kv[k] == vals[i]) 30 | 31 | #del 32 | assert (len(keys) == r.delete(*keys) ) 33 | 34 | #mget again 35 | vals = r.mget(keys) 36 | 37 | for i, k in enumerate(keys): 38 | assert(None == vals[i]) 39 | 40 | def test_mget_mset_on_key_not_exist(kv=default_kv): 41 | r = getconn() 42 | 43 | def insert_by_pipeline(): 44 | pipe = r.pipeline(transaction=False) 45 | for k, v in kv.items(): 46 | pipe.set(k, v) 47 | pipe.execute() 48 | 49 | def insert_by_mset(): 50 | ret = r.mset(**kv) 51 | 52 | try: 53 | insert_by_mset() #only the mget-imporve branch support this 54 | except: 55 | insert_by_pipeline() 56 | 57 | keys = kv.keys() 58 | keys2 = ['x-'+k for k in keys] 59 | keys = keys + keys2 60 | random.shuffle(keys) 61 | 62 | #mget to check 63 | vals = r.mget(keys) 64 | for i, k in enumerate(keys): 65 | if k in kv: 66 | assert(kv[k] == vals[i]) 67 | else: 68 | assert(vals[i] == None) 69 | 70 | #del 71 | assert (len(kv) == r.delete(*keys) ) 72 | 73 | #mget again 74 | vals = r.mget(keys) 75 | 76 | for i, k in enumerate(keys): 77 | assert(None == vals[i]) 78 | 79 | def test_mget_mset_large(): 80 | for cnt in range(171, large, 171): 81 | kv = {'kkk-%s' % i :'vvv-%s' % i for i in range(cnt)} 82 | test_mget_mset(kv) 83 | 84 | def test_mget_special_key(cnt=5): 85 | #key length = 512-48-1 86 | kv = {} 87 | for i in range(cnt): 88 | k = 'kkk-%s' % i 89 | k = k + 'x'*(512-48-1-len(k)) 90 | kv[k] = 'vvv' 91 | 92 | test_mget_mset(kv) 93 | 94 | def test_mget_special_key_2(cnt=5): 95 | #key length = 512-48-2 96 | kv = {} 97 | for i in range(cnt): 98 | k = 'kkk-%s' % i 99 | k = k + 'x'*(512-48-2-len(k)) 100 | kv[k] = 'vvv'*9 101 | 102 | test_mget_mset(kv) 103 | 104 | def test_mget_on_backend_down(): 105 | #one backend down 106 | 107 | r = redis.Redis(nc.host(), nc.port()) 108 | assert_equal(None, r.get('key-2')) 109 | assert_equal(None, r.get('key-1')) 110 | 111 | all_redis[0].stop() 112 | 113 | assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-1') 114 | assert_fail('Connection refused|reset by peer|Broken pipe', r.get, 'key-1') 115 | assert_equal(None, r.get('key-2')) 116 | 117 | keys = ['key-1', 'key-2', 'kkk-3'] 118 | assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, *keys) 119 | 120 | #all backend down 121 | all_redis[1].stop() 122 | r = redis.Redis(nc.host(), nc.port()) 123 | 124 | assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-1') 125 | assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, 'key-2') 126 | 127 | keys = ['key-1', 'key-2', 'kkk-3'] 128 | assert_fail('Connection refused|reset by peer|Broken pipe', r.mget, *keys) 129 | 130 | for r in all_redis: 131 | r.start() 132 | 133 | def test_mset_on_backend_down(): 134 | all_redis[0].stop() 135 | r = redis.Redis(nc.host(),nc.port()) 136 | 137 | assert_fail('Connection refused|Broken pipe',r.mset,default_kv) 138 | 139 | all_redis[1].stop() 140 | assert_fail('Connection refused|Broken pipe',r.mset,default_kv) 141 | 142 | for r in all_redis: 143 | r.start() 144 | 145 | def test_mget_pipeline(): 146 | r = getconn() 147 | 148 | pipe = r.pipeline(transaction=False) 149 | for k,v in default_kv.items(): 150 | pipe.set(k,v) 151 | keys = default_kv.keys() 152 | pipe.mget(keys) 153 | kv = {} 154 | for i in range(large): 155 | kv['kkk-%s' % i] = os.urandom(100) 156 | for k,v in kv.items(): 157 | pipe.set(k,v) 158 | for k in kv.keys(): 159 | pipe.get(k) 160 | rst = pipe.execute() 161 | 162 | #print rst 163 | #check the result 164 | keys = default_kv.keys() 165 | 166 | #mget to check 167 | vals = r.mget(keys) 168 | for i, k in enumerate(keys): 169 | assert(kv[k] == vals[i]) 170 | 171 | #del 172 | assert (len(keys) == r.delete(*keys) ) 173 | 174 | #mget again 175 | vals = r.mget(keys) 176 | 177 | for i, k in enumerate(keys): 178 | assert(None == vals[i]) 179 | 180 | def test_multi_delete_normal(): 181 | r = getconn() 182 | 183 | for i in range(100): 184 | r.set('key-%s'%i, 'val-%s'%i) 185 | for i in range(100): 186 | assert_equal('val-%s'%i, r.get('key-%s'%i) ) 187 | 188 | keys = ['key-%s'%i for i in range(100)] 189 | assert_equal(100, r.delete(*keys)) 190 | 191 | for i in range(100): 192 | assert_equal(None, r.get('key-%s'%i) ) 193 | 194 | def test_multi_delete_on_readonly(): 195 | all_redis[0].slaveof(all_redis[1].args['host'], all_redis[1].args['port']) 196 | 197 | r = redis.Redis(nc.host(), nc.port()) 198 | 199 | # got "You can't write against a read only slave" 200 | assert_fail("You can't write against a read only slave.", r.delete, 'key-1') 201 | assert_equal(0, r.delete('key-2')) 202 | assert_fail("You can't write against a read only slave", r.delete, 'key-3') 203 | 204 | keys = ['key-1', 'key-2', 'kkk-3'] 205 | assert_fail('Invalid argument', r.delete, *keys) # got "Invalid argument" 206 | 207 | def test_multi_delete_on_backend_down(): 208 | #one backend down 209 | all_redis[0].stop() 210 | r = redis.Redis(nc.host(), nc.port()) 211 | 212 | assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-1') 213 | assert_equal(None, r.get('key-2')) 214 | 215 | keys = ['key-1', 'key-2', 'kkk-3'] 216 | assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, *keys) 217 | 218 | #all backend down 219 | all_redis[1].stop() 220 | r = redis.Redis(nc.host(), nc.port()) 221 | 222 | assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-1') 223 | assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, 'key-2') 224 | 225 | keys = ['key-1', 'key-2', 'kkk-3'] 226 | assert_fail('Connection refused|reset by peer|Broken pipe', r.delete, *keys) 227 | 228 | for r in all_redis: 229 | r.start() 230 | 231 | 232 | def test_multi_delete_20140525(): 233 | r = getconn() 234 | 235 | cnt = 126 236 | keys = ['key-%s'%i for i in range(cnt)] 237 | pipe = r.pipeline(transaction=False) 238 | pipe.mget(keys) 239 | pipe.delete(*keys) 240 | pipe.execute() 241 | 242 | 243 | -------------------------------------------------------------------------------- /tests/test_redis/test_pipeline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | from common import * 5 | 6 | def test_pipeline(): 7 | r = getconn() 8 | 9 | pipe = r.pipeline(transaction = False) 10 | 11 | pipe.set('a', 'a1').get('a').zadd('z', z1=1).zadd('z', z2=4) 12 | pipe.zincrby('z', 'z1').zrange('z', 0, 5, withscores=True) 13 | 14 | assert pipe.execute() == \ 15 | [ 16 | True, 17 | 'a1', 18 | True, 19 | True, 20 | 2.0, 21 | [('z1', 2.0), ('z2', 4)], 22 | ] 23 | 24 | def test_invalid_pipeline(): 25 | r = getconn() 26 | 27 | pipe = r.pipeline(transaction = False) 28 | 29 | pipe.set('a', 1).set('b', 2).lpush('a', 3).set('d', 4).get('a') 30 | result = pipe.execute(raise_on_error = False) 31 | 32 | assert result[0] 33 | assert result[1] 34 | 35 | # we can't lpush to a key that's a string value, so this should 36 | # be a ResponseError exception 37 | assert isinstance(result[2], redis.ResponseError) 38 | 39 | # since this isn't a transaction, the other commands after the 40 | # error are still executed 41 | assert result[3] 42 | assert result[4] == '1' 43 | 44 | # make sure the pipe was restored to a working state 45 | assert pipe.set('z', 'zzz').execute() == [True] 46 | 47 | def test_parse_error_raised(): 48 | r = getconn() 49 | 50 | pipe = r.pipeline(transaction = False) 51 | 52 | # the zrem is invalid because we don't pass any keys to it 53 | pipe.set('a', 1).zrem('b').set('b', 2) 54 | result = pipe.execute(raise_on_error = False) 55 | 56 | assert result[0] 57 | assert isinstance(result[1], redis.ResponseError) 58 | assert result[2] 59 | -------------------------------------------------------------------------------- /tests/test_redis/test_protocol.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from common import * 3 | from pprint import pprint 4 | 5 | def get_conn(): 6 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 7 | s.connect((nc.host(), nc.port())) 8 | s.settimeout(.3) 9 | return s 10 | 11 | def _test(req, resp, sleep=0): 12 | s = get_conn() 13 | 14 | for i in req: 15 | s.sendall(i) 16 | time.sleep(sleep) 17 | 18 | s.settimeout(.3) 19 | 20 | data = s.recv(10000) 21 | assert(data == resp) 22 | 23 | def test_slow(): 24 | req = '*1\r\n$4\r\nPING\r\n' 25 | resp = '+PONG\r\n' 26 | 27 | if large > 1000: 28 | sleep = 1 29 | else: 30 | sleep = .1 31 | 32 | _test(req, resp, sleep) 33 | 34 | def test_pingpong(): 35 | req = '*1\r\n$4\r\nPING\r\n' 36 | resp = '+PONG\r\n' 37 | _test(req, resp) 38 | 39 | def test_quit(): 40 | if nc.version() < '0.4.2': 41 | return 42 | req = '*1\r\n$4\r\nQUIT\r\n' 43 | resp = '+OK\r\n' 44 | _test(req, resp) 45 | 46 | def test_quit_without_recv(): 47 | if nc.version() < '0.4.2': 48 | return 49 | req = '*1\r\n$4\r\nQUIT\r\n' 50 | resp = '+OK\r\n' 51 | s = get_conn() 52 | 53 | s.sendall(req) 54 | s.close() 55 | info = nc._info_dict() 56 | #pprint(info) 57 | assert(info['ntest']['client_err'] == 1) 58 | 59 | def _test_bad(req): 60 | s = get_conn() 61 | 62 | s.sendall(req) 63 | data = s.recv(10000) 64 | print data 65 | 66 | assert('' == s.recv(1000)) # peer is closed 67 | 68 | def test_badreq(): 69 | reqs = [ 70 | # '*1\r\n$3\r\nPING\r\n', 71 | '\r\n', 72 | # '*3abcdefg\r\n', 73 | '*3\r\n*abcde\r\n', 74 | 75 | '*4\r\n$4\r\nMSET\r\n$1\r\nA\r\n$1\r\nA\r\n$1\r\nA\r\n', 76 | '*2\r\n$4\r\nMSET\r\n$1\r\nA\r\n', 77 | # '*3\r\n$abcde\r\n', 78 | # '*3\r\n$3abcde\r\n', 79 | # '*3\r\n$3\r\nabcde\r\n', 80 | ] 81 | 82 | for req in reqs: 83 | _test_bad(req) 84 | 85 | 86 | def test_wrong_argc(): 87 | s = get_conn() 88 | 89 | s.sendall('*1\r\n$3\r\nGET\r\n') 90 | assert('' == s.recv(1000)) # peer is closed 91 | -------------------------------------------------------------------------------- /tests/test_system/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bilibili/bilitw/a6c46feef6a0f41c15b11e96938658eec13d41f6/tests/test_system/__init__.py -------------------------------------------------------------------------------- /tests/test_system/test_reload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | #file : test_reload.py 4 | #author : ning 5 | #date : 2014-09-03 12:28:16 6 | 7 | import os 8 | import sys 9 | import redis 10 | 11 | PWD = os.path.dirname(os.path.realpath(__file__)) 12 | WORKDIR = os.path.join(PWD,'../') 13 | sys.path.append(os.path.join(WORKDIR,'lib/')) 14 | sys.path.append(os.path.join(WORKDIR,'conf/')) 15 | 16 | import conf 17 | 18 | from server_modules import * 19 | from utils import * 20 | from nose import with_setup 21 | 22 | CLUSTER_NAME = 'ntest' 23 | nc_verbose = int(getenv('T_VERBOSE', 5)) 24 | mbuf = int(getenv('T_MBUF', 512)) 25 | large = int(getenv('T_LARGE', 1000)) 26 | 27 | T_RELOAD_DELAY = 3 + 1 28 | 29 | all_redis = [ 30 | RedisServer('127.0.0.1', 2100, '/tmp/r/redis-2100/', CLUSTER_NAME, 'redis-2100'), 31 | RedisServer('127.0.0.1', 2101, '/tmp/r/redis-2101/', CLUSTER_NAME, 'redis-2101'), 32 | ] 33 | 34 | nc = NutCracker('127.0.0.1', 4100, '/tmp/r/nutcracker-4100', CLUSTER_NAME, 35 | all_redis, mbuf=mbuf, verbose=nc_verbose) 36 | 37 | def _setup(): 38 | print 'setup(mbuf=%s, verbose=%s)' %(mbuf, nc_verbose) 39 | for r in all_redis + [nc]: 40 | r.deploy() 41 | r.stop() 42 | r.start() 43 | 44 | def _teardown(): 45 | for r in all_redis + [nc]: 46 | assert(r._alive()) 47 | r.stop() 48 | 49 | def get_tcp_conn(host, port): 50 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 51 | s.connect((host, port)) 52 | s.settimeout(.3) 53 | return s 54 | 55 | def send_cmd(s, req, resp): 56 | s.sendall(req) 57 | data = s.recv(10000) 58 | assert(data == resp) 59 | 60 | @with_setup(_setup, _teardown) 61 | def test_reload_with_old_conf(): 62 | if nc.version() < '0.4.2': 63 | print 'Ignore test_reload for version %s' % nc.version() 64 | return 65 | pid = nc.pid() 66 | # print 'old pid:', pid 67 | r = redis.Redis(nc.host(), nc.port()) 68 | r.set('k', 'v') 69 | 70 | conn = get_tcp_conn(nc.host(), nc.port()) 71 | send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') 72 | 73 | # nc.reload() is same as nc.stop() and nc.start() 74 | nc.reload() 75 | time.sleep(.01) #it need time for the old process fork new process. 76 | 77 | # the old connection is still ok in T_RELOAD_DELAY seconds 78 | send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') 79 | 80 | # conn2 should connect to new instance 81 | conn2 = get_tcp_conn(nc.host(), nc.port()) 82 | send_cmd(conn2, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') 83 | 84 | # the old connection is still ok in T_RELOAD_DELAY seconds 85 | send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') 86 | 87 | time.sleep(T_RELOAD_DELAY) 88 | assert(pid != nc.pid()) 89 | 90 | # assert the old connection is closed. 91 | send_cmd(conn, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '') 92 | 93 | # conn2 should survive 94 | send_cmd(conn2, '*2\r\n$3\r\nGET\r\n$1\r\nk\r\n', '$1\r\nv\r\n') 95 | 96 | r = redis.Redis(nc.host(), nc.port()) 97 | rst = r.set('k', 'v') 98 | assert(r.get('k') == 'v') 99 | 100 | @with_setup(_setup, _teardown) 101 | def test_new_port(): 102 | if nc.version() < '0.4.2': 103 | print 'Ignore test_reload for version %s' % nc.version() 104 | return 105 | r = redis.Redis(nc.host(), nc.port()) 106 | r.set('k', 'v') 107 | 108 | content = ''' 109 | reload_test: 110 | listen: 0.0.0.0:4101 111 | hash: fnv1a_64 112 | distribution: modula 113 | redis: true 114 | timeout: 400 115 | servers: 116 | - 127.0.0.1:2100:1 redis-2100 117 | - 127.0.0.1:2101:1 redis-2101 118 | ''' 119 | 120 | nc.set_config(content) 121 | time.sleep(T_RELOAD_DELAY) 122 | 123 | r1 = redis.Redis(nc.host(), nc.port()) 124 | r2 = redis.Redis(nc.host(), 4101) 125 | 126 | assert_fail('Connection refused', r1.get, 'k') 127 | assert(r2.get('k') == 'v') 128 | 129 | @with_setup(_setup, _teardown) 130 | def test_pool_add_del(): 131 | if nc.version() < '0.4.2': 132 | print 'Ignore test_reload for version %s' % nc.version() 133 | return 134 | 135 | r = redis.Redis(nc.host(), nc.port()) 136 | 137 | r.set('k', 'v') 138 | content = ''' 139 | reload_test: 140 | listen: 0.0.0.0:4100 141 | hash: fnv1a_64 142 | distribution: modula 143 | redis: true 144 | servers: 145 | - 127.0.0.1:2100:1 redis-2100 146 | - 127.0.0.1:2101:1 redis-2101 147 | 148 | reload_test2: 149 | listen: 0.0.0.0:4101 150 | hash: fnv1a_64 151 | distribution: modula 152 | redis: true 153 | servers: 154 | - 127.0.0.1:2100:1 redis-2100 155 | - 127.0.0.1:2101:1 redis-2101 156 | ''' 157 | 158 | nc.set_config(content) 159 | time.sleep(T_RELOAD_DELAY) 160 | 161 | r1 = redis.Redis(nc.host(), nc.port()) 162 | r2 = redis.Redis(nc.host(), 4101) 163 | 164 | assert(r1.get('k') == 'v') 165 | assert(r2.get('k') == 'v') 166 | 167 | content = ''' 168 | reload_test: 169 | listen: 0.0.0.0:4102 170 | hash: fnv1a_64 171 | distribution: modula 172 | redis: true 173 | preconnect: true 174 | servers: 175 | - 127.0.0.1:2100:1 redis-2100 176 | - 127.0.0.1:2101:1 redis-2101 177 | ''' 178 | nc.set_config(content) 179 | time.sleep(T_RELOAD_DELAY) 180 | pid = nc.pid() 181 | print system('ls -l /proc/%s/fd/' % pid) 182 | 183 | r3 = redis.Redis(nc.host(), 4102) 184 | 185 | assert_fail('Connection refused', r1.get, 'k') 186 | assert_fail('Connection refused', r2.get, 'k') 187 | assert(r3.get('k') == 'v') 188 | 189 | fds = system('ls -l /proc/%s/fd/' % pid) 190 | sockets = [s for s in fds.split('\n') if strstr(s, 'socket:') ] 191 | # pool + stat + 2 backend + 1 client 192 | assert(len(sockets) == 5) 193 | 194 | -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #file : travis.sh 3 | #author : ning 4 | #date : 2014-05-10 16:54:43 5 | 6 | #install deps if we are in travis 7 | if [ -n "$TRAVIS" ]; then 8 | sudo apt-get install socat 9 | 10 | #python libs 11 | sudo pip install redis 12 | sudo pip install nose 13 | 14 | sudo pip install git+https://github.com/andymccurdy/redis-py.git@2.10.3 15 | sudo pip install git+https://github.com/idning/python-memcached.git#egg=memcache 16 | fi 17 | 18 | #build twemproxy 19 | CFLAGS="-ggdb3 -O0" autoreconf -fvi && ./configure --enable-debug=log && make 20 | 21 | ln -s `pwd`/src/nutcracker tests/_binaries/ 22 | cp `which redis-server` tests/_binaries/ 23 | cp `which redis-cli` tests/_binaries/ 24 | cp `which memcached` tests/_binaries/ 25 | 26 | #run test 27 | cd tests/ && nosetests --nologcapture -x -v 28 | 29 | --------------------------------------------------------------------------------