分布式缓存中间件Codis简单介绍
Posted 海科融通技术团队
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式缓存中间件Codis简单介绍相关的知识,希望对你有一定的参考价值。
Codis 是一个分布式 Redis 解决方案, 对于上层的应用来说, 连接到 Codis Proxy 和连接原生的 Redis Server 没有显著区别 (不支持的命令列表), 上层应用可以像使用单机的 Redis 一样使用, Codis 底层会处理请求的转发, 不停机的数据迁移等工作, 所有后边的一切事情, 对于前面的客户端来说是透明的, 可以简单的认为后边连接的是一个内存无限大的 Redis 服务。
方案提供了
数据分片存储,缓存动态扩容
缓存访问负载均衡,且高可用
客户端与服务端解耦,服务端变化不影响客户端使用
可视化运维管理后台,集群状态一目了然
方案是基于redis实现的,我们先简单了解一下redis。
Redis
简单介绍
Redis 是一个开源 , 高性能的key-value内存数据库,可以作为数据库、缓存或者消息服务使用。基于内存运行并支持持久化。支持复杂的数据结构并且提供原子性操作 。Redis的数据类型都是基本的数据结构,简单易懂,如List、Set、Map等等。
使用场景
缓存-热数据
计数器-递增、递减
队列
排重-集合之间交集、并集、差集
排行榜(有序集合)
……
在新平台中,redis可以用来做:
用途 | 用法 |
---|---|
分布式Session | 集群部署管理后台统一存储用户session信息 |
防止表单重复提交 | 表单提交使用redis校验请求是否合法有效 |
短信验证码 | 短信验证码时效性校验 |
唯一id | 大批量生成唯一id,放入队列,减少服务调用,保证数据唯一 |
银联商户路由 | 缓存银联商户路由信息,减少复杂查询,缩短系统响应时间 |
热数据访问 | 缓存热点数据,降低数据库压力 |
由上可见,redis存在于系统的各个功能点中,一点一点使用redis来优化系统,提高系统稳定性和健壮性的效果是非常可观的。因此,redis相关知识是非常值得学习并应用到项目之中的。
单机缓存服务随着系统慢慢发展,无论存储量还是并发请求数都到了一个瓶颈期,缓存分片迫在眉睫。
集群方案
分片技术
客户端分片
客户端做key与后台实例的关系映射,每次操作都需要根据映射关系寻找对应的实例。
这种方案将分片工作放在业务程序端,程序代码根据预先设置的路由规则,直接对多个Redis实例进行分布式访问。这样的好处是,不依赖于第三方分布式中间件,实现方法和代码都自己掌控,可随时调整,不用担心踩到坑。
这实际上是一种静态分片技术。Redis实例的增减,都得手工调整分片程序。这种分片机制的性能比代理式更好(少了一个中间分发环节)。但缺点是升级麻烦,对研发人员的个人依赖性强。
所以,这种方式下,可运维性较差。出现故障,定位和解决都得研发和运维配合着解决,故障时间变长。这种方案,难以进行标准化运维,所以这种方案是不可取的。基于此分片机制的开源产品,到现在几户没有了。
代理分片
这种方案,将分片工作交给专门的代理程序来做。代理程序接收到客户端的数据请求,根据路由规则,将这些请求分发给正确的Redis实例并返回给客户端。
这种机制下,一般会选用第三方代理程序(而不是自己研发),因为后端有多个Redis实例,所以这类程序又称为分布式中间件。这样的好处是,客户端不用关心后端Redis实例,运维起来也方便。由于中间加了一层代理路由转发 ,虽然会因此带来些性能损耗,但对于Redis这种内存读写型应用,相对而言性能还是可观的。
这是我们推荐的集群实现方案,像基于该机制的开源产品Twemproxy,豌豆荚的codis,便是其中代表,应用非常广泛。
Redis官方集群
无中心集群方案,客户端随机连接集群中的某个节点,数据不在该节点,则二次路由定位到正确的节点上操作。
简单了解一下Redis官方集群 Redis Cluster。
由图所示,该方案是一个3主3从6节点架构(最少3主)。整个集群分配16384 个slot(槽)。A节点分配[0-5000]、B节点分配[5001-10000]、C节点分配[10001-16383]。对于每个Redis的key都有唯一的slot来与之对应,具体算法为CRC16(key)%16384 = 0-16383中的某个值。
客户端创建对所有主节点的连接,客户端发起一个请求,并不知道key对应处于哪个节点上 , 所以他会选择从连接池随机获取一个连接 , 服务端判断key是否在当前节点 , 如果在,返回对应操作的结果。如果不在,获取key对应的节点信息,返回给客户端,客户端重新连接到对应的正确节点上执行对应操作。
最好的情况一次命中节点,最坏的情况是两次。会有性能损耗。
集群健康检测,选举容错
容错选举过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前master节点挂掉。
什么时候整个集群不可用(cluster_state:fail),当集群不可用时,所有对集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)错误:
1.如果集群任意master挂掉,且当前master没有slave。集群进入fail状态,也可以理解成进群的slot映射[0-16383]不完成时进入fail状态。
2.如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态。
客户端连接
JedisPoolConfig config = new JedisPoolConfig();
// 最大连接数
config.setMaxTotal(30);
// 最大连接空闲数
config.setMaxIdle(2);
//集群结点
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7001));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7002));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7003));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7004));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7005));
jedisClusterNode.add(new HostAndPort("192.168.101.3", 7006));
JedisCluster jc = new JedisCluster(jedisClusterNode, config);
JedisCluster jcd = new JedisCluster(jedisClusterNode);
jcd.set("name", "zhangsan");
String value = jcd.get("name");
System.out.println(value);
客户端硬编码,由于后端节点的变化,导致客户端也需要做代码更新调整,服务重启。这样不仅影响线上业务,对程序人员也是一种摧残。理想情况是服务端做好封装,前端只负责发送请求,无论服务端有什么变化,前端并不需要关心。
由于存在二次路由,会有性能损耗,且无可视化运维界面。因此,我们将目光转向Codis。
Codis使用
组件
codis-server 对原生redis进行封装使用,等同于redis
codis-group 主从实例进行分组区分(分片),不同的分组负责不同的slot
codis-proxy 负责连接redis,可集群部署,用以提高并发处理能力
codis-dashboard 集群管理工具,支持 codis-proxy、codis-server 的添加、删除,以及据迁移等操作。在集群状态发生改变时,codis-dashboard 维护集群下所有 codis-proxy 的状态的一致性。
codis-fe 集群管理界面,集群状态一目了然
管理后台
qps 动态展示redis每秒操作数
添加codis-proxy 做负载均衡和高可用
后台命令行启动codis-proxy
管理页面将启动好的codis-proxy添加到集群中
组之间进行slot迁移
固定槽迁移,将783槽里的key迁移到组2中
指定数量的槽迁移到新的组中,将5个槽数据从组1迁移到组3中
通过slot的迁移,我们可以根据每个实例内存空间大小来动态调整内存使用情况,使slot分配更加合理。
添加分组,并分配实例
启动codis-server实例,将192.168.0.163:6390实例启动
添加分组,并分配codis-server
指定数量的槽迁移到新的组中,将5个槽数据从组1迁移到组4中
这样,就实现了动态扩容。接着,我们为组4中的主节点添加一个从节点,达到高可用。
至此,集群扩容、高可用多负载的目的就通过Codis达到了。
客户端Jodis
客户端可以使用开源的jodis来轮询请求后端的无状态codis-proxy,由此达到负载均衡的目的。我们看一下关键代码。
//监听zookeepr路径下节点
watcher.getListenable().addListener(new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event)
throws Exception {
StringBuilder sb = new StringBuilder("zookeeper event received: type=")
.append(event.getType());
if (event.getData() != null) {
ChildData data = event.getData();
sb.append(", path=").append(data.getPath()).append(", stat=")
.append(data.getStat());
}
LOG.info(sb.toString());
//当有节点(codis-proxy信息)添加、修改、删除
if (RESET_TYPES.contains(event.getType())) {
//进行重置连接池
resetPools();
}
}
});
由关键代码可知,后端服务的变化对前端都是可以动态感知,由此可以达到动态调整连接池,而前端是无需关心。
编辑:闫杰明
以上是关于分布式缓存中间件Codis简单介绍的主要内容,如果未能解决你的问题,请参考以下文章
6.《持续演进的Cloud Native 云原生架构下微服务最佳实践》读书笔记-第三章基于Codis实现Redis分布式缓存集群