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