lane 发布的文章

先上图


无标题.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中是不行的。

golang随机数有一个很好玩的地方,如果我们不自行定义随机数种子的话,每次的随机数都是一样的。


比如我们for循环,取8个随机数,每次运行结果是一模一样的。代码如下:


package main


import (

    "fmt"

    "math/rand"

)


var c chan int


func product(){

    r := rand.Intn(10)

    fmt.Println("随机数: ", r)

    c <- r

}


func main(){

    c = make(chan int, 8)

    for i:= 0; i<8; i++{

        go product()

    }

    total := 0

    for i:= 0; i<8; i++{

        total += <- c

    }

    fmt.Println("总和: ", total)

}


结果如下:

➜  src go run index.go

随机数:  9

随机数:  7

随机数:  7

随机数:  1

随机数:  8

随机数:  5

随机数:  1

随机数:  0

总和:  38

➜  src go run index.go

随机数:  7

随机数:  8

随机数:  5

随机数:  1

随机数:  0

随机数:  1

随机数:  9

随机数:  7

总和:  38

➜  src go run index.go

随机数:  1

随机数:  8

随机数:  1

随机数:  5

随机数:  7

随机数:  0

随机数:  7

随机数:  9

总和:  38

➜  src go run index.go

随机数:  7

随机数:  1

随机数:  9

随机数:  1

随机数:  8

随机数:  5

随机数:  0

随机数:  7

总和:  38

➜  src go run index.go

随机数:  7

随机数:  9

随机数:  1

随机数:  8

随机数:  0

随机数:  5

随机数:  1

随机数:  7

总和:  38

➜  src go run index.go

随机数:  7

随机数:  1

随机数:  1

随机数:  9

随机数:  8

随机数:  5

随机数:  0

随机数:  7

总和:  38


可以看出,每次随机数都是0、1、1、5、7、7、8、9,总和一直都是38。因为使用了go关键字,所以顺序不同,但是如果去掉go关键字,改为单去程的话,那真就是顺序和值都完全一模一样了。


为什么?


我们打开golang的源码,可以看到:


rand.Int()实际是func Int() int { return globalRand.Int() }

而globalRand是var globalRand = New(&lockedSource{src: NewSource(1)})

NewSource又是

func NewSource(seed int64) Source {

    var rng rngSource rng.Seed(seed) 

    return &rng 

}


也就是说,每次在默认随机的时候,golang是固定了以数字1作为种子,进行随机。那种子都固定了的话,每次执行的时候结果当然是一样的了。


而要解决这个问题,就需要以时间作为随机数种子。代码如下:


package main


import (

    "fmt"

    "math/rand"

    "time"

)


var c chan int


func product(){

    rand.Seed(int64(time.Now().Nanosecond()))

    r := rand.Intn(10)

    fmt.Println("随机数: ", r)

    c <- r

}


func main(){

    c = make(chan int, 8)

    for i:= 0; i<8; i++{

        go product()

    }

    total := 0

    for i:= 0; i<8; i++{

        total += <- c

    }

    fmt.Println("总和: ", total)

}


这时再运行,就会发现比较“随机”了。


➜  src go run index.go

随机数:  8

随机数:  1

随机数:  3

随机数:  9

随机数:  6

随机数:  8

随机数:  5

随机数:  7

总和:  47

➜  src go run index.go

随机数:  0

随机数:  5

随机数:  1

随机数:  3

随机数:  2

随机数:  3

随机数:  5

随机数:  1

总和:  20

➜  src go run index.go

随机数:  2

随机数:  9

随机数:  8

随机数:  2

随机数:  5

随机数:  1

随机数:  9

随机数:  0

总和:  36

➜  src go run index.go

随机数:  4

随机数:  1

随机数:  0

随机数:  4

随机数:  6

随机数:  2

随机数:  8

随机数:  8

总和:  33

➜  src go run index.go

随机数:  0

随机数:  3

随机数:  5

随机数:  1

随机数:  1

随机数:  6

随机数:  5

随机数:  6

总和:  27


而PHP作为最好的语言,这点还是比较人性化的,PHP默认就是以时间作为种子的。在文档中有这么一句话:

Note: 自 PHP 4.2.0 起,不再需要用 srand() 或 mt_srand() 给随机数发生器播种 ,因为现在是由系统自动完成的。

一、Web缓存是什么

 

Web缓存,顾名思义,是一种缓存服务,当用户第一次访问一个URL的时候,Web缓存服务器会发送请求给后端服务器,获取数据,然后Web缓存服务器将数据保存起来,再将数据返回给客户端,由浏览器进行输出显示内容。当用户第二次访问同一个URL的时候,Web缓存服务器就会直接将之前保存的内容返回给客户端。如此一来,减少了后端服务器和数据库服务器的负载,也降低了网络时延。Web缓存服务器,就是是位于服务端和客户端之间的中间层。其中最为出名的Web缓存服务器就是Squid

Web缓存服务器应用广泛,DNS系统、网站服务等都或多或少的使用者Web缓存服务器。如今,Nginx也开始支持Web缓存服务。

 

 

二、NginxWeb缓存服务

 

       Nginx0.7.48版本开始,也提供了Web缓存服务。我们先来介绍一下Nginx如果开启了Web缓存服务后的处理流程:

       1)、用户访问一个URL

       2)、后端服务器接受请求,由Web Service软件Nginx开始处理。

       3)、Nginx根据请求交由PHP/Python/Java等动态语言处理。

       4)、Nginx获得动态语言的处理结果;

       5)、Nginx对结果进行缓存;

       6)、将结果返回给客户端,由客户端的浏览器输出显示内容。

       了解了流程,我们再来看看第五步,Nginx是如何缓存的:

       1)、Nginx将请求访问的URL进行MD5哈希后,将这个值作为key

       2)、根据1)中得到的key,在缓存目录中生成缓存文件,并且将后端服务器的PHP等动态语言处理结果保存在缓存文件中。

       NginxWeb缓存服务器主要由proxy_cache指令集和fastcgi指令集实现。其中proxy_cache指令集用来实现反向代理的同时,也对后端返回的内容进行缓存。而fastcgi指令集用来实现对FastCGI程序缓存。

 

 

三、设置Nginxproxy_cache系列缓存

 

       1、安装ngx_cache_purge模块

proxy_cachengxin的模块ngx_cache_purge。在使用之前,需要重新编译安装nginx,并将ngx_cache_purge模块一同编入。

1)、下载ngx_cache_purge

       wget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gz

       tar –zxvf ngx_cache_purge-2.3.tar.gz

2)、下载nginx

       wget http://nginx.org/download/nginx-1.9.11.tar.gz

       tar –zxvf nginx-1.9.11.tar.gz

       cd nginx-1.9.11

       ./configure –add-module=../ngx_cache_purge-2.3

       make

       sudo make install

 

       2proxy_cache指令:

用来设置哪个缓存区将被使用,并定义缓存区的名称。

格式:proxy_cache name;

示例:proxy_cache blog_article_cache

      

3proxy_cache_path指令:

              用来设置缓存文件的路径

格式:proxy_cache_path path levels keys_zone inactive max_size;

       示例:proxy_cache_path /var/www/nginx/proxy/cache levels=1:2 keys_zone=blog_article_cache:100m inactive=1d max_size=30g

示例的意思是,缓存目录有两层,是levels指定的,其中第一层是第一个字母,第二层是第二个字母。这个缓存区的名字叫做blog_article_cache,缓存区空间为100M(内存),过期时间为1天以后,缓存空间为30G(硬盘)

 

4proxy_cache_valid指令:

用来设置对不同HTTP状态码的不同缓存时间。如果不指定状态码,则200301302才有效果。要对所有状态码设置一个统一时间的话,使用any代替状态码。

       格式:proxy_cache_valid 200 301 302 5m;

 

5proxy_cache_min_uses指令:

       用来设置缓存最小使用次数,默认为1

       格式:proxy_cache_min_uses 1;

 

6proxy_cache_key指令

              设置Web缓存的key,真实的存储key是根据我们设置的key然后求md5。通常我们设置为域名+URI+参数。

              格式:proxy_cache_key value

 

7proxy_cache_methods指令:

用来设置HTTP哪些方法会被缓存,默认为GETHEAD。可以设置POSTPUTDELETE等。

       格式:proxy_cache_methods methods;

示例:proxy_cache_methods GET POST PUT DELETE HEAD;

 

8、最佳实践:

首先,我们先设置两个缓存目录,用来存放缓存文件。目录的路径赋值给proxy_temp_pathproxy_cache_path

mkdir /var/www/nginx/proxy/temp

       mkdir /var/www/nginx/proxy/cache

       

       我们直接在Nginx的主配置文件中进行演示,conf/nginx.conf。演示如下:

user www www;

worker_processes 4;

error_log /usr/local/nginx/logs/nginx_error.log crit;

pid  /usr/local/nginx/logs/nginx.pid;

worker_rlimit_nofile 51200;

events

{

    use epoll;

    worker_connections 51200;

}

http

{

    include mime.types;

    default_type application/octet-stream;

    charset UTF-8;

 

    server_names_hash_bucket_size 128;

    client_header_buffer_size 32k;

    large_client_header_buffers 4 32k;

 

    sendfile on;

    tcp_nopush on;

    keepalive_timeout 60;

    tcp_nodelay on;

 

    #proxy_temp_pathproxy_cache_path所指定的路径必须是在同一个磁盘分区

    proxy_temp_path /var/www/nginx/proxy/temp

    #设置缓存路径,缓存区名,缓存区内存空间大小,缓存区数据过期时间,缓存区硬盘空间大小

    proxy_cache_path /var/www/nginx/proxy/cache levels=1:2 keys_zone=blog_article_cache:500m inactive=1d max_size=30g

 

    #设置反向代理的集群IP,权重,错误重试次数,错误超时时间

    upstream www_server_pool

    {

        server 192.168.1.100:80 weight=1 max_fails=3 fail_timeout=30s

        server 192.168.1.101:80 weight=1 max_fails=3 fail_timeout=30s

        server 192.168.1.102:80 weight=2 max_fails=3 fail_timeout=30s

    }

 

    server

    {

        listen 80;

        server_name www.lanecn.com;

               root /var/www/blog;

        access_log /usr/local/nginx/logs/www.lanecn.com_access.log;

        location /

        {

            proxy_set_header Host $host;

            proxy_set_header X-Real-IP $remote_addr;

            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_pass http://www_server_pool;

        }

 

        #对常见的图片和js,css进行设置

        location ~ .*\.(gif|png|jpg|jpeg|bmp|js|css)$

        {

            #使用设置的缓存区blog_article_cache,blog_article_cache是缓存区名称,proxy_cache_path设置

            proxy_cache blog_article_cache;

            #设置不同的HTTP状态码享有不同的缓存时间

            proxy_cache_valid 200 301 302 1m;

            proxy_cache_valid 404 1d;

            #proxy_cache_valid any 1h;

 

            #设置Web缓存的key,真是的存储key是根据我们设置的key然后求md5.我们设置为域名+URI+参数

            proxy_cache_key $host$url$is_args$args;

 

            #反向代理

            proxy_set_header Host $host;

            proxy_set_header X-Real-IP $remote_addr;

            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_pass http://www_server_pool;

        }

 

        #清除缓存

        #访问www.lanecn.com/purge_nginx_cache/logo.gif即可清除www.lanecn.com/logo.gifNginx的缓存

        location ~ /purge_nginx_cache(/.*)

        {

            #设置IP白名单.清除缓存只能由指定IP进行,防止恶意清除,增加服务器负载.

            allow 127.0.0.1;

            allow 192.168.1.1/254;

            proxy_cache_purge blog_article_cache $host$1$is_args$args;

        }

    }

}

 

 

四、设置Nginxfastcgi_cache系列缓存

 

1fastcgi_cache指令:

用来设置哪个缓存区将被使用,并定义缓存区的名称。

格式:proxy_cache name;

示例:proxy_cache blog_article_cache

      

2fastcgi_cache_path指令:

              用来设置缓存文件的路径

格式:fastcgi_cache_path path levels keys_zone inactive max_size;

       示例:fastcgi_cache_path /var/www/nginx/fastcgi/cache levels=1:2 keys_zone=blog_article_cache:100m inactive=1d max_size=30g

示例的意思是,缓存目录有两层,是levels指定的,其中第一层是第一个字母,第二层是第二个字母。这个缓存区的名字叫做blog_article_cache,缓存区空间为100M(内存),过期时间为1天以后,缓存空间为30G(硬盘)

 

3fasgcgi_cache_valid指令:

用来设置对不同HTTP状态码的不同缓存时间。如果不指定状态码,则200301302才有效果。要对所有状态码设置一个统一时间的话,使用any代替状态码。

       格式:fasgcgi_cache_valid 200 301 302 5m;

 

4fasgcgi_cache_min_uses指令:

       用来设置缓存最小使用次数,默认为1

       格式:fasgcgi_cache_min_uses 1;

 

5fasgcgi_cache_methods指令:

用来设置HTTP哪些方法会被缓存,默认为GETHEAD。可以设置POSTPUTDELETE等。

       格式:fasgcgi_cache_methods methods;

示例:fasgcgi_cache_methods GET POST PUT DELETE HEAD;

 

       6fastcgi_cache_key指令

              设置Web缓存的key,真实的存储key是根据我们设置的key然后求md5。通常我们设置为域名+URI+参数。

              格式:fastcgi_cache_key value

 

       7、最佳实践

首先,我们先设置两个缓存目录,用来存放缓存文件。目录的路径赋值给fastcgi_temp_pathfastcgi_cache_path

mkdir /var/www/nginx/fastcgi/temp

       mkdir /var/www/nginx/fastcgi/cache

       

       我们直接在Nginx的主配置文件中进行演示,conf/nginx.conf。演示如下:

user www www;

worker_processes 4;

error_log /usr/local/nginx/logs/nginx_error.log crit;

pid  /usr/local/nginx/logs/nginx.pid;

worker_rlimit_nofile 51200;

events

{

    use epoll;

    worker_connections 51200;

}

http

{

    include mime.types;

    default_type application/octet-stream;

    charset UTF-8;

 

    server_names_hash_bucket_size 128;

    client_header_buffer_size 32k;

    large_client_header_buffers 4 32k;

 

    sendfile on;

    tcp_nopush on;

    keepalive_timeout 60;

    tcp_nodelay on;

 

    #fastcgi_temp_pathfastcgi_cache_path所指定的路径必须是在同一个磁盘分区

    fastcgi_temp_path /var/www/nginx/fastcgi/temp

    #设置缓存路径,缓存区名,缓存区内存空间大小,缓存区数据过期时间,缓存区硬盘空间大小

    fastcgi_cache_path /var/www/nginx/fastcgi/cache levels=1:2 keys_zone=blog_article_cache:500m inactive=1d max_size=30g

 

    #设置反向代理的集群IP,权重,错误重试次数,错误超时时间

    upstream www_server_pool

    {

        server 192.168.1.100:80 weight=1 max_fails=3 fail_timeout=30s

        server 192.168.1.101:80 weight=1 max_fails=3 fail_timeout=30s

        server 192.168.1.102:80 weight=2 max_fails=3 fail_timeout=30s

    }

 

    server

    {

        listen 80;

        server_name www.lanecn.com;

        root /var/www/blog;

        access_log /usr/local/nginx/logs/www.lanecn.com_access.log;

        location ~ .*\.(php|php5)$

        {

            #使用设置的缓存区blog_article_cache,blog_article_cache是缓存区名称,fastcgi_cache_path设置

            fastcgi_cache blog_article_cache;

            #设置不同的HTTP状态码享有不同的缓存时间

            fastcgi_cache_valid 200 301 302 1m;

            fastcgi_cache_valid 404 1d;

            #fastcgi_cache_valid any 1h;

 

            #设置Web缓存的key,真是的存储key是根据我们设置的key然后求md5.我们设置为域名+URI+参数

            fastcgi_cache_key $host$url$is_args$args;

 

            #FastCGI

            fastcgi_pass http://www_server_pool;

            fastcgi_index index.php;

            include fastcgi.conf

        }

    }

}