http://www.zhzq.site
难点:并发量大,应用、数据库都承受不了。难控制超卖。
设计要点:
其他业务和技术保障措施:
另外需要复习缓存穿透、雪崩等等问题,主要的流量都落在了缓存数据库上,需要针对缓存数据库的高可用作保障。
如何设计一个秒杀系统08-答疑解惑:缓存失效的策略应该怎么定?
比较公认的方案:
看了下新浪微博的短链接,8位,理论上可以保存超过200万亿对关系,具体怎么存储的还有待研究。

雪花算法分布式ID组成部分(64bit)
SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢:: 同一毫秒的ID数量 = 1024 X 4096 = 4194304
优点是不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,是递增且唯一的。缺点是,如果服务器出现时钟回拨,可以导致ID重复。
时间位可以是与某个指定时间点的时间戳差值,而不是时间戳的0值.
MongoDB的ObjectID也是通过”时间+机器码+pid+inc”共12个字节,通过4+3+2+3的方式最终标识成一个24长度的十六进制字符。
对于worker_id的确定,可以借助通过 Zookeeper、Consul、Etcd 等提供分布式配置功能的中间件,也可以直接通过该机器的ip或本机配置获取worker id。
由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可。或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警。
实现关键代码
public class SnowFlakeService {
//起始时间戳( 2020-12-26 00:00:00 )
private static final long START_STAMP = 1608912000000L;
//序列号最大值
private static final long MAX_SEQUENCE = -1L ^ (-1L << 12); // 4095
private static long sequence; //序列号 range(0 ~ 4095)
public static synchronized long getNextId() {
long currentStamp = System.currentTimeMillis();
if (currentStamp < lastStamp) {
throw new IllegalArgumentException("时间被回退,不能继续产生id");
}
if (currentStamp == lastStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
if (sequence == 0L) {
//序列号已经到最大值
System.out.println("序列号已经到达最大值");
//使用下一个时间戳(如果还没到下一时间戳,就陷入阻塞直至获得下一个时间戳才返回)
currentStamp = getNextStamp();
}
} else {
//不同毫秒,序列号重置
sequence = 0L;
}
lastStamp = currentStamp;//当前时间戳存档,用于判断下次产生id时间戳是否相同
return (currentStamp - START_STAMP) << TIMESTAMP_LEFT
| dataCenterId << DATACENTER_LEFT
| machineId << MACHINE_LEFT
| sequence;
}
/**
* 阻塞直至获得下一个时间戳
* 注意:本机获取时间效率不够高,并且可能因为NTP导致时钟回拨出现错误。美团 Leaf-snowflake 方案引入zookeeper,每隔一段时间让本机上报时间,首先从zookeeper服务获取时间,如果发生回拨就拒绝服务或等待时钟恢复到递增,如果zookeeper服务异常才从本机获取时间。
*
* @return
*/
public static long getNextStamp() {
long newStamp = getCurrentStamp();
while (newStamp <= lastStamp) {
newStamp = getCurrentStamp();
}
return newStamp;
}
...
}
微信红包(尤其是发在微信群里的红包,即群红包),业务形态上很类似网上的普通商品秒杀活动。
就像下面这样:
不过除了上面的相同点之外,微信红包在业务形态上与普通商品秒杀活动相比,还具备自身的特点。
首先:微信红包业务比普通商品秒杀有更海量的并发要求。
微信红包用户在微信群里发一个红包,等同于在网上发布一次商品秒杀活动。假设同一时间有 10 万个群里的用户同时在发红包,那就相当于同一时间有 10 万个秒杀活动发布出去。10 万个微信群里的用户同时抢红包,将产生海量的并发请求。也就是说,同一个秒杀的总量不大,但是全局的并发量非常大,比如春晚可能几百万人同时抢红包。
其次:微信红包业务要求更严格的安全级别。
微信红包业务本质上是资金交易。微信红包是微信支付的一个商户,提供资金流转服务。
用户发红包时,相当于在微信红包这个商户上使用微信支付购买一笔钱,并且收货地址是微信群。当用户支付成功后,红包发货到微信群里,群里的用户拆开红包后,微信红包提供了将钱转入折红包用户微信零钱的服务。
资金交易业务比普通商品秒杀活动有更高的安全级别要求。普通的商品秒杀商品由商户提供,库存是商户预设的,秒杀时可以允许存在超卖(即实际被抢的商品数量比计划的库存多)、少卖(即实际被抢的商户数量比计划的库存少)的情况。但是对于微信红包,用户发 100 元的红包绝对不可以被拆出 101 元;用户发 100 元只被领取 99 元时,剩下的 1 元在 24 小时过期后要精确地退还给发红包用户,不能多也不能少。
以上是微信红包业务模型上的两大特点。
不能采用的方式:
建议的方式:
对 DB 的操作流程有以下三步,这三步操作需要在一个事务中完成:
设计难点就在这个事务操作上。商品库存在 DB 中记为一行,大量用户同时秒杀同一商品时,第一个到达 DB 的请求锁住了这行库存记录。在第一个事务完成提交之前这个锁一直被第一个请求占用,后面的所有请求需要排队等待。同时参与秒杀的用户越多,并发进 DB 的请求越多,请求排队越严重。因此,并发请求抢锁,是典型的商品秒杀系统的设计难点。
社交软件红包技术解密(一):全面解密QQ红包技术方案——架构、技术实现等
在接入层,为了解决同时存在海量事务级操作的问题,将海量化为小量。可以
在抽奖阶段,进行按设计合理的几率完成抽奖操作,将抽奖结果安全落地保存,并顺利发货等过程。面对海量抽奖请求,如何及时作出响应,是抽奖系统面临的难题。需要
抽奖之前,需要先进行安全审计、账号鉴权、用户信息查询,然后执行抽奖逻辑后写入流水系统。流水系统和发货系统通过异步对账,保证库存变化和不会超卖。当抽奖模块请求发货系统给用户发货时,发货系统将执行发放过程。
另外,为了尽可能在抽奖前阶段缓存更多数据,而避免动态查询,可以将用户信息缓存在主机内存中,如果要从业务测改变中奖逻辑,还可以缓存用户中奖历史信息。
流水系统用于保存活动过程中的抽奖流水记录,可以在活动后对奖品发放和领用进行统计和对账。该系统还定时对领用失败的请求进行重做和对账,确保奖品发放到用户账户里。由于流水需要记录用户中奖的信息和领用的的情况,数据量巨大,所以抽奖逻辑层本地采用顺序写文件的方式进行记录。抽奖逻辑层会定期的把本地的流水文件同步到远程流水系统进行汇总和备份,同时,流水系统会对领用失败的流水进行重做,发送请求到抽奖逻辑层,抽奖逻辑层会调用发货系统的接口完成发货操作。
发货系统可能会对接各种不同的业务接口,为了避免发奖请求引起业务侧雪崩,就需要发货系统有队列延迟功能,可以暂存发奖信息,延迟发货。可以采用开源的 RocketMQ 消息中间件作为异步消息队列,暂存发货请求,再由礼券发货模块根据各业务的限速配置均匀地调用业务接口进行发货。
发货模块首先会到存储系统检查奖品是否真实有效,再到发货状态存储检查状态是否正常,只有真正需要的发货的奖品才向业务系统发起发货请求,确保发货的有效性,避免错发和多发。
由于采用异步发货,抽奖时刻奖品不能保证立即发放到用户账户中。但用户的奖品不会丢失,通过在异步队列中暂存,礼券发货模块逐步以合适的速度将奖品发放到用户账户中。如果发货过程中有延时或失败,用户可以通过多次领取提起发货请求,系统支持多次提交。如果多次发货仍然失败,对账工具第 2 天会从流水系统中将用户抽奖数据与发货数据进行对账,对发货异常用户再次发起发货。如果对账仍然失败,则提醒管理人员介入处理。
另外,在客户端也需要做一些工作
当遇到瞬时请求量激增或恶意请求时,会导致接口占用过多服务器资源,使得其他请求响应速度降低或是超时,更有甚者可能导致服务器宕机。
限流(Ratelimiting)指对应用服务的请求进行限制,例如某一接口的请求限制为 100 个每秒,对超过限制的请求则进行快速失败或丢弃。
常见的限流方法:

Google 的开源项目 guava 提供了 RateLimiter 类,实现了单点的令牌桶限流。
分布式环境下,常用的有 Hystrix、resilience4j、Sentinel 等框架,但这些框架都需引入第三方的类库。
Sentinel: The Sentinel of Your Microservices
也可以考虑用 Redis的hash+Lua 脚本实现令牌桶。
或者用队列实现
几个业务场景:
一般来说,系统可以使用消息传递代替部分定时任务,两者有很多相似之处,可以相互替换场景。如,上面发货成功发短信通知客户的业务场景,我们可以在发货成功后发送MQ消息到队列,然后去消费mq消息,发送短信。
但在某些场景下不能互换:
分布式定时任务框架
发货后超过10天未收货时系统自动确认收货的多种实现方式:
每天定时半夜筛选第二天 可以自动确认收货的订单,然后第二天 每10分钟 执行一次确认收货 开销不会太大吧 时间也相对精确 自动确认收货这个状态如果仅仅是让客户端看的话,等用户下一次上线的时间,做一次运算就可以了。 延迟和定时消息投递 ActiveMQ提供了一种broker端消息定时调度机制。适用于:1、不希望消息马上被broker投递出去,而是想要消息60秒以后发给消费者,2、想让消息每隔一定时间投递一次,一共投递指定的次数 RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter。利用DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange。这时候消息就可以重新被消费。
自己实现的话,要根据任务轮询或任务轮询+抢占排队方案
在主站或客户端点一下刷新,最新获得了十到十五条微博,它这个是怎么构建出来的呢?刷新之后,首先会获得用户的关注关系,比如她有一千个关注,会把这一千个 ID 拿到,根据这一千个 UID,拿到每个用户发表的一些微博,同时会获取这个用户的 Inbox,就是她收到的特殊的一些消息,比如分组的一些微博,群的微博,下面她的关注关系,她关注人的微博列表,拿到这一系列微博列表之后进行集合、排序,拿到所需要的那些 ID,再对这些 ID 去取每一条微博 ID 对应的微博内容,如果这些微博是转发过来的,它还有一个原微博,会进一步取原微博内容,通过原微博取用户信息,进一步根据用户的过滤词,对这些微博进行过滤,过滤掉用户不想看到的微博,留下这些微博后,再进一步来看,用户对这些微博有没有收藏、赞,做一些 flag 设置,最后还会对这些微博各种计数,转发、评论、赞数进行组装,最后才把这十几条微博返回给用户的各种端。这样看,用户一次请求,最终得到十几条记录,后端服务器大概要对几百甚至几千条数据进行实时组装,再返回给用户,整个过程对 Cache 体系强度依赖。所以 Cache 架构设计优劣直接会影响到微博体系表现的好坏。
微博 Cache 架构演进过程,最开始微博上线的时候,都是把它作为一个简单的 KV 证人数据类型来存储,我们主要采取哈希分片存储在 MC 池子里,上线几个月之后发现一些问题,有一些节点机器宕机或者其它方面原因,大量的请求会穿透 Cache 层达到 DB 上去,导致整个请求变慢,甚至 DB 僵死。于是我们很快给它改造增加一个 HA 层,这样即便 Main 层出现某些节点宕机情况或者挂掉之后,这些请求会进一步穿透到 HA 层,不会穿透 DB 层,这样的话可以保证在任何情况下,整个系统命中率不会降低,系统服务稳定性比较大提升。
微博和微信很大的区别,实际上微博是一个广场型的业务,比如突发事件,某明星找个女朋友,瞬间流量就 30%,突发事件后,大量的请求会出现在某一些节点,会导致这个节点非常热,即便是 MC 也没办法满足这么大的请求量。这时候整个 MC 就会变成瓶颈,导致整个系统变慢,基于这个原因我们引入 L1 层,还是一个 Main 关系池,每一个 L1 大概是 Main 层的 N 分之一,六分之一、八分之一、十分之一这样一个内存量,根据请求量我会增加 4 到 8 个 L1,这样所有的请求来了之后首先会访问 L1,L1 命中的话就会直接访,如果没有命中再来访问 Main-HA 层,这样在一些突发流量的时候,可以由 L1 来抗住大部分热的请求。对微博本身来说,新的数据就会越热,只用增加很少一部分内存就会抗住更大的量。
| 对读写策略,采取多写,读的话采用逐层穿透,如果 Miss 的话就进行回写,对存在里面的数据,我们最初采用 Json/xml,12 年之后就直接采用 Protocol | Buffer 格式,对一些比较大的用 QuickL 进行压缩。 |
对于复杂的集合类数据怎么来处理,比如我关注了 2000 人,新增一个人,这就涉及到部分修改。有一种方式把 2000 个 ID 全部拿下来进行修改,这种对带宽、机器压力会更大。还有一些分页获取,我存了 2000 个,只需要取其中的第几页,比如第二页,也就是第十到第二十个,能不能不要全量把所有数据取回去。还有一些资源的联动计算,会计算到我关注的某些人里面 ABC 也关注了用户 D,这种涉及到部分数据的修改、获取,包括计算,对 MC 来说它实际上是不太擅长的。各种关注关系都存在 Redis 里面取,通过 Hash 分布、储存,一组多存的方式来进行读写分离。现在 Redis 的内存大概有 30 个 T,每天都有 2-3 万亿的请求。
在使用 Redis 的过程中实际上还是遇到其他一些问题,比如从关注关系,我关注了 2000 个 UID,有一种方式是全量存储,但微博有大量的用户,有些用户登陆比较少,有些用户特别活跃,这样全部放在内存里面成本开销是比较大的。所以我们就把 Redis 使用改成 Cache,比如只存活跃的用户,如果你最近一段时间没有活跃之后,会把你从 Redis 里面踢掉,再次有访问到你的时候把你加进来。这时候存在一个问题,Redis 工作机制是单线程模式,如果它加某一个 UV,关注 2000 个用户,可能扩展到两万个 UID,两万个 UID 塞回去基本上 Redis 就卡住了,没办法提供其他服务。所以我们扩展一种新的数据结构,两万个 UID 直接开了端,写的时候直接依次把它写到 Redis 里面去,读写的整个效率就会非常高,它的实现是一个 long 型的开放数组,通过 Double Hash 进行寻址。
对 Redis 来说我们进行了一些其他的扩展,之前的一些分享,大家在网上也会看到,把数据放到公共变量里面,整个升级过程,我们测试 1G 的话加载要 10 分钟,10G 大概要十几分钟以上,现在是毫秒级升级。对于 AOF,我们采用滚动的 AOF,每个 AOF 是带一个 ID 的,达到一定的量再滚动到下一个 AOF 里面去。对 RDB 落地的时候,我们会记录构建这个 RDB 时,AOF 文件以及它所在的位置,通过新的 RDB、AOF 扩展模式,实现全增量复制。
接下来还有一些其他的数据类型,比如一个计数,实际上计数在每个互联网公司都可能会遇到,对一些中小型的业务来说,实际上 MC 和 Redis 足够用的,但在微博里面计数出现了一些特点,单条 Key 有多条计数,比如一条微博,有转发数、评论数、还有点赞,一个用户有粉丝数、关注数等各种各样的数字,因为是计数,它的 Value size 是比较小的,根据它的各种业务场景,大概就是 2-8 个字节,一般 4 个字节为多,然后每日新增的微博大概十亿条记录,总记录就更可观了,然后一次请求,可能几百条计数要返回去。
最初是可以采取 Memcached,但它有个问题,如果计数超过它内容容量的时候,它会导致一些计数的剔除,宕机或重启后计数就没有了。另外可能有很多计数它是为零,那这个时候怎么存,要不要存,存的话就占很多内存。微博每天上十亿的计数,光存 0 都要占大量的内存,如果不存又会导致穿透到 DB 里面去,对服务的可溶性就会存在影响。2010 年之后我们又采用 Redis 访问,随着数据量越来越大之后,发现 Redis 内存有效负荷还是比较低的,它一条 KV 大概需要至少 65 个字节,但实际上我们一个计数需要 8 个字节,然后 Value 大概 4 个字节,实际上有效只有 12 个字节,其他还有四十多个字节都是被浪费掉的,这还只是单个 KV,如果一条 Key 有多个计数的情况下,它就浪费得更多了,比如说四个计数,一个 Key8 个字节,四个计数每个计数是 4 个字节,16 个字节大概需要 26 个字节就行了。但是用 Redis 存大概需要 200 多个字节。后来通过自己研发 Counter Service,内存降至 Redis 的五分之一到十五分之一以下,而且进行冷热分离,热数据存在内存里面,冷数据如果重新变热,就把它放到 LRU 里面去。落地 RDB、AOF,实现全增量复制,通过这种方式,热数据单机可以存百亿级,冷数据可以存千亿级。
在内存里面是预先把它分成 N 个 Table,每个 Table 根据 ID 的指针序列,划出一定范围,任何一个 ID 过来先找到它所在的 Table,如果有直接对它增增减减,有新的计数过来,发现内存不够的时候,就会把一个小的 Table Dump 到 SSD 里面去,留着新的位置放在最上面供新的 ID 来使用。有些人疑问说,如果在某个范围内,我的 ID 本来设的计数是 4 个字节,但是微博特别热,超过了 4 个字节,变成很大的一个计数怎么处理,对于超过限制的把它放在 Aux dict 进行存放,对于落在 SSD 里面的 Table,我们有专门的 IndAux 进行访问,通过 RDB 方式进行复制。
除了计数的话,微博还有一些业务,一些存在性判断,比如一条微博展现的,有没有点赞、阅读、推荐,如果这个用户已经读过这个微博了,就不要再显示给他,这种有个很大的特点,它检查是否存在,每条记录非常小,比如 Value1 个 bit 就可以了,但总数据量巨大。比如微博每天新发表微博 1 亿左右,读的可能有上百亿、上千亿这种总的数据需要判断,怎么来存储是个很大的问题,而且这里面很多存在性就是 0,还是前面说的,0 要不要存,如果存了,每天就存上千亿的记录,如果不存,那大量的请求最终会穿透 Cache 层到 DB 层,任何 DB 都没有办法抗住那么大的流量。
首先直接考虑我们能不能用 Redis,单条 KV65 个字节,一个 KV 可以 8 个字节的话,Value 只有 1 个 bit,这样算下来我每日新增内存有效率是非常低的。第二种我们新开发的 Counter Service,单条 KV Value1 个 bit,我就存 1 个 byt,总共 9 个 byt 就可以了,这样每日新增内存 900G,存的话可能就只能存最新若干天的,存个三天差不多快 3 个 T 了,压力也挺大,但比 Redis 已经好很多。
最终方案采用自己开发 Phantom,先采用把共享内存分段分配,最终使用的内存只用 120G 就可以,算法很简单,对每个 Key 可以进行 N 次哈希,如果哈希的某一个位它是 1,如果进行 3 次哈希,三个数字把它设为 1,把 X2 也进行三次哈希,后面来判断 X1 是否存在的时候,进行三次哈希来看,如果都为 1 就认为它是存在的,如果某一个哈希 X3,它的位算出来是 0,那就百分百肯定不存在的。
它的实现架构比较简单,把共享内存预先拆分到不同 Table 里面,在里面进行开方式计算,然后读写,落地的话采用 AOF+RDB 的方式进行处理。整个过程因为放在共享内存里面,进程要升级重启数据也不会丢失。对外访问的时候,建 Redis 协议,它直接扩展新的协议就可以访问我们这个服务了。
存储成本以及运维性呢?
采取的方案首先就是对整个 Cache 进行服务化管理,对配置进行服务化管理,避免频繁重启,另外如果配置发生变更,直接用一个脚本修改一下。
服务化还引入 Cluster Manager,实现对外部的管理,通过一个界面来进行管理,可以进行服务校验。服务治理方面,可以做到扩容、缩容,SLA 也可以得到很好保障。
主要难点:关系复杂,数据量大。一个人可以关注非常多的用户,一个大 V 也有可能有几千万的粉丝。
先介绍最基本的方案:
一般采用推拉结合的方式,用户发送状态之后,先推送给粉丝里面在线的用户,然后不在线的那部分等到上线的时候再来拉取。
另外冷热数据分离,用户关系在缓存里面可以设置一个过期时间,比如七天。七天没上线的可能就很少用这个 APP。
对于远高于内存的文件排序。
外归并排序:
如果是数字,可以用位图排序,但是要求比较苛刻:
比较适合电话号之类的。
外归并排序(External merge sort),它读入一些能放在内存内的数据量,在内存中排序后输出为一个顺串(即是内部数据有序的临时文件),处理完所有的数据后再进行归并。比如,要对900MB的数据进行排序,但机器上只有100 MB的可用内存时,外归并排序按如下方法操作:
读入100 MB的数据至内存中,用某种常规方式(如快速排序、堆排序、归并排序等方法)在内存中完成排序。 将排序完成的数据写入磁盘。 重复步骤1和2直到所有的数据都存入了不同的100 MB的块(临时文件)中。在这个例子中,有900 MB数据,单个临时文件大小为100 MB,所以会产生9个临时文件。 读入每个临时文件(顺串)的前10 MB( = 100 MB / (9块 + 1))的数据放入内存中的输入缓冲区,最后的10 MB作为输出缓冲区。(实践中,将输入缓冲适当调小,而适当增大输出缓冲区能获得更好的效果。) 执行九路归并算法,将结果输出到输出缓冲区。一旦输出缓冲区满,将缓冲区中的数据写出至目标文件,清空缓冲区。一旦9个输入缓冲区中的一个变空,就从这个缓冲区关联的文件,读入下一个10M数据,除非这个文件已读完。这是外归并排序能在主存外完成排序的关键步骤 – 因为归并算法(merge algorithm)对每一个大块只是顺序地做一轮访问(进行归并),每个大块不用完全载入主存。
负载均衡Load Balance)是高可用网络基础架构的关键组件,通常用于将工作负载分布到多个服务器来提高网站、应用、数据库或其他服务的性能和可靠性。负载均衡,其核心就是网络流量分发,分很多维度。
负载均衡是建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
用户访问负载均衡器,再由负载均衡器将请求转发给后端服务器。在这种情况下,单点故障现在转移到负载均衡器上了。 这里又可以通过引入第二个负载均衡器来缓解。
负载均衡器如何选择要转发的后端服务器?
负载均衡器一般根据两个因素来决定要将请求转发到哪个服务器。首先,确保所选择的服务器能够对请求做出响应,然后根据预先配置的规则从健康服务器池(healthy pool)中进行选择。
因为,负载均衡器应当只选择能正常做出响应的后端服务器,因此就需要有一种判断后端服务器是否健康的方法。为了监视后台服务器的运行状况,运行状态检查服务会定期尝试使用转发规则定义的协议和端口去连接后端服务器。 如果,服务器无法通过健康检查,就会从池中剔除,保证流量不会被转发到该服务器,直到其再次通过健康检查为止。
负载均衡算法?
负载均衡算法决定了后端的哪些健康服务器会被选中。 其中常用的算法包括:
除此之外,想要解决负载均衡器的单点故障问题,可以将第二个负载均衡器连接到第一个上,从而形成一个集群。
在系统选型上,对于热数据系统,需要重点考虑读写的性能问题,诸如MySQL、Elasticsearch等会成为首选;而对于冷数据系统,则需要重点关注低成本存储问题,通常会选择存储在HDFS或云对象存储(比如AWS S3)中,再选择一个相应的查询系统。冷热数据是按照时间推移来区分的,因此必然要敲定一个时间分割线,即多久以内的数据为热数据,这个值通常会结合业务与历史访问情况来综合考量。对于超过时间线的数据,会被迁移到冷数据中,迁移过程需要确保两点:不能对热数据系统产生性能影响、不能影响数据查询。数据分离后,不可避免的会出现某个查询在时间上跨到两个系统里面,需要进行查询结果的合并,对于统计类查询就可能会出现一定的误差,需要在业务层面有所妥协。
业务背景是,用户在系统下单后会生成相应的交易订单信息,每天会产生大量的订单数据。这些数据需要永久保存,随时面对用户的低延迟查询,通常近3个月的订单是用户查询的主要对象。
在该系统中,热数据毫无疑问会采用MySQL(InnoDB)来实现,满足事务操作和高效查询的需求。当然,在查询系统前面还会有一层缓存,这里略过。冷数据以宽表的形式存储到HBase中,并采用Elasticsearch来提供相关的索引查询,配合HBase的数据查询。热数据在MySQL中保留90天,之后迁移到HBase中作为冷数据永久保存。
对于一个交易请求,会先在MySQL的订单表中创建订单记录,这些操作会通过BinLog同步到Kafka中,由Spark Streaming程序从Kafka中将相关订单信息变动提取出来,做相应的关联处理后写入到HBase中,同时更新Elasticsearch中的索引信息。每天定期将冷热分割线往前推移,并删除热数据中对应时间的订单表。
从以下几个方面准备: