除了拼搏和努力,成功也是有方法的。写这个话题,用以阐述下自己学习新技术的一套形而上学的东西,希望能给大家带来帮助。当然,每个人都有自己的方法论,适合自己的即是最好的。
PHP 7下安装Swoole和Yar,Yaf
周末闲来无事,玩玩swoole。笔者开发坏境:CentOS 7,PHP 7.0.16
用户系统设计与实现
用户系统,主要分为账号体系和用户信息两大类。账号体系包括,登陆验证、注册、第三方授权、以及权限管理。用户信息包括,用户地理位置、用户属性、用户设备信息、还有用户日志信息。本文会介绍用户模块的具体落地方案。
Feed设计与实现
Feed,在社交和信息推荐的App与网站中,基本都会用到的。例如常用的新浪微博,用户登录进入后,展现给我们的就是feed信息流。新浪微博的信息,来自于你关注人所发布的内容。还有微信的朋友圈,今日头条的信息流,好友发布的美拍等,这些都是Feed。玩过知乎的人应该知道,在知乎Feed中,会显示某某关注了某某话题,某某点赞或者赞同了某个回答。广义来讲,这些也算是一种Feed。
本文会先介绍几种不同的Feed设计,让大家对Feed实现有初步的了解。其次会对我们采用的Feed方案作出详细的解答。
App首屏接口性能优化
目前所在项目组开发的是一款母婴产品,集工具和社区属性。截止本文发布,注册用户接近7000万,首屏接口日访问量过百万。在首屏中,会给用户展现不同的数据,比如每日任务,宝宝(婴儿)每日概述,胎教音乐,运动视频,热帖等模块。首屏接口性能的好坏,将直接影响到app的使用体验。
我们服务端RPC框架采用RESTful,其底层是curl实现的。curl采用http协议的,另外我们服务端的技术栈是PHP。我们都知道http协议相比较TCP而言,不仅多了http的报头,PHP本身性能也是大问题。在不做大重构的情况下,怎么做最小的修改,完成最大的性能提高。还是很有挑战性的。
针对首屏接口,我们针对其完成了两次性能优化。
分屏加载
将本来属于一个接口的内容,单独在两个请求中返回。第一屏API返回关键的数据,减少用户初次进入的等待时间。第二屏,返回剩余的大部分数据。具体API返回内容的划分,是依赖客户端渲染顺序的。也没绝对标准。但我们的原则是,第一屏API力所能及返回最少的数据,避免用户等待。剩余的留给第二屏的API去完成。分屏加载的难点有以下几个。
- 确定好数据加载顺序,最基础的数据肯定是需要最先返回的,另外也要客户端的同学配合。
- 首屏内容是和用户的怀孕状态紧密联系在一起的,用户切换宝宝(怀孕中切换到宝宝已出生,宝宝A切换到宝宝B)时,API的刷新频率怎么控制。最后为了可操作性,采用了比较粗暴的做法,每次切换均会请求两次接口。
完成分屏加载以后,用户进入首页的等待时间,已经由2-3S减为1S左右。不必像以前客户端拿到全部内容后,才去渲染界面。现在只需要拿到第一屏的接口,即可完成界面的渲染工作。
分屏后第一屏接口耗时
分屏后第二屏接口耗时
xhprof性能分析
通过在alpha坏境和beta坏境部署Xhprof性能分析工具。我们可以看到实际的API在函数级别的性能损耗。这里不详述该工具的部署方式和使用方法。在Xhprof的帮助下,我们得到了以下几个结论。
- RPC采用的是HTTP协议,单纯的RPC调用便接近10MS的耗时。首屏RPC调用次数接近30次。
- 每个RPC服务层内部,通过函数调用即可,也采用RPC的方式。
- 热点数据直接查库,缓存利用率低下。
- 数据表索引乱用,存在慢查询。
结合上面几点,在实际操作过程中,由简到难,逐步完成。第一能减少RPC,便减少RPC请求。逻辑层数据由以前的多次获取改为一次获取。其次,服务层内部,不垮服务层不走RPC,直接以函数调用的方式请求。第三,热点不变动的数据,直接在逻辑层缓存,拿到后丢给API返回。第四,追踪MYSQL慢查询,优化查询SQL。完成后,第一屏性能提升30%~50%。第二屏提升40%~60%。实际结果可看下图
第二次优化第一屏接口耗时
第二次优化第二屏接口耗时
InnoDB存储引擎(一)
InnoDB存储引擎(一)
整体结构
内存池
- 维护线程/进程内部数据结构
- 缓存磁盘文件(cache)
- 对数据的更新或者新增操作,避免直接刷新磁盘
- 重做日志缓冲(redo log)
后台线程
- Master Hhread,将更新的数据(包括新增和被修改的数据)异步刷新到磁盘,维护内存池和磁盘中的数据的一致性。
- IO Thread分为Insert buffer Thread,log thread,read thread,write thread。
- purge thread,回收undo log
- Page cleaner thread,刷新脏页到磁盘(由master thread 独立出来的)
Write Ahead log:是指当事务提交上,先做重做日志,再修改页。满足事务中持久性要求。
Check Point:检查点技术,数据库系统为了缩短数据库发生宕机恢复所需的时间所采取的一种刷新脏页的方法。 检查点技术分为两种,sharp checkpoint和fuzzy checkpoint。sharp checkpoint用在数据库关机时,将所有的脏数据全部刷新到磁盘。fuzzy checkpoint是按照一定的策略定时部分的刷新脏页数据,较为复杂,此处不做介绍。
InnoDB关键特性
- 插入缓冲(性能提升)
- Insert Buffer 当新增数据并且存在非聚焦索引时,会有对非聚焦索引页的离散读。当出现这种情况时,mysql会先判断当前的非聚焦索引页是否存在于缓冲池中,如果存在则直接修改或者新增。如果不存在缓冲池中,则会先放到缓冲池中(类似于脏页),再以一定的频率merge之后,插入到实际的非聚焦索引页。
- Change Buffer 插入缓冲的升级,用在删除或者更新的情况下。在删除时,并不会对非聚焦索引全部删除。在更新是,也不马上更新非聚焦索引。
- 以上两种优化手段,适用于 1. 存在非聚焦索引 2. 非聚焦索引不唯一。
- 两次写(可靠性)
- 对某页写回磁盘的过程中,如果出现宕机,并且导致页破坏(无法使用重做日志)。会出现数据丢失的情况。针对此问题,mysql首先会将所有需要写入的页(2MB)全部刷回到共享表空间中(没有离散写),再将更新的数据写入到各自的表空间中。
- 对某页写回磁盘的过程中,如果出现宕机,并且导致页破坏(无法使用重做日志)。会出现数据丢失的情况。针对此问题,mysql首先会将所有需要写入的页(2MB)全部刷回到共享表空间中(没有离散写),再将更新的数据写入到各自的表空间中。
- 自适应哈希索引(性能提升)
- InnoDB查找耗时主要在于B+树的查找,通过使用哈希这种数据结构,将查询参数(Key)和值(位置)对应来减少查找的耗时。
- 异步IO(性能提升)
- 异步IO在对磁盘进行写入时,是非阻塞式的,能对多个查询或者插入操作进行merge,以减少实际的IO次数。
- 刷新邻接页(性能提升)
- 在刷新脏页时,会检查该页所在所在区的所有页,连同刷新。
About 2016
很久没安静下来写点什么了,太懶了。
Review Plan
学习
去年计划看一些底层的书籍,基本都完成了。书单是
- redis设计与实现 100%
- mysql技术内幕 100%
- 深入理解nginx 11%
- nginx实战开发 100%
- 大型网站技术架构 100%
- Hadoop实战 70%
- 微服务设计 21%
- 激荡三十年 14%
- 利用python进行数据分析 8%
设计模式,架构这块内容,2016年接触的还是蛮少。
工作
5月份从美图离职,来到了喜欢的城市-深圳,也正把户口迁过来。以前,在厦门这座安逸的城市,反而有一些压力。但在深圳,感觉工作按部就班,有种温水煮青蛙之感。
一直说分享有三个层次。第一层次,写博客。第二层次,分享给他人。第三层次,给大众演讲。2016年在技术上做了第一次分享,效果自己还是挺满意的。
经历两次实习和半年的工作,由初入职场的菜鸟,逐渐变成了现在的团队核心。这个也蛮欣慰。在工作以前,一直坚信,技术乃技术人的立身之本。真正工作以后,反而觉得技术只是工作的一部分。沟通能力,团队协调能力,责任心,产品理解能力。这些综合素质,在团队里面更重要。在与其他人沟通的过程中,自己很急躁,说话很快。
其他
一直计划每年去一个地方,今年在厦门待了两个月,去了香港。唯一的遗憾,7天的国庆假期,没有好好利用。另外,国庆期间也发生了一件对我影响很大的事情。
技术上,虽然看了一些书,但与我期望的还有很大的差距。GitHub后半年基本没更新了。博客也久未更新,简直浪费当年的精力。真正的大牛,应该是理论和实践相结合的。
感情上,不知道怎么说。可能自己是很随和的人,感情很被动。真正的爱情,每个个体精神上既相互慰藉也是相互独立的。与女朋友的时间分配上,希望有所改变。
健身,从大学想到了现在。依旧没能真正执行。公司每周三的篮球,也经常缺席。虽然现在工作强度不大,但想想以后,还是要多锻炼。
厨艺上,很是骄傲的,后面这段时间,有空就是自己做饭吃。野外生存,至少不会饿肚子了。
Preview Plan
学习
今年想看些架构和思维方面的书籍,暂时先列出以下这些。想到再加。
- 微服务设计
- 设计模式之禅
- 软技能-代码之外的生存指南
- python-codebook
- 高效程序员的45个习惯
- 激荡三十年
- Docker——容器与容器云
工作
目前在做业务开发,技术上没难点,主要提升下综合能力。沟通时,思考之后再说,慢一点。其次,对产品也要有自己的判断能力,学会反驳。明年,希望能转岗到架构组。
其他
去年的计划完成的挺不错的,虽然计划的内容比较少。今年继续加油!
- 2017年计划去趟国外,任何一个国家都可以,或者去趟西藏或者新疆。
- 技术上,GitHub不做具体的要求。博客,一个月至少两篇。还是要有点强制的好。
- 感情上,做的不好的地方,会改正。精神上还是需要独立一些。
- 学会理财,每月整理自己的账单,坚持一年。
- 健身,每周三固定打球时间,坚持一年。
- 学习外语。空闲时间记单词,坚持用英文查资料。
短视频和技术
在美图有一段时间了。从两方面总结下。下段时间,继续努力!
对短视频的理解
近两年因为4G的普及,手机拍摄技术的改善。传统的媒介方式,由文字到语音,再到图片,再转到了视频。现在看来短视频的火爆,也是很正常的。秒拍,快手,美拍,包括腾讯的微视,这几款平台型的短视频应用都发展的挺不错的。秒拍背后有新浪爸爸。美拍背后依靠超级app美图秀秀。快手,由做gif出身,生的早。根据公开数据显示,美拍目前的平均日活是450万。最近的papi酱,和二更则针对内容型,类似于美拍里面的达人,用于产生内容。
下面着重说下美拍,从产品角度看。美拍很像淘宝,达人雷同于高级旗舰店铺,普通人则类比普通商家。达人,是主要的内容创造者,都想建立自己的品牌。目前的话,美拍在做淘宝之前的事情,免费服务商家和用户。后期要怎么发展了?也可以参考淘宝现在的模式,但是视频内容比较缺乏商品属性。用户不会有太多的消费欲望。这个也可以参考知乎和微信公众号和简书一类的,通过打赏机制一方面鼓励内容生产者-达人,另外平台本身也收取一定分成。所以美拍现阶段的产品重点是挖掘达人,制造优质内容。
从技术角度分析的话,美拍又与新浪微博选型非常像。包含话题,排行榜,评论,点赞,附近的人,个人主页,私信。又是一个社交体系。
对技术的理解
美拍的用户量,已经达到一定量级了,平均日活450万。对系统架构很有挑战性的。因为自己平时主要做的是业务性的,架构方面了解的不是很多。就简单说下几点想法。第一个,项目代码的糟糕程度和接手人的适应速度正相关。第二个,写代码的时候考虑未来性的东西要深一点。第三个,业务模块化,应用分层化。第四个,国内大部分技术都是业务驱动的。第五个,开发也要善于表现自己,对需求的沟通能力很重要。
Redis数据结构(二)
压缩列表
1:压缩列表是为了节省内存而设计的,是一种线性的数据结构。主要用在哈希和列表两种数据类型中。
2:压缩列表包含主要包含五个部分,这五个部分顺序排列组合在一起。
结构如下图所示。
表节点,有三个域组成。previous_entry_length,用来记录前一个节点的长度。encoding,记录下一个域的数据类型和长度。content,保存节点的值。
3:压缩列表有一种极端情况,会导致性能下降——连锁更新。previous_entry_length,记录的是上一节点的长度,当上一节点小于254字节时,该属性使用1字节存储。当大于254字节时,使用5字节。如果之前previous_entry_length都使用1字节,且长度都接近254字节时,会导致连锁更新的情况。
具体使用情况
1:不同数据类型对应的编码对象
2:字符串对应的编码方式。1:整数值实现的字符串对象。2:embstr实现的字符串(类似SDS,用于保存端的字符串)。3:raw,SDS动态字符串
转换规则:
3:列表对象,编码包含两种,ziplist(压缩列表),linkedlist(双向链表)。
转换规则:
1:保存的所有字符串长度都小于64字节。
2:列表对象保存的元素数量小于512个。
当同时满足上面两条规则时,使用列表对象,否则使用链表对象。
4:哈希对象,使用ziplist和hashtable编码
ziplist:
hashtable:
转换规则:
1:哈希对象保存的所有键和值的字符串长度都小于64字节。
2:保存的键值对数量小于512个。
同时满足,使用ziplist,否则使用hashtable.
5:集合对象,使用intset或者hashtable编码。
intset:
hashtable:
转换规则:
1:集合对象保存的元素值都是整数。
2:集合对象保存的元素数量不超过512个。
同时满足上面两个条件,则使用intset,否则hashtable。
6:有序集合,使用ziplist或者skiplist
转换规则:
1:有序集合保存的元素数量小于128个。
2:保存的所有成员长度都小于64字节。
同时满足上面两个条件,则使用ziplist,否则skiplist。
基于数据结构的东西就写这么多吧,当然内容都是《redis设计与实现-黄健宏》上的。我只是个搬运工,这本书写的挺不错的,建议购买。更详细的实现细节,可以看redis源码。
Redis数据结构(一)
SDS简单动态字符串
结构:
1:SDS是在C语言字符串的基础上,构造的一种简单的动态字符串(simple daynamic string)。SDS结构体包含三个变量,len用来记录长度,free用来记录未使用字节的数量,buf存储实际的字符串数组。
2:SDS不以‘\0’为结束符,而是以len的长度标识
优点
1:O(1)获取字符串长度。sdshdr结构体中,直接存储了字符串的长度。因此直接以sdshdr.len读取字符串长度就可以了,相对于C语言的遍历。
2:防止缓冲区溢出。因为sds自动记录了字符串的长度,当需要扩充某个字符串时,会先检测长度是否满足。不满足,会先扩充sdshdr.buf的长度。而C语言,strcat会直接拼接两个string,后面的string可能会占用已经占用的内存区域。
3:减少字符串改变带来的内存重分配次数。简单一点说,当扩充字符串时,sds会给它多分配一些额外的内存。当缩减字符串时,并没有立即将其归回,而是用sdshdr.free记录。
4:二进制安全。直白点,就是不以’\0’为结束符,而是记录其二进制数据,即’\0’对应的00000000。结束是以sdshdr.len来标识的
5:可以重用部分C语言的函数。但因为结束机制不一样,也只是部分而已。
用处
1:redis中常见的字符串值都是用sds实现的。例如,常用的键。
双向链表
结构
1:节点(struct ListNode),包含前节点和后置节点两个指针域。
链表(struct List),包含头,尾节点。以及相关的操作函数。
###优点
1:新增和删除元素非常方便。很大部分来自链表的优点
2:其中的value字段,可以用来存储其他的类型,非常方便扩展
用处
1:redis中List数据类型就是用这个实现的
字典
结构
1:首先说明下,字典也是由其他的数据结构构造的。它包括,哈希节点,哈希表,字典三个部分。哈希表连接哈希节点,字典包含两个哈希表。
2:哈希节点,包含三个域。键,值,下个节点的指针。其中下个节点是为解决哈希冲突的。
3:哈希表,包含四个域。dictht.table,是一个数组,对应的值就是哈希表节点。dictht.size,总大小。sizemask,用来计算索引值。used,记录已经使用多少。
4:字典,包含四个域。其中,ht就是指向上面的哈希表的。
5:完整的结构,未进行rehash,存在冲突。
优点
1:有效解决了哈希冲突,通过dictEntry的next指针,将具有同一索引的值链接起来。
2:很方便就行rehash。我们知道到哈希表大小不足时,就很容易造成哈希冲突。此时便需要扩容。dict.ht[2]其中一个就是用来扩容的,当dictht.size == used时,便将dict.ht[1]扩大到used*2,再将dict.ht[0]缓慢哈希到dict.ht[1]。完成后,将dict.ht[0]指向dict.ht[1],将dict.ht[1]置为空即可。