摸完鱼就放国庆,祝大家国庆快乐

2019-09-30 12:15:27 +08:00
 timeromantic
摸鱼鱼塘 2.0 版本发布

https://printf520.com/hot.html

欢迎体验新版

更新说明
https://printf520.com/single.html?id=69


之前一直打算优化热榜的历史架构问题,在讲优化点前,先看一看热榜遗留的历史问题:

热榜数据由一整个 json 串组成,造成的问题就是后期无法对热榜的历史数据查看,和每一条数据后期进行条件性筛选。
高强度的零散数据写入和读取,关系型数据库在表结构的扩展和数据库的写入造成问题
一次性获取几百条热榜数据,api 接口需要传输接近 20kb 的数据量,在用户高峰时期造成了热榜的卡顿
解决问题一:

对于造成上面三点的历史原因分析在于在初期创立热榜这个页面,基于快捷开发,产品先上,用户体验优化后期跟进的原则。所以遗留了这些问题,目前正是后期的优化时间点。先看第一个点,前期为了快速开发,数据的存储放在 mysql,并且由表的一个字段存储。表的结构如下图 1.0:



1.0 表结构图

可以看到在之前的设计里面每次 api 返回的数据其实就是一个 str 字段,当然这样的设计对于数据的读取和写入都是十分方便的,但是如果后期想要对热榜单独的每一条数据进行分类,这个 json 数据就显得十分的不方便了,加之综合第二点关系型数据库并不适合这样零散的数据,所以为了解决这一点,我选择了 nosql 领域比较成熟的一款数据库软件——mangodb。

解决问题二:

其实若不是第二点的原因,想要单独的存储每个数据不一致的热榜信息,mysql 也是可以胜任的,不过主要是因为不同的网站热榜所带的参数不一致,所以这个时候 mangodb 的面向文档存储就很适用了。对于文档型存储,其数据是用二进制的 Json 格式 Bson 存储的。数据就像 Ruby 的 hashes,或者 Python 的字典,或者 PHP 的数组,适用这样的数据类型完全可以满足每个热榜的数据差异性,并且 mangodb 没有固定的表结构,不用为了修改表结构而进行数据迁移,比如如果业务需要在原有的表增加一个字段用于记录每行数据的点击量,如果使用 mysql 我们需要修改整个表结构,然后再更新整个字段,但是对于 mangodb 来说,基于文档的存储可以不用修改表结构,直接把带有用户点击量的字段插入到表里即可。

解决问题三:

之前有不少的用户反馈一次性获取 200 条热榜数据,会响应一段时间,接到这个问题后,发现其中一个原因就是因为 api 每次需要返回的数据量过大,在不增加服务器带宽和使用加速 CDN 的情况下,我使用的解决方式是开启 API 的 gzip 模式,开启 gzip 后 api 返回的不再是原滋原味的原生数据,而是通过服务器端压缩后传输的 gzip 压缩数据,带来最直接的效果就是数据被压缩了不少,对比图如下:



3.0 压缩后的 api 数据大小



3.1 压缩前的 api 数据大小

从 2 张图的对比可以看到,开启 gzip 后的 api 返回数据明显比未开启前小了不少,数据越小传输速度越快,当然压缩带来的成本是 API 在每次返回数据前 CPU 会执行压缩算法,不过在当前带宽不足,而 CPU 足余的情况下可以忽略不计,并且压缩后的数据可以直接存放在 redis 的缓存里面,避免二次压缩。值得一提的是 Golang 语言并不需要使用 Nginx 和 tomcat 的服务器软件,所以开启 Golang 的 http gzip 需要由自己实现,下面贴出 Golang 开启 Gzip 的关键代码段:

package main

import (
"compress/gzip"
"io"
"net/http"
"strings"
)

type gzipResponseWriter struct {
io.Writer
http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}

func makeGzipHandler(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
fn(w, r)
return
}
w.Header().Set("Content-Encoding", "gzip")
gz := gzip.NewWriter(w)
defer gz.Close()
gzr := gzipResponseWriter{Writer: gz, ResponseWriter: w}
fn(gzr, r)
}
}

func handler(w http.ResponseWriter, r *http.Request) {

w.Header().Set("Content-Type", "text/plain")
jsonStr := `{"Code":0,"Message":"获取数据成功","Data":"热榜数据","CreateTime":1569673821}]}`
w.Write([]byte(jsonStr))
}

func main() {
http.HandleFunc("/a", makeGzipHandler(handler)) // 设置访问的路由
http.ListenAndServe(":1113", nil)
}
3505 次点击
所在节点    程序员
32 条回复
xuromky
2019-09-30 14:01:06 +08:00
还有三个小时就下班的我已无心工作
charleyking
2019-09-30 14:03:38 +08:00
终于更新了,又可以快乐的摸鱼了
timeromantic
2019-09-30 14:13:19 +08:00
@charleyking 哈哈,更新了一些遗留问题。欢迎常来
timeromantic
2019-09-30 14:13:41 +08:00
@xuromky 同,无心工作
Understarry
2019-09-30 14:20:07 +08:00
无心工作+1
fyxtc
2019-09-30 14:34:24 +08:00
赞,收藏了
jy02201949
2019-09-30 14:37:17 +08:00
我洗好手,剥好橘子就等着下班了现在,谁也不别想让我今天下午工作!!!
kkshell
2019-09-30 14:40:21 +08:00
无心工作
timeromantic
2019-09-30 14:43:47 +08:00
@jy02201949 下午人体宕机
jy02201949
2019-09-30 14:46:46 +08:00
@timeromantic #9 老铁你这网站我打不开
timeromantic
2019-09-30 14:56:11 +08:00
@jy02201949 建议用 chrome 打开
sunziren
2019-09-30 15:00:36 +08:00
你这网站,真的太卡了。
unco020511
2019-09-30 15:04:42 +08:00
这加载有点太慢了啊老哥
timeromantic
2019-09-30 15:08:59 +08:00
@sunziren 今天下午人流量太大了。看来要加服务器了
@unco020511
hikarumx
2019-09-30 15:18:05 +08:00
非常好,简单清爽
G2838
2019-09-30 15:22:20 +08:00
这加载有点太慢了啊老哥
SSW
2019-09-30 15:22:49 +08:00
你在今天放出来,这摸鱼的人也太多了,鱼塘都炸了
doveyoung
2019-09-30 15:23:38 +08:00
我昨天还在摸,今天就打不开了烙铁,是带宽不够了吗
MX123
2019-09-30 15:31:53 +08:00
为什么不用电报群?
timeromantic
2019-09-30 15:32:27 +08:00
鱼塘炸鱼了,人太多。哈哈,该升级服务器了
@SSW
@doveyoung
@G2838

多刷新还是可以打开

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/605629

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX