image欢迎转载。转载请注明出处。
概要
- Redis server 基于5.0.0的stable版本
- Client基于 Jedis 2.9.0
- Scala 基于 2.11.X
本文内容:
- redis集群模式配置参数说明
- 以实例说明如何配置集群模式
- 创建集群模式连接池的scala实现
- 遇到和解决问题
cluster服务配置参数
cluster模式所有节点都是Redis实例,不存在sentinel模式下的监控进程。所以不需要附加配置文件,一切配置信息在redis-port.conf
。如下:
cluster-enabled yes
cluster-config-file nodes-6379.conf #启动该进程对应的配置文件名称
cluster-node-timeout 15000
# cluster-replica-no-failover no
# cluster-require-full-coverage yes
# cluster-migration-barrier 1
如何搭建cluster集群
搭建和配置集群是redis模式中“最复杂的”。
下面就逐步说明:
- 启动多个实例,以6个实例(3主3从为例)所有数据只有一个备份。启动命令:
redis-server conf/xxxx.conf
- 节点配对。建立节点间连接。
redis-cli -h ip -p port cluster meet destip dest port
,生成40字符ID标识节点,后续据此区分节点. - 建立集群备份机制,分配数据如何存放。
redis-cli -h ip -p port cluster addslots {0...5461}
自动分配失败,可以手动修改。分配完成之后,所有给其它的部分节点设置了slot。完成之后,所有节点都是主节点,还需要建立主从关系. - 建立主从关系.
cluster replicate 主节点的id
,使用命令查看对应关系./cluster-cli -h ip -p port cluster nodes
数据存放和分区
cluster模式下,每个master节点存储部分数据,master集合存储完整一份数据。
在如何搭建cluster集群时步骤3 提到需要分配数据,那么cluster集群是如何分配数据,也是如何进行数据分区哪?
分布式系统数据分区分为哈希分区和顺序分区。Redis使用哈希空间,所有数据映射到一个整数集合内,也就是我们说的slot
(哈希槽)。步骤3提到数据分区就是设置某个节点存放slot的范围。slot值的计算公式:slot=CRC16(key)&16383。通过CRC16算法强制计算出slot值,存储在对应的节点上。数据分区如同小学生入学分班,根据表现分散在各个地方。
scala创建cluster模式连接池
-
集群模式下配置参数
trait MyClusterPool extends Pool { def pool: JedisCluster } class JedisClusterConf(conf: JedisPoolConfig, nodes: Set[HostAndPort]) extends MyClusterPool { lazy private val pl = init override def pool : JedisCluster = pl def init: JedisCluster = { val s: util.Set[HostAndPort] = JavaConversions.setAsJavaSet[HostAndPort](nodes) new JedisCluster(nodes, conf) } }
-
创建cluster单例对象
object RedisFactory extends LogSupport { def newJedisPool(conf: JedisPoolConfig, nodes: Set[HostAndPort]): MyClusterPool = { logWarning("#### RedisFactory create ClusterPool start") val ret = new JedisClusterConf(conf, nodes) logWarning("#### RedisFactory create ClusterPool end") ret } }
-
如何使用
trait RedisClusterInterface extends LogSupport with RedisAction with RedisPara{ val hostList = nodesList.map(HostAndPort.parseString(_)) val nodes = JavaConversions.setAsJavaSet[HostAndPort](hostList) lazy val handler = new JedisSlotBasedConnectionHandler(nodes, conf, 2000) private lazy val jedisCluster = { //cluster pool RedisFactory.newJedisPool(conf, hostList) } def connect: JedisCluster = { redisCluster.pool } lazy val clusterNodes = jedisCluster.pool.getClusterNodes //todo in formal version val should be update period. //节点故障问题会导致主从变换,需要周期更新状态。jedis没有自动更新机制 lazy val clusterInfoCache = { val cache = new JedisClusterInfoCache(conf, 1000) val it = nodesList.map(HostAndPort.parseString(_)) for(el <- it.toList) { val ne = el val jedis = new Jedis(ne.getHost,ne.getPort) cache.discoverClusterNodesAndSlots(jedis) jedis.close() //close connect jedis } for(el <- it.toList) cache.setupNodeIfNotExist(el) cache } //批量数据分配到不同的slot上,要对k-v进行分组,才能使用pipeline 事务等批处理 def group(t: List[(String, util.HashMap[String,String])]) = { val ret = t.map{ case (k, v) => val slot = JedisClusterCRC16.getSlot(k) val conn = clusterInfoCache.getSlotPool(slot) (conn, (k,v)) } ret.groupBy(_._1).map { el => val key = el._1 val re = el._2.map { v => (v._2._1,v._2._2) } (key, re) } } }
以上是集群模式代码实现,拿来即用。祝你嗨皮!
实践中遇到问题
集群模式下,Redis接受到任何k-v相关的命令,都会先计算出slot的值,然后根据slot去访问节点。数据是分布在不同的Redis节点上,使用客户端命令访问时,会出现数据不在该客户端访问节点上,Redis提供重定向(MOVED)实现对数据的访问。客户端登录时,通过设置可以实现自动重定向:
./redis-cli -h ip -p port -c
.发生重定向时,提示**-> Redirected to slot [5798] located **
访问流程:key -> slot -> node -> 数据操作
原因: 读写分离指 redis 服务端 提供机制,只有在master提供读写功能,slave只能读不能写。用户利用该原理在客户端实现 读写分离的功能,redis提供了相关API。
批量设置slot失败 (error) ERR Invalid or out of range slot?
原因:redis集群服务创建问题。检查各个集群节点的IP和port
集群模式是更高复杂度系统,数据分区、故障迁移、负载均衡、开发运维困难等,每一个都是大课题,有机会在讨论。
后记
Redis提供缓存服务分布式锁解决数据一致性问题;数据恢复和容灾需要优化Redis配置;负载均衡用户使用第三方工具解决 ......等等。 遗留问题随着使用深入会有整理出更多的文档。
sohutv开源了Redis运维平台CacheCloud,非常棒的运维工具。
参考资料
- Redis开发和运维 [付磊 张益军]
- Redisn 英文官网
- Redis中国社区
写在最后:
本系列redis基础功能和基于scala的实现到这里,依然到了尾声.理论层面上文章很多,我只是蜻蜓点水略过,深入探索靠实践.scala代码实现读写和各中模式下访问,奉行拿来主义.
我们征程是星辰大海,持续探索才有是未来.
被人追问:只有那点访问和点赞数,写作意义何在?有一个阅读量也是需求。凡事讲意义,就是功利。
对于ta,出门左转,不送。
对读者,只有感谢.感谢你们.
网友评论