release 1.0.0

This commit is contained in:
joyield 2017-07-24 11:00:44 +08:00
parent 755a9bc944
commit 801df5e2b8
103 changed files with 13769 additions and 1 deletions

View File

@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2017, joyieldInc
Copyright (c) 2017, Joyield, Inc. <joyield.com@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without

19
Makefile Normal file
View File

@ -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

118
README.md Normal file
View File

@ -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 <latency-name>
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 <server-address> [latency-name]
Reset all stats and latency monitors
redis> INFO ResetStats
## License
Copyright (C) 2017 Joyield, Inc. <joyield.com<at>gmail.com>
All rights reserved.
License under BSD 3-clause "New" or "Revised" License

71
conf/auth.conf Normal file
View File

@ -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
}
}

31
conf/cluster.conf Normal file
View File

@ -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
# }
#}

47
conf/dc.conf Normal file
View File

@ -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
# }
# }
#}

133
conf/latency.conf Normal file
View File

@ -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
}
}

98
conf/predixy.conf Normal file
View File

@ -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

47
conf/sentinel.conf Normal file
View File

@ -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 {
# }
#}

7
conf/try.conf Normal file
View File

@ -0,0 +1,7 @@
## This conf is only for test
ClusterServerPool {
Servers {
+ 127.0.0.1:6379
}
}

246
src/AcceptConnection.cpp Normal file
View File

@ -0,0 +1,246 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<RequestParser::MaxCmdLen> 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;
}

102
src/AcceptConnection.h Normal file
View File

@ -0,0 +1,102 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<AcceptConnection, SharePtr<AcceptConnection>>,
public DequeNode<AcceptConnection, SharePtr<AcceptConnection>>,
public RefCntObj<AcceptConnection>
{
public:
typedef AcceptConnection Value;
typedef ListNode<AcceptConnection, SharePtr<AcceptConnection>> ListNodeType;
typedef DequeNode<AcceptConnection, SharePtr<AcceptConnection>> 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<AcceptConnection> AcceptConnectionList;
typedef Deque<AcceptConnection> AcceptConnectionDeque;
typedef Alloc<AcceptConnection, Const::AcceptConnectionAllocCacheSize> AcceptConnectionAlloc;
#endif

41
src/AcceptSocket.cpp Normal file
View File

@ -0,0 +1,41 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}
}

25
src/AcceptSocket.h Normal file
View File

@ -0,0 +1,25 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

10
src/Alloc.cpp Normal file
View File

@ -0,0 +1,10 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include "Alloc.h"
long AllocBase::MaxMemory(0);
AtomicLong AllocBase::UsedMemory(0);

250
src/Alloc.h Normal file
View File

@ -0,0 +1,250 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_ALLOC_H_
#define _PREDIXY_ALLOC_H_
#include <stdlib.h>
#include <iostream>
#include <thread>
#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<class T>
inline int allocSize()
{
return sizeof(T);
}
template<class T, int CacheSize = 64>
class Alloc : public AllocBase
{
public:
template<class... Targs>
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<T>();
::operator delete((void*)obj);
}
throw;
}
logVerb("alloc create object with old memory %d @%p", allocSize<T>(), obj);
return obj;
}
UsedMemory += allocSize<T>();
if (MaxMemory == 0 || UsedMemory <= MaxMemory) {
void* p = ::operator new(allocSize<T>());
if (p) {
try {
obj = new (p) T(args...);
logVerb("alloc create object with new memory %d @%p", allocSize<T>(), obj);
return obj;
} catch (...) {
UsedMemory -= allocSize<T>();
::operator delete(p);
throw;
}
} else {
UsedMemory -= allocSize<T>();
Throw(MemLimit, "system memory alloc fail");
}
} else {
UsedMemory -= allocSize<T>();
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<T>();
::operator delete((void*)obj);
}
logVerb("alloc destroy object with size %d @%p delete %s", (int)allocSize<T>(), obj, del ? "true" : "false");
}
private:
thread_local static T* Free[CacheSize];
thread_local static int Size;
};
template<class T, int CacheSize>
thread_local T* Alloc<T, CacheSize>::Free[CacheSize];
template<class T, int CacheSize>
thread_local int Alloc<T, CacheSize>::Size = 0;
template<class T>
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<T>::destroy(static_cast<T*>(this));
} else if (n < 0) {
logError("unref object %p with cnt %d", this, n);
abort();
}
}
protected:
~RefCntObj()
{
mCnt = 0;
}
private:
AtomicInt mCnt;
};
template<class T>
class SharePtr
{
public:
SharePtr():
mObj(nullptr)
{
}
SharePtr(T* obj):
mObj(obj)
{
if (obj) {
obj->ref();
}
}
SharePtr(const SharePtr<T>& sp):
mObj(sp.mObj)
{
if (mObj) {
mObj->ref();
}
}
SharePtr(SharePtr<T>&& 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

106
src/Auth.cpp Normal file
View File

@ -0,0 +1,106 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <algorithm>
#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;
}
}

59
src/Auth.h Normal file
View File

@ -0,0 +1,59 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_AUTH_H_
#define _PREDIXY_AUTH_H_
#include <map>
#include <set>
#include <vector>
#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<String> 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<String, Auth*> mAuthMap;
Auth* mDefault;
static Auth DefaultAuth;
};
#endif

42
src/Backtrace.h Normal file
View File

@ -0,0 +1,42 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_BACKTRACE_H_
#define _PREDIXY_BACKTRACE_H_
#include "Logger.h"
#if _PREDIXY_BACKTRACE_
#include <execinfo.h>
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

381
src/Buffer.cpp Normal file
View File

@ -0,0 +1,381 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<Buffer> p = ListNode<Buffer, SharePtr<Buffer>>::next();
ListNode<Buffer, SharePtr<Buffer>>::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<Segment*>(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';
}

233
src/Buffer.h Normal file
View File

@ -0,0 +1,233 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<Buffer> BufferPtr;
class Buffer :
public ListNode<Buffer, SharePtr<Buffer>>,
public RefCntObj<Buffer>
{
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<Buffer, Const::BufferAllocCacheSize>;
};
typedef List<Buffer> BufferList;
template<>
inline int allocSize<Buffer>()
{
return Buffer::getSize() + sizeof(Buffer);
}
typedef Alloc<Buffer, Const::BufferAllocCacheSize> 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<Segment> SegmentList;
template<int Size>
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

180
src/ClusterNodesParser.cpp Normal file
View File

@ -0,0 +1,180 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <string.h>
#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;
}

100
src/ClusterNodesParser.h Normal file
View File

@ -0,0 +1,100 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<NodeIdLen> mNodeId;
SString<AddrLen> mAddr;
SString<FlagsLen> mFlags;
SString<NodeIdLen> mMaster;
int mSlotBegin;
int mSlotEnd;
};
#endif

231
src/ClusterServerPool.cpp Normal file
View File

@ -0,0 +1,231 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <time.h>
#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<Const::MaxKeyLen> 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);
}
}
}
}

49
src/ClusterServerPool.h Normal file
View File

@ -0,0 +1,49 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_CLUSTER_SERVER_POOL_H_
#define _PREDIXY_CLUSTER_SERVER_POOL_H_
#include <vector>
#include <string>
#include <map>
#include "ServerPool.h"
class ClusterServerPool : public ServerPoolTmpl<ClusterServerPool>
{
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<Server*>& 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<ClusterServerPool>;
private:
Hash mHash;
std::vector<Server*> mServPool;
std::map<String, ServerGroup*> mGroups;
ServerGroup* mSlots[Const::RedisClusterSlots];
};
#endif

189
src/Command.cpp Normal file
View File

@ -0,0 +1,189 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <string.h>
#include <strings.h>
#include <map>
#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;
}
}

251
src/Command.h Normal file
View File

@ -0,0 +1,251 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<String, const Command*, StringCaseCmp> CommandMap;
static CommandMap CmdMap;
};
#endif

41
src/Common.h Normal file
View File

@ -0,0 +1,41 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_COMMON_H_
#define _PREDIXY_COMMON_H_
#include <limits.h>
#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<<ServGroupBits;
static const int ServGroupMask = ((1<<ServGroupBits) - 1);
static const int MaxServInGroup = 64;
static const int MaxAddrLen = 128;
static const int MaxDcLen = 32;
static const int MaxIOVecLen = IOV_MAX;
static const int MaxKeyLen = 512;
static const int BufferAllocCacheSize = 64;
static const int RequestAllocCacheSize = 32;
static const int ResponseAllocCacheSize = 32;
static const int AcceptConnectionAllocCacheSize = 32;
static const int ConnectConnectionAllocCacheSize = 4;
};
#endif

604
src/Conf.cpp Normal file
View File

@ -0,0 +1,604 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <ctype.h>
#include <iostream>
#include <fstream>
#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 <conf-file> [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<std::string>& 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<ServerConf>& 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;
}

221
src/Conf.h Normal file
View File

@ -0,0 +1,221 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_CONF_H_
#define _PREDIXY_CONF_H_
#include <limits.h>
#include <string.h>
#include <strings.h>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <bitset>
#include "Predixy.h"
#include "Distribution.h"
#include "ConfParser.h"
#include "Auth.h"
struct AuthConf
{
std::string password;
int mode; //Command::Mode
std::vector<std::string> keyPrefix;
std::vector<std::string> readKeyPrefix;
std::vector<std::string> writeKeyPrefix;
};
struct ServerConf
{
std::string password;
std::string addr;
static bool parse(ServerConf& s, const char* str);
};
struct ServerGroupConf
{
std::string name;
std::vector<ServerConf> 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<ServerConf> servers;
};
struct SentinelServerPoolConf : public ServerPoolConf
{
Distribution dist = Distribution::None;
Hash hash = Hash::None;
char hashTag[2];
std::vector<ServerConf> sentinels;
std::vector<ServerGroupConf> groups;
};
struct ReadPolicyConf
{
std::string name;
int priority;
int weight;
};
struct DCConf
{
std::string name;
std::vector<std::string> addrPrefix;
std::vector<ReadPolicyConf> readPolicy;
};
struct LatencyMonitorConf
{
std::string name;
std::bitset<Command::Sentinel> cmds;
std::vector<long> 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<AuthConf>& 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<DCConf>& dcConfs() const
{
return mDCConfs;
}
const std::vector<LatencyMonitorConf>& 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<ServerConf>& 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<AuthConf> mAuthConfs;
int mServerPoolType;
ClusterServerPoolConf mClusterServerPool;
SentinelServerPoolConf mSentinelServerPool;
std::vector<DCConf> mDCConfs;
std::string mLocalDC;
std::vector<LatencyMonitorConf> mLatencyMonitors;
};
#endif

308
src/ConfParser.cpp Normal file
View File

@ -0,0 +1,308 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <unistd.h>
#include <limits.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <iostream>
#include <fstream>
#include <memory>
#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<std::ifstream> stream;
int line;
};
ConfParser::Node* ConfParser::load(const char* file)
{
clear();
std::string* name = getFile(file, nullptr);
mStrings.push_back(name);
std::shared_ptr<std::ifstream> s(new std::ifstream(name->c_str()));
if (!s->good()) {
Throw(OpenFileFail, "open file %s fail %s", name->c_str(), StrError());
}
std::vector<File> files;
files.push_back(File{name->c_str(), s, 0});
std::string line;
std::string key;
std::string val;
std::vector<Node*> 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;
}

86
src/ConfParser.h Normal file
View File

@ -0,0 +1,86 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_CONF_PARSER_H_
#define _PREDIXY_CONF_PARSER_H_
#include <string.h>
#include <string>
#include <set>
#include <vector>
#include <fstream>
#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<std::string*> mStrings;
};
#endif

242
src/ConnectConnection.cpp Normal file
View File

@ -0,0 +1,242 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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();
}

94
src/ConnectConnection.h Normal file
View File

@ -0,0 +1,94 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<ConnectConnection>,
public DequeNode<ConnectConnection>
{
public:
typedef ConnectConnection Value;
typedef ListNode<ConnectConnection> ListNodeType;
typedef DequeNode<ConnectConnection> 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<ConnectConnection> ConnectConnectionList;
typedef Deque<ConnectConnection> ConnectConnectionDeque;
typedef Alloc<ConnectConnection, Const::ConnectConnectionAllocCacheSize> ConnectConnectionAlloc;
#endif

View File

@ -0,0 +1,205 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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);
}
}
}

View File

@ -0,0 +1,81 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_CONNECT_CONNECTION_POOL_H_
#define _PREDIXY_CONNECT_CONNECTION_POOL_H_
#include <vector>
#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<LatencyMonitor>& latencyMonitors()
{
return mLatencyMonitors;
}
const std::vector<LatencyMonitor>& 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<ConnectConnection*> mShareConns;
std::vector<ConnectConnectionList> mPrivateConns;
ServerStats mStats;
std::vector<LatencyMonitor> mLatencyMonitors;
};
#endif

66
src/ConnectSocket.cpp Normal file
View File

@ -0,0 +1,66 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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();
}

58
src/ConnectSocket.h Normal file
View File

@ -0,0 +1,58 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_CONNECT_SOCKET_H_
#define _PREDIXY_CONNECT_SOCKET_H_
#include "Socket.h"
#include <string>
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

38
src/Connection.cpp Normal file
View File

@ -0,0 +1,38 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<Buffer>::create();
if (mBuf) {
mBuf->concat(buf);
}
mBuf = buf;
++mBufCnt;
}
return mBuf;
}

61
src/Connection.h Normal file
View File

@ -0,0 +1,61 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

56
src/Crc16.cpp Normal file
View File

@ -0,0 +1,56 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

79
src/DC.cpp Normal file
View File

@ -0,0 +1,79 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

72
src/DC.h Normal file
View File

@ -0,0 +1,72 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_DC_H_
#define _PREDIXY_DC_H_
#include <vector>
#include <map>
#include "ID.h"
#include "Conf.h"
#include "String.h"
struct DCReadPolicy
{
int priority = 0;
int weight = 0;
};
class DC : public ID<DC>
{
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<DCReadPolicy> 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<String, DC*> mDCs;
std::map<String, DC*> mAddrDC;
DC* mLocalDC;
};
#endif

193
src/Deque.h Normal file
View File

@ -0,0 +1,193 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_DEQUE_H_
#define _PREDIXY_DEQUE_H_
template<class T, class P = T*, int Size = 1>
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<DequeNode*>(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 N, int Idx = 0>
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<N*>(obj);
if (mTail) {
static_cast<Node*>((T*)mTail)->concat(p, Idx);
mTail = p;
} else {
mHead = mTail = p;
}
++mSize;
}
void push_front(T* obj)
{
N* p = static_cast<N*>(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<N*>(obj);
}
private:
int mSize;
P mHead;
P mTail;
};
#endif

36
src/Distribution.cpp Normal file
View File

@ -0,0 +1,36 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <string.h>
#include <strings.h>
#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;
}

34
src/Distribution.h Normal file
View File

@ -0,0 +1,34 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

106
src/EpollMultiplexor.cpp Normal file
View File

@ -0,0 +1,106 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

65
src/EpollMultiplexor.h Normal file
View File

@ -0,0 +1,65 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_EPOLL_MULTIPLEXOR_H_
#define _PREDIXY_EPOLL_MULTIPLEXOR_H_
#include <unistd.h>
#include <sys/epoll.h>
#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<class T>
int wait(long usec, T* handler);
private:
int mFd;
static const int MaxEvents = 1024;
epoll_event mEvents[MaxEvents];
};
template<class T>
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<Socket*>(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

63
src/Exception.h Normal file
View File

@ -0,0 +1,63 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_EXCEPTION_H_
#define _PREDIXY_EXCEPTION_H_
#include <exception>
#include <stdarg.h>
#include <stdio.h>
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<class... A> \
T(A&&... args):ExceptionBase(args...) {} \
}
#define Throw(T, ...) throw T(__FILE__, __LINE__, ##__VA_ARGS__)
#endif

1424
src/Handler.cpp Normal file

File diff suppressed because it is too large Load Diff

132
src/Handler.h Normal file
View File

@ -0,0 +1,132 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_HANDLER_H_
#define _PREDIXY_HANDLER_H_
#include <vector>
#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<Handler>
{
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<LatencyMonitor>& 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<ConnectConnectionPool*> mConnPool;
AcceptConnectionDeque mAcceptConns;
AcceptConnectionList mPostAcceptConns;
ConnectConnectionList mPostConnectConns;
ConnectConnectionDeque mWaitConnectConns;
long mStatsVer;
HandlerStats mStats;
std::vector<LatencyMonitor> mLatencyMonitors;
IDUnique mIDUnique;
unsigned int mRandSeed;
};
#endif

83
src/HashFunc.cpp Normal file
View File

@ -0,0 +1,83 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <string.h>
#include <strings.h>
#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;
}

47
src/HashFunc.h Normal file
View File

@ -0,0 +1,47 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_HASH_FUNC_H_
#define _PREDIXY_HASH_FUNC_H_
#include <string.h>
#include <stdint.h>
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

94
src/ID.h Normal file
View File

@ -0,0 +1,94 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_ID_H_
#define _PREDIXY_ID_H_
#include "Sync.h"
#include <vector>
template<class T>
class TID
{
public:
TID():
mId(++Id)
{
}
long id() const
{
return mId;
}
private:
long mId;
thread_local static long Id;
};
template<class T>
thread_local long TID<T>::Id(0);
template<class T>
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<class T>
AtomicInt ID<T>::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<class C>
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<bool> mMarker;
};
#endif

30
src/IOVec.h Normal file
View File

@ -0,0 +1,30 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_IOVEC_H_
#define _PREDIXY_IOVEC_H_
#include <sys/uio.h>
class Request;
class Response;
class Segment;
struct IOVec
{
char* dat;
int len;
int pos;
Buffer* buf;
Segment* seg;
Request* req;
};
#endif

86
src/KqueueMultiplexor.cpp Normal file
View File

@ -0,0 +1,86 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

72
src/KqueueMultiplexor.h Normal file
View File

@ -0,0 +1,72 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_KQUEUE_MULTIPLEXOR_H_
#define _PREDIXY_KQUEUE_MULTIPLEXOR_H_
#include <unistd.h>
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#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<class T>
int wait(long usec, T* handler);
private:
int mFd;
static const int MaxEvents = 1024;
struct kevent mEvents[MaxEvents];
};
template<class T>
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<Socket*>(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

61
src/LatencyMonitor.cpp Normal file
View File

@ -0,0 +1,61 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<LatencyMonitorConf>& 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);
}
}
}
}

135
src/LatencyMonitor.h Normal file
View File

@ -0,0 +1,135 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_LATENCY_MONITOR_H_
#define _PREDIXY_LATENCY_MONITOR_H_
#include <algorithm>
#include <vector>
#include <map>
#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<Command::Sentinel>* mCmds;
std::vector<TimeSpan> mTimeSpan;
TimeSpan mLast;
};
class LatencyMonitorSet
{
public:
DefException(DuplicateDef);
public:
LatencyMonitorSet()
{
}
void init(const std::vector<LatencyMonitorConf>& conf);
const std::vector<LatencyMonitor>& 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<int>& cmdIndex(Command::Type type) const
{
return mCmdIdx[type];
}
private:
std::vector<LatencyMonitor> mPool;
std::map<String, int> mNameIdx;
std::vector<std::vector<int>> mCmdIdx;
};
#endif

129
src/List.h Normal file
View File

@ -0,0 +1,129 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_LIST_H_
#define _PREDIXY_LIST_H_
template<class T, class P = T*, int Size = 1>
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 N, int Idx = 0>
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<N*>(obj);
if (mTail) {
static_cast<Node*>((T*)mTail)->concat(p, Idx);
mTail = p;
} else {
mHead = mTail = p;
}
++mSize;
}
void push_front(T* obj)
{
N* p = static_cast<N*>(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<N*>(obj);
}
private:
int mSize;
P mHead;
P mTail;
};
#endif

57
src/ListenSocket.cpp Normal file
View File

@ -0,0 +1,57 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <string.h>
#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;
}

31
src/ListenSocket.h Normal file
View File

@ -0,0 +1,31 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

191
src/LogFileSink.cpp Normal file
View File

@ -0,0 +1,191 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <sstream>
#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);
}
}

47
src/LogFileSink.h Normal file
View File

@ -0,0 +1,47 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_LOG_FILE_SINK_H_
#define _PREDIXY_LOG_FILE_SINK_H_
#include <stdio.h>
#include <atomic>
#include <string>
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

214
src/Logger.cpp Normal file
View File

@ -0,0 +1,214 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <chrono>
#include "LogFileSink.h"
#include "Util.h"
#include "Logger.h"
#include <stdio.h>
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<std::mutex> lck(mMtx);
mCond.notify_one();
}
void Logger::run()
{
std::vector<LogUnit*> logs(mFree.capacity());
logs.resize(0);
while (!mStop) {
long missLogs = 0;
do {
std::unique_lock<std::mutex> 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<std::mutex> lck(mMtx);
mFree.push_back(log);
mCond.notify_one();
}
} else {
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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;
}

141
src/Logger.h Normal file
View File

@ -0,0 +1,141 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_LOGGER_H_
#define _PREDIXY_LOGGER_H_
#include <stdarg.h>
#include <vector>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <thread>
#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<std::mutex> 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<LogUnit*> mLogs;
std::vector<LogUnit*> 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

113
src/Makefile Normal file
View File

@ -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)

39
src/Multiplexor.h Normal file
View File

@ -0,0 +1,39 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

68
src/PollMultiplexor.cpp Normal file
View File

@ -0,0 +1,68 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

74
src/PollMultiplexor.h Normal file
View File

@ -0,0 +1,74 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_POLL_MULTIPLEXOR_H_
#define _PREDIXY_POLL_MULTIPLEXOR_H_
#include <unistd.h>
#include <poll.h>
#include <vector>
#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<class T>
int wait(long usec, T* handler);
private:
struct SktIdx
{
Socket* skt;
int idx;
};
private:
std::vector<SktIdx> mSkts;
std::vector<struct pollfd> mFds;
};
template<class T>
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

76
src/Predixy.h Normal file
View File

@ -0,0 +1,76 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_H_
#define _PREDIXY_H_
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <sys/uio.h>
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<Request> RequestPtr;
typedef SharePtr<Response> ResponsePtr;
typedef SharePtr<AcceptConnection> AcceptConnectionPtr;
typedef SharePtr<ConnectConnection> ConnectConnectionPtr;
#endif

179
src/Proxy.cpp Normal file
View File

@ -0,0 +1,179 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <sys/types.h>
#include <iostream>
#include <thread>
#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<std::shared_ptr<std::thread>> tasks;
for (auto h : mHandlers) {
std::shared_ptr<std::thread> 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;
}

94
src/Proxy.h Normal file
View File

@ -0,0 +1,94 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_PROXY_H_
#define _PREDIXY_PROXY_H_
#include <vector>
#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<Handler*>& 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<Handler*> mHandlers;
ServerPool* mServPool;
time_t mStartTime;
AtomicLong mStatsVer;
LatencyMonitorSet mLatencyMonitorSet;
};
#endif

16
src/Reply.cpp Normal file
View File

@ -0,0 +1,16 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include "Reply.h"
const char* Reply::TypeStr[Sentinel] = {
"None",
"Status",
"Err",
"Str",
"Int",
"Array"
};

27
src/Reply.h Normal file
View File

@ -0,0 +1,27 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

388
src/Request.cpp Normal file
View File

@ -0,0 +1,388 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

177
src/Request.h Normal file
View File

@ -0,0 +1,177 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_REQUEST_H_
#define _PREDIXY_REQUEST_H_
#include "Predixy.h"
enum RequestListIndex
{
Recv = 0,
Send,
Size
};
class Request :
public TID<Request>,
public ListNode<Request, SharePtr<Request>, RequestListIndex::Size>,
public RefCntObj<Request>
{
public:
typedef Request Value;
typedef ListNode<Request, SharePtr<Request>, 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<Request, RequestListIndex::Recv> RecvRequestList;
typedef List<Request, RequestListIndex::Send> SendRequestList;
typedef Alloc<Request, Const::RequestAllocCacheSize> RequestAlloc;
#endif

290
src/RequestParser.cpp Normal file
View File

@ -0,0 +1,290 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<MaxCmdLen> 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;
}
}

124
src/RequestParser.h Normal file
View File

@ -0,0 +1,124 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_REQUEST_PARSER_H_
#define _PREDIXY_REQUEST_PARSER_H_
#include <map>
#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

213
src/Response.cpp Normal file
View File

@ -0,0 +1,213 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<Const::MaxAddrLen>& addr, const char* token) const
{
SegmentStr<Const::MaxAddrLen + 16> 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;
}

140
src/Response.h Normal file
View File

@ -0,0 +1,140 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_RESPONSE_H_
#define _PREDIXY_RESPONSE_H_
#include "Predixy.h"
#include "ResponseParser.h"
class Response :
public TID<Response>,
public ListNode<Response, SharePtr<Response>>,
public RefCntObj<Response>
{
public:
typedef Response Value;
typedef ListNode<Response, SharePtr<Response>> 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<Const::MaxAddrLen>& addr) const
{
return getAddr(slot, addr, "-MOVED ");
}
bool isAsk() const
{
return mType == Reply::Error && mRes.hasPrefix("-ASK ");
}
bool getAsk(SString<Const::MaxAddrLen>& 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<Const::MaxAddrLen>& addr, const char* token) const;
private:
Reply::Type mType;
int64_t mInteger;
Segment mHead; //for mget
Segment mRes;
};
typedef List<Response> ResponseList;
typedef Alloc<Response, Const::ResponseAllocCacheSize> ResponseAlloc;
#endif

260
src/ResponseParser.cpp Normal file
View File

@ -0,0 +1,260 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

86
src/ResponseParser.h Normal file
View File

@ -0,0 +1,86 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

446
src/SentinelServerPool.cpp Normal file
View File

@ -0,0 +1,446 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <algorithm>
#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<Const::MaxKeyLen> 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<Const::MaxAddrLen>& 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<Const::MaxAddrLen>& 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<Const::MaxAddrLen> 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<Const::MaxAddrLen + 32> str(res->body());
if (!str.complete()) {
return;
}
if (strncmp(str.data(), "*2\r\n$", 5) != 0) {
return;
}
SString<Const::MaxAddrLen> 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<Const::MaxAddrLen> 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;
}
}
}

43
src/SentinelServerPool.h Normal file
View File

@ -0,0 +1,43 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_SENTINEL_SERVER_POOL_H_
#define _PREDIXY_SENTINEL_SERVER_POOL_H_
#include <map>
#include "Predixy.h"
#include "ServerPool.h"
class SentinelServerPool : public ServerPoolTmpl<SentinelServerPool>
{
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<SentinelServerPool>;
private:
std::vector<Server*> mSentinels;
std::vector<Server*> mServPool;
std::vector<ServerGroup*> mGroups;
Distribution mDist;
Hash mHash;
char mHashTag[2];
};
#endif

57
src/Server.cpp Normal file
View File

@ -0,0 +1,57 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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);
}
}

144
src/Server.h Normal file
View File

@ -0,0 +1,144 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_SERVER_H_
#define _PREDIXY_SERVER_H_
#include <string>
#include "Predixy.h"
#include "DC.h"
#include "ConnectConnection.h"
class Server : public ID<Server>
{
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<Const::MaxAddrLen> mAddr;
SString<Const::MaxServNameLen> mName;
SString<Const::MaxServNameLen> mMasterName;
String mPassword;
AtomicLong mNextActivateTime; //us
AtomicLong mFailureCnt;
bool mStatic;
bool mFail;
bool mOnline;
bool mUpdating;
};
#endif

303
src/ServerGroup.cpp Normal file
View File

@ -0,0 +1,303 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <algorithm>
#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;
}
}
}

37
src/ServerGroup.h Normal file
View File

@ -0,0 +1,37 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_SERVER_GROUP_H_
#define _PREDIXY_SERVER_GROUP_H_
#include <string>
#include <deque>
#include <vector>
#include "Server.h"
#include "String.h"
#include "Predixy.h"
class ServerGroup : public ID<ServerGroup>
{
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<Const::MaxServNameLen> mName;
std::vector<Server*> mServs;
};
#endif

77
src/ServerPool.cpp Normal file
View File

@ -0,0 +1,77 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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<Mutex> 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<Server*>& 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<Server*>& 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;
}

169
src/ServerPool.h Normal file
View File

@ -0,0 +1,169 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_SERVER_POOL_H_
#define _PREDIXY_SERVER_POOL_H_
#include <string>
#include <map>
#include "Predixy.h"
#include <vector>
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<Mutex> 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<class T>
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<Server*>& servs);
static Server* iter(const std::vector<Server*>& servs, int& cursor);
protected:
std::map<String, Server*> mServs;
std::vector<ServerGroup*> 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 T>
class ServerPoolTmpl : public ServerPool
{
public:
ServerPoolTmpl(Proxy* p, int type):
ServerPool(p, type, (ServerPoolTmpl<T>*)this)
{
}
private:
static Server* getServer(const ServerPool* p, Handler* h, Request* req)
{
return static_cast<const T*>(p)->getServer(h, req);
}
static Server* iter(const ServerPool* p, int& cursor)
{
return static_cast<const T*>(p)->iter(cursor);
}
static void refreshRequest(ServerPool* p, Handler* h)
{
static_cast<T*>(p)->refreshRequest(h);
}
static void handleResponse(ServerPool* p, Handler* h, ConnectConnection* s, Request* req, Response* res)
{
static_cast<T*>(p)->handleResponse(h, s, req, res);
}
friend class ServerPool;
};
#endif

212
src/Socket.cpp Normal file
View File

@ -0,0 +1,212 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <string.h>
#include <string>
#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;
}

105
src/Socket.h Normal file
View File

@ -0,0 +1,105 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_SOCKET_H_
#define _PREDIXY_SOCKET_H_
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#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

79
src/Stats.h Normal file
View File

@ -0,0 +1,79 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

325
src/String.h Normal file
View File

@ -0,0 +1,325 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_STRING_h_
#define _PREDIXY_STRING_h_
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdarg.h>
#include <string>
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 <int Size>
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<class T>
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<class T>
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

59
src/Subscribe.cpp Normal file
View File

@ -0,0 +1,59 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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;
}

63
src/Subscribe.h Normal file
View File

@ -0,0 +1,63 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* 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

60
src/Sync.h Normal file
View File

@ -0,0 +1,60 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_SYNC_H_
#define _PREDIXY_SYNC_H_
#ifndef _PREDIXY_SINGLE_THREAD_
#include <atomic>
#include <mutex>
#define Atomic std::atomic
#define AtomicInt std::atomic<int>
#define AtomicLong std::atomic<long>
#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 M>
class UniqueLock
{
public:
template<class... A>
UniqueLock(A&&... args)
{
}
operator bool() const
{
return true;
}
};
#endif //_PREDIXY_MULTI_THREAD_
#endif

62
src/Timer.cpp Normal file
View File

@ -0,0 +1,62 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#include <stdio.h>
#include <algorithm>
#include <iostream>
#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<Mutex> 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;
}
}

109
src/Timer.h Normal file
View File

@ -0,0 +1,109 @@
/*
* predixy - A high performance and full features proxy for redis.
* Copyright (C) 2017 Joyield, Inc. <joyield.com@gmail.com>
* All rights reserved.
*/
#ifndef _PREDIXY_TIMER_H_
#define _PREDIXY_TIMER_H_
#include <map>
#include <chrono>
#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<microseconds>(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

Some files were not shown because too many files have changed in this diff Show More