一、数据库集群:读写分离

二、数据库集群:分库分表

三、NoSQL = not only sql

四、缓存

    1、用户请求发现缓存不存在则自行查库并生成缓存。并发下可能会同时很多请求同时生成,造成雪崩。

    2、用户请求发现缓存不存在,通过队列后台生产缓存,并发下,消费者应判断缓存是否已存在,不存在时在生成。

    3、热点数据后台脚本生成,在内存紧张时新生成一个就会淘汰一个,脚本应不断检测若缓存不存在则重新生成。

    4、全网热点数据,如明星官宣,这时应生成多份缓存,以集群来分散单机压力。

五、单机高性能:PPC和TPC

    1、PPC:每来一个链接就创建一个进程。优化版是启动时预创建多进程。

    2、TPC:同PPC,进程换线程。

六、单机高性能:Reactor和Proactor

    1、Reactor:就是IO多路复用,也叫Dispatcher,同步非阻塞模型。如epoll、kqueue等。单reactor单进程有redis,多reactor单进程没任何优势,多reactor多进程有nginx和memcache。通俗讲:来了事件操作系统通知应用程序,应用程序来处理。

    2、Proactor:异步非阻塞。Reactor需要用户进程同步做read write等IO操作,如果改为异步可以提高性能,这就是Proactor。通俗讲:来了事件操作系统来处理,处理完通知应用程序。虽然性能更好,但是Linux下的AIO并不完善,真正实现了异步非阻塞的操作系统是windows的IOCP。

    3、IO操作分两个阶段

        1、等待数据准备好(读到内核缓存)

        2、将数据从内核读到用户空间(进程空间)

        一般来说1花费的时间远远大于2。

        1上阻塞2上也阻塞的是同步阻塞IO

        1上非阻塞2阻塞的是同步非阻塞IO,这就是Reactor就是这种模型 

        1非阻塞2上非阻塞是异步非阻塞IO,这讲说的Proactor模型就是这种模型

七、复杂均衡的分类和架构

    1、最上层DNS:针对地域。就近访问。缺点更新缓存慢,不可定制化。

    2、硬件层F5:性能最好,价格昂贵,便宜的也要一台马六。qps两百万到八百万。

    3、软件层LVS:性能始终,qps八十万。在三层网络  

    4、软件层nginx:性能最差,qps五万左右。

八、负载均衡的算法

    1、轮询:简单,但由于简单,也是缺点。宕机也无法知道。

    2、加权轮训:根据机器性能等权重不同,其他缺点同轮询。

    3、最小负载:LVS四层可以根据最小连接数,Nginx七层可以根据最少HTTP请求数(默认不支持)。 自研的话可以根据CPU IO等来判断,但是复杂度太高,是1、5、15分钟的负载?太短波动太频繁,太长峰值来临时感知太晚。

    4、性能最优:同最小负载,都是要感知服务器状态,复杂都高。如果收集量太大影响性能,如果采样,又需要合理的采样率,否则不准确。

    5、Hash:根据id hash、根据ip hash。如银行业务,请求都要落在同一台机器上。


一、概述

1、架构是顶层设计;框架是面向编程或配置的半成品;组件是从技术维度上的复用;模块是从业务维度上职责的划分;系统是相互协同可运行的实体。

2、架构是为了应对软件系统复杂度而提出的一个解决方案。个人感悟是:架构即(重要)决策,是在一个有约束的盒子里去求解或接近最合适的解。这个有约束的盒子是团队经验、成本、资源、进度、业务所处阶段等所编织、掺杂在一起的综合体(人,财,物,时间,事情等)。架构无优劣,但是存在恰当的架构用在合适的软件系统中,而这些就是决策的结果。

需求驱动架构。在分析设计阶段,需要考虑一定的人力与时间去"跳出代码,总揽全局",为业务和IT技术之间搭建一座"桥梁"。

架构设计处于软件研制的前期,一方面,越是前期,如有问题,就能够越早发现,修改的代价也就越低;另外一方面,也意味着,软件实施后期若有架构上的修改,也需要付出更多的代价。

    1)架构是为了应对软件系统复杂度而提出的一个解决方案。

    2)架构即(重要)决策

    3)需求驱动架构,架起分析与设计实现的桥梁

    4)架构与开发成本的关系


二、架构复杂度来源

3、系统复杂度来源:高性能。从单机到集群的服复杂度来源:

    1)任务分配:F5、交换机、LVS、Nginx、HAProxy等

    2)任务分解:拆成服务。业务本身不变的情况下性能有上限,系统拆封可以逼近这个极限,但不能达到。过多的服务调用会降低性能。

4、系统复杂度来源:高可用。复杂度通常高于高性能,因为涉及到知识点更多。

    1)计算高可用:冗余。

    2)存储高可用:难点不是如何备份,而是如何减少数据不一致带来的影响。分布式领域里有CAP理论,即一致性,可用性,分区容错性只能取其二。

    3)状态决策:

        (1)独裁式,仍有单点问题;

        (2)协商式,两台备,然后自行决策选出主,在主备连接中断后变成两主,不符合预期;

        (3)民主式,如zookeeper,节点自行投票,为解决脑裂问题,需要投票过半数。但在1、2、3节点故障后,4、5仍可用,但投票不过半数,无法选主,系统不可用。

5、系统复杂来源:可扩展性。应对变化的适应能力。不能不做预测,预测也可能出错,也不可以过度预测。封装变化,隔离可变性。

6、系统复杂度来源:成本。高性能高可用意味着更多的机器,当规模达到一定程度后,成本却就是考虑因素之一了。低成本的架构方案意味着引入新技术或开发新技术。开发新技术复杂度更高,如HHVM,kafka等。

7、系统复杂度来源:安全。分为功能安全如windows漏洞SQL注入等,和 架构安全如防火墙ACL等。防火墙性能低,目前没有很好的办法,更多是靠运营商清洗。

8、系统复杂度来源:规模。分为功能规模和数据规模。数据规模是数据量越来越大发生质变,比如mysql,超过五千万行后要开始分库分表。


 三、架构设计原则

1、三大原则:合适 > 演化 > 简单 这个优先级。

    1)合适原则:合适优于业界领先。没那么多人、没那么多积累、没那么卓越的业务场景。

    2)简单原则:简单优于复杂。结构的复杂性、逻辑的复杂性。

    3)演化原则:演化优于一步到位。首先要满足当前的业务需求,不贪大贪全,照搬大厂架构。要遵循演化原则。

先上图


无标题.jpg


明显可以看到,抖动已经完全消除。这个接口每天有2000万次请求,粗算QPS = 20000000次 / 40000秒 = 500。


一、feed推荐接口中包含哪些内容

根据唱吧的业务需求,feed推荐接口主要包含三部分内容:

1、你关注的好友中正在唱歌的。

2、你关注的好友中正在直播间直播。

3、你关注的好友中正在火星直播中表演的。

4、如果以上为空的话,会推荐热门的火星主播,一月推荐一次。


二、feed推荐接口都做哪些事?

在优化之前,这块纯粹是流程化的开发方式。先获取你关注了哪些好友,然后循环这些好友,依次从唱歌/直播间/火星直播三个库中获取你的关注是否正在表演。最后,如果取到了值,就获取正在收看他表演的观众数等维度来打分排序,取得分最高的好友,获取他的用户信息,把用户信息和正在做的事情返回给客户端。


三、思考。

我们来思考一下,这样的流程中,怎样来优化,哪里有优化的空间?

1、依次去唱歌/直播间/火星直播中取数据,起码需要三次网络请求,这块是不是融合成一个大列表,而不是维护着三个列表。大列表在晚高峰也不超过1万人,所以其实还好。

2、循环好友列表,每次循环都是要做上面的第一点。

3、如果我和你都关注了同一个网红主播,你feed推荐接口的请求和我feed推荐接口的请求进来的时候,我们都要查询的他的观众人数和计算得分,这是重复的操作。

4、最后返回之前都要查一下用户信息,用户修改昵称的频率并不会很高,这块也可以优化掉。

5、部分操作可以转入后台,比如crontab,从而减轻前台的压力。



四、操作。

1、后台起一个cron,10秒一次,从三个来源中依次取出正在表演的列表。

2、分别循环三个列表中的所有表演者,计算得分,获取正在唱什么歌,获取用户头像昵称等信息。

3、三个列表融合成一个大列表,并用type字段来标志是正在唱歌/直播间/火星直播,并根据他们当前的操作来拼接文案。

4、这个大列表写入memcache。

5、请求进来的时候,从memcache中获取这个大列表,把这个列表存入本地的opcache中,这样还避免了每次都有一个memcache的网络请求。

6、获取我的好友列表,选出我的好友中正在表演的人。


五、收获

缕清楚需求和现有代码,找到痛点,开动脑筋。

      一个很常见的业务场景,我们有很多的MP4视频文件,在列表页中,是需要展示一个图片作为封面。在此之前,我们的封面图片都是jpg。然鹅,某天产品汪心血来潮,想要把gif动画来作为封面,那么问题就来了,如何把MP4的几秒钟内容提出来生成一个gif动画呢?比如每个MP4视频文件的第10秒到15秒这5秒钟的内容转成gif呢?


      大的思路上,MP4是一个视频,视频是由帧组成的,一帧是一张图片,比如一秒播放60张,连起来就是个视频。


      先上最关键的命令,ffmpeg提供了将MP4转gif的操作:

ffmpeg -ss 25 -t 5 -i shipin.mp4 -r 5 -s 150x150 -y -f gif shipin.gif


-ss 25 -t 5:从视频25开始,一共5秒。

-i shipin.mp4:输入的视频

-r 5: 一秒取5帧。

-s 150x150:生成的图片是150*150尺寸。

-y 同名覆盖

-f gif:输出gif格式。


方法一:

MP4是由客户端APP直接上传到阿里云的OSS存储服务中,那么阿里云是否由接口,这样最简单方便可以打发走PM。调研发现,阿里云提供了MP4转GIF服务,实测发现,这个服务不能只转视频的一部分,它会把整个视频文件都给转了,比如38M的MP4文件会转成一个158M的GIF。这种方式PASS掉。


方法二:

在方法一的基础上,咨询阿里云的客服,给出的方案是,阿里云提供MP4指定秒数的一帧提出来生成一张jpg,然后多张jpg生成一张GIF。这样的话,阿里云生成的多个jpg要自己下载下来用生成一个gif。


方法三:

使用ffmpeg自己把MP4转gif。

ffmpeg提供了一个功能,可以把MP4中,指定秒数开始 + 一共多少秒 + 一秒取多少帧 + 图片多少*多少。


最佳时间:


以服务的形式,对外提供MP4生成GIF的服务。生成完成后,再以http回调的方式告诉上层应用,我这边生成好了。


具体步骤:


1、上层应用会在半夜开始跑脚本,根据各项指标,计算出几万个上首页的视频。

2、上层应用调我的service代码,传video_id给我。

3、我拿video_id查库得出视频在阿里云的http地址。

4、计算token,并将video_id + token存在Memcache里,过期时间7200秒。

5、以http的方式调MP4转GIF的服务。将video_id + token + 视频url + 回调函数的http地址。

6、在服务层,收到了请求,校验参数和token的合法性,执行一个ffmpeg命令

Ps:第5步和第6步可以用消息队列来解耦,太麻烦了。

7、生成完成后,传到阿里云的CDN上去,再以回调函数的形式,告诉上层应用,我已经做完了。

8、给上层应用service代码中,验证一下回调参数的video_id和token是不是之前存好的。

9、最后就是,上层应用修改数据库里的字段。


问题:不用消息队列的话,就面临着每次请求时一个HTTP,请求方会一直等着响应方回话。

解决:这个时候,我们可以在请求方的curl中设置超时时间,比如1秒。在响应方,让程序继续往下走,不要因为连接断开而终止执行。毕竟,请求方不需要等待响应,而是用回调方式的告知结果。


代码很简单,不放了。

Golang的Switch与Select的逻辑,和其他语言不同。一不留神就是个坑。


多个Case中时,第一个case总是被抛弃的,同时也不会进到default里面。


如下的例子:

a := 2

switch a {

case 2:

case 3:

    fmt.Println("第一个case")

case 4, 5:

    fmt.Println("第二个case")

default:

    fmt.Println("Default")

}


当a=2时,没有任何输出。无论第一个打印,还是default都没有。

当a=3时,输出“第一个case”。

当a=4时,输出“第二个case”。

当a=5时,输出“第二个case”。

当a=其他值时,才输出default。


和switch一样,select是相同的逻辑和分支走向。


c1 := make(chan int, 10)

c2 := make(chan int, 10)

c1<-1

select{

case <-c1:

case <-c2:

    fmt.Println("进来了")

default:

    fmt.Println("default")

}

此时虽然c1有值,但是c1在select中是被抛弃的,所以无任何输出。

把c1<-1改成c2<-1,输出“进来了”。

当c1和c2都没有值,才会进入到default中。

另外,switch可以case1,2这样逗号分隔,同一行的case中写多个值,这种写法在select中是不行的。