个人随笔
目录
架构师:从业务需求开始
2023-09-13 18:35:20

一、用缓存来缓解数据库的压力,提高应用的性能

1、背景

随着应用访问人数的上升,每个请求都打到数据库中,数据库的压力会过大,此时,我们可以引入缓存来缓解数据库的压力。我们可以先使用本地缓存,有条件的话使用分布式缓存,这样多级缓存来。本地缓存和分布式缓存可以两个一起用也可以单独使用,使用缓存后的请求流程如下

1、请求到应用服务器,先从本地缓存获取数据
2、本地缓存没有则从分布式缓存获取数据
3、分布式缓存没有再从数据库获取数据写回分布式缓存再写回本地缓存

因为缓存是直接读取内存的,所以相对比查询数据库,极大的提高了查询性能,也降低了数据库的压力。

2、场景

商品详情查询、商品列表查询、首页热门消息查询等。

3、技术选型

本地缓存:Ehcache、GuavaCache、Caffeine
分布式缓存:Redis、Memcached

二、用消息队列来进行异步、解耦和流量削峰

1、背景

比如我们用户注册,假设注册完后需要发送短信给用户或者提醒应用管理员,那么总不可能在一个接口中又注册,又发短信,又发邮件,所以也可以引入消息队列,注册成功后登记发短信发邮件的消息后就返回给前端。后面消费程序慢慢去发短信和邮件。这也是用消息队列来进行异步化提高系统性能,异步目的就是减少请求响应时间,实现非核心流程异步化,提高系统相应性能。

再比如我们经常会遇到类似授权获取用户信息的功能,比如用户进入页面,首先跳转到微信去授权登录获取code,然后我们的页面把code通过接口请求微信接口获取openid等信息。此时如果直接在接口里面调用微信的接口(或者其它第三方接口),然后前端等着接口返回,如果第三方接口比较耗时,那么接口服务器会一致占据这个线程,知道第三方返回或者超时,这种业务场景严重影响了接口服务器处理其它请求。我们可以引用消息队列,在接口服务器获取code后,登记消息到消息队列,直接返回个流水号给前端,然后消费程序获取code后再去调用第三方接口,返回结果后存入缓存中,前端根据流水号定时查询结果即可。这是用消息队列来进行异步处理的一种场景。

这里虽然也是核心流程,异步化的原因是因为调用了第三方,而第三的系统性能是未知的,所以为了不影响接口服务的性能,进行了异步化处理。其实也用了解耦思想,毕竟对于这种登录的场景,一个系统中可能会有多种登录,比如企微登录,微信登录,QQ登录,各种第三方对接的合作方登录授权,我们都可以抽取成这种模式。
1、前端请求获取第三方的code
2、调用登录接口传入code,返回流水号
3、根据流水号轮询调用接口查询登录结果
4、若轮询次数超过限制则提示超时。

后续要新增对对接方,只需要修改消费程序即可。

再比如用户下单,下单成功后,要调用库存系统扣减库存,要调用第三方系统推送订单,如果这些逻辑都写在接口里,后续新增业务场景,要把订单信息推送给更多的第三方,或者更具订单信息搞营销活动给用户赠送权益岂不是接口N行代码?所以这种业务场景一定要引入MQ消息队列来解耦,下单完就登记消息,要消费消息的系统直接跟消息队列对接即可,或者消息队列对接第三方系统。如果用RocketMQ的话还可以用分组消费模式,也就是多个消费者消费同一条消息。

总之来说,异步是为了提高性能,解耦是为了扩展和好修改。一部和解耦其实会有一部分重合的概念。

对于流量销峰,一个简单的业务场景就是秒杀或者抢购,如果直接打到数据库,数据库扛不住,那么我们用消息队列中消费者来跟数据库打交道做秒杀库存扣减逻辑,这样相当于让前端请求排队处理了,前端异步查询结果,虽然这里也起了异步的作用,但是业务本身的目的是为了流量销峰。对于秒杀这些场景,如果用户不是太大,比如并发几千上万而已,那么也可以用redis来做库存控制。但是最终还是会到数据库的,如果库存多的话,所以用消息队列加redis双重来做控制,那么几乎可以应多大多数秒杀业务场景。

2、场景

下单、注册、秒杀、抽奖等。

3、技术选型

RocketMQ、Kafka、Pulsar、RabbitMQ、NSQ

待续…

三、用负载均衡器对应用进行代理,实现应用的横向扩展以及防止单节点故障

1、背景

通常我们的应用前面都是有个nginx来对应用进行反向代理,隐藏应用的详细信息,并且可以通过负载均衡策略代理多个应用节点,通过横向扩展应用来提高系统的处理能力,并且可以保证应用的热更新,当然热更新需要的是前后端分离的应用,也就是最起码我们的应用必须是无状态的,通过token标识维系,这样更新应用的时候只要有一个或几个应用可用即可。所以为了防止单节点故障,正常我们都需要两个节点。

通常负载均衡服务器一个节点很少出现故障,但是也不一定,所以我们负载均衡服务器如果有必要的话,也可以用两个节点通过keepalived进行监测。

2、场景

负载均衡、横向扩展、热更新发版

3、技术选型

硬件负载均衡主要有:F5、Array、NetScaler
软件负载均衡主要有:LVS、HAProxy、Nginx
负载均衡服务器通常可以用keepalived来实现高可用。

四、用数据库读写分离来提高数据库的并发能力

1、背景

随着我们业务的复杂性,但是靠缓存已经不足以支撑,毕竟还有很多业务逻辑,比如点赞,收藏,下单,查询用户个人信息等很多还是直接到数据库的,随意我们可以在缓存的基础上,再进行数据库的读写分离,也就是主从同步,我们主库进行写入,而读取就从N个从库获取,毕竟互联网应用,大部分还是读,写比较少。

2、场景

读写分离

3、技术选型

MyCat、mysql proxy、Apache ShardingSphere(Sharding-JDBC、Sharding-Proxy、Sharding-Sidecar)

五、用数据库分库来降低各系统对数据库的资源竞争,分表解决单表过大的问题

1、背景

随着业务系统的发展,如果所有业务都链接同一个数据库,那么肯定会导致互相抢占资源,我们可以按业务进行分库,来降低资源抢占问题。当我们随着用户的增长或者业务的发展,某个表的数据量太大了后,我们可以对单表进行分表,来提高单表查询能力,当然这里分表也可以在不同库间进行,比如user表分为user1和user2,这里user1或者user2可能在一个库中,也可能在完全不相同的库中。

2、场景

对订单表,用户表进行分表,对支付系统,订单系统,会员系统进行分库

3、技术选型

这里的技术选型跟读写分离差不多:MyCat、mysql proxy、Apache ShardingSphere(Sharding-JDBC、Sharding-Proxy、Sharding-Sidecar)

六、使用搜索引擎解决全文检索问题

1、背景

比如我们的商城,需要根据用户输入的关键字,查询出推荐商品来,用户的关键字可能是商品的价格,名称,描述,简介,在海量的商品面前,不可能直接查询数据库,那么我们需要借助搜索引擎来提高查询性能。

再比如我们业务系统如果搭建了微服务,那么每天会产生无数的海量日志,我们查询故障的时候去看日志不可能一个个应用节点打开来搜索,此时我们也可以用搜索引擎来帮助我们快速定位问题。

2、场景

商品查询、新闻查询、日志查询

3、技术选型

Lucence、ElasticSearch、Solr

六、使用微服务将复用的功能抽离出来,每个应用都可以调用

1、背景

加入随着业务的发展,公司业务做大做强,涉及很多领取模块,比如有短视频、直播、论坛、微博,电影等业务,但是里面都有涉及会员、支付、下单这些公共的业务逻辑。这是如果对会员逻辑进行更新修改,全部业务模块的应用都需要改一遍,显然是不科学的,那么我们就得想办法把这些每个业务模块复用的公共的功能抽离出来,抽成独立的服务,各业务模块直接调用这些独立的服务即可,后续更新也只需要独立服务升级。

2、场景

商城的商品服务、会员服务、订单服务等。

3、技术选型

Apache Dubbo、Spring Cloud Alibaba、Spring Cloud、Spring Cloud Tencent

七、使用容器化技术对服务进行动态扩缩容

1、背景

如果我们的业务越做越大,比如做个商城,经常会有秒杀、抽奖大促啊,我们的服务一开始可能只有三四个节点,但是大促搞活动的时候访问量暴增可能需要几十个节点,不可能手动去拷贝部署吧,如果是类似大型商城全国大促双十一,可能会扩容到几千上万个节点,等大促结束后又缩回几十个节点,节省资源,此时我们就需要用容器化技术,比如用docker还有容器管理服务k8s,还有KubeSphere来辅助k8s来进行管理编排。

当然如果是扩缩容,建议使用云服务器,按量进行付费,这样可以在大促的时候动态购买扩容,结束后又释放,节约成本。

2、场景

大促服务扩缩容

3、技术选型

docker、kata container、lxd 、rkt、k8s、KubeSphere

八、通过用户行为匹配任务规则触发任务完成判定赠送相关权益来应对千奇百怪的活动需求

1、需求场景

假设我们的平台需要开发一个活动,业务需求说,让用户报名参加就送一次抽奖资格,然后我们开发人员噼里啪啦很快就在报名的接口里加上“报名成功在用户抽奖资格表加上一条抽奖资格”的逻辑,上线发版美滋滋。

过了一段时间,业务说,才一次抽奖资格不够,用户进来抽了就没了,就不会再进来活动了,最好用户每天首次进来活动都送一次抽奖资格。然后我们新增一个“访问活动”的接口,在访问活动里面加上“用户第一次访问活动就插入一条抽奖资格的逻辑”,上线发版美滋滋。

又过了一段时间,业务觉得,怎么每天新增参加抽奖活动的用户这么少呢?是不是用户不去分享拉新啊,立马叫开发加一个用户每天分享送一次抽奖资格的逻辑。然后我们开发人员新增一个“活动分享”接口,在业务逻辑加上“用户每天第一次分享就送一条抽奖资格”。

就这样又过了一段时间,发现新增用户还是不够,原来很多用户都直接分享到文件传输助手,或者分享到自己的一个小号,或者分享到一个毫无意义的群里,或者朋友圈仅自己可见,毫无意义,然后业务就告诉产品,每邀请2个人参加活动,就送一次抽奖机会。开发进过严密分析,打算分享的时候带出分享者的参赛流水,用户报名的时候顺便在报名接口里面保存连接中的参赛流水,然后根据参赛流水找到通过这个参赛流水报名的用户人数,看看够不够2人,够的话就给这个参赛流水用户插入一条抽奖资格。美滋滋,虽然有点小复杂,但是还是发版上线。

又过了一段时间,业务告知产品,为了最好的提高用户的粘度,新增个签到功能 ,用户每次签到送积分,连续签到送更多积分,连续签到3天 还额外赠送一次抽奖资格,然后用户还可以通过积分来抽奖。开发接到这个需求,新增了一个“签到”接口,根据签到的明细来赠送积分,如果满多少就再插入抽奖资格,还要再“抽奖”里面新增扣减积分进行抽奖的逻辑,虽然复杂,但是也还是测试上线了。

又过了一段时间
业务叫产品加上:用户点击页面的一些商品宣传海报,送积分,送抽奖资格
业务叫产品加上:通过用户的在商城的下单记录送积分,送抽奖资格
业务叫产品加上:用户开通VIP会员,送积分,送抽奖资格,直接送一袋大米。
业务叫产品加上:加入企微,送积分,抽奖资格和实物礼品。
….

我们为了业务的每一项需求写了无数的业务逻辑,新增了各种业务表,其中很多业务逻辑里面代码拷贝来拷贝去,改了这个牵一发动全身,关联的每个业务线都需要进行改动测试。。。

总之,业务需求是一直在扩展的,我们没有办法,必须适应业务需求进行迭代升级,毕竟技术是服务于需求的,抛开需求谈技术就是耍流氓.那么有什么办法来应对上面的需求叠加呢?

2、解决方案

在上面的需求叠加了几次后,比如到签到送抽奖资格积分的时候,我们开发人员就应该意识到,这些业务需求都有很多通用之处,也意识到业务既然分享会送抽奖资格,那么明天就可能提分享送积分的功能。大概分析总结下

  1. 用户通过报名,送抽奖资格和积分或者礼品
  2. 用户通过访问活动,送抽奖资格和积分或者礼品
  3. 用户通过分享行为,送抽奖资格和积分或者礼品
  4. 用户通过邀请参加活动,送抽奖资格和积分或者礼品
  5. 用户通过签到,送抽奖资格和积分或者礼品
  6. 用户通过下单,送抽奖资格和积分或者礼品
  7. 用户通过点击广告,送抽奖资格和积分或者礼品

上面的签名,是用户完成的各种操作,各种行为,各种任务,比如报名,分享,签到,累计签到,有些任务是单次行为来触发的,比如访问活动,报名,有些任务需要多次行为比如邀请N个用户报名,连续签到3天等,但是用户的行为导致任务达标后都可能产生送抽奖资格,送积分,或者直接赠送礼品的目的。

此时我们就会想到是否需要一个行为表tb_action(id,type,time,content),里面保存用户的每一次行为。类型(type)来区分用户的行为,比如报名,邀请报名,分享,访问,下单,签到等。content保存任务的内容,比如订单号什么的。

然后再有一个表,任务规则表tb_task_rule(id,type,name,condition,limit,right_type,right_info)。type为任务类型,其实跟行为类型对应,name为任务名字,condition为任务完成条件,比如当次完成,多次行为完成,limit为获取权益的限制,right_type为权益类型比如抽奖资格,积分,礼品,right_info为权益内容,比如抽奖资格数目,积分数目,礼品ID等。

然后再有一个任务完成记录tb_task_ok(id,type,name,rule_id,value,date,time,status)。type跟行为和任务规则一一致。rule_id为这条任务对应的ID,value为次数,date为日期,time为时间,status为状态。

上面只是大概的一些表设计,实际上肯定更加复杂,那么数据执行逻辑如下

  • 1、用户前端的行为会首先记录到行为表tb_action
  • 2、根据行为表的记录查询该行为匹配的任务规则表tb_task_rule
  • 3、根据任务规则表的条件来判断任务是否完成,要是完成则插入任务完成记录tb_task_ok
  • 5、根据规则中的权益类型来赠送权益。

上面是数据执行逻辑,那么代码架构怎样呢?如果说我们都在接口服务中实现,按模块来分三个模块:

  • 1、记录行为表tb_action的模块
  • 2、任务规则表tb_task_rule判断模块
  • 3、赠送权益模块。

也是可以的,但是可能会面临一些问题。

  • 1、每个业务接口都要调用这三个模块才能完成业务逻辑,
  • 2、后续有些需求判断任务规则可能需要调用第三方接口判断,耗时很久
  • 3、有些行为不允许通过前端接口产生,是后台产生的数据

所以我们实际实现推荐如下架构逻辑

  • 1、接口服务插入记录到行为表tb_action,登记行为消息
  • 2、行为消费程序根据行为匹配规则判断任务是否完成,完成登记任务完成消息、
  • 3、任务完成消费程序根据任务赠送权益类型赠送用户权益。

这样就完全异步解耦了,扩展任务规则,扩展权益赠送也很方便。产生各种行为也很方便,只需要记录到行为表tb_action和登记消息即可,可以直接操作数据库,不一定非得接口服务。困难点可能是要引入消息队列,增加了业务复杂度。有些任务判断规则比较复杂可能还需要引入reddis来放并发。

这样慢慢我们就形成一套:行为-任务规则-权益赠送的模式。后续再复杂的需求都可以有个较好的解决方案啦。

3、涉及技术

消息队列,缓存,规则判定等

九、通过匹配规则来完成千奇百怪的抽奖需求

1、需求场景

假设我们有一个抽奖功能,一开始只是简单的按概率来抽取奖品发给用户,刚开始上线是没有什么问题的,但是过了一段时间,业务会觉得,能不能控制每天的发奖上限,然后开发加了一段逻辑:每次抽奖判断当天已中奖上限,超过后就不允许中奖。

又过了一段时间,业务觉得,能不能控制每个奖品每天抽奖上限,然后开发加了一段逻辑:抽中奖品A后,判断奖品A的当天抽奖上限有没有超过限制,超过了就不允许再中奖。

又过了一段时间,业务觉得,能不能控制每个用户每天中奖上限,开发噼里啪啦加代码!

能不能控制每个用户总中奖上限,开发加代码!

能不能控制只能白天八点到晚上九点之间才能中奖,开发加代码!

能不能控制只有下了订单的才能中奖,开发加代码!

能不能控制广州的用户每天只能中固定奖品,开发加代码!

需求无穷无尽!

难道我们每次都要修改抽奖逻辑?这样风险也太大了!

2、解决方案

其实对上面的业务需求总结下,大概如下

1、根据用户配置的概率抽奖,若是抽奖奖品
2、判断总中奖上限
3、判断每天中奖上限
4、判断每个奖品每天中奖上限
5、判断是否在允许中奖时间内抽奖
6、判断用户是否下单
7、判断用户所在地区中奖是否超过上限
8、判断用户库存
9、用户中奖

可以看到,从2~8都是中奖后,一关关判断能否中奖。那么其实我们可以用规则模式就很容易重构代码了,伪代码如下

  1. interface CheckDrawService{
  2. boolean checkDraw();
  3. }
  1. //判断总中奖上限
  2. CheckAllLimit implement CheckDrawService{
  3. boolean checkDraw(){
  4. //判断总中奖上限,返回true则能中奖
  5. ...
  6. };
  7. }

//判断每天中奖上限
CheckAllLimit implement CheckDrawService{

  1. boolean checkDraw(){
  2. //判断每天中奖上限,返回true则能中奖
  3. ...
  4. };

}

  1. //判断每个奖品每天中奖上限
  2. CheckAllLimit implement CheckDrawService{
  3. boolean checkDraw(){
  4. //判断每个奖品每天中奖上限,返回true则能中奖
  5. ...
  6. };
  7. }

然后抽奖服务里面的逻辑如下

  1. //1、抽奖
  2. int awardid = 随机抽奖获取奖品;
  3. //2、获取检查中奖规则列表
  4. List<CheckDrawService> rules = 从数据库或者缓存中获取中奖规则列表;
  5. int can = 1;
  6. //3、循环规则
  7. for(CheckDrawService rule:rules){
  8. //规则校验
  9. if(!rule.checkDraw()){
  10. //不能中奖
  11. can = 0;
  12. break;
  13. }
  14. }
  15. if(can==1){
  16. //用户中奖
  17. }else{
  18. //用户不能中奖
  19. }

后续不管业务怎么扩展中奖规则,都只需要加个业务实现类实现CheckDrawService接口的checkDraw方法即可。

注:这里不考虑抽奖具体怎么抽奖,可以用消息队列,也可以直接redis控制!

待续…

 57

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2