pika支持codis的slot迁移

概述

pika使用redis协议,命令操作和redis一致,因而可以直接使用codis作为pika的集群解决方案,但这里需要解决一些问题,主要是pika要支持codis的slot迁移。而codis的slot迁移分为两种方式:sync和semi-async。本篇文章主要分三部分说明:一、pika作为codis 2.0的后端server;二、pika作为codis 3.2的后端server;三、pika作为codis server快速扩容迁移方法。

pika作为codis 2.0后端server

在codis 2.0及以前的版本,codis使用sync模式slot迁移,而且codis proxy和后端server是使用单链接,所有pika如果作为codis 2.0的后端server需要解决两个问题:1.单连接性能问题;2.sync slot迁移,具体如下:

单连接限制性能

官方版本的codis proxy和后端server是单连接,即一个proxy和后端server只有一个连接,为保障操作的顺序性。单连接的实现机制在后端server是redis时对整体性能无太大损耗,但当后端server是pika或者ssdb时,性能下降严重(经过多次测试定位,确定为请求都block在codis的请求队列处,原因应该是pika或者ssdb处理请求性能低于redis,导致一个请求花费更多的时间,但由于pika或者ssdb均是多线程的,所以如果codis proxy和后端server是多连接的,可以解决该性能瓶颈)

解决办法

codis保障操作的顺序性,也就是要保证每个key操作的顺序性,每个key在codis里面是有一个固定的slot = crc32(key)%1024,所以如果我们能保证一个slot用一个连接,那就可以保证了key操作的顺序性。在研究codis 2.0源码后,实现了每个slot对应一个proxy和后端server连接,为能够依然支持官方版本的单连接,将proxy和后端server连接方式,通过配置文件可以进行配置,在config.ini中设置如下的配置项,backend_connection_model=server为单连接,为slot是多连接:

# Proxy connections number model with backend server: server/slot, server means only one connection between proxy and backend server, 
# slot means every slot has one connection between proxy and backend server, default is server
backend_connection_model=server

在使用多连接的模式下,经测试性能是原来单连接性能的2~3倍左右。曾为官方提交过pr (codis支持多连接),作者回复codis 2.0作为稳定分支,只修bug,不再添加feature,所以就没继续在这事上进行下去,所以codis 2.0版本如果想使用多连接模式,可以从https://github.com/left2right/codis branch multi-backen-conns进行编译。这里有个坑点,codis使用vendor管理依赖库,所以可以按如下方式进行编译:

$ mkdir -p $GOPATH/src/github.com/CodisLabs
$ git clone https://github.com/left2right/codis -b multi-backen-conns
$ cd codis
$ make

pika支持sync slot迁移

pika使用redis 协议,操作命令和redis一致,理论上可以直接挂载在codis proxy后面,经过测试使用pika官方版本(https://github.com/Qihoo360/pika),确实可以直接挂载在codis proxy后面做server,但有以下问题:

  1. 不支持 codis的slot迁移,即不能水平扩容节点
  2. 添加server时不能使用hostname,需要使用ip地址,不然不能主从同步(这个根pika作者进行过沟通,目前还没有收到进展,有时间会看看,但目前看使用ip还能接受)
codis sync slot迁移原理

codis自身有个循环线程会不停的检查当前是否有迁移的任务,如果有迁移的任务就主动发迁移命令到相应的后端server上。
数据迁移的最小单位是 key, codis redis 中添加了一些迁移指令, 实现基于key的迁移, 如 slotsmgrtslot, slotsmgrtone, slotsmgrttagslot, slotsmgrttagone , 每次会将特定 slot 一个随机的 key 发送给另外一个 codis redis 实例, 这个命令会确认对方已经接收, 同时删除本地的这个 k-v 键值, 返回这个 slot 的剩余 key 的数量, 整个操作是原子的(数据迁移),如果迁移某slot过程中操作了该slot某个相应的key,则会先运行slotsmgrttagone命令将该key迁移到目的server,然后在目的server运行相应操作
在 codis-config 管理工具中, 每次迁移任务的最小单位是 slot如: 将slot id 为 [0-511] 的slot的数据, 迁移到 server group 2上, --delay 参数表示每迁移一个 key 后 sleep 的毫秒数, 默认是 0, 用于限速.

$ bin/codis-config slot migrate 0 511 2 --delay=10

迁移建议:由于迁移过程中可能出现异常因而建议每次迁移过程只迁移一个slot,迁移完后再迁移下一个slot。如果迁移过程中确实遇到错误,可以通过修改zookeeper相应LOCK数据(这个需要对相应的数据结构有充分了解),或者使用https://github.com/left2right/codis-ha 运行相应的命令,停止迁移的任务(使用这个只是能快速的让服务恢复,但是需要你对整个原理比较了解,而且这后面还要对相应slot的数据进行恢复~~):

$ codis-ha migrate   -s localhost:18087 -z localhost:2181 -k 
pika支持codis sync slot迁移设计实现

为兼容pika官方版本,所以pika支持codis slot迁移实现是修改pika实现的,而不是修改pika的存储库nemo实现。
实现方法是,增加slot key(共1024个),set类型,用户发过来的每个key在进行修改操作(set, del, incr, hset, sadd , lpush, zadd......)时,根据slot = crc32(key)%1024计算出该key对应的的slot,然后将该key sadd或者srem到该slot对应的set key;迁移时,指定slot,从该slot spop(由于pika的spop会触发compact,所以是通过sscan和srem实现spop)出一个key然后迁移到目的的pika server。该实现的优点是和官方版本数据兼容,可以直接进行替换升级,不需要洗数据等,回退也方便。不足之处是每次修改操作均要有额外的set操作,降低了性能(大概降低10%~20%),增加了存储(增加30%左右),为了解决性能降低,存储增加的问题,所以增加了pika支持slot迁移的开关,开关关闭时,性能及存储使用和官方版本一致,打开开关时支持slot迁移。开关可以通过配置文件配置conf/pika.conf:

# slotmigrate [yes | no]
slotmigrate : yes

也可以通过命令行config set进行配置:

$ redis-cli -h 127.0.0.1 -p 9221 config set slotmigrate yes
pika添加命令

codis slot迁移命令见
**slotsmgrtslot / slotsmgrttagslot: **这两个命令 在pika实现一样,随机在某个 slot 下迁移一个 key-value 到目标机器,codis slot迁移时使用的命令, 在slotmigrate为yes时可用

127.0.0.1:9221> slotsmgrttagslot 127.0.0.1 9222 3000 256

**slotsmgrtone / slotsmgrttagone: ** 这两个命令 在pika实现一样,将指定的 key-value 迁移到目标机,codis slot迁移时使用的命令, 在slotmigrate为yes时可用

127.0.0.1:9221> slotsmgrttagslot 127.0.0.1 9222 3000 256

slotsinfo: 返回所有slot的信息 ,后面不跟参数, 在slotmigrate为yes时可用

127.0.0.1:9221> slotsinfo

slotsdel: 只删除相应的slot key,并不删除该slot key里面那些key对应的存储, 在slotmigrate为yes时可用

127.0.0.1:9221> slotsdel 1013 990

slotshashkey: 获得key对应的slot数

127.0.0.1:9221> slotshashkey test1

slotsscan: 获取slot key里面的key,使用方式同sscan, 在slotmigrate为yes时可用

127.0.0.1:9221> slotsscan 256 0

slotsreload: 在slotmigrate为yes时可用,遍历pika存储的数据,并根据各个key 的slot数值,sadd到相应的slot key。设计该命令的目的是平时pika为不支持slot迁移状态(slotmigrate 为no),不降低性能,不增加额外的存储,在需要迁移时运行该命令,在命令运行结束后(该命令会立即返回,但有后台线程进行处理,可通过info命令查看该命令是否运行结束)

127.0.0.1:9221> slotsreload

slotsreloadoff: 在slotmigrate为yes时可用,结束slots reload命令,如果当时请求压力比较大,运行slotsreload命令造成系统响应慢,可通过该命令立即终止后台运行的slotsreload线程

127.0.0.1:9221> slotsreloadoff

pika作为codis 3.2后端server

codis 3.2添加了semi-async模式的slot迁移,大幅度的提升了迁移速度,pika如果作为semi-async模式下的后端server,也需要对该模式的一些迁移命令进行支持。由于codis 3.2已支持proxy和后端server多个连接,所有codis 3.2版本下,不需要解决codis proxy和后端pika多连接的问题,只需要配置codis的时候配置多个链接即可。

codis 3.2 semi-async slot迁移说明

如果要满足codis 3.2 semi-async slot迁移,需要后端server实现两个迁移命令:slotsmgrttagslot-async和slotsmgrt-exec-wrapper。codis循环不停的向后端源server发送slotsmgrttagslot-async命令来迁移数据,而对于某个正在迁移的slot,如果这时有请求的key是处于该slot,则向后端发送并执行slotsmgrt-exec-wrapper命令。也就是slotsmgrttagslot-async命令被循环执行批量的迁移该slot的key,直到该slot没有剩余的key;slotsmgrt-exec-wrapper命令被用来处理请求的key属于正在迁移slot,slotsmgrt-exec-wrapper命令在源server上执行相应的命令(如果该key不存在,则返回0及表示key不存在的信息,如果该key正在迁移中则返回1及key正在被迁移的信息,如果该key存在,且没有被迁移,则返回2及该请求的执行结果)

pika支持semi-async slot迁移

由于pika支持多线程,而不像redis是单线程的,所以pika提升迁移性能要比redis更容易些,因而pika在支持semi-async slot迁移上只是两个迁移命令和codis 3.2兼容,但其实际实现和codis 3.2是不一样的。pika在支持semi-async slot迁移时,是起了一个新的线程负责迁移,每当收到slotsmgrttagslot-async命令时,pika取出对应slot一批key,由迁移线程发送,当这时codis接受到属于该slot的key的请求时,则检查该key是否存在,如果不存在返回0,如果存在则将该key加入到迁移线程的迁移key队列中,返回1,表示迁移中,而不会像codis那样会执行该key的请求,并返回2和对应结果。pika支持semi-async slot迁移和pika支持sync slot迁移在实现上的区别是:后者是一个key一个key进行的迁移,而且每次都要建立新的redis链接;而前者是由一个独立的线程,复用同一个redis链接。这样做即提升了迁移速度,也兼容了codis 3.2对semi-async slot迁移。

pika支持semi-async的迁移命令

除了pika支持sync迁移的一些如slotsinfo,slotsdel等,又添加了四个迁移命令:分别是slotsmgrttagslot-async,slotsmgrt-exec-wrapper,slotsmgrt-async-status,slotsmgrt-async-cancel:
slotsmgrttagslot-async: semi-async模式批量迁移key,使用方法如下:

127.0.0.1:9221> SLOTSMGRTTAGSLOT-ASYNC pika1 9221 5000 200 33554432 518 500

slotsmgrt-exec-wrapper: semi-async模式处理请求的key属于正在迁移slot的请求,使用方法如下:

127.0.0.1:9221> slotsmgrt-exec-wrapper test1 set test1 100

slotsmgrt-async-status: semi-async模式,查看迁移的状态,使用方法如下:

127.0.0.1:9221> slotsmgrt-async-status

slotsmgrt-async-cancel: semi-async模式,在pika层面停止正在进行的迁移,使用方法如下:

127.0.0.1:9221> slotsmgrt-async-cancel

pika作为codis 3.2后端server配置使用

1.codis proxy和后端server配置为多链接(出于性能考虑),修改codis config/proxy.toml文件

backend_primary_parallel = 64
  1. pika打开slotmigrate开关,具体和codis 2.0时,pika的方式一样,如果迁移之前没有打开,则在迁移前,打开slot migrate开关config set slotmigrate yes,然后执行slotsreload,具体和上面一样。
  2. 其他的就不需要操作了,玩的愉快~~

pika作为codis server快速扩容迁移方法

pika支持sync模式slot迁移,迁移速度大概是每秒300key左右,支持semi-async模式slot迁移,迁移速度大概是每秒2000key左右。由于pika数据存储在硬盘上,存储的数据量很大,一个pika实例可能存几十亿的数据,上面两个迁移方法在这样的数量级时,迁移速度不能令人满意及接受。考虑到pika支持多线程,而且pika主从全量同步使用rsync非常快,所以考虑一种快速的扩容迁移方案:假如codis+pika集群,后端原来只有一个group(slot 0~1023均在该group),现在要扩容到两个group(slot 0~512在group1,513~1023在group2),则可以通过主从全量同步的方式(group2 的master pika slaveof group1 masterpika )将所有数据均迁移过去,然后执行后台清理命令,清理多余的数据,经实测,这种方式,扩容迁移速度为每小时100GB数据。

pika支持后台清理slot数据命令

前面介绍了slotsreload命令,该命令会遍历数据库里面所有的key,并根据hash算法函数,将key分别添加到对应slot的set slotkey里面。而pika添加的后台清理slot数据命令slotscleanup,则是遍历数据库里面的key时,如果发现属于被删除slot的key,则删除该key。由于遍历数据库所有的key会对系统产生一些压力,所以在一些压力大的情况下需要将slotscleanup停止掉,则可以执行slotscleanupoff命令,具体如下:
slotscleanup: 后台清理不需要的slot的key,该命令执行后会立即返回,具体清除操作会在后台执行,可以通过info命令查看。如要清除slot 10,11,12,13,14,15的key,则可按如下执行:

127.0.0.1:9221> slotscleanup 10 11 12 13 14 15

slotscleanupoff: 停止后台执行的slot清除操作,使用方法如下:

127.0.0.1:9221> slotscleanupoff

pika快速迁移操作流程

为方便说明,假定该集群扩容前有2个group(1,2),需要扩容到4个group(1,2,3,4),新加group3和4

  1. group 3, group4 的pika server设置slave可写(即关闭slave-read-only)
redis-cli -h ${pika-server} -p $port config set slave-read-only 0
  1. 规划slot分配
    假定扩容迁移之前的分配如下:group1(0,511),group2(512,1023);扩容迁移后slot分配如下:group1(0,255),group3(256,511),group2(512,768),group4(769,1023)。即将group1的slot 256~511迁移到group3,将group2的slot769~1023迁移到group4
  2. 在命令行上将group3的master server作为group1的slave,group4的master作为group2的slave,操作参考如下:
redis-cli -h ${group3-master} -p ${group3-master-port} SLAVEOF ${group1-master } ${group1-master-port}
redis-cli -h ${group4-master} -p ${group4-master-port} SLAVEOF ${group2-master } ${group2-master-port}
  1. 在观察到3步骤的主从数据全量同步完时,在codis dashboard/fe或者命令行将256~511的slot迁移到group3,将slot769~1023迁移到group4(因为这些slot里面没有数据,实际相当于一个切换开关,迁移操作瞬间完成),具体如何迁移slot,参考codis
  2. 关闭group3和group4里面的master和group1和group2的主从关系,
redis-cli -h $group3-master -p $port SLAVEOF no one
redis-cli -h $group4-master -p $port SLAVEOF no one
  1. 清理各个group多余的数据,即group1的256~511的slot的数据,group2的slot769~1023的数据,group3的slot0~255的数据,group4的slot的512~768的数据
###例如清除slot [256,511]的数据
echo `seq 256 511` | xargs redis-cli -h $host -p $port slotscleanup
  1. 执行compact,因为pika删除数据只是将其标记为不可访问,实际从硬盘上删除掉是在compact时,所以在slotscleanup后,应该执行compact
redis-cli -h ${server} -p ${port} compact

备注:由于slotsclanup会遍历数据库,会增加系统的压力,所以为降低风险,上述的操作可以在slave上执行,然后结合codis的主从切换完成。

部署codis pika

codis 2.0 sync slot迁移部署

codis

GitHub:https://github.com/left2right/codis/tree/multi-backen-conns 也可以使用官方codis,只是使用单连接,性能有些损耗
编译部署:
https://github.com/CodisLabs/codis/blob/release2.0/doc/tutorial_zh.md
注意事项:在配置文件中需要配置为支持多连接,如何配置见上面说明。

pika

GitHub:https://github.com/Qihoo360/pika。
编译部署:https://github.com/Qihoo360/pika/wiki/%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8
注意事项:如果想一直支持slot迁移,需要在配置文件中将slotmigrate配置为yes,详情见上面说明

codis 3.2 semi-async slot迁移部署

codis

GitHub:https://github.com/left2right/codis/tree/release3.2-pika,codis 3.2在设置主从时,使用了transaction,而pika不支持transaction,所以对相应的代码做了下修改(https://github.com/left2right/codis/blob/a295f5446a6661e84d20574c8a57455a4d439f48/pkg/utils/redis/client.go),将transaction那部分去掉:

func (c *Client) SetMaster(master string) error {
    host, port, err := net.SplitHostPort(master)
    if err != nil {
        return errors.Trace(err)
    }
    if _, err := c.Do("CONFIG", "SET", "masterauth", c.Auth); err != nil {
        return errors.Trace(err)
    }
    if _, err := c.Do("SLAVEOF", host, port); err != nil {
        return errors.Trace(err)
    }
    /*
        c.conn.Send("MULTI")
        c.conn.Send("CONFIG", "SET", "masterauth", c.Auth)
        c.conn.Send("SLAVEOF", host, port)
        c.conn.Send("CONFIG", "REWRITE")
        c.conn.Send("CLIENT", "KILL", "TYPE", "normal")
        values, err := redigo.Values(c.Do("EXEC"))
        if err != nil {
            return errors.Trace(err)
        }
        for _, r := range values {
            if err, ok := r.(redigo.Error); ok {
                return errors.Trace(err)
            }
        }
    */
    return nil
}

编译部署:
https://github.com/CodisLabs/codis/blob/release3.2/doc/tutorial_zh.md
注意事项:在codis proxy配置文件中需要配置为支持多连接,如何配置见上面说明。

pika

GitHub:https://github.com/left2right/pika/tree/async-and-fast-slot-migrate,已经合进pika官方https://github.com/Qihoo360/pika。
编译部署:https://github.com/Qihoo360/pika/wiki/%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8
注意事项:如果想一直支持semi-async slot迁移,需要在配置文件中将slotmigrate配置为yes,详情见上面说明

codis pika快速迁移扩容部署

codis

codis使用上面2.0和3.2的均可

pika

GitHub:https://github.com/left2right/pika/tree/async-and-fast-slot-migrate,已经合进pika官方https://github.com/Qihoo360/pika。
编译部署:https://github.com/Qihoo360/pika/wiki/%E5%AE%89%E8%A3%85%E4%BD%BF%E7%94%A8
注意事项:如果要使用快速迁移方式,需要关闭slotmigrate,即不需要做任何打开的操作(为了能更快的完成slot迁移,状态切换)

后言

pika在和codis结合使用时,需要留意两个点:

  1. codis proxy和后端pika链接数如果少,不能发挥pika最大性能
  2. pika数据存储在硬盘上,数据量很大,所以迁移速度是一个很重要的问题

而上面这两个问题也就是这篇文章要解决的问题,祝玩的愉快~~

你可能感兴趣的:(pika支持codis的slot迁移)