聊聊服务端和客户端开发异同

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

服务端和客户端区别的细节太多,无法进行一一比较,因此选了几个点来谈谈。

准备从三个方向来聊聊:

  1. 服务端和客户端面临的环境和解决的问题
  2. 基础技术组件的对比
  3. 服务端和客户端关注点区别

服务端和客户端面临的问题和解决的问题

面临的环境

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

服务端:

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

客户端:

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

解决的问题:

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

服务端:

业务架构演进

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

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

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

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

扫描二维码关注公众号,回复: 13770847 查看本文章

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

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

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

客户端:

跨端的探索

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

跨端开发方案的几个时代:

web容器

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

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

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

泛 Web 容器时代

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

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

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

Flutter系列

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

其他跨端方案系列

除了上面几个跨端的方案,其实还有一些技术可以提一下

具体使用可以看看官网:kotlinlang.org/docs/refere…

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

  1. 性能的追求

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

比如我们经常app开发中会遇到的一些性能优化点。

  • 滑动流畅度不够,肉眼可见的卡顿。
  • 启动时长过慢,一直在启动界面
  • 应用使用一段时间磁盘就满了
  • 用了一会应用自己就被系统杀了

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

技术组件选型对比

我们选取4种典型的技术组件来进行一些对比。

1. 微服务 vs 模块化

微服务介绍:

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

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

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

组件化介绍:

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

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

  1. 基于路由URL的UI页面跳转管理、把每个页面和一个url进行绑定,需要跳转时,通过一个url进行跳转
  2. 基于反射的远程接口调用封装、因为不想import某个类的头文件,所以通过反射来进行解耦。
  3. 基于面向协议思想的服务注册方案、每个模块会把自己支持的能力进行注册,其他模块通过相关的协议进行调用
  4. 基于通知的广播方案、直接全局发通知

整体对比:

我们发现各自的方案有一些相似的点和一些不同的点。

相似的点:

  • 把一个复杂的应用进行了不同模块的拆分,开发同学只需要关注自己相关模块的逻辑,降低了单个模块的复杂度,逻辑上更加清晰。
  • 服务拆分后,各个单体服务,服务端可以单独部署和升级,对于客户端也可以单独进行编译,以及进行二进制化的优化。加快了编译的速度,代码的互相影响也有所降低
  • 客户端拆分出组件以后可以更好的进行复用,服务端拆分出服务相关领域以后,后续也是可以更好的进行复用。

不同的点:

  • 服务端因为从单机变成了多机,所以首先需要解决的是服务发现相关,并且为了解决高可用等,又需要进行分布式相关的处理。而对于客户端来说,组件化或者模块化还是在单机进行解决,所以基本不需要采用网络通讯的方式,而是函数级别或者其他跨进程的通讯方式。
  • 因为服务端面临了更多的流量和用户,所以会遇到负载均衡,超时配置,机房部署等等问题,这点也是客户端不太会去考虑的点。
  • 对于日志来说,服务端因为多机器,流程长,所以需要一个串联的字段,一般使用logId进行一次请求的串联。 而客户端一般日志本身是按单个用户维度的,对于用户问题的发现有时间维度,关键词维度等,所以一般通过这些来进行排查。

2. Redis vs mmkv

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

Redis介绍:

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

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

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

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

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

MMKV介绍:

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

  • 内存准备

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

  • 数据组织

数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。

  • 写入优化

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

  • 空间增长

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

整体对比:

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

相似的点:

  1. 数据结构的支持,我们可以看到redis提供了很多的数据结构,包括string, hash, list, set, sorted set等数据结构。MMKV提供的更多的是一些基本数据类型,包括bool, int32, int64, double, 二进制,object等,虽然不太一样,不过多样性也是公需。
  2. 都比较追求性能,MMKV采用mmap方式,redis直接采用内存。

不同的点:

  1. 两大组件解决问题的场景不太一样,MMKV主要解决的背景前面说了,除了这块以外,对于一些常见的比如保存feed流首屏用于网络不好的情况下场景的优化,一些弹窗是否展示的状态位等等都可以用到。对于redis来说,减少数据库压力是一个非常重要的点,另外的一些场景比如计数器,分布式锁,排行榜等应用。
  2. Redis因为服务端的场景,必须提供主从服务,哨兵机制,集群等服务,而客户端不太需要这么一套设施。

3. Mysql VS sqlite

Mysql介绍:

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

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

Mysql的优势

  • 易用很容易安装。第三方工具,包括可视化工具,让用户能够很容易入门。
  • 功能丰富MySQL 支持关系型数据库应该有的大部分功能——或者直接支持、或者间接支持。
  • 安全支持很多安全特性,有些非常高级,并且是内置于 MySQL 中。
  • 可扩展也非常强大MySQL 能够处理大量数据,并且在需要的时候可以规模化使用。
  • 快速放弃某些标准让 MySQL 能够非常高效、简捷地工作,因而速度更快。

Sqlite介绍:

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

SQLite的优点

  • 基于文件整个数据库完全由磁盘上的一个文件构成,这使得它的可移植性非常好。
  • 标准化尽管它看起来像一个“简化版”的数据库实现,但是 SQLite 确实支持 SQL。它省略了一些功能(RIGHT OUTER JOIN 和 FOR EACH STATEMENT),但同时也增加了一些额外的功能。
  • SQLite 包含丰富的功能,所能提供的特性超乎开发所需,使用起来也非常简洁——只需要一个文件和一个 C 链接库

SQLite的缺点

  • 没有用户管理高级数据库都支持用户系统,例如管理连接对数据库和表的访问权限。鉴于 SQLite 的目的和性质(没有多客户端并发的高层设计),它并不包含这些功能。

整体对比:

相似的点:

  1. 都实现了基本的一些sql标准,包括数据库的一些基本性质的实现,关于事务的支持等。
  2. 都是各端在日常遇到的场景中常用的选择方式,一些基本的底层原理是一致的,包括数据结构等。

不同的点:

  1. 服务端存储的数据是非常重要的,数据不会随便丢失,所以一般会做数据备份,而客户端受限于磁盘空间,一般是不会做备份的,不过也有一些特殊情况。比如IM的场景中,因为消息数据完全存储于用户设备上,一种方式是通过恢复的方式来进行损坏,另外还有一种兜底的方式就是定时备份一份数据。
  2. 服务端对于安全性要求会比较高,所以一般对于用户的权限会比较看重,客户端一般不会使用到用户管理这块。
  3. 服务端为了性能考虑需要进行读写分离,分库分表等,一般客户端的瓶颈不会是数据库,所以一般对于数据库的性能要求没有这么高,一般情况下,写操作汇聚在一个队列中进行写入往往也是没问题的。

4. 消息队列 vs 广播通知

其实这两个技术场景可能不是特别适合对应,不过其中也有一些相似的点可以聊聊。

消息队列介绍

消息队列中间件是分布式系统中重要的组件,主要有这么几个作用:

  1. 解耦:传统的软件开发模式,各个模块之间相互调用,数据共享,每个模块都要时刻关注其他模块的是否更改或者是否挂掉等等,使用消息队列,可以避免模块之间直接调用,将所需共享的数据放在消息队列中,对于新增业务模块,只要对该类消息感兴趣,即可订阅该类消息,对原有系统和业务没有任何影响,降低了系统各个模块的耦合度,提高了系统的可扩展性。
  2. 异步:消息队列提供了异步处理机制,在很多时候应用不想也不需要立即处理消息,允许应用把一些消息放入消息中间件中,并不立即处理它,在之后需要的时候再慢慢处理。
  3. 削峰:在访问了骤增的场景下,需要保证应用系统的平稳性,但是这样突发流量并不常见,如果以这类峰值的标准而投放资源的话,那无疑是巨大的浪费,使用消息队列能够使关键组件支撑突发访问压力,不会因为突发的超负荷请求而完全崩溃。消息队列的容量可以配置的很大,如果采用磁盘存储消息,则几乎等于“无限”容量,这样一来,高峰期的消息可以被积压起来,在随后的时间内进行平滑的处理完成,而不至于让系统短时间内无法承载而导致崩溃,在电商网站的秒杀抢购这种突发性流量很强的业务场景中,消息队列的强大缓冲能力可以很好的起到削峰作用。

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

广播通知介绍

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

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

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

整体对比:

相似点:

  1. 对于一些不同的系统,解决了解耦的问题,这点上是消息队列和广播通知类似的功能。
  2. 广播通知可以通知不同的接收方,而对于消息队列来说,也可以起多个消费组来进行消费,大家互不影响。

不同点:

  1. 消息队列因为场景的复杂性需要考虑更多的点,比如消息需要存储在磁盘中,并且保证接手的消息不丢失。而对于广播通知来说,是一个内存级别的通知,所以并不会持久化。
  2. 消息队列需要考虑更多可用性方面,比如副本机制,考虑分区,对于机器宕机情况的重新选举等问题。

关注点区别

举一个场景,我们看下具体有哪些关注点的区别,我们以feed流的实现来看看。

客户端关注点:

  • 对于feed流来说,UI稿是非常关注的问题,比如设计在一些封面图上增加了一些高斯模糊,针对不同的feed类型需要展示不同的样式。因为这些样式,对于客户端的工作量影响会比较大,比如我们常见的一个是,feed流有一段文字的详情,但是因为文字太多,我们只想展示最多4行,多余的部分我们用一个展开的按钮来替换。 这里就有很多纠结的点,比如点击展开,这里是否需要加一个展开的动画。比如这个展开字段
  • feed流整体的数据更新方式,比如对用户点赞之后,如何去更新feed流相关的UI。比如有一些用户的信息,是放在每个feed流数据中,还是需要全局存一个用户的数据等。对于此,可能有些采用函数响应式的方式来进行数据的更新,有一些可能采用的是原生table的代理方式来实现,或者采用广播的方式来进行一些数据的传递等等。
  • 关注协议格式,和服务端约定相关的协议格式,看是否能够满足需求。
  • 关注实现的方式,比如一般详情页因为多变的原因,很可能会评估采用web方式的可行性。目前比如头条等的详情web页就是比较成熟的方案
  • 关注性能优化,性能关注的点可能有很多,比如OOM问题,我们知道如果直接使用一些原始的图片的话,因为解出来的bitmap很大,很有可能造成OOM的问题,所以会去关注图片格式问题。 比如feed流的滑动性能,会去关注是不是有哪些可以异步去做的事,却阻塞了主线程等。 比如详情页用web的话,是不是可以采用预加载的方式,一些视频类能不能采用客户端的组件等。
  • 会关注一些用户体验相关方面的改进,比如用户如果没有网的话或者网络比较慢的情况下,是不是我们可以缓存上次刷的首屏数据进行优化。比如用户在没有网的情况下,我们是不是可以把点赞和评论的请求缓存起来,先客户端假写,等到用户有网的时候再进行请求等。
  • 相关业务上线以后,可能会有一些常见的反馈问题等,一般客户端常见的排查手段是打一些log,通过sladar平台进行日志的捞取,而性能方面会时长关注一下页面的fps,crash率等等。

服务端关注点:

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

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

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

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

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

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

总结

客户端和服务端因为场景和分工的不同,有很多不一样的点,我们选取了其中三个点聊了聊。

  • 各端面临的问题和一些规划的方向
  • 技术组件选型对比,看看类似的一些组件具体有哪些区别
  • 针对了feed流这么一个具体需求,聊了下客户端和服务端在整个需求过程中重点关注的事

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

猜你喜欢

转载自juejin.im/post/7083128406665068552