[关闭]
@yanglfyangl 2018-05-22T10:07:13.000000Z 字数 2799 阅读 442

引擎技术分析

引擎的一些要求

  • 每个“对前端业务端”的接口的性能都要在平均100ms以下,需要支持批量查询,让业务逻辑能更高效的调用。(目标应该是平均50ms)
  • 对于聚合结果部分,不能在“对前端业务端”的调用中进行计算,走异步计算,将计算结果存入。
  • Mysql的使用上,只能用主键写入和读取数据库,所有的查询不能用数据库的groupby, join, count, sum 等任何方式。

需要几个“小”引擎

  1. 关系引擎。
    用于各种关系的处理。包括在大数据量的情况下的(需要毫秒级)
    a. 加入/删除关系。
    b. 判断是否存在关系。(速度要求极高
    c. 进行查询(比如列举我的好友,列举我加入的群。。。)
  2. 规则引擎。
    a. 支持自定义规则。
    b. 根据规则调用相关的接口。
    c. 线上更新规则而不停机。
  3. 统计查询引擎。
    a. 支持大数据量。
    b. 多维度。
    c. 支持准实时和离线。

基本调用流程

写数据流程示例

Created with Raphaël 2.1.2业务端业务端服务A服务A用户库用户库RedisRedisKafkaKafka用户统计服务用户统计服务其它统计服务其它统计服务新建用户入库写缓存入队“有新用户”的消息统计更新统计后结果统计更新统计后结果如果有缓存,更新如果有缓存,更新

读数据流程示例

Created with Raphaël 2.1.2读用户信息查缓存Yes or No?刷新缓存返回数据给调用端查数据库写缓存yesno

稍特殊设计

分层统计

引擎有很多统计性要求,这些统计型要求如果使用mysql来抗,几乎是不可能的。但对运营或业务来说,统计的实时性又有一定的要求。所以我们也需要进行一定的支持。

基于此,对应的统计方式建议是

将统计方式分为:

  • 实时
  • 准实时
  • 离线
  • 非预期

不同的计算方式用不同的技术栈

  • 离线部分:主要是指 基于日,周,月的统计和全部数量的计算;由Spark来做。完成后写入历史记录库。
  • 准实时部分:每天的统计,走Kafka Streaming或计算中心。
  • 实时部分:我们尽量避开
  • 难预期部分:我们可以将数据写入HDFS,然后如果需要查询,走Hive.

流程示例:用趣豆花费举例

Created with Raphaël 2.1.2用户用户ControllerControllerPaymentServicePaymentService豆账户DB豆账户DB豆流水DB豆流水DB队列队列准实时统计服务准实时统计服务统计DB统计DB大数据大数据买鲜花扣趣豆扣趣豆写流水返回返回扣豆入队计算近期数据读取批量读取离线计算历史数据计算读今天统计读近期统计表返回。读上周统计读历史统计表返回。

大并发下的调用第三方的异步调用怎么处理

引擎中有很多调用会涉及到调用第三方,比如

  • 融云(注册,发系统消息)
  • 短信运营商
  • 。。。

调用这些服务最大的问题是时间会很长,很容易造成性能问题,这种情况下的逻辑应该是:

融云创建用户慢的解决方案:

  • 提前创建一批融云用户ID和群ID,这样在注册时,就不需要再与融云服务器同步,大大提高速度。
  • 当用户注册时,选择一个可用的;
  • 同时异步去刷新用户或群状态(头像,名称。。。)

其它的更新操作,全部走“异步,最终一致性”,不轻易使用同步调用。

规则流控制

引擎中有一部分是通过规则来控制流程的。

比如:
一个用户在圈子里购买了什么东西
1. 如果是行为会员,则向圈子里送XXX钱
2. 如果是圈主,则趣币加20%
3. 。。。

这部分我们可以增加一个规则处理中心,这个处理中心通过消息来处理,不要阻塞业务端逻辑

Created with Raphaël 2.1.2业务端业务端购买服务购买服务数据库数据库RocketRocket规则引擎服务规则引擎服务服务B服务B数据库C数据库C新建用户入库有新的购买事件订阅执行(QLExpress)

使用规则引擎有如下好处:
1. 规则可以和产品经理一起定义和审核。
2. 规则可以在线不停机修改。(需要一定的工作量)
3. 某种程度上的逻辑变化容易,可维护性高。

也有一些限制
1. 不如写代码灵活。
2. 性能有一定的损失(5%左右)。
3. 调试需要一定的技巧,不太方便。

阿里开源的QLExpress可以做为参考项。

各种关系的处理方式

关系尽量走图引擎,这样查询相对容易

创建并加入关系的逻辑走如下模式:

  1. @Translation(...)
  2. bool CreateTopic(){
  3. bool isSucess = true;
  4. try{
  5. TopicService.Save(); //这时候数据库选择需要是关系型的
  6. } catch(error){
  7. isSuccess = false;
  8. }
  9. if(isSuccess){
  10. if(relationEngine.publicTopicToCircle(topicID, circleID)){
  11. rollback();
  12. isSuccess = false;
  13. }
  14. }
  15. return isSucess;
  16. }

支持大规模关系的关系引擎

我们的场景对于关系的量可能性会比较大,比如:

  • 一个圈子里有百W甚至千万的人。
  • 一个大V可能会有上千万的粉丝。
  • 。。。

需要支持的功能有:

  • 快速判断两个ID之间的关系(支持批量判断)
  • 快速列举某个范围内的某种关系的列表(比如大V的6w到6w零10个人的列表)
  • 支持关系型搜索。

设计思路:

  • 关系本身存入KV数据库。
  • 为了支持更复杂的查询,一部分最新的数据存入图引擎中。(比如我在圈中好友发的朋友圈。。。)

Kv数据库可选方案

  • SSDB --推荐--(类似中通快递等在用)
  • LedisDB PinCAP(北京)公司开源的。但相对用的人少。

KV数据库Key定义及作用:

Key名 格式 描述 用途
Relation_fromID_toID 固定Value为1 用户每加一个关系,增加一条 判断两个用户是否有这种关系
Relation-List_fromID_toID ZSet 用户表,按时间排序 某个ID某种关系下的列表

伪代码:

  1. RelationType:{
  2. BeFriend,
  3. BeGreenGriend,
  4. ...
  5. ...
  6. }
  7. bool addRelation(from, to, RelationType){
  8. //
  9. addToKvDB(){
  10. addRelation();
  11. addToList();
  12. }
  13. sendToQueue(){
  14. //队列的接收端执行写入ES操作。
  15. addToTu();
  16. }
  17. }
  18. List<Bool> areTheyFriends(List<id1, id2>){
  19. //如果支持管道,就走管道。
  20. String Key = "BeFriend_" + id1 + "_" + id2;
  21. if(1 == KvDB.getKey(Key)){
  22. 如果取出来值为1,则是好
  23. } else {
  24. 否则不是好友关系。
  25. }
  26. }
  27. List<String> getSameFriends(fromID, toID){
  28. String Key1 = "BeFriend_List" + fromID
  29. String Key2 = "BeFriend_List" + toID
  30. return sinterstore(key1, key2).get(100); //求交集,只取一定的数量。
  31. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注