JIMDB是Jingdong In Memory DataBase(京东内存数据库)的缩写,是对原生redis(2.8)的集群化方案;它据有如下的特性:
1、支持无缝自动伸缩
- 能够根据业务对集群的访问情况自动将集群调整到一个合适的规模
2、自动故障检测、恢复
- 支持分片级别的并行故障恢复,大幅提升故障恢复速度
- 支持跨机房热备
- 具有持久化机制
- 支持机房就近访问
- 支持不同客户端读不同的从组
- 支持多种读策略
- 支持物理分区
- 提供实例级别的限速功能
- server自升级
- 自扩容
- 故障自恢复
- 全量复制
寻根问源:什么是JIMDB?
据现场介绍,JIMDB从缓存发展而来, 目前服务于京东的几乎所有的业务系统,包括很多重要的业务系统,例如, 前台的商品详情页, 交易平台, 广告,搜索, 即时通讯…… , 后台的订单履约, 库存管理, 派送和物流……。
JIMDB发展历程主要有三个阶段
阶段1、JIMDB 1.0
主要采用官方Redis作为单节点服务; 客户端一致性Hash + Presharding技术; 管理,监控和报警。
阶段2、JIMDB 2.0
功能包括:故障检测和自动切换;平滑纵向扩容和平滑横向扩容;基于内存+SSD的两级存储结构和自主研发存储引擎。
阶段3、JIMDB 3.0
包括了:自助接入和自动部署;容器化;全自动弹性调度。
JIMDB功能特性
1、支持大容量缓存
将缓存数据分摊到多个分片(每个分片上具有相同的构成,比如:都是一主一从两个节点)上,从而可以创建出大容量的缓存。
2、数据的高可用性
支持异步复制和同步复制,目前可以达到等同于Mysql级别的数据可用性。
3、支持多种I/O策略
针对读操作可分为“主优先”、“从优先”、“随意挑选”等方式;不同的I/O策略,对数据一致性的影响也不同,应用可以根据自身对数据一致性的需求,选择不同的I/O策略。
4、哨兵服务和故障自动切换
通过选举算法实现的哨兵服务能够自动判断实例的不存活状态,通知 Failover服务进行主从自动切换, 切换时间在秒级,以保证缓存服务的7 *24小时不间断运行。
5、支持动态扩容
可通过多种途径实现动态扩容:
第一种形式,通过在单个节点上预留内存,然后需要扩容时直接使用预留内存的方法达到扩容的目的。
第二种形式,通过数据迁移来实现扩容。(平滑纵向扩容)
第三种形式,通过增加分片数来实现扩容。(平滑横向扩容)
IMDB技术的应用体现在:
1、数据查询和维护
提供类似于redis命令窗口的web控制台,禁止危险命令,严格控制写命令和一些运维相关命令,适当放开查询命令。
将缓存数据分摊到多个分片(每个分片上具有相同的构成,比如:都是一主一从两个节点)上,从而可以创建出大容量的缓存。
2、监控与报警
Pains(痛点)
例如:网络不佳的情况下可能发生误判; Redis单线程执行,在进行长任务时可能发生误判。
Solution(解决方案)
比如:在机房中不同区域部署多个Failure Detector;多个Failure Detector之间采用分布式选举算法,判断Redis实例的死活;连接健康度不佳时, 验证端口是否通畅。
3、迁移与扩容
Scaling Up – 纵向扩容
首先,在内存不够, 需要增加内存时首先考虑的是纵向扩容,即增加每个分片的主、从节点的内存。
其次,纵向扩容时如果Redis实例所在计算机物理内存不够,就需要进行数据迁移。
再就是,数据迁移的同时,服务不能暂停
Scaling Out – 横向扩容
首先,单一分片的内存是不能无限扩容(纵向)的, 太大了会影响复制的效率;其次,在纵向扩容无法进行的情况下(单一分片内存已经很大,或者流量压力很大),就需要进行横向扩容,即增加集群的分片数。再有,横向扩容的同时,服务不能暂停。
纵向扩容
横向扩容
Pains(痛点)
首先,纵向扩容并不增加分片数,简单修改JIMDB实例运行时参数可提高该实例可用内存上限,但在机器内存吃紧时,若要提高该分片内存上限,需要将该实例平滑迁移至一台内存资源更加充沛的机器。
其次,流量打满或者出现热点时, 需要加分片分散压力。 机器内存不够时, 有时也需要加分片
再次,Pre-sharding的方式, 在不影响服务的情况下增加分片有难度。
还有,可以通过定制开发引入bucket来进行横向扩容, 但线上还有2.4, 2.6, 2.8等既有版本,也有加分片的需求。
再有,避免主从数据不一致。
最后,服务不能暂停,平滑不影响业务。
Solution(解决方案)
首先,通过Filtered replication, 实现某个结点的分裂(split)
其次,开发一个支持Hold和Split的Proxy, 并通过一个流程控制器来协调客户端,服务结点,Proxy, 等相关各方。
横向扩容
对于水平扩容,则是依赖于bucket来解决的。每个JIMDB实例内部都含有若干个buckets,和上述第一类扩容相似,水平扩容也是通过对数据进行平滑迁移来实现的,但迁移的粒度不再是整个实例,而是针对集群中的这些buckets。扩容前后如下图所示:
4、冷热数据及二级存储
Pains
比如,Redis完全依赖于内存,往往内存不够使用;Redis启动时需要把全部数据加载到内存,在数据量大时启动速度慢; 规划总是赶不上业务发展, 内存总量不断被突破, 不断陷入扩容, 再扩容…的梦魇。
Solution
引入RAM + SSD两级存储,在内存中存储热点数据, 冷数据被自动交换到磁盘,解决了内存不足的问题;启动时并不把所有数据加载入内存,而是在运行时根据需要加载,解决了启动速度慢的问题; 因为引入了二级存储, 存储容量通常比较大, 所以不需要频繁的扩容了。
京东私有云技术体系包括:
1、存储
JFS:京东文件系统,提供统一的文件/对象/块存储服务
JIMDB:京东分布式缓存与高速KV存储,兼容Redis协议
2、中间件
SAF:服务框架,SOA之基石
JMQ:消息队列,The Datacenter Pipes!
3、弹性计算
JDOS:软件定义数据中心 = 软件定义计算单元(VM & Container)+ 软件定义网络(自研SDN)+ 软件定义存储(JFS & JIMDB)。
CAP: 弹性计算云 = 软件定义数据中心 + 自动化容器集群调度。
适用场景
适合于所有需要加速的场景
系统架构
系统的基础架构图如下:
1.4.5相比1.4.4在API上没有变化,其Changelog为:
- 支持jimUrl形式配置
- 支持自动扩容
- 通过com.jd.jim.cli.compat.ReloadableJimClientFactoryBean兼容configId+token配置形式
其中,超时时间、连接池以及读写分离的情形可以通过点击以下界面按钮自行设置:
公共测试环境:
1 | jim://1803528818953446384/1 |
2 | jim://1803528671997086613/2 |
3 | jim://2809907038999011671/4 |
4 | jim://2809907022807591111/5 |
如果不喜欢上面这种方式,可继续按照老的configId+token的方式使用以前提供的测试环境(公用configId和token现有8个)。
自已可控的专有测试环境:
第一步: jim://2870414099454224775/3
第二步: 配置host
192.168.150.61 cfs.jim.jd.local
最新版本(055e848cb62e1f4dac31a542d0e67688 jimdb-3.2.24.tgz )
执行步骤如下:mkdir -p /export/Data/redis_data/6379
mkdir -p /export/Logs/redis/
mkdir -p /export/servers/
tar -zxvf jimdb-3.2.24.tgz -C /export/servers
nohup /export/servers/jimdb-3.2.24/src/redis-server /export/servers/jimdb-3.2.24/conf/redis_6379.conf &- 在部署服务器通过redis-cli 连接redis实例
/export/servers/jimdb-3.2.24/src/redis-cli -h <redis实例所在的IP> -p <redis实例端口>
生产环境:
生产环境的jimUrl(或configId、token),在垂直运维处理完你的集群申请后,系统会发送邮件通知。
客户端使用配置:建议直接demo下载
(JimDB client 1.4.5 demo下载 兼容老的配置风格)
通过maven引入jar包 |
<dependency>
<groupId>com.jd.jim.cli</groupId>
<artifactId>jim-cli-jedis</artifactId>
<version>1.4.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.jd.jim.cli</groupId>
<artifactId>jim-cli-api</artifactId>
<version>1.4.5-SNAPSHOT</version>
</dependency>
| ||||||||||||||
jimUrl风格配置形式 |
| ||||||||||||||
|
1.4.5新旧机房相同配置方式说明:兼容旧的配置方式(访问新旧机房配置方式都相同) 相同的com.jd.jim.cli.compat.ReloadableJimClientFactoryBean,相同的工厂com.jd.jim.cli.compat.ReloadableJimClientFactory,相同的配置项configId、token,相同的api。
Jimdb S是一个完全兼容redis协议的大容量高速NoSQL数据库,存储大容量数据的同时提供高性能和低延迟, 用SSD持久化数据。使用Jimdb S以后可以保存全量数据,把缓存+数据库的两层架构用一层架构取代,提供更低的延迟,同时简化应用逻辑,不需要再关心缓存更新和失效的问题,避免数据不一致的风险。
主要特性:1) 可靠性: 如果配置了主从复制,可以用同步命令的方式保证数据写入多个副本后才返回成功,保证成功写入的数据即使发生意外也不会丢失。
2)可扩展性: Jimdb S支持平滑的迁移和扩展,可以通过增加分片承担更大的访问压力和更大的数据容量,理论上单集群可以支持上百TB的数据。
3)单实例读性能: A,数据在内存命中; B,数据磁盘文件中,需要硬盘IO。 A的性能和原生redis是相同的,ops可以达到十万以上,延迟低于1ms。B 的延迟基本在2ms - 20ms之间,大多数情况下小于10ms,并发越多,延迟会越高,总的ops在3万左右。 所以实际应用场景的读性能取决于读请求中属于A, B 两种情况的比例。热点数据越集中,性能越好。
4)单实例写性能:
5)范围查询功能: JRANGE [{start} ({end} LIMIT <offset> <limit> 其中’[’代表开区间,’(‘代表闭区间,LIMIT是可选项,后面跟两个参数。
JRANGE [key001 (key005 会返回 key001, key002, key003, key004; JRANGE [key001 (key005 LIMIT 1 2 会返回 key002, key003。
6)主从复制:
7)启动时间: redis如果配置了持久化,启动时会把所有数据从硬盘载入内存,如果数据量大,会需要几十秒到几分钟的时间,而Jimdb S启动时先从索引文件中载入索引,通常索引文件远小于数据文件,因而加快了启动速度,如果索引没有保存才会从数据文件中读取数据创建索引。
8)数据统计: Jimdb S除了支持redis已有的统计数据之外,还添加了网络流量统计和延迟统计,在客户端遇到高延迟的问题时,更容易定位问题的原因。
注意事项:1) Jimdb S目前不支持大数据结构,最大限制为1024,如果元素个数超过这个值,性能会有下降。建议将这部分数据分离出来单独存储。 2) Jimdb S单实例的数据大小最好不要超过100GB,太大的实例会导致主从复制的时间太长。 3) Jimdb S会将全量索引保存在内存中,如果数据的value都很小,则需要比较多的内存。
设计和实现:
1)写入逻辑: 当Jimdb S在内存里完成写操作时,不会立即执行写cycledb操作,而是把修改过的key放入dirtyKeys map里,在每30毫秒执行的时间任务里,把脏数据写入cycledb。 在前台执行写入cycledb时,会在内存中更新索引和元数据,然后再触发一次磁盘文件的顺序写,性能很高。数据会在30毫秒的周期内持久化在硬盘上,即使系统崩溃,最多丢失30毫秒的数据。 redis的数据结构类型有5种,string/list/hash/set/zset,在Jimdb S里,这5种类型在写入cycledb时会统一用序列化rdb的方式生成一个string,默认使用redis自带的LZF的压缩算法进行压缩,LZF是一种高速的压缩算法,即使数据是已经压缩过的,再压缩一遍开销也是很低的。
2)内存硬盘数据交换逻辑: 当Jimdb S接收到一个请求时,首先需要算出这个命令里涉及到的key有哪些,在执行这个命令之前,我们需要保证这些key都已经载入内存,Jimdb S会在内存里查找这些key,如果这些key在内存里都有,那就会立即执行这个命令,如果有的key在内存找不到,就需要查询cycledb,并把cycledb里的数据载入内存,这个函数名loadKeyValues。 如果我们在主线程执行读cycledb的命令,那么主线程很可能会阻塞几毫秒,因为redis是单线程模型,这会严重影响性能,所以读取cycledb的操作需要在后台线程完成。而直接把后台读取cycledb得到的数据载入内存会带来一个问题,就是数据不一致,有可能导致旧数据覆盖新数据。Jimdb S的解决方案是, 第一次执行loadKeyValues的时候,遇到需要读取cycledb的时候,创建后台任务读取cycledb, 这个后台任务的作用实际上并不是读取,而是预热,读到数据后并不把结果载入内存,执行完成后,前台主线程会重新执行一次loadKeyValues,这次直接在前台读取cycledb并载入内存,因为这个key刚刚被读取过一次,所以一定会被操作系统缓存,第二次读不会有硬盘IO,所以不会阻塞,操作瞬间完成。 当Jimdb S的内存达到配置的maxmemory时,会用随机的lru算法淘汰内存里较久没有访问过的key,保证内存使用量在maxmemory之内,因为数据在硬盘上全量保存,所以即使key被淘汰,数据也不会丢失。
3)主从复制逻辑: 原生redis在复制时需要fork一个后台进程dump RDB文件,如果没有设置overcommit_memory,有可能会fork失败。而Jimdb S完全不需要fork后台进程,所有操作都在一个进程内完成,通过后台线程生成数据快照。 当Jimdb S接收到从发来的复制请求时,会启动后台线程遍历cycledb里的数据,保存在一个rdb文件里,这个过程消耗的时间跟数据文件中的数据量有关,数据量越大耗时会越久,完成之后,Jimdb S会把这个rdb文件发送给从,传输的速度会受到网卡和硬盘读写速度的限制。当Jimdb S作为从接收到rdb文件后会读取并载入内存,并持续地把新写入的脏数据写入cycledb,这个过程也会消耗很长时间。 由于一个Jimdb S实例会保存几十G到上百G的数据,主从全量复制的代价很高,如果一块硬盘上多个实例同时复制,很可能导致硬盘写满,或者占用大量网卡带宽,所以jimdb加入了全量复制锁save-lock,在同一台主机上,同一时间只有一个实例会保存快照,如果多个实例同时接受到从发来的全量同步请求,只有获取文件锁的那个实例才会执行复制操作,其他的实例会定期重试。
未来的开发计划:1) 智能动态的调整内部参数配置,减少需要人工调整的配置项,预防配置失误的风险。 2) 支持多种存储引擎。
|