diff --git a/LICENSE b/LICENSE index d522c61..6ea4fc1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2017, joyieldInc +Copyright (c) 2017, Joyield, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ed82317 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ + +.PHONY : default debug clean + +make = make +plt = $(shell uname) +ifeq ($(plt), FreeBSD) + make = gmake +else ifeq ($(plt), OpenBSD) + make = gmake +endif + +default: + @$(make) -C src -f Makefile + +debug: + @$(make) -C src -f Makefile debug + +clean: + @$(make) -C src -f Makefile clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7a6868 --- /dev/null +++ b/README.md @@ -0,0 +1,118 @@ +# Predixy + +**Predixy** is a high performance and full features proxy for [redis](http://redis.io/) + +## Features + ++ High performance and lightweight. ++ Multi-threads support. ++ Works on Linux, OSX, BSD, Windows([Cygwin](http://www.cygwin.com/)). ++ Supports Redis Sentinel, single/multi redis group[s]. ++ Supports Redis Cluster. ++ Supports redis block command, eg:blpop, brpop, brpoplpush. ++ Supports scan command, even multi redis instances. ++ Multi-databases support, means redis command select is avaliable. ++ Supports redis transaction, limit in Redis Sentinel single redis group. ++ Supports redis Scripts, script load, eval, evalsha. ++ Supports redis Pub/Sub. ++ Multi-DataCenters support. ++ Extend AUTH, readonly/readwrite/admin permission, keyspace limit. ++ Log level sample, async log record. ++ Log file auto rotate by time and/or file size. ++ Stats info, CPU/Memory/Requests/Responses and so on. ++ Latency monitor. + +## Build + +Predixy can be compiled and used on Linux, OSX, BSD, Windows([Cygwin](http://www.cygwin.com/)). + +It is as simple as: + + $ make + +To build in debug mode: + + $ make debug + +Some other build options: ++ CXX=c++compiler, default is g++, you can specify other, eg:CXX=clang++ ++ EV=epoll|poll|kqueue, default it is auto detect according by platform. ++ MT=false, disable multi-threads support. ++ TS=true, enable predixy function call time stats, debug only for developer. + +For examples: + + $ make CXX=clang++ + $ make EV=poll + $ make MT=false + $ make debug MT=false TS=true + +## Configuration + +See below files: ++ predixy.conf, basic config. ++ cluster.conf, Redis Cluster backend config. ++ sentinel.conf, Redis Sentinel backend config. ++ auth.conf, authority control config. ++ dc.conf, multi-datacenters config. ++ latency.conf, latency monitor config. + +## Running + + $ ./predixy ../conf/predixy.conf + +With default predixy.conf, Predixy will proxy to Redis Cluster 127.0.0.1:6379, +In general, 127.0.0.1:6379 is not running in Redis Cluster mode, +So you will look mass log output. But you can still test it with redis-cli. + + $ redis-cli -p 7617 info + +More command line arguments: + + $ ./predixy -h + +## Stats + +Like redis, predixy use INFO command to give stats. + +Show predixy running info and latency monitors + + redis> INFO + +Show latency monitors by latency name + + redis> INFO Latency + +A latency monitor example: + + LatencyMonitorName:all + latency(us) sum(us) counts + <= 100 3769836 91339 91.34% + <= 200 777185 5900 97.24% + <= 300 287565 1181 98.42% + <= 400 185891 537 98.96% + <= 500 132773 299 99.26% + <= 600 85050 156 99.41% + <= 700 85455 133 99.54% + <= 800 40088 54 99.60% + <= 1000 67788 77 99.68% + > 1000 601012 325 100.00% + T 60 6032643 100001 + The last line is total summary, 624 is average latency(us) + + +Show latency monitors by server address and latency name + + redis> INFO ServerLatency [latency-name] + +Reset all stats and latency monitors + + redis> INFO ResetStats + +## License + +Copyright (C) 2017 Joyield, Inc. gmail.com> + +All rights reserved. + +License under BSD 3-clause "New" or "Revised" License diff --git a/conf/auth.conf b/conf/auth.conf new file mode 100644 index 0000000..226bd26 --- /dev/null +++ b/conf/auth.conf @@ -0,0 +1,71 @@ +## Authority control +## Authority { +## Auth [password] { +## Mode read|write|admin +## [KeyPrefix Prefix1 Prefix2...] +## [ReadKeyPrefix Prefix1 Prefix2...] +## [WriteKeyPrefix Prefix1 Prefix2...] +## }... +## } + +## Example: +# Authority { +##------------------------------------------------------------------------ +# Auth { +# Mode read +# } +#### password is empty, this Auth is default auth +#### Mode read, client connection is readonly, +#### No KeyPrefix or ReadKeyPrefix defined, all key can be visit +##------------------------------------------------------------------------ +# Auth abc { +# Mode write +# } +#### password is "abc", the client must send command Auth abc +#### Mode write, client connection can read and write +#### No KeyPrefix, ReadKeyPrefix, WriteKeyPrefix define, all key can be visit +##------------------------------------------------------------------------ +# Auth bcd { +# Mode admin +# } +#### password is "abc", the client must send command Auth bcd +#### Mode admin, client connection can read and write and admin, +#### the CONFIG command need admin permission +#### No KeyPrefix, ReadKeyPrefix, WriteKeyPrefix define, all key can be visit +##------------------------------------------------------------------------ +# Auth cde { +# Mode read +# KeyPrefix User +# } +#### password is "cde", the client must send command Auth cde +#### Mode read, client connection is readonly, +#### KeyPrefix User, client can read UserXXX key, eg: GET User.123, +#### if client request GET hello, will be deny +##------------------------------------------------------------------------ +# Auth def { +# Mode write +# ReadKeyPrefix User Stats +# WriteKeyPrefix User +# } +#### password is "cde", the client must send command Auth cde +#### Mode read, client connection can read and write, but read and write +#### keyspace is diffrent, client can GET User.123 and also +#### SET User.123 SomeValue, but SET Stats.123 will be deny +##------------------------------------------------------------------------ +# } +## if no Authority spcified, equality below Authority +# Authority { +# Auth { +# Mode admin +# } +# } + +Authority { + Auth { + Mode write + } + Auth "#a complex password#" { + Mode admin + } +} + diff --git a/conf/cluster.conf b/conf/cluster.conf new file mode 100644 index 0000000..583a3d1 --- /dev/null +++ b/conf/cluster.conf @@ -0,0 +1,31 @@ +## redis cluster server pool define + +##ClusterServerPool { +## [Password xxx] #default no +## [MasterReadPriority [0-100]] #default 50 +## [StaticSlaveReadPriority [0-100]] #default 0 +## [DynamicSlaveReadPriority [0-100]] #default 0 +## [RefreshInterval seconds] #default 1 +## [ServerFailureLimit number] #default 10 +## [ServerRetryTimeout seconds] #default 1 +## Servers { +## + addr +## ... +## } +##} + + +## Examples: +#ClusterServerPool { +# MasterReadPriority 60 +# StaticSlaveReadPriority 50 +# DynamicSlaveReadPriority 50 +# RefreshInterval 1 +# ServerFailureLimit 10 +# ServerRetryTimeout 1 +# Servers { +# + 192.168.2.107:2211 +# + 192.168.2.107:2212 +# } +#} + diff --git a/conf/dc.conf b/conf/dc.conf new file mode 100644 index 0000000..6577582 --- /dev/null +++ b/conf/dc.conf @@ -0,0 +1,47 @@ +## DataCenter +## DataCenter { +## DC name { +## AddrPrefix { +## + IpPrefix +## ... +## } +## ReadPolicy { +## name priority [weight] +## other priority [weight] +## } +## } +## ... +## } +## Examples: +#DataCenter { +# DC bj { +# AddrPrefix { +# + 10.1 +# } +# ReadPolicy { +# bj 50 +# sh 20 +# sz 10 +# } +# } +# DC sh { +# AddrPrefix { +# + 10.2 +# } +# ReadPolicy { +# sh 50 +# bj 20 5 +# sz 20 2 +# } +# } +# DC sz { +# AddrPrefix { +# + 10.3 +# } +# ReadPolicy { +# sz 50 +# sh 20 +# bj 10 +# } +# } +#} diff --git a/conf/latency.conf b/conf/latency.conf new file mode 100644 index 0000000..48c72eb --- /dev/null +++ b/conf/latency.conf @@ -0,0 +1,133 @@ +## LatencyMonitor record command time eplapsed +## redis command INFO will show the latency monitor results +## +## see predixy info, include latency monitor for predixy +## redis> INFO +## +## see latency monitor for specify latency name +## redis> INFO Latency name +## +## see latency monitor for specify server +## redis> INFO ServerLatency ServAddr [name] +## +## reset all stats info, include latency monitor +## redis> INFO ResetStats +## +## Examples: +## LatencyMonitor name { +## Commands { +## + cmd +## [- cmd] +## ... +## } +## TimeSpan { +## + TimeElapsedUS +## ... +## } +## } +## cmd is redis commands, "all" means all commands + +LatencyMonitor all { + Commands { + + all + - blpop + - brpop + - brpoplpush + } + TimeSpan { + + 100 + + 200 + + 300 + + 400 + + 500 + + 600 + + 700 + + 800 + + 900 + + 1000 + + 1200 + + 1400 + + 1600 + + 1700 + + 1800 + + 2000 + + 2500 + + 3000 + + 3500 + + 4000 + + 4500 + + 5000 + + 6000 + + 7000 + + 8000 + + 9000 + + 10000 + } +} + +LatencyMonitor get { + Commands { + + get + } + TimeSpan { + + 100 + + 200 + + 300 + + 400 + + 500 + + 600 + + 700 + + 800 + + 900 + + 1000 + } +} + +LatencyMonitor set { + Commands { + + set + + setnx + + setex + } + TimeSpan { + + 100 + + 200 + + 300 + + 400 + + 500 + + 600 + + 700 + + 800 + + 900 + + 1000 + } +} + +LatencyMonitor blist { + Commands { + + blpop + + brpop + + brpoplpush + } + TimeSpan { + + 1000 + + 2000 + + 3000 + + 4000 + + 5000 + + 6000 + + 7000 + + 8000 + + 9000 + + 10000 + + 20000 + + 30000 + + 40000 + + 50000 + + 60000 + + 70000 + + 80000 + + 90000 + + 100000 + } +} diff --git a/conf/predixy.conf b/conf/predixy.conf new file mode 100644 index 0000000..aa76713 --- /dev/null +++ b/conf/predixy.conf @@ -0,0 +1,98 @@ +################################### GENERAL #################################### +## Predixy configuration file example + +## Specify a name for this predixy service +## redis command INFO can get this +Name PredixyExample + +## Specify listen address, support IPV4, IPV6, Unix socket +## Examples: +# Bind 127.0.0.1:7617 +# Bind 0.0.0.0:7617 +# Bind /tmp/predixy + +## Default is 0.0.0.0:7617 +# Bind 0.0.0.0:7617 + +## Worker threads +WorkerThreads 1 + +## Memory limit, 0 means unlimited + +## Examples: +# MaxMemory 100M +# MaxMemory 1G +# MaxMemory 0 + +## MaxMemory can change online by CONFIG SET MaxMemory xxx +## Default is 0 +# MaxMemory 0 + +## Close the connection after a client is idle for N seconds (0 to disable) +## ClientTimeout can change online by CONFIG SET ClientTimeout N +## Default is 0 +ClientTimeout 300 + + +## IO buffer size +## Default is 4096 +# BufSize 4096 + +################################### LOG ######################################## +## Log file path +## Unspecify will log to stdout +## Default is Unspecified +# Log ./predixy.log + +## LogRotate support + +## 1d rotate log every day +## nh rotate log every n hours 1 <= n <= 24 +## nm rotate log every n minutes 1 <= n <= 1440 +## nG rotate log evenry nG bytes +## nM rotate log evenry nM bytes +## time rotate and size rotate can combine eg 1h 2G, means 1h or 2G roate a time + +## Examples: +# LogRotate 1d 2G +# LogRotate 1d + +## Default is disable LogRotate + + +## In multi-threads, worker thread log need lock, +## AllowMissLog can reduce lock time for improve performance +## AllowMissLog can change online by CONFIG SET AllowMissLog true|false +## Default is true +# AllowMissLog false + +## LogLevelSample, output a log every N +## all level sample can change online by CONFIG SET LogXXXSample N +LogVerbSample 0 +LogDebugSample 0 +LogInfoSample 10000 +LogNoticeSample 1 +LogWarnSample 1 +LogErrorSample 1 + + +################################### AUTHORITY ################################## +Include auth.conf + +################################### SERVERS #################################### +# Include cluster.conf +# Include sentinel.conf +Include try.conf + + +################################### DATACENTER ################################# +## LocalDC specify current machine dc +# LocalDC bj + +## see dc.conf +# Include dc.conf + + +################################### LATENCY #################################### +## Latency monitor define, see latency.conf +Include latency.conf diff --git a/conf/sentinel.conf b/conf/sentinel.conf new file mode 100644 index 0000000..305c3dc --- /dev/null +++ b/conf/sentinel.conf @@ -0,0 +1,47 @@ +## redis sentinel server pool define + +##SentinelServerPool { +## [Password xxx] #default no +## [Databases number] #default 1 +## Hash atol|crc16 +## [HashTag "xx"] #default no +## Distribution modula|random +## [MasterReadPriority [0-100]] #default 50 +## [StaticSlaveReadPriority [0-100]] #default 0 +## [DynamicSlaveReadPriority [0-100]] #default 0 +## [RefreshInterval seconds] #default 1 +## [ServerFailureLimit number] #default 10 +## [ServerRetryTimeout seconds] #default 1 +## Sentinels { +## + addr +## ... +## } +## Group xxx { +## [+ addr] +## ... +## } +##} + + +## Examples: +#SentinelServerPool { +# Databases 16 +# Hash crc16 +# HashTag "{}" +# Distribution modula +# MasterReadPriority 60 +# StaticSlaveReadPriority 50 +# DynamicSlaveReadPriority 50 +# RefreshInterval 1 +# ServerFailureLimit 10 +# ServerRetryTimeout 1 +# Sentinels { +# + 10.2.2.2 +# + 10.2.2.3 +# + 10.2.2.4 +# } +# Group shard001 { +# } +# Group shard002 { +# } +#} diff --git a/conf/try.conf b/conf/try.conf new file mode 100644 index 0000000..ca9eb26 --- /dev/null +++ b/conf/try.conf @@ -0,0 +1,7 @@ +## This conf is only for test + +ClusterServerPool { + Servers { + + 127.0.0.1:6379 + } +} diff --git a/src/AcceptConnection.cpp b/src/AcceptConnection.cpp new file mode 100644 index 0000000..677e159 --- /dev/null +++ b/src/AcceptConnection.cpp @@ -0,0 +1,246 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "AcceptConnection.h" +#include "Conf.h" +#include "Handler.h" + + +AcceptConnection::AcceptConnection(int fd, sockaddr* addr, socklen_t len): + AcceptSocket(fd, addr, len), + mAuth(nullptr), + mConnectConnection(nullptr), + mLastActiveTime(0), + mBlockRequest(false) +{ + mClassType = Connection::AcceptType; +} + +AcceptConnection::~AcceptConnection() +{ + close(); +} + +void AcceptConnection::close() +{ + AcceptSocket::close(); + while (!mRequests.empty()) { + auto req = mRequests.front(); + req->detach(); + mRequests.pop_front(); + } +} + +bool AcceptConnection::writeEvent(Handler* h) +{ + FuncCallTimer(); + IOVec bufs[Const::MaxIOVecLen]; + bool finished = true; + while (true) { + int len = fill(h, bufs, Const::MaxIOVecLen); + if (len == 0) { + finished = true; + break; + } + finished = write(h, bufs, len); + if (!finished || len < Const::MaxIOVecLen) { + break; + } + } + return finished; +} + +int AcceptConnection::fill(Handler* h, IOVec* bufs, int len) +{ + FuncCallTimer(); + int cnt = 0; + Request* req = mRequests.empty() ? nullptr : mRequests.front(); + while (req && len > 0) { + if (!req->isDone()) { + if (!isBlockRequest()) { + while (req) { + if (req->isDelivered()) { + break; + } + h->handleRequest(req); + req = mRequests.next(req); + } + } + break; + } + auto res = req->getResponse(); + Request* next = mRequests.next(req); + if (res) { + logDebug("h %d c %s %d req %ld fill res %ld", + h->id(), peer(), fd(), req->id(), res->id()); + int n = res->fill(bufs, len, req); + bufs += n; + cnt += n; + len -= n; + } else if (cnt == 0) {//req no res and req is first in list + mRequests.pop_front(); + logDebug("h %d c %s %d req %ld in front is done and no res", + h->id(), peer(), fd(), req->id()); + } + req = next; + } + return cnt; +} + +bool AcceptConnection::write(Handler* h, IOVec* bufs, int len) +{ + FuncCallTimer(); + logDebug("h %d c %s %d writev %d", + h->id(), peer(), fd(), len); + struct iovec iov[Const::MaxIOVecLen]; + for (int i = 0; i < len; ++i) { + iov[i].iov_base = bufs[i].dat; + iov[i].iov_len = bufs[i].len; + } + int num = writev(iov, len); + if (num < 0) { + logDebug("h %d c %s %d writev %d fail %s", + h->id(), peer(), fd(), len, StrError()); + return false; + } else if (num == 0) { + return len == 0; + } + h->stats().sendClientBytes += num; + IOVec* vec = nullptr; + int i; + for (i = 0; i < len && num > 0; ++i) { + vec = bufs + i; + int n = vec->len; + vec->seg->seek(vec->buf, vec->pos, n <= num ? n : num); + if (vec->req && n <= num) { + while (!mRequests.empty()) { + auto req = mRequests.pop_front(); + if (vec->req == req) { + logVerb("h %d c %s %d req %ld res sent", + h->id(), peer(), fd(), req->id()); + req->clear(); + break; + } else { + logVerb("h %d c %s %d req %ld no res", + h->id(), peer(), fd(), req->id()); + req->clear(); + } + } + } + num -= n; + } + return i == len && num == 0; +} + +void AcceptConnection::readEvent(Handler* h) +{ + FuncCallTimer(); + while (true) { + auto buf = getBuffer(h, mParser.isIdle()); + int pos = buf->length(); + int len = buf->room(); + int n = read(buf->tail(), len); + if (n > 0) { + buf->use(n); + h->stats().recvClientBytes += n; + parse(h, buf, pos); + } + if (n < len) { + break; + } + } +} + +void AcceptConnection::parse(Handler* h, Buffer* buf, int pos) +{ + FuncCallTimer(); + bool goOn = true; + bool split = h->proxy()->isSplitMultiKey() && !inTransaction(); + while (pos < buf->length() && goOn) { + auto ret = mParser.parse(buf, pos, split); + switch (ret) { + case RequestParser::Normal: + goOn = false; + break; + case RequestParser::Partial: + { + Request* req = RequestAlloc::create(this); + mRequests.push_back(req); + if (!mReqLeader) { + mReqLeader = req; + } + req->set(mParser, mReqLeader); + h->handleRequest(req); + } + break; + case RequestParser::Complete: + { + Request* req = RequestAlloc::create(this); + mRequests.push_back(req); + if (mReqLeader) { + req->set(mParser, mReqLeader); + mReqLeader = nullptr; + } else { + req->set(mParser); + } + h->handleRequest(req); + mParser.reset(); + } + break; + case RequestParser::CmdError: + { + Request* req = RequestAlloc::create(this); + mRequests.push_back(req); + if (inTransaction()) { + req->set(mParser); + h->handleRequest(req); + } else { + SegmentStr cmd(mParser.cmd()); + ResponsePtr res = ResponseAlloc::create(); + char err[1024]; + int len = snprintf(err, sizeof(err), "unknown command '%.*s'", + cmd.length(), cmd.data()); + res->setErr(err, len); + h->handleResponse(nullptr, req, res); + } + mParser.reset(); + } + break; + case RequestParser::ArgError: + { + Request* req = RequestAlloc::create(this); + mRequests.push_back(req); + if (inTransaction()) { + req->set(mParser); + h->handleRequest(req); + } else { + ResponsePtr res = ResponseAlloc::create(); + char err[1024]; + int len = snprintf(err, sizeof(err), + "wrong number of arguments for '%s' command", + Command::get(mParser.type()).name); + res->setErr(err, len); + h->handleResponse(nullptr, req, res); + } + mParser.reset(); + } + break; + default: + setStatus(ParseError); + goOn = false; + break; + } + } +} + +bool AcceptConnection::send(Handler* h, Request* req, Response* res) +{ + FuncCallTimer(); + if (mRequests.front()->isDone()) { + return true; + } + return false; +} diff --git a/src/AcceptConnection.h b/src/AcceptConnection.h new file mode 100644 index 0000000..2f55a1b --- /dev/null +++ b/src/AcceptConnection.h @@ -0,0 +1,102 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_ACCEPT_CONNECTION_H_ +#define _PREDIXY_ACCEPT_CONNECTION_H_ + +#include "Predixy.h" +#include "AcceptSocket.h" +#include "Connection.h" +#include "Transaction.h" +#include "Subscribe.h" +#include "RequestParser.h" +#include "Request.h" +#include "Response.h" + +class AcceptConnection : + public AcceptSocket, + public Connection, + public Transaction, + public Subscribe, + public ListNode>, + public DequeNode>, + public RefCntObj +{ +public: + typedef AcceptConnection Value; + typedef ListNode> ListNodeType; + typedef DequeNode> DequeNodeType; +public: + AcceptConnection(int fd, sockaddr* addr, socklen_t len); + ~AcceptConnection(); + void close(); + bool writeEvent(Handler* h); + void readEvent(Handler* h); + bool send(Handler* h, Request* req, Response* res); + long lastActiveTime() const + { + return mLastActiveTime; + } + void setLastActiveTime(long v) + { + mLastActiveTime = v; + } + bool empty() const + { + return mRequests.empty(); + } + ConnectConnection* connectConnection() const + { + return mConnectConnection; + } + void attachConnectConnection(ConnectConnection* s) + { + mConnectConnection = s; + } + void detachConnectConnection() + { + mConnectConnection = nullptr; + } + const Auth* auth() const + { + return mAuth; + } + void setAuth(const Auth* auth) + { + mAuth = auth; + } + bool isBlockRequest() const + { + return mBlockRequest; + } + void setBlockRequest(bool v) + { + mBlockRequest = v; + } + void append(Request* req) + { + mRequests.push_back(req); + } +private: + void parse(Handler* h, Buffer* buf, int pos); + int fill(Handler* h, IOVec* bufs, int len); + bool write(Handler* h, IOVec* bufs, int len); +private: + RequestParser mParser; + RecvRequestList mRequests; + RequestPtr mReqLeader; + ResponseList mResponses; + const Auth* mAuth; + ConnectConnection* mConnectConnection; + long mLastActiveTime; //steady us + bool mBlockRequest; +}; + +typedef List AcceptConnectionList; +typedef Deque AcceptConnectionDeque; +typedef Alloc AcceptConnectionAlloc; + +#endif diff --git a/src/AcceptSocket.cpp b/src/AcceptSocket.cpp new file mode 100644 index 0000000..b739d8d --- /dev/null +++ b/src/AcceptSocket.cpp @@ -0,0 +1,41 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "AcceptSocket.h" + + +AcceptSocket::AcceptSocket(int fd, sockaddr* addr, socklen_t len): + Socket(fd) +{ + mClassType = AcceptType; + mPeer[0] = '\0'; + switch (addr->sa_family) { + case AF_INET: + { + char host[INET_ADDRSTRLEN]; + sockaddr_in* in = (sockaddr_in*)addr; + inet_ntop(AF_INET, (void*)&in->sin_addr, host, sizeof(host)); + int port = ntohs(in->sin_port); + snprintf(mPeer, sizeof(mPeer), "%s:%d", host, port); + } + break; + case AF_INET6: + { + char host[INET6_ADDRSTRLEN]; + sockaddr_in6* in = (sockaddr_in6*)addr; + inet_ntop(AF_INET6, (void*)&in->sin6_addr, host, sizeof(host)); + int port = ntohs(in->sin6_port); + snprintf(mPeer, sizeof(mPeer), "%s:%d", host, port); + } + break; + case AF_UNIX: + snprintf(mPeer, sizeof(mPeer), "unix"); + break; + default: + snprintf(mPeer, sizeof(mPeer), "unknown"); + break; + } +} diff --git a/src/AcceptSocket.h b/src/AcceptSocket.h new file mode 100644 index 0000000..f951510 --- /dev/null +++ b/src/AcceptSocket.h @@ -0,0 +1,25 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_ACCEPT_SOCKET_H_ +#define _PREDIXY_ACCEPT_SOCKET_H_ + +#include "Socket.h" + +class AcceptSocket : public Socket +{ +public: + AcceptSocket(int fd, sockaddr* addr, socklen_t len); + const char* peer() const + { + return mPeer; + } +private: + char mPeer[32]; +}; + + +#endif diff --git a/src/Alloc.cpp b/src/Alloc.cpp new file mode 100644 index 0000000..163033e --- /dev/null +++ b/src/Alloc.cpp @@ -0,0 +1,10 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Alloc.h" + +long AllocBase::MaxMemory(0); +AtomicLong AllocBase::UsedMemory(0); diff --git a/src/Alloc.h b/src/Alloc.h new file mode 100644 index 0000000..12b6c91 --- /dev/null +++ b/src/Alloc.h @@ -0,0 +1,250 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_ALLOC_H_ +#define _PREDIXY_ALLOC_H_ + +#include +#include +#include +#include "Sync.h" +#include "Exception.h" +#include "Util.h" +#include "Logger.h" +#include "Timer.h" + +class AllocBase +{ +public: + DefException(MemLimit); +public: + static void setMaxMemory(long m) + { + MaxMemory = m; + } + static long getMaxMemory() + { + return MaxMemory; + } + static long getUsedMemory() + { + return UsedMemory; + } +protected: + static long MaxMemory; + static AtomicLong UsedMemory; +}; + +template +inline int allocSize() +{ + return sizeof(T); +} + +template +class Alloc : public AllocBase +{ +public: + template + static T* create(Targs&&... args) + { + FuncCallTimer(); + T* obj = nullptr; + if (Size > 0) { + obj = Free[--Size]; + } + if (obj) { + try { + new ((void*)obj) T(args...); + } catch (...) { + bool del = true; + if (Size < CacheSize) { + Free[Size++] = obj; + del = false; + } + if (del) { + UsedMemory -= allocSize(); + ::operator delete((void*)obj); + } + throw; + } + logVerb("alloc create object with old memory %d @%p", allocSize(), obj); + return obj; + } + UsedMemory += allocSize(); + if (MaxMemory == 0 || UsedMemory <= MaxMemory) { + void* p = ::operator new(allocSize()); + if (p) { + try { + obj = new (p) T(args...); + logVerb("alloc create object with new memory %d @%p", allocSize(), obj); + return obj; + } catch (...) { + UsedMemory -= allocSize(); + ::operator delete(p); + throw; + } + } else { + UsedMemory -= allocSize(); + Throw(MemLimit, "system memory alloc fail"); + } + } else { + UsedMemory -= allocSize(); + Throw(MemLimit, "maxmemory used"); + } + return nullptr; + } + static void destroy(T* obj) + { + FuncCallTimer(); + bool del = true; + obj->~T(); + if (Size < CacheSize) { + Free[Size++] = obj; + del = false; + } + if (del) { + UsedMemory -= allocSize(); + ::operator delete((void*)obj); + } + logVerb("alloc destroy object with size %d @%p delete %s", (int)allocSize(), obj, del ? "true" : "false"); + } +private: + thread_local static T* Free[CacheSize]; + thread_local static int Size; +}; + +template +thread_local T* Alloc::Free[CacheSize]; +template +thread_local int Alloc::Size = 0; + +template +class RefCntObj +{ +public: + RefCntObj(): + mCnt(0) + { + } + RefCntObj(const RefCntObj&) = delete; + RefCntObj& operator=(const RefCntObj&) = delete; + int count() const + { + return mCnt; + } + void ref() + { + ++mCnt; + } + void unref() + { + int n = --mCnt; + if (n == 0) { + Alloc::destroy(static_cast(this)); + } else if (n < 0) { + logError("unref object %p with cnt %d", this, n); + abort(); + } + } +protected: + ~RefCntObj() + { + mCnt = 0; + } +private: + AtomicInt mCnt; +}; + +template +class SharePtr +{ +public: + SharePtr(): + mObj(nullptr) + { + } + SharePtr(T* obj): + mObj(obj) + { + if (obj) { + obj->ref(); + } + } + SharePtr(const SharePtr& sp): + mObj(sp.mObj) + { + if (mObj) { + mObj->ref(); + } + } + SharePtr(SharePtr&& sp): + mObj(sp.mObj) + { + sp.mObj = nullptr; + } + ~SharePtr() + { + if (mObj) { + mObj->unref(); + mObj = nullptr; + } + } + operator T*() const + { + return mObj; + } + SharePtr& operator=(const SharePtr& sp) + { + if (this != &sp) { + T* obj = mObj; + mObj = sp.mObj; + if (mObj) { + mObj->ref(); + } + if (obj) { + obj->unref(); + } + } + return *this; + } + T& operator*() + { + return *mObj; + } + const T& operator*() const + { + return *mObj; + } + T* operator->() + { + return mObj; + } + const T* operator->() const + { + return mObj; + } + bool operator!() const + { + return !mObj; + } + operator bool() const + { + return mObj; + } + bool operator==(const SharePtr& sp) const + { + return mObj == sp.mObj; + } + bool operator!=(const SharePtr& sp) const + { + return mObj != sp.mObj; + } +private: + T* mObj; +}; + +#endif diff --git a/src/Auth.cpp b/src/Auth.cpp new file mode 100644 index 0000000..284e099 --- /dev/null +++ b/src/Auth.cpp @@ -0,0 +1,106 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include "Auth.h" +#include "Conf.h" +#include "Request.h" + + +Auth::Auth(): + mMode(Command::Read|Command::Write|Command::Admin), + mReadKeyPrefix(nullptr), + mWriteKeyPrefix(nullptr) +{ +} + +Auth::Auth(const AuthConf& conf): + mPassword(conf.password.c_str(), conf.password.size()), + mMode(conf.mode), + mReadKeyPrefix(nullptr), + mWriteKeyPrefix(nullptr) +{ + if (!conf.readKeyPrefix.empty()) { + mReadKeyPrefix = new KeyPrefixSet(conf.readKeyPrefix.begin(), conf.readKeyPrefix.end()); + } + if (!conf.writeKeyPrefix.empty()) { + mWriteKeyPrefix = new KeyPrefixSet(conf.writeKeyPrefix.begin(), conf.writeKeyPrefix.end()); + } + if ((!mReadKeyPrefix || !mWriteKeyPrefix) && !conf.keyPrefix.empty()) { + auto kp = new KeyPrefixSet(conf.keyPrefix.begin(), conf.keyPrefix.end()); + if (!mReadKeyPrefix) { + mReadKeyPrefix = kp; + } + if (!mWriteKeyPrefix) { + mWriteKeyPrefix = kp; + } + } +} + +Auth::~Auth() +{ + if (mReadKeyPrefix) { + delete mReadKeyPrefix; + } + if (mWriteKeyPrefix && mWriteKeyPrefix != mReadKeyPrefix) { + delete mWriteKeyPrefix; + } +} + +bool Auth::permission(Request* req, const String& key) const +{ + auto& cmd = Command::get(req->type()); + if (!(mMode & cmd.mode)) { + return false; + } + const KeyPrefixSet* kp = nullptr; + if (cmd.mode & Command::Read) { + kp = mReadKeyPrefix; + } else if (cmd.mode & Command::Write) { + kp = mWriteKeyPrefix; + } + if (kp) { + auto it = kp->upper_bound(key); + const String* p = nullptr; + if (it == kp->end()) { + p = &(*kp->rbegin()); + } else if (it != kp->begin()) { + p = &(*--it); + } + return p && key.hasPrefix(*p); + } + return true; +} + +Auth Authority::DefaultAuth; + +Authority::Authority(): + mDefault(&DefaultAuth) +{ +} + +Authority::~Authority() +{ + for (auto& i : mAuthMap) { + delete i.second; + } + mAuthMap.clear(); +} + +void Authority::add(const AuthConf& ac) +{ + Auth* a = new Auth(ac); + auto it = mAuthMap.find(a->password()); + if (it != mAuthMap.end()) { + Auth* p = it->second; + mAuthMap.erase(it); + delete p; + } + mAuthMap[a->password()] = a; + if (a->password().empty()) { + mDefault = a; + } +} diff --git a/src/Auth.h b/src/Auth.h new file mode 100644 index 0000000..1f1b3c5 --- /dev/null +++ b/src/Auth.h @@ -0,0 +1,59 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_AUTH_H_ +#define _PREDIXY_AUTH_H_ + +#include +#include +#include +#include "Predixy.h" + +class Auth +{ +public: + Auth(); + Auth(const AuthConf& conf); + ~Auth(); + const String& password() const + { + return mPassword; + } + bool permission(Request* req, const String& key) const; +private: + String mPassword; + int mMode; + typedef std::set KeyPrefixSet; + KeyPrefixSet* mReadKeyPrefix; + KeyPrefixSet* mWriteKeyPrefix; +}; + +class Authority +{ +public: + Authority(); + ~Authority(); + bool hasAuth() const + { + return !mAuthMap.empty(); + } + Auth* get(const String& pd) const + { + auto it = mAuthMap.find(pd); + return it == mAuthMap.end() ? nullptr : it->second; + } + Auth* getDefault() const + { + return mDefault; + } + void add(const AuthConf& ac); +private: + std::map mAuthMap; + Auth* mDefault; + static Auth DefaultAuth; +}; + +#endif diff --git a/src/Backtrace.h b/src/Backtrace.h new file mode 100644 index 0000000..ab3d419 --- /dev/null +++ b/src/Backtrace.h @@ -0,0 +1,42 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_BACKTRACE_H_ +#define _PREDIXY_BACKTRACE_H_ + +#include "Logger.h" + +#if _PREDIXY_BACKTRACE_ + +#include +inline void traceInfo() +{ +#define Size 128 + logError("predixy backtrace"); + void* buf[Size]; + int num = ::backtrace(buf, Size); + int fd = -1; + if (Logger::gInst) { + fd = Logger::gInst->logFileFd(); + if (fd >= 0) { + backtrace_symbols_fd(buf, num, fd); + } + } + if (fd != STDOUT_FILENO) { + backtrace_symbols_fd(buf, num, STDOUT_FILENO); + } +} + +#else + +inline void traceInfo() +{ + logError("predixy backtrace, but current system unspport backtrace"); +} + +#endif + +#endif diff --git a/src/Buffer.cpp b/src/Buffer.cpp new file mode 100644 index 0000000..d6d89b4 --- /dev/null +++ b/src/Buffer.cpp @@ -0,0 +1,381 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "String.h" +#include "Logger.h" +#include "Buffer.h" +#include "IOVec.h" + +int Buffer::Size = 4096 - sizeof(Buffer); + +Buffer::Buffer(): + mLen(0) +{ +} + +Buffer::Buffer(const Buffer& oth): + mLen(oth.mLen) +{ + memcpy(mDat, oth.mDat, mLen); +} + +Buffer::~Buffer() +{ + SharePtr p = ListNode>::next(); + ListNode>::reset(); + while (p) { + if (p->count() == 1) { + p = p->next(); + } else { + break; + } + } +} + +Buffer& Buffer::operator=(const Buffer& oth) +{ + if (this != &oth) { + mLen = oth.mLen; + memcpy(mDat, oth.mDat, mLen); + } + return *this; +} + +Buffer* Buffer::append(const char* str) +{ + return append(str, strlen(str)); +} + +Buffer* Buffer::append(const char* dat, int len) +{ + if (len <= 0) { + return this; + } + Buffer* buf = this; + while (len > 0) { + if (len <= buf->room()) { + memcpy(buf->tail(), dat, len); + buf->use(len); + break; + } else { + int n = buf->room(); + memcpy(buf->tail(), dat, n); + buf->use(n); + dat += n; + len -= n; + Buffer* nbuf = BufferAlloc::create(); + buf->concat(nbuf); + buf = nbuf; + } + } + return buf; +} + +Buffer* Buffer::fappend(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + char* dat = tail(); + int len = room(); + int n = vsnprintf(dat, len, fmt, ap); + va_end(ap); + if (n >= len) { + if (n > MaxBufFmtAppendLen) { + return nullptr; + } + char buf[MaxBufFmtAppendLen]; + va_list aq; + va_start(aq, fmt); + n = vsnprintf(buf, sizeof(buf), fmt, aq); + va_end(aq); + return append(buf, n); + } + use(n); + return this; +} + +Buffer* Buffer::vfappend(const char* fmt, va_list ap) +{ + char* dat = tail(); + int len = room(); + va_list aq; + va_copy(aq, ap); + int n = vsnprintf(dat, len, fmt, ap); + if (n >= len) { + if (n > MaxBufFmtAppendLen) { + va_end(aq); + return nullptr; + } + char buf[MaxBufFmtAppendLen]; + n = vsnprintf(buf, sizeof(buf), fmt, aq); + va_end(aq); + return append(buf, n); + } + use(n); + va_end(aq); + return this; +} + +Segment::Segment() +{ +} + +Segment::Segment(BufferPtr beginBuf, int beginPos, BufferPtr endBuf, int endPos): + mBegin(beginBuf, beginPos), + mCur(beginBuf, beginPos), + mEnd(endBuf, endPos) +{ +} + +Segment::Segment(const Segment& oth): + mBegin(oth.mBegin), + mCur(oth.mCur), + mEnd(oth.mEnd) +{ +} + +Segment::Segment(Segment&& oth): + mBegin(oth.mBegin), + mCur(oth.mCur), + mEnd(oth.mEnd) +{ +} + +Segment::~Segment() +{ + clear(); +} + +Segment& Segment::operator=(const Segment& oth) +{ + if (this != &oth) { + mBegin = oth.mBegin; + mCur = oth.mCur; + mEnd = oth.mEnd; + } + return *this; +} + +void Segment::clear() +{ + mBegin.buf = nullptr; + mBegin.pos = 0; + mCur.buf = nullptr; + mCur.pos = 0; + mEnd.buf = nullptr; + mEnd.pos = 0; +} + +Buffer* Segment::set(Buffer* buf, const char* dat, int len) +{ + if (!buf) { + buf = BufferAlloc::create(); + } + int pos = buf->length(); + Buffer* nbuf = buf->append(dat, len); + mBegin.buf = buf; + mBegin.pos = pos; + mCur.buf = buf; + mCur.pos = pos; + mEnd.buf = nbuf; + mEnd.pos = nbuf->length(); + return nbuf; +} + +Buffer* Segment::fset(Buffer* buf, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + try { + return vfset(buf, fmt, ap); + } catch (...) { + va_end(ap); + throw; + } + return nullptr; +} + +Buffer* Segment::vfset(Buffer* buf, const char* fmt, va_list ap) +{ + if (!buf) { + buf = BufferAlloc::create(); + } + int pos = buf->length(); + Buffer* nbuf = buf->vfappend(fmt, ap); + mBegin.buf = buf; + mBegin.pos = pos; + mCur = mBegin; + mEnd.buf = nbuf; + mEnd.pos = nbuf->length(); + return nbuf; +} + +int Segment::length() const +{ + Buffer* buf = mBegin.buf; + int pos = mBegin.pos; + int len = 0; + while (buf) { + if (buf == mEnd.buf) { + len += mEnd.pos - pos; + break; + } else { + len += buf->length() - pos; + } + buf = buf->next(); + pos = 0; + } + return len; +} + +bool Segment::get(const char*& dat, int& len) const +{ + if (mCur.buf == mEnd.buf && mCur.pos >= mEnd.pos) { + dat = nullptr; + len = 0; + return false; + } + dat = mCur.buf->data() + mCur.pos; + len = (mCur.buf == mEnd.buf ? mEnd.pos : mCur.buf->length()) - mCur.pos; + return true; +} + +int Segment::fill(IOVec* vecs, int len, bool& all) const +{ + int n = 0; + BufferPos p = mCur; + all = false; + while (n < len && p != mEnd) { + vecs[n].dat = p.buf->data() + p.pos; + vecs[n].len = (p.buf == mEnd.buf ? mEnd.pos : p.buf->length()) - p.pos; + vecs[n].seg = const_cast(this); + vecs[n].buf = p.buf; + vecs[n].pos = p.pos; + vecs[n].req = nullptr; + ++n; + if (p.buf == mEnd.buf) { + all = true; + break; + } else { + p.buf = p.buf->next(); + p.pos = 0; + } + } + if (n < len && n == 0) { + all = true; + } + return n; +} + +//caller must sure cnt < segment length +void Segment::cut(int cnt) +{ + if (cnt > 0) { + while (cnt > 0 && mBegin.buf) { + BufferPtr buf = mBegin.buf; + int len = (buf == mEnd.buf ? mEnd.pos : buf->length()) - mBegin.pos; + if (len <= cnt) { + if (buf == mEnd.buf) { + mBegin.buf = nullptr; + mBegin.pos = 0; + mCur = mEnd = mBegin; + break; + } + cnt -= len; + mBegin.buf = buf->next(); + mBegin.pos = 0; + if (mCur.buf == buf) { + mCur = mBegin; + } + } else { + mBegin.pos += cnt; + if (mCur.buf == mBegin.buf && mCur.pos < mBegin.pos) { + mCur.pos = mBegin.pos; + } + break; + } + } + } +} + +//caller must sure cnt < segment length +void Segment::use(int cnt) +{ + while (cnt > 0) { + if (mCur.buf == mEnd.buf) { + mCur.pos += cnt; + if (mCur.pos > mEnd.pos) { + mCur.pos = mEnd.pos; + } + break; + } else { + int n = mCur.buf->length() - mCur.pos; + if (n <= cnt) { + mCur.buf = mCur.buf->next(); + mCur.pos = 0; + } else { + mCur.pos += cnt; + } + cnt -= n; + } + } +} + +void Segment::seek(Buffer* buf, int pos, int cnt) +{ + mCur.buf = buf; + mCur.pos = pos; + use(cnt); +} + +int Segment::dump(char* dat, int len) const +{ + int cnt = 0; + BufferPtr buf = mBegin.buf; + int pos = mBegin.pos; + bool end = false; + while (buf && !end) { + int num; + if (buf == mEnd.buf) { + num = mEnd.pos - pos; + end = true; + } else { + num = buf->length() - pos; + } + if (dat && len > 0) { + memcpy(dat, buf->data() + pos, len > num ? num : len); + dat += num; + len -= num; + } + cnt += num; + buf = buf->next(); + pos = 0; + } + return cnt; +} + +bool Segment::hasPrefix(const char* prefix) const +{ + const char* p = prefix; + BufferPtr buf = mBegin.buf; + int pos = mBegin.pos; + bool end = false; + while (buf && !end) { + int last = buf->length(); + if (buf == mEnd.buf) { + last = mEnd.pos; + end = true; + } + while (pos < last && *p) { + if (buf->data()[pos++] != *p++) { + return false; + } + } + buf = buf->next(); + pos = 0; + } + return *p == '\0'; +} diff --git a/src/Buffer.h b/src/Buffer.h new file mode 100644 index 0000000..ba8ba41 --- /dev/null +++ b/src/Buffer.h @@ -0,0 +1,233 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_BUFFER_H_ +#define _PREDIXY_BUFFER_H_ + +#include "Common.h" +#include "List.h" +#include "Alloc.h" +#include "Timer.h" +#include "String.h" + +class Segment; +class Buffer; +struct IOVec; +typedef SharePtr BufferPtr; + +class Buffer : + public ListNode>, + public RefCntObj +{ +public: + static const int MaxBufFmtAppendLen = 8192; +public: + Buffer& operator=(const Buffer&); + Buffer* append(const char* str); + Buffer* append(const char* dat, int len); + Buffer* fappend(const char* fmt, ...); + Buffer* vfappend(const char* fmt, va_list ap); + void reset() + { + mLen = 0; + } + char* data() + { + return mDat; + } + const char* data() const + { + return mDat; + } + int length() const + { + return mLen; + } + char* tail() + { + return mDat + mLen; + } + const char* tail() const + { + return mDat + mLen; + } + int room() const + { + return Size - mLen; + } + void use(int cnt) + { + mLen += cnt; + } + bool full() const + { + return mLen >= Size; + } + bool empty() const + { + return mLen == 0; + } + static int getSize() + { + return Size; + } + static void setSize(int sz) + { + Size = sz; + } +private: + Buffer(); + Buffer(const Buffer&); + ~Buffer(); +private: + int mLen; + char mDat[0]; + + static int Size; + friend class Segment; + friend class Alloc; +}; + +typedef List BufferList; +template<> +inline int allocSize() +{ + return Buffer::getSize() + sizeof(Buffer); +} +typedef Alloc BufferAlloc; + +struct BufferPos +{ + BufferPtr buf; + int pos; + + BufferPos(): + pos(0) + { + } + BufferPos(BufferPtr b, int p): + buf(b), + pos(p) + { + } + BufferPos(const BufferPos& oth): + buf(oth.buf), + pos(oth.pos) + { + } + BufferPos(BufferPos&& oth): + buf(oth.buf), + pos(oth.pos) + { + } + BufferPos& operator=(const BufferPos& oth) + { + if (this != &oth) { + buf = oth.buf; + pos = oth.pos; + } + return *this; + } + bool operator==(const BufferPos& oth) const + { + return buf == oth.buf && pos == oth.pos; + } + bool operator!=(const BufferPos& oth) const + { + return !operator==(oth); + } +}; + +class Segment +{ +public: + Segment(); + Segment(BufferPtr beginBuf, int beginPos, BufferPtr endBuf, int endPos); + Segment(const Segment& oth); + Segment(Segment&& oth); + ~Segment(); + Segment& operator=(const Segment& oth); + void clear(); + Buffer* set(Buffer* buf, const char* dat, int len); + Buffer* fset(Buffer* buf, const char* fmt, ...); + Buffer* vfset(Buffer* buf, const char* fmt, va_list ap); + int length() const; + void cut(int cnt); + void use(int cnt); + void seek(Buffer* buf, int pos, int cnt); + bool get(const char*& dat, int& len) const; + int fill(IOVec* vecs, int len, bool& all) const; + int dump(char* dat, int len) const; + bool hasPrefix(const char* prefix) const; + Buffer* set(Buffer* buf, const char* str) + { + return set(buf, str, strlen(str)); + } + bool across() const + { + return mBegin.buf != mEnd.buf; + } + BufferPos& begin() + { + return mBegin; + } + const BufferPos& begin() const + { + return mBegin; + } + BufferPos& end() + { + return mEnd; + } + const BufferPos& end () const + { + return mEnd; + } + void rewind() + { + mCur = mBegin; + } + bool empty() const + { + return mCur.buf == mEnd.buf && mCur.pos == mEnd.pos; + } +private: + BufferPos mBegin; + BufferPos mCur; + BufferPos mEnd; +}; + +typedef List SegmentList; + +template +class SegmentStr : public String +{ +public: + SegmentStr(const Segment& seg) + { + set(seg); + } + void set(const Segment& seg) + { + FuncCallTimer(); + if (seg.across()) { + mDat = mBuf; + int len = seg.dump(mBuf, Size); + mLen = len < Size ? len : Size; + } else { + mDat = seg.begin().buf->data() + seg.begin().pos; + mLen = seg.end().pos - seg.begin().pos; + } + } + bool complete() const + { + return mDat != mBuf || mLen < Size; + } +private: + char mBuf[Size]; +}; + +#endif diff --git a/src/ClusterNodesParser.cpp b/src/ClusterNodesParser.cpp new file mode 100644 index 0000000..4845097 --- /dev/null +++ b/src/ClusterNodesParser.cpp @@ -0,0 +1,180 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include "ClusterNodesParser.h" + + +ClusterNodesParser::ClusterNodesParser(): + mState(Idle), + mLen(0) +{ +} + +ClusterNodesParser::~ClusterNodesParser() +{ +} + +void ClusterNodesParser::set(const Segment& s) +{ + mNodes = s; + mNodes.rewind(); +} + +ClusterNodesParser::Status ClusterNodesParser::parse() +{ + if (mState == SlotBegin || mState == SlotEnd) { + mSlotBegin = mSlotEnd = -1; + mState = SlotBegin; + } + const char* dat; + int len; + while (mNodes.get(dat, len)) { + int n = 0; + bool node = false; + while (n < len && !node) { + char c = dat[n++]; + switch (mState) { + case Idle: + if (c == '$') { + mState = Len; + } else { + return Error; + } + break; + case Len: + if (c >= '0' && c <= '9') { + mLen = mLen * 10 + (c - '0'); + } else if (c == '\r') { + mState = LenLF; + } else { + return Error; + } + break; + case LenLF: + if (c == '\n') { + mState = NodeStart; + } else { + return Error; + } + break; + case NodeStart: + if (c == '\r') { + return Finished; + } + mState = Field; + mRole = Server::Unknown; + mFieldCnt = 0; + mNodeId.clear(); + mAddr.clear(); + mFlags.clear(); + mMaster.clear(); + mSlotBegin = -1; + mSlotEnd = -1; + //break;***NO break***, continue to Field + case Field: + if (c == ' ') { + if (mFieldCnt == Flags) { + if (strstr(mFlags, "master")) { + mRole = Server::Master; + } else if (strstr(mFlags, "slave")) { + mRole = Server::Slave; + } + if (strstr(mFlags, "noaddr")) { + mAddr.clear(); + } + } + if (++mFieldCnt == Slot) { + mState = SlotBegin; + } + } else if (c == '\n') { + node = true; + mState = NodeStart; + } else { + switch (mFieldCnt) { + case NodeId: + if (!mNodeId.append(c)) { + return Error; + } + break; + case Addr: + if (!mAddr.append(c)) { + return Error; + } + break; + case Flags: + if (!mFlags.append(c)) { + return Error; + } + break; + case Master: + if (!mMaster.append(c)) { + return Error; + } + break; + default: + break; + } + } + break; + case SlotBegin: + if (c >= '0' && c <= '9') { + if (mSlotBegin < 0) { + mSlotBegin = c - '0'; + } else { + mSlotBegin = mSlotBegin * 10 + (c - '0'); + } + } else if (c == '-') { + mState = SlotEnd; + mSlotEnd = 0; + } else if (c == '[') { + mState = SlotMove; + } else if (c == ' ') { + mSlotEnd = mSlotBegin + 1; + node = true; + } else if (c == '\n') { + mSlotEnd = mSlotBegin + 1; + node = true; + mState = NodeStart; + } else { + return Error; + } + break; + case SlotEnd: + if (c >= '0' && c <= '9') { + mSlotEnd = mSlotEnd * 10 + (c - '0'); + } else if (c == ' ') { + ++mSlotEnd; + node = true; + mState = SlotBegin; + } else if (c == '\n') { + ++mSlotEnd; + node = true; + mState = NodeStart; + } else { + return Error; + } + break; + case SlotMove: + if (c == ' ') { + mState = SlotBegin; + mSlotBegin = mSlotEnd = -1; + } else if (c == '\n') { + node = true; + mState = NodeStart; + } + break; + default: + return Error; + } + } + mNodes.use(n); + if (node) { + return Node; + } + } + return Error; +} diff --git a/src/ClusterNodesParser.h b/src/ClusterNodesParser.h new file mode 100644 index 0000000..e17c0c8 --- /dev/null +++ b/src/ClusterNodesParser.h @@ -0,0 +1,100 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _CLUSTER_NODES_PARSER_H_ +#define _CLUSTER_NODES_PARSER_H_ + +#include "Buffer.h" +#include "String.h" +#include "Server.h" + +class ClusterNodesParser +{ +public: + enum Status + { + Error, + Node, + Finished + }; +public: + ClusterNodesParser(); + ~ClusterNodesParser(); + void set(const Segment& s); + Status parse(); + void rewind() + { + mNodes.rewind(); + } + Server::Role role() const + { + return mRole; + } + const String& nodeId() const + { + return mNodeId; + } + const String& addr() const + { + return mAddr; + } + const String& flags() const + { + return mFlags; + } + const String& master() const + { + return mMaster; + } + bool getSlot(int& begin, int& end) const + { + begin = mSlotBegin; + end = mSlotEnd; + return begin >= 0 && begin < end; + } +private: + enum State + { + Idle, + Len, + LenLF, + NodeStart, + Field, + SlotBegin, + SlotEnd, + SlotMove, + NodeLF + }; + enum FieldType + { + NodeId, + Addr, + Flags, + Master, + PingSent, + PongRecv, + ConfigEpoch, + LinkState, + Slot + }; + static const int NodeIdLen = 48; + static const int AddrLen = 32; + static const int FlagsLen = 48; +private: + Segment mNodes; + State mState; + int mLen; + Server::Role mRole; + int mFieldCnt; + SString mNodeId; + SString mAddr; + SString mFlags; + SString mMaster; + int mSlotBegin; + int mSlotEnd; +}; + +#endif diff --git a/src/ClusterServerPool.cpp b/src/ClusterServerPool.cpp new file mode 100644 index 0000000..31bbe21 --- /dev/null +++ b/src/ClusterServerPool.cpp @@ -0,0 +1,231 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include "ClusterServerPool.h" +#include "ClusterNodesParser.h" +#include "ServerGroup.h" +#include "Handler.h" + +const char* ClusterServerPool::HashTag = "{}"; + +ClusterServerPool::ClusterServerPool(Proxy* p): + ServerPoolTmpl(p, Cluster), + mHash(Hash::Crc16) +{ + mServPool.reserve(Const::MaxServNum); + mGroupPool.reserve(Const::MaxServGroupNum); + for (int i = 0; i < Const::RedisClusterSlots; ++i) { + mSlots[i] = nullptr; + } +} + +ClusterServerPool::~ClusterServerPool() +{ + for (auto i : mServPool) { + delete i; + } + for (auto i : mGroupPool) { + delete i; + } +} + +Server* ClusterServerPool::getServer(Handler* h, Request* req) const +{ + FuncCallTimer(); + switch (req->type()) { + case Command::ClusterNodes: + case Command::Randomkey: + return randServer(h, mServPool); + default: + break; + } + SegmentStr key(req->key()); + int i = mHash.hash(key.data(), key.length(), HashTag); + i &= Const::RedisClusterSlotsMask; + ServerGroup* g = mSlots[i]; + if (!g) { + return randServer(h, mServPool); + } + return g->getServer(h, req); +} + +Server* ClusterServerPool::redirect(const String& addr, Server* old) const +{ + Server* serv = nullptr; + int size = mServPool.size(); + for (int i = 0; i < size; ++i) { + Server* s = mServPool[i]; + if (s->addr() == addr) { + serv = s; + break; + } + } + return serv; +} + +void ClusterServerPool::init(const ClusterServerPoolConf& conf) +{ + ServerPool::init(conf); + mServPool.resize(conf.servers.size()); + int i = 0; + for (auto& sc : conf.servers) { + Server* s = new Server(this, sc.addr.c_str(), true); + s->setPassword(sc.password.empty() ? conf.password : sc.password); + mServPool[i++] = s; + mServs[s->addr()] = s; + } +} + +void ClusterServerPool::refreshRequest(Handler* h) +{ + logDebug("h %d update redis cluster pool", h->id()); + RequestPtr req = RequestAlloc::create(Request::ClusterNodes); + h->handleRequest(req); +} + +void ClusterServerPool::handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res) +{ + ClusterNodesParser p; + p.set(res->body()); + for (auto serv : mServPool) { + serv->setUpdating(true); + } + while (true) { + ClusterNodesParser::Status st = p.parse(); + if (st == ClusterNodesParser::Node) { + logDebug("redis cluster update parse node %s %s %s %s", + p.nodeId().data(), + p.addr().data(), + p.flags().data(), + p.master().data()); + if (p.addr().empty()) { + logWarn("redis cluster nodes get node invalid %s %s %s %s", + p.nodeId().data(), + p.addr().data(), + p.flags().data(), + p.master().data()); + continue; + } + auto it = mServs.find(p.addr()); + Server* serv = it == mServs.end() ? nullptr : it->second; + if (!serv) { + const char* flags = p.flags().data(); + if (p.role() == Server::Unknown || + strstr(flags, "fail") || strstr(flags, "handshake")) { + logWarn("redis cluster nodes get node abnormal %s %s %s %s", + p.nodeId().data(), + p.addr().data(), + p.flags().data(), + p.master().data()); + continue; + } + if (mServPool.size() == mServPool.capacity()) { + logWarn("cluster server pool had too many servers %d, will ignore new server %s", + (int)mServPool.size(), p.addr().data()); + continue; + } + serv = new Server(this, p.addr(), false); + serv->setPassword(password()); + mServPool.push_back(serv); + mServs[serv->addr()] = serv; + logNotice("redis cluster create new server %s %s %s %s %s", + p.nodeId().data(), + p.addr().data(), + p.flags().data(), + p.master().data(), + serv->dcName().data()); + } else { + serv->setOnline(true); + serv->setUpdating(false); + } + serv->setRole(p.role()); + serv->setName(p.nodeId()); + serv->setMasterName(p.master()); + if (p.role() == Server::Master) { + ServerGroup* g = getGroup(p.nodeId()); + if (!g) { + if (mGroupPool.size() == mGroupPool.capacity()) { + logNotice("redis cluster too many group %s %s %s %s", + p.nodeId().data(), + p.addr().data(), + p.flags().data(), + p.master().data()); + continue; + } + g = new ServerGroup(this, p.nodeId()); + mGroupPool.push_back(g); + mGroups[g->name()] = g; + logNotice("redis cluster create new group %s %s %s %s", + p.nodeId().data(), + p.addr().data(), + p.flags().data(), + p.master().data()); + } + g->add(serv); + int begin, end; + if (p.getSlot(begin, end)) { + while (begin < end) { + mSlots[begin++] = g; + } + } + } + } else if (st == ClusterNodesParser::Finished) { + logDebug("redis cluster nodes update finish"); + break; + } else { + logError("redis cluster nodes parse error"); + return; + } + } + for (auto serv : mServPool) { + if (serv->updating()) { + serv->setOnline(false); + serv->setUpdating(false); + if (ServerGroup* g = serv->group()) { + g->remove(serv); + serv->setGroup(nullptr); + } + continue; + } + if (serv->role() == Server::Master) { + ServerGroup* g = getGroup(serv->name()); + if (serv->group() && serv->group() != g) { + serv->group()->remove(serv); + } + if (g) { + g->add(serv); + serv->setGroup(g); + } else { + logWarn("redis cluster update can't find group for master %.*s %.*s", + serv->name().length(), serv->name().data(), + serv->addr().length(), serv->addr().data()); + } + } else if (serv->role() == Server::Slave) { + ServerGroup* g = getGroup(serv->masterName()); + if (serv->group() && serv->group() != g) { + serv->group()->remove(serv); + serv->setGroup(nullptr); + } + if (g) { + g->add(serv); + serv->setGroup(g); + } else { + logWarn("redis cluster update can't find master %.*s for slave %.*s %.*s", + serv->masterName().length(), serv->masterName().data(), + serv->name().length(), serv->name().data(), + serv->addr().length(), serv->addr().data()); + } + } else { + logWarn("redis cluster update server %s %s role unknown", + serv->name().data(), serv->addr().data()); + if (ServerGroup* g = serv->group()) { + g->remove(serv); + } + } + } +} + diff --git a/src/ClusterServerPool.h b/src/ClusterServerPool.h new file mode 100644 index 0000000..eb4ccc5 --- /dev/null +++ b/src/ClusterServerPool.h @@ -0,0 +1,49 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_CLUSTER_SERVER_POOL_H_ +#define _PREDIXY_CLUSTER_SERVER_POOL_H_ + +#include +#include +#include +#include "ServerPool.h" + +class ClusterServerPool : public ServerPoolTmpl +{ +public: + static const char* HashTag; +public: + ClusterServerPool(Proxy* p); + ~ClusterServerPool(); + void init(const ClusterServerPoolConf& conf); + Server* redirect(const String& addr, Server* old) const; + const std::vector& servers() const + { + return mServPool; + } +private: + Server* getServer(Handler* h, Request* req) const; + void refreshRequest(Handler* h); + void handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res); + ServerGroup* getGroup(const String& nodeid) const + { + auto it = mGroups.find(nodeid); + return it == mGroups.end() ? nullptr : it->second; + } + Server* iter(int& cursor) const + { + return ServerPool::iter(mServPool, cursor); + } + friend class ServerPoolTmpl; +private: + Hash mHash; + std::vector mServPool; + std::map mGroups; + ServerGroup* mSlots[Const::RedisClusterSlots]; +}; + +#endif diff --git a/src/Command.cpp b/src/Command.cpp new file mode 100644 index 0000000..27cab06 --- /dev/null +++ b/src/Command.cpp @@ -0,0 +1,189 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include "String.h" +#include "Command.h" + +const Command Command::CmdPool[Sentinel] = { + {None, "", 0, 0, Read}, + {Ping, "ping", 1, 2, Read}, + {PingServ, "ping", 1, 2, Inner}, + {Echo, "echo", 2, 2, Read}, + {Auth, "auth", 2, 2, Read}, + {AuthServ, "auth", 2, 2, Inner}, + {Select, "select", 2, 2, Read}, + {SelectServ, "select", 2, 2, Inner}, + {SentinelSentinels, "sentinel sentinels",3, 3, Inner}, + {SentinelGetMaster, "sentinel get-m-a..",3, 3, Inner}, + {SentinelSlaves, "sentinel slaves", 3, 3, Inner}, + {Cmd, "command", 1, 1, Read}, + {Info, "info", 1, 4, Read}, + {Config, "config", 3, 4, Admin}, + {Cluster, "cluster", 2, 2, Inner}, + {ClusterNodes, "cluster nodes", 2, 2, SubCmd|Inner}, + {Asking, "asking", 1, 1, Inner}, + {Readonly, "readonly", 1, 1, Inner}, + {Watch, "watch", 2, MaxArgs, Write|Private}, + {Unwatch, "unwatch", 1, 1, Write}, + {UnwatchServ, "unwatch", 1, 1, Inner}, + {Multi, "multi", 1, 1, Write|Private|NoKey}, + {Exec, "exec", 1, 1, Write|NoKey}, + {Discard, "discard", 1, 1, Write|NoKey}, + {DiscardServ, "discard", 1, 1, Inner|NoKey}, + {Eval, "eval", 4, MaxArgs, Write|KeyAt3}, + {Evalsha, "evalsha", 4, MaxArgs, Write|KeyAt3}, + {Script, "script", 2, MaxArgs, Write}, + {ScriptLoad, "script", 3, 3, Write|SubCmd}, + {Del, "del", 2, MaxArgs, Write|MultiKey}, + {Dump, "dump", 2, 2, Read}, + {Exists, "exists", 2, MaxArgs, Read|MultiKey}, + {Expire, "expire", 3, 3, Write}, + {Expireat, "expireat", 3, 3, Write}, + {Move, "move", 3, 3, Write}, + {Persist, "persist", 2, 2, Write}, + {Pexpire, "pexpire", 3, 3, Write}, + {Pexpireat, "pexpireat", 3, 3, Write}, + {Pttl, "pttl", 2, 2, Read}, + {Randomkey, "randomkey", 1, 1, Read|NoKey}, + {Rename, "rename", 3, 3, Write}, + {Renamenx, "renamenx", 3, 3, Write}, + {Restore, "restore", 4, 5, Write}, + {Sort, "sort", 2, MaxArgs, Read}, + {Touch, "touch", 2, MaxArgs, Write|MultiKey}, + {Ttl, "ttl", 2, 2, Read}, + {TypeCmd, "type", 2, 2, Read}, + {Unlink, "unlink", 2, MaxArgs, Write|MultiKey}, + {Scan, "scan", 2, 6, Read}, + {Append, "append", 3, 3, Write}, + {Bitcount, "bitcount", 2, 4, Read}, + {Bitfield, "bitfield", 2, MaxArgs, Write}, + {Bitop, "bitop", 4, MaxArgs, Write}, + {Bitpos, "bitpos", 3, 5, Read}, + {Decr, "decr", 2, 2, Write}, + {Decrby, "decrby", 3, 3, Write}, + {Get, "get", 2, 2, Read}, + {Getbit, "getbit", 3, 3, Read}, + {Getrange, "getrange", 4, 4, Read}, + {Getset, "getset", 3, 3, Write}, + {Incr, "incr", 2, 2, Write}, + {Incrby, "incrby", 3, 3, Write}, + {Incrbyfloat, "incrbyfloat", 3, 3, Write}, + {Mget, "mget", 2, MaxArgs, Read|MultiKey}, + {Mset, "mset", 3, MaxArgs, Write|MultiKeyVal}, + {Msetnx, "msetnx", 3, MaxArgs, Write|MultiKeyVal}, + {Psetex, "psetex", 4, 4, Write}, + {Set, "set", 3, 8, Write}, + {Setbit, "setbit", 4, 4, Write}, + {Setex, "setex", 4, 4, Write}, + {Setnx, "setnx", 3, 3, Write}, + {Setrange, "setrange", 4, 4, Write}, + {Strlen, "strlen", 2, 2, Read}, + {Hdel, "hdel", 3, MaxArgs, Write}, + {Hexists, "hexists", 3, 3, Write}, + {Hget, "hget", 3, 3, Write}, + {Hgetall, "hgetall", 2, 2, Read}, + {Hincrby, "hincrby", 4, 4, Write}, + {Hincrbyfloat, "hincrbyfloat", 4, 4, Write}, + {Hkeys, "hkeys", 2, 2, Read}, + {Hlen, "hlen", 2, 2, Read}, + {Hmget, "hmget", 3, MaxArgs, Read}, + {Hmset, "hmset", 4, MaxArgs, Write}, + {Hscan, "hscan", 3, 7, Read}, + {Hset, "hset", 4, 4, Write}, + {Hsetnx, "hsetnx", 4, 4, Write}, + {Hstrlen, "hstrlen", 3, 3, Read}, + {Hvals, "hvals", 2, 2, Read}, + {Blpop, "blpop", 3, MaxArgs, Write|Private}, + {Brpop, "brpop", 3, MaxArgs, Write|Private}, + {Brpoplpush, "brpoplpush", 4, 4, Write|Private}, + {Lindex, "lindex", 3, 3, Read}, + {Linsert, "linsert", 5, 5, Write}, + {Llen, "llen", 2, 2, Read}, + {Lpop, "lpop", 2, 2, Write}, + {Lpush, "lpush", 3, MaxArgs, Write}, + {Lpushx, "lpushx", 3, 3, Write}, + {Lrange, "lrange", 4, 4, Write}, + {Lrem, "lrem", 4, 4, Write}, + {Lset, "lset", 4, 4, Write}, + {Ltrim, "ltrim", 4, 4, Write}, + {Rpop, "rpop", 2, 2, Write}, + {Rpoplpush, "rpoplpush", 3, 3, Write}, + {Rpush, "rpush", 3, MaxArgs, Write}, + {Rpushx, "rpushx", 3, 3, Write}, + {Sadd, "sadd", 3, MaxArgs, Write}, + {Scard, "scard", 2, 2, Read}, + {Sdiff, "sdiff", 2, MaxArgs, Read}, + {Sdiffstore, "sdiffstore", 3, MaxArgs, Write}, + {Sinter, "sinter", 2, MaxArgs, Read}, + {Sinterstore, "sinterstore", 3, MaxArgs, Write}, + {Sismember, "sismember", 3, 3, Read}, + {Smembers, "smembers", 2, 2, Read}, + {Smove, "smove", 4, 4, Write}, + {Spop, "spop", 2, 3, Write}, + {Srandmember, "srandmember", 2, 3, Read}, + {Srem, "srem", 3, MaxArgs, Write}, + {Sscan, "sscan", 3, 7, Read}, + {Sunion, "sunion", 2, MaxArgs, Read}, + {Sunionstore, "sunionstore", 3, MaxArgs, Write}, + {Zadd, "zadd", 4, MaxArgs, Write}, + {Zcard, "zcard", 2, 2, Read}, + {Zcount, "zcount", 4, 4, Read}, + {Zincrby, "zincrby", 4, 4, Write}, + {Zinterstore, "zinterstore", 4, MaxArgs, Write}, + {Zlexcount, "zlexcount", 4, 4, Read}, + {Zrange, "zrange", 4, 5, Read}, + {Zrangebylex, "zrangebylex", 4, 7, Read}, + {Zrangebyscore, "zrangebyscore", 4, 8, Read}, + {Zrank, "zrank", 3, 3, Read}, + {Zrem, "zrem", 3, MaxArgs, Write}, + {Zremrangebylex, "zremrangebylex", 4, 4, Write}, + {Zremrangebyrank, "zremrangebyrank", 4, 4, Write}, + {Zremrangebyscore, "zremrangebyscore", 4, 4, Write}, + {Zrevrange, "zrevrange", 4, 5, Read}, + {Zrevrangebylex, "zrevrangebylex", 4, 7, Read}, + {Zrevrangebyscore, "zrevrangebyscore", 4, 8, Read}, + {Zrevrank, "zrevrank", 3, 3, Read}, + {Zscan, "zscan", 3, 7, Read}, + {Zscore, "zscore", 3, 3, Read}, + {Zunionstore, "zunionstore", 4, MaxArgs, Write}, + {Pfadd, "pfadd", 3, MaxArgs, Write}, + {Pfcount, "pfcount", 2, MaxArgs, Read}, + {Pfmerge, "pfmerge", 3, MaxArgs, Write}, + {Geoadd, "geoadd", 5, MaxArgs, Write}, + {Geodist, "geodist", 4, 5, Read}, + {Geohash, "geohash", 3, MaxArgs, Read}, + {Geopos, "geopos", 3, MaxArgs, Read}, + {Georadius, "georadius", 6, 16, Read}, + {Georadiusbymember, "georadiusbymember",5, 15, Read}, + {Psubscribe, "psubscribe", 2, MaxArgs, Write|SMultiKey|Private}, + {Publish, "publish", 3, 3, Write}, + {Pubsub, "pubsub", 2, MaxArgs, Read}, + {Punsubscribe, "punsubscribe", 1, MaxArgs, Write}, + {Subscribe, "subscribe", 2, MaxArgs, Write|SMultiKey|Private}, + {Unsubscribe, "unsubscribe", 1, MaxArgs, Write}, + {SubMsg, "\000SubMsg", 0, 0, Admin} +}; + +Command::CommandMap Command::CmdMap; + +void Command::init() +{ + int type = 0; + for (auto& i : CmdPool) { + if (i.type != type) { + Throw(InitFail, "command %s unmatch the index in commands table", i.name); + } + ++type; + if (i.mode & (Command::Inner|Command::SubCmd)) { + continue; + } + CmdMap[i.name] = &i; + } +} + diff --git a/src/Command.h b/src/Command.h new file mode 100644 index 0000000..6c427ef --- /dev/null +++ b/src/Command.h @@ -0,0 +1,251 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_COMMAND_H_ +#define _PREDIXY_COMMAND_H_ + +#include "Exception.h" + +class Command +{ +public: + DefException(InitFail); + enum Type + { + None = 0, + Ping, + PingServ, + Echo, + Auth, + AuthServ, + Select, + SelectServ, + + SentinelSentinels, + SentinelGetMaster, + SentinelSlaves, + + Cmd, + Info, + Config, + + Cluster, + ClusterNodes, + Asking, + Readonly, + + Watch, + Unwatch, + UnwatchServ, + Multi, + Exec, + Discard, + DiscardServ, + + Eval, + Evalsha, + Script, + ScriptLoad, + + Del, + Dump, + Exists, + Expire, + Expireat, + Move, + Persist, + Pexpire, + Pexpireat, + Pttl, + Randomkey, + Rename, + Renamenx, + Restore, + Sort, + Touch, + Ttl, + TypeCmd, + Unlink, + Scan, + Append, + Bitcount, + Bitfield, + Bitop, + Bitpos, + Decr, + Decrby, + Get, + Getbit, + Getrange, + Getset, + Incr, + Incrby, + Incrbyfloat, + Mget, + Mset, + Msetnx, + Psetex, + Set, + Setbit, + Setex, + Setnx, + Setrange, + Strlen, + + Hdel, + Hexists, + Hget, + Hgetall, + Hincrby, + Hincrbyfloat, + Hkeys, + Hlen, + Hmget, + Hmset, + Hscan, + Hset, + Hsetnx, + Hstrlen, + Hvals, + + Blpop, + Brpop, + Brpoplpush, + Lindex, + Linsert, + Llen, + Lpop, + Lpush, + Lpushx, + Lrange, + Lrem, + Lset, + Ltrim, + Rpop, + Rpoplpush, + Rpush, + Rpushx, + + Sadd, + Scard, + Sdiff, + Sdiffstore, + Sinter, + Sinterstore, + Sismember, + Smembers, + Smove, + Spop, + Srandmember, + Srem, + Sscan, + Sunion, + Sunionstore, + + Zadd, + Zcard, + Zcount, + Zincrby, + Zinterstore, + Zlexcount, + Zrange, + Zrangebylex, + Zrangebyscore, + Zrank, + Zrem, + Zremrangebylex, + Zremrangebyrank, + Zremrangebyscore, + Zrevrange, + Zrevrangebylex, + Zrevrangebyscore, + Zrevrank, + Zscan, + Zscore, + Zunionstore, + + Pfadd, + Pfcount, + Pfmerge, + + Geoadd, + Geodist, + Geohash, + Geopos, + Georadius, + Georadiusbymember, + + Psubscribe, + Publish, + Pubsub, + Punsubscribe, + Subscribe, + Unsubscribe, + SubMsg, + + Sentinel + }; + enum Mode + { + Unknown = 0, + Read = 1<<0, + Write = 1<<1, + Admin = 1<<2, + Private = 1<<3, //require private connection + NoKey = 1<<4, + MultiKey = 1<<5, + SMultiKey = 1<<6, + MultiKeyVal = 1<<7, + KeyAt3 = 1<<8, + SubCmd = 1<<9, + Inner = 1<<10 //proxy use only + }; + static const int AuthMask = Read|Write|Admin; + static const int KeyMask = NoKey|MultiKey|SMultiKey|MultiKeyVal|KeyAt3; +public: + Type type; + const char* name; + int minArgs; + int maxArgs; + int mode; + + bool isMultiKey() const + { + return mode & MultiKey; + } + bool isSMultiKey() const + { + return mode & SMultiKey; + } + bool isMultiKeyVal() const + { + return mode & MultiKeyVal; + } + static void init(); + static const Command& get(Type type) + { + return CmdPool[type]; + } + static const Command* iter(int& cursor) + { + if (cursor < Sentinel) { + return &CmdPool[cursor++]; + } + return nullptr; + } + static const Command* find(const String& cmd) + { + auto it = CmdMap.find(cmd); + return it == CmdMap.end() ? nullptr : it->second; + } +private: + static const int MaxArgs = 100000000; + static const Command CmdPool[Sentinel]; + typedef std::map CommandMap; + static CommandMap CmdMap; +}; + +#endif diff --git a/src/Common.h b/src/Common.h new file mode 100644 index 0000000..aa4bdfe --- /dev/null +++ b/src/Common.h @@ -0,0 +1,41 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_COMMON_H_ +#define _PREDIXY_COMMON_H_ + +#include + +#define _PREDIXY_NAME_ "predixy" +#define _PREDIXY_VERSION_ "1.0.0" + +namespace Const +{ + static const long MaxMemoryLower = 10 << 20; //10MB + static const int MaxBufListNodeNum = 64; + static const int MinBufSize = 1; + static const int MinBufSpaceLeft = 1;//64; + static const int RedisClusterSlots = 16384; + static const int RedisClusterSlotsMask = 16383; + static const int MaxServNameLen = 64; //nodeid in redis cluster; master name in sentinel + static const int MaxServNum = 2048; + static const int ServGroupBits = 10; + static const int MaxServGroupNum = 1< + * All rights reserved. + */ + +#include +#include +#include +#include "LogFileSink.h" +#include "ServerPool.h" +#include "Conf.h" + +using std::string; + +// [password@]addr role +bool ServerConf::parse(ServerConf& s, const char* str) +{ + const char* p = strchr(str, '@'); + if (p) { + s.password.assign(str, p); + str = p + 1; + } else { + s.password.clear(); + } + for (p = str; *p != '\0'; ++p) { + if (isspace(*p)) { + break; + } + } + s.addr.assign(str, p); + return !s.addr.empty(); +} + +Conf::Conf(): + mBind("0.0.0.0:7617"), + mWorkerThreads(1), + mMaxMemory(0), + mClientTimeout(0), + mBufSize(0), + mLogRotateSecs(0), + mLogRotateBytes(0), + mAllowMissLog(true), + mServerPoolType(ServerPool::Unknown) +{ + mLogSample[LogLevel::Verb] = 0; + mLogSample[LogLevel::Debug] = 0; + mLogSample[LogLevel::Info] = 0; + mLogSample[LogLevel::Notice] = 1; + mLogSample[LogLevel::Warn] = 1; + mLogSample[LogLevel::Error] = 1; + mSentinelServerPool.refreshInterval = 1; + mClusterServerPool.refreshInterval = 1; +} + +Conf::~Conf() +{ +} + +static void printHelp(const char* name) +{ + fprintf(stderr, + "Usage: \n" + " %s [options]\n" + " %s -h or --help\n" + " %s -v or --version\n" + "\n" + "Options:\n" + " --Name=name set current service name\n" + " --Bind=addr set bind address, eg:127.0.0.1:7617, /tmp/predixy\n" + " --WorkerThreads=N set worker threads\n" + " --LocalDC=dc set local dc\n" + ,name, name, name + ); +} + +static void printVersion(const char* name) +{ + fprintf(stderr, "%s %s-%s\n", name, _PREDIXY_NAME_, _PREDIXY_VERSION_); +} + +#define GetVal(s, p) \ + (strncasecmp(s, p, sizeof(p) - 1) == 0 ? s + sizeof(p) - 1 : nullptr) + +bool Conf::init(int argc, char* argv[]) +{ + if (argc < 2) { + printHelp(argv[0]); + Throw(InvalidStartArg, "start %s arguments invalid", argv[0]); + } else if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) { + printHelp(argv[0]); + return false; + } else if (strcmp(argv[1], "-v") == 0 || strcmp(argv[1], "--version") == 0){ + printVersion(argv[0]); + return false; + } + ConfParser p; + ConfParser::Node* n = p.load(argv[1]); + setGlobal(n); + for (int i = 2; i < argc; ++i) { + if (char* v = GetVal(argv[i], "--Name=")) { + mName = v; + } else if (char* v = GetVal(argv[i], "--Bind=")) { + mBind = v; + } else if (char* v = GetVal(argv[i], "--WorkerThreads=")) { + mWorkerThreads = atoi(v); + } else if (char* v = GetVal(argv[i], "--LocalDC=")) { + mLocalDC = v; + } else { + printHelp(argv[0]); + Throw(InvalidStartArg, "invalid argument \"%s\"", argv[i]); + } + } + check(); + return true; +} + +void Conf::setGlobal(const ConfParser::Node* node) +{ + const ConfParser::Node* authority = nullptr; + const ConfParser::Node* clusterServerPool = nullptr; + const ConfParser::Node* sentinelServerPool = nullptr; + const ConfParser::Node* dataCenter = nullptr; + for (auto p = node; p; p = p->next) { + if (setStr(mName, "Name", p)) { + } else if (setStr(mBind, "Bind", p)) { + } else if (setStr(mLocalDC, "LocalDC", p)) { +#ifdef _PREDIXY_SINGLE_THREAD_ + } else if (setInt(mWorkerThreads, "WorkerThreads", p, 1, 1)) { +#else + } else if (setInt(mWorkerThreads, "WorkerThreads", p, 1)) { +#endif + } else if (setMemory(mMaxMemory, "MaxMemory", p)) { + } else if (setLong(mClientTimeout, "ClientTimeout", p, 0)) { + mClientTimeout *= 1000000; + } else if (setInt(mBufSize, "BufSize", p, Const::MinBufSize + sizeof(Buffer))) { + mBufSize -= sizeof(Buffer); + } else if (setStr(mLog, "Log", p)) { + } else if (strcasecmp(p->key.c_str(), "LogRotate") == 0) { + if (!LogFileSink::parseRotate(p->val.c_str(), mLogRotateSecs, mLogRotateBytes)) { + Throw(InvalidValue, "%s:%d invalid LogRotate \"%s\"", + p->file, p->line, p->val.c_str()); + } + } else if (setBool(mAllowMissLog, "AllowMissLog", p)) { + } else if (setInt(mLogSample[LogLevel::Verb], "LogVerbSample", p)) { + } else if (setInt(mLogSample[LogLevel::Debug], "LogDebugSample", p)) { + } else if (setInt(mLogSample[LogLevel::Info], "LogInfoSample", p)) { + } else if (setInt(mLogSample[LogLevel::Notice], "LogNoticeSample", p)) { + } else if (setInt(mLogSample[LogLevel::Warn], "LogWarnSample", p)) { + } else if (setInt(mLogSample[LogLevel::Error], "LogErrorSample", p)) { + } else if (strcasecmp(p->key.c_str(), "LatencyMonitor") == 0) { + mLatencyMonitors.push_back(LatencyMonitorConf{}); + setLatencyMonitor(mLatencyMonitors.back(), p); + } else if (strcasecmp(p->key.c_str(), "Authority") == 0) { + authority = p; + } else if (strcasecmp(p->key.c_str(), "ClusterServerPool") == 0) { + clusterServerPool = p; + } else if (strcasecmp(p->key.c_str(), "SentinelServerPool") == 0) { + sentinelServerPool = p; + } else if (strcasecmp(p->key.c_str(), "DataCenter") == 0) { + dataCenter = p; + } else { + Throw(UnknownKey, "%s:%d unknown key %s", p->file, p->line, p->key.c_str()); + } + } + if (authority) { + setAuthority(authority); + } + if (clusterServerPool && sentinelServerPool) { + Throw(LogicError, "Can't define ClusterServerPool and SentinelServerPool at the same time"); + } else if (clusterServerPool) { + setClusterServerPool(clusterServerPool); + mServerPoolType = ServerPool::Cluster; + } else if (sentinelServerPool) { + setSentinelServerPool(sentinelServerPool); + mServerPoolType = ServerPool::Sentinel; + } else { + Throw(LogicError, "Must define a server pool"); + } + if (dataCenter) { + setDataCenter(dataCenter); + } +} + +static void setKeyPrefix(std::vector& dat, const std::string& v) +{ + const char* p = v.c_str(); + const char* s = nullptr; + do { + if (*p == ' ' || *p == '\t' || *p == '\0') { + if (s) { + dat.push_back(std::string(s, p - s)); + s = nullptr; + } + } else if (!s) { + s = p; + } + } while (*p++); +} + +void Conf::setAuthority(const ConfParser::Node* node) +{ + if (!node->sub) { + Throw(InvalidValue, "%s:%d Authority require scope value", node->file, node->line); + } + for (auto p = node->sub; p; p = p->next) { + if (strcasecmp(p->key.c_str(), "Auth") != 0) { + Throw(InvalidValue, "%s:%d Authority allow only Auth element", p->file, p->line); + } + if (!p->sub) { + Throw(InvalidValue, "%s:%d Auth require scope value", p->file, p->line); + } + mAuthConfs.push_back(AuthConf{}); + auto& c = mAuthConfs.back(); + c.password = p->val; + for (auto n = p->sub; n; n = n->next) { + const std::string& k = n->key; + const std::string& v = n->val; + if (strcasecmp(k.c_str(), "Mode") == 0) { + if (strcasecmp(v.c_str(), "read") == 0) { + c.mode = Command::Read; + } else if (strcasecmp(v.c_str(), "write") == 0) { + c.mode = Command::Write|Command::Read; + } else if (strcasecmp(v.c_str(), "admin") == 0) { + c.mode = Command::Write|Command::Read|Command::Admin; + } else { + Throw(InvalidValue, "%s:%d Auth Mode must be read or write", n->file, n->line); + } + } else if (strcasecmp(k.c_str(), "KeyPrefix") == 0) { + setKeyPrefix(c.keyPrefix, v); + } else if (strcasecmp(k.c_str(), "ReadKeyPrefix") == 0) { + setKeyPrefix(c.readKeyPrefix, v); + } else if (strcasecmp(k.c_str(), "WriteKeyPrefix") == 0) { + setKeyPrefix(c.writeKeyPrefix, v); + } else { + Throw(UnknownKey, "%s:%d unknown key %s", n->file, n->line, k.c_str()); + } + } + } +} + +bool Conf::setServerPool(ServerPoolConf& sp, const ConfParser::Node* p) +{ + if (setStr(sp.password, "Password", p)) { + return true; + } else if (setInt(sp.masterReadPriority, "MasterReadPriority", p, 0, 100)) { + return true; + } else if (setInt(sp.staticSlaveReadPriority, "StaticSlaveReadPriority", p, 0, 100)) { + return true; + } else if (setInt(sp.dynamicSlaveReadPriority, "DynamicSlaveReadPriority", p, 0, 100)) { + return true; + } else if (setInt(sp.refreshInterval, "RefreshInterval", p, 1)) { + return true; + } else if (setInt(sp.serverFailureLimit, "ServerFailureLimit", p, 1)) { + return true; + } else if (setInt(sp.serverRetryTimeout, "ServerRetryTimeout", p, 1)) { + return true; + } else if (setInt(sp.databases, "Databases", p, 1, 128)) { + return true; + } + return false; +} + +void Conf::setClusterServerPool(const ConfParser::Node* node) +{ + if (!node->sub) { + Throw(InvalidValue, "%s:%d ClusterServerPool require scope value", node->file, node->line); + } + for (auto p = node->sub; p; p = p->next) { + if (setServerPool(mClusterServerPool, p)) { + } else if (setServers(mClusterServerPool.servers, "Servers", p)) { + } else { + Throw(UnknownKey, "%s:%d unknown key %s", + p->file, p->line, p->key.c_str()); + } + } + if (mClusterServerPool.databases != 1) { + Throw(InvalidValue, "ClusterServerPool Databases must be 1"); + } + if (mClusterServerPool.servers.empty()) { + Throw(LogicError, "ClusterServerPool no server"); + } +} + +void Conf::setSentinelServerPool(const ConfParser::Node* node) +{ + if (!node->sub) { + Throw(InvalidValue, "%s:%d SentinelServerPool require scope value", node->file, node->line); + } + mSentinelServerPool.hashTag[0] = '\0'; + mSentinelServerPool.hashTag[1] = '\0'; + for (auto p = node->sub; p; p = p->next) { + if (setServerPool(mSentinelServerPool, p)) { + } else if (strcasecmp(p->key.c_str(), "Distribution") == 0) { + mSentinelServerPool.dist = Distribution::parse(p->val.c_str()); + if (mSentinelServerPool.dist == Distribution::None) { + Throw(InvalidValue, "%s:%d unknown Distribution", p->file, p->line); + } + } else if (strcasecmp(p->key.c_str(), "Hash") == 0) { + mSentinelServerPool.hash = Hash::parse(p->val.c_str()); + if (mSentinelServerPool.hash == Hash::None) { + Throw(InvalidValue, "%s:%d unknown Hash", p->file, p->line); + } + } else if (strcasecmp(p->key.c_str(), "HashTag") == 0) { + if (p->val.empty()) { + mSentinelServerPool.hashTag[0] = '\0'; + mSentinelServerPool.hashTag[1] = '\0'; + } else if (p->val.size() == 2) { + mSentinelServerPool.hashTag[0] = p->val[0]; + mSentinelServerPool.hashTag[1] = p->val[1]; + } else { + Throw(InvalidValue, "%s:%d HashTag invalid", p->file, p->line); + } + } else if (setServers(mSentinelServerPool.sentinels, "Sentinels", p)) { + } else if (strcasecmp(p->key.c_str(), "Group") == 0) { + mSentinelServerPool.groups.push_back(ServerGroupConf{p->val}); + if (p->sub) { + auto& g = mSentinelServerPool.groups.back(); + setServers(g.servers, "Group", p); + } + } else { + Throw(UnknownKey, "%s:%d unknown key %s", + p->file, p->line, p->key.c_str()); + } + } + if (mSentinelServerPool.sentinels.empty()) { + Throw(LogicError, "SentinelServerPool no sentinel server"); + } + if (mSentinelServerPool.groups.empty()) { + Throw(LogicError, "SentinelServerPool no server group"); + } + if (mSentinelServerPool.groups.size() > 1) { + if (mSentinelServerPool.dist == Distribution::None) { + Throw(LogicError, "SentinelServerPool must define Dsitribution in multi groups"); + } + if (mSentinelServerPool.hash == Hash::None) { + Throw(LogicError, "SentinelServerPool must define Hash in multi groups"); + } + } +} + +void Conf::setDataCenter(const ConfParser::Node* node) +{ + if (!node->sub) { + Throw(InvalidValue, "%s:%d DataCenter require scope value", node->file, node->line); + } + for (auto p = node->sub; p; p = p->next) { + if (strcasecmp(p->key.c_str(), "DC") != 0) { + Throw(InvalidValue, "%s:%d DataCenter allow only DC element", p->file, p->line); + } + mDCConfs.push_back(DCConf{}); + auto& dc = mDCConfs.back(); + dc.name = p->val; + setDC(dc, p); + } +} + +void Conf::setDC(DCConf& dc, const ConfParser::Node* node) +{ + if (!node->sub) { + Throw(InvalidValue, "%s:%d DC require scope value", + node->file, node->line); + } + for (auto p = node->sub; p; p = p->next) { + if (strcasecmp(p->key.c_str(), "AddrPrefix") == 0) { + if (!p->sub) { + Throw(InvalidValue, "%s:%d AddrPrefix require scope value", + p->file, p->line); + } + for (auto n = p->sub; n; n = n->next) { + if (strcmp(n->key.c_str(), "+") == 0) { + dc.addrPrefix.push_back(n->val); + } else { + Throw(InvalidValue, "%s:%d invalid AddrPrefix", + n->file, n->line); + } + } + } else if (strcasecmp(p->key.c_str(), "ReadPolicy") == 0) { + if (!p->sub) { + Throw(InvalidValue, "%s:%d ReadPolicy require scope value", + p->file, p->line); + } + for (auto n = p->sub; n; n = n->next) { + dc.readPolicy.push_back(ReadPolicyConf{}); + setReadPolicy(dc.readPolicy.back(), n); + } + } else { + Throw(UnknownKey, "%s:%d unknown key %s", + p->file, p->line, p->key.c_str()); + } + } +} + +void Conf::setReadPolicy(ReadPolicyConf& c, const ConfParser::Node* n) +{ + c.name = n->key; + int num = sscanf(n->val.c_str(), "%d %d", &c.priority, &c.weight); + if (num == 1) { + c.weight = 1; + } else if (num != 2) { + Throw(InvalidValue, "%s:%d invalid ReadPolicy \"%s\"", + n->file, n->line, n->val.c_str()); + } +} + +void Conf::setLatencyMonitor(LatencyMonitorConf& m, const ConfParser::Node* node) +{ + if (!node->sub) { + Throw(InvalidValue, "%s:%d LatencyMonitor require scope value", + node->file, node->line); + } + m.name = node->val; + for (auto p = node->sub; p; p = p->next) { + if (strcasecmp(p->key.c_str(), "Commands") == 0) { + for (auto n = p->sub; n; n = n->next) { + bool add = true; + if (strcmp(n->key.c_str(), "+") == 0) { + } else if (strcmp(n->key.c_str(), "-") == 0) { + add = false; + } else { + Throw(InvalidValue, "%s:%d invalid Command assign", + n->file, n->line); + } + const char* v = n->val.c_str(); + if (strcasecmp(v, "all") == 0) { + add ? m.cmds.set() : m.cmds.reset(); + } else if (auto c = Command::find(v)) { + add ? m.cmds.set(c->type) : m.cmds.reset(c->type); + } else { + Throw(InvalidValue, "%s:%d unknown Command \"%s\"", + n->file, n->line, v); + } + } + } else if (strcasecmp(p->key.c_str(), "TimeSpan") == 0) { + long span = 0; + for (auto n = p->sub; n; n = n->next) { + if (setLong(span, "+", n, span + 1)) { + m.timeSpan.push_back(span); + } else { + Throw(InvalidValue, "%s:%d invalid TimeSpan", + n->file, n->line); + } + } + } + } +} + +void Conf::check() +{ +#ifdef _PREDIXY_SINGLE_THREAD_ + if (mWorkerThreads != 1) { + Throw(InvalidValue, "WorkerThreads must be 1 in single thread mode"); + } +#else + if (mWorkerThreads < 1) { + Throw(InvalidValue, "WorkerThreads must >= 1"); + } +#endif + if (mLocalDC.empty()) { + if (!mDCConfs.empty()) { + Throw(LogicError, "conf define DataCenter but not define LocalDC"); + } + } else { + bool found = false; + for (auto& dc : mDCConfs) { + if (dc.name == mLocalDC) { + found = true; + break; + } + } + if (!found) { + Throw(LogicError, "LocalDC \"%s\" no exists in DataCenter", + mLocalDC.c_str()); + } + for (auto& dc : mDCConfs) { + for (auto& rp : dc.readPolicy) { + found = false; + for (auto& i :mDCConfs) { + if (rp.name == i.name) { + found = true; + break; + } + } + if (!found) { + Throw(LogicError, "DC \"%s\" ReadPolicy \"%s\" no eixsts in DataCenter", dc.name.c_str(), rp.name.c_str()); + } + } + } + } +} + +bool Conf::setStr(std::string& attr, const char* name, const ConfParser::Node* n) +{ + if (strcasecmp(name, n->key.c_str()) == 0) { + attr = n->val; + return true; + } + return false; +} + +bool Conf::setInt(int& attr, const char* name, const ConfParser::Node* n, int lower, int upper) +{ + if (strcasecmp(name, n->key.c_str()) == 0) { + if (sscanf(n->val.c_str(), "%d", &attr) != 1) { + Throw(InvalidValue, "%s:%d %s %s is not an integer", + n->file, n->line, name, n->val.c_str()); + } + if (attr < lower || attr > upper) { + Throw(InvalidValue, "%s:%d %s %s not in range [%d, %d]", + n->file, n->line, name, n->val.c_str(), lower, upper); + } + return true; + } + return false; +} + +bool Conf::setLong(long& attr, const char* name, const ConfParser::Node* n, long lower, long upper) +{ + if (strcasecmp(name, n->key.c_str()) == 0) { + if (sscanf(n->val.c_str(), "%ld", &attr) != 1) { + Throw(InvalidValue, "%s:%d %s %s is not an long integer", + n->file, n->line, name, n->val.c_str()); + } + if (attr < lower || attr > upper) { + Throw(InvalidValue, "%s:%d %s %s not in range [%ld, %ld]", + n->file, n->line, name, n->val.c_str(), lower, upper); + } + return true; + } + return false; +} + +bool Conf::setBool(bool& attr, const char* name, const ConfParser::Node* n) +{ + if (strcasecmp(name, n->key.c_str()) == 0) { + if (strcasecmp(n->val.c_str(), "true") == 0) { + attr = true; + } else if (strcasecmp(n->val.c_str(), "false") == 0) { + attr = false; + } else { + Throw(InvalidValue, "%s:%d %s expect true or false", + n->file, n->line, name); + } + return true; + } + return false; +} + +bool Conf::parseMemory(long& m, const char* str) +{ + char u[4]; + int c = sscanf(str, "%ld%3s", &m, u); + if (c == 2 && m > 0) { + if (strcasecmp(u, "B") == 0) { + } else if (strcasecmp(u, "K") == 0 || strcasecmp(u, "KB") == 0) { + m <<= 10; + } else if (strcasecmp(u, "M") == 0 || strcasecmp(u, "MB") == 0) { + m <<= 20; + } else if (strcasecmp(u, "G") == 0 || strcasecmp(u, "GB") == 0) { + m <<= 30; + } else { + return false; + } + } else if (c != 1) { + return false; + } + return m >= 0; +} + +bool Conf::setMemory(long& m, const char* name, const ConfParser::Node* n) +{ + if (strcasecmp(name, n->key.c_str()) != 0) { + return false; + } + if (!parseMemory(m, n->val.c_str())) { + Throw(InvalidValue, "%s:%d %s invalid memory value \"%s\"", + n->file, n->line, name, n->val.c_str()); + } + return true; +} + +bool Conf::setServers(std::vector& servs, const char* name, const ConfParser::Node* p) +{ + if (strcasecmp(p->key.c_str(), name) != 0) { + return false; + } + if (!p->sub) { + Throw(InvalidValue, "%s:%d %s require scope value", name, p->file, p->line); + } + for (auto n = p->sub; n; n = n->next) { + if (strcasecmp(n->key.c_str(), "+") == 0) { + ServerConf s; + if (ServerConf::parse(s, n->val.c_str())) { + servs.push_back(s); + } else { + Throw(InvalidValue, "%s:%d invalid server define %s", + n->file, n->line, n->val.c_str()); + } + } else { + Throw(InvalidValue, "%s:%d invalid server define", n->file, n->line); + } + } + return true; +} diff --git a/src/Conf.h b/src/Conf.h new file mode 100644 index 0000000..e1d1dfa --- /dev/null +++ b/src/Conf.h @@ -0,0 +1,221 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_CONF_H_ +#define _PREDIXY_CONF_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Predixy.h" +#include "Distribution.h" +#include "ConfParser.h" +#include "Auth.h" + +struct AuthConf +{ + std::string password; + int mode; //Command::Mode + std::vector keyPrefix; + std::vector readKeyPrefix; + std::vector writeKeyPrefix; +}; + +struct ServerConf +{ + std::string password; + std::string addr; + + static bool parse(ServerConf& s, const char* str); +}; + +struct ServerGroupConf +{ + std::string name; + std::vector servers; +}; + +struct ServerPoolConf +{ + std::string password; + int masterReadPriority = 50; + int staticSlaveReadPriority = 0; + int dynamicSlaveReadPriority = 0; + int refreshInterval = 1; //seconds + int serverFailureLimit = 10; + int serverRetryTimeout = 1; //seconds + int databases = 1; +}; + +struct ClusterServerPoolConf : public ServerPoolConf +{ + std::vector servers; +}; + +struct SentinelServerPoolConf : public ServerPoolConf +{ + Distribution dist = Distribution::None; + Hash hash = Hash::None; + char hashTag[2]; + std::vector sentinels; + std::vector groups; +}; + +struct ReadPolicyConf +{ + std::string name; + int priority; + int weight; +}; + +struct DCConf +{ + std::string name; + std::vector addrPrefix; + std::vector readPolicy; +}; + +struct LatencyMonitorConf +{ + std::string name; + std::bitset cmds; + std::vector timeSpan;//us +}; + +class Conf +{ +public: + DefException(InvalidStartArg); + DefException(UnknownKey); + DefException(InvalidValue); + DefException(LogicError); +public: + Conf(); + ~Conf(); + bool init(int argc, char* argv[]); + const char* name() const + { + return mName.c_str(); + } + const char* bind() const + { + return mBind.c_str(); + } + int workerThreads() const + { + return mWorkerThreads; + } + long maxMemory() const + { + return mMaxMemory; + } + void setClientTimeout(long v) + { + mClientTimeout = v; + } + long clientTimeout() const + { + return mClientTimeout; + } + int bufSize() const + { + return mBufSize; + } + const std::vector& authConfs() const + { + return mAuthConfs; + } + const char* log() const + { + return mLog.c_str(); + } + int logRotateSecs() const + { + return mLogRotateSecs; + } + long logRotateBytes() const + { + return mLogRotateBytes; + } + bool allowMissLog() const + { + return mAllowMissLog; + } + int logSample(LogLevel::Type lvl) const + { + return mLogSample[lvl]; + } + int serverPoolType() const + { + return mServerPoolType; + } + const ClusterServerPoolConf& clusterServerPool() const + { + return mClusterServerPool; + } + const SentinelServerPoolConf& sentinelServerPool() const + { + return mSentinelServerPool; + } + const std::string& localDC() const + { + return mLocalDC; + } + const std::vector& dcConfs() const + { + return mDCConfs; + } + const std::vector& latencyMonitors() const + { + return mLatencyMonitors; + } +public: + static bool parseMemory(long& m, const char* str); +private: + void setGlobal(const ConfParser::Node* node); + void setAuthority(const ConfParser::Node* node); + void setClusterServerPool(const ConfParser::Node* node); + void setSentinelServerPool(const ConfParser::Node* node); + void setDataCenter(const ConfParser::Node* node); + void check(); + bool setServerPool(ServerPoolConf& sp, const ConfParser::Node* n); + bool setStr(std::string& attr, const char* name, const ConfParser::Node* n); + bool setInt(int& attr, const char* name, const ConfParser::Node* n, int lower = INT_MIN, int upper = INT_MAX); + bool setLong(long& attr, const char* name, const ConfParser::Node* n, long lower = LONG_MIN, long upper = LONG_MAX); + bool setBool(bool& attr, const char* name, const ConfParser::Node* n); + bool setMemory(long& mem, const char* name, const ConfParser::Node* n); + bool setServers(std::vector& servs, const char* name, const ConfParser::Node* n); + void setDC(DCConf& dc, const ConfParser::Node* n); + void setReadPolicy(ReadPolicyConf& c, const ConfParser::Node* n); + void setLatencyMonitor(LatencyMonitorConf& m, const ConfParser::Node* n); +private: + std::string mName; + std::string mBind; + int mWorkerThreads; + long mMaxMemory; + long mClientTimeout; //us + int mBufSize; + std::string mLog; + int mLogRotateSecs; + long mLogRotateBytes; + bool mAllowMissLog; + int mLogSample[LogLevel::Sentinel]; + std::vector mAuthConfs; + int mServerPoolType; + ClusterServerPoolConf mClusterServerPool; + SentinelServerPoolConf mSentinelServerPool; + std::vector mDCConfs; + std::string mLocalDC; + std::vector mLatencyMonitors; +}; + + +#endif diff --git a/src/ConfParser.cpp b/src/ConfParser.cpp new file mode 100644 index 0000000..2da48f2 --- /dev/null +++ b/src/ConfParser.cpp @@ -0,0 +1,308 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ConfParser.h" + +ConfParser::ConfParser(int maxNodeDepth): + mMaxNodeDepth(maxNodeDepth) +{ + mRoot.next = mRoot.sub = nullptr; +} + +ConfParser::~ConfParser() +{ + clear(); +} + +void ConfParser::clear() +{ + if (mRoot.next) { + delete mRoot.next; + } + if (mRoot.sub) { + delete mRoot.sub; + } + mRoot.next = nullptr; + mRoot.sub = nullptr; + for (auto s : mStrings) { + delete s; + } + mStrings.clear(); +} + +std::string* ConfParser::getFile(const std::string& name, const char* parent) +{ + if (name.empty()) { + Throw(EmptyFileName, "filename is empty"); + } + if (name[0] == '/') { + return new std::string(name); + } + std::string ret; + char buf[PATH_MAX]; + if (parent && *parent) { + int len = strlen(parent); + if (len < (int)sizeof(buf)) { + int n = len - 1; + while (n >= 0) { + if (parent[n] == '/') { + break; + } + --n; + } + if (n >= 0) { + ret.assign(parent, 0, n + 1); + } + } else { + Throw(FileNameTooLong, "parent file name %s too long", parent); + } + } else { + if (char* path = getcwd(buf, sizeof(buf))) { + ret = path; + ret += '/'; + } else { + Throw(GetCwdFail, "get current directory fail %s", StrError()); + } + } + ret += name; + std::string* r = new std::string(); + r->swap(ret); + return r; +} + +struct File +{ + const char* name; + std::shared_ptr stream; + int line; +}; + +ConfParser::Node* ConfParser::load(const char* file) +{ + clear(); + std::string* name = getFile(file, nullptr); + mStrings.push_back(name); + std::shared_ptr s(new std::ifstream(name->c_str())); + if (!s->good()) { + Throw(OpenFileFail, "open file %s fail %s", name->c_str(), StrError()); + } + std::vector files; + files.push_back(File{name->c_str(), s, 0}); + std::string line; + std::string key; + std::string val; + std::vector stack; + Node* node = &mRoot; + while (!files.empty()) { + File* f = &files.back(); + while (std::getline(*f->stream, line)) { + f->line += 1; + Status st = parse(line, key, val); + switch (st) { + case None: + break; + case KeyVal: + if (strcasecmp(key.c_str(), "Include") == 0) { + name = getFile(val, f->name); + mStrings.push_back(name); + for (auto& pre : files) { + if (strcmp(pre.name, name->c_str()) == 0) { + Throw(IncludeFileDuplicate, "include file %s duplicate in %s:%d", + name->c_str(), f->name, f->line); + } + } + s = decltype(s)(new std::ifstream(name->c_str())); + if (!s->good()) { + Throw(OpenFileFail, "open file %s fail %s", name->c_str(), StrError()); + } + files.push_back(File{name->c_str(), s, 0}); + f = &files.back(); + } else { + auto n = new Node{f->name, f->line, key, val, nullptr, nullptr}; + if (node) { + node->next = n; + } else if (!stack.empty()) { + stack.back()->sub = n; + } + node = n; + } + break; + case BeginScope: + if ((int)stack.size() == mMaxNodeDepth) { + Throw(NodeDepthTooLong, "node depth limit %d in %s:%d", + mMaxNodeDepth, f->name, f->line); + } else { + auto n = new Node{f->name, f->line, key, val, nullptr, nullptr}; + if (node) { + node->next = n; + } else if (!stack.empty()) { + stack.back()->sub = n; + } + stack.push_back(n); + node = nullptr; + } + break; + case EndScope: + if (stack.empty()) { + Throw(ParseError, "parse error unmatched end scope in %s:%d", f->name, f->line); + } else { + node = stack.back(); + stack.resize(stack.size() - 1); + } + break; + case Error: + default: + Throw(ParseError, "parse error in %s:%d", f->name, f->line); + } + } + if (f->stream->eof()) { + files.resize(files.size() - 1); + } else if (f->stream->bad()) { + Throw(FileReadError, "read file %s error", f->name); + } + } + if (!stack.empty()) { + Throw(ParseError, "parse error some scope no end %s:%d", + stack.back()->file, stack.back()->line); + } + return mRoot.next; +} + +ConfParser::Status ConfParser::parse(std::string& line, std::string& key, std::string& val) +{ + key.clear(); + val.clear(); + State s = KeyReady; + std::string::size_type i = 0; + std::string::size_type pos = 0; + bool escape = false; + for (i = 0; i < line.size(); ++i) { + char c = line[i]; + switch (s) { + case KeyReady: + if (c == '#' || c == '\n') { + goto Done; + } else if (c != ' ' && c != '\t' && c != '\r') { + pos = i; + s = KeyBody; + } + break; + case KeyBody: + if (c == '#' || c == '\n') { + line.resize(i); + goto Done; + } else if (c == ' ' || c == '\t') { + key.assign(line, pos, i - pos); + s = ValReady; + } + break; + case ValReady: + if (c == '#' || c == '\n') { + goto Done; + } else if (c != ' ' && c != '\t' && c != '\r') { + pos = i; + if (c == '"') { + val.resize(line.size() - i); + val.resize(0); + s = SValBody; + } else { + s = VValBody; + } + } + break; + case SValBody: + if (c == '\\') { + if (escape) { + escape = false; + val += c; + } else { + escape = true; + } + } else if (c == '"') { + if (escape) { + escape = false; + val += c; + } else { + s = ScopeReady; + } + } else { + val += c; + } + break; + case VValBody: + if (c == '#' || c == '\n') { + line.resize(i); + goto Done; + } + break; + case ScopeReady: + if (c == '#' || c == '\n') { + goto Done; + } else if (c == '{') { + s = ScopeBody; + } else if (c != ' ' && c != '\t' && c != '\r') { + return Error; + } + break; + case ScopeBody: + if (c == '#' || c == '\n') { + goto Done; + } else if (c != ' ' && c != '\t' && c != '\r') { + return Error; + } + default: + break; + } + } +Done: + switch (s) { + case KeyReady: + break; + case KeyBody: + key.assign(line, pos, line.size() - pos); + if (key.size() == 1 && key[0] == '}') { + return EndScope; + } + return KeyVal; + case ValReady: + if (key.size() == 1 && key[0] == '}') { + return EndScope; + } + return KeyVal; + case SValBody: + return KeyVal; + case VValBody: + val.assign(line, pos, line.size() - pos); + if (val.back() == '{') { + val.resize(val.size() - 1); + int vsp = 0; + for (auto it = val.rbegin(); it != val.rend(); ++it) { + if (isspace(*it)) { + ++vsp; + } + } + val.resize(val.size() - vsp); + return BeginScope; + } else { + return KeyVal; + } + case ScopeReady: + return KeyVal; + case ScopeBody: + return BeginScope; + default: + return Error; + } + return None; +} diff --git a/src/ConfParser.h b/src/ConfParser.h new file mode 100644 index 0000000..981b42a --- /dev/null +++ b/src/ConfParser.h @@ -0,0 +1,86 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_CONF_PARSER_H_ +#define _PREDIXY_CONF_PARSER_H_ + +#include +#include +#include +#include +#include +#include "Exception.h" +#include "Util.h" + +class ConfParser +{ +public: + DefException(EmptyFileName); + DefException(FileNameTooLong); + DefException(GetCwdFail); + DefException(OpenFileFail); + DefException(IncludeFileDuplicate); + DefException(NodeDepthTooLong); + DefException(FileReadError); + DefException(ParseError); + struct Node + { + const char* file; + int line; + std::string key; + std::string val; + Node* next; + Node* sub; + + ~Node() + { + if (sub) { + delete sub; + } + Node* n = next; + while (n) { + Node* nn = n->next; + n->next = nullptr; + delete n; + n = nn; + } + } + }; + +private: + enum Status + { + None, + KeyVal, + BeginScope, + EndScope, + Error + }; + enum State + { + KeyReady, + KeyBody, + ValReady, + SValBody, + VValBody, + ScopeReady, + ScopeBody + }; +public: + ConfParser(int maxNodeDepth = 10); + ~ConfParser(); + void clear(); + Node* load(const char* file); +private: + static std::string* getFile(const std::string& name, const char* parent); + Status parse(std::string& line, std::string& key, std::string& val); +private: + Node mRoot; + int mMaxNodeDepth; + std::vector mStrings; +}; + +#endif diff --git a/src/ConnectConnection.cpp b/src/ConnectConnection.cpp new file mode 100644 index 0000000..b680172 --- /dev/null +++ b/src/ConnectConnection.cpp @@ -0,0 +1,242 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "ConnectConnection.h" +#include "Handler.h" +#include "Subscribe.h" + + +ConnectConnection::ConnectConnection(Server* serv, bool shared): + ConnectSocket(serv->addr().data(), SOCK_STREAM), + mServ(serv), + mAcceptConnection(nullptr), + mShared(shared), + mAuthed(false), + mReadonly(false) +{ + mClassType = Connection::ConnectType; +} + +ConnectConnection::~ConnectConnection() +{ +} + +bool ConnectConnection::writeEvent(Handler* h) +{ + FuncCallTimer(); + if (connectStatus() == Connecting) { + logInfo("h %d s %s %d connect succ", + h->id(), peer(), fd()); + setConnectStatus(Connected); + } + IOVec bufs[Const::MaxIOVecLen]; + bool finished = true; + while (true) { + int len = fill(h, bufs, Const::MaxIOVecLen); + if (len == 0) { + finished = true; + break; + } + finished = write(h, bufs, len); + if (!finished || len < Const::MaxIOVecLen) { + break; + } + } + return finished; +} + +int ConnectConnection::fill(Handler* h, IOVec* bufs, int len) +{ + FuncCallTimer(); + int cnt = 0; + for (auto req = mSendRequests.front(); req; req = mSendRequests.next(req)) { + int n = req->fill(bufs, len); + bufs += n; + cnt += n; + len -= n; + if (len == 0) { + break; + } + } + return cnt; +} + +bool ConnectConnection::write(Handler* h, IOVec* bufs, int len) +{ + FuncCallTimer(); + logDebug("h %d s %s %d writev %d", + h->id(), peer(), fd(), len); + struct iovec iov[Const::MaxIOVecLen]; + for (int i = 0; i < len; ++i) { + iov[i].iov_base = bufs[i].dat; + iov[i].iov_len = bufs[i].len; + } + int num = writev(iov, len); + if (num < 0) { + logWarn("h %d s %s %d writev %d fail %s", + h->id(), peer(), fd(), len, StrError()); + return false; + } else if (num == 0) { + return len == 0; + } + h->addServerWriteStats(mServ, num); + IOVec* vec = nullptr; + int i; + for (i = 0; i < len && num > 0; ++i) { + vec = bufs + i; + int n = vec->len; + vec->seg->seek(vec->buf, vec->pos, n <= num ? n : num); + if (vec->req && n <= num) { + while (!mSendRequests.empty()) { + RequestPtr req = mSendRequests.front(); + mSendRequests.pop_front(); + if (vec->req == req) { + mSentRequests.push_back(req); + logVerb("h %d s %s %d req %ld sent", + h->id(), peer(), fd(), req->id()); + break; + } else { + logNotice("h %d s %s %d req %ld is empty", + h->id(), peer(), fd(), req->id()); + h->handleResponse(this, req, nullptr); + } + } + } + num -= n; + } + return i == len && num == 0; +} + +void ConnectConnection::readEvent(Handler* h) +{ + FuncCallTimer(); + while (true) { + Buffer* buf = getBuffer(h, mParser.isIdle()); + int pos = buf->length(); + int len = buf->room(); + int n = read(buf->tail(), len); + if (n > 0) { + logVerb("h %d s %s %d read bytes %d", + h->id(), peer(), fd(), n); + buf->use(n); + h->addServerReadStats(mServ, n); + parse(h, buf, pos); + if (n < len) { + break; + } + } else { + if (!good()) { + logWarn("h %d s %s %d read error %s", + h->id(), peer(), fd(), StrError()); + } + break; + } + } +} + +void ConnectConnection::parse(Handler* h, Buffer* buf, int pos) +{ + FuncCallTimer(); + bool goOn = true; + while (pos < buf->length() && goOn) { + auto ret = mParser.parse(buf, pos); + switch (ret) { + case ResponseParser::Normal: + case ResponseParser::Partial: + goOn = false; + break; + case ResponseParser::Complete: + handleResponse(h); + break; + default: + setStatus(ParseError); + goOn = false; + break; + } + } +} + +void ConnectConnection::handleResponse(Handler* h) +{ + FuncCallTimer(); + if (mAcceptConnection) { + if (mAcceptConnection->inSub(true)) { + int chs; + switch (SubscribeParser::parse(mParser.response(), chs)) { + case SubscribeParser::Subscribe: + case SubscribeParser::Psubscribe: + mAcceptConnection->decrPendSub(); + //NO break; let it continue to setSub + case SubscribeParser::Unsubscribe: + case SubscribeParser::Punsubscribe: + if (chs < 0) { + setStatus(LogicError); + logError("h %d s %s %d parse subscribe response error", + h->id(), peer(), fd()); + } else { + mAcceptConnection->setSub(chs); + } + break; + case SubscribeParser::Message: + case SubscribeParser::Pmessage: + { + RequestPtr req = RequestAlloc::create(mAcceptConnection); + req->setType(Command::SubMsg); + mAcceptConnection->append(req); + mSentRequests.push_front(req); + } + break; + default: + if (Request* req = mSentRequests.front()) { + switch (req->type()) { + case Command::Psubscribe: + case Command::Subscribe: + mAcceptConnection->decrPendSub(); + break; + default: + break; + } + } + break; + } + } + } + if (Request* req = mSentRequests.front()) { + ResponsePtr res = ResponseAlloc::create(); + res->set(mParser); + mParser.reset(); + logDebug("h %d s %s %d create res %ld match req %ld", + h->id(), peer(), fd(), res->id(), req->id()); + h->handleResponse(this, req, res); + mSentRequests.pop_front(); + } else { + logNotice("h %d s %s %d recv res but no req", + h->id(), peer(), fd()); + } +} + +void ConnectConnection::send(Handler* h, Request* req) +{ + FuncCallTimer(); + mSendRequests.push_back(req); + logDebug("h %d s %s %d pend req %ld", + h->id(), peer(), fd(), req->id()); +} + +void ConnectConnection::close(Handler* h) +{ + SendRequestList* reqs[2] = {&mSentRequests, &mSendRequests}; + for (int i = 0; i < 2; ++i) { + while (!reqs[i]->empty()) { + auto req = reqs[i]->front(); + h->directResponse(req, Response::ServerConnectionClose, this); + reqs[i]->pop_front(); + } + } + ConnectSocket::close(); + mParser.reset(); +} + diff --git a/src/ConnectConnection.h b/src/ConnectConnection.h new file mode 100644 index 0000000..dbe85c7 --- /dev/null +++ b/src/ConnectConnection.h @@ -0,0 +1,94 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_CONNECT_CONNECTION_H_ +#define _PREDIXY_CONNECT_CONNECTION_H_ + +#include "Predixy.h" +#include "ConnectSocket.h" +#include "Connection.h" +#include "Request.h" +#include "ResponseParser.h" + +class ConnectConnection : + public ConnectSocket, + public Connection, + public ListNode, + public DequeNode +{ +public: + typedef ConnectConnection Value; + typedef ListNode ListNodeType; + typedef DequeNode DequeNodeType; +public: + ConnectConnection(Server* s, bool shared); + ~ConnectConnection(); + bool writeEvent(Handler* h); + void readEvent(Handler* h); + void send(Handler* h, Request* req); + void close(Handler* h); + Server* server() const + { + return mServ; + } + bool isShared() const + { + return mShared; + } + bool isAuth() const + { + return mAuthed; + } + void setAuth(bool v) + { + mAuthed = v; + } + bool readonly() const + { + return mReadonly; + } + void setReadonly(bool v) + { + mReadonly = v; + } + void attachAcceptConnection(AcceptConnection* c) + { + mAcceptConnection = c; + } + void detachAcceptConnection() + { + mAcceptConnection = nullptr; + } + AcceptConnection* acceptConnection() const + { + return mAcceptConnection; + } + int pendRequestCount() const + { + return mSendRequests.size() + mSentRequests.size(); + } +private: + void parse(Handler* h, Buffer* buf, int pos); + void handleResponse(Handler* h); + void write(); + int fill(Handler* h, IOVec* bufs, int len); + bool write(Handler* h, IOVec* bufs, int len); +private: + Server* mServ; + AcceptConnection* mAcceptConnection; + ResponseParser mParser; + SendRequestList mSendRequests; + SendRequestList mSentRequests; + bool mShared; + bool mAuthed; + bool mReadonly; +}; + +typedef List ConnectConnectionList; +typedef Deque ConnectConnectionDeque; +typedef Alloc ConnectConnectionAlloc; + +#endif diff --git a/src/ConnectConnectionPool.cpp b/src/ConnectConnectionPool.cpp new file mode 100644 index 0000000..d87964c --- /dev/null +++ b/src/ConnectConnectionPool.cpp @@ -0,0 +1,205 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Proxy.h" +#include "ConnectConnectionPool.h" + +ConnectConnectionPool::ConnectConnectionPool(Handler* h, Server* s, int dbnum): + mHandler(h), + mServ(s), + mPendRequests(0), + mShareConns(dbnum), + mPrivateConns(dbnum), + mLatencyMonitors(h->latencyMonitors()) +{ + resetStats(); +} + +ConnectConnectionPool::~ConnectConnectionPool() +{ +} + +ConnectConnection* ConnectConnectionPool::getShareConnection(int db) +{ + FuncCallTimer(); + if (db >= (int)mShareConns.size()) { + logWarn("h %d get share connection for db %d >= %d", + mHandler->id(), db, (int)mShareConns.size()); + return nullptr; + } + ConnectConnection* c = mShareConns[db]; + if (!c) { + c = ConnectConnectionAlloc::create(mServ, true); + c->setDb(db); + ++mStats.connections; + mShareConns[db] = c; + logNotice("h %d create server connection %s %d", + mHandler->id(), c->peer(), c->fd()); + } else if (c->fd() < 0) { + if (mServ->fail()) { + return nullptr; + } + c->reopen(); + logNotice("h %d reopen server connection %s %d", + mHandler->id(), c->peer(), c->fd()); + } else { + return c; + } + if (!init(c)) { + c->close(mHandler); + return nullptr; + } + return c; +} + +ConnectConnection* ConnectConnectionPool::getPrivateConnection(int db) +{ + FuncCallTimer(); + if (db >= (int)mPrivateConns.size()) { + logWarn("h %d get private connection for db %d >= %d", + mHandler->id(), db, (int)mPrivateConns.size()); + return nullptr; + } + auto& ccl = mPrivateConns[db]; + ConnectConnection* c = ccl.pop_front(); + bool needInit = false; + if (!c) { + if (mServ->fail()) { + return nullptr; + } + c = ConnectConnectionAlloc::create(mServ, false); + c->setDb(db); + ++mStats.connections; + needInit = true; + logNotice("h %d create private server connection %s %d", + mHandler->id(), c->peer(), c->fd()); + } + if (c->fd() < 0) { + if (mServ->fail()) { + return nullptr; + } + c->reopen(); + needInit = true; + logNotice("h %d reopen server connection %s %d", + mHandler->id(), c->peer(), c->fd()); + } + if (needInit && !init(c)) { + c->close(mHandler); + ccl.push_back(c); + return nullptr; + } + return c; +} + +void ConnectConnectionPool::putPrivateConnection(ConnectConnection* s) +{ + logDebug("h %d put private connection s %s %d", + mHandler->id(), s->peer(), s->fd()); + unsigned db = s->db(); + if (db < mPrivateConns.size()) { + mPrivateConns[db].push_back(s); + } else { + logWarn("h %d s %s %d put to pool with db %d invalid", + mHandler->id(), s->peer(), s->fd(), s->db()); + } +} + +void ConnectConnectionPool::putTransactionConnection(ConnectConnection* s, bool inWatch, bool inMulti) +{ + if (s->good()) { + if (inMulti) { + RequestPtr req = RequestAlloc::create(Request::DiscardServ); + mHandler->handleRequest(req, s); + logDebug("h %d s %s %d discard req %ld", + mHandler->id(), s->peer(), s->fd(), req->id()); + } else if (inWatch) { + RequestPtr req = RequestAlloc::create(Request::UnwatchServ); + mHandler->handleRequest(req, s); + logDebug("h %d s %s %d unwatch req %ld", + mHandler->id(), s->peer(), s->fd(), req->id()); + } + } + putPrivateConnection(s); +} + +bool ConnectConnectionPool::init(ConnectConnection* c) +{ + if (!c->setNonBlock()) { + logWarn("h %d s %s %d set non block fail", + mHandler->id(), c->peer(), c->fd()); + return false; + } + if (!c->setTcpNoDelay()) { + logWarn("h %d s %s %d settcpnodelay fail %s", + mHandler->id(), c->peer(), c->fd(), StrError()); + } + auto m = mHandler->eventLoop(); + if (!m->addSocket(c, Multiplexor::ReadEvent|Multiplexor::WriteEvent)) { + logWarn("h %d s %s %d add to eventloop fail", + mHandler->id(), c->peer(), c->fd()); + return false; + } + ++mStats.connect; + if (!c->connect()) { + logWarn("h %d s %s %d connect fail", + mHandler->id(), c->peer(), c->fd()); + m->delSocket(c); + return false; + } + if (mServ->password().empty()) { + c->setAuth(true); + } else { + c->setAuth(false); + RequestPtr req = RequestAlloc::create(); + req->setAuth(mServ->password()); + mHandler->handleRequest(req, c); + logDebug("h %d s %s %d auth req %ld", + mHandler->id(), c->peer(), c->fd(), req->id()); + } + auto sp = mHandler->proxy()->serverPool(); + if (sp->type() == ServerPool::Cluster) { + RequestPtr req = RequestAlloc::create(Request::Readonly); + mHandler->handleRequest(req, c); + logDebug("h %d s %s %d readonly req %ld", + mHandler->id(), c->peer(), c->fd(), req->id()); + } + int db = c->db(); + if (db != 0) { + RequestPtr req = RequestAlloc::create(); + req->setSelect(db); + mHandler->handleRequest(req, c); + logDebug("h %d s %s %d select %d req %ld", + mHandler->id(), c->peer(), c->fd(), db, req->id()); + } + RequestPtr req = RequestAlloc::create(Request::PingServ); + mHandler->handleRequest(req, c); + logDebug("h %d s %s %d ping req %ld", + mHandler->id(), c->peer(), c->fd(), req->id()); + return true; +} + +void ConnectConnectionPool::check() +{ + FuncCallTimer(); + if (!mServ->fail() || !mServ->online()) { + return; + } + if (mServ->activate()) { + auto c = mShareConns.empty() ? nullptr : mShareConns[0]; + if (!c) { + return; + } + if (c->fd() >= 0) { + return; + } + c->reopen(); + logNotice("h %d check server reopen connection %s %d", + mHandler->id(), c->peer(), c->fd()); + if (!init(c)) { + c->close(mHandler); + } + } +} diff --git a/src/ConnectConnectionPool.h b/src/ConnectConnectionPool.h new file mode 100644 index 0000000..94b8228 --- /dev/null +++ b/src/ConnectConnectionPool.h @@ -0,0 +1,81 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_CONNECT_CONNECTION_POOL_H_ +#define _PREDIXY_CONNECT_CONNECTION_POOL_H_ + +#include +#include "ConnectConnection.h" +#include "Server.h" +#include "Handler.h" +#include "Request.h" +#include "Predixy.h" +#include "Stats.h" +#include "LatencyMonitor.h" + +class ConnectConnectionPool +{ +public: + ConnectConnectionPool(Handler* h, Server* s, int dbnum); + ~ConnectConnectionPool(); + ConnectConnection* getShareConnection(int db=0); + ConnectConnection* getPrivateConnection(int db=0); + void putPrivateConnection(ConnectConnection* s); + void putTransactionConnection(ConnectConnection* s, bool inWatch, bool inMulti); + void check(); + Server* server() const + { + return mServ; + } + int pendRequests() + { + return mPendRequests; + } + int incrPendRequests() + { + return ++mPendRequests; + } + int decrPendRequests() + { + return --mPendRequests; + } + ServerStats& stats() + { + return mStats; + } + const ServerStats& stats() const + { + return mStats; + } + std::vector& latencyMonitors() + { + return mLatencyMonitors; + } + const std::vector& latencyMonitors() const + { + return mLatencyMonitors; + } + void resetStats() + { + mStats.reset(); + for (auto& m : mLatencyMonitors) { + m.reset(); + } + } +private: + bool init(ConnectConnection* c); +private: + Handler* mHandler; + Server* mServ; + int mPendRequests; + std::vector mShareConns; + std::vector mPrivateConns; + ServerStats mStats; + std::vector mLatencyMonitors; +}; + + +#endif diff --git a/src/ConnectSocket.cpp b/src/ConnectSocket.cpp new file mode 100644 index 0000000..5628121 --- /dev/null +++ b/src/ConnectSocket.cpp @@ -0,0 +1,66 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "ConnectSocket.h" + + +ConnectSocket::ConnectSocket(const char* peer, int type, int protocol): + mPeer(peer), + mType(type), + mProtocol(protocol) +{ + mClassType = ConnectType; + mPeerAddrLen = sizeof(mPeerAddr); + getFirstAddr(peer, type, protocol, (sockaddr*)&mPeerAddr, &mPeerAddrLen); + sockaddr* in = (sockaddr*)&mPeerAddr; + int fd = Socket::socket(in->sa_family, type, protocol); + attach(fd); + mStatus = Unconnected; +} + +bool ConnectSocket::connect() +{ + if (mStatus == Connecting || mStatus == Connected) { + return true; + } + bool retry; + do { + retry = false; + int ret = ::connect(fd(), (const sockaddr*)&mPeerAddr, mPeerAddrLen); + if (ret == 0) { + mStatus = Connected; + } else { + if (errno == EINPROGRESS || errno == EALREADY) { + mStatus = Connecting; + } else if (errno == EISCONN) { + mStatus = Connected; + } else if (errno == EINTR) { + retry = true; + } else { + mStatus = Unconnected; + return false; + } + } + } while (retry); + return true; +} + +void ConnectSocket::reopen() +{ + if (fd() >= 0) { + return; + } + sockaddr* in = (sockaddr*)&mPeerAddr; + int fd = Socket::socket(in->sa_family, mType, mProtocol); + attach(fd); + mStatus = Unconnected; +} + +void ConnectSocket::close() +{ + mStatus = Disconnected; + Socket::close(); +} diff --git a/src/ConnectSocket.h b/src/ConnectSocket.h new file mode 100644 index 0000000..0dd5bb9 --- /dev/null +++ b/src/ConnectSocket.h @@ -0,0 +1,58 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_CONNECT_SOCKET_H_ +#define _PREDIXY_CONNECT_SOCKET_H_ + +#include "Socket.h" +#include + +class ConnectSocket : public Socket +{ +public: + enum ConnectStatus + { + Unconnected, + Connecting, + Connected, + Disconnected + }; +public: + ConnectSocket(const char* peer, int type, int protocol = 0); + bool connect(); + void reopen(); + void close(); + const char* peer() const + { + return mPeer.c_str(); + } + ConnectStatus connectStatus() const + { + return good() ? mStatus : Disconnected; + } + bool isConnecting() const + { + return mStatus == Connecting; + } + void setConnected() + { + mStatus = Connected; + } + void setConnectStatus(ConnectStatus st) + { + mStatus = st; + } +private: + std::string mPeer; + sockaddr_storage mPeerAddr; + socklen_t mPeerAddrLen; + int mType; + int mProtocol; + ConnectStatus mStatus; +}; + + +#endif diff --git a/src/Connection.cpp b/src/Connection.cpp new file mode 100644 index 0000000..51daf69 --- /dev/null +++ b/src/Connection.cpp @@ -0,0 +1,38 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Connection.h" + +Connection::Connection(): + mPostEvts(0), + mBufCnt(0), + mDb(0) +{ +} + +BufferPtr Connection::getBuffer(Handler* h, bool allowNew) +{ + FuncCallTimer(); + if (allowNew && mBuf) { + if (mBufCnt >= Const::MaxBufListNodeNum) { + mBuf = nullptr; + mBufCnt = 0; + } else if (mBuf->room() < Const::MinBufSpaceLeft) { + mBuf = nullptr; + mBufCnt = 0; + } + } + if (!mBuf || mBuf->full()) { + BufferPtr buf = Alloc::create(); + if (mBuf) { + mBuf->concat(buf); + } + mBuf = buf; + ++mBufCnt; + } + return mBuf; +} + diff --git a/src/Connection.h b/src/Connection.h new file mode 100644 index 0000000..6ec6df3 --- /dev/null +++ b/src/Connection.h @@ -0,0 +1,61 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_CONNECTION_H_ +#define _PREDIXY_CONNECTION_H_ + +#include "Socket.h" +#include "List.h" +#include "Common.h" +#include "Buffer.h" + +class Handler; + +class Connection +{ +public: + enum ClassType + { + AcceptType = Socket::CustomType, + ConnectType + }; + enum StatusEnum + { + ParseError = Socket::CustomStatus, + LogicError + }; +public: + Connection(); + int getPostEvent() const + { + return mPostEvts; + } + void addPostEvent(int evts) + { + mPostEvts |= evts; + } + void setPostEvent(int evts) + { + mPostEvts = evts; + } + int db() const + { + return mDb; + } + void setDb(int db) + { + mDb = db; + } +protected: + BufferPtr getBuffer(Handler* h, bool allowNew); +private: + int mPostEvts; + BufferPtr mBuf; + int mBufCnt; + int mDb; +}; + +#endif diff --git a/src/Crc16.cpp b/src/Crc16.cpp new file mode 100644 index 0000000..863c99b --- /dev/null +++ b/src/Crc16.cpp @@ -0,0 +1,56 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "HashFunc.h" + +static const uint16_t crc16tab[256]= { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, + 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, + 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, + 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, + 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, + 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, + 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, + 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, + 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, + 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, + 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, + 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, + 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, + 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, + 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, + 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, + 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, + 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, + 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, + 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, + 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, + 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, + 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, + 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, + 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, + 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, + 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, + 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, + 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, + 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, + 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 +}; + +uint16_t Hash::crc16(const char *buf, int len) { + int counter; + uint16_t crc = 0; + for (counter = 0; counter < len; counter++) + crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; + return crc; +} + +uint16_t Hash::crc16(uint16_t crc, char k) +{ + crc = (crc<<8) ^ crc16tab[((crc>>8) ^ k)&0x00FF]; + return crc; +} diff --git a/src/DC.cpp b/src/DC.cpp new file mode 100644 index 0000000..b6e2535 --- /dev/null +++ b/src/DC.cpp @@ -0,0 +1,79 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "DC.h" + +DC::DC(const String& name, int size): + mName(name) +{ + mReadPolicy.resize(size); +} + +DC::~DC() +{ +} + +DataCenter::DataCenter(): + mLocalDC(nullptr) +{ +} + +DataCenter::~DataCenter() +{ +} + +void DataCenter::init(const Conf* conf) +{ + for (auto& c : conf->dcConfs()) { + if (mDCs.find(c.name) != mDCs.end()) { + Throw(InitFail, "DC \"%s\" duplicate define", c.name.c_str()); + } + DC* dc = new DC(c.name, conf->dcConfs().size()); + mDCs[c.name] = dc; + if (c.name == conf->localDC()) { + mLocalDC = dc; + } + for (auto& addr : c.addrPrefix) { + if (mAddrDC.find(addr) != mAddrDC.end()) { + Throw(InitFail, "DC \"%s\" AddrPrefix \"%s\" collision with DC \"%s\"", + c.name.c_str(), addr.c_str(), mAddrDC[addr]->name().data()); + } + mAddrDC[addr] = dc; + } + } + if (!mLocalDC) { + Throw(InitFail, "DataCenter can't find localDC \"%s\"", + conf->localDC().c_str()); + } + for (auto& c : conf->dcConfs()) { + DC* dc = mDCs[c.name]; + for (auto& rp : c.readPolicy) { + auto it = mDCs.find(rp.name); + if (it == mDCs.end()) { + Throw(InitFail, "DC \"%s\" ReadPolicy \"%s\" no exists in DataCenter", + dc->name().data(), rp.name.c_str()); + } + dc->set(it->second, rp); + } + } +} + +DC* DataCenter::getDC(const String& addr) const +{ + String p = addr; + while (true) { + auto it = mAddrDC.upper_bound(p); + if (it == mAddrDC.begin()) { + return nullptr; + } + --it; + if (p.hasPrefix(it->first)) { + return it->second; + } + p = p.commonPrefix(it->first); + } + return nullptr; +} diff --git a/src/DC.h b/src/DC.h new file mode 100644 index 0000000..241a8b9 --- /dev/null +++ b/src/DC.h @@ -0,0 +1,72 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_DC_H_ +#define _PREDIXY_DC_H_ + +#include +#include +#include "ID.h" +#include "Conf.h" +#include "String.h" + +struct DCReadPolicy +{ + int priority = 0; + int weight = 0; +}; + +class DC : public ID +{ +public: + DC(const String& name, int size); + ~DC(); + const String& name() const + { + return mName; + } + void set(DC* oth, const ReadPolicyConf& c) + { + mReadPolicy[oth->id()].priority = c.priority; + mReadPolicy[oth->id()].weight = c.weight; + } + int getReadPriority(DC* oth) const + { + return oth ? mReadPolicy[oth->id()].priority : 1; + } + int getReadWeight(DC* oth) const + { + return oth ? mReadPolicy[oth->id()].weight : 1; + } + const DCReadPolicy& getReadPolicy(DC* oth) const + { + return mReadPolicy[oth->id()]; + } +private: + String mName; + std::vector mReadPolicy; +}; + +class DataCenter +{ +public: + DefException(InitFail); +public: + DataCenter(); + ~DataCenter(); + void init(const Conf* conf); + DC* getDC(const String& addr) const; + DC* localDC() const + { + return mLocalDC; + } +private: + std::map mDCs; + std::map mAddrDC; + DC* mLocalDC; +}; + +#endif diff --git a/src/Deque.h b/src/Deque.h new file mode 100644 index 0000000..eae90d2 --- /dev/null +++ b/src/Deque.h @@ -0,0 +1,193 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_DEQUE_H_ +#define _PREDIXY_DEQUE_H_ + + +template +class DequeNode +{ +public: + typedef P Ptr; + DequeNode() + { + for (int i = 0; i < Size; ++i) { + mPrev[i] = nullptr; + mNext[i] = nullptr; + } + } + ~DequeNode() + { + for (int i = 0; i < Size; ++i) { + mPrev[i] = nullptr; + mNext[i] = nullptr; + } + } + void reset(int idx) + { + mPrev[idx] = nullptr; + mNext[idx] = nullptr; + } + void resetPrev(int idx) + { + mPrev[idx] = nullptr; + } + void resetNext(int idx) + { + mNext[idx] = nullptr; + } + void concat(T* obj, int idx) + { + mNext[idx] = obj; + static_cast(obj)->mPrev[idx] = (T*)this; + } + P prev(int idx) const + { + return mPrev[idx]; + } + P next(int idx) const + { + return mNext[idx]; + } +private: + P mPrev[Size]; + P mNext[Size]; +}; + +template +class Deque +{ +public: + typedef typename N::Value T; + typedef typename N::DequeNodeType Node; + typedef typename Node::Ptr P; +public: + Deque(): + mSize(0), + mHead(nullptr), + mTail(nullptr) + { + } + ~Deque() + { + while (mSize > 0) { + pop_front(); + } + } + P prev(T* obj) + { + return node(obj)->prev(Idx); + } + P next(T* obj) + { + return node(obj)->next(Idx); + } + void push_back(T* obj) + { + N* p = static_cast(obj); + if (mTail) { + static_cast((T*)mTail)->concat(p, Idx); + mTail = p; + } else { + mHead = mTail = p; + } + ++mSize; + } + void push_front(T* obj) + { + N* p = static_cast(obj); + if (mHead) { + node(obj)->concat(mHead, Idx); + mHead = p; + } else { + mHead = mTail = p; + } + ++mSize; + } + void remove(T* obj) + { + auto n = node(obj); + auto prev = n->prev(Idx); + auto next = n->next(Idx); + int exists = 1; + if (prev && next) { + node(prev)->concat(next, Idx); + } else if (prev) { + node(prev)->resetNext(Idx); + mTail = prev; + } else if (next) { + node(next)->resetPrev(Idx); + mHead = next; + } else { + if (mHead == n) { + mHead = mTail = nullptr; + } else { + exists = 0; + } + } + mSize -= exists; + n->reset(Idx); + } + void move_back(T* obj) + { + auto n = node(obj); + if (auto next = n->next(Idx)) { + if (auto prev = n->prev(Idx)) { + node(prev)->concat(next, Idx); + } else { + next->resetPrev(Idx); + mHead = next; + } + node(mTail)->concat(obj, Idx); + mTail = obj; + n->resetNext(Idx); + } + } + P pop_front() + { + P obj = mHead; + if (obj) { + remove(obj); + } + return obj; + } + P pop_back() + { + P obj = mTail; + if (obj) { + remove(obj); + } + return obj; + } + P front() const + { + return mHead; + } + P back() const + { + return mTail; + } + int size() const + { + return mSize; + } + bool empty() const + { + return mSize == 0; + } +private: + static Node* node(T* obj) + { + return static_cast(obj); + } +private: + int mSize; + P mHead; + P mTail; +}; + +#endif diff --git a/src/Distribution.cpp b/src/Distribution.cpp new file mode 100644 index 0000000..444b545 --- /dev/null +++ b/src/Distribution.cpp @@ -0,0 +1,36 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include "Distribution.h" + +struct TypeName +{ + Distribution::Type type; + const char* name; +}; + +static TypeName Pairs[] = { + {Distribution::None, "none"}, + {Distribution::Modula, "modula"}, + {Distribution::Random, "random"} +}; + +const char* Distribution::name() const +{ + return Pairs[mType].name; +} + +Distribution Distribution::parse(const char* str) +{ + for (auto& i : Pairs) { + if (strcasecmp(i.name, str) == 0) { + return i.type; + } + } + return Distribution::None; +} diff --git a/src/Distribution.h b/src/Distribution.h new file mode 100644 index 0000000..cdd51bb --- /dev/null +++ b/src/Distribution.h @@ -0,0 +1,34 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_DISTRIBUTION_H_ +#define _PREDIXY_DISTRIBUTION_H_ + +class Distribution +{ +public: + enum Type + { + None, + Modula, + Random + }; +public: + Distribution(Type t = None): + mType(t) + { + } + operator Type() const + { + return mType; + } + const char* name() const; + static Distribution parse(const char* str); +private: + Type mType; +}; + +#endif diff --git a/src/EpollMultiplexor.cpp b/src/EpollMultiplexor.cpp new file mode 100644 index 0000000..ea34a10 --- /dev/null +++ b/src/EpollMultiplexor.cpp @@ -0,0 +1,106 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "EpollMultiplexor.h" +#include "Socket.h" + +EpollMultiplexor::EpollMultiplexor(): + mFd(-1) +{ + int fd = epoll_create(1024); + if (fd < 0) { + Throw(EpollCreateFail, "epoll create fail %s", StrError()); + } + mFd = fd; +} + +EpollMultiplexor::~EpollMultiplexor() +{ + if (mFd >= 0) { + ::close(mFd); + } +} + +bool EpollMultiplexor::addSocket(Socket* s, int evts) +{ + epoll_event event; + memset(&event, 0, sizeof(event)); + event.events |= (evts & ReadEvent) ? EPOLLIN : 0; + event.events |= (evts & WriteEvent) ? EPOLLOUT : 0; + event.events |= EPOLLET; + //event.events |= EPOLLONESHOT; + event.data.ptr = s; + int ret = epoll_ctl(mFd, EPOLL_CTL_ADD, s->fd(), &event); + if (ret == 0) { + s->setEvent(evts); + } + return ret == 0; +} + +void EpollMultiplexor::delSocket(Socket* s) +{ + epoll_event event; + memset(&event, 0, sizeof(event)); + epoll_ctl(mFd, EPOLL_CTL_DEL, s->fd(), &event); + s->setEvent(0); +} + +bool EpollMultiplexor::addEvent(Socket* s, int evts) +{ + epoll_event event; + memset(&event, 0, sizeof(event)); + event.data.ptr = s; + if ((evts & ReadEvent) || (s->getEvent() & ReadEvent)) { + event.events |= EPOLLIN; + } + if ((evts & WriteEvent) || (s->getEvent() & WriteEvent)) { + event.events |= EPOLLOUT; + } + if ((s->getEvent() | evts) != s->getEvent()) { + event.events |= EPOLLET; + //event.events |= EPOLLONESHOT; + int ret = epoll_ctl(mFd, EPOLL_CTL_MOD, s->fd(), &event); + if (ret == 0) { + s->setEvent(s->getEvent() | evts); + } else { + return false; + } + } + return true; +} + +bool EpollMultiplexor::delEvent(Socket* s, int evts) +{ + bool mod = false; + epoll_event event; + memset(&event, 0, sizeof(event)); + event.data.ptr = s; + if (s->getEvent() & ReadEvent) { + if (evts & ReadEvent) { + mod = true; + } else { + event.events |= EPOLLIN; + } + } + if (s->getEvent() & WriteEvent) { + if (evts & WriteEvent) { + mod = true; + } else { + event.events |= EPOLLOUT; + } + } + if (mod) { + event.events |= EPOLLET; + //event.events |= EPOLLONESHOT; + int ret = epoll_ctl(mFd, EPOLL_CTL_MOD, s->fd(), &event); + if (ret == 0) { + s->setEvent(s->getEvent() & ~evts); + } else { + return false; + } + } + return true; +} diff --git a/src/EpollMultiplexor.h b/src/EpollMultiplexor.h new file mode 100644 index 0000000..14262ca --- /dev/null +++ b/src/EpollMultiplexor.h @@ -0,0 +1,65 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_EPOLL_MULTIPLEXOR_H_ +#define _PREDIXY_EPOLL_MULTIPLEXOR_H_ + +#include +#include +#include "Multiplexor.h" +#include "Exception.h" +#include "Util.h" + +class EpollMultiplexor : public MultiplexorBase +{ +public: + DefException(EpollCreateFail); + DefException(EpollWaitFail); +public: + EpollMultiplexor(); + ~EpollMultiplexor(); + bool addSocket(Socket* s, int evts = ReadEvent); + void delSocket(Socket* s); + bool addEvent(Socket* s, int evts); + bool delEvent(Socket* s, int evts); + template + int wait(long usec, T* handler); +private: + int mFd; + static const int MaxEvents = 1024; + epoll_event mEvents[MaxEvents]; +}; + + +template +int EpollMultiplexor::wait(long usec, T* handler) +{ + int timeout = usec < 0 ? usec : usec / 1000; + logVerb("epoll wait with timeout %ld usec", usec); + int num = epoll_wait(mFd, mEvents, MaxEvents, timeout); + logVerb("epoll wait return with event count:%d", num); + if (num == -1) { + if (errno == EINTR) { + return 0; + } + Throw(EpollWaitFail, "handler %d epoll wait fail %s", handler->id(), StrError()); + } + for (int i = 0; i < num; ++i) { + Socket* s = static_cast(mEvents[i].data.ptr); + int evts = 0; + evts |= (mEvents[i].events & EPOLLIN) ? ReadEvent : 0; + evts |= (mEvents[i].events & EPOLLOUT) ? WriteEvent : 0; + evts |= (mEvents[i].events & (EPOLLERR|EPOLLHUP)) ? ErrorEvent : 0; + handler->handleEvent(s, evts); + } + return num; +} + + +typedef EpollMultiplexor Multiplexor; +#define _MULTIPLEXOR_ASYNC_ASSIGN_ + +#endif diff --git a/src/Exception.h b/src/Exception.h new file mode 100644 index 0000000..3156dd8 --- /dev/null +++ b/src/Exception.h @@ -0,0 +1,63 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_EXCEPTION_H_ +#define _PREDIXY_EXCEPTION_H_ + +#include +#include +#include + +class ExceptionBase : public std::exception +{ +public: + static const int MaxMsgLen = 1024; +public: + ExceptionBase() + { + mMsg[0] = '\0'; + } + ExceptionBase(const char* file, int line, const char* fmt, ...) + { + int n = snprintf(mMsg, sizeof(mMsg), "%s:%d ", file, line); + va_list ap; + va_start(ap, fmt); + vsnprintf(mMsg + n, sizeof(mMsg) - n, fmt, ap); + va_end(ap); + } + ExceptionBase(const char* fmt, ...) + { + va_list ap; + va_start(ap, fmt); + vsnprintf(mMsg, sizeof(mMsg), fmt, ap); + va_end(ap); + } + ~ExceptionBase() + { + } + const char* what() const noexcept + { + return mMsg; + } +protected: + void init(const char* fmt, va_list ap) + { + vsnprintf(mMsg, sizeof(mMsg), fmt, ap); + } +private: + char mMsg[MaxMsgLen]; +}; + +#define DefException(T) class T : public ExceptionBase \ +{ \ +public: \ + template \ + T(A&&... args):ExceptionBase(args...) {} \ +} + +#define Throw(T, ...) throw T(__FILE__, __LINE__, ##__VA_ARGS__) + +#endif diff --git a/src/Handler.cpp b/src/Handler.cpp new file mode 100644 index 0000000..c22736c --- /dev/null +++ b/src/Handler.cpp @@ -0,0 +1,1424 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "Alloc.h" +#include "Handler.h" +#include "Proxy.h" +#include "ListenSocket.h" +#include "ServerGroup.h" +#include "AcceptConnection.h" +#include "ConnectConnection.h" +#include "ClusterServerPool.h" +#include "SentinelServerPool.h" + +Handler::Handler(Proxy* p): + mStop(false), + mProxy(p), + mEventLoop(new Multiplexor()), + mStatsVer(0), + mLatencyMonitors(p->latencyMonitorSet().latencyMonitors()), + mRandSeed(time(nullptr) * (id() + 1)) +{ + if (!mEventLoop->addSocket(p->listener())) { + logError("handler %d add listener to multiplexor fail:%sa", + id(), StrError()); + Throw(AddListenerEventFail, "handler %d add listener to multiplexor fail:%sa", + id(), StrError()); + } + mConnPool.reserve(Const::MaxServNum); + Conf* conf = p->conf(); + mIDUnique.resize(conf->dcConfs().size()); +} + +Handler::~Handler() +{ +} + +void Handler::run() +{ + auto conf = mProxy->conf(); + refreshServerPool(); + while (!mStop) { + mEventLoop->wait(100000, this); + postEvent(); + long timeout = conf->clientTimeout(); + if (timeout > 0) { + int num = checkClientTimeout(timeout); + if (num > 0) { + postEvent(); + } + } + refreshServerPool(); + checkConnectionPool(); + if (mStatsVer < mProxy->statsVer()) { + resetStats(); + } + } + logNotice("handler %d stopped", id()); +} + +void Handler::stop() +{ + mStop = true; +} + +void Handler::refreshServerPool() +{ + FuncCallTimer(); + try { + ServerPool* sp = mProxy->serverPool(); + if (!sp->refresh()) { + return; + } + sp->refreshRequest(this); + } catch (ExceptionBase& excp) { + logError("h %d refresh server pool exception:%s", + id(), excp.what()); + } +} + +void Handler::checkConnectionPool() +{ + for (auto p : mConnPool) { + try { + if (p) { + p->check(); + } + } catch (ExceptionBase& excp) { + logError("h %d check connection pool %s excp %s", + id(), p->server()->addr().data(), excp.what()); + } + } +} + +void Handler::handleEvent(Socket* s, int evts) +{ + FuncCallTimer(); + switch (s->classType()) { + case Socket::ListenType: + handleListenEvent(static_cast(s), evts); + break; + case Connection::AcceptType: + handleAcceptConnectionEvent(static_cast(s),evts); + break; + case Connection::ConnectType: + handleConnectConnectionEvent(static_cast(s),evts); + break; + default: + //should no reach + logError("h %d unexpect socket %d ev %d", id(), s->fd(), evts); + break; + } +} + +void Handler::postEvent() +{ + FuncCallTimer(); + while (!mPostAcceptConns.empty() || !mPostConnectConns.empty()) { + if (!mPostConnectConns.empty()) { + postConnectConnectionEvent(); + } + if (!mPostAcceptConns.empty()) { + postAcceptConnectionEvent(); + } + } +} + +void Handler::addPostEvent(AcceptConnection* c, int evts) +{ + if (!c->getPostEvent()) { + mPostAcceptConns.push_back(c); + setAcceptConnectionActiveTime(c); + } + c->addPostEvent(evts); +} + +void Handler::addPostEvent(ConnectConnection* c, int evts) +{ + if (!c->getPostEvent()) { + mPostConnectConns.push_back(c); + } + c->addPostEvent(evts); +} + +void Handler::postAcceptConnectionEvent() +{ + FuncCallTimer(); + while (!mPostAcceptConns.empty()) { + AcceptConnection* c = mPostAcceptConns.front(); + int evts = c->getPostEvent(); + c->setPostEvent(0); + if (c->good() && (evts & Multiplexor::WriteEvent)) { + bool finished = c->writeEvent(this); + if (c->good()) { + bool ret; + if (finished) { + ret = mEventLoop->delEvent(c, Multiplexor::WriteEvent); + } else { + ret = mEventLoop->addEvent(c, Multiplexor::WriteEvent); + } + if (!ret) { + c->setStatus(Multiplexor::ErrorEvent); + } + } + } + if ((!c->good()||(evts & Multiplexor::ErrorEvent)) && c->fd() >= 0){ + logNotice("h %d remove c %s %d with status %d %s", + id(), c->peer(), c->fd(), c->status(), c->statusStr()); + mEventLoop->delSocket(c); + if (auto s = c->connectConnection()) { + auto cp = mConnPool[s->server()->id()]; + if (c->inTransaction()) { + cp->putTransactionConnection(s, c->inPendWatch(), c->inPendMulti()); + } else if (c->inSub(true)) { + cp->putPrivateConnection(s); + s->setStatus(Connection::LogicError); + addPostEvent(s, Multiplexor::ErrorEvent); + } else { + cp->putPrivateConnection(s); + } + c->detachConnectConnection(); + s->detachAcceptConnection(); + } + mAcceptConns.remove(c); + c->unref(); + c->close(); + --mStats.clientConnections; + } + mPostAcceptConns.pop_front(); + } +} + +void Handler::postConnectConnectionEvent() +{ + FuncCallTimer(); + while (!mPostConnectConns.empty()) { + ConnectConnection* s = mPostConnectConns.pop_front(); + int evts = s->getPostEvent(); + s->setPostEvent(0); + if (s->good() && evts == Multiplexor::WriteEvent && !s->isConnecting()) { + bool finished = s->writeEvent(this); + if (s->good()) { + bool ret; + if (finished) { + ret = mEventLoop->delEvent(s, Multiplexor::WriteEvent); + } else { + ret = mEventLoop->addEvent(s, Multiplexor::WriteEvent); + } + if (!ret) { + s->setStatus(Multiplexor::ErrorEvent); + } + } + } + if ((!s->good()||(evts & Multiplexor::ErrorEvent)) && s->fd() >= 0){ + switch (s->status()) { + case Socket::End: + case Socket::IOError: + case Socket::EventError: + { + Server* serv = s->server(); + serv->incrFail(); + if (serv->fail()) { + logNotice("server %s mark failure", serv->addr().data()); + } + } + break; + default: + break; + } + auto c = s->acceptConnection(); + logNotice("h %d close s %s %d and c %s %d with status %d %s", + id(), s->peer(), s->fd(), + c ? c->peer() : "None", c ? c->fd() : -1, + s->status(), s->statusStr()); + mEventLoop->delSocket(s); + s->close(this); + if (c) { + addPostEvent(c, Multiplexor::ErrorEvent); + s->detachAcceptConnection(); + c->detachConnectConnection(); + } + } + } +} + +void Handler::handleListenEvent(ListenSocket* s, int evts) +{ + FuncCallTimer(); + while (true) { + try { + sockaddr_storage addr; + socklen_t len = sizeof(addr); + int fd = s->accept((sockaddr*)&addr, &len); + if (fd >= 0) { + ++mStats.accept; + addAcceptSocket(fd, (sockaddr*)&addr, len); + } else { + break; + } + } catch (ListenSocket::TooManyOpenFiles& f) { + logNotice("h %d p %s %d e %s", id(), s->addr(), s->fd(), f.what()); + break; + } catch (ExceptionBase& e) { + logError("h %d p %s %d e %s", id(), s->addr(), s->fd(), e.what()); + throw; + } + } +} + +void Handler::addAcceptSocket(int fd, sockaddr* addr, socklen_t len) +{ + FuncCallTimer(); + AcceptConnection* c = nullptr; + try { + c = AcceptConnectionAlloc::create(fd, addr, len); + logNotice("h %d accept c %s %d", id(), c->peer(), fd); + } catch (ExceptionBase& e) { + logWarn("h %d create connection for client %d fail %s", + id(), fd, e.what()); + ::close(fd); + return; + } + if (!c->setNonBlock()) { + logWarn("h %d destroy c %s %d with setnonblock fail:%s", + id(), c->peer(), c->fd(), StrError()); + AcceptConnectionAlloc::destroy(c); + return; + } + if (addr->sa_family == AF_INET || addr->sa_family == AF_INET6) { + if (!c->setTcpNoDelay()) { + logNotice("h %d ignore c %s %d settcpnodelay fail:%s", + id(), c->peer(), c->fd(), StrError()); + } + } + if (auto auth = mProxy->authority()->getDefault()) { + c->setAuth(auth); + } + Handler* dst = this; +#ifdef _MULTIPLEXOR_ASYNC_ASSIGN_ + int clients = INT_MAX; + for (auto h : mProxy->handlers()) { + int num = h->mAcceptConns.size(); + if (num < clients) { + clients = num; + dst = h; + } + } +#endif + c->ref(); + bool fail = false; + if (dst == this) { + if (mEventLoop->addSocket(c)) { + c->setLastActiveTime(Util::elapsedUSec()); + mAcceptConns.push_back(c); + ++mStats.clientConnections; + } else { + fail = true; + } + } else { + c->setLastActiveTime(-1); + if (!dst->mEventLoop->addSocket(c, Multiplexor::ReadEvent|Multiplexor::WriteEvent)) { + fail = true; + } + } + if (fail) { + logWarn("h %d destroy c %s %d with add to event loop fail:%s", + id(), c->peer(), c->fd(), StrError()); + AcceptConnectionAlloc::destroy(c); + } +} + +void Handler::handleAcceptConnectionEvent(AcceptConnection* c, int evts) +{ + FuncCallTimer(); + logVerb("h %d c %s %d ev %d", id(), c->peer(), c->fd(), evts); + if (c->lastActiveTime() < 0) { + c->setLastActiveTime(Util::elapsedUSec()); + mAcceptConns.push_back(c); + ++mStats.clientConnections; + } + if (evts & Multiplexor::ErrorEvent) { + c->setStatus(AcceptConnection::EventError); + } + try { + if (c->good() && (evts & Multiplexor::ReadEvent)) { + c->readEvent(this); + setAcceptConnectionActiveTime(c); + } + if (c->good() && (evts & Multiplexor::WriteEvent)) { + addPostEvent(c, Multiplexor::WriteEvent); + } + } catch (ExceptionBase& e) { + logWarn("h %d c %s %d handle event %d exception %s", + id(), c->peer(), c->fd(), evts, e.what()); + c->setStatus(AcceptConnection::ExceptError); + } + if (!c->good()) { + addPostEvent(c, Multiplexor::ErrorEvent); + logDebug("h %d c %s %d will be close with status %d %s", + id(), c->peer(), c->fd(), c->status(), c->statusStr()); + } +} + +void Handler::handleConnectConnectionEvent(ConnectConnection* s, int evts) +{ + FuncCallTimer(); + logVerb("h %d s %s %d ev %d", id(), s->peer(), s->fd(), evts); + if (evts & Multiplexor::ErrorEvent) { + logDebug("h %d s %s %d error event", + id(), s->peer(), s->fd()); + s->setStatus(ConnectConnection::EventError); + } + try { + if (s->good() && (evts & Multiplexor::ReadEvent)) { + s->readEvent(this); + } + if (s->good() && (evts & Multiplexor::WriteEvent)) { + if (s->isConnecting()) { + s->setConnected(); + } + addPostEvent(s, Multiplexor::WriteEvent); + } + } catch (ExceptionBase& e) { + logError("h %d s %s %d handle event %d exception %s", + id(), s->peer(), s->fd(), evts, e.what()); + s->setStatus(ConnectConnection::ExceptError); + } + if (!s->good()) { + addPostEvent(s, Multiplexor::ErrorEvent); + logError("h %d s %s %d will be close with status %d %s", + id(), s->peer(), s->fd(), s->status(), s->statusStr()); + } +} + +ConnectConnection* Handler::getConnectConnection(Request* req, Server* serv) +{ + FuncCallTimer(); + unsigned sid = serv->id(); + auto p = sid < mConnPool.size() ? mConnPool[sid] : nullptr; + if (!p) { + if (sid >= mConnPool.size()) { + if (sid >= mConnPool.capacity()) { + logError("h %d too many servers %d in server pool ignore server %s", + id(), sid, serv->addr().data()); + return nullptr; + } + mConnPool.resize(sid + 1, nullptr); + } + logNotice("h %d create connection pool for server %s", + id(), serv->addr().data()); + p = new ConnectConnectionPool(this, serv, serv->pool()->dbNum()); + mConnPool[sid] = p; + } + p->stats().requests++; + int db = 0; + auto c = req->connection(); + if (c) { + if (auto s = c->connectConnection()) { + return s; + } + db = c->db(); + } + if (req->requirePrivateConnection()) { + return p->getPrivateConnection(db); + } + return p->getShareConnection(db); +} + +int Handler::checkClientTimeout(long timeout) +{ + int num = 0; + long now = Util::elapsedUSec(); + auto c = mAcceptConns.front(); + while (c) { + long t = now - c->lastActiveTime(); + if (t < timeout) { + break; + } + if (c->empty()) { + ++num; + addPostEvent(c, Multiplexor::ErrorEvent); + logDebug("h %d c %s %d timeout", + id(), c->peer(), c->fd()); + c = mAcceptConns.next(c); + } else { + auto n = mAcceptConns.next(c); + c->setLastActiveTime(now); + mAcceptConns.move_back(c); + c = n; + } + } + return num; +} + +void Handler::handleRequest(Request* req) +{ + FuncCallTimer(); + auto c = req->connection(); + if (c && c->isBlockRequest()) { + return; + } + ++mStats.requests; + req->setDelivered(); + SegmentStr key(req->key()); + logDebug("h %d c %s %d handle req %ld %s %.*s", + id(), c ? c->peer() : "None", c ? c->fd() : -1, + req->id(), req->cmd(), key.length(), key.data()); + Response::GenericCode code; + if (!permission(req, key, code)) { + directResponse(req, code); + return; + } + if (preHandleRequest(req, key)) { + return; + } + auto sp = mProxy->serverPool(); + Server* serv = sp->getServer(this, req); + if (!serv) { + directResponse(req, Response::NoServer); + return; + } + ConnectConnection* s = getConnectConnection(req, serv); + if (!s || !s->good()) { + directResponse(req, Response::NoServerConnection); + return; + } + if (s->isShared()) { + mConnPool[serv->id()]->incrPendRequests(); + } + s->send(this, req); + addPostEvent(s, Multiplexor::WriteEvent); + postHandleRequest(req, s); +} + +bool Handler::preHandleRequest(Request* req, const String& key) +{ + FuncCallTimer(); + auto c = req->connection(); + if (c && c->inTransaction()) { + switch (req->type()) { + case Command::Select: + case Command::Psubscribe: + case Command::Subscribe: + { + ResponsePtr res = ResponseAlloc::create(); + char buf[128]; + snprintf(buf, sizeof(buf), "forbid command \"%s\" in transaction", + req->cmd()); + res->setErr(buf); + handleResponse(nullptr, req, res); + addPostEvent(c, Multiplexor::ErrorEvent); + return true; + } + default: + break; + } + return false; + } + switch (req->type()) { + case Command::Ping: + case Command::Echo: + if (key.empty()) { + directResponse(req, Response::Pong); + } else { + ResponsePtr res = ResponseAlloc::create(); + res->setStr(key.data(), key.length()); + handleResponse(nullptr, req, res); + } + return true; + case Command::Select: + { + int db = -1; + if (key.toInt(db)) { + if (db >= 0 && db < mProxy->serverPool()->dbNum()) { + c->setDb(db); + directResponse(req, Response::Ok); + return true; + } + } + directResponse(req, Response::InvalidDb); + } + return true; + case Command::Cmd: + directResponse(req, Response::Cmd); + return true; + case Command::Info: + infoRequest(req, key); + return true; + case Command::Config: + configRequest(req, key); + return true; + case Command::Script: + if (key.length() == 4 && strncasecmp(key.data(), "load", 4) == 0) { + int cursor = 0; + auto sp = mProxy->serverPool(); + Server* leaderServ = sp->iter(cursor); + if (!leaderServ) { + directResponse(req, Response::NoServer); + return true; + } + req->setType(Command::ScriptLoad); + req->follow(req); + while (Server* serv = sp->iter(cursor)) { + RequestPtr r = RequestAlloc::create(); + r->follow(req); + ConnectConnection* s = getConnectConnection(r, serv); + if (!s) { + directResponse(r, Response::NoServerConnection); + break; + } + handleRequest(r, s); + } + //req must be handle in the last, avoid directResponse this req + ConnectConnection* s = getConnectConnection(req, leaderServ); + if (!s) { + directResponse(req, Response::NoServerConnection); + return true; + } + handleRequest(req, s); + } else { + directResponse(req, Response::UnknownCmd); + } + return true; + case Command::Watch: + case Command::Multi: + if (!mProxy->supportTransaction()) { + directResponse(req, Response::ForbidTransaction); + return true; + } + break; + case Command::Scan: + { + auto sp = mProxy->serverPool(); + unsigned long cursor = atol(key.data()); + int groupIdx = cursor & Const::ServGroupMask; + auto g = sp->getGroup(groupIdx); + if (!g) { + directResponse(req, Response::InvalidScanCursor); + return true; + } + Server* serv = g->getMaster(); + while (!serv && (g = sp->getGroup(++groupIdx)) != nullptr) { + serv = g->getMaster(); + } + if (!serv) { + directResponse(req, Response::ScanEnd); + return true; + } + if (ConnectConnection* s = getConnectConnection(req, serv)) { + if (cursor != 0) { + req->adjustScanCursor(cursor >> Const::ServGroupBits); + } + handleRequest(req, s); + } else { + directResponse(req, Response::NoServerConnection); + } + return true; + } + break; + default: + break; + } + return false; +} + +void Handler::postHandleRequest(Request* req, ConnectConnection* s) +{ + auto c = req->connection(); + if (!c) { + return; + } + switch (req->type()) { + case Command::Blpop: + case Command::Brpop: + case Command::Brpoplpush: + case Command::Unwatch: + case Command::Exec: + case Command::Discard: + case Command::Punsubscribe: + case Command::Unsubscribe: + c->setBlockRequest(true); + break; + case Command::Watch: + if (c->incrPendWatch() <= 0) { + addPostEvent(c, Multiplexor::ErrorEvent); + return; + } + c->attachConnectConnection(s); + s->attachAcceptConnection(c); + break; + case Command::Multi: + if (c->incrPendMulti() <= 0) { + addPostEvent(c, Multiplexor::ErrorEvent); + return; + } + c->attachConnectConnection(s); + s->attachAcceptConnection(c); + break; + case Command::Psubscribe: + case Command::Subscribe: + if (c->incrPendSub() <= 0) { + addPostEvent(c, Multiplexor::ErrorEvent); + return; + } + c->attachConnectConnection(s); + s->attachAcceptConnection(c); + break; + default: + break; + } +} + +void Handler::handleRequest(Request* req, ConnectConnection* s) +{ + if (!s->good()) { + return; + } + s->send(this, req); + addPostEvent(s, Multiplexor::WriteEvent); + mStats.requests++; + mConnPool[s->server()->id()]->stats().requests++; + if (s->isShared()) { + mConnPool[s->server()->id()]->incrPendRequests(); + } +} + +void Handler::directResponse(Request* req, Response::GenericCode code, ConnectConnection* s) +{ + FuncCallTimer(); + if (auto c = req->connection()) { + if (c->good()) { + try { + ResponsePtr res = ResponseAlloc::create(code); + handleResponse(s, req, res); + } catch (ExceptionBase& excp) { + c->setStatus(AcceptConnection::LogicError); + addPostEvent(c, Multiplexor::ErrorEvent); + logWarn("h %d c %s %d will be close req %ld direct response %d excp %s", + id(), c->peer(), c->fd(), req->id(), code, excp.what()); + } + } else { + logInfo("h %d ignore req %ld res code %d c %s %d status %d %s", + id(), req->id(), code, c->peer(), c->fd(), c->status(), c->statusStr()); + } + } else { + logInfo("h %d ignore req %ld res code %d without accept connection", + id(), req->id(), code); + } +} + +void Handler::handleResponse(ConnectConnection* s, Request* req, Response* res) +{ + FuncCallTimer(); + SegmentStr key(req->key()); + logDebug("h %d s %s %d req %ld %s %.*s res %ld %s", + id(), (s ? s->peer() : "None"), (s ? s->fd() : -1), + req->id(), req->cmd(), key.length(), key.data(), + res->id(), res->typeStr()); + mStats.responses++; + if (s) { + mConnPool[s->server()->id()]->stats().responses++; + if (s->isShared()) { + mConnPool[s->server()->id()]->decrPendRequests(); + } + } + if (req->isInner()) { + innerResponse(s, req, res); + return; + } + auto sp = mProxy->serverPool(); + AcceptConnection* c = req->connection(); + if (!c) { + logInfo("h %d ignore req %ld res %ld", id(), req->id(), res->id()); + return; + } else if (!c->good()) { + logWarn("h %d ignore req %ld res %ld for c %s %d with status %d %s", + id(), req->id(), res->id(), + c->peer(), c->fd(), c->status(), c->statusStr()); + return; + } + if (sp->type() == ServerPool::Cluster && res->type() == Reply::Error) { + if (res->isMoved()) { + if (redirect(s, req, res, true)) { + return; + } + } else if (res->isAsk()) { + if (redirect(s, req, res, false)) { + return; + } + } + } else if (req->type() == Command::Scan && s && res->type() == Reply::Array) { + SegmentStr<64> str(res->body()); + if (const char* p = strchr(str.data() + sizeof("*2\r\n$"), '\n')) { + long cursor = atol(p + 1); + auto g = s->server()->group(); + if (cursor != 0 || (g = sp->getGroup(g->id() + 1)) != nullptr) { + cursor <<= Const::ServGroupBits; + cursor |= g->id(); + if ((p = strchr(p, '*')) != nullptr) { + char buf[32]; + int n = snprintf(buf, sizeof(buf), "%ld", cursor); + res->head().fset(nullptr, + "*2\r\n" + "$%d\r\n" + "%s\r\n", + n, buf); + res->body().cut(p - str.data()); + } + } + } + } + if (req->leader()) { + res->adjustForLeader(req); + } + req->setResponse(res); + if (c->send(this, req, res)) { + addPostEvent(c, Multiplexor::WriteEvent); + } + long elapsed = Util::elapsedUSec() - req->createTime(); + if (auto cp = s ? mConnPool[s->server()->id()] : nullptr) { + for (auto i : mProxy->latencyMonitorSet().cmdIndex(req->type())) { + int idx = mLatencyMonitors[i].add(elapsed); + if (idx >= 0) { + cp->latencyMonitors()[i].add(elapsed, idx); + } + } + } else { + for (auto i : mProxy->latencyMonitorSet().cmdIndex(req->type())) { + mLatencyMonitors[i].add(elapsed); + } + } + logInfo("RESP h %d c %s %d req %ld %s %.*s s %s %d res %ld %s t %ld", + id(), c->peer(), c->fd(), + req->id(), req->cmd(), key.length(), key.data(), + (s ? s->peer() : "None"), (s ? s->fd() : -1), + res->id(), res->typeStr(), elapsed); + switch (req->type()) { + case Command::Blpop: + case Command::Brpop: + case Command::Brpoplpush: + case Command::Punsubscribe: + case Command::Unsubscribe: + c->setBlockRequest(false); + break; + case Command::Watch: + if (res->isOk()) { + c->incrWatch(); + } else { + c->decrPendWatch(); + } + break; + case Command::Unwatch: + if (res->isOk()) { + c->unwatch(); + } + c->setBlockRequest(false); + break; + case Command::Multi: + if (res->isOk()) { + c->incrMulti(); + } else { + c->decrPendMulti(); + } + break; + case Command::Exec: + case Command::Discard: + if (c->inMulti()) { + if (s) { + c->unwatch(); + c->decrMulti(); + } else { + addPostEvent(c, Multiplexor::ErrorEvent); + } + } + c->setBlockRequest(false); + break; + case Command::Psubscribe: + case Command::Subscribe: + if (!s) { + c->decrPendSub(); + } + break; + default: + break; + } + if (auto cs = c->connectConnection()) { + if (!c->inTransaction() && !c->inSub(true)) { + mConnPool[cs->server()->id()]->putPrivateConnection(cs); + c->detachConnectConnection(); + cs->detachAcceptConnection(); + } + } +} + +void Handler::infoRequest(Request* req, const String& key) +{ + if (key.equal("ResetStats", true)) { + mProxy->incrStatsVer(); + directResponse(req, Response::Ok); + return; + } else if (key.equal("Latency", true)) { + infoLatencyRequest(req); + return; + } else if (key.equal("ServerLatency", true)) { + infoServerLatencyRequest(req); + return; + } + ResponsePtr res = ResponseAlloc::create(); + res->setType(Reply::String); + Segment& body = res->body(); + BufferPtr buf = body.fset(nullptr, "# Proxy\n"); + buf = buf->fappend("Version:%s\n", _PREDIXY_VERSION_); + buf = buf->fappend("Name:%s\n", mProxy->conf()->name()); + buf = buf->fappend("Bind:%s\n", mProxy->conf()->bind()); + buf = buf->fappend("RedisMode:proxy\n"); +#ifdef _PREDIXY_SINGLE_THREAD_ + buf = buf->fappend("SingleThread:true\n"); +#else + buf = buf->fappend("SingleThread:false\n"); +#endif + buf = buf->fappend("WorkerThreads:%d\n", mProxy->conf()->workerThreads()); + SString<32> timeStr; + timeStr.strftime("%Y-%m-%d %H:%M:%S", mProxy->startTime()); + buf = buf->fappend("UptimeSince:%s\n", timeStr.data()); + buf = buf->fappend("\n"); + + buf = buf->fappend("# SystemResource\n"); + buf = buf->fappend("UsedMemory:%ld\n", AllocBase::getUsedMemory()); + buf = buf->fappend("MaxMemory:%ld\n", AllocBase::getMaxMemory()); + struct rusage ru; + int ret = getrusage(RUSAGE_SELF, &ru); + if (ret == 0) { + buf = buf->fappend("MaxRSS:%ld\n", ru.ru_maxrss<<10); + buf = buf->fappend("UsedCpuSys:%d.%d\n", ru.ru_stime.tv_sec, ru.ru_stime.tv_usec / 1000); + buf = buf->fappend("UsedCpuUser:%d.%d\n", ru.ru_utime.tv_sec, ru.ru_utime.tv_usec / 1000); + } else { + logError("h %d getrusage fail %s", id(), StrError()); + } + buf = buf->fappend("\n"); + + buf = buf->fappend("# Stats\n"); + HandlerStats st(mStats); + for (auto h : mProxy->handlers()) { + if (h == this) { + continue; + } + st += h->mStats; + } + buf = buf->fappend("Accept:%ld\n", st.accept); + buf = buf->fappend("ClientConnections:%ld\n", st.clientConnections); + buf = buf->fappend("TotalRequests:%ld\n", st.requests); + buf = buf->fappend("TotalResponses:%ld\n", st.responses); + buf = buf->fappend("TotalRecvClientBytes:%ld\n", st.recvClientBytes); + buf = buf->fappend("TotalSendServerBytes:%ld\n", st.sendServerBytes); + buf = buf->fappend("TotalRecvServerBytes:%ld\n", st.recvServerBytes); + buf = buf->fappend("TotalSendClientBytes:%ld\n", st.sendClientBytes); + buf = buf->fappend("\n"); + + buf = buf->fappend("# Servers\n"); + int servCursor = 0; + auto sp = mProxy->serverPool(); + while (Server* serv = sp->iter(servCursor)) { + ServerStats st; + for (auto h : mProxy->handlers()) { + if (auto cp = h->getConnectConnectionPool(serv->id())) { + st += cp->stats(); + } + } + buf->fappend("Server:%s\n", serv->addr().data()); + buf->fappend("Role:%s\n", serv->roleStr()); + auto g = serv->group(); + buf->fappend("Group:%s\n", g ? g->name().data() : ""); + buf->fappend("DC:%s\n", serv->dcName().data()); + buf->fappend("CurrentIsFail:%d\n", (int)serv->fail()); + buf->fappend("Connections:%d\n", st.connections); + buf->fappend("Connect:%ld\n", st.connect); + buf->fappend("Requests:%ld\n", st.requests); + buf->fappend("Responses:%ld\n", st.responses); + buf->fappend("SendBytes:%ld\n", st.sendBytes); + buf->fappend("RecvBytes:%ld\n", st.recvBytes); + buf = buf->fappend("\n"); + } + buf = buf->fappend("\n"); + + buf = buf->fappend("# LatencyMonitor\n"); + LatencyMonitor lm; + for (size_t i = 0; i < mLatencyMonitors.size(); ++i) { + lm = mLatencyMonitors[i]; + for (auto h : mProxy->handlers()) { + if (h == this) { + continue; + } + lm += h->mLatencyMonitors[i]; + } + buf = buf->fappend("LatencyMonitorName:%s\n", lm.name().data()); + buf = lm.output(buf); + buf = buf->fappend("\n"); + } + + buf = buf->fappend("\r\n"); + body.end().buf = buf; + body.end().pos = buf->length(); + body.rewind(); + res->head().fset(nullptr, "$%d\r\n", body.length() - 2); + handleResponse(nullptr, req, res); +} + +void Handler::infoLatencyRequest(Request* req) +{ + SegmentStr<128> d(req->body()); + if (!d.hasPrefix("*3\r\n")) { + directResponse(req, Response::ArgWrong); + return; + } + const char* p = d.data() + sizeof("*3\r\n$4\r\ninfo\r\n$7\r\nlatency\r\n"); + int len = atoi(p); + p = strchr(p, '\r') + 2; + String key(p, len); + ResponsePtr res = ResponseAlloc::create(); + Segment& body = res->body(); + int i = mProxy->latencyMonitorSet().find(key); + if (i < 0) { + res->setType(Reply::Error); + body.fset(nullptr, "-ERR latency \"%.*s\" no exists\r\n", key.length(), key.data()); + handleResponse(nullptr, req, res); + return; + } + + BufferPtr buf = body.fset(nullptr, "# LatencyMonitor\n"); + LatencyMonitor lm = mLatencyMonitors[i]; + for (auto h : mProxy->handlers()) { + if (h == this) { + continue; + } + lm += h->mLatencyMonitors[i]; + } + buf = buf->fappend("LatencyMonitorName:%s\n", lm.name().data()); + buf = lm.output(buf); + buf = buf->fappend("\n"); + + buf = buf->fappend("# ServerLatencyMonitor\n"); + auto sp = mProxy->serverPool(); + int servCursor = 0; + while (Server* serv = sp->iter(servCursor)) { + lm = mLatencyMonitors[i]; + lm.reset(); + for (auto h : mProxy->handlers()) { + if (auto cp = h->getConnectConnectionPool(serv->id())) { + lm += cp->latencyMonitors()[i]; + } + } + buf = buf->fappend("ServerLatencyMonitorName:%s %s\n", + serv->addr().data(), lm.name().data()); + buf = lm.output(buf); + buf = buf->fappend("\n"); + } + + buf = buf->fappend("\r\n"); + body.end().buf = buf; + body.end().pos = buf->length(); + body.rewind(); + res->head().fset(nullptr, "$%d\r\n", body.length() - 2); + res->setType(Reply::String); + handleResponse(nullptr, req, res); +} + +void Handler::infoServerLatencyRequest(Request* req) +{ + SegmentStr<256> d(req->body()); + int argc = atoi(d.data() + 1); + if (argc != 3 && argc != 4) { + directResponse(req, Response::ArgWrong); + return; + } + const char* p = d.data() + sizeof("*3\r\n$4\r\ninfo\r\n$13\r\nserverlatency\r\n"); + int len = atoi(p); + p = strchr(p, '\r') + 2; + String addr(p, len); + ResponsePtr res = ResponseAlloc::create(); + Segment& body = res->body(); + auto sp = mProxy->serverPool(); + Server* serv = sp->getServer(addr); + if (!serv) { + res->setType(Reply::Error); + body.fset(nullptr, "-ERR server \"%.*s\" no exists\r\n", + addr.length(), addr.data()); + handleResponse(nullptr, req, res); + return; + } + BufferPtr buf = body.fset(nullptr, "# ServerLatencyMonitor\n"); + if (argc == 4) { + p += len + 3; + len = atoi(p); + p = strchr(p, '\r') + 2; + String key(p, len); + int i = mProxy->latencyMonitorSet().find(key); + if (i < 0) { + res->setType(Reply::Error); + body.fset(nullptr, "-ERR latency \"%.*s\" no exists\r\n", + key.length(), key.data()); + handleResponse(nullptr, req, res); + return; + } + LatencyMonitor lm = mLatencyMonitors[i]; + lm.reset(); + for (auto h : mProxy->handlers()) { + if (auto cp = h->getConnectConnectionPool(serv->id())) { + lm += cp->latencyMonitors()[i]; + } + } + buf = buf->fappend("ServerLatencyMonitorName:%s %s\n", + serv->addr().data(), lm.name().data()); + buf = lm.output(buf); + buf = buf->fappend("\n"); + } else { + for (size_t i = 0; i < mLatencyMonitors.size(); ++i) { + LatencyMonitor lm = mLatencyMonitors[i]; + lm.reset(); + for (auto h : mProxy->handlers()) { + if (auto cp = h->getConnectConnectionPool(serv->id())) { + lm += cp->latencyMonitors()[i]; + } + } + buf = buf->fappend("ServerLatencyMonitorName:%s %s\n", + serv->addr().data(), lm.name().data()); + buf = lm.output(buf); + buf = buf->fappend("\n"); + } + } + + buf = buf->fappend("\r\n"); + body.end().buf = buf; + body.end().pos = buf->length(); + body.rewind(); + res->head().fset(nullptr, "$%d\r\n", body.length() - 2); + res->setType(Reply::String); + handleResponse(nullptr, req, res); + +} + +void Handler::resetStats() +{ + mStats.reset(); + for (auto& m : mLatencyMonitors) { + m.reset(); + } + for (auto cp : mConnPool) { + if (cp) { + cp->resetStats(); + } + } + mStatsVer = mProxy->statsVer(); +} + +void Handler::configRequest(Request* req, const String& key) +{ + if (key.equal("get", true)) { + configGetRequest(req); + } else if (key.equal("set", true)) { + configSetRequest(req); + } else { + directResponse(req, Response::ConfigSubCmdUnknown); + } +} + +void Handler::configGetRequest(Request* req) +{ + SegmentStr<128> d(req->body()); + if (!d.hasPrefix("*3\r\n")) { + directResponse(req, Response::ArgWrong); + return; + } + const char* p = d.data() + sizeof("*3\r\n$6\r\nconfig\r\n$3\r\nget\r\n"); + int len = atoi(p); + p = strchr(p, '\r'); + String key(p + 2, len); + bool all = key.equal("*"); + ResponsePtr res = ResponseAlloc::create(); + res->setType(Reply::Array); + int num = 0; + Segment& body = res->body(); + BufferPtr buf = BufferAlloc::create(); + body.begin().buf = buf; + body.begin().pos = buf->length(); + SString<512> s; + auto conf = mProxy->conf(); + auto log = Logger::gInst; + +#define Append(name, fmt, ...) \ + if (all || key.equal(name, true)) { \ + buf = buf->fappend("$%d\r\n%s\r\n", sizeof(name) - 1, name);\ + s.printf(fmt, __VA_ARGS__); \ + buf = buf->fappend("$%d\r\n%s\r\n", s.length(), s.data()); \ + num += 2; \ + if (!all) break; \ + } + + do { + Append("MaxMemory", "%ld", AllocBase::getMaxMemory()); + Append("ClientTimeout", "%d", conf->clientTimeout() / 1000000); + Append("AllowMissLog", "%s", log->allowMissLog() ? "true" : "false"); + Append("LogVerbSample", "%d", log->logSample(LogLevel::Verb)); + Append("LogDebugSample", "%d", log->logSample(LogLevel::Debug)); + Append("LogInfoSample", "%d", log->logSample(LogLevel::Info)); + Append("LogNoticeSample", "%d", log->logSample(LogLevel::Notice)); + Append("LogWarnSample", "%d", log->logSample(LogLevel::Warn)); + Append("LogErrorSample", "%d", log->logSample(LogLevel::Error)); + } while (0); + body.end().buf = buf; + body.end().pos = buf->length(); + body.rewind(); + res->head().fset(nullptr, "*%d\r\n", num); + handleResponse(nullptr, req, res); +} + +void Handler::configSetRequest(Request* req) +{ + SegmentStr<128> d(req->body()); + int argc = atoi(d.data() + 1); + if (argc < 4) { + directResponse(req, Response::ArgWrong); + return; + } + auto conf = mProxy->conf(); + auto log = Logger::gInst; + char* p = strchr((char*)d.data(), '\r'); + p += sizeof("\r\n$6\r\nconfig\r\n$3\r\nset\r\n"); + int len = atoi(p); + p = strchr(p, '\r') + 2; + String key(p, len); + p += len + 3; + len = atoi(p); + p = strchr(p, '\r') + 2; + p[len] = '\0'; + String val(p, len); + if (key.equal("MaxMemory", true)) { + long m; + if (Conf::parseMemory(m, val.data())) { + AllocBase::setMaxMemory(m); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("ClientTimeout", true)) { + int v; + if (sscanf(val.data(), "%d", &v) == 1 && v >= 0) { + conf->setClientTimeout(v * 1000000L); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("LogVerbSample", true)) { + int v; + if (sscanf(val.data(), "%d", &v) == 1 && v >= 0) { + log->setLogSample(LogLevel::Verb, v); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("LogDebugSample", true)) { + int v; + if (sscanf(val.data(), "%d", &v) == 1 && v >= 0) { + log->setLogSample(LogLevel::Debug, v); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("LogInfoSample", true)) { + int v; + if (sscanf(val.data(), "%d", &v) == 1 && v >= 0) { + log->setLogSample(LogLevel::Info, v); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("LogNoticeSample", true)) { + int v; + if (sscanf(val.data(), "%d", &v) == 1 && v >= 0) { + log->setLogSample(LogLevel::Notice, v); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("LogWarnSample", true)) { + int v; + if (sscanf(val.data(), "%d", &v) == 1 && v >= 0) { + log->setLogSample(LogLevel::Warn, v); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("LogErrorSample", true)) { + int v; + if (sscanf(val.data(), "%d", &v) == 1 && v >= 0) { + log->setLogSample(LogLevel::Error, v); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else if (key.equal("AllowMissLog", true)) { + if (val.equal("true")) { + log->setAllowMissLog(true); + directResponse(req, Response::Ok); + } else if (val.equal("false")) { + log->setAllowMissLog(false); + directResponse(req, Response::Ok); + } else { + directResponse(req, Response::ArgWrong); + } + } else { + directResponse(req, Response::ArgWrong); + } +} + +void Handler::innerResponse(ConnectConnection* s, Request* req, Response* res) +{ + logInfo("h %d s %s %d inner req %ld %s res %ld %s", + id(), (s ? s->peer() : "None"), (s ? s->fd() : -1), + req->id(), req->cmd(), + res->id(), res->typeStr()); + switch (req->type()) { + case Command::PingServ: + if (s && res->isPong()) { + Server* serv = s->server(); + if (serv->fail()) { + serv->setFail(false); + logNotice("h %d s %s %d mark server alive", + id(), s->peer(), s->fd()); + } + } + break; + case Command::AuthServ: + if (!res->isOk()) { + s->setStatus(ConnectConnection::LogicError); + addPostEvent(s, Multiplexor::ErrorEvent); + logWarn("h %d s %s %d auth fail", + id(), s->peer(), s->fd()); + } + break; + case Command::Readonly: + if (!res->isOk()) { + s->setStatus(ConnectConnection::LogicError); + addPostEvent(s, Multiplexor::ErrorEvent); + logWarn("h %d s %s %d readonly fail", + id(), s->peer(), s->fd()); + } + break; + case Command::SelectServ: + if (!res->isOk()) { + s->setStatus(ConnectConnection::LogicError); + addPostEvent(s, Multiplexor::ErrorEvent); + logWarn("h %d s %s %d db select %d fail", + id(), s->peer(), s->fd(), s->db()); + } + break; + case Command::ClusterNodes: + case Command::SentinelSentinels: + case Command::SentinelGetMaster: + case Command::SentinelSlaves: + mProxy->serverPool()->handleResponse(this, s, req, res); + break; + case Command::UnwatchServ: + if (!res->isOk()) { + s->setStatus(ConnectConnection::LogicError); + addPostEvent(s, Multiplexor::ErrorEvent); + logWarn("h %d s %s %d unwatch fail", + id(), s->peer(), s->fd(), s->db()); + } + break; + case Command::DiscardServ: + if (!res->isOk()) { + s->setStatus(ConnectConnection::LogicError); + addPostEvent(s, Multiplexor::ErrorEvent); + logWarn("h %d s %s %d discard fail", + id(), s->peer(), s->fd(), s->db()); + } + break; + default: + break; + } +} + +bool Handler::redirect(ConnectConnection* c, Request* req, Response* res, bool moveOrAsk) +{ + FuncCallTimer(); + if (req->incrRedirectCnt() > Request::MaxRedirectLimit) { + return false; + } + int slot; + SString addr; + if (moveOrAsk) { + if (!res->getMoved(slot, addr)) { + return false; + } + } else { + if (!res->getAsk(addr)) { + return false; + } + } + auto p = static_cast(mProxy->serverPool()); + Server* serv = p->redirect(addr, c->server()); + if (!serv) { + logDebug("h %d req %ld %s redirect to %s can't get server", + id(), req->id(), (moveOrAsk ? "MOVE" : "ASK"), addr.data()); + return false; + } + req->rewind(); + auto s = getConnectConnection(req, serv); + if (!s) { + return false; + } + logDebug("h %d %s redirect req %ld from %s %d to %s", + id(), (moveOrAsk ? "MOVE" : "ASK"), + req->id(), c->peer(), c->fd(), addr.data()); + if (!moveOrAsk) { + RequestPtr asking = RequestAlloc::create(Request::Asking); + handleRequest(asking, s); + } + handleRequest(req, s); + return true; +} + +bool Handler::permission(Request* req, const String& key, Response::GenericCode& code) +{ + FuncCallTimer(); + AcceptConnection* c = req->connection(); + if (!c) { + return true; + } + if (req->type() == Command::Auth) { + auto m = mProxy->authority(); + if (!m->hasAuth()) { + code = Response::NoPasswordSet; + } else if (auto auth = m->get(key)) { + c->setAuth(auth); + code = Response::Ok; + } else { + logNotice("h %d c %s %d auth '%.*s' invalid", + id(), c->peer(), c->fd(), key.length(), key.data()); + c->setAuth(m->getDefault()); + code = Response::InvalidPassword; + } + return false; + } + auto a = c->auth(); + if (!a) { + code = Response::Unauth; + return false; + } + if (!a->permission(req, key)) { + code = Response::PermissionDeny; + return false; + } + return true; +} diff --git a/src/Handler.h b/src/Handler.h new file mode 100644 index 0000000..4fe13af --- /dev/null +++ b/src/Handler.h @@ -0,0 +1,132 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_HANDLER_H_ +#define _PREDIXY_HANDLER_H_ + +#include +#include "Predixy.h" +#include "Multiplexor.h" +#include "Stats.h" +#include "LatencyMonitor.h" +#include "AcceptConnection.h" +#include "ConnectConnectionPool.h" +#include "Proxy.h" + +class Handler : public ID +{ +public: + DefException(AddListenerEventFail); +public: + Handler(Proxy* p); + ~Handler(); + void run(); + void stop(); + void handleEvent(Socket* s, int evts); + void handleRequest(Request* req); + void handleRequest(Request* req, ConnectConnection* c); + void handleResponse(ConnectConnection* c, Request* req, Response* res); + void handleResponse(ConnectConnection* c, Request* req, Response::GenericCode code); + void directResponse(Request* req, Response::GenericCode code, ConnectConnection* s=nullptr); + Proxy* proxy() const + { + return mProxy; + } + Multiplexor* eventLoop() const + { + return mEventLoop; + } + HandlerStats& stats() + { + return mStats; + } + const HandlerStats& stats() const + { + return mStats; + } + const std::vector& latencyMonitors() const + { + return mLatencyMonitors; + } + ConnectConnectionPool* getConnectConnectionPool(int id) const + { + return id < (int)mConnPool.size() ? mConnPool[id] : nullptr; + } + int getPendRequests(Server* serv) const + { + if (auto cp = getConnectConnectionPool(serv->id())) { + return cp->pendRequests(); + } + return 0; + } + void addServerReadStats(Server* serv, int num) + { + mStats.recvServerBytes += num; + mConnPool[serv->id()]->stats().recvBytes += num; + } + void addServerWriteStats(Server* serv, int num) + { + mStats.sendServerBytes += num; + mConnPool[serv->id()]->stats().sendBytes += num; + } + IDUnique& idUnique() + { + return mIDUnique; + } + int rand() + { + return rand_r(&mRandSeed); + } +private: + bool preHandleRequest(Request* req, const String& key); + void postHandleRequest(Request* req, ConnectConnection* s); + void addPostEvent(AcceptConnection* c, int evts); + void addPostEvent(ConnectConnection* c, int evts); + void postEvent(); + void handleListenEvent(ListenSocket* s, int evts); + void addAcceptSocket(int c, sockaddr* addr, socklen_t len); + void handleAcceptConnectionEvent(AcceptConnection* c, int evts); + void handleConnectConnectionEvent(ConnectConnection* c, int evts); + void postAcceptConnectionEvent(); + void postConnectConnectionEvent(); + ConnectConnection* getConnectConnection(Request* req, Server* s); + void refreshServerPool(); + void checkConnectionPool(); + int checkClientTimeout(long timeout); + void innerResponse(ConnectConnection* c, Request* req, Response* res); + void infoRequest(Request* req, const String& key); + void infoLatencyRequest(Request* req); + void infoServerLatencyRequest(Request* req); + void configRequest(Request* req, const String& key); + void configGetRequest(Request* req); + void configSetRequest(Request* req); + bool redirect(ConnectConnection* c, Request* req, Response* res, bool moveOrAsk); + bool permission(Request* req, const String& key, Response::GenericCode& code); + void resetStats(); + void setAcceptConnectionActiveTime(AcceptConnection* c) + { + c->setLastActiveTime(Util::elapsedUSec()); + mAcceptConns.remove(c); + mAcceptConns.push_back(c); + } +private: + bool mStop; + Proxy* mProxy; + Multiplexor* mEventLoop; + std::vector mConnPool; + AcceptConnectionDeque mAcceptConns; + AcceptConnectionList mPostAcceptConns; + ConnectConnectionList mPostConnectConns; + ConnectConnectionDeque mWaitConnectConns; + long mStatsVer; + HandlerStats mStats; + std::vector mLatencyMonitors; + IDUnique mIDUnique; + unsigned int mRandSeed; +}; + + +#endif diff --git a/src/HashFunc.cpp b/src/HashFunc.cpp new file mode 100644 index 0000000..e38ef73 --- /dev/null +++ b/src/HashFunc.cpp @@ -0,0 +1,83 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include "HashFunc.h" + + +Hash Hash::parse(const char* str) +{ + if (strcasecmp(str, "atol") == 0) { + return Atol; + } else if (strcasecmp(str, "crc16") == 0) { + return Crc16; + } + return None; +} + +const char* Hash::hashTagStr(const char* buf, int& len, const char* tag) +{ + if (tag && tag[0] && tag[1]) { + int i = 0; + while (i < len && buf[i] != tag[0]) { + ++i; + } + if (i == len) { + return buf; + } + const char* b = buf + ++i; + while (i < len && buf[i] != tag[1]) { + ++i; + } + if (i == len) { + return buf; + } + const char* e = buf + i; + if (b < e) { + len = e - b; + return b; + } + } + return buf; +} + +long Hash::hash(const char* buf, int len) const +{ + switch (mType) { + case Atol: + return atol(buf, len); + case Crc16: + return crc16(buf, len); + default: + break; + } + return 0; +} + +long Hash::atol(const char* buf, int len) +{ + long v = 0; + if (buf) { + int i = 0; + if (buf[i] == '+') { + ++i; + } else if (buf[i] == '-') { + ++i; + } + for ( ; i < len; ++i) { + if (buf[i] >= '0' && buf[i] <= '9') { + v = v * 10 + buf[i] - '0'; + } else { + break; + } + } + if (buf[0] == '-') { + v = -v; + } + } + return v; +} diff --git a/src/HashFunc.h b/src/HashFunc.h new file mode 100644 index 0000000..97eed37 --- /dev/null +++ b/src/HashFunc.h @@ -0,0 +1,47 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_HASH_FUNC_H_ +#define _PREDIXY_HASH_FUNC_H_ + +#include +#include + +class Hash +{ +public: + enum Type + { + None, + Atol, + Crc16 + }; + static Hash parse(const char* str); + static const char* hashTagStr(const char* buf, int& len, const char* tag); + static uint16_t crc16(const char* buf, int len); + static uint16_t crc16(uint16_t crc, char k); + static long atol(const char* buf, int len); +public: + Hash(Type t = None): + mType(t) + { + } + operator Type() const + { + return mType; + } + long hash(const char* buf, int len) const; + long hash(const char* buf, int len, const char* tag) const + { + buf = hashTagStr(buf, len, tag); + return hash(buf, len); + } +private: + Type mType; +}; + + +#endif diff --git a/src/ID.h b/src/ID.h new file mode 100644 index 0000000..fc3d40a --- /dev/null +++ b/src/ID.h @@ -0,0 +1,94 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_ID_H_ +#define _PREDIXY_ID_H_ + +#include "Sync.h" +#include + +template +class TID +{ +public: + TID(): + mId(++Id) + { + } + long id() const + { + return mId; + } +private: + long mId; + thread_local static long Id; +}; + +template +thread_local long TID::Id(0); + +template +class ID +{ +public: + ID(): + mId(++Id - 1) + { + } + int id() const + { + return mId; + } + static int maxId() + { + return Id; + } +protected: + ~ID() + { + } +private: + int mId; + static AtomicInt Id; +}; + +template +AtomicInt ID::Id(0); + +class IDUnique +{ +public: + IDUnique(int sz = 0): + mMarker(sz, false) + { + } + void resize(int sz) + { + if (sz > (int)mMarker.size()) { + mMarker.resize(sz, false); + } + } + template + int unique(C d, int num) + { + int n = 0; + for (int i = 0; i < num; ++i) { + auto p = d[i]; + if (!mMarker[p->id()]) { + mMarker[p->id()] = true; + d[n++] = p; + } + } + for (int i = 0; i < n; ++i) { + mMarker[d[i]->id()] = false; + } + return n; + } +private: + std::vector mMarker; +}; + +#endif diff --git a/src/IOVec.h b/src/IOVec.h new file mode 100644 index 0000000..84a4494 --- /dev/null +++ b/src/IOVec.h @@ -0,0 +1,30 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_IOVEC_H_ +#define _PREDIXY_IOVEC_H_ + + +#include + +class Request; +class Response; +class Segment; + +struct IOVec +{ + char* dat; + int len; + int pos; + Buffer* buf; + Segment* seg; + Request* req; +}; + + + + +#endif diff --git a/src/KqueueMultiplexor.cpp b/src/KqueueMultiplexor.cpp new file mode 100644 index 0000000..0fed116 --- /dev/null +++ b/src/KqueueMultiplexor.cpp @@ -0,0 +1,86 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "KqueueMultiplexor.h" +#include "Socket.h" + +KqueueMultiplexor::KqueueMultiplexor(): + mFd(-1) +{ + int fd = kqueue(); + if (fd < 0) { + Throw(KqueueCreateFail, "kqueue call fail %s", StrError()); + } + mFd = fd; +} + +KqueueMultiplexor::~KqueueMultiplexor() +{ + if (mFd >= 0) { + ::close(mFd); + } +} + +bool KqueueMultiplexor::addSocket(Socket* s, int evts) +{ + return addEvent(s, evts); +} + +void KqueueMultiplexor::delSocket(Socket* s) +{ + struct kevent event; + EV_SET(&event, s->fd(), EVFILT_READ, EV_DELETE, 0, 0, s); + kevent(mFd, &event, 1, NULL, 0, NULL); + EV_SET(&event, s->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, s); + kevent(mFd, &event, 1, NULL, 0, NULL); +} + +bool KqueueMultiplexor::addEvent(Socket* s, int evts) +{ + if ((evts & ReadEvent) && !(s->getEvent() & ReadEvent)) { + struct kevent event; + EV_SET(&event, s->fd(), EVFILT_READ, EV_ADD, 0, 0, s); + int ret = kevent(mFd, &event, 1, NULL, 0, NULL); + if (ret == -1) { + return false; + } + s->setEvent(s->getEvent() | ReadEvent); + } + if ((evts & WriteEvent) && !(s->getEvent() & WriteEvent)) { + struct kevent event; + EV_SET(&event, s->fd(), EVFILT_WRITE, EV_ADD, 0, 0, s); + int ret = kevent(mFd, &event, 1, NULL, 0, NULL); + if (ret == -1) { + return false; + } + s->setEvent(s->getEvent() | WriteEvent); + } + return true; + +} + +bool KqueueMultiplexor::delEvent(Socket* s, int evts) +{ + if ((evts & ReadEvent) && (s->getEvent() & ReadEvent)) { + struct kevent event; + EV_SET(&event, s->fd(), EVFILT_READ, EV_DELETE, 0, 0, s); + int ret = kevent(mFd, &event, 1, NULL, 0, NULL); + if (ret == -1) { + return false; + } + s->setEvent(s->getEvent() & ~ReadEvent); + } + if ((evts & WriteEvent) && (s->getEvent() & WriteEvent)) { + struct kevent event; + EV_SET(&event, s->fd(), EVFILT_WRITE, EV_DELETE, 0, 0, s); + int ret = kevent(mFd, &event, 1, NULL, 0, NULL); + if (ret == -1) { + return false; + } + s->setEvent(s->getEvent() & ~WriteEvent); + } + return true; +} diff --git a/src/KqueueMultiplexor.h b/src/KqueueMultiplexor.h new file mode 100644 index 0000000..a9c804e --- /dev/null +++ b/src/KqueueMultiplexor.h @@ -0,0 +1,72 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_KQUEUE_MULTIPLEXOR_H_ +#define _PREDIXY_KQUEUE_MULTIPLEXOR_H_ + +#include +#include +#include +#include +#include "Multiplexor.h" +#include "Util.h" + +class KqueueMultiplexor : public MultiplexorBase +{ +public: + DefException(KqueueCreateFail); + DefException(KqueueWaitFail); + DefException(AddSocketFail); +public: + KqueueMultiplexor(); + ~KqueueMultiplexor(); + bool addSocket(Socket* s, int evts = ReadEvent); + void delSocket(Socket* s); + bool addEvent(Socket* s, int evts); + bool delEvent(Socket* s, int evts); + template + int wait(long usec, T* handler); +private: + int mFd; + static const int MaxEvents = 1024; + struct kevent mEvents[MaxEvents]; +}; + + +template +int KqueueMultiplexor::wait(long usec, T* handler) +{ + struct timespec timeout; + timeout.tv_sec = usec / 1000000; + timeout.tv_nsec = (usec % 1000000) * 1000; + int num = kevent(mFd, nullptr, 0, mEvents, MaxEvents, usec < 0 ? nullptr : &timeout); + if (num == -1) { + if (errno == EINTR) { + return 0; + } + Throw(KqueueWaitFail, "h %d kqueue wait fail %s", + handler->id(), StrError()); + } + for (int i = 0; i < num; ++i) { + Socket* s = static_cast(mEvents[i].udata); + int evts = 0; + if (mEvents[i].flags & EV_ERROR) { + evts = ErrorEvent; + } else if (mEvents[i].filter == EVFILT_READ) { + evts = ReadEvent; + } else if (mEvents[i].filter == EVFILT_WRITE) { + evts = WriteEvent; + } + handler->handleEvent(s, evts); + } + return num; +} + + +typedef KqueueMultiplexor Multiplexor; +#define _MULTIPLEXOR_ASYNC_ASSIGN_ + +#endif diff --git a/src/LatencyMonitor.cpp b/src/LatencyMonitor.cpp new file mode 100644 index 0000000..4088b9b --- /dev/null +++ b/src/LatencyMonitor.cpp @@ -0,0 +1,61 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "LatencyMonitor.h" + +Buffer* LatencyMonitor::output(Buffer* buf) const +{ + long tc = mLast.count; + for (auto& s : mTimeSpan) { + tc += s.count; + } + if (tc == 0) { + return buf; + } + long te = mLast.total; + long t = 0; + float p = 0; + for (auto& s : mTimeSpan) { + if (s.count == 0) { + continue; + } + te += s.total; + t += s.count; + p = t * 100. / tc; + buf = buf->fappend("<= %12ld %20ld %16ld %.2f%%\n", s.span, s.total, s.count, p); + } + if (mLast.count > 0) { + buf = buf->fappend("> %12ld %20ld %16ld 100.00%\n", mLast.span, mLast.total, mLast.count); + } + buf = buf->fappend("T %12ld %20ld %16ld\n", te / tc, te, tc); + return buf; +} + + +void LatencyMonitorSet::init(const std::vector& conf) +{ + mPool.resize(conf.size()); + int i = 0; + for (auto& c : conf) { + if (mNameIdx.find(c.name) != mNameIdx.end()) { + Throw(DuplicateDef, "LatencyMonitor \"%s\" duplicate", + c.name.c_str()); + } + mPool[i].init(c); + mNameIdx[mPool[i].name()] = i; + ++i; + } + mCmdIdx.resize(Command::Sentinel); + int cursor = 0; + while (auto cmd = Command::iter(cursor)) { + int size = conf.size(); + for (int i = 0; i < size; ++i) { + if (conf[i].cmds[cmd->type]) { + mCmdIdx[cmd->type].push_back(i); + } + } + } +} diff --git a/src/LatencyMonitor.h b/src/LatencyMonitor.h new file mode 100644 index 0000000..866a400 --- /dev/null +++ b/src/LatencyMonitor.h @@ -0,0 +1,135 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_LATENCY_MONITOR_H_ +#define _PREDIXY_LATENCY_MONITOR_H_ + +#include +#include +#include +#include "String.h" +#include "Buffer.h" +#include "Conf.h" + +class LatencyMonitor +{ +public: + struct TimeSpan + { + long span; + long total; + long count; + + bool operator<(const TimeSpan& oth) const + { + return span < oth.span; + } + TimeSpan& operator+=(const TimeSpan& s) + { + total += s.total; + count += s.count; + return *this; + } + }; +public: + LatencyMonitor(): + mCmds(nullptr), + mLast{0, 0, 0} + { + } + LatencyMonitor& operator+=(const LatencyMonitor& m) + { + for (size_t i = 0; i < mTimeSpan.size(); ++i) { + mTimeSpan[i] += m.mTimeSpan[i]; + } + mLast += m.mLast; + return *this; + } + void init(const LatencyMonitorConf& c) + { + mName = c.name; + mCmds = &c.cmds; + mTimeSpan.resize(c.timeSpan.size()); + for (size_t i = 0; i < mTimeSpan.size(); ++i) { + mTimeSpan[i].span = c.timeSpan[i]; + mTimeSpan[i].total = 0; + mTimeSpan[i].count = 0; + } + if (!mTimeSpan.empty()) { + mLast.span = mTimeSpan.back().span; + } + } + void reset() + { + for (auto& s : mTimeSpan) { + s.total = 0; + s.count = 0; + } + mLast.total = 0; + mLast.count = 0; + } + const String& name() const + { + return mName; + } + int add(long v) + { + TimeSpan span{v}; + auto it = std::lower_bound(mTimeSpan.begin(), mTimeSpan.end(), span); + if (it == mTimeSpan.end()) { + mLast.total += v; + ++mLast.count; + return mTimeSpan.size(); + } else { + it->total += v; + ++it->count; + return it - mTimeSpan.begin(); + } + } + void add(long v, int idx) + { + TimeSpan& s(idx < (int)mTimeSpan.size() ? mTimeSpan[idx] : mLast); + s.total += v; + ++s.count; + } + Buffer* output(Buffer* buf) const; +private: + String mName; + const std::bitset* mCmds; + std::vector mTimeSpan; + TimeSpan mLast; +}; + +class LatencyMonitorSet +{ +public: + DefException(DuplicateDef); +public: + LatencyMonitorSet() + { + } + void init(const std::vector& conf); + const std::vector& latencyMonitors() const + { + return mPool; + } + int find(const String& name) const + { + auto it = mNameIdx.find(name); + return it == mNameIdx.end() ? -1 : it->second; + } + const std::vector& cmdIndex(Command::Type type) const + { + return mCmdIdx[type]; + } +private: + std::vector mPool; + std::map mNameIdx; + std::vector> mCmdIdx; +}; + + +#endif diff --git a/src/List.h b/src/List.h new file mode 100644 index 0000000..bb6b555 --- /dev/null +++ b/src/List.h @@ -0,0 +1,129 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_LIST_H_ +#define _PREDIXY_LIST_H_ + +template +class ListNode +{ +public: + typedef P Ptr; + ListNode() + { + for (int i = 0; i < Size; ++i) { + mNext[i] = nullptr; + } + } + ~ListNode() + { + for (int i = 0; i < Size; ++i) { + mNext[i] = nullptr; + } + } + void reset(int idx = 0) + { + mNext[idx] = nullptr; + } + void concat(T* obj, int idx = 0) + { + mNext[idx] = obj; + } + P next(int idx = 0) const + { + return mNext[idx]; + } +private: + P mNext[Size]; +}; + +template +class List +{ +public: + typedef typename N::Value T; + typedef typename N::ListNodeType Node; + typedef typename Node::Ptr P; +public: + List(): + mSize(0), + mHead(nullptr), + mTail(nullptr) + { + } + ~List() + { + while (mSize > 0) { + pop_front(); + } + } + P next(T* obj) + { + return node(obj)->next(Idx); + } + void push_back(T* obj) + { + N* p = static_cast(obj); + if (mTail) { + static_cast((T*)mTail)->concat(p, Idx); + mTail = p; + } else { + mHead = mTail = p; + } + ++mSize; + } + void push_front(T* obj) + { + N* p = static_cast(obj); + if (mHead) { + node(obj)->concat(mHead, Idx); + mHead = p; + } else { + mHead = mTail = p; + } + ++mSize; + } + P pop_front() + { + P obj = mHead; + if (obj) { + Node* n = node((T*)obj); + mHead = n->next(Idx); + if (--mSize == 0) { + mTail = nullptr; + } + n->reset(Idx); + } + return obj; + } + P front() const + { + return mHead; + } + P back() const + { + return mTail; + } + int size() const + { + return mSize; + } + bool empty() const + { + return mSize == 0; + } +private: + static Node* node(T* obj) + { + return static_cast(obj); + } +private: + int mSize; + P mHead; + P mTail; +}; + +#endif diff --git a/src/ListenSocket.cpp b/src/ListenSocket.cpp new file mode 100644 index 0000000..ecc5d8f --- /dev/null +++ b/src/ListenSocket.cpp @@ -0,0 +1,57 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include "Util.h" +#include "ListenSocket.h" + +ListenSocket::ListenSocket(const char* addr, int type, int protocol) +{ + mClassType = ListenType; + strncpy(mAddr, addr, sizeof(mAddr)); + sockaddr_storage saddr; + socklen_t len = sizeof(saddr); + getFirstAddr(addr, type, protocol, (sockaddr*)&saddr, &len); + sockaddr* in = (sockaddr*)&saddr; + int fd = Socket::socket(in->sa_family, type, protocol); + int flags = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)); + int ret = ::bind(fd, in, len); + if (ret != 0) { + int err = errno; + ::close(fd); + errno = err; + Throw(BindFail, "socket bind to %s fail %s", addr, StrError()); + } + attach(fd); +} + +void ListenSocket::listen(int backlog) +{ + int ret = ::listen(fd(), backlog); + if (ret != 0) { + Throw(ListenFail, "socket listen fail %s", StrError()); + } +} + +int ListenSocket::accept(sockaddr* addr, socklen_t* len) +{ + while (true) { + int c = ::accept(fd(), addr, len); + if (c < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return -1; + } else if (errno == EINTR || errno == ECONNABORTED) { + continue; + } else if (errno == EMFILE || errno == ENFILE) { + Throw(TooManyOpenFiles, "socket accept fail %s", StrError()); + } + Throw(AcceptFail, "socket accept fail %s", StrError()); + } + return c; + } + return -1; +} diff --git a/src/ListenSocket.h b/src/ListenSocket.h new file mode 100644 index 0000000..ddc4f27 --- /dev/null +++ b/src/ListenSocket.h @@ -0,0 +1,31 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_LISTEN_SOCKET_H_ +#define _PREDIXY_LISTEN_SOCKET_H_ + +#include "Socket.h" + +class ListenSocket : public Socket +{ +public: + DefException(BindFail); + DefException(ListenFail); + DefException(TooManyOpenFiles); + DefException(AcceptFail); +public: + ListenSocket(const char* addr, int type, int protocol = 0); + void listen(int backlog = 511); + int accept(sockaddr* addr, socklen_t* len); + const char* addr() const + { + return mAddr; + } +private: + char mAddr[128]; +}; + +#endif diff --git a/src/LogFileSink.cpp b/src/LogFileSink.cpp new file mode 100644 index 0000000..00e25a2 --- /dev/null +++ b/src/LogFileSink.cpp @@ -0,0 +1,191 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include "LogFileSink.h" +#include "Logger.h" + +static const int DaySecs = 86400; +static const int FileSuffixReserveLen = 20; + +LogFileSink::LogFileSink(): + mFilePathLen(0), + mFileSuffixFmt(nullptr), + mFile(nullptr), + mRotateSecs(0), + mRotateBytes(0), + mLastReopenTime(0), + mBytes(0), + mError(0) +{ +} + +LogFileSink::~LogFileSink() +{ + if (mFile && mFile != stdout) { + fclose(mFile); + mFile = nullptr; + } +} + +bool LogFileSink::parseRotate(const char* rotate, int& secs, long& bytes) +{ + secs = 0; + bytes = 0; + if (!rotate || *rotate == '\0') { + return true; + } + std::istringstream iss(rotate); + std::string s; + while (iss >> s) { + int n; + char unit[4]; + int c = sscanf(s.c_str(), "%d%2s", &n, unit); + if (c != 2 || n <= 0) { + return false; + } + if (strcmp(unit, "d") == 0) { + if (n != 1) { + return false; + } + secs = DaySecs; + } else if (strcmp(unit, "h") == 0) { + if (n > 24) { + return false; + } + secs = n * 3600; + } else if (strcmp(unit, "m") == 0) { + if (n > 1440) { + return false; + } + secs = n * 60; + } else if (strcmp(unit, "G") == 0) { + bytes = n * (1 << 30); + } else if (strcmp(unit, "M") == 0) { + bytes = n * (1 << 20); + } else { + return false; + } + } + return true; +} + +time_t LogFileSink::roundTime(time_t t) +{ + if (mRotateSecs > 0) { + struct tm m; + localtime_r(&t, &m); + m.tm_sec = 0; + m.tm_min = 0; + m.tm_hour = 0; + time_t start = mktime(&m); + return start + (t - start) / mRotateSecs * mRotateSecs; + } + return t; +} + +bool LogFileSink::reopen(time_t t) +{ + if (mFileSuffixFmt) { + struct tm m; + localtime_r(&t, &m); + char* p = mFilePath + mFilePathLen; + int len = MaxPathLen - mFilePathLen; + int num = strftime(p, len, mFileSuffixFmt, &m); + if (num <= 0) { + return false; + } + } + if (mFilePathLen > 0) { + FILE* f = fopen(mFilePath, "a"); + if (!f) { + return false; + } + if (mFile && mFile != stdout) { + fclose(mFile); + } + mFile = f; + if (mLastReopenTime != t) { + mBytes = ftell(f); + mLastReopenTime = t; + } + if (mFileSuffixFmt) { + unlink(mFileName.c_str()); + if (symlink(mFilePath, mFileName.c_str()) == -1) { + fprintf(stderr, "create symbol link for %s fail", mFileName.c_str()); + } + } + } else { + mFile = stdout; + } + return true; +} + +bool LogFileSink::setFile(const char* path, int rotateSecs, long rotateBytes) +{ + int len = strlen(path); + if (rotateSecs > 0 || rotateBytes > 0) { + if (len > 4 && strcasecmp(path + len - 4, ".log") == 0) { + len -= 4; + } + if (len + FileSuffixReserveLen >= MaxPathLen) { + return false; + } + } + mFileName = path; + mFilePathLen = len; + memcpy(mFilePath, path, len); + mFilePath[len] = '\0'; + mRotateSecs = rotateSecs; + mRotateBytes = rotateBytes; + if (len > 0) { + if (rotateBytes > 0) { + mFileSuffixFmt = ".%Y%m%d%H%M%S.log"; + } else if (rotateSecs >= 86400) { + mFileSuffixFmt = ".%Y%m%d.log"; + } else if (rotateSecs >= 3600) { + mFileSuffixFmt = ".%Y%m%d%H.log"; + } else if (rotateSecs > 0) { + mFileSuffixFmt = ".%Y%m%d%H%M.log"; + } + } + time_t now = time(nullptr); + return reopen(mRotateBytes > 0 ? now : roundTime(now)); +} + +void LogFileSink::checkRotate() +{ + bool rotate = false; + time_t now = time(nullptr); + if (mRotateBytes > 0 && mBytes >= mRotateBytes) { + rotate = reopen(now); + } + if (!rotate && mRotateSecs > 0) { + now = roundTime(now); + if (now > mLastReopenTime && + now - roundTime(mLastReopenTime) >= mRotateSecs) { + rotate = reopen(now); + } + } +} + +void LogFileSink::write(const LogUnit* log) +{ + if (mFile) { + int len = log->length(); + int n = fwrite(log->data(), len, 1, mFile); + if (n != 1) { + ++mError; + } else { + mBytes += len; + } + fflush(mFile); + } +} diff --git a/src/LogFileSink.h b/src/LogFileSink.h new file mode 100644 index 0000000..5336ad6 --- /dev/null +++ b/src/LogFileSink.h @@ -0,0 +1,47 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_LOG_FILE_SINK_H_ +#define _PREDIXY_LOG_FILE_SINK_H_ + +#include +#include +#include + +class LogUnit; + +class LogFileSink +{ +public: + LogFileSink(); + ~LogFileSink(); + bool setFile(const char* path, int rotateSecs = 0, long rotateBytes = 0); + void checkRotate(); + void write(const LogUnit* log); + int fd() const + { + return mFile ? fileno(mFile) : -1; + } + + static bool parseRotate(const char* rotate, int& secs, long& bytes); + static const int MaxPathLen = 1024; +private: + time_t roundTime(time_t t); + bool reopen(time_t t); +private: + std::string mFileName; + char mFilePath[MaxPathLen]; + int mFilePathLen; + const char* mFileSuffixFmt; + FILE* mFile; + int mRotateSecs; + long mRotateBytes; + time_t mLastReopenTime; + long mBytes; + long mError; +}; + +#endif diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..718d5e5 --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,214 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include "LogFileSink.h" +#include "Util.h" +#include "Logger.h" +#include + +const char* LogLevel::Str[Sentinel] = { + "V", + "D", + "I", + "N", + "W", + "E" +}; + +LogUnit::LogUnit(): + mLen(0) +{ +} + +LogUnit::~LogUnit() +{ + mLen = 0; +} + +void LogUnit::format(LogLevel::Type level, const char* file, int line, const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vformat(level, file, line, fmt, ap); + va_end(ap); +} + +void LogUnit::vformat(LogLevel::Type level, const char* file, int line, const char* fmt, va_list ap) +{ + long us = Util::nowUSec(); + time_t t = us / 1000000; + struct tm m; + localtime_r(&t, &m); + char* p = mBuf; + size_t len = MaxLogLen; + int n = strftime(p, len, "%Y-%m-%d %H:%M:%S", &m); + p += n; + mLen = n; + len -= n; + us %= 1000000; + n = snprintf(p, len, ".%06ld %s %s:%d ", us, LogLevel::Str[level], file, line); + p += n; + mLen += n; + len -= n; + n = vsnprintf(p, len, fmt, ap); + mLen += n; + if (mLen >= MaxLogLen) { + mLen = MaxLogLen - 1; + } + mBuf[mLen++] = '\n'; +} + +thread_local long Logger::LogCnt[LogLevel::Sentinel]{0, 0, 0, 0, 0, 0}; +Logger* Logger::gInst = NULL; + +Logger::Logger(int maxLogUnitNum): + mStop(false), + mAllowMissLog(true), + mMissLogs(0), + mLogSample{0, 0, 100, 1, 1, 1}, + mLogUnitCnt(0), + mLogs(maxLogUnitNum), + mFree(maxLogUnitNum), + mFileSink(nullptr) +{ + mLogs.resize(0); + mFree.resize(0); +} + +Logger::~Logger() +{ + if (mFileSink) { + delete mFileSink; + } + if (mThread) { + delete mThread; + } +} + +void Logger::setLogFile(const char* file, int rotateSecs, long rotateBytes) +{ + if (!mFileSink) { + mFileSink = new LogFileSink(); + } + if (!mFileSink->setFile(file, rotateSecs, rotateBytes)) { + Throw(SetLogFileFail, "set log file %s fail %s", file, StrError()); + } +} + +void Logger::start() +{ + mThread = new std::thread([=](){this->run();}); + mThread->detach(); +} + +void Logger::stop() +{ + mStop = true; + std::unique_lock lck(mMtx); + mCond.notify_one(); +} + +void Logger::run() +{ + std::vector logs(mFree.capacity()); + logs.resize(0); + while (!mStop) { + long missLogs = 0; + do { + std::unique_lock lck(mMtx); + while (mLogs.empty() && !mStop) { + mCond.wait(lck); + } + logs.swap(mLogs); + missLogs = mMissLogs; + mMissLogs = 0; + } while (false); + if (mFileSink) { + mFileSink->checkRotate(); + for (auto log : logs) { + mFileSink->write(log); + std::unique_lock lck(mMtx); + mFree.push_back(log); + mCond.notify_one(); + } + } else { + std::unique_lock lck(mMtx); + for (auto log : logs) { + mFree.push_back(log); + } + mCond.notify_one(); + } + logs.resize(0); + if (missLogs > 0 && mFileSink) { + LogUnit log; + log.format(LogLevel::Notice, __FILE__, __LINE__, "MissLog count %ld", missLogs); + mFileSink->write(&log); + } + } +} + +void Logger::log(LogLevel::Type lvl, const char* file, int line, const char* fmt, ...) +{ + LogUnit* log = getLogUnit(); + if (!log) { + return; + } + va_list ap; + va_start(ap, fmt); + log->vformat(lvl, file, line, fmt, ap); + va_end(ap); + put(log); +} + +LogUnit* Logger::getLogUnit() +{ + LogUnit* log = nullptr; + if (mAllowMissLog) { + std::unique_lock lck(mMtx, std::try_to_lock_t()); + if (!lck) { + ++mMissLogs; + return nullptr; + } + if (mFree.size() > 0) { + log = mFree.back(); + mFree.resize(mFree.size() - 1); + } else if (mLogUnitCnt < mFree.capacity()) { + ++mLogUnitCnt; + } else { + ++mMissLogs; + return nullptr; + } + } else { + std::unique_lock lck(mMtx); + if (!mFree.empty()) { + log = mFree.back(); + mFree.resize(mFree.size() - 1); + } else if (mLogUnitCnt < mFree.capacity()) { + ++mLogUnitCnt; + } else { + while (mFree.empty() && !mStop) { + mCond.wait(lck); + } + if (!mFree.empty()) { + log = mFree.back(); + mFree.resize(mFree.size() - 1); + } else { + return nullptr; + } + } + } + return log ? log : new LogUnit(); +} + +int Logger::logFileFd() const +{ + return mFileSink ? mFileSink->fd() : -1; +} diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 0000000..2b372f2 --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,141 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_LOGGER_H_ +#define _PREDIXY_LOGGER_H_ + +#include +#include +#include +#include +#include +#include +#include "Exception.h" + +class LogFileSink; + +class LogLevel +{ +public: + enum Type + { + Verb, + Debug, + Info, + Notice, + Warn, + Error, + + Sentinel + }; + static const char* Str[Sentinel]; +}; + +class LogUnit +{ +public: + static const int MaxLogLen = 1024; +public: + LogUnit(); + ~LogUnit(); + void format(LogLevel::Type level, const char* file, int line, const char* fmt, ...); + void vformat(LogLevel::Type level, const char* file, int line, const char* fmt, va_list ap); + const char* data() const {return mBuf;} + int length() const {return mLen;} +private: + int mLen; + char mBuf[MaxLogLen]; +}; + +class Logger +{ +public: + DefException(SetLogFileFail); +public: + Logger(int maxLogUnitNum = 1024); + ~Logger(); + void start(); + void stop(); + void setLogFile(const char* file, int rotateSecs, long rotateBytes); + bool allowMissLog() const + { + return mAllowMissLog; + } + void setAllowMissLog(bool v) + { + mAllowMissLog = v; + } + int logSample(LogLevel::Type lvl) const + { + return mLogSample[lvl]; + } + void setLogSample(LogLevel::Type lvl, int val) + { + mLogSample[lvl] = val; + } + LogUnit* log(LogLevel::Type lvl) + { + if (mLogSample[lvl] <= 0 || ++LogCnt[lvl] % mLogSample[lvl] != 0) { + return nullptr; + } + return getLogUnit(); + } + void put(LogUnit* u) + { + std::unique_lock lck(mMtx); + mLogs.push_back(u); + mCond.notify_one(); + } + void log(LogLevel::Type lvl, const char* file, int line, const char* fmt, ...); + int logFileFd() const; + static Logger* gInst; +private: + LogUnit* getLogUnit(); + void run(); +private: + bool mStop; + bool mAllowMissLog; + long mMissLogs; + int mLogSample[LogLevel::Sentinel]; + unsigned mLogUnitCnt; + std::vector mLogs; + std::vector mFree; + std::mutex mMtx; + std::condition_variable mCond; + std::thread* mThread; + LogFileSink* mFileSink; + thread_local static long LogCnt[LogLevel::Sentinel]; +}; + +#if 0 + +#define logVerb(fmt, ...) +#define logDebug(fmt, ...) +#define logInfo(fmt, ...) +#define logNotice(fmt, ...) +#define logWarn(fmt, ...) +#define logError(fmt, ...) + +#else + +#define logMacroImpl(lvl, fmt, ...) \ + do { \ + if (auto _lu_ = Logger::gInst->log(lvl)) { \ + _lu_->format(lvl, __FILE__, __LINE__, fmt, ##__VA_ARGS__);\ + Logger::gInst->put(_lu_); \ + } \ + } while(0) + +#define logVerb(fmt, ...) logMacroImpl(LogLevel::Verb, fmt, ##__VA_ARGS__) +#define logDebug(fmt, ...) logMacroImpl(LogLevel::Debug, fmt, ##__VA_ARGS__) +#define logInfo(fmt, ...) logMacroImpl(LogLevel::Info, fmt, ##__VA_ARGS__) +#define logNotice(fmt, ...) logMacroImpl(LogLevel::Notice, fmt, ##__VA_ARGS__) +#define logWarn(fmt, ...) logMacroImpl(LogLevel::Warn, fmt, ##__VA_ARGS__) +#define logError(fmt, ...) logMacroImpl(LogLevel::Error, fmt, ##__VA_ARGS__) + +#endif + +#endif diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..9170fa8 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,113 @@ +CXX ?= g++ +LVL ?= -O3 +Opts += $(LVL) + +ifeq ($(MT), false) + Opts += -D_PREDIXY_SINGLE_THREAD_ +endif + +ifeq ($(TS), true) + Opts += -D_PREDIXY_TIMER_STATS_ +endif + +EV ?= auto + +LDLIBCPP = -Wl,-Bstatic -lstdc++ -Wl,-Bdynamic + +ifeq ($(EV), auto) + plt = $(shell uname) + ifeq ($(plt), Linux) + EV = epoll + Opts += -D_PREDIXY_BACKTRACE_ + else ifeq ($(plt), Darwin) + EV = kqueue + Opts += -D_PREDIXY_BACKTRACE_ + LDLIBCPP = -static-libstdc++ + else ifeq ($(plt), FreeBSD) + EV = kqueue + Opts += -D_PREDIXY_BACKTRACE_ + LDFLAGS += $(shell pkg info -Dx gcc|grep 'rpath') -lexecinfo + else ifeq ($(plt), OpenBSD) + EV = kqueue + else + EV = poll + endif +endif + +ifeq ($(EV), epoll) + multiplexor = EpollMultiplexor + Opts += -D_EPOLL_ +else ifeq ($(EV), poll) + multiplexor = PollMultiplexor + Opts += -D_POLL_ +else ifeq ($(EV), kqueue) + multiplexor = KqueueMultiplexor + Opts += -D_KQUEUE_ +else +$(error Unknown event:$(EV)) +endif + +CFLAGS = -std=c++11 -g -Wall -w $(Opts) +INCFLAGS = +LDFLAGS += $(LDLIBCPP) -rdynamic -lpthread + + +target = predixy +objs = \ + Crc16.o \ + HashFunc.o \ + Timer.o \ + Logger.o \ + LogFileSink.o \ + Alloc.o \ + Socket.o \ + ListenSocket.o \ + AcceptSocket.o \ + ConnectSocket.o \ + $(multiplexor).o \ + Subscribe.o \ + Connection.o \ + AcceptConnection.o \ + ConnectConnection.o \ + Buffer.o \ + Command.o \ + Distribution.o \ + Reply.o \ + ConfParser.o \ + Conf.o \ + Auth.o \ + DC.o \ + LatencyMonitor.o \ + RequestParser.o \ + Request.o \ + ResponseParser.o \ + Response.o \ + Server.o \ + ServerGroup.o \ + ServerPool.o \ + ClusterNodesParser.o \ + ClusterServerPool.o \ + SentinelServerPool.o \ + ConnectConnectionPool.o \ + Handler.o \ + Proxy.o \ + main.o + +.PHONY : default debug clean + +default: $(target) + + +$(target): $(objs) + $(CXX) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +debug: + @make LVL= + +clean: + @rm -rf $(objs) $(target) + @echo Done. + +%.o : %.cpp + $(CXX) $(CFLAGS) -c $^ $(INCFLAGS) + diff --git a/src/Multiplexor.h b/src/Multiplexor.h new file mode 100644 index 0000000..2cff8a9 --- /dev/null +++ b/src/Multiplexor.h @@ -0,0 +1,39 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_MULTIPLEXOR_H_ +#define _PREDIXY_MULTIPLEXOR_H_ + +#include "Socket.h" +#include "Logger.h" + +class MultiplexorBase +{ +public: + enum EventEnum + { + ReadEvent = 0x1, + WriteEvent = 0x2, + ErrorEvent = 0x4, + AddedMask = 0x8 + }; +public: + virtual ~MultiplexorBase() {} +protected: + MultiplexorBase() {} +private: +}; + + +#ifdef _KQUEUE_ +#include "KqueueMultiplexor.h" +#elif _EPOLL_ +#include "EpollMultiplexor.h" +#elif _POLL_ +#include "PollMultiplexor.h" +#endif + +#endif diff --git a/src/PollMultiplexor.cpp b/src/PollMultiplexor.cpp new file mode 100644 index 0000000..6621ac3 --- /dev/null +++ b/src/PollMultiplexor.cpp @@ -0,0 +1,68 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "PollMultiplexor.h" +#include "Socket.h" + +PollMultiplexor::PollMultiplexor(int maxFdSize): + mSkts(maxFdSize), + mFds(maxFdSize) +{ + mSkts.resize(0); + mFds.resize(0); +} + +PollMultiplexor::~PollMultiplexor() +{ +} + +bool PollMultiplexor::addSocket(Socket* s, int evts) +{ + if (s->fd() >= (int)mSkts.size()) { + mSkts.resize(s->fd() + 1, {nullptr, -1}); + } + mSkts[s->fd()].skt = s; + mSkts[s->fd()].idx = mFds.size(); + short int e = 0; + e |= (evts & ReadEvent) ? POLLIN : 0; + e |= (evts & WriteEvent) ? POLLOUT : 0; + mFds.push_back({s->fd(), e, 0}); + return true; +} + +void PollMultiplexor::delSocket(Socket* s) +{ + if (s->fd() < (int)mSkts.size()) { + int idx = mSkts[s->fd()].idx; + if (idx >= 0 && idx < (int)mFds.size()) { + mFds[idx] = mFds.back(); + mFds.resize(mFds.size() - 1); + if (!mFds.empty()) { + mSkts[mFds[idx].fd].idx = idx; + } + } + mSkts[s->fd()].skt = nullptr; + mSkts[s->fd()].idx = -1; + } +} + +bool PollMultiplexor::addEvent(Socket* s, int evts) +{ + int e = 0; + e |= (evts & ReadEvent) ? POLLIN : 0; + e |= (evts & WriteEvent) ? POLLOUT : 0; + mFds[mSkts[s->fd()].idx].events |= e; + return true; +} + +bool PollMultiplexor::delEvent(Socket* s, int evts) +{ + int e = 0; + e |= (evts & ReadEvent) ? POLLIN : 0; + e |= (evts & WriteEvent) ? POLLOUT : 0; + mFds[mSkts[s->fd()].idx].events &= ~e; + return true; +} diff --git a/src/PollMultiplexor.h b/src/PollMultiplexor.h new file mode 100644 index 0000000..5028a94 --- /dev/null +++ b/src/PollMultiplexor.h @@ -0,0 +1,74 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_POLL_MULTIPLEXOR_H_ +#define _PREDIXY_POLL_MULTIPLEXOR_H_ + +#include +#include +#include +#include "Multiplexor.h" +#include "Util.h" + +class PollMultiplexor : public MultiplexorBase +{ +public: + DefException(PollWaitFail); +public: + PollMultiplexor(int maxFdSize = 81920); + ~PollMultiplexor(); + bool addSocket(Socket* s, int evts = ReadEvent); + void delSocket(Socket* s); + bool addEvent(Socket* s, int evts); + bool delEvent(Socket* s, int evts); + template + int wait(long usec, T* handler); +private: + struct SktIdx + { + Socket* skt; + int idx; + }; +private: + std::vector mSkts; + std::vector mFds; +}; + + +template +int PollMultiplexor::wait(long usec, T* handler) +{ + int timeout = usec < 0 ? usec : usec / 1000; + logVerb("poll wait with timeout %ld usec", usec); + int num = poll(mFds.data(), mFds.size(), timeout); + logVerb("poll wait return with events:%d", num); + if (num == -1) { + if (errno == EINTR) { + return 0; + } + Throw(PollWaitFail, "pool wait fail %s", StrError()); + } + int cnt = 0; + for (auto e : mFds) { + if (e.revents) { + Socket* s = mSkts[e.fd].skt; + int evts = 0; + evts |= (e.revents & POLLIN) ? ReadEvent : 0; + evts |= (e.revents & POLLOUT) ? WriteEvent : 0; + evts |= (e.revents & (POLLERR|POLLHUP)) ? ErrorEvent : 0; + handler->handleEvent(s, evts); + if (++cnt == num) { + break; + } + } + } + return num; +} + + +typedef PollMultiplexor Multiplexor; + +#endif diff --git a/src/Predixy.h b/src/Predixy.h new file mode 100644 index 0000000..730a90a --- /dev/null +++ b/src/Predixy.h @@ -0,0 +1,76 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_H_ +#define _PREDIXY_H_ + +#include +#include +#include +#include +#include +#include +#include + +class Socket; +class ListenSocket; +class AcceptSocket; +class ConnectSocket; +class Buffer; +class Command; +class Reply; +class Auth; +class Authority; +class DC; +class DataCenter; +class RequestParser; +class ResponseParser; +class Request; +class Response; +class AcceptConnection; +class ConnectConnection; +class ConnectConnectionPool; +class Server; +class ServerGroup; +class ServerPool; +struct AuthConf; +struct ServerConf; +struct ServerGroupConf; +struct ServerPoolConf; +struct SentinelServerPoolConf; +struct ClusterServerPoolConf; +class ConfParser; +class Conf; +class Transaction; +class Subscribe; +class Handler; +class Proxy; + +#include "Common.h" +#include "Sync.h" +#include "HashFunc.h" +#include "ID.h" +#include "IOVec.h" +#include "List.h" +#include "Deque.h" +#include "Util.h" +#include "String.h" +#include "Timer.h" +#include "Exception.h" +#include "Logger.h" +#include "Alloc.h" +#include "Command.h" +#include "Reply.h" +#include "Multiplexor.h" +#include "Buffer.h" + +typedef SharePtr RequestPtr; +typedef SharePtr ResponsePtr; +typedef SharePtr AcceptConnectionPtr; +typedef SharePtr ConnectConnectionPtr; + + +#endif diff --git a/src/Proxy.cpp b/src/Proxy.cpp new file mode 100644 index 0000000..9f5987e --- /dev/null +++ b/src/Proxy.cpp @@ -0,0 +1,179 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "Proxy.h" +#include "Handler.h" +#include "Socket.h" +#include "Alloc.h" +#include "ListenSocket.h" +#include "AcceptSocket.h" +#include "RequestParser.h" +#include "Backtrace.h" + +static bool Running = false; +static bool Abort = false; +static bool Stop = false; + +static void abortHandler(int sig) +{ + Abort = true; + if (sig == SIGABRT) { + traceInfo(); + } + if (!Running) { + abort(); + } +} + +static void stopHandler(int sig) +{ + Stop = true; + if (!Running) { + abort(); + } +} + +Proxy::Proxy(): + mListener(nullptr), + mDataCenter(nullptr), + mServPool(nullptr), + mStartTime(time(nullptr)), + mStatsVer(0) +{ +} + +Proxy::~Proxy() +{ + for (auto h : mHandlers) { + delete h; + } + delete mServPool; + delete mDataCenter; + delete mListener; + delete mConf; +} + +bool Proxy::init(int argc, char* argv[]) +{ + signal(SIGPIPE, SIG_IGN); + signal(SIGFPE, abortHandler); + signal(SIGILL, abortHandler); + signal(SIGSEGV, abortHandler); + signal(SIGABRT, abortHandler); + signal(SIGBUS, abortHandler); + signal(SIGQUIT, abortHandler); + signal(SIGHUP, abortHandler); + signal(SIGINT, stopHandler); + signal(SIGTERM, stopHandler); + + Command::init(); + mConf = new Conf(); + if (!mConf->init(argc, argv)) { + return false; + } + Logger::gInst = new Logger(); + Logger::gInst->setLogFile(mConf->log(), mConf->logRotateSecs(), mConf->logRotateBytes()); + Logger::gInst->setAllowMissLog(mConf->allowMissLog()); + for (int i = 0; i < LogLevel::Sentinel; ++i) { + LogLevel::Type lvl = LogLevel::Type(i); + Logger::gInst->setLogSample(lvl, mConf->logSample(lvl)); + } + Logger::gInst->start(); + for (auto& ac : mConf->authConfs()) { + mAuthority.add(ac); + } + if (!mConf->localDC().empty()) { + mDataCenter = new DataCenter(); + mDataCenter->init(mConf); + } + AllocBase::setMaxMemory(mConf->maxMemory()); + if (mConf->bufSize() > 0) { + Buffer::setSize(mConf->bufSize()); + } + + mLatencyMonitorSet.init(mConf->latencyMonitors()); + Request::init(); + Response::init(); + ListenSocket* s = new ListenSocket(mConf->bind(), SOCK_STREAM); + if (!s->setNonBlock()) { + logError("proxy listener set nonblock fail:%s", StrError()); + Throw(InitFail, "listener set nonblock", StrError()); + } + s->listen(); + mListener = s; + logNotice("predixy listen in %s", mConf->bind()); + switch (mConf->serverPoolType()) { + case ServerPool::Cluster: + { + ClusterServerPool* p = new ClusterServerPool(this); + p->init(mConf->clusterServerPool()); + mServPool = p; + } + break; + case ServerPool::Sentinel: + { + SentinelServerPool* p = new SentinelServerPool(this); + p->init(mConf->sentinelServerPool()); + mServPool = p; + } + break; + default: + Throw(InitFail, "unknown server pool type"); + break; + } + for (int i = 0; i < mConf->workerThreads(); ++i) { + Handler* h = new Handler(this); + mHandlers.push_back(h); + } + return true; +} + +int Proxy::run() +{ + logNotice("predixy running with Name:%s Workers:%d", + mConf->name(), + (int)mHandlers.size()); + std::vector> tasks; + for (auto h : mHandlers) { + std::shared_ptr t(new std::thread([=](){h->run();})); + tasks.push_back(t); + } + Running = true; + bool stop = false; + while (!stop) { + if (Abort) { + stop = true; + abort(); + } else if (Stop) { + fprintf(stderr, "predixy will quit ASAP Bye!\n"); + stop = true; + } + if (!stop) { + sleep(1); + TimerPoint::report(); + } + } + for (auto h : mHandlers) { + h->stop(); + } + for (auto t : tasks) { + t->join(); + } + Logger::gInst->stop(); + TimerPoint::report(); + if (*mConf->bind() == '/') { + unlink(mConf->bind()); + } + return 0; +} + diff --git a/src/Proxy.h b/src/Proxy.h new file mode 100644 index 0000000..35d2a07 --- /dev/null +++ b/src/Proxy.h @@ -0,0 +1,94 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_PROXY_H_ +#define _PREDIXY_PROXY_H_ + +#include +#include "Predixy.h" +#include "Handler.h" +#include "DC.h" +#include "ServerPool.h" +#include "ClusterServerPool.h" +#include "SentinelServerPool.h" +#include "LatencyMonitor.h" + +class Proxy +{ +public: + DefException(InitFail); +public: + Proxy(); + ~Proxy(); + bool init(int argc, char* argv[]); + int run(); + time_t startTime() const + { + return mStartTime; + } + Conf* conf() const + { + return mConf; + } + ListenSocket* listener() const + { + return mListener; + } + const Authority* authority() const + { + return &mAuthority; + } + DataCenter* dataCenter() const + { + return mDataCenter; + } + ServerPool* serverPool() const + { + return mServPool; + } + bool isSplitMultiKey() const + { + return mConf->sentinelServerPool().groups.size() != 1; + } + bool supportTransaction() const + { + return mConf->sentinelServerPool().groups.size() == 1; + } + bool supportSubscribe() const + { + return mConf->sentinelServerPool().groups.size() == 1 || + mConf->clusterServerPool().servers.size() > 0; + } + const std::vector& handlers() const + { + return mHandlers; + } + long statsVer() const + { + return mStatsVer; + } + long incrStatsVer() + { + return ++mStatsVer; + } + const LatencyMonitorSet& latencyMonitorSet() const + { + return mLatencyMonitorSet; + } +private: + Conf* mConf; + ListenSocket* mListener; + Authority mAuthority; + DataCenter* mDataCenter; + std::vector mHandlers; + ServerPool* mServPool; + time_t mStartTime; + AtomicLong mStatsVer; + LatencyMonitorSet mLatencyMonitorSet; +}; + + +#endif diff --git a/src/Reply.cpp b/src/Reply.cpp new file mode 100644 index 0000000..974f058 --- /dev/null +++ b/src/Reply.cpp @@ -0,0 +1,16 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Reply.h" + +const char* Reply::TypeStr[Sentinel] = { + "None", + "Status", + "Err", + "Str", + "Int", + "Array" +}; diff --git a/src/Reply.h b/src/Reply.h new file mode 100644 index 0000000..e4390cb --- /dev/null +++ b/src/Reply.h @@ -0,0 +1,27 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_REPLY_H_ +#define _PREDIXY_REPLY_H_ + +class Reply +{ +public: + enum Type + { + None, + Status, + Error, + String, + Integer, + Array, + + Sentinel + }; + static const char* TypeStr[Sentinel]; +}; + +#endif diff --git a/src/Request.cpp b/src/Request.cpp new file mode 100644 index 0000000..0b4b58c --- /dev/null +++ b/src/Request.cpp @@ -0,0 +1,388 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Request.h" +#include "RequestParser.h" +#include "Response.h" + +struct GenericRequest +{ + Request::GenericCode code; + Command::Type type; + const char* content; + const Request* req; +}; + +static GenericRequest GenericRequests[] = { + {Request::Ping, Command::Ping, "*1\r\n$4\r\nping\r\n", nullptr}, + {Request::PingServ, Command::PingServ, "*1\r\n$4\r\nping\r\n", nullptr}, + {Request::ClusterNodes, Command::ClusterNodes, "*2\r\n$7\r\ncluster\r\n$5\r\nnodes\r\n", nullptr}, + {Request::Asking, Command::Asking, "*1\r\n$6\r\nasking\r\n", nullptr}, + {Request::Readonly, Command::Readonly, "*1\r\n$8\r\nreadonly\r\n", nullptr}, + {Request::UnwatchServ, Command::UnwatchServ, "*1\r\n$7\r\nunwatch\r\n", nullptr}, + {Request::DiscardServ, Command::DiscardServ, "*1\r\n$7\r\ndiscard\r\n", nullptr}, + {Request::MgetHead, Command::Mget, "*2\r\n$4\r\nmget\r\n", nullptr}, + {Request::MsetHead, Command::Mset, "*3\r\n$4\r\nmset\r\n", nullptr}, + {Request::MsetnxHead, Command::Msetnx, "*3\r\n$6\r\nmsetnx\r\n", nullptr}, + {Request::TouchHead, Command::Touch, "*2\r\n$5\r\ntouch\r\n", nullptr}, + {Request::ExistsHead, Command::Exists, "*2\r\n$6\r\nexists\r\n", nullptr}, + {Request::DelHead, Command::Del, "*2\r\n$3\r\ndel\r\n", nullptr}, + {Request::UnlinkHead, Command::Unlink, "*2\r\n$6\r\nunlink\r\n", nullptr}, + {Request::PsubscribeHead,Command::Psubscribe, "*2\r\n$10\r\npsubscribe\r\n", nullptr}, + {Request::SubscribeHead,Command::Subscribe, "*2\r\n$9\r\nsubscribe\r\n", nullptr} +}; + +void Request::init() +{ + BufferPtr buf = BufferAlloc::create(); + for (auto& r : GenericRequests) { + Request* req = new Request(); + req->mType= r.type; + if (buf->room() < (int)strlen(r.content)) { + buf = BufferAlloc::create(); + } + buf = req->mReq.set(buf, r.content); + r.req = req; + } +} + +Request::Request(): + mConn(nullptr), + mType(Command::None), + mDone(false), + mDelivered(false), + mFollowers(0), + mFollowersDone(0), + mRedirectCnt(0), + mCreateTime(Util::elapsedUSec()), + mData(nullptr) +{ +} + +Request::Request(AcceptConnection* c): + mConn(c), + mType(Command::None), + mDone(false), + mDelivered(false), + mFollowers(0), + mFollowersDone(0), + mRedirectCnt(0), + mCreateTime(Util::elapsedUSec()), + mData(nullptr) +{ +} + +Request::Request(GenericCode code): + mConn(nullptr), + mDone(false), + mDelivered(false), + mFollowers(0), + mFollowersDone(0), + mRedirectCnt(0), + mCreateTime(Util::elapsedUSec()), + mData(nullptr) +{ + auto r = GenericRequests[code].req; + mType = r->mType; + mReq = r->mReq; +} + +Request::~Request() +{ +} + +void Request::clear() +{ + mRes = nullptr; + mHead.clear(); + mReq.clear(); + mKey.clear(); + mLeader = nullptr; +} + +void Request::set(const RequestParser& p, Request* leader) +{ + mType = p.type(); + if (leader) { + const Request* r = nullptr; + switch (mType) { + case Command::Mget: + r = GenericRequests[MgetHead].req; + break; + case Command::Mset: + r = GenericRequests[MsetHead].req; + break; + case Command::Msetnx: + r = GenericRequests[MsetnxHead].req; + break; + case Command::Touch: + r = GenericRequests[TouchHead].req; + break; + case Command::Exists: + r = GenericRequests[ExistsHead].req; + break; + case Command::Del: + r = GenericRequests[DelHead].req; + break; + case Command::Unlink: + r = GenericRequests[UnlinkHead].req; + break; + case Command::Psubscribe: + r = GenericRequests[PsubscribeHead].req; + break; + case Command::Subscribe: + r = GenericRequests[SubscribeHead].req; + break; + default: + //should never reach + break; + } + mHead = r->mReq; + mReq = p.arg(); + mLeader = leader; + if (leader == this) { + if (mType == Command::Mset || mType == Command::Msetnx) { + mFollowers = (p.argNum() - 1) >> 1; + } else { + mFollowers = p.argNum() - 1; + } + } + } else { + mReq = p.request(); + } + mKey = p.key(); +} + +void Request::setAuth(const String& password) +{ + mType = Command::AuthServ; + mHead.clear(); + mReq.fset(nullptr, + "*2\r\n" + "$4\r\n" + "auth\r\n" + "$%d\r\n" + "%s\r\n", + password.length(), password.data() ? password.data() : ""); +} + +void Request::setSelect(int db) +{ + char buf[16]; + int num = snprintf(buf, sizeof(buf), "%d", db); + mType = Command::SelectServ; + mHead.clear(); + mReq.fset(nullptr, + "*2\r\n" + "$6\r\n" + "select\r\n" + "$%d\r\n" + "%s\r\n", + num, buf); +} + +void Request::setSentinels(const String& master) +{ + mType = Command::SentinelSentinels; + mHead.clear(); + mReq.fset(nullptr, + "*3\r\n" + "$8\r\n" + "sentinel\r\n" + "$9\r\n" + "sentinels\r\n" + "$%d\r\n" + "%.*s\r\n", + master.length(), master.length(), master.data()); +} + +void Request::setSentinelGetMaster(const String& master) +{ + mType = Command::SentinelGetMaster; + mHead.clear(); + mReq.fset(nullptr, + "*3\r\n" + "$8\r\n" + "sentinel\r\n" + "$23\r\n" + "get-master-addr-by-name\r\n" + "$%d\r\n" + "%.*s\r\n", + master.length(), master.length(), master.data()); +} + +void Request::setSentinelSlaves(const String& master) +{ + mType = Command::SentinelSlaves; + mHead.clear(); + mReq.fset(nullptr, + "*3\r\n" + "$8\r\n" + "sentinel\r\n" + "$6\r\n" + "slaves\r\n" + "$%d\r\n" + "%.*s\r\n", + master.length(), master.length(), master.data()); +} + +void Request::adjustScanCursor(long cursor) +{ + char buf[32]; + int n = snprintf(buf, sizeof(buf), "%ld", cursor); + if (mHead.empty()) { + SegmentStr<64> str(mReq); + const char* p = strchr(str.data(), '$'); + if (!p) { + return; + } + p = strchr(p + 1, '$'); + if (!p) { + return; + } + mHead.fset(nullptr, + "%.*s" + "$%d\r\n" + "%s\r\n", + p - str.data(), str.data(), n, buf); + p = strchr(p, '\r'); + if (!p) { + return; + } + mReq.cut(p - str.data() + 2 + mKey.length() + 2); + } else { + SegmentStr<64> str(mHead); + const char* p = strchr(str.data(), '$'); + if (!p) { + return; + } + p = strchr(p + 1, '$'); + if (!p) { + return; + } + mHead.fset(nullptr, + "%.*s" + "$%d\r\n" + "%s\r\n", + p - str.data(), str.data(), n, buf); + } +} + +void Request::follow(Request* leader) +{ + ++mFollowers; + if (leader == this) { + return; + } + mType = leader->mType; + mHead = leader->mHead; + mReq = leader->mReq; + mKey = leader->mKey; + mLeader = leader; +} + +bool Request::send(Socket* s) +{ + const char* dat; + int len; + while (mHead.get(dat, len)) { + int n = s->write(dat, len); + if (n > 0) { + mHead.use(n); + } else { + return false; + } + } + while (mReq.get(dat, len)) { + int n = s->write(dat, len); + if (n > 0) { + mReq.use(n); + } else { + return false; + } + } + return true; +} + +int Request::fill(IOVec* vecs, int len) +{ + bool all = false; + int n = mHead.fill(vecs, len, all); + if (!all) { + return n; + } + n += mReq.fill(vecs + n, len - n, all); + if (n > 0 && all) { + vecs[n - 1].req = this; + } + return n; +} + +void Request::setResponse(Response* res) +{ + mDone = true; + if (mLeader) { + mLeader->mFollowersDone += 1; + switch (mType) { + case Command::Mget: + mRes = res; + break; + case Command::Mset: + if (Response* leaderRes = mLeader->getResponse()) { + if (res->isError() && !leaderRes->isError()) { + mLeader->mRes = res; + } + } else { + mLeader->mRes = res; + } + break; + case Command::Msetnx: + case Command::Touch: + case Command::Exists: + case Command::Del: + case Command::Unlink: + if (!mLeader->mRes) { + mLeader->mRes = res; + } + if (mLeader->isDone()) { + mLeader->mRes->set(mLeader->mRes->integer()); + } + break; + case Command::ScriptLoad: + if (Response* leaderRes = mLeader->getResponse()) { + if (leaderRes->isString() && !res->isString()) { + mLeader->mRes = res; + } + } else { + mLeader->mRes = res; + } + break; + default: + //should never reach here + mRes = res; + break; + } + } else { + mRes = res; + } +} + +bool Request::isDone() const +{ + if (mLeader == this) { + switch (mType) { + case Command::Mget: + case Command::Psubscribe: + case Command::Subscribe: + return mDone; + default: + break; + } + return mFollowers == mFollowersDone; + } + return mDone; +} + diff --git a/src/Request.h b/src/Request.h new file mode 100644 index 0000000..344d815 --- /dev/null +++ b/src/Request.h @@ -0,0 +1,177 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_REQUEST_H_ +#define _PREDIXY_REQUEST_H_ + +#include "Predixy.h" + +enum RequestListIndex +{ + Recv = 0, + Send, + + Size +}; + +class Request : + public TID, + public ListNode, RequestListIndex::Size>, + public RefCntObj +{ +public: + typedef Request Value; + typedef ListNode, RequestListIndex::Size> ListNodeType; + static const int MaxRedirectLimit = 3; + enum GenericCode + { + Ping, + PingServ, + ClusterNodes, + Asking, + Readonly, + UnwatchServ, + DiscardServ, + MgetHead, + MsetHead, + MsetnxHead, + TouchHead, + ExistsHead, + DelHead, + UnlinkHead, + PsubscribeHead, + SubscribeHead + }; + static void init(); +public: + Request(); + Request(AcceptConnection* c); + Request(GenericCode code); + ~Request(); + void clear(); + void set(const RequestParser& p, Request* leader = nullptr); + void setAuth(const String& password); + void setSelect(int db); + void setSentinels(const String& master); + void setSentinelGetMaster(const String& master); + void setSentinelSlaves(const String& master); + void adjustScanCursor(long cursor); + void follow(Request* leader); + void setResponse(Response* res); + bool send(Socket* s); + int fill(IOVec* vecs, int len); + bool isDone() const; + AcceptConnection* connection() const + { + return mConn; + } + void detach() + { + mConn = nullptr; + } + void setType(Command::Type t) + { + mType = t; + } + Command::Type type() const + { + return mType; + } + const char* cmd() const + { + return Command::get(mType).name; + } + bool isInner() const + { + return Command::get(mType).mode & Command::Inner; + } + const Segment& key() const + { + return mKey; + } + const Segment& body() const + { + return mReq; + } + void setData(void* dat) + { + mData = dat; + } + void* data() const + { + return mData; + } + void rewind() + { + mHead.rewind(); + mReq.rewind(); + } + Response* getResponse() const + { + return mRes; + } + Request* leader() const + { + return mLeader; + } + bool isLeader() const + { + return mLeader == this; + } + bool isDelivered() const + { + return mDelivered; + } + void setDelivered() + { + mDelivered = true; + } + int followers() const + { + return mFollowers; + } + int redirectCnt() const + { + return mRedirectCnt; + } + int incrRedirectCnt() + { + return ++mRedirectCnt; + } + bool requireWrite() const + { + return Command::get(mType).mode & Command::Write; + } + bool requirePrivateConnection() const + { + return Command::get(mType).mode & Command::Private; + } + long createTime() const + { + return mCreateTime; + } +private: + AcceptConnection* mConn; + Command::Type mType; + ResponsePtr mRes; + bool mDone; + bool mDelivered; + Segment mHead; //for multi key command mget/mset/del... + Segment mReq; + Segment mKey; + RequestPtr mLeader; + int mFollowers; + int mFollowersDone; + int mRedirectCnt; + long mCreateTime; //steady time point us + void* mData; //user data for response +}; + +typedef List RecvRequestList; +typedef List SendRequestList; +typedef Alloc RequestAlloc; + +#endif diff --git a/src/RequestParser.cpp b/src/RequestParser.cpp new file mode 100644 index 0000000..b0e6067 --- /dev/null +++ b/src/RequestParser.cpp @@ -0,0 +1,290 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "RequestParser.h" + +RequestParser::RequestParser() +{ + reset(); +} + +RequestParser::~RequestParser() +{ + reset(); +} + +void RequestParser::reset() +{ + mType = Command::None; + mCommand = nullptr; + mReq.clear(); + mCmd.clear(); + mArg.clear(); + mKey.clear(); + mStatus = Normal; + mState = Idle; + mInline = false; + mArgNum = 0; + mArgCnt = 0; + mArgLen = 0; + mArgBodyCnt = 0; + mByteCnt = 0; +} + +bool RequestParser::isKey(bool split) const +{ + if (mCommand) { + switch (mCommand->mode & Command::KeyMask) { + case Command::NoKey: + return false; + case Command::MultiKey: + return split ? mArgCnt > 0 : mArgCnt == 1; + case Command::SMultiKey: + return mArgCnt > 0; + case Command::MultiKeyVal: + return split ? (mArgCnt & 1) : mArgCnt == 1; + case Command::KeyAt3: + return mArgCnt == 3; + default: + return mArgCnt == 1; + } + } + return false; +} + +RequestParser::Status RequestParser::parse(Buffer* buf, int& pos, bool split) +{ + FuncCallTimer(); + int start = pos; + char* cursor = buf->data() + pos; + char* end = buf->tail(); + int error = 0; + while (cursor < end && !error) { + ++mByteCnt; + if (mStatus != Normal && mByteCnt > MaxAllowInvalidByteCount) { + logWarn("request command argument invalid"); + error = __LINE__; + break; + } + char ch = *cursor; + switch (mState) { + case Idle: + mArgNum = 0; + if (ch == '*') { + mReq.begin().buf = buf; + mReq.begin().pos = pos; + mState = ArgNum; + break; + } else { + mState = InlineBegin; + mInline = true; + } + case InlineBegin: + if (ch == '\r') { + error = __LINE__; + } else if (!isspace(ch)) { + mReq.begin().buf = buf; + mReq.begin().pos = pos; + mCmd.begin() = mReq.begin(); + mState = InlineCmd; + } + break; + case InlineCmd: + if (isspace(ch)) { + mCmd.end().buf = buf; + mCmd.end().pos = pos; + parseCmd(); + mState = ch == '\r' ? InlineLF : InlineArg; + } + break; + case InlineArg: + if (ch == '\r') { + mState = InlineLF; + } + break; + case InlineLF: + if (ch == '\n') { + mState = Finished; + goto Done; + } else { + error = __LINE__; + } + break; + case ArgNum: + if (ch >= '0' && ch <= '9') { + mArgNum = mArgNum * 10 + (ch - '0'); + } else if (ch == '\r') { + //mState = mArgNum > 0 ? ArgNumLF : Error; + mArgNum > 0 ? mState = ArgNumLF : error = __LINE__; + } else { + error = __LINE__; + } + break; + case ArgNumLF: + mArgCnt = 0; + //mState = ch == '\n' ? ArgTag : Error; + ch == '\n' ? mState = ArgTag : error = __LINE__; + break; + case ArgTag: + mArgLen = 0; + if (ch == '$') { + mState = ArgLen; + if (isKey(split)) { + mArg.begin().buf = buf; + mArg.begin().pos = pos; + } + } else { + error = __LINE__; + } + break; + case ArgLen: + if (ch >= '0' && ch <= '9') { + mArgLen = mArgLen * 10 + (ch - '0'); + } else if (ch == '\r') { + //mState = mArgLen >= 0 ? ArgLenLF : Error; + mArgLen >= 0 ? mState = ArgLenLF : error = __LINE__; + } else { + error = __LINE__; + } + break; + case ArgLenLF: + mArgBodyCnt = 0; + ch == '\n' ? mState = ArgBody : error = __LINE__; + break; + case ArgBody: + if (mArgBodyCnt == 0) { + if (mArgCnt == 0) { + mCmd.begin().buf = buf; + mCmd.begin().pos = pos; + } else if (isKey(split)) { + mKey.begin().buf = buf; + mKey.begin().pos = pos; + } + } + if (mArgBodyCnt + (end - cursor) > mArgLen) { + pos += mArgLen - mArgBodyCnt; + cursor = buf->data() + pos; + if (*cursor == '\r') { + mState = ArgBodyLF; + if (mArgCnt == 0) { + mCmd.end().buf = buf; + mCmd.end().pos = pos; + parseCmd(); + } else if (isKey(split)) { + mKey.end().buf = buf; + mKey.end().pos = pos; + } + } else { + error = __LINE__; + } + } else { + mArgBodyCnt += end - cursor; + pos = buf->length() - 1; + } + break; + case ArgBodyLF: + if (ch == '\n') { + if (++mArgCnt == mArgNum) { + mState = Finished; + goto Done; + } else { + mState = ArgTag; + if (mArgCnt > 1 && isKey(split) && mStatus == Normal && + (mCommand->mode&(Command::MultiKey|Command::SMultiKey|Command::MultiKeyVal))) { + goto Done; + } + if (mArgCnt > 1 && mCommand && mStatus == Normal && split) { + if (mCommand->isMultiKey()) { + goto Done; + } else if (mCommand->isMultiKeyVal() && (mArgCnt & 1)) { + goto Done; + } + } + } + } else { + error = __LINE__; + } + break; + default: + error = __LINE__; + break; + } + ++pos; + cursor = buf->data() + pos; + } + if (error) { + SString<64> bufHex; + bufHex.printHex(buf->data() + start, buf->length() - start); + SString<16> errHex; + errHex.printHex(cursor - 1, end - cursor + 1); + logDebug("request parse error %d state %d buf:%s errpos %d err:%s", + error, mState, bufHex.data(), + pos - 1 - start, errHex.data()); + return ParseError; + } + return Normal; +Done: + mReq.end().buf = buf; + mReq.end().pos = ++pos; + mReq.rewind(); + mArg.end() = mReq.end(); + mArg.rewind(); + if (mState == Finished) { + return mStatus == Normal ? Complete : mStatus; + } else { + return mStatus == Normal ? Partial : ParseError; + } +} + +void RequestParser::parseCmd() +{ + FuncCallTimer(); + SegmentStr cmd(mCmd); + if (!cmd.complete()) { + mStatus = CmdError; + mType = Command::None; + logNotice("unknown request cmd too long:%.*s...", + cmd.length(), cmd.data()); + return; + } + auto c = Command::find(cmd); + if (!c) { + mStatus = CmdError; + mType = Command::None; + logNotice("unknown request cmd:%.*s", cmd.length(), cmd.data()); + return; + } + mType = c->type; + mCommand = c; + if (mInline) { + if (mType != Command::Ping) { + mStatus = CmdError; + logNotice("unsupport command %s in inline command protocol", c->name); + return; + } + return; + } + if (mArgNum < c->minArgs || mArgNum > c->maxArgs) { + mStatus = ArgError; + logNotice("request argument is invalid cmd %.*s argnum %d", + cmd.length(), cmd.data(), mArgNum); + return; + } + switch (mType) { + case Command::Mset: + case Command::Msetnx: + if (!(mArgNum & 1)) { + mStatus = ArgError; + logNotice("request argument is invalid cmd %.*s argnum %d", + cmd.length(), cmd.data(), mArgNum); + return; + } + break; + default: + break; + } +} + diff --git a/src/RequestParser.h b/src/RequestParser.h new file mode 100644 index 0000000..c31548a --- /dev/null +++ b/src/RequestParser.h @@ -0,0 +1,124 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_REQUEST_PARSER_H_ +#define _PREDIXY_REQUEST_PARSER_H_ + +#include +#include "Predixy.h" + +class RequestParser +{ +public: + static const int MaxAllowInvalidByteCount = 1024; + static const int MaxCmdLen = 32; + enum State + { + Idle, // * or inline command + InlineBegin, + InlineCmd, + InlineLF, + InlineArg, + ArgNum, // 2 + ArgNumLF, // \r\n + ArgTag, // $ $ + ArgLen, // 3 5 + ArgLenLF, // \r\n \r\n + ArgBody, // get hello + ArgBodyLF, // \r\n \r\n + Finished, + + Error + }; + enum Status + { + Normal, + Partial, + Complete, + CmdError, + ArgError, + ParseError + }; + static void init(); +public: + RequestParser(); + ~RequestParser(); + Status parse(Buffer* buf, int& pos, bool split); + void reset(); + bool isIdle() const + { + return mState == Idle; + } + Command::Type type() const + { + return mType; + } + const Command* command() const + { + return mCommand; + } + bool isInline() const + { + return mInline; + } + Segment& request() + { + return mReq; + } + const Segment& request() const + { + return mReq; + } + int argNum() const + { + return mArgNum; + } + Segment& arg() + { + return mArg; + } + const Segment& arg() const + { + return mArg; + } + Segment& cmd() + { + return mCmd; + } + const Segment& cmd() const + { + return mCmd; + } + Segment& key() + { + return mKey; + } + const Segment& key() const + { + return mKey; + } +private: + void parseCmd(); + bool isKey(bool split) const; +private: + Command::Type mType; + const Command* mCommand; + // *2\r\n$3\r\nget\r\n$3\r\nkey\r\n + Segment mReq; // |------------------------------| + Segment mCmd; // |-| + Segment mArg; // |-----------| + Segment mKey; // |-| + Status mStatus; + State mState; + bool mInline; + int mArgNum; + int mArgCnt; + int mArgLen; + int mArgBodyCnt; + int mByteCnt; +}; + +#endif diff --git a/src/Response.cpp b/src/Response.cpp new file mode 100644 index 0000000..9212fad --- /dev/null +++ b/src/Response.cpp @@ -0,0 +1,213 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Response.h" +#include "Request.h" + +struct GenericResponse +{ + Response::GenericCode code; + Reply::Type type; + const char* content; + const Response* res; +}; + +static GenericResponse GenericResponses[] = { + {Response::Pong, Reply::Status, "+PONG\r\n", nullptr}, + {Response::Ok, Reply::Status, "+OK\r\n", nullptr}, + {Response::Cmd, Reply::Array, "*0\r\n"}, + {Response::UnknownCmd, Reply::Error, "-ERR unknown command\r\n", nullptr}, + {Response::ArgWrong, Reply::Error, "-ERR argument wrong\r\n", nullptr}, + {Response::InvalidDb, Reply::Error, "-ERR invalid DB index\r\n", nullptr}, + {Response::NoPasswordSet, Reply::Error, "-ERR Client sent AUTH, but no password is set\r\n", nullptr}, + {Response::InvalidPassword, Reply::Error, "-ERR invalid password\r\n", nullptr}, + {Response::Unauth, Reply::Error, "-NOAUTH Authentication required.\r\n", nullptr}, + {Response::PermissionDeny, Reply::Error, "-ERR auth permission deny\r\n", nullptr}, + {Response::NoServer, Reply::Error, "-ERR no server avaliable\r\n", nullptr}, + {Response::NoServerConnection, Reply::Error, "-ERR no server connection avaliable\r\n", nullptr}, + {Response::ServerConnectionClose, Reply::Error, "-ERR server connection close\r\n", nullptr}, + {Response::DeliverRequestFail, Reply::Error, "-ERR deliver request fail\r\n", nullptr}, + {Response::ForbidTransaction, Reply::Error, "-ERR forbid transaction in current server pool\r\n", nullptr}, + {Response::ConfigSubCmdUnknown, Reply::Error, "-ERR CONFIG subcommand must be one of GET, SET\r\n", nullptr}, + {Response::InvalidScanCursor, Reply::Error, "-ERR invalid cursor\r\n", nullptr}, + {Response::ScanEnd, Reply::Array, "*2\r\n$1\r\n0\r\n*0\r\n", nullptr} +}; + +void Response::init() +{ + BufferPtr buf = BufferAlloc::create(); + for (auto& r : GenericResponses) { + Response* res = new Response(); + res->mType = r.type; + if (buf->room() < (int)strlen(r.content)) { + buf = BufferAlloc::create(); + } + buf = res->mRes.set(buf, r.content); + r.res = res; + } +} + +Response::Response(): + mType(Reply::None), + mInteger(0) +{ +} + +Response::Response(GenericCode code): + mType(Reply::None), + mInteger(0) +{ + auto r = GenericResponses[code].res; + mType = r->mType; + mRes = r->mRes; +} + + +Response::~Response() +{ +} + +void Response::set(const ResponseParser& p) +{ + mType = p.type(); + mInteger = p.integer(); + mHead.clear(); + mRes = p.response(); +} + + +void Response::set(int64_t num) +{ + mType = Reply::Integer; + mInteger = num; + mHead.clear(); + mRes.fset(nullptr, ":%ld\r\n", num); +} + +void Response::setStr(const char* str, int len) +{ + mType = Reply::String; + mHead.clear(); + if (len < 0) { + len = strlen(str); + } + mRes.fset(nullptr, "$%d\r\n%.*s\r\n", len, len, str); +} + +void Response::setErr(const char* str, int len) +{ + mType = Reply::Error; + mHead.clear(); + if (len < 0) { + len = strlen(str); + } + mRes.fset(nullptr, "-ERR %.*s\r\n", len, str); +} + +void Response::adjustForLeader(Request* req) +{ + if (Request* leader = req->leader()) { + switch (req->type()) { + case Command::Mget: + if (mType == Reply::Array) { + mRes.cut(4);// cut "*1\r\n" + if (leader == req) { + mHead.fset(nullptr, "*%d\r\n", req->followers()); + } + } else { + mType = Reply::Array; + if (leader == req) { + mRes.fset(nullptr, "*%d\r\n$-1\r\n", req->followers()); + } else { + mRes.set(nullptr, "$-1\r\n"); + } + } + break; + case Command::Mset: + break; + case Command::Msetnx: + case Command::Touch: + case Command::Exists: + case Command::Del: + case Command::Unlink: + if (Response* r = leader->getResponse()) { + if (r != this && mType == Reply::Integer && mInteger > 0) { + r->mInteger += mInteger; + } + } + break; + default: + break; + } + } +} + +bool Response::send(Socket* s) +{ + const char* dat; + int len; + while (mHead.get(dat, len)) { + int n = s->write(dat, len); + if (n > 0) { + mHead.use(n); + } else { + return false; + } + } + while (mRes.get(dat, len)) { + int n = s->write(dat, len); + if (n > 0) { + mRes.use(n); + } else { + return false; + } + } + return true; +} + +int Response::fill(IOVec* vecs, int len, Request* req) const +{ + bool all = false; + int n = mHead.fill(vecs, len, all); + if (!all) { + return n; + } + n += mRes.fill(vecs + n, len - n, all); + if (n > 0 && all) { + vecs[n - 1].req = req; + } + return n; + +} + +bool Response::getAddr(int& slot, SString& addr, const char* token) const +{ + SegmentStr str(mRes); + if (!str.complete() || !str.hasPrefix(token)) { + return false; + } + const char* p = str.data() + strlen(token); + slot = atoi(p); + if (slot < 0 || slot >= Const::RedisClusterSlots) { + return false; + } + p = strchr(p, ' '); + if (!p) { + return false; + } + p++; + const char* e = strchr(p, '\r'); + if (!e) { + return false; + } + int len = e - p; + if (len >= Const::MaxAddrLen) { + return false; + } + addr.set(p, len); + return true; +} + diff --git a/src/Response.h b/src/Response.h new file mode 100644 index 0000000..448f6e8 --- /dev/null +++ b/src/Response.h @@ -0,0 +1,140 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_RESPONSE_H_ +#define _PREDIXY_RESPONSE_H_ + +#include "Predixy.h" +#include "ResponseParser.h" + +class Response : + public TID, + public ListNode>, + public RefCntObj +{ +public: + typedef Response Value; + typedef ListNode> ListNodeType; + enum GenericCode + { + Pong, + Ok, + Cmd, + UnknownCmd, + ArgWrong, + InvalidDb, + NoPasswordSet, + InvalidPassword, + Unauth, + PermissionDeny, + NoServer, + NoServerConnection, + ServerConnectionClose, + DeliverRequestFail, + ForbidTransaction, + ConfigSubCmdUnknown, + InvalidScanCursor, + ScanEnd + }; + static void init(); + static Response* create(GenericCode code, Request* req = nullptr); +public: + Response(); + Response(GenericCode code); + ~Response(); + void set(const ResponseParser& p); + void set(int64_t num); + void setStr(const char* str, int len = -1); + void setErr(const char* str, int len = -1); + void adjustForLeader(Request* req); + bool send(Socket* s); + int fill(IOVec* vecs, int len, Request* req) const; + void setType(Reply::Type t) + { + mType = t; + } + Reply::Type type() const + { + return mType; + } + const char* typeStr() const + { + return Reply::TypeStr[mType]; + } + int64_t integer() const + { + return mInteger; + } + bool isOk() const + { + return mType == Reply::Status && mRes.hasPrefix("+OK"); + } + bool isPong() const + { + return mType == Reply::Status && mRes.hasPrefix("+PONG"); + } + bool isError() const + { + return mType == Reply::Error; + } + bool isInteger() const + { + return mType == Reply::Integer; + } + bool isString() const + { + return mType == Reply::String; + } + bool isArray() const + { + return mType == Reply::Array; + } + bool isMoved() const + { + return mType == Reply::Error && mRes.hasPrefix("-MOVED "); + } + bool getMoved(int& slot, SString& addr) const + { + return getAddr(slot, addr, "-MOVED "); + } + bool isAsk() const + { + return mType == Reply::Error && mRes.hasPrefix("-ASK "); + } + bool getAsk(SString& addr) const + { + int slot; + return getAddr(slot, addr, "-ASK "); + } + Segment& head() + { + return mHead; + } + const Segment& head() const + { + return mHead; + } + const Segment& body() const + { + return mRes; + } + Segment& body() + { + return mRes; + } +private: + bool getAddr(int& slot, SString& addr, const char* token) const; +private: + Reply::Type mType; + int64_t mInteger; + Segment mHead; //for mget + Segment mRes; +}; + +typedef List ResponseList; +typedef Alloc ResponseAlloc; + +#endif diff --git a/src/ResponseParser.cpp b/src/ResponseParser.cpp new file mode 100644 index 0000000..7a3bf38 --- /dev/null +++ b/src/ResponseParser.cpp @@ -0,0 +1,260 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "String.h" +#include "ResponseParser.h" + +ResponseParser::ResponseParser() +{ + reset(); +} + +ResponseParser::~ResponseParser() +{ +} + +void ResponseParser::reset() +{ + mType = Reply::None; + mRes.clear(); + mState = Idle; + mInteger = 0; + mStringLen = 0; + mStringCnt = 0; + mDepth = 0; + mSign = false; +} + +ResponseParser::Status ResponseParser::parse(Buffer* buf, int& pos) +{ + FuncCallTimer(); + char* cursor = buf->data() + pos; + char* end = buf->tail(); + int error = 0; + while (cursor < end && !error) { + char ch = *cursor; + switch (mState) { + case Idle: + mRes.begin().buf = buf; + mRes.begin().pos = cursor - buf->data(); + mRes.rewind(); + switch (ch) { + case '+': + mType = Reply::Status; + mState = StatusHead; + break; + case '-': + mType = Reply::Error; + mState = ErrorHead; + break; + case ':': + mType = Reply::Integer; + mState = IntegerHead; + break; + case '$': + mType = Reply::String; + mState = StringHead; + break; + case '*': + mType = Reply::Array; + mState = ArrayHead; + break; + default: + error = __LINE__; + break; + } + break; + case StatusHead: + case ErrorHead: + if (ch == '\r') { + mState = HeadLF; + } + break; + case IntegerHead: + case StringHead: + case ArrayHead: + if (ch >= '0' && ch <= '9') { + mInteger = mInteger * 10 + ch - '0'; + } else if (ch == '\r') { + if (mSign) { + mInteger = -mInteger; + } + mState = HeadLF; + } else if (ch == '-' && !mSign) { + mSign = true; + } else { + error = __LINE__; + } + break; + case HeadLF: + if (ch != '\n') { + error = __LINE__; + break; + } + switch (mType) { + case Reply::Status: + case Reply::Error: + case Reply::Integer: + goto Done; + case Reply::String: + if (mInteger < 0) { + goto Done; + } + mStringLen = mInteger; + mStringCnt = 0; + mState = StringBody; + break; + case Reply::Array: + if (mInteger <= 0) { + goto Done; + } + mDepth = 0; + mArrayNum[mDepth] = mInteger; + mElementCnt[mDepth++] = 0; + mState = ArrayBody; + break; + default: + error = __LINE__; + break; + } + break; + case StringBody: + if (mStringCnt == mStringLen) { + ch == '\r' ? mState = StringBodyLF : error = __LINE__; + } else { + if (mStringCnt + (end - cursor) >= mStringLen) { + cursor += mStringLen - mStringCnt - 1; + mStringCnt = mStringLen; + } else { + mStringCnt += end - cursor; + cursor = end - 1; + } + } + break; + case StringBodyLF: + if (ch == '\n') { + goto Done; + } + error = __LINE__; + break; + case ArrayBody: + switch (ch) { + case '+': + case '-': + case ':': + mState = LineBody; + break; + case '$': + mSign = false; + mInteger = 0; + mState = SubStringLen; + break; + case '*': + mInteger = 0; + mState = SubArrayLen; + break; + default: + error = __LINE__; + break; + } + break; + case LineBody: + if (ch == '\r') { + mState = ElementLF; + } + break; + case SubStringLen: + if (ch >= '0' && ch <= '9') { + mInteger = mInteger * 10 + ch - '0'; + } else if (ch == '\r') { + mState = mSign ? ElementLF : SubStringLenLF; + } else if (ch == '-' && !mSign) { + mSign = true; + } else { + error = __LINE__; + } + break; + case SubArrayLen: + if (ch >= '0' && ch <= '9') { + mInteger = mInteger * 10 + ch - '0'; + } else if (ch == '\r') { + mState = mInteger > 0 ? SubArrayLenLF : ElementLF; + } else { + error = __LINE__; + } + break; + case SubStringLenLF: + mStringLen = mInteger; + mStringCnt = 0; + ch == '\n' ? mState = SubStringBody : error = __LINE__; + break; + case SubArrayLenLF: + if (ch == '\n') { + if (mDepth < MaxArrayDepth) { + mArrayNum[mDepth] = mInteger; + mElementCnt[mDepth++] = 0; + mState = ArrayBody; + } else { + logError("response parse error:array too depth"); + error = __LINE__; + } + } else { + error = __LINE__; + } + break; + case SubStringBody: + if (mStringCnt + (end - cursor) > mStringLen) { + cursor += mStringLen - mStringCnt; + *cursor == '\r' ? mState = ElementLF : error = __LINE__; + } else { + mStringCnt += end - cursor; + cursor = end - 1; + } + break; + case ElementLF: + if (ch != '\n') { + error = __LINE__; + break; + } + mState = ArrayBody; + while (true) { + if (++mElementCnt[mDepth - 1] == mArrayNum[mDepth - 1]) { + if (--mDepth == 0) { + goto Done; + } + } else { + break; + } + } + break; + default: + error = __LINE__; + break; + } + ++cursor; + } + if (error) { + SString<64> bufHex; + bufHex.printHex(buf->data() + pos, buf->length() - pos); + SString<16> errHex; + errHex.printHex(cursor - 1, end - cursor + 1); + logError("response parse error %d state %d buf:%s errpos %d err:%s", + error, mState, bufHex.data(), + cursor - 1 - buf->data() - pos, errHex.data()); + return ParseError; + } + pos = cursor + 1 - buf->data(); + mRes.end().buf = buf; + mRes.end().pos = pos; + return (mState > HeadLF) ? Partial : Normal; +Done: + mState = Finished; + pos = cursor + 1 - buf->data(); + mRes.end().buf = buf; + mRes.end().pos = pos; + return Complete; +} + diff --git a/src/ResponseParser.h b/src/ResponseParser.h new file mode 100644 index 0000000..1bbab6d --- /dev/null +++ b/src/ResponseParser.h @@ -0,0 +1,86 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_RESPONSE_PARSER_H_ +#define _PREDIXY_RESPONSE_PARSER_H_ + +#include "Predixy.h" + +class ResponseParser +{ +public: + static const int MaxArrayDepth = 8; + enum State + { + Idle, + StatusHead, + ErrorHead, + IntegerHead, + StringHead, + ArrayHead, + HeadLF, + ArrayBody, + StringLen, + StringBody, + StringBodyLF, + LineBody, + SubStringLen, + SubStringLenLF, + SubStringBody, + SubStringBodyLF, + SubArrayLen, + SubArrayLenLF, + ElementLF, + Finished, + + Error + }; + enum Status + { + Normal, + Partial, + Complete, + ParseError + }; +public: + ResponseParser(); + ~ResponseParser(); + void reset(); + Status parse(Buffer* buf, int& pos); + bool isIdle() const + { + return mState == Idle; + } + Reply::Type type() const + { + return mType; + } + Segment& response() + { + return mRes; + } + const Segment& response() const + { + return mRes; + } + int64_t integer() const + { + return mInteger; + } +private: + Reply::Type mType; + Segment mRes; + State mState; + int64_t mInteger; + int mStringLen; + int mStringCnt; + int mArrayNum[MaxArrayDepth]; + int mElementCnt[MaxArrayDepth]; + int mDepth; + bool mSign; +}; + +#endif diff --git a/src/SentinelServerPool.cpp b/src/SentinelServerPool.cpp new file mode 100644 index 0000000..a1efca2 --- /dev/null +++ b/src/SentinelServerPool.cpp @@ -0,0 +1,446 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include "Logger.h" +#include "ServerGroup.h" +#include "Handler.h" +#include "SentinelServerPool.h" + +SentinelServerPool::SentinelServerPool(Proxy* p): + ServerPoolTmpl(p, Sentinel), + mDist(Distribution::Modula) +{ + mSentinels.reserve(MaxSentinelNum); + mServPool.reserve(Const::MaxServNum); + mHashTag[0] = mHashTag[1] = '\0'; +} + +SentinelServerPool::~SentinelServerPool() +{ +} + +void SentinelServerPool::init(const SentinelServerPoolConf& conf) +{ + ServerPool::init(conf); + mDist = conf.dist; + mHash = conf.hash; + mHashTag[0] = conf.hashTag[0]; + mHashTag[1] = conf.hashTag[1]; + mSentinels.resize(conf.sentinels.size()); + int i = 0; + for (auto& sc : conf.sentinels) { + Server* s = new Server(this, sc.addr, true); + s->setRole(Server::Sentinel); + s->setPassword(sc.password.empty() ? conf.password : sc.password); + mSentinels[i++] = s; + mServs[s->addr()] = s; + } + mGroups.resize(conf.groups.size()); + i = 0; + for (auto& gc : conf.groups) { + ServerGroup* g = new ServerGroup(this, gc.name); + mGroups[i++] = g; + for (auto& sc : gc.servers) { + Server* s = new Server(this, sc.addr, true); + s->setPassword(sc.password.empty() ? conf.password : sc.password); + s->setOnline(false); + mServPool.push_back(s); + mServs[s->addr()] = s; + g->add(s); + } + } +} + +Server* SentinelServerPool::getServer(Handler* h, Request* req) const +{ + FuncCallTimer(); + switch (req->type()) { + case Command::SentinelGetMaster: + case Command::SentinelSlaves: + case Command::SentinelSentinels: + if (mSentinels.empty()) { + return nullptr; + } else { + Server* s = randServer(h, mSentinels); + logDebug("sentinel server pool get server %s for sentinel command", + s->addr().data()); + return s; + } + break; + case Command::Randomkey: + return randServer(h, mServPool); + default: + break; + } + if (mGroups.size() == 1) { + return mGroups[0]->getServer(h, req); + } else if (mGroups.size() > 1) { + switch (mDist) { + case Distribution::Modula: + { + SegmentStr key(req->key()); + long idx = mHash.hash(key.data(), key.length(), mHashTag); + idx %= mGroups.size(); + return mGroups[idx]->getServer(h, req); + } + break; + case Distribution::Random: + { + int idx = h->rand() % mGroups.size(); + return mGroups[idx]->getServer(h, req); + } + break; + default: + break; + } + } + return nullptr; +} + +void SentinelServerPool::refreshRequest(Handler* h) +{ + logDebug("h %d update sentinel server pool", h->id()); + for (auto g : mGroups) { + RequestPtr req = RequestAlloc::create(); + req->setSentinels(g->name()); + req->setData(g); + h->handleRequest(req); + req = RequestAlloc::create(); + req->setSentinelGetMaster(g->name()); + req->setData(g); + h->handleRequest(req); + req = RequestAlloc::create(); + req->setSentinelSlaves(g->name()); + req->setData(g); + h->handleRequest(req); + } +} + +void SentinelServerPool::handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res) +{ + switch (req->type()) { + case Command::SentinelSentinels: + handleSentinels(h, s, req, res); + break; + case Command::SentinelGetMaster: + handleGetMaster(h, s, req, res); + break; + case Command::SentinelSlaves: + handleSlaves(h, s, req, res); + break; + default: + break; + } +} + +class AddrParser +{ +public: + enum Status { + Ok, + Error, + Done + }; +public: + AddrParser(const Segment& res): + mRes(res), + mState(Idle), + mCnt(0), + mArgLen(0), + mIp(false), + mPort(false) + { + mRes.rewind(); + } + int count() const {return mCnt;} + Status parse(SString& addr); +private: + enum State { + Idle, + Count, + CountLF, + Arg, + ArgLen, + ArgLenLF, + SubArrayLen, + Body, + BodyLF, + Invalid, + Finished + }; +private: + Segment mRes; + State mState; + int mCnt; + int mArgLen; + bool mIp; + bool mPort; + SString<4> mKey; +}; + +AddrParser::Status AddrParser::parse(SString& addr) +{ + const char* dat; + int len; + addr.clear(); + while (mRes.get(dat, len) && mState != Invalid) { + for (int i = 0; i < len && mState != Invalid; ++i) { + char ch = dat[i]; + switch (mState) { + case Idle: + mState = ch == '*' ? Count : Invalid; + break; + case Count: + if (ch >= '0' && ch <= '9') { + mCnt = mCnt * 10 + (ch - '0'); + } else if (ch == '\r') { + if (mCnt == 0) { + mState = Finished; + return Done; + } else if (mCnt < 0) { + mState = Invalid; + return Error; + } + mState = CountLF; + } else { + mState = Invalid; + } + break; + case CountLF: + mState = ch == '\n' ? Arg : Invalid; + break; + case Arg: + if (ch == '$') { + mState = ArgLen; + mArgLen = 0; + } else if (ch == '*') { + mState = SubArrayLen; + } else { + mState = Invalid; + } + break; + case ArgLen: + if (ch >= '0' && ch <= '9') { + mArgLen = mArgLen * 10 + (ch - '0'); + } else if (ch == '\r') { + mState = ArgLenLF; + } else { + mState = Invalid; + } + break; + case ArgLenLF: + mState = ch == '\n' ? Body : Invalid; + break; + case SubArrayLen: + if (ch == '\n') { + mState = Arg; + } + break; + case Body: + if (ch == '\r') { + mState = BodyLF; + if (mPort) { + mPort = false; + mRes.use(i + 1); + return Ok; + } else if (mIp) { + mIp = false; + addr.append(':'); + } else if (mArgLen == 2 && strcmp(mKey.data(), "ip") == 0) { + mIp = true; + } else if (mArgLen == 4 && strcmp(mKey.data(), "port") == 0) { + mPort = true; + } + break; + } + if (mIp || mPort) { + addr.append(ch); + } else if (mArgLen == 2 || mArgLen == 4) { + mKey.append(ch); + } + break; + case BodyLF: + mKey.clear(); + mState = ch == '\n' ? Arg : Invalid; + break; + default: + break; + } + } + mRes.use(len); + } + return mState != Invalid ? Done : Error; +} + +void SentinelServerPool::handleSentinels(Handler* h, ConnectConnection* s, Request* req, Response* res) +{ + if (!res || !res->isArray()) { + return; + } + AddrParser parser(res->body()); + SString addr; + while (true) { + auto st = parser.parse(addr); + if (st == AddrParser::Ok) { + logDebug("sentinel server pool parse sentinel %s", addr.data()); + auto it = mServs.find(addr); + Server* serv = it == mServs.end() ? nullptr : it->second; + if (!serv) { + if (mSentinels.size() == mSentinels.capacity()) { + logWarn("too many sentinels %d, will ignore new sentinel %s", + (int)mSentinels.size(), addr.data()); + continue; + } + serv = new Server(this, addr, false); + serv->setRole(Server::Sentinel); + serv->setPassword(password()); + mSentinels.push_back(serv); + mServs[serv->addr()] = serv; + logNotice("h %d create new sentinel %s", + h->id(), addr.data()); + } + serv->setOnline(true); + } else if (st == AddrParser::Done) { + break; + } else { + logError("sentinel server pool parse sentinel sentinels error"); + break; + } + } +} + +void SentinelServerPool::handleGetMaster(Handler* h, ConnectConnection* s, Request* req, Response* res) +{ + if (!res || !res->isArray()) { + return; + } + ServerGroup* g = (ServerGroup*)req->data(); + if (!g) { + return; + } + SegmentStr str(res->body()); + if (!str.complete()) { + return; + } + if (strncmp(str.data(), "*2\r\n$", 5) != 0) { + return; + } + SString addr; + const char* p = str.data() + 5; + int len = atoi(p); + if (len <= 0) { + return; + } + p = strchr(p, '\r') + 2; + if (!addr.append(p, len)) { + return; + } + if (!addr.append(':')) { + return; + } + p += len + 3; + len = atoi(p); + if (len <= 0) { + return; + } + p = strchr(p, '\r') + 2; + if (!addr.append(p, len)) { + return; + } + logDebug("sentinel server pool group %s get master %s", + g->name().data(), addr.data()); + auto it = mServs.find(addr); + Server* serv = it == mServs.end() ? nullptr : it->second; + if (serv) { + serv->setOnline(true); + serv->setRole(Server::Master); + auto old = serv->group(); + if (old) { + if (old != g) { + old->remove(serv); + g->add(serv); + serv->setGroup(g); + } + } else { + g->add(serv); + serv->setGroup(g); + } + } else { + if (mServPool.size() == mServPool.capacity()) { + logWarn("too many servers %d, will ignore new master server %s", + (int)mServPool.size(), addr.data()); + return; + } + serv = new Server(this, addr, false); + serv->setRole(Server::Master); + serv->setPassword(password()); + mServPool.push_back(serv); + g->add(serv); + serv->setGroup(g); + mServs[serv->addr()] = serv; + logNotice("sentinel server pool group %s create master server %s %s", + g->name().data(), addr.data(), serv->dcName().data()); + } +} + +void SentinelServerPool::handleSlaves(Handler* h, ConnectConnection* s, Request* req, Response* res) +{ + if (!res || !res->isArray()) { + return; + } + ServerGroup* g = (ServerGroup*)req->data(); + if (!g) { + return; + } + AddrParser parser(res->body()); + SString addr; + while (true) { + auto st = parser.parse(addr); + if (st == AddrParser::Ok) { + logDebug("sentinel server pool group %s parse slave %s", + g->name().data(), addr.data()); + auto it = mServs.find(addr); + Server* serv = it == mServs.end() ? nullptr : it->second; + if (serv) { + serv->setOnline(true); + serv->setRole(Server::Slave); + auto old = serv->group(); + if (old) { + if (old != g) { + old->remove(serv); + g->add(serv); + serv->setGroup(g); + } + } else { + g->add(serv); + serv->setGroup(g); + } + } else { + if (mServPool.size() == mServPool.capacity()) { + logWarn("too many servers %d, will ignore new slave server %s", + (int)mServPool.size(), addr.data()); + return; + } + serv = new Server(this, addr, false); + serv->setRole(Server::Slave); + serv->setPassword(password()); + mServPool.push_back(serv); + g->add(serv); + serv->setGroup(g); + mServs[serv->addr()] = serv; + logNotice("sentinel server pool group %s create slave server %s %s", + g->name().data(), addr.data(), serv->dcName().data()); + } + } else if (st == AddrParser::Done) { + break; + } else { + logError("sentinel server pool group %s parse sentinel sentinels error", + g->name().data()); + break; + } + } +} diff --git a/src/SentinelServerPool.h b/src/SentinelServerPool.h new file mode 100644 index 0000000..2a6acd9 --- /dev/null +++ b/src/SentinelServerPool.h @@ -0,0 +1,43 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_SENTINEL_SERVER_POOL_H_ +#define _PREDIXY_SENTINEL_SERVER_POOL_H_ + +#include +#include "Predixy.h" +#include "ServerPool.h" + +class SentinelServerPool : public ServerPoolTmpl +{ +public: + static const int MaxSentinelNum = 64; +public: + SentinelServerPool(Proxy* p); + ~SentinelServerPool(); + void init(const SentinelServerPoolConf& conf); + Server* getServer(Handler* h, Request* req) const; + Server* iter(int& cursor) const + { + return ServerPool::iter(mServPool, cursor); + } + void refreshRequest(Handler* h); + void handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res); +private: + void handleSentinels(Handler* h, ConnectConnection* s, Request* req, Response* res); + void handleGetMaster(Handler* h, ConnectConnection* s, Request* req, Response* res); + void handleSlaves(Handler* h, ConnectConnection* s, Request* req, Response* res); + friend class ServerPoolTmpl; +private: + std::vector mSentinels; + std::vector mServPool; + std::vector mGroups; + Distribution mDist; + Hash mHash; + char mHashTag[2]; +}; + +#endif diff --git a/src/Server.cpp b/src/Server.cpp new file mode 100644 index 0000000..0da0e20 --- /dev/null +++ b/src/Server.cpp @@ -0,0 +1,57 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Proxy.h" +#include "Server.h" +#include "ServerPool.h" + +const char* Server::RoleStr[] = { + "unknown", + "master", + "slave", + "sentinel" +}; + +Server::Server(ServerPool* pool, const String& addr, bool isStatic): + mPool(pool), + mGroup(nullptr), + mRole(Unknown), + mDC(nullptr), + mAddr(addr), + mNextActivateTime(0), + mFailureCnt(0), + mStatic(isStatic), + mFail(false), + mOnline(true), + mUpdating(false) +{ + if (auto dataCenter = pool->proxy()->dataCenter()) { + mDC = dataCenter->getDC(addr); + } +} + +Server::~Server() +{ +} + +bool Server::activate() +{ + long v = mNextActivateTime; + long now = Util::nowUSec(); + if (now < v) { + return false; + } + return AtomicCAS(mNextActivateTime, v, now + mPool->serverRetryTimeout()); +} + +void Server::incrFail() +{ + long cnt = ++mFailureCnt; + if (cnt % mPool->serverFailureLimit() == 0) { + setFail(true); + } +} + diff --git a/src/Server.h b/src/Server.h new file mode 100644 index 0000000..328d5ab --- /dev/null +++ b/src/Server.h @@ -0,0 +1,144 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_SERVER_H_ +#define _PREDIXY_SERVER_H_ + +#include +#include "Predixy.h" +#include "DC.h" +#include "ConnectConnection.h" + +class Server : public ID +{ +public: + enum Role + { + Unknown, + Master, + Slave, + Sentinel + }; + static const char* RoleStr[]; +public: + Server(ServerPool* pool, const String& addr, bool isStatic); + ~Server(); + bool activate(); + void incrFail(); + ServerPool* pool() const + { + return mPool; + } + ServerGroup* group() const + { + return mGroup; + } + void setGroup(ServerGroup* g) + { + mGroup = g; + } + void setRole(Role role) + { + mRole = role; + } + Role role() const + { + return mRole; + } + const char* roleStr() const + { + return RoleStr[mRole]; + } + bool isMaster() const + { + return mRole == Master; + } + bool isSlave() const + { + return mRole == Slave; + } + bool isStatic() const + { + return mStatic; + } + const String& name() const + { + return mName; + } + void setName(const String& str) + { + mName = str; + } + const String& masterName() const + { + return mMasterName; + } + void setMasterName(const String& str) + { + mMasterName = str; + } + const String& addr() const + { + return mAddr; + } + const String& password() const + { + return mPassword; + } + void setPassword(const String& pw) + { + mPassword = pw; + } + DC* dc() const + { + return mDC; + } + String dcName() const + { + return mDC ? mDC->name() : String("", 0); + } + bool fail() const + { + return mFail; + } + void setFail(bool v) + { + mFail = v; + } + bool online() const + { + return mOnline; + } + void setOnline(bool v) + { + mOnline = v; + } + bool updating() const + { + return mUpdating; + } + void setUpdating(bool v) + { + mUpdating = v; + } +private: + ServerPool* mPool; + ServerGroup* mGroup; + Role mRole; + DC* mDC; + SString mAddr; + SString mName; + SString mMasterName; + String mPassword; + AtomicLong mNextActivateTime; //us + AtomicLong mFailureCnt; + bool mStatic; + bool mFail; + bool mOnline; + bool mUpdating; +}; + +#endif diff --git a/src/ServerGroup.cpp b/src/ServerGroup.cpp new file mode 100644 index 0000000..399a2d5 --- /dev/null +++ b/src/ServerGroup.cpp @@ -0,0 +1,303 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include "DC.h" +#include "Proxy.h" +#include "ServerPool.h" +#include "ServerGroup.h" + +ServerGroup::ServerGroup(ServerPool* pool, const String& name): + mPool(pool), + mName(name) +{ + mServs.reserve(Const::MaxServInGroup); +} + +ServerGroup::~ServerGroup() +{ +} + +Server* ServerGroup::getMaster() const +{ + FuncCallTimer(); + int cnt = mServs.size(); + for (int i = 0; i < cnt; ++i) { + Server* s = mServs[i]; + if (!s->online()) { + continue; + } + if (s->role() == Server::Master) { + return s; + } + } + return nullptr; +} + +struct ServCond +{ + Server* serv = nullptr; + int priority = 0; +}; + +Server* ServerGroup::getServer(Handler* h, Request* req) const +{ + FuncCallTimer(); + Server* serv = nullptr; + if (req->requireWrite()) { + int cnt = mServs.size(); + for (int i = 0; i < cnt; ++i) { + Server* s = mServs[i]; + if (!s->online()) { + continue; + } + if (s->role() == Server::Master) { + serv = s; + } + } + } else if (auto dataCenter = mPool->proxy()->dataCenter()) { + serv = getReadServer(h, dataCenter->localDC()); + } else { + serv = getReadServer(h); + } + logDebug("server group %s for req %ld get server %s", + mName.data(), req->id(), (serv ? serv->addr().data() : "None")); + return serv; +} + +Server* ServerGroup::getReadServer(Handler* h) const +{ + FuncCallTimer(); + Server* servs[Const::MaxServInGroup]; + Server* deadServs[Const::MaxServInGroup]; + int n = 0; + int dn = 0; + int prior = 0; + int dprior = 0; + int pendRequests = INT_MAX; + int cnt = mServs.size(); + for (int i = 0; i < cnt; ++i) { + Server* s = mServs[i]; + if (!s->online()) { + continue; + } + int rp = 0; + if (s->role() == Server::Master) { + rp = mPool->masterReadPriority(); + } else if (s->role() == Server::Slave) { + rp = s->isStatic() ? mPool->staticSlaveReadPriority() : mPool->dynamicSlaveReadPriority(); + } + if (rp <= 0) { + continue; + } + if (s->fail()) { + if (rp > dprior) { + dprior = rp; + deadServs[0] = s; + dn = 1; + } else if (rp == dprior) { + deadServs[dn++] = s; + } + } else { + if (rp > prior) { + prior = rp; + pendRequests = h->getPendRequests(s); + servs[0] = s; + n = 1; + } else if (rp == prior) { + int preqs = h->getPendRequests(s); + if (preqs < pendRequests) { + pendRequests = preqs; + servs[0] = s; + n = 1; + } else if (preqs == pendRequests) { + servs[n++] = s; + } + } + } + } + Server** ss = nullptr; + if (n > 0) { + ss = servs; + } else if (dn > 0) { + ss = deadServs; + n = dn; + } else { + return nullptr; + } + n = h->rand() % n; + return ss[n]; +} + +Server* ServerGroup::getReadServer(Handler* h, DC* localDC) const +{ + FuncCallTimer(); + Server* servs[Const::MaxServInGroup]; + int sprior[Const::MaxServInGroup]; + int pendRequests[Const::MaxServInGroup]; + int num = 0; + DC* dcs[Const::MaxServInGroup]; + DC* deadDCs[Const::MaxServInGroup]; + int n = 0; + int dn = 0; + int prior = 0; + int dprior = 0; + int cnt = mServs.size(); + for (int i = 0; i < cnt; ++i) { + Server* s = mServs[i]; + if (!s->online()) { + continue; + } + int rp = 0; + if (s->role() == Server::Master) { + rp = mPool->masterReadPriority(); + } else if (s->role() == Server::Slave) { + rp = s->isStatic() ? mPool->staticSlaveReadPriority() : mPool->dynamicSlaveReadPriority(); + } + if (rp <= 0) { + continue; + } + DC* dc = s->dc(); + int dcrp = localDC->getReadPriority(dc); + if (dcrp <= 0) { + continue; + } + servs[num] = s; + sprior[num] = rp; + pendRequests[num++] = h->getPendRequests(s); + if (s->fail()) { + if (dcrp > dprior) { + dprior = dcrp; + deadDCs[0] = dc; + dn = 1; + } else if (dcrp == dprior) { + deadDCs[dn++] = dc; + } + } else { + if (dcrp > prior) { + prior = dcrp; + dcs[0] = dc; + n = 1; + } else if (dcrp == prior) { + dcs[n++] = dc; + } + } + } + DC** sdc = nullptr; + if (n > 0) { + sdc = dcs; + } else if (dn > 0) { + sdc = deadDCs; + n = dn; + } else { + return nullptr; + } + bool found = false; + DC* dc = nullptr; + if (n > 1) { + int m = h->idUnique().unique(sdc, n); + int weights[Const::MaxServInGroup]; + int w = 0; + n = 0; + for (int i = 0; i < m; ++i) { + int dw = localDC->getReadWeight(sdc[i]); + if (dw > 0) { + w += dw; + sdc[n] = sdc[i]; + weights[n++] = w; + } + } + if (w > 0) { + w = h->rand() % w; + int i = std::lower_bound(weights, weights + n, w) - weights; + dc = sdc[i]; + found = true; + } + } else if (localDC->getReadWeight(sdc[0]) > 0) { + dc = sdc[0]; + found = true; + } + if (!found) {//dc maybe nullptr even we found + return nullptr; + } + Server* deadServs[Const::MaxServInGroup]; + n = 0; + dn = 0; + prior = 0; + dprior = 0; + int preqs = INT_MAX; + for (int i = 0; i < num; ++i) { + Server* s = servs[i]; + if (servs[i]->dc() != dc) { + continue; + } + if (s->fail()) { + if (sprior[i] > dprior) { + dprior = sprior[i]; + deadServs[0] = s; + dn = 1; + } else if (sprior[i] == dprior) { + deadServs[dn++] = s; + } + } else { + if (sprior[i] > prior) { + prior = sprior[i]; + preqs = pendRequests[i]; + servs[0] = s; + n = 1; + } else if (sprior[i] == prior) { + if (pendRequests[i] < preqs) { + preqs = pendRequests[i]; + servs[0] = s; + n = 1; + } else if (pendRequests[i] == preqs) { + servs[n++] = s; + } + } + } + } + Server** ss = nullptr; + if (n > 0) { + ss = servs; + } else if (dn > 0) { + ss = deadServs; + n = dn; + } else { + return nullptr; + } + n = h->rand() % n; + return ss[n]; +} + +/*************************************************** + * other thread may read ServerGroup when add or remoev, + * but we don't use lock + * + * + **************************************************/ +void ServerGroup::add(Server* s) +{ + if (mServs.size() == mServs.capacity()) { + return; + } + for (unsigned i = 0; i < mServs.size(); ++i) { + if (mServs[i] == s) { + return; + } + } + mServs.push_back(s); +} + +void ServerGroup::remove(Server* s) +{ + for (unsigned i = 0; i < mServs.size(); ++i) { + if (mServs[i] == s) { + mServs[i] = mServs.back(); + mServs.resize(mServs.size() - 1); + return; + } + } +} diff --git a/src/ServerGroup.h b/src/ServerGroup.h new file mode 100644 index 0000000..d908cdc --- /dev/null +++ b/src/ServerGroup.h @@ -0,0 +1,37 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_SERVER_GROUP_H_ +#define _PREDIXY_SERVER_GROUP_H_ + +#include +#include +#include +#include "Server.h" +#include "String.h" +#include "Predixy.h" + +class ServerGroup : public ID +{ +public: + ServerGroup(ServerPool* pool, const String& name); + ~ServerGroup(); + ServerPool* pool() const {return mPool;} + String name() const {return mName;} + Server* getMaster() const; + Server* getServer(Handler* h, Request* req) const; + void add(Server* s); + void remove(Server* s); +private: + Server* getReadServer(Handler* h) const; + Server* getReadServer(Handler* h, DC* localDC) const; +private: + ServerPool* mPool; + SString mName; + std::vector mServs; +}; + +#endif diff --git a/src/ServerPool.cpp b/src/ServerPool.cpp new file mode 100644 index 0000000..45e4f79 --- /dev/null +++ b/src/ServerPool.cpp @@ -0,0 +1,77 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Server.h" +#include "ServerPool.h" +#include "Handler.h" + +ServerPool::~ServerPool() +{ +} + +void ServerPool::init(const ServerPoolConf& conf) +{ + mPassword = conf.password; + mMasterReadPriority = conf.masterReadPriority; + mStaticSlaveReadPriority = conf.staticSlaveReadPriority; + mDynamicSlaveReadPriority = conf.dynamicSlaveReadPriority; + mRefreshInterval = conf.refreshInterval * 1000000; + mServerFailureLimit = conf.serverFailureLimit; + mServerRetryTimeout = conf.serverRetryTimeout * 1000000; + mDbNum = conf.databases; +} + +bool ServerPool::refresh() +{ + long last = mLastRefreshTime; + long now = Util::nowUSec(); + if (now - last < mRefreshInterval) { + return false; + } + return AtomicCAS(mLastRefreshTime, last, now); +} + +void ServerPool::handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res) +{ + UniqueLock lck(mMtx, TryLockTag); + if (!lck) { + logNotice("server pool is updating by other thread"); + return; + } + mHandleResponseFunc(this, h, s, req, res); +} + +Server* ServerPool::randServer(Handler* h, const std::vector& servs) +{ + Server* s = nullptr; + int idx = h->rand() % servs.size(); + for (size_t i = 0; i < servs.size(); ++i) { + Server* serv = servs[idx++ % servs.size()]; + if (!serv->online()) { + continue; + } else if (serv->fail()) { + s = serv; + } else { + return serv; + } + } + return s; +} + +Server* ServerPool::iter(const std::vector& servs, int& cursor) +{ + int size = servs.size(); + if (cursor < 0 || cursor >= size) { + return nullptr; + } + while (cursor < size) { + Server* serv = servs[cursor++]; + if (serv->online()) { + return serv; + } + } + return nullptr; +} diff --git a/src/ServerPool.h b/src/ServerPool.h new file mode 100644 index 0000000..b6c9df8 --- /dev/null +++ b/src/ServerPool.h @@ -0,0 +1,169 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_SERVER_POOL_H_ +#define _PREDIXY_SERVER_POOL_H_ + +#include +#include +#include "Predixy.h" + +#include + +class ServerPool +{ +public: + enum Type + { + Unknown, + Cluster, + Sentinel + }; + static const int DefaultServerRetryTimeout = 10000000; + static const int DefaultRefreshInterval = 1000000; +public: + virtual ~ServerPool(); + void init(const ServerPoolConf& conf); + Proxy* proxy() const + { + return mProxy; + } + bool refresh(); + int type() const + { + return mType; + } + const String& password() const + { + return mPassword; + } + int masterReadPriority() const + { + return mMasterReadPriority; + } + int staticSlaveReadPriority() const + { + return mStaticSlaveReadPriority; + } + int dynamicSlaveReadPriority() const + { + return mDynamicSlaveReadPriority; + } + long refreshInterval() const + { + return mRefreshInterval; + } + int serverFailureLimit() const + { + return mServerFailureLimit; + } + long serverRetryTimeout() const + { + return mServerRetryTimeout; + } + int dbNum() const + { + return mDbNum; + } + ServerGroup* getGroup(int idx) + { + return idx < (int)mGroupPool.size() ? mGroupPool[idx] : nullptr; + } + Server* getServer(const String& addr) + { + UniqueLock lck(mMtx); + auto it = mServs.find(addr); + return it == mServs.end() ? nullptr : it->second; + } + Server* getServer(Handler* h, Request* req) const + { + return mGetServerFunc(this, h, req); + } + Server* iter(int& cursor) const + { + return mIterFunc(this, cursor); + } + void refreshRequest(Handler* h) + { + mRefreshRequestFunc(this, h); + } + void handleResponse(Handler* h, ConnectConnection* s, Request* req, Response* res); +protected: + typedef Server* (*GetServerFunc)(const ServerPool* p, Handler* h, Request* req); + typedef Server* (*IterFunc)(const ServerPool* p, int& cursor); + typedef void (*RefreshRequestFunc)(ServerPool* p, Handler* h); + typedef void (*HandleResponseFunc)(ServerPool* p, Handler* h, ConnectConnection* s, Request* req, Response* res); + template + ServerPool(Proxy* p, int type, T* sub = nullptr): + mProxy(p), + mType(type), + mGetServerFunc(T::getServer), + mIterFunc(T::iter), + mRefreshRequestFunc(T::refreshRequest), + mHandleResponseFunc(T::handleResponse), + mLastRefreshTime(0), + mMasterReadPriority(50), + mStaticSlaveReadPriority(0), + mDynamicSlaveReadPriority(0), + mRefreshInterval(DefaultRefreshInterval), + mServerFailureLimit(10), + mServerRetryTimeout(DefaultServerRetryTimeout), + mDbNum(1) + { + } + static Server* randServer(Handler* h, const std::vector& servs); + static Server* iter(const std::vector& servs, int& cursor); +protected: + std::map mServs; + std::vector mGroupPool; +private: + Proxy* mProxy; + int mType; + GetServerFunc mGetServerFunc; + IterFunc mIterFunc; + RefreshRequestFunc mRefreshRequestFunc; + HandleResponseFunc mHandleResponseFunc; + AtomicLong mLastRefreshTime; + Mutex mMtx; + String mPassword; + int mMasterReadPriority; + int mStaticSlaveReadPriority; + int mDynamicSlaveReadPriority; + long mRefreshInterval; + int mServerFailureLimit; + long mServerRetryTimeout; + int mDbNum; +}; + +template +class ServerPoolTmpl : public ServerPool +{ +public: + ServerPoolTmpl(Proxy* p, int type): + ServerPool(p, type, (ServerPoolTmpl*)this) + { + } +private: + static Server* getServer(const ServerPool* p, Handler* h, Request* req) + { + return static_cast(p)->getServer(h, req); + } + static Server* iter(const ServerPool* p, int& cursor) + { + return static_cast(p)->iter(cursor); + } + static void refreshRequest(ServerPool* p, Handler* h) + { + static_cast(p)->refreshRequest(h); + } + static void handleResponse(ServerPool* p, Handler* h, ConnectConnection* s, Request* req, Response* res) + { + static_cast(p)->handleResponse(h, s, req, res); + } + friend class ServerPool; +}; + +#endif diff --git a/src/Socket.cpp b/src/Socket.cpp new file mode 100644 index 0000000..cf0ec74 --- /dev/null +++ b/src/Socket.cpp @@ -0,0 +1,212 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Socket.h" +#include "Util.h" +#include "Logger.h" +#include "Timer.h" + +Socket::Socket(int fd): + mClassType(RawType), + mFd(fd), + mEvts(0), + mStatus(fd >= 0 ? Normal : None) +{ +} + +Socket::Socket(int domain, int type, int protocol): + mClassType(RawType), + mFd(Socket::socket(domain, type, protocol)), + mStatus(Normal) +{ +} + +Socket::~Socket() +{ + close(); +} + +void Socket::close() +{ + if (mFd >= 0) { + ::close(mFd); + mFd = -1; + mEvts = 0; + mStatus = None; + } +} + +void Socket::attach(int fd) +{ + close(); + mFd = fd; + mEvts = 0; + mStatus = fd >= 0 ? Normal : None; +} + +void Socket::detach() +{ + mFd = -1; + mEvts = 0; + mStatus = None; +} + +const char* Socket::statusStr() const +{ + static const char* strs[] = { + "Normal", + "None", + "End", + "IOError", + "EventError", + "ExceptError" + }; + return mStatus < CustomStatus ? strs[mStatus] : "Custom"; +} + +int Socket::socket(int domain, int type, int protocol) +{ + int fd = ::socket(domain, type, protocol); + if (fd < 0) { + Throw(SocketCallFail, "%s", StrError()); + } + return fd; +} + +void Socket::getFirstAddr(const char* addr, int type, int protocol, sockaddr* res, socklen_t* len) +{ + if (*addr == '/') { //unix socket + struct sockaddr_un sun; + memset(&sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; + strncpy(sun.sun_path, addr, sizeof(sun.sun_path)); + if (*len < sizeof(sun)) { + *len = sizeof(sun); + Throw(AddrLenTooShort, "result address space too short"); + } + *len = sizeof(sun); + memcpy(res, &sun, *len); + } else { + std::string tmp; + const char* host = addr; + const char* port = strchr(addr, ':'); + if (port) { + tmp.append(addr, port - addr); + host = tmp.c_str(); + port++; + } + struct addrinfo hints; + struct addrinfo *dst; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = type; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = protocol; + int s = getaddrinfo(host, port, &hints, &dst); + if (s != 0) { + Throw(InvalidAddr, "invalid addr %s:%s", addr, gai_strerror(s)); + } + if (!dst) { + Throw(InvalidAddr, "invalid addr %s", addr); + } + if (*len < dst->ai_addrlen) { + *len = dst->ai_addrlen; + Throw(AddrLenTooShort, "result address space too short"); + } + *len = dst->ai_addrlen; + memcpy(res, dst->ai_addr, *len); + freeaddrinfo(dst); + } +} + +bool Socket::setNonBlock(bool val) +{ + int flags = fcntl(mFd, F_GETFL, NULL); + if (flags < 0) { + return false; + } + if (val) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + int ret = fcntl(mFd, F_SETFL, flags); + return ret == 0; +} + +bool Socket::setTcpNoDelay(bool val) +{ + int nodelay = val ? 1 : 0; + socklen_t len = sizeof(nodelay); + int ret = setsockopt(mFd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); + return ret == 0; +} + +int Socket::read(void* buf, int cnt) +{ + FuncCallTimer(); + while (cnt > 0) { + int n = ::read(mFd, buf, cnt); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; + } else if (errno == EINTR) { + continue; + } + mStatus = IOError; + } else if (n == 0) { + mStatus = End; + } + return n; + } + return 0; +} + +int Socket::write(const void* buf, int cnt) +{ + FuncCallTimer(); + while (cnt > 0) { + int n = ::write(mFd, buf, cnt); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + break; + } else if (errno == EINTR) { + continue; + } + mStatus = IOError; + } + return n; + } + return 0; +} + +int Socket::writev(const struct iovec* vecs, int cnt) +{ + FuncCallTimer(); + while (cnt > 0) { + int n = ::writev(mFd, vecs, cnt); + if (n < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; + } else if (errno == EINTR) { + continue; + } + mStatus = IOError; + } + return n; + } + return 0; +} diff --git a/src/Socket.h b/src/Socket.h new file mode 100644 index 0000000..4a730bc --- /dev/null +++ b/src/Socket.h @@ -0,0 +1,105 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_SOCKET_H_ +#define _PREDIXY_SOCKET_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Exception.h" + +class Socket +{ +public: + DefException(SocketCallFail); + DefException(InvalidAddr); + DefException(AddrLenTooShort); + + enum ClassType + { + RawType, + ListenType, + AcceptType, + ConnectType, + + CustomType = 1024 + }; + enum StatusCode + { + Normal = 0, + None, + End, + IOError, + EventError, + ExceptError, + + CustomStatus + }; +public: + Socket(int fd = -1); + Socket(int domain, int type, int protocol = 0); + Socket(const Socket&) = delete; + Socket(const Socket&&) = delete; + ~Socket(); + void attach(int fd); + void detach(); + void close(); + bool setNonBlock(bool val = true); + bool setTcpNoDelay(bool val = true); + int read(void* buf, int cnt); + int write(const void* buf, int cnt); + int writev(const struct iovec* vecs, int cnt); + int classType() const + { + return mClassType; + } + int fd() const + { + return mFd; + } + bool good() const + { + return mStatus == Normal; + } + int status() const + { + return mStatus; + } + const char* statusStr() const; + void setStatus(int st) + { + mStatus = st; + } + int getEvent() const + { + return mEvts; + } + void setEvent(int evts) + { + mEvts = evts; + } +public: + static int socket(int domain, int type, int protocol); + static void getFirstAddr(const char* addr, int type, int protocol, sockaddr* res, socklen_t* len); +protected: + int mClassType; +private: + int mFd; + int mEvts; + int mStatus; +}; + +#endif diff --git a/src/Stats.h b/src/Stats.h new file mode 100644 index 0000000..e341038 --- /dev/null +++ b/src/Stats.h @@ -0,0 +1,79 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_STATS_H_ +#define _PREDIXY_STATS_H_ + + +struct HandlerStats +{ + long accept = 0; + long clientConnections = 0; + long requests = 0; + long responses = 0; + long recvClientBytes = 0; + long sendClientBytes = 0; + long sendServerBytes = 0; + long recvServerBytes = 0; + + HandlerStats& operator+=(const HandlerStats& oth) + { + accept += oth.accept; + clientConnections += oth.clientConnections; + requests += oth.requests; + responses += oth.responses; + recvClientBytes += oth.recvClientBytes; + sendClientBytes += oth.sendClientBytes; + sendServerBytes += oth.sendServerBytes; + recvServerBytes += oth.recvServerBytes; + + return *this; + } + void reset() + { + accept = 0; + requests = 0; + responses = 0; + recvClientBytes = 0; + sendClientBytes = 0; + sendServerBytes = 0; + recvServerBytes = 0; + } +}; + +struct ServerStats +{ + int connections = 0; + long connect = 0; + long close = 0; + long requests = 0; + long responses = 0; + long sendBytes = 0; + long recvBytes = 0; + + ServerStats& operator+=(const ServerStats& oth) + { + connections += oth.connections; + connect += oth.connect; + close += oth.close; + requests += oth.requests; + responses += oth.responses; + sendBytes += oth.sendBytes; + recvBytes += oth.recvBytes; + return *this; + } + void reset() + { + connect = 0; + close = 0; + requests = 0; + responses = 0; + sendBytes = 0; + recvBytes = 0; + } +}; + +#endif diff --git a/src/String.h b/src/String.h new file mode 100644 index 0000000..2dadae7 --- /dev/null +++ b/src/String.h @@ -0,0 +1,325 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_STRING_h_ +#define _PREDIXY_STRING_h_ + +#include +#include +#include +#include +#include + +class String +{ +public: + String(): + mLen(0), + mDat(nullptr) + { + } + String(const char* str): + mLen(strlen(str)), + mDat(str) + { + } + String(const char* dat, int len): + mLen(len), + mDat(dat) + { + } + String(const std::string& s): + mLen(s.size()), + mDat(s.c_str()) + { + } + String(const String& str): + mLen(str.mLen), + mDat(str.mDat) + { + } + String(String&& str): + mLen(str.mLen), + mDat(str.mDat) + { + } + String& operator=(const String& str) + { + if (this != &str) { + mDat = str.mDat; + mLen = str.mLen; + } + return *this; + } + String& operator=(const std::string& str) + { + mDat = str.c_str(); + mLen = str.size(); + return *this; + } + String& operator=(const char* str) + { + mDat = str; + mLen = str ? strlen(str) : 0; + return *this; + } + operator const char*() const + { + return mDat; + } + bool operator<(const String& s) const + { + int r = strncmp(mDat, s.mDat, mLen < s.mLen ? mLen : s.mLen); + return r < 0 || (r == 0 && mLen < s.mLen); + } + bool operator==(const char* s) const + { + return s ? (mLen == (int)strlen(s) && strncmp(mDat, s, mLen) == 0) : mLen == 0; + } + bool operator==(const String& s) const + { + return mLen == s.mLen && (mLen > 0 ? (strncmp(mDat, s.mDat, mLen) == 0) : true); + } + bool operator!=(const String& s) const + { + return !operator==(s); + } + bool equal(const String& s, bool ignoreCase= false) const + { + if (ignoreCase) { + return mLen == s.mLen && (mLen > 0 ? (strncasecmp(mDat, s.mDat, mLen) == 0) : true); + } + return mLen == s.mLen && (mLen > 0 ? (strncmp(mDat, s.mDat, mLen) == 0) : true); + } + bool empty() const + { + return mLen == 0; + } + const char* data() const + { + return mDat; + } + int length() const + { + return mLen; + } + bool hasPrefix(const String& s) const + { + return s.mLen <= mLen ? (memcmp(mDat, s.mDat, s.mLen) == 0): false; + } + String commonPrefix(const String& s) const + { + int cnt = 0; + int len = mLen < s.mLen ? mLen : s.mLen; + while (cnt < len && mDat[cnt] == s.mDat[cnt]) { + ++cnt; + } + return String(mDat, cnt); + } + bool toInt(int& v) const + { + v = 0; + int i = 0; + int sign = 1; + if (i > 0) { + if (mDat[0] == '+') { + ++i; + } else if (mDat[0] == '+') { + sign = -1; + ++i; + } else { + return false; + } + } + for ( ; i < mLen; ++i) { + if (mDat[i] >= '0' && mDat[i] <= '9') { + v = v * 10 + (mDat[i] - '0'); + } else { + return false; + } + } + v *= sign; + return true; + } +protected: + int mLen; + const char* mDat; +}; + +class StringCaseCmp +{ +public: + bool operator()(const String& s1, const String& s2) const + { + int r = strncasecmp(s1.data(), s2.data(), s1.length() < s2.length() ? s1.length() : s2.length()); + return r < 0 || (r == 0 && s1.length() < s2.length()); + } +}; + +template +class SString : public String +{ +public: + SString(): + String(mBuf, 0) + { + mBuf[0] = '\0'; + } + SString(const char* str): + String(mBuf, 0) + { + set(str); + } + SString(const char* dat, int len): + String(mBuf, 0) + { + set(dat, len); + } + SString(const std::string& str) + { + set(str.c_str(), str.length()); + } + SString(const SString& str) + { + set(str.mDat, str.mLen); + } + template + SString(const T& str) + { + set(str.data(), str.length()); + } + SString& operator=(const SString& str) + { + if (this != &str) { + set(str.data(), str.length()); + } + return *this; + } + template + SString& operator=(const T& str) + { + set(str.data(), str.length()); + return *this; + } + void clear() + { + mLen = 0; + mBuf[0] = '\0'; + } + int size() const + { + return Size; + } + bool set(const char* str) + { + return set(str, str ? strlen(str) : 0); + } + bool set(const char* dat, int len) + { + mDat = mBuf; + if ((mLen = len < Size ? len : Size) > 0 ) { + memcpy(mBuf, dat, mLen); + } + mBuf[mLen < 0 ? 0 : mLen] = '\0'; + return len <= Size; + } + bool printf(const char* fmt, ...) + { + va_list ap; + va_start(ap, fmt); + bool ret = vprintf(fmt, ap); + va_end(ap); + return ret; + } + bool vprintf(const char* fmt, va_list ap) + { + mDat = mBuf; + int len = vsnprintf(mBuf, Size, fmt, ap); + mLen = len < Size ? len : Size; + return mLen == len; + } + bool strftime(const char* fmt, time_t t) + { + struct tm m; + localtime_r(&t, &m); + int ret = ::strftime(mBuf, Size, fmt, &m); + return ret > 0 && ret < Size; + } + bool append(char c) + { + if (mLen < Size) { + if (mLen < 0) { + mLen = 0; + } + mBuf[mLen++] = c; + mBuf[mLen] = '\0'; + return true; + } + return false; + } + bool append(const char* str) + { + return append(str, strlen(str)); + } + bool append(const char* dat, int len) + { + if (dat && len >= 0) { + if (mLen < 0) { + mLen = 0; + } + int room = Size - mLen; + memcpy(mBuf + mLen, dat, len < room ? len : room); + mLen += len < room ? len : room; + mBuf[mLen] = '\0'; + return len <= room; + } + return true; + } + bool printHex(const char* dat, int len) + { + mDat = mBuf; + int i; + int n = 0; + for (i = 0; i < len && n < Size; ++i) { + switch (dat[i]) { + case '\r': + if (n + 1 < Size) { + mBuf[n++] = '\\'; + mBuf[n++] = 'r'; + } else { + i = len; + } + break; + case '\n': + if (n + 1 < Size) { + mBuf[n++] = '\\'; + mBuf[n++] = 'n'; + } else { + i = len; + } + break; + default: + if (isgraph(dat[i])) { + mBuf[n++] = dat[i]; + } else if (n + 4 < Size) { + snprintf(mBuf + n, Size - n, "\\x%02X", (int)dat[i]); + n += 4; + } else { + i = len; + } + break; + } + } + mBuf[n] = '\0'; + mLen = n; + return mLen < Size; + } +protected: + char mBuf[Size + 1]; +}; + + +#endif diff --git a/src/Subscribe.cpp b/src/Subscribe.cpp new file mode 100644 index 0000000..47c2b60 --- /dev/null +++ b/src/Subscribe.cpp @@ -0,0 +1,59 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include "Subscribe.h" + +Subscribe::Subscribe(): + mPendSub(0), + mSub(0) +{ +} + + + +SubscribeParser::Status SubscribeParser::parse(const Segment& body, int& chs) +{ + SegmentStr<128> str(body); + Status st = Unknown; + chs = 0; + if (str.hasPrefix("*3\r\n$7\r\nmessage\r\n")) { + st = Message; + } else if (str.hasPrefix("*4\r\n$8\r\npmessage\r\n")) { + st = Pmessage; + } else if (str.hasPrefix("*3\r\n$9\r\nsubscribe\r\n")) { + st = Subscribe; + chs = -1; + } else if (str.hasPrefix("*3\r\n$10\r\npsubscribe\r\n")) { + st = Psubscribe; + chs = -1; + } else if (str.hasPrefix("*3\r\n$11\r\nunsubscribe\r\n")) { + st = Unsubscribe; + chs = -1; + } else if (str.hasPrefix("*3\r\n$12\r\npunsubscribe\r\n")) { + st = Punsubscribe; + chs = -1; + } else if (str.hasPrefix("-")) { + st = Error; + } else if (str.hasPrefix("$")) { + st = String; + } + if (chs < 0) { + if (!str.complete()) { + Segment tmp(body); + tmp.rewind(); + tmp.cut(body.length() - 12); + str.set(tmp); + } + const char* p = str.data() + str.length(); + for (int i = 0; i < str.length(); ++i) { + if (*--p == ':') { + chs = atoi(p + 1); + break; + } + } + } + return st; +} diff --git a/src/Subscribe.h b/src/Subscribe.h new file mode 100644 index 0000000..b8a91ad --- /dev/null +++ b/src/Subscribe.h @@ -0,0 +1,63 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_SUBSCRIBE_H_ +#define _PREDIXY_SUBSCRIBE_H_ + +#include "Predixy.h" + +class Subscribe +{ +public: + static const int16_t MaxSub = 32000; +public: + Subscribe(); + int incrPendSub() + { + return mPendSub < MaxSub ? ++mPendSub : 0; + } + int decrPendSub() + { + return mPendSub > 0 ? --mPendSub : 0; + } + bool inPendSub() const + { + return mPendSub > 0; + } + void setSub(int chs) + { + mSub = chs; + } + bool inSub(bool includePend) const + { + return mSub > 0 || (includePend && mPendSub > 0); + } +private: + int16_t mPendSub; + int16_t mSub; +}; + +class SubscribeParser +{ +public: + enum Status + { + Unknown, + Subscribe, + Psubscribe, + Unsubscribe, + Punsubscribe, + Message, + Pmessage, + Error, + String + }; +public: + static Status parse(const Segment& body, int& chs); +}; + + +#endif diff --git a/src/Sync.h b/src/Sync.h new file mode 100644 index 0000000..0b2a8a0 --- /dev/null +++ b/src/Sync.h @@ -0,0 +1,60 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_SYNC_H_ +#define _PREDIXY_SYNC_H_ + +#ifndef _PREDIXY_SINGLE_THREAD_ + +#include +#include + +#define Atomic std::atomic +#define AtomicInt std::atomic +#define AtomicLong std::atomic +#define AtomicCAS(v, o, n) v.compare_exchange_strong(o, n) +#define Mutex std::mutex +#define UniqueLock std::unique_lock +#define TryLockTag std::try_to_lock_t() + + +#else //_PREDIXY_SINGLE_THREAD_ + +#define AtomicInt int +#define AtomicLong long +#define AtomicCAS(v, oldVal, newVal) (v == (oldVal) ? v = (newVal), true : false) +#define TryLockTag "" + +class Mutex +{ +public: + void lock() const + { + } + void unlock() const + { + } +}; + +template +class UniqueLock +{ +public: + template + UniqueLock(A&&... args) + { + } + operator bool() const + { + return true; + } +}; + + +#endif //_PREDIXY_MULTI_THREAD_ + + +#endif diff --git a/src/Timer.cpp b/src/Timer.cpp new file mode 100644 index 0000000..0b5382f --- /dev/null +++ b/src/Timer.cpp @@ -0,0 +1,62 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include +#include "Timer.h" + +Mutex TimerPoint::Mtx; +TimerPoint* TimerPoint::Head = nullptr; +TimerPoint* TimerPoint::Tail = nullptr; +int TimerPoint::PointCnt = 0; + +TimerPoint::TimerPoint(const char* key): + mKey(key), + mElapsed(0), + mCount(0), + mNext(nullptr) +{ + UniqueLock lck(Mtx); + ++PointCnt; + if (Tail) { + Tail->mNext = this; + Tail = this; + } else { + Head = Tail = this; + } +} + +#define StaticPointLen 1024 +void TimerPoint::report() +{ + TimerPoint* points0[StaticPointLen]; + TimerPoint** points = points0; + int cnt = PointCnt; + if (cnt <= 0) { + return; + } else if (cnt > StaticPointLen) { + points = new TimerPoint*[cnt]; + } + int i = 0; + for (TimerPoint* p = Head; p && i < cnt; p = p->mNext) { + points[i++] = p; + } + std::sort(points, points + cnt, + [](TimerPoint* p1, TimerPoint* p2) + {return p1->elapsed() > p2->elapsed();}); + printf("%16s %12s %8s %s\n","Total(us)", "Count", "Avg(us)", "Point" ); + for (i = 0; i < cnt; ++i) { + auto p = points[i]; + printf("%16ld %12ld %8ld %s\n", + p->elapsed(), p->count(), + p->elapsed()/p->count(), p->key()); + } + if (points != points0) { + delete[] points; + } +} + diff --git a/src/Timer.h b/src/Timer.h new file mode 100644 index 0000000..e2b5aed --- /dev/null +++ b/src/Timer.h @@ -0,0 +1,109 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_TIMER_H_ +#define _PREDIXY_TIMER_H_ + +#include +#include +#include "Sync.h" + +class TimerPoint +{ +public: + TimerPoint(const char* key); + const char* key() const + { + return mKey; + } + long elapsed() const + { + return mElapsed; + } + long count() const + { + return mCount; + } + void add(long v) + { + mElapsed += v; + ++mCount; + } + static void report(); +private: + const char* mKey; + AtomicLong mElapsed; + AtomicLong mCount; + TimerPoint* mNext; +private: + static Mutex Mtx; + static TimerPoint* Head; + static TimerPoint* Tail; + static int PointCnt; +}; + +class Timer +{ +public: + Timer(): + mKey(nullptr), + mStart(now()) + { + } + Timer(TimerPoint* key): + mKey(key), + mStart(now()) + { + } + ~Timer() + { + if (mKey) { + long v = elapsed(); + if (v >= 0) { + mKey->add(v); + } + } + } + void restart() + { + mStart = now(); + } + long stop() + { + long ret = elapsed(); + mStart = -1; + if (ret >= 0 && mKey) { + mKey->add(ret); + } + return ret; + } + long elapsed() const + { + return mStart < 0 ? -1 : now() - mStart; + } +private: + static long now() + { + using namespace std::chrono; + return duration_cast(steady_clock::now().time_since_epoch()).count(); + } +private: + TimerPoint* mKey; + long mStart; +}; + +#ifdef _PREDIXY_TIMER_STATS_ + +#define FuncCallTimer() static TimerPoint _timer_point_tttt_(__PRETTY_FUNCTION__);\ + Timer _tiemr_tttt_(&_timer_point_tttt_) + +#else + +#define FuncCallTimer() + +#endif + +#endif diff --git a/src/Transaction.h b/src/Transaction.h new file mode 100644 index 0000000..18b09cf --- /dev/null +++ b/src/Transaction.h @@ -0,0 +1,96 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_TRANSACTION_H_ +#define _PREDIXY_TRANSACTION_H_ + +#include + +class Transaction +{ +public: + static const int16_t MaxWatch = 4096; + static const int16_t MaxMulti = 4096; +public: + Transaction(): + mPendWatch(0), + mWatch(0), + mPendMulti(0), + mMulti(0) + { + } + bool inPendWatch() const + { + return mPendWatch > 0; + } + int incrPendWatch() + { + return mPendWatch < MaxWatch ? ++mPendWatch : 0; + } + int decrPendWatch(int cnt = 1) + { + if (mPendWatch > cnt) { + mPendWatch -= cnt; + } else { + mPendWatch = 0; + } + return mPendWatch; + } + bool inWatch() const + { + return mWatch > 0; + } + int incrWatch() + { + return mWatch < MaxWatch ? ++mWatch : 0; + } + int decrWatch() + { + decrPendWatch(); + return mWatch > 0 ? --mWatch : 0; + } + void unwatch() + { + decrPendWatch(mWatch); + mWatch = 0; + } + bool inPendMulti() const + { + return mPendMulti > 0; + } + int incrPendMulti() + { + return mPendMulti < MaxMulti ? ++mPendMulti : 0; + } + int decrPendMulti() + { + return mPendMulti > 0 ? --mPendMulti : 0; + } + bool inMulti() const + { + return mMulti > 0; + } + int incrMulti() + { + return mMulti < MaxMulti ? ++mMulti : 0; + } + int decrMulti() + { + decrPendMulti(); + return mMulti > 0 ? --mMulti : 0; + } + bool inTransaction() const + { + return mPendWatch > 0 || mPendMulti > 0; + } +private: + int16_t mPendWatch; + int16_t mWatch; + int16_t mPendMulti; + int16_t mMulti; +}; + +#endif diff --git a/src/Util.h b/src/Util.h new file mode 100644 index 0000000..164c996 --- /dev/null +++ b/src/Util.h @@ -0,0 +1,86 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#ifndef _PREDIXY_UTIL_H_ +#define _PREDIXY_UTIL_H_ + +#include +#include +#include +#include +#include + +template +class PtrObjCmp +{ +public: + bool operator()(const T* p1, const T* p2) const + { + return *p1 < *p2; + } +}; + +class StrErrorImpl +{ +public: + StrErrorImpl() + { + set(errno); + } + StrErrorImpl(int err) + { + set(err); + } + void set(int err) + { +#if _GNU_SOURCE + mStr = strerror_r(err, mBuf, sizeof(mBuf)); +#else + strerror_r(err, mBuf, sizeof(mBuf)); + mStr = mBuf; +#endif + } + const char* str() const + { + return mStr; + } +private: + char* mStr; + char mBuf[256]; +}; + +#define StrError(...) StrErrorImpl(__VA_ARGS__).str() + +namespace Util +{ + using namespace std::chrono; + inline long nowSec() + { + return duration_cast(system_clock::now().time_since_epoch()).count(); + } + inline long nowMSec() + { + return duration_cast(system_clock::now().time_since_epoch()).count(); + } + inline long nowUSec() + { + return duration_cast(system_clock::now().time_since_epoch()).count(); + } + inline long elapsedSec() + { + return duration_cast(steady_clock::now().time_since_epoch()).count(); + } + inline long elapsedMSec() + { + return duration_cast(steady_clock::now().time_since_epoch()).count(); + } + inline long elapsedUSec() + { + return duration_cast(steady_clock::now().time_since_epoch()).count(); + } +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5bade3f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,23 @@ +/* + * predixy - A high performance and full features proxy for redis. + * Copyright (C) 2017 Joyield, Inc. + * All rights reserved. + */ + +#include +#include +#include "Proxy.h" + +int main(int argc, char* argv[]) +{ + try { + Proxy p; + if (!p.init(argc, argv)) { + return 0; + } + return p.run(); + } catch (std::exception& e) { + fprintf(stderr, "predixy running exception:%s\n", e.what()); + } + return 1; +}