18

聊聊DaaS数据库即服务和微服务下数据库拆分(200921)

 3 years ago
source link: http://blog.sina.com.cn/s/blog_493a84550102z95r.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今天准备聊下DaaS数据库即服务,以及在微服务下的数据库拆分,数据库拆分后应用的开发和对分布式事务的处理。这些内容实际在我们建设企业私有云PaaS平台的时候就已经提到,到现在企业的微服务架构转型和改造也同样适用。

DaaS数据库即服务平台设计

DaaS(数据库即服务)是一种IT架构与运行模式,它能够使得服务提供者把数据库的能力以服务的形式提供给一个或多个服务消费者。数据库即服务包括如下关键能力:

  • 多租户共享能力;
  • 数据库实例的按需提供与动态管理能力;
  • 数据库服务的QoS/SLA指标自动监控与动态资源调配能力。

在私有云PaaS平台建设过程中,中间件资源池包括了应用中间件资源池,也包括了数据库资源池。DaaS层的核心是真正实现了对底层各种异构的数据库资源的访问透明,同时通过管理平台实现数据库资源的灵活调度和动态管理。

在去IOE的核心思想下,往往采用开源的数据库解决方案单点能力很难真正满足海量数据存储和业务高并发数据访问的需求。因此需要对数据库进行水平和垂直拆分,读写分离等形成多个分片后的数据库处理和存储节点。

而对于这种物理层面的数据库拆分,上层应用往往并不关心,上层应用希望的是访问一个集中的逻辑数据库,DaaS服务层的出现也刚好可以解决这个问题,提供给业务组件底层数据库资源的统一访问和管理能力。

在DaaS数据库即服务层的设计过程中,需要考虑如下设计目标的实现:

对数据库 高并发读写 的需求

需要能够支撑10T甚至更多规模的海量数据存储需求,同时在海量数据存储下需要能够实现数据库的高并发读写。对于具体需要达到的TPS性能指标需要根据企业实际业务规模和场景进行业务模型测算和评估。

对数据库的 高可扩展性和高可用性 的需求

当一个应用系统的用户量和访问量与日俱增的时候,能够通过添加更多的硬件和服务节点来快速扩展性能和负载能力; 在高可用性方面,需要能够完全避免数据库服务器或存储损坏的单点故障。

整体架构设计参考

对于DaaS服务层的设计,当前没有统一的标准。通过私有云建设实践可以看到多租户管理、SQL解析、路由、集群和负载均衡仍然是一个DaaS平台需要具备的基本功能。一个具体的多租户DaaS平台架构可以参考下图:

数据库接口代理: 实现统一的数据访问接口代理,业务组件或模块通过接口代理来访问底层的数据库服务。在接口代理的实现过程中需要考虑数据库连接池的管理,数据库负载均衡等相关内容。

SQL解析: 负责解析客户请求的SQL语法,需解析出语句的读、写特性,并根据语句特性进一步解析其中的schema、表、字段、条件等信息。如:新增语句需解析出所插入字段的字段名和值;查询、修改、删除语句需解析出Where条件中包含了哪些条件表达式。

数据路由: 负责根据语法解析的结果,在规则池中查找与之相关的规则。找到后将解析结果代入规则中进行运算,得到语句需要转发的具体物理数据库节点。而对于规则池则主要包括语句的读写规则,水平拆分的分片规则,数据对象的访问规则等。

多租户管理: 可以实现在数据库实例和数据库Schema两个级别的多租户共享和管理功能。数据库层共享以数据库为基本的划分单元,即为每一个租户创建/分配一个数据库实例,共享存储和服务器。Schema层共享以User/Schema为基本的划分单元,即数据库实例已经创建,在此基础上为每一个租户创建一个Schema,多租户之间共享存储、服务器、操作系统服务和数据库实例。

管控代理: 为实现对整个数据库资源池的集中管控和性能监控,需要对每一个数据库物理节点放置数据库管控代理。管控代理一方面实现对物理数据库节点的统一操作入口,一方面实现对资源的实时监控和性能数据采集。

管控功能: 提供对服务集群中的不同数据库服务节点进行节点的添加、删除、启动、停止等功能。完成服务集群的伸缩;完成节点信息的采集,以及针对节点进行的手动操作的日志记录; 完成和节点代理服务进行交互的工作;完成和监控系统进行交互;共同完成服务的管理和监控功能。

DaaS层架构设计约束

在数据持久层引入分布式数据库架构,主要是为了解决海量数据下关系型数据库的读写性能问题,但同时也会由于其分布式的架构而带来很多问题。具体约束主要包括:

不支持跨库的关联查询,跨库的排序和聚合等操作。

虽然在同一个逻辑库可以实现分布式事务管理,但是采用分布式事务后会带来严重的性能下降,在实际使用中应尽量减少使用。

为了保证全局数据的唯一性,需要调用DaaS的服务接口来实现全局序列号的统一生成和管理。

在读/写分离集群使用中,虽然读节点的增加可以提升整个数据库集群的访问性能,但是随着读节点的增加会导致数据库复制延迟的提升。

业务应用在进行水平切分设计的时候,需要尽量考虑80%甚至更多的业务操作场景能够在同一个物理分片完成,以减少跨库操作带来的性能消耗和数据一致性等问题。对于需要大量依赖存储过程,函数运算的场景更不适合进行切分。

微服务下数据库拆分-数据共享和数据冗余

在前面我们谈微服务架构的时候就一直在强调,微服务架构不仅仅是前端和逻辑层组件的拆分,更加重要的就是数据库本身也要进行拆分。

即:从数据库到逻辑层到前端全部实现独立自治和解耦。

一个完整的微服务组件不仅仅是实现相应的业务规则和业务功能,更加重要的是还需要完成业务组件自身的数据存储。因此松耦合的微服务不仅仅是在应用中间件层部署包隔离,还包括了在数据库层面的严格资源隔离。

共享数据下沉并提供统一领域服务能力

对于业务系统,主要分为两种主要的形态:

  • 一种是以核心数据主导的业务系统
  • 一种是以工单流程主导的业务系统

对于前一种典型的主要是资产系统、资源系统等,其中资产和资源都是核心数据,所有业务功能都围绕核心业务数据展开,改变核心数据的属性状态或关联关系;而对于OA、电子运维等系统则是工单流程驱动型系统,这种系统往往并没有一个全局的核心数据层,不同业务类型下的单据相互之间相对独立。

因此对于以数据主导的业务系统来看,多个业务模块会共享一个全局的共享核心数据层,所有的上层业务模块都可能产生、变更或消费这些共享数据。

那么这类数据放在单独的微服务业务模块里面实现已经不合适,必须要抽象和提取出来放到公共的共享数据库进行存储,然后共享数据的能力以领域服务的方式暴露给上层业务模块使用。对于上层业务微服务自身还具备的私有数据,由于不存在共享和交互的需求,因此仍然可以放在业务组件自己的数据库中进行设计和管理。

要注意SID共享数据不是简单上传统的主数据,主数据只是跨多个业务系统共享的基础静态数据,而对于一些动态数据如项目、合同、订单等仍然存在跨多个业务系统或业务模块共享的问题,因此也属于共享数据的范畴。

当前这种将共享数据下沉,并形成统一的共享数据库并向上提供领域数据服务能力,这种方式下可以将原来多个微服务间交叉依赖转变为对底层共享数据模块的依赖,以实现上层各个微服务间的进一步解耦。

数据冗余设计-解决大量跨库读问题

前面谈到了共享数据设计,其设计的核心目标是 减少业务组件间的横向交互,同时通过数据只有一套不冗余的方式来保证数据的一致性和完整性。 在共享数据设计模式下,可以看到具体的数据库参考架构如下:

在这种情况下,四个应用的所有SID共享数据全部集中到共享数据库,然后以领域服务的模式提供共享数据能力。而对于每个应用中只保留私有数据库。

在这种模式下,虽然达到了共享数据只保留一份数据拷贝,不会有任何数据不一致和数据实时性问题,但是却带来了单个业务组件开发复杂度的增加。因为原来 在传统模式下一个数据库内部能够解决的问题,都转换为了跨库问题和分布式事务处理问题 ,这是在共享数据架构设计下不可回避的一个关键问题。

基于上面的问题,需要探索一种在共享数据和非共享数据设计两种方案之间的一种折中设计方案,这种方案的核心要求就是既可以解决共享数据能力提供和数据一致性问题,又可以尽量减少对上层业务组件设计开发带来的跨库和分布式事务影响。

在该思想下可以采用数据冗余和数据复制技术来实现,具体的数据架构参考如下:

该数据库参考架构参考数据库读写分离的思路,即首先识别各个业务数据中需要共享的共享数据,对于这部分共享数据通过数据库日志复制技术准实时的同步到SID共享库中。全局共享库通过领域服务层提供数据的读取服务。

举例来讲,如项目管理系统会产生项目信息数据,这部分数据是各个业务系统都会消费和使用的共享数据。对于项目信息的产生和CUD操作仍然在原来的项目管理数据库中完成,在数据产生或变更后则实时的通过数据复制同步到共享的读数据库。对于采购、合同、仓储等业务系统在需要项目信息的时候不再访问项目管理数据库,也不再同步项目管理信息数据,而是直接通过共享数据库提供的“项目管理信息查询服务”实时查询。

在该场景下,共享数据库仅仅是读库,由于数据复制可能存在一定的延迟或发生脏读,但是本身不会对业务操作类事务的一致性造成影响。对于共享数据的产生源头单元,非完全实时性要求的场景下都可以采用这种数据冗余模式。

这种模式既解决了业务系统间大量的数据复制和相互交叉调用,同时也解决了业务系统在实际的CUD操作中进行跨库操作和分布式事务处理的问题。

在这种模式下可以看到和传统的MySQL数据库的读写分离思路相当类似。SID库仅仅是一个读库,供所有的应用读,同时为了包括性能读库本身可以扩展为多个读库集群。进行CUD操作的写库还是在原有应用中,可以大量地避免分布式事务的处理问题。

在这种模式下的实时性和一致性也较容易保持,例如对于本地应用库中的SID数据可以采用类似读写分离集群中的BinLog日志复制技术,这样可以基本保证SID库的准实时性。虽然不是完全高度实时和一致,但是由于是数据读,本身问题不大。

数据集中设计

在区分完共享数据和私有数据后,需要考虑共享数据和私有数据的集中化问题。

在基于私有云的大集中化建设模型下,识别出的SID共享数据库存储规模可能超过10T甚至更大,那么就必须考虑一个集中化的数据库能否支持大数据的存储和数据性能访问要求。对于数据集中设计主要包括了物理集中和逻辑集中两个方面的内容,具体描述如下:

01-物理集中

只使用一个大数据库进行数据存储并提供数据访问能力,在这种模式下往往需要考虑的是类似SAN集中数据存储,类似Oracle的ExaData和RAC集群的数据库方案。只有这种方案能够提供海量数据存储下的数据库高性能并发要求。

02-逻辑集中

类似分布式数据库集群方案,底层采用多个物理数据库节点提供数据存储和访问能力,然后通过前面讲到的DaaS数据库服务层进行逻辑集中和统一访问。特别是在互联网企业如淘宝的去IOE运动下,这种模式已经逐步被企业所认识和采用。

在基于DaaS的逻辑集中下,会带来两个关键问题:

其一是数据存储的水平和垂直拆分问题,只有通过拆分后数据才能够进入到底层物理分片库;

其二是DaaS层当前很难真正做到底层数据库的完全透明,通过DaaS层访问数据库将对业务组件和模块的开发造成新的约束和限制,类似分布式事务的处理、跨分片库查询、标准SQL支持等。

分布式数据库

在结构化数据存储和处理领域,支持高性能OLTP操作的分布式数据库发展缓慢。MySQL也推出了MySQL Cluster分布式数据库集群技术。但是经过实际测试在应对海量数据的复杂业务处理和查询的时候,仍然存在无法解决的性能瓶颈问题。

在OLAP领域,可以看到基于MPP+ShareNothing思想的商用和开源数据库都发展迅速,这个也是在企业私有云平台建设后期,面对OLAP数据分析需求可以借鉴和采用的解决方案。

注:当前类似Oracle RAC集群这种完整意义上的分布式数据库实际并不多。类似阿里的PolarDB数据库也只能说和RAC类似,但所有所有节点都能够读写,只有一个写节点。

微服务下数据库拆分-水平拆分和垂直拆分

在面对海量数据的大并发要求场景下,如果数据库采用物理和逻辑双集中涉及,则需要类似IOE的高性能数据库和存储解决方案。而在去IOE思想下为了达到同样的高性能和并发要求,则需要对数据进行水平或垂直切分。

通过数据库的切分后将对数据库的并发访问分散到不同的数据节点进行处理,以降低单数据库节点的压力。对于垂直和水平切分简要描述如下:

垂直切分

将传统的单体应用拆分为多个微服务,每个微服务业务组件业务组件对应一个自己的数据库。在技术层面实现了传统的一个大数据库中的物理表根据业务组件划分后的分离。

注意数据库垂直拆分是在物理架构层面就形成了多套独立的数据库,垂直拆分出来的多个数据库上层并不需要一个DaaS数据库中间件。在垂直切分模式下需要考虑共享数据库的抽取问题,需要考虑业务操作本身的跨库问题。

水平切分

将企业内部的业务根据组织,产品线等各种维度进行切分,分别在不同的数据库中进行存放。

比如我们常说的一个大型集团性企业,可以将不同的地域或省份的数据拆分为独立的逻辑数据库。底层逻辑数据库对上层透明,通过DaaS统一对外访问。

在这种模式下每个数据库中的表结构完全相同,只是将企业业务数据水平分散到了不同的数据库节点存放。水平切分时需要使用表中某个非空列作为分片的片键,并为片键指定分片规则。 水平切分可能存在跨库事务、跨库连接、跨库分组、跨库排序、全局序列、全局唯一性校验、以及对某些数据库对象(存储过程、函数、触发器等)的支持等问题。

在讨论数据切分前,首先分析下企业应用的类型。

对于企业内业务系统包括两种类型:一种是以核心数据为中心的应用;一种是以纯粹的流程工单为核心的应用。前者如资产管理,主数据管理等相关应用;后者如运维工单管理,OA办公管理等应用。

类型一:以数据为中心的应用

对于以数据为核心的应用,往往存在一个较厚的底层数据共享层。这个底层数据将为上层的所有业务组件和模块提供相应的数据支撑能力。在这种模式下可以看到不论哪种方式的垂直切分都将带来上层业务组件和底层数据组件之间的强耦合关系。

因此在这种业务场景下建议首先考虑进行水平切分。这种类型的应用在集中化建设模式下核心的业务场景仍然是在一个子组织中需要保持高内聚和完整性,因此可以将总公司和子公司等通过水平切分拆分为不同的数据库。

在这种切分后唯一需要考虑的就是跨多子公司的业务操作场景的处理问题。水平拆分更加类似于一个集团型企业内部的支撑多组织的SaaS应用,只是需要考虑集团到下属子公司,子公司到各个分支机构的业务协同问题。

如果在进行水平切分后数据库能力仍然无法达到要求,则必须再考虑进行组件的垂直切分操作。这种垂直切分需要采用粗粒度的切分方式,因为切分的越细往往带来的交互访问和并发越大。在进行了垂直切分后,需要基于底层的共享数据库构建统一的领域对象层,然后将领域对象以服务化的方式暴露给上层的业务组件和模块使用。

由于上层的业务组件模块对底层领域服务对象的高并发访问,因此需要考虑领域服务以一种轻量化的服务方式进行实现和管控,而不是将这些服务也接入到重量级的ESB上。比如我们常说的服务注册中心,API网关等注册接入方式。

在SOA参考架构和业务组件化背景下,更多强调的是业务组件本身向外部提供业务服务能力。业务组件间的相互调用必须走业务组件提供的业务服务,即业务组件横向之间彻底解耦。

但是对于单个应用模块而言,其展现和应用层到底层业务组件间是更多强调的是业务逻辑层API能力的梳理和定义。这些API业务能力在纵向调用时候完全可以通过轻量的API,只有在跨业务组件调用的时候才需要通过代理对外发布为轻量的业务服务。

对于是否进行垂直切分,水平切分或垂直+水平的混合切分一定要注意到判断的关键原则是 拆分后对业务实现的影响,特别是拆分后会带来多少跨库操作和分布式事务问题 。对于切分粒度和切分方式都需要围绕这个原则尽量减少带来跨数据库和分布式事务处理操作。

类型二:以流程为中心的应用

对于以流程为中心的工单型应用,这类应用各个业务组件之间的耦合性非常低,需要共享的底层基础数据也相当少,因此这类应用最适合进行垂直切分,对于垂直切分的微服务大小也没有必然的限制,如有20类工单流程完全可以垂直切分为20个不同的业务组件模块。

由于对于一个完整的端到端流程往往会涉及到从总公司到子公司再到各个分支结构的流程流转,因此在这种业务模式下往往并不建议再进行相应的水平切分操作,如果水平切分反而会引入大量的业务跨库操作问题。

数据拆分总结

对于以数据为核心的单体应用系统,在进行微服务化和拆分的时候, 底层共享数据库不要进行垂直拆分以避免引入大量分布式事务问题 。但是底层共享数据库可以按地域或组织域进行水平拆分。

数据库拆分后可以通过数据库同步复制进行,建立准实时的共享ODS库,提供跨数据库整合后的领域数据服务能力。这点有点类似于数据中台中的贴源数据层能力暴露。

对于以流程为中心的业务应用,建议的方式是直接进行细粒度的垂直切分 ,不用太考虑水平拆分的问题;而这种模式下的扩容只是模块的进一步垂直切割。

在进行数据库拆分的时候,具体哪些数据对象,数据表应该归属到同一个数据库,需要基于业务功能和数据对象,进行详细的CRUD交互分析。

我们首先将CUD操作拆分为四个操作,即增加一个Import操作,变化为ICUD操作。 对于Import操作理解为业务功能在完成后形成正式的业务对象,作为流程末端的输出写入,即Import操作。 比如招投标管理里面有一个功能供应商认证,该功能在认证和审批通过后会形成一个合格供应商数据,写入到供应商管理模块。那么这个供应商认证功能对于供应商对象即是Import操作。

因此,我们可以将 对数据对象的CRUD操作全部打包在一起形成一个完整的业务模块 。对于R我们理解为基础查询功能,而非其他业务功能实现中的类似Reference的引用查询功能。

不论哪种场景,实际的建议还是尽量少进行垂直+水平模式的混合切分。在这种混合切分场景下将极大的增加应用设计和开发的复杂度,加大对事务一致性控制的难度。数据虽然比较容易达到分布式水平扩展的需求,但是应用开发复杂度和业务一致性的要求却大大降低,往往得不偿失。

数据库拆分后的应用开发

在这里以采购订单的创建和查询场景为例进行说明。

为了简化模型,在考虑了数据共享设计后,SID共享数据库中包括了供应商信息、物料信息、数据字典信息、人员组织信息。SID库为纯读库。而采购模块的私有库中包括了采购订单头和采购订单行信息。基于以上数据模型和假设,具体分析下采购订单创建流程和采购订单查看功能的实现:

对于采购订单创建流程,具体的实现过程可以描述如下:

1.进入创建界面后的初始化,直接取Session信息,当然Session信息应该在一登录时候就获取SID库人员,权限信息进行初始化。

2.订单头选择供应商-直接查询SID库供应商查询服务,获取供应商ID,名称和关键熟悉信息。

3.采购订单类型,付款方式等直接查询SID库,在第一次查询后该部分数据直接进行缓存

4.录入订单明细的时候,选择物料调用SID库物料信息查询服务,取回物料ID和相关属性信息。

5.订单保存时,直接调用ADB库自己的保存方法,和SID库不再有关系。

6.对于采购订单查看流程,具体的实现过程可以描述如下:

7.从本地ADB库获取到订单头和订单明细信息。

8.根据订单头中的供应商ID信息,查询SID库供应商查询服务,获取供应商名称和其它属性。

9.根据订单头中订单类型,付款方式的ID查询数据字典服务,获取信息或直接从缓存获取。

对于订单明细查看,注意一般是分页查询操作,具体如下:

1)对当前页显示的订单明细条目的物料ID信息进行组合,形成字符串;2)根据组合字符串,调用物料信息查询服务,获取所有的相关物料的属性信息集合;3)逻辑层进一步组合,将返回的详细物料属性和订单明细属性组合现实到订单明细表格。

可以看到在SID库不通过数据复制到本地,调用服务方式仍然是比较容易实现的。在这种模式下,传统实现中在一个大库中简单的关联查询结果集操作,将转换为应用逻辑层多次调用后的组装。这个带来了一定在应用开发中的工作量,但是总体问题并不大。只要不出现在分库后出现的业务模块操作需要跨库的CUD操作,就不会带来分布式事务的场景和问题。

数据库拆分后的其它关键问题说明

01-分布式事务问题

对于分布式事务的问题,前面已经多次提及。对于一个在DaaS环境下,针对逻辑库(一个逻辑库下面存在多个物理库节点)的操作是可以通过标准的XA两阶段提交协议来实现分布式事务的。但是这里不仅仅是可靠性的问题,更加关键的是性能问题,特别是在高并发场景下的性能问题。

因此在应用实现的过程中还是需要尽量避免使用分布式事务,仅仅在需要使用分布式事务的少数特殊场景通过显性声明的方式使用分布式事务。对于能够采用事务最终一致性BASE的场景,尽量是结合消息中间件的能力,采用最终一致性的方式;对于不能接受最终一致性的场景尽量采用事务补偿的方式来弥补事务失败造成的影响。

基于上面的业务,考虑一个最简单的分布式事务场景,即:在最终订单生效的时候,一方面是更新订单的生效状态;另一方面是需要向配送模块触发生成一张配送单。在这里涉及到CRUD的跨库操作。对于这种场景,建议是能够用BASE模式解决的尽量用BASE模式解决。具体如下:

更新订单最终生效状态,同时发送生成配送单消息(可以是本地的临时队列表以避免分布式事务),消息发送基于消息中间件模式,只需发送到MQ即可。

消息中间件对接收到的消息进行处理和分发,如果失败的话进行重试。

如果多次失败,则需要进行手工处理或手工对订单进行回滚。在这里关键是思考除非出现技术故障,否则不可能出现配送单无法发送成功和生成出来的可能。

02-跨库查询问题

在数据拆分有原有的一个单库多表关联查询操作,往往会转变为一个跨库的Join查询操作,而现在的针对MySQLl的DaaS方案很难真正的支撑到这种类型的操作,即使能够支持估计也很难真正达到一个高性能的状态。

在我们原来的设想中这些问题都简单地转化为应用层去解决,这势必增加了一个应用层开发的复杂度和难度。针对这种情况最好的方法是构建一个统一的领域服务层来解决,即最终的上层或顶层关注的是领域服务能力,虽然跨库的问题在DaaS层很难解决,但是在领域服务层却比较容易定制开发相应的服务来解决。

举例来说,一个采购订单查询,采购订单头和明细信息在一个逻辑库,而对于物料和供应商主数据在另外一个物理库,但是对于应用来说关注的是一个完整的采购订单信息。因此完全是可以在领域服务层提供一个采购订单查询的服务,在服务内部进行多次的DaaS层服务调用和组装来完成内部的复杂性。

这也是我们常说的,在进行数据库拆分后,务必需要引入更加强壮的领域服务层的原因。

03-数据统计分析问题

在数据拆分后还有一个比较难以解决的问题,是对于业务系统的大量查询分析和统计功能的处理。由于我们的数据库进行了切分,导致这些功能已经类似于传统BI里面的OLAP层的功能特性。对于这种业务场景和需求,往往并没有完全的实时性需求,我们能够满足准实时性就可以了。

因此对于这类功能推荐的方法仍然是需要将当前的各个分库里面的数据整合到NewSQL数据库里面进行处理(Hive,infobright,impala)等,这些数据库需要满足的特性就是MPP+Share nothing架构特性。在这种架构下可以看到对于海量数据的分析和统计可以保证业务需要的准实时性要求。唯一需要考虑的是当前很多的NewSQL数据库都是一个读库,很难进行CUD等各种操作。

因此转化后需要解决的问题就是对于业务库中的增量数据如何实时的更新到NewSQL数据库里面,注意是增量更新而不是类似当前很多方案里面的全库重新导入和生成,这也是在解决查询统计功能的一个难点。

对于MySQL的读写分离集群我们看到,随着slave节点的增加,为了保证master和slave节点之间的一致性,将会出现明显的延迟,也直接影响到应用CUD操作的性能。对于这个问题,当前可以考虑的解决方案就是要拆分为两级的读写分离集群,对于第一级的读节点保证高一致性和性能,对于第二级允许有较大的延迟,仅仅用于查询分析等。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK