camellia-redis-proxy(English)
camellia-redis-proxy是一款高性能的redis代理,使用netty4开发
- 支持代理到redis-standalone、redis-sentinel、redis-cluster
- 支持自定义分片
- 支持读写分离
- 支持双(多)写,可以proxy直连双写,也可以基于mq(如kafka)双写,也可以基于插件体系自定义双写规则
- 支持双(多)读
- 支持设置密码
- 支持SELECT命令,当前仅当后端redis不包含redis-cluster(此时仅支持SELECT 0),可以是redis-standalone/redis-sentinel/redis-proxies或者其组合(分片/读写分离)
- 支持阻塞式命令,如BLPOP/BRPOP/BRPOPLPUSH/BZPOPMIN/BZPOPMAX等
- 支持PUBSUB系列命令,代理到redis-standalone/redis-sentinel/redis-cluster均支持
- 支持事务命令(MULTI/EXEC/DISCARD/WATCH/UNWATCH),代理到redis-standalone/redis-sentinel/redis-cluster均支持
- 支持redis5.0的STREAMS系列命令
- 支持SCAN命令(代理到redis-standalone/redis-sentinel/redis-cluster均支持,自定义分片时也支持)
- 支持阿里TairZSet、TairHash、TairString系列命令
- 支持RedisJSON和RedisSearch系列命令
- 支持读slave(redis-sentinel/redis-cluster均支持配置读从节点)
- 支持多租户,即租户A路由到redis1,租户B路由到redis2(可以通过不同的clientname区分,也可以通过不同的password区分)
- 支持多租户动态路由,支持自定义的动态路由数据源
- 支持自定义插件,并且内置了很多插件,可以按需使用(包括:大key监控、热key监控、热key缓存、key命名空间、ip黑白名单、速率控制等等)
- 支持丰富的监控,可以监控客户端连接数、调用量、方法耗时、大key、热key、后端redis连接数和耗时等,并且支持以http接口形式获取监控数据
- 支持info命令获取服务器相关信息(包括后端redis集群的信息)
- 提供了一个spring-boot-starter,可以快速搭建proxy集群
- 高可用,可以基于lb组成集群,也可以基于注册中心组成集群,也可以伪装成redis-cluster组成集群
- 提供了一个默认的注册发现实现组件(依赖zookeeper),如果端侧是java,则可以很简单的将JedisPool替换为RedisProxyJedisPool,即可接入redis proxy
- 提供了一个spring-boot-starter用于SpringRedisTemplate以注册发现模式接入proxy
- 支持整合hbase实现string/zset/hash等数据结构的冷热分离存储操作,具体见: redis-proxy-hbase
- 完整支持
##DataBase
PING,AUTH,HELLO,ECHO,CLIENT,QUIT,EXISTS,DEL,TYPE,EXPIRE,
EXPIREAT,TTL,PERSIST,PEXPIRE,PEXPIREAT,PTTL,SORT,UNLINK,TOUCH,DUMP,RESTORE,SCAN,COMMAND,
##String
SET,GET,GETSET,MGET,SETNX,SETEX,MSET,DECRBY,DECR,INCRBY,INCR,APPEND,
STRLEN,INCRBYFLOAT,PSETEX,SETRANGE,GETRANGE,SUBSTR,GETEX,GETDEL,
##Hash
HSET,HGET,HSETNX,HMSET,HMGET,HINCRBY,HEXISTS,HDEL,HLEN,HKEYS,
HVALS,HGETALL,HINCRBYFLOAT,HSCAN,HSTRLEN,HRANDFIELD,
##List
RPUSH,LPUSH,LLEN,LRANGE,LTRIM,LINDEX,LSET,LREM,LPOP,RPOP,LINSERT,LPUSHX,RPUSHX,LPOS,
##Set
SADD,SMEMBERS,SREM,SPOP,SCARD,SISMEMBER,SRANDMEMBER,SSCAN,SMISMEMBER,
##ZSet
ZADD,ZINCRBY,ZRANK,ZCARD,ZSCORE,ZCOUNT,ZRANGE,ZRANGEBYSCORE,ZRANGEBYLEX,
ZREVRANK,ZREVRANGE,ZREVRANGEBYSCORE,ZREVRANGEBYLEX,ZREM,
ZREMRANGEBYRANK,ZREMRANGEBYSCORE,ZREMRANGEBYLEX,ZLEXCOUNT,ZSCAN,
ZPOPMAX,ZPOPMIN,ZMSCORE,ZRANDMEMBER,
##BitMap
SETBIT,GETBIT,BITPOS,BITCOUNT,BITFIELD,
##Geo
GEOADD,GEODIST,GEOHASH,GEOPOS,GEORADIUS,GEORADIUSBYMEMBER,GEOSEARCH,
##HyperLogLog
PFADD
##Stream
XACK,XADD,XCLAIM,XDEL,XLEN,XPENDING,XRANGE,XREVRANGE,XTRIM,XGROUP,XINFO,
##BloomFilter
BF.ADD,BF.EXISTS,BF.INFO,BF.INSERT,BF.LOADCHUNK,BF.MADD,BF.MEXISTS,BF.SCANDUMP,
##TairZSet
EXZADD,EXZINCRBY,EXZSCORE,EXZRANGE,EXZREVRANGE,EXZRANGEBYSCORE,EXZREVRANGEBYSCORE,
EXZRANGEBYLEX,EXZREVRANGEBYLEX,EXZREM,EXZREMRANGEBYSCORE,EXZREMRANGEBYRANK,EXZREMRANGEBYLEX,
EXZCARD,EXZRANK,EXZREVRANK,EXZCOUNT,EXZMSCORE,EXZLEXCOUNT,EXZRANDMEMBER,EXZSCAN,EXZPOPMAX,EXZPOPMIN,
##TairHash
EXHSET,EXHGET,EXHMSET,EXHPEXPIREAT,EXHPEXPIRE,EXHEXPIREAT,EXHEXPIRE,EXHPERSIST,EXHPTTL,EXHTTL,
EXHVER,EXHSETVER,EXHINCRBY,EXHINCRBYFLOAT,EXHGETWITHVER,EXHMGET,EXHMGETWITHVER,EXHDEL,EXHLEN,
EXHEXISTS,EXHSTRLEN,EXHKEYS,EXHVALS,EXHGETALL,EXHGETALLWITHVER,EXHSCAN,
##TairString
EXSET,EXGET,EXSETVER,EXINCRBY,EXINCRBYFLOAT,EXCAS,EXCAD,EXAPPEND,EXPREPEND,EXGAE,
##RedisJSON
JSON.ARRAPPEND,JSON.ARRINDEX,JSON.ARRINSERT,JSON.ARRLEN,JSON.ARRPOP,JSON.ARRTRIM,JSON.CLEAR,
JSON.DEL,JSON.FORGET,JSON.GET,JSON.MGET,JSON.NUMINCRBY,JSON.NUMMULTBY,JSON.OBJKEYS,JSON.OBJLEN,
JSON.RESP,JSON.SET,JSON.STRAPPEND,JSON.STRLEN,JSON.TOGGLE,JSON.TYPE,
- 限制性支持
当且仅当以下命令涉及的key被分在相同分片,或者被分在redis cluster的相同slot下
特别的,如果是阻塞式的命令,则不允许双(多)写
##DataBase
RENAME,RENAMENX,
##String
MSETNX,
##Set
SINTER,SINTERSTORE,SUNION,SUNIONSTORE,SDIFF,SDIFFSTORE,SMOVE,
##List
BLPOP,BRPOP,RPOPLPUSH,BRPOPLPUSH,LMOVE,BLMOVE,LMPOP,BLMPOP,
##ZSet
ZINTER,ZINTERSTORE,ZINTERCARD,ZUNION,ZUNIONSTORE,ZDIFF,ZDIFFSTORE,
BZPOPMAX,BZPOPMIN,ZRANGESTORE,ZMPOP,BZMPOP,
##HyperLogLog
PFCOUNT,PFMERGE,
##BitMap
BITOP,
##Script
EVAL,EVALSHA,EVAL_RO,EVALSHA_RO,SCRIPT,
##Stream
XREADGROUP,XREAD,
##Geo
GEOSEARCHSTORE,
##TairZSet
EXZUNIONSTORE,EXZUNION,EXZINTERSTORE,EXZINTER,EXZINTERCARD,
EXZDIFFSTORE,EXZDIFF,EXBZPOPMIN,EXBZPOPMAX,
- 部分支持1
当且仅当路由不是自定义分片
备注:1.1.4开始,PUBSUB命令支持双写(会订阅双写中的第一个地址,发布给双写中的所有地址)
##PUBSUB(will sub first write redis resource, pub all write redis resource)
SUBSCRIBE,PUBLISH,UNSUBSCRIBE,PSUBSCRIBE,PUNSUBSCRIBE,PUBSUB,
- 部分支持2
当且仅当路由后端是单个redis-standalone或者单个redis-sentinel或者单个redis-cluster
备注:1.2.6版本开始,TRANSACTION支持双写,但是要求读地址只能配置一个,且必须和写地址的第一个是一样的
##TRANSACTION(keys must in same slot)
MULTI,DISCARD,EXEC,WATCH,UNWATCH,
##RedisSearch
FT.LIST,FT.AGGREGATE,FT.ALIASADD,FT.ALIASDEL,FT.ALIASUPDATE,FT.ALTER,FT.CONFIG,
FT.CREATE,FT.CURSOR,FT.DICTADD,FT.DICTDEL,FT.DICTDUMP,FT.DROPINDEX,FT.EXPLAIN,
FT.EXPLAINCLI,FT.INFO,FT.PROFILE,FT.SEARCH,FT.SPELLCHECK,FT.SYNDUMP,
FT.SYNUPDATE,FT.TAGVALS,
- 部分支持3 当且仅当路由后端是单个redis-standalone或者单个redis-sentinel
##DataBase
KEYS,RANDOMKEY,
- 部分支持4 仅支持部分参数
##DataBase
#if upstream contains redis-cluster, only support 'select 0', other-wise, support select xx
SELECT,
#only support 'CONFIG GET XXX'
CONFIG,
#only support RESP2
HELLO,
#only proxy start with cluster-mode support
#only support: 'cluster info', 'cluster nodes', 'cluser slots', 'cluser proxy_heartbeat'
CLUSTER,
#direct reply OK for proxy start with cluster-mode
ASKING,
#proxy info
INFO,
- 首先创建一个spring-boot的工程,然后添加以下依赖(最新1.2.9),如下:(see sample-code):
<dependency>
<groupId>com.netease.nim</groupId>
<artifactId>camellia-redis-proxy-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
- 编写主类Application.java, 如下:
import com.netease.nim.camellia.redis.proxy.springboot.EnableCamelliaRedisProxyServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableCamelliaRedisProxyServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
- 配置application.yml, 如下:
server:
port: 6380
spring:
application:
name: camellia-redis-proxy-server
camellia-redis-proxy:
password: pass123
transpond:
type: local
local:
type: simple
resource: redis-cluster://@127.0.0.1:6379,127.0.0.1:6378,127.0.0.1:6377
- 启动Application.java即可. 你可以使用redis-cli去连接proxy,端口是6380,密码是pass123(如果不需要密码,则在application.yml里去掉这一行即可)
➜ ~ ./redis-cli -h 127.0.0.1 -p 6380 -a pass123
127.0.0.1:6380> set k1 v1
OK
127.0.0.1:6380> get k1
"v1"
127.0.0.1:6380> mget k1 k2 k3
1) "v1"
2) (nil)
3) (nil)
具体可见:代码结构
路由配置表示了camellia-redis-proxy在收到客户端的redis命令之后的转发规则,包括:
- 最简单的示例
- 支持的后端redis类型
- 动态配置和复杂配置(读写分离、分片等)
- 多租户支持
- 使用camellia-dashboard管理多租户动态路由
- 集成ProxyRouteConfUpdater自定义管理多租户动态路由
具体可见:路由配置
- 1.1.x版本开始,重构了监控、大key、热key等功能,统一作为插件化体系的一部分,用户可以通过简单的配置按需引入内置的插件
- 插件使用统一的接口来拦截和控制请求和响应
- proxy内置了很多插件,可以通过简单配置后即可直接使用
- 你也可以实现自定义插件
具体可见:插件
在生产环境,需要部署至少2个proxy实例来保证高可用,并且proxy是可以水平扩展的,包括:
- 部署模式
- 集成Zookeeper
- 伪redis-cluster模式
- 随机端口
- 优雅上下线
- 客户端接入(java之jedis)
- 客户端接入(java之SpringRedisTemplate)
- 客户端接入(其他语言)
- 注意事项(容器环境部署)
- 部署最佳实践
具体可见:部署和接入
camellia-redis-proxy提供了丰富的监控功能,包括:
- 提供的监控项
- 监控数据获取方式
- 通过info命令获取服务器相关信息
- 把proxy当做一个监控redis集群状态的平台(通过http接口暴露)
- 使用prometheus和grafana监控proxy集群
具体可见:监控
- 如何控制客户端连接数,具体见客户端连接控制
- netty配置,具体见:netty-conf
- 使用nacos托管proxy配置,具体见:nacos-conf
- 关于双(多)写的若干问题,具体见:multi-write
- 关于scan和相关说明,具体见:scan
- 关于lua的相关说明,具体见:lua
- 使用redis-shake进行数据迁移的说明,具体见:redis-shake
- 关于自定义分片函数,具体见:sharding
- 如何使用spring管理bean生成,具体见:spring
- 关于多租户的一个完整示例,具体见:multi-telant
- 多读场景下自动摘除故障读节点读,具体见:multi-read
- 关于ProxyDynamicConf(camellia-redis-proxy.properties),具体见:dynamic-conf
- 业务开始使用redis-standalone或者redis-sentinel,现在需要切换到redis-cluster,但是客户端需要改造(比如jedis访问redis-sentinel和redis-cluster是不一样的),此时你可以使用proxy,从而做到不改造(使用四层代理LB)或者很少的改造(使用注册中心)
- 使用双写功能进行集群的迁移或者灾备
- 使用分片功能应对单集群容量不足的问题(单个redis-cluster集群有节点和容量上限)
- 使用内建或者自定义的插件监控和控制客户端的访问(热key、大key、ip黑白名单、速率控制、key命名空间、数据加解密等)
- 使用丰富的监控功能控制系统的运行
- 等等
基于v1.0.37的性能测试报告
performance-redis-standalone
performance-redis-cluster