服务器开发和客户端开发wordpress开发


从客户端转服务端也有一段时间,日常工作中,也经常会有同学问我客户端转成服务端开发有什么感受,从我自己的角度出发,写写这块的感受。

随着互联网技术的不断演进,服务端和客户端的技术都在持续更新,但是服务端和客户端面临的场景和问题是不一样的,所以发展出了很多不同的技术来解决这些问题。

服务端通常部署的机器是一个比较标准和稳定的环境,网络,带宽,机器性能,操作系统版本等方面都相对来说比较稳定。但是随着互联网用户的增长,会面临更多的用户,对于服务的高性能,高可用,高并发等提出了更高的要求。一般服务端直接面对的都是各种数据,包括用户产生的数据,行为日志记录,机器log等等,需要处理大量的数据,所以对于数据的敏感程度更高。

客户端通常面临的环境是单机的环境,但是因为直接面对用户的设备,所以环境总是处于一个复杂和不可控的环境中,面临的设备类型比较多,各种设备类型又会有不同的情况,例如不同机器的屏幕大小不一样,需要做不同的适配,不同厂商的推送服务,后台管理策略等等不太一样,造成体验的不一致,不同设备的内存空间,磁盘大小等也不一样,需要有一些相对应的优化手段。因为客户端通常直接面向用户的体验,所以对UI的展示,用户的操作等等这块会比较敏感。

各端都有各自方向的探索来解决各自遇到的问题,我们各选择其中两个方向,来看看服务端和客户端追求的点有哪些不一样。

随着互联网的发展,用户量越来越大,使用的功能越来越丰富,对于服务端的挑战也越来越大,各种不同的架构方式开始出现来解决这些问题。

对于一个初创产品,用户量少,并发量低,数据量小,往往只需要单应用服务器就可以满足需要,数据库和文件服务器都部署在单个服务器上。比如我们一般日常自己开发一些小的demoapp的话,租个简单的云服务器就搞定。

随着访问量的激增,单机服务器总是资源有限,单个数据库的流量也越来越大,这时候出现了数据库性能的瓶颈,于是我们把数据库服务和文件服务拆分出去,单独起一个服务。因为数据库的读写会有影响,一般情况下读的请求远远大于写的请求,我们把数据库读写分离来进行优化。

随着访问量的继续激增,我们发现数据库的性能又有了瓶颈,于是我们把数据库进行分库分表的优化。对于一些访问量特别大的数据,我们又引入了缓存层,一方面来减小数据库的访问压力,一方面也能提供访问的速度。

随着访问量的增加,我们发现用户有一些页面的访问会比较慢,我们发现有些页面是静态的页面,有些页面的数据是实时更新的,于是我们可以采用动静分离的技术,把一些静态资源与后台应用进行分开部署。我们知道在互联网上,用户的网络情况是错综复杂的,如果我们的每次请求都需要直接打到服务端进行处理的话也会非常慢,所以我们又增加了CDN的优化机制,尽量把数据保存在离用户网络最近的地方来进行加速,另一方面,这部分压力直接通过使用缓存,也会减轻服务器端的压力。

随着用户规模和业务量的不断上涨,单个应用服务器也会出现性能瓶颈,于是我们服务器单机开始变成集群,后端开始使用负载均衡的方式来将压力分摊到多个集群。

随着业务规模的扩大和复杂化,不同的业务区别也越来越大,业务放在一起开发会变的越来越复杂,互相之间的影响也会比较大,各业务的应用情况和量级也是有区别的。针对系统各个业务功能进行拆分,不同的业务团队负责不同的业务模块。我们通过微服务的方式进行了业务领域的拆分,来解决单体业务遇到的问题,包括复杂度过高,开发效率低下,从提交到部署耗时长等问题。同时为了更好的知道业务逻辑的划分,我们又通过领域驱动设计的方法来指导对微服务如何进行拆分。

随着移动互联网的发展,出现了iOS和Android平台,加上之前的PC等,我们需要用不同的语言去实现同样的功能,对于企业而言,付出的成本和时间都会成倍增加。

web技术在PC时代就相对比较成熟,移动时代也都提供了主流的浏览器支持,依靠webview容器展示H5页面,iOS一般为UIWebView或者WKWebView,Android为WebView。除了本身webiview提供的能力,客户端也通过把系统能力暴露给web侧来进行交互和能力扩展,这种开发方式被称为Hybrid开发模式。

web代码只需要开发一次就能在多个系统运行,而且web技术社区和资源丰富,开发效率比较高。而且本身又是可以做动态化的加载,又解决了客户端另外的一个难题动态化。

不过在开发的过程中也发现了一些问题,webview本身的渲染效率和js执行性能不佳,跟原生体验差距比较大,还会有一些经常需要优化的点,比如首屏加载过慢,白屏,复杂交互跟原生比卡顿,各设备还是需要做一些适配等问题。

2015年Facebook推出了一套ReactNative,能够通过JavaScript和React开发移动平台的应用。针对webview性能等问题,把绘制交还给原生渲染,通过js调用原生控件进行绘制,但是把逻辑处理还是放在js层。

优点自然是通过原生的渲染,在性能上去媲美原生应用,保留了js的开发,提高开发效率。并且提供了一系列的组件,能够跨平台节约开发成本,因为采用js的方式,相关代码也同样可以进行动态化。当然时隔一年,阿里也推出了weex方案,基本的原理类似,但是这里选择了与vue进行结合,并且针对很多遇到的问题进行了一些优化。

不过泛web的方案也有自身的一些问题,比如开发体验比较一般,一般遇到难解决的问题,不太容易找寻答案。因为使用了原生控件,有一些控件是iOS专属或者安卓专属,控件不完善,尤其刚推出期间,很多控件的功能缺失。如果需要作出一款比较优质的app,需要花费更多的时间,并没有减少开发成本等问题。

2018年Flutter出现,通过Dart语言构建了一套跨平台的开发组件,所有组件通过跨平台的Skia图形库实现渲染引擎,所有控件都是使用Dart语言重新开发,不使用原生控件,可以在更大程度上保证不同平台和不同设备的体验一致性。

当然这些方案相比于上面几个主流方案,主要还在于UI这块还是需要通过原生组件进行支持,这里共用的只是逻辑处理方面的代码,所以还是需要各个端出人来做UI相关的工作。

客户端面临的始终是一个资源受限的环境,用户的手机性能有好有坏,但是为了保证用户的体验,客户端同学始终有一颗追求极致体验的心。

因为都是用户切身能感受到的问题,为了解决这些问题,对客户端开发提出了更高的要求。一般第一点要解决的是问题数据的发现,第二点是要解决体具体遇到的问题。

目前很多公司服务端很多业务都采用了微服务的架构,通过将功能拆分到各个服务中以实现业务逻辑的解耦。

微服务的优势有很多,包括可以独立开发,单体应用分解为一组服务,开发的速度要快很多,更容易理解和维护,每个微服务可以独立部署,开发人员无需协调对服务升级或更改的部署。故障隔离更好,即使应用中的一个服务不起作用,系统仍然可以继续运行。可以混合相关技术栈,使用不同的语言和技术来构建不同服务。

但是微服务有相关优势的同时也有着一些缺陷,微服务的分布式带来了相关的复杂性,增加了故障排除的难度,由于远程调用导致的延迟增加,需要配套相关的基础设施,包括服务发现,通讯组件,链路跟踪等。

客户端相关逻辑因为是单机运行,所以各种模块的代码其实是耦合在一起的,这样导致项目会存在一些组件之间依赖和耦合比较严重。另一方面是编译会比较慢,业务分工上因为代码比较耦合,修改可能会影响到其他业务,所以需要有一套更合理的技术方案。

目前很多项目的组件化是以类似这么一个架构形式来组织的,包括业务组件,基础组件,基础sdk等,其中最困难的是各个业务组件或者说业务模块的解耦是比较麻烦的,一般业务有几种常见的模块间通讯方案

关于kv相关组件的选择来说,也有很多的共性和不同点,目前存在前后端都存在不少Nosql的选择,我们选择redis和mmkv进行一个比较。对于服务端来说,使用redis更多的是做一些缓存作用,减弱对于数据库的访问压力。对于客户端来说,本身并发存储的压力不大,更多的是解决一些想简单key-value进行的存储,而不是很重的使用数据库,当然还有一些其他需求,是性能上的考虑。

redis有其自身的一些特点,Redis不仅仅支持简单的key-value类型的数据,同时还提供比较丰富的数据结构,包括String、Hash、List、Set、SortedSet。

Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。为此

redis提供了AOF持久化和RDB持久化的两种方式。AOF持久化是通过保存redis服务器所执行的写命令来记录数据库状态的。RDB持久化是通过生成一个经过压缩的二进制文件,把redis在内存中的数据保存到磁盘里面的。

Redis缓存一旦挂了,请求响应会都打到数据库,造成巨大的压力。所以对于redis的可用性提出了要求。redis通过哨兵的方式来解决高可用的问题。由一个或多个哨兵组成了哨兵系统,监视任意多个主服务器,以及这些主服务器下的所有从服务器,在主服务器进入下线状态时,自动将主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理请求。

主从服务器方案可以提供读取的性能,不过随着数据量的不断增大,单台服务器资源总是有上限的,比如内存资源有限的话,我们需要采用另外的一种解决方案,redis提供了redis集群的方式,通过使用数据分片来实现,一个redis集群会包含16384个哈希槽,数据库中的每个键都属于这些哈希槽其中的一个。

MMKV是基于mmap内存映射的移动端通用key-value组件,底层序列化/反序列化使用protobuf实现,性能高,稳定性强。开发的背景在于有一些特殊文字引起系统的carsh,所以需要在很多地方进行前后的计数和存储来确定具体是哪些特殊文字,所以对于性能的要求是比较高的。

对于性能要求比较高,而且确保crash的时候不能丢失数据。直接写文件的方式性能有问题,所以通过mmap内存映射到文件,提供一段可供随时写入的内存块,App只管往里面写数据,由操作系统负责将内存回写到文件,不必担心crash导致数据丢失。

考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量kv对象序列化后,append到内存末尾。

使用append实现增量更新带来了一个新的问题,就是不断append的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。

其实从相似程度上,感觉服务端使用bitcask来进行对比更加的相似,索引都是采用hash的方式,文件append操作。不过平常用的比较多的是redis,就拿这个来补充,有兴趣的同学可以也看看bitcask的实现。

MySQL是一个轻量级关系型数据库管理系统,由瑞典MySQLAB公司开发,目前属于Oracle公司。目前MySQL被广泛地应用在上的中小型网站中,由于体积小、速度快、总体拥有成本低,开放源码、免费,一般中小型网站的开发都选择Linux MySQL作为网站数据库。

MySQL是一个关系型数据库管理系统,MySQL是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,就增加了速度并提高了灵活性。

SQLite是一个进程内的轻量级嵌入式数据库,它的数据库就是一个文件,实现了自给自足、无服务器、零配置的、事务性的SQL数据库引擎。它是一个零配置的数据库,这就体现出来SQLite与其他数据库的最大的区别:SQLite不需要在系统中配置,直接可以使用。且SQLite不是一个独立的进程,可以按应用程序需求进行静态或动态连接,SQLite可直接访问其存储文件。

在我们日常业务的应用中有很多的场景都会用到消息队列,比如我们有一些需要处理的异步任务例如一些音视频的流程化处理,一些设备相关属性的同步,我们经常会丢到一个消息队列中用来处理。还有一些比如不同系统之间的同步,也可以使用消息队列进行数据的同步。

对于移动来说,广播通知一般解决的问题是有一些状态的变更,但是接收者其实是并不固定的,也就是发送者并不清楚具体的接收人。

观察者只要向消息中心注册,即可接受其他对象发来的消息,消息发送者和消息接受者两者可以完全解耦。在观察者不需要接受消息的时候可以向消息中心注销。

一般这种广播通知在业务中的应用场景在于一些状态的同步,例如有些feed流进行了点赞,其他地方需要知道这个点赞的变化,我们就可以通过发送广播的方式来进行更新。

对于feed流这个需求出来以后,我们会想到我们需要怎样的一个存储结构来保存这个feed的数据,针对不同的需求我们可能会有很多的结构来保存。比如如果只是一些量比较少的数据,我们是不是可以直接使用mysql来保存数据,带来的好处自然是技术成熟,能够很快的按各种维度进行筛选,方便数据的增删改查操作等。

数据存储确定以后,会思考一下我们这个服务的对接方有哪些,比如是否涉及到feed流的推荐,那需要跟推荐算法那边进行具体的沟通,是否需要推送相关消息,那需要跟负责推送服务的同学确认一下。

对于这类需求是否可以进行一些抽象和整合,比如是否可以按照领域的划分,划分到一个具体的领域,或者复用原先的服务。

关注数据的来源和安全性,比如对于用户发布的数据,是否需要接入审核,关注审核流程,以及审核涉及到的各个方面以及审核涉及到的人。另外比如如果数据的来源不仅仅是用户的上传,而是来自于一些公众号的数据,那就会去关注一下这些数据的来源相关。

上线以后会关注线上的数据,包括协议的时延,调用下游的成功率,报警数据,数据库慢查询相关等。

业务上线以后,如果定位问题,一般比较喜欢的是如果某些具体请求失败了,可以直接拿到相关的ID进行log的串联,发现问题所在。

不管遇到的场景和具体的分工是什么,总体技术同学的目标还是一致的,为产品和业务能更快更好的发展提供支持,希望能互相有一定的了解,在以后的配合中能更加提高效率。