twemproxy 配置详解不支持keys pattern怎么办

博主最新文章
博主热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)博客分类:
目前对于互联网公司不使用Redis的很少,Redis不仅仅可以作为key-value缓存,而且提供了丰富的数据结果如set、list、map等,可以实现很多复杂的功能;但是Redis本身主要用作内存缓存,不适合做持久化存储,因此目前有如SSDB、ARDB等,还有如京东的JIMDB,它们都支持Redis协议,可以支持Redis客户端直接访问;而这些持久化存储大多数使用了如LevelDB、RocksDB、LMDB持久化引擎来实现数据的持久化存储;京东的JIMDB主要分为两个版本:LevelDB和LMDB,而我们看到的京东商品详情页就是使用LMDB引擎作为存储的,可以实现海量KV存储;当然SSDB在京东内部也有些部门在使用;另外调研过得如豆瓣的beansDB也是很不错的。具体这些持久化引擎之间的区别可以自行查找资料学习。
Twemproxy是一个Redis/Memcached代理中间件,可以实现诸如分片逻辑、HashTag、减少连接数等功能;尤其在有大量应用服务器的场景下Twemproxy的角色就凸显了,能有效减少连接数。
Redis安装与使用
1、下载redis并安装
cd /usr/servers/
wget https://github.com/antirez/redis/archive/2.8.19.tar.gz
tar -xvf 2.8.19.tar.gz
cd redis-2.8.19/
通过如上步骤构建完毕。
2、后台启动Redis服务器
nohup /usr/servers/redis-2.8.19/src/redis-server
/usr/servers/redis-2.8.19/redis.conf &
3、查看是否启动成功
ps -aux | grep redis
4、进入客户端
/usr/servers/redis-2.8.19/src/redis-cli
5、执行如下命令
127.0.0.1:6379& set i 1
127.0.0.1:6379& get i
通过如上命令可以看到我们的Redis安装成功。更多细节请参考。
SSDB安装与使用
1、下载SSDB并安装
#首先确保安装了g++,如果没有安装,如ubuntu可以使用如下命令安装
apt-get install g++
cd /usr/servers
wget https://github.com/ideawu/ssdb/archive/1.8.0.tar.gz
tar -xvf 1.8.0.tar.gz
2、后台启动SSDB服务器
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/servers/ssdb-1.8.0/ssdb.conf &
3、查看是否启动成功
ps -aux | grep ssdb
4、进入客户端
/usr/servers/ssdb-1.8.0/tools/ssdb-cli -p 8888
/usr/servers/redis-2.8.19/src/redis-cli
因为SSDB支持Redis协议,所以用Redis客户端也可以访问
5、执行如下命令
127.0.0.1:8888& set i 1
127.0.0.1:8888& get i
安装过程中遇到错误请参考;对于SSDB的配置请参考官方文档。
Twemproxy安装与使用
首先需要安装autoconf、automake、libtool工具,比如ubuntu可以使用如下命令安装
apt-get install autoconf automake
apt-get install libtool
1、下载Twemproxy并安装
cd /usr/servers
wget https://github.com/twitter/twemproxy/archive/v0.4.0.tar.gz
tar -xvf v0.4.0.tar.gz
cd twemproxy-0.4.0/
autoreconf -fvi
./configure && make
此处根据要注意,如上安装方式在有些服务器上可能在大量如mset时可能导致Twemproxy崩溃,需要使用如 CFLAGS="-O1" ./configure && make或CFLAGS="-O3 -fno-strict-aliasing" ./configure && make安装。
vim /usr/servers/twemproxy-0.4.0/conf/nutcracker.yml
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
- 127.0.0.1:6379:1
3、启动Twemproxy代理
/usr/servers/twemproxy-0.4.0/src/nutcracker
-d -c /usr/servers/twemproxy-0.4.0/conf/nutcracker.yml
-d指定后台启动
-c指定配置文件;此处我们指定了代理端口为1111,其他配置的含义后续介绍。
4、查看是否启动成功
ps -aux | grep nutcracker
5、进入客户端
/usr/servers/redis-2.8.19/src/redis-cli
6、执行如下命令
127.0.0.1:1111& set i 1
127.0.0.1:1111& get i
Twemproxy文档请参考。
到此基本的安装就完成了。接下来做一些介绍。
#端口设置,默认6379
#日志文件,默认/dev/null
logfile ""
内存大小对应关系
# 1k =& 1000 bytes
# 1kb =& 1024 bytes
# 1m =& 1000000 bytes
# 1gb =& 24 bytes
#设置Redis占用100mb的大小
#如果内存满了就需要按照如相应算法进行删除过期的/最老的
#volatile-lru 根据LRU算法移除设置了过期的key
#allkeys-lru
根据LRU算法移除任何key(包含那些未设置过期时间的key)
#volatile-random/allkeys-&random 使用随机算法而不是LRU进行删除
#volatile-ttl 根据Time-To-Live移除即将过期的key
#noeviction
永不过期,而是报错
maxmemory-policy volatile-lru
#Redis并不是真正的LRU/TTL,而是基于采样进行移除的,即如采样10个数据移除其中最老的/即将过期的
maxmemory-samples 10
而如Memcached是真正的LRU,此处要根据实际情况设置缓存策略,如缓存用户数据时可能带上了过期时间,此时采用volatile-lru即可;而假设我们的数据未设置过期时间,此时可以考虑使用allkeys-lru/allkeys-&random;假设我们的数据不允许从内存删除那就使用noeviction。
内存大小尽量在系统内存的60%~80%之间,因为如客户端、主从时复制时都需要缓存区的,这些也是耗费系统内存的。
Redis本身是单线程的,因此我们可以设置每个实例在6-8GB之间,通过启动更多的实例提高吞吐量。如128GB的我们可以开启8GB * 10个实例,充分利用多核CPU。
实际项目时,为了提高吞吐量,我们使用主从策略,即数据写到主Redis,读的时候从从Redis上读,这样可以通过挂载更多的从来提高吞吐量。而且可以通过主从机制,在叶子节点开启持久化方式防止数据丢失。
#在配置文件中挂载主从,不推荐这种方式,我们实际应用时Redis可能是会宕机的
slaveof masterIP masterPort
#从是否只读,默认yes
slave-read-only yes
#当从失去与主的连接或者复制正在进行时,从是响应客户端(可能返回过期的数据)还是返回“SYNC with master in progress”错误,默认yes响应客户端
slave-serve-stale-data yes
#从库按照默认10s的周期向主库发送PING测试连通性
repl-ping-slave-period 10
#设置复制超时时间(SYNC期间批量I/O传输、PING的超时时间),确保此值大于repl-ping-slave-period
#repl-timeout 60
#当从断开与主的连接时的复制缓存区,仅当第一个从断开时创建一个,缓存区越大从断开的时间可以持续越长
# repl-backlog-size 1mb
#当从与主断开持续多久时清空复制缓存区,此时从就需要全量复制了,如果设置为0将永不清空
# repl-backlog-ttl 3600
#slave客户端缓存区,如果缓存区超过256mb将直接断开与从的连接,如果持续60秒超过64mb也会断开与从的连接
client-output-buffer-limit slave 256mb 64mb 60
此处需要根据实际情况设置client-output-buffer-limit slave和 repl-backlog-size;比如如果网络环境不好,从与主经常断开,而每次设置的数据都特别大而且速度特别快(大量设置html片段)那么就需要加大repl-backlog-size。
cd /usr/servers/redis-2.8.19
cp redis.conf redis_6660.conf
cp redis.conf redis_6661.conf
vim redis_6660.conf
vim redis_6661.conf
将端口分别改为port 6660和port 6661,然后启动
nohup /usr/servers/redis-2.8.19/src/redis-server
/usr/servers/redis-2.8.19/redis_6660.conf &
nohup /usr/servers/redis-2.8.19/src/redis-server
/usr/servers/redis-2.8.19/redis_6661.conf &
查看是否启动
ps -aux | grep redis
进入从客户端,挂主
/usr/servers/redis-2.8.19/src/redis-cli
127.0.0.1:6661& slaveof 127.0.0.1 6660
127.0.0.1:6661& info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6660
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:57
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
/usr/servers/redis-2.8.19# /usr/servers/redis-2.8.19/src/redis-cli
127.0.0.1:6660& info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6661,state=online,offset=85,lag=1
master_repl_offset:85
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:84
127.0.0.1:6660& set i 1
/usr/servers/redis-2.8.19/src/redis-cli
127.0.0.1:6661& get i
此时可以看到主从挂载成功,可以进行主从复制了。使用slaveof no one断开主从。
Redis持久化
Redis虽然不适合做持久化存储,但是为了防止数据丢失有时需要进行持久化存储,此时可以挂载一个从(叶子节点)只进行持久化存储工作,这样假设其他服务器挂了,我们可以通过这个节点进行数据恢复。
Redis持久化有RDB快照模式和AOF追加模式,根据自己需求进行选择。
#格式save seconds changes 即N秒变更N次则保存,从如下默认配置可以看到丢失数据的周期很长,通过save “” 配置可以完全禁用此持久化
save 900 1
save 300 10
save 60 10000
#RDB是否进行压缩,压缩耗CPU但是可以减少存储大小
rdbcompression yes
#RDB保存的位置,默认当前位置
#RDB保存的数据库名称
dbfilename dump.rdb
#不使用AOF模式,即RDB模式
appendonly no
可以通过set一个数据,然后很快的kill掉redis进程然后再启动会发现数据丢失了。
AOF(append only file)即文件追加模式,即把每一个用户操作的命令保存下来,这样就会存在好多重复的命令导致恢复时间过长,那么可以通过相应的配置定期进行AOF重写来减少重复。
appendonly yes
#AOF保存的位置,默认当前位置
#AOF保存的数据库名称
appendfilename appendonly.aof
#持久化策略,默认每秒fsync一次,也可以选择always即每次操作都进行持久化,或者no表示不进行持久化而是借助操作系统的同步将缓存区数据写到磁盘
appendfsync everysec
#AOF重写策略(同时满足如下两个策略进行重写)
#当AOF文件大小占到初始文件大小的多少百分比时进行重写
auto-aof-rewrite-percentage 100
#触发重写的最小文件大小
auto-aof-rewrite-min-size 64mb
#为减少磁盘操作,暂缓重写阶段的磁盘同步
no-appendfsync-on-rewrite no
此处的appendfsync everysec可以认为是RDB和AOF的一个折中方案。
#当bgsave出错时停止写(MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk.),遇到该错误可以暂时改为no,当写成功后再改回yes
stop-writes-on-bgsave-error yes
更多Redis持久化请参考。
Redis动态调整配置
获取maxmemory(10mb)
127.0.0.1:6660& config get maxmemory
1) "maxmemory"
设置新的maxmemory(20mb)
127.0.0.1:6660& config set maxmemory
但是此时重启redis后该配置会丢失,可以执行如下命令重写配置文件
127.0.0.1:6660& config rewrite
注意:此时所以配置包括主从配置都会重写。
Redis执行Lua脚本
Redis客户端支持解析和处理lua脚本,因为Redis的单线程机制,我们可以借助Lua脚本实现一些原子操作,如扣减库存/红包之类的。此处不建议使用直接发送lua脚本到客户端,因为其每次都会进行Lua脚本的解析,而是使用+ 进行操作。未来不知道是否会用luajit来代替lua,让redis lua脚本性能更强。
到此基本的Redis知识就讲完了。
Twemproxy设置
一旦涉及到一台物理机无法存储的情况就需要考虑使用分片机制将数据存储到多台服务器,可以说是Redis集群;如果客户端都是如Java没什么问题,但是如果有多种类型客户端(如PHP、C)等也要使用那么需要保证它们的分片逻辑是一样的;另外随着客户端的增加,连接数也会随之增多,发展到一定地步肯定会出现连接数不够用的;此时Twemproxy就可以上场了。主要作用:分片、减少连接数。另外还提供了Hash Tag机制来帮助我们将相似的数据存储到同一个分片。另外也可以参考豌豆荚的。
其使用YML语法,如
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
timeout:1000
redis: true
- 127.0.0.1:6660:1
- 127.0.0.1:6661:1
server1:是给当前分片配置起的名字,一个配置文件可以有多个分片配置;
listen : 监听的ip和端口;
hash:散列算法;
distribution:分片算法,比如一致性Hash/取模;
timeout:连接后端Redis或接收响应的超时时间;
redis:是否是redis代理,如果是false则是memcached代理;
servers:代理的服务器列表,该列表会使用distribution配置的分片算法进行分片;
hash算法:
one_at_a_time
crc32 (crc32 implementation compatible with )
crc32a (correct crc32 implementation as per the spec)
分片算法:
ketama(一致性Hash算法)
modula(取模)
random(随机算法)
服务器列表
- ip:port:weight alias
- 127.0.0.1:6660:1
- 127.0.0.1:6661:1
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
推荐使用后一种方式,默认情况下使用ip:port:weight进行散列并分片,这样假设服务器宕机换上新的服务器,那么此时得到的散列值就不一样了,因此建议给每个配置起一个别名来保证映射到自己想要的服务器。即如果不使用一致性Hash算法来作缓存服务器,而是作持久化存储服务器时就更有必要了(即不存在服务器下线的情况,即使服务器ip:port不一样但仍然要得到一样的分片结果)。
比如一个商品有:商品基本信息(p:id:)、商品介绍(d:id:)、颜色尺码(c:id:)等,假设我们存储时不采用HashTag将会导致这些数据不会存储到一个分片,而是分散到多个分片,这样获取时将需要从多个分片获取数据进行合并,无法进行mget;那么如果有了HashTag,那么可以使用“::”中间的数据做分片逻辑,这样id一样的将会分到一个分片。
nutcracker.yml配置如下
listen: 127.0.0.1:1111
hash: fnv1a_64
distribution: ketama
redis: true
hash_tag: "::"
- 127.0.0.1:6660:1 server1
- 127.0.0.1:6661:1 server2
连接Twemproxy
/usr/servers/redis-2.8.19/src/redis-cli
127.0.0.1:1111& set p:12: 1
127.0.0.1:1111& set d:12: 1
127.0.0.1:1111& set c:12: 1
在我的服务器上可以连接6660端口
/usr/servers/redis-2.8.19/src/redis-cli
127.0.0.1:6660& get p:12:
127.0.0.1:6660& get d:12:
127.0.0.1:6660& get c:12:
一致性Hash与服务器宕机
如果我们把Redis服务器作为缓存服务器并使用一致性Hash进行分片,当有服务器宕机时需要自动从一致性Hash环上摘掉,或者其上线后自动加上,此时就需要如下配置:
#是否在节点故障无法响应时自动摘除该节点,如果作为存储需要设置为为false
auto_eject_hosts: true
#重试时间(毫秒),重新连接一个临时摘掉的故障节点的间隔,如果判断节点正常会自动加到一致性Hash环上
server_retry_timeout: 30000
#节点故障无法响应多少次从一致性Hash环临时摘掉它,默认是2
server_failure_limit: 2
支持的Redis命令
不是所有Redis命令都支持,请参考。
因为我们所有的Twemproxy配置文件规则都是一样的,因此我们应该将其移到我们项目中。
cp /usr/servers/twemproxy-0.4.0/conf/nutcracker.yml
/usr/example/
另外Twemproxy提供了启动/重启/停止脚本方便操作,但是需要修改配置文件位置为/usr/example/nutcracker.yml。
chmod +x /usr/servers/twemproxy-0.4.0/scripts/nutcracker.init
vim /usr/servers/twemproxy-0.4.0/scripts/nutcracker.init
将OPTIONS改为
OPTIONS="-d -c /usr/example/nutcracker.yml"
另外注释掉. /etc/rc.d/init.d/functions;将daemon --user ${USER} ${prog} $OPTIONS改为${prog} $OPTIONS;将killproc改为killall。
这样就可以使用如下脚本进行启动、重启、停止了。
/usr/servers/twemproxy-0.4.0/scripts/nutcracker.init {start|stop|status|restart|reload|condrestart}
对于扩容最简单的办法是:
1、创建新的集群;
2、双写两个集群;
3、把数据从老集群迁移到新集群(不存在才设置值,防止覆盖新的值);
4、复制速度要根据实际情况调整,不能影响老集群的性能;
5、切换到新集群即可,如果使用Twemproxy代理层的话,可以做到迁移对读的应用透明。
浏览 26384
如果有3台redis服务器用Twemproxy 做了代理集群的话,如果其中1台redis 挂了如何做故障转移的,不影响正常的使用,自动变成访问2台正常。自动剔除故障的redis呢作为存储的时候
qingfengjushi1 写道jinnianshilongnian 写道qingfengjushi1 写道博主您好,用Twemproxy做redis集群有一处不明,当nutcracker.yml中设置- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2分片规则后,会有一部分数据写入到从设备中,而此时从设备是只读的,无法写入,是需要将从设备设置为可写入吗?另外,分片后主从设备的数据按说是不一样的,两边是如何做同步的?不会写入从啊,写的时候只写主这点还真有点糊涂了,博主的意思是不是在你的例子中nutcracker.yml配置了hash_tag: "::"哈希规则后,set数据又是类似于p:12: d:12:时会根据规则找到同一台设备,但如果set数据是不规则的时候,不会分散写入么?如果不会那配置- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2分片还有什么意义?可能是我理解错了?配置的- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2并不是主从关系?只是和之前的主从一节混了,所以认为6661是从了可以配置两个: 一个只有主用于写; 另一个主从混合,但是只读
jinnianshilongnian 写道qingfengjushi1 写道博主您好,用Twemproxy做redis集群有一处不明,当nutcracker.yml中设置- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2分片规则后,会有一部分数据写入到从设备中,而此时从设备是只读的,无法写入,是需要将从设备设置为可写入吗?另外,分片后主从设备的数据按说是不一样的,两边是如何做同步的?不会写入从啊,写的时候只写主这点还真有点糊涂了,博主的意思是不是在你的例子中nutcracker.yml配置了hash_tag: "::"哈希规则后,set数据又是类似于p:12: d:12:时会根据规则找到同一台设备,但如果set数据是不规则的时候,不会分散写入么?如果不会那配置- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2分片还有什么意义?可能是我理解错了?配置的- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2并不是主从关系?只是和之前的主从一节混了,所以认为6661是从了
qingfengjushi1 写道博主您好,用Twemproxy做redis集群有一处不明,当nutcracker.yml中设置- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2分片规则后,会有一部分数据写入到从设备中,而此时从设备是只读的,无法写入,是需要将从设备设置为可写入吗?另外,分片后主从设备的数据按说是不一样的,两边是如何做同步的?不会写入从啊,写的时候只写主这点还真有点糊涂了,博主的意思是不是在你的例子中nutcracker.yml配置了hash_tag: "::"哈希规则后,set数据又是类似于p:12: d:12:时会根据规则找到同一台设备,但如果set数据是不规则的时候,不会分散写入么?如果不会那配置- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2分片还有什么意义?
博主您好,用Twemproxy做redis集群有一处不明,当nutcracker.yml中设置- 127.0.0.1:6660:1 server1& - 127.0.0.1:6661:1 server2分片规则后,会有一部分数据写入到从设备中,而此时从设备是只读的,无法写入,是需要将从设备设置为可写入吗?另外,分片后主从设备的数据按说是不一样的,两边是如何做同步的?不会写入从啊,写的时候只写主
将OPTIONS改为OPTIONS="-d -c /usr/example/nutcracker.yml" 另外注释掉. /etc/rc.d/init.d/functions;将daemon --user ${USER} ${prog} $OPTIONS改为${prog} $OPTIONS;将killproc改为killall。 这样就可以使用如下脚本进行启动、重启、停止了。/usr/servers/twemproxy-0.4.0/scripts/nutcracker.init {start|stop|status|restart|reload|condrestart}这里我改完在用上面的脚本启动报daemon:not found twemproxy启动不起来了可能需要将/usr/servers/twemproxy-0.4.0/src路径配置到环境变量中
想问下,你是意思是说同时使用三种nosql DB?看场景
博主的意思是Redis+Twemproxy配合使用或者是SSDB+Twemproxy配合使用对吗?而不是SSDB+Redis+Twemproxy,因为SSDB其实是为取代redis而生的。是的,SSDB不是为取代redis,而是应用不同的场景:持久化。
jinnianshilongnian
浏览量:1926818
浏览量:2431940
浏览量:5035311
浏览量:194384
浏览量:1385265
浏览量:203762
浏览量:4526329
浏览量:536765
浏览量:584017
1、org.springframework.aop.suppo ...
Unable to set property 'passwor ...
基于shiro最新版、SpringBoot开发的一套权限系统, ...
cx 写道adc333 写道为什么我验证“l ...
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'麦子迈 | 存储分片和Twemproxy核心解读
(又称为nutcracker)是一个轻量级的Redis和Memcached代理,主要用来减少对后端缓存服务器的连接数。由Twitter开源出来的缓存服务器集群管理工具,主要用来弥补Redis和Memcached对集群(cluster)管理指出的不足。
antirez(Redis作者)写过一篇对twemproxy的介绍,他认为twemproxy是目前Redis 分片管理的最好方案,虽然antirez的Redis cluster正在实现并且对其给予厚望,但是我从现有的cluster实现上还是认为cluster除了增加Redis复杂度,对于集群的管理没有twemproxy来的轻量和有效。
谈到集群管理不得不又说到数据的分片管理,为了满足数据的日益增长和扩展性,数据存储系统一般都需要进行一定的分片,如传统的MySQL进行横向分表和纵向分表,然后应用程序访问正确的位置就需要找的正确的表。这时候,这个数据定向工作一般有三个位置可以放,数据存储系统本身支持,服务器端和客户端中间建代理支持或者客户端支持。Redis Cluster就是典型的试图在数据存储系统上支持分片,而twemproxy就是试图在服务器端和客户端中间建代理支持,Memcached的客户端对分片的支持就是客户端层面的。
在三种方案中,客户端方案我认为欠妥,因为这样每个客户端需要维护一定的服务器信息,但是如果动态的增加或减少节点就需要重写配置各个客户端。而在服务器端增加集群管理有利于使用者,减少使用者需要了解的东西,整合集群管理使得性能比其他方案都要更高,但是缺点是其会严重增加代码复杂度,导致服务器端代码爆炸。而采用中间层代理的方式我认为是最优雅和有效的,在不改动服务器端程序的情况下,twemproxy使得集群管理更简单,去除不支持的操作和合并,同时更可以支持多个后端服务,大大减少连接数等等,但是缺点也是显而易见的,它不能更有效的利用集群的优势,如多键运算和范围查找操作等等,这都是需要服务器端程序本身支持。
最后,如果就Redis而言,我认为最好的方式是在Redis的基础上做一个代理计算层,也就是所有的操作通过这个代理计算层进行对Redis集群的操作,也就是一个Master-Slave结构的Redis集群,因为Redis作为一个后端服务,本身连接数不宜过多,通过多Slave备份Master达到的效果比无核心节点我认为更好。
回到Twemproxy,它主要通过事件驱动模型来达到高并发,每收到一个请求,通过解析请求,发送请求到后端服务,再等待回应,发送回请求方。主要涉及到三个重要的结构:server, connection, message。
struct server {
/* server index */
struct server_pool *
/* owner pool */
/* name:port:weight (ref in conf_server) */
/* name (ref in conf_server) */
/* port */
/* weight */
/* socket family */
/* socket length */
struct sockaddr
/* socket address (ref in conf_server) */
ns_conn_q;
/* # server connection */
struct conn_tqh
/* server connection q */
/* next retry time in usec */
failure_ /* # consecutive failures */
每个server其实就是一个后端的缓存服务程序,Twemproxy可以预先连接每个server或者不,根据接收到的请求具体分析出key,然后根据key来选择适当的server,这里的选择过程采用一致性哈希算法,具体算法可以根据配置文件选择。
connection
connection在Twemproxy中非常重要,它分为三种类型的connection:proxy,client和server,也就是监听的socket,客户端连接的socket和连接后端的socket,其中proxy类型的工作比较简单,就是接受到请求,然后产生一个client connection或者server connection。
struct conn {
TAILQ_ENTRY(conn)
/* link in server_pool / server / free q */
/* connection owner - server_pool / server */
/* socket descriptor */
/* socket address family */
/* socket length */
struct sockaddr
/* socket address (ref in server or server_pool) */
struct msg_tqh
/* incoming request Q */
struct msg_tqh
/* outstanding request Q */
struct msg
/* current message being rcvd */
struct msg
/* current message being sent */
conn_recv_
/* recv (read) handler */
conn_recv_next_t
/* recv next message handler */
conn_recv_done_t
/* read done handler */
conn_send_
/* send (write) handler */
conn_send_next_t
/* write next message handler */
conn_send_done_t
/* write done handler */
conn_close_
/* close handler */
conn_active_
/* active? handler */
/* connection reference handler */
conn_unref_
/* connection unreference handler */
conn_msgq_t
/* connection inq msg enqueue handler */
conn_msgq_t
/* connection inq msg dequeue handler */
conn_msgq_t
/* connection outq msg enqueue handler */
conn_msgq_t
/* connection outq msg dequeue handler */
/* received (read) bytes */
/* sent (written) bytes */
/* connection io events */
/* connection errno */
recv_active:1; /* recv active? */
recv_ready:1;
/* recv ready? */
send_active:1; /* send active? */
send_ready:1;
/* send ready? */
/* client? or server? */
/* proxy? */
connecting:1;
/* connecting? */
connected:1;
/* connected? */
/* eof? aka passive close? */
/* done? aka close? */
/* redis? */
可以用面向对象的方法来考虑这个结构,struct conn就是个基类,而proxy,client和server是派生类,通过recv_done, recv_next, recv和send_done, send_next, send来达到多态的效果。
当客户端建立与Twemproxy的连接或者Twemproxy建立与后端服务的连接后,会生成一个conn结构,或为client或为server类。
struct msg {
TAILQ_ENTRY(msg)
/* link in client q */
TAILQ_ENTRY(msg)
/* link in server q */
TAILQ_ENTRY(msg)
/* link in send q / free q */
/* message id */
struct msg
/* message peer */
struct conn
/* message owner - client | server */
struct rbnode
/* entry in rbtree */
/* message mbuf header */
/* message length */
/* current parser state */
/* parser position marker */
/* token marker */
msg_parse_
/* message parser */
msg_parse_result_
/* message parsing result */
mbuf_copy_t
/* message pre-split copy */
msg_post_splitcopy_t post_
/* message post-split copy */
msg_coalesce_t
/* message pre-coalesce */
msg_coalesce_t
/* message post-coalesce */
/* message type */
/* key start */
/* key end */
/* value length (memcache) */
/* end marker (memcache) */
/* narg start (redis) */
/* narg end (redis) */
/* # arguments (redis) */
/* running # arg used by parsing fsa (redis) */
/* running length in parsing fsa (redis) */
/* integer reply value (redis) */
struct msg
/* owner of fragment message */
/* # fragment */
/* id of fragmented message */
/* errno on error? */
/* error? */
/* one or more fragments are in error? */
request:1;
/* request? or response? */
/* quit request? */
noreply:1;
/* noreply? */
/* done? */
/* all fragments are done? */
first_fragment:1;/* first fragment? */
last_fragment:1; /* last fragment? */
swallow:1;
/* swallow response? */
/* redis? */
struct msg是连接建立后的消息内容发送载体,这个复杂的msg结构很大程度是因为需要实现pipeline的效果,多个msg属于同一个conn,conn通过接收到内容解析来发现几个不同的msg。
static void
core_core(struct context *ctx, struct conn *conn, uint32_t events)
log_debug(LOG_VVERB, "event %04"PRIX32" on %c %d", events,
conn->client ? 'c' : (conn->proxy ? 'p' : 's'), conn->sd);
conn->events =
/* read takes precedence over write */
if (events & EVENT_READABLE) {
status = core_recv(ctx, conn);
if (status != NC_OK || conn->done || conn->err) {
core_close(ctx, conn);
if (events & EVENT_WRITABLE) {
status = core_send(ctx, conn);
if (status != NC_OK || conn->done || conn->err) {
core_close(ctx, conn);
事件驱动库会绑定每一个socket fd到一个conn,当检测到可读时,会调用conn->recv来推动,如果是proxy的fd可读,这时conn->recv的工作就是建立一个新的连接来接收消息。如果client的fd可读,其对应的conn的conn->recv的工作就是接收内容并分析。如果server的fd可读时,其对应的server conn的conn->recv的工作是接收到后端服务的回应,然后再回发给请求方。
Twemproxy的架构比较清晰,对Twemproxy源码印象较深的是对logging的合理布局和错误处理的清晰,这是第一次看大公司开源出来的代码,非常重视logging和错误处理。
由于Twitter开源的Twemproxy直接使用epoll驱动,导致其他不支持epoll的系统无法使用,因此我fork了一个版本,加入了kqueue支持,让FreeBSD和Mac os x能够成功编译运行,并且因为Twemproxy不合理的将epoll嵌入到业务逻辑中,因此,只能大改事件驱动的相关代码,将复用IO的结构抽象出来,并且增加了部分结构。

我要回帖

更多关于 twemproxy redis 的文章

 

随机推荐