技术

并发的成本 基础设施优化 hashicorp raft源码学习 docker 架构 mosn细节 与微服务框架整合 Java动态代理 编程范式 并发通信模型 《网络是怎样连接的》笔记 go细节 codereview mat使用 jvm 线程实现 go打包机制 go interface及反射 如何学习Kubernetes 《编译原理之美》笔记——后端部分 《编译原理之美》笔记——前端部分 Pilot MCP协议分析 go gc 内存管理玩法汇总 软件机制 istio流量管理 Pilot源码分析 golang io 学习Spring mosn源码浅析 MOSN简介 《datacenter as a computer》笔记 学习JVM Tomcat源码分析 Linux可观测性 MVCC 学习存储 学计算 Gotty源码分析 kubernetes operator kaggle泰坦尼克问题实践 kubernetes自动扩容缩容 神经网络模型优化 直觉上理解机器学习 knative入门 如何学习机器学习 神经网络系列笔记 TIDB源码分析 《阿里巴巴云原生实践15讲》笔记 Alibaba Java诊断工具Arthas TIDB存储——TIKV 《Apache Kafka源码分析》——简介 netty中的线程池 guava cache 源码分析 Springboot 启动过程分析 Spring 创建Bean的年代变迁 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 docker和k8s安全机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占cpu怎么办? 容器狂打日志怎么办? Kubernetes资源调度——scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 自动化mock AIOps说的啥 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes介绍 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 jib源码分析之细节 线程排队 跨主机容器通信 jib源码分析及应用 为容器选择一个合适的entrypoint kubernetes yaml配置 marathon-client 源码分析 《持续交付36讲》笔记 mybatis学习 程序猿应该知道的 无锁数据结构和算法 CNI 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 swagger PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 编程语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS1——论文学习 Unsafe Spark Stream 学习 linux vfs轮廓 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 hbase 泛谈 forkjoin 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 bgp初识 mesos 的一些tips mesos 集成 calico calico学习 AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 硬件对软件设计的影响 《Elasticsearch权威指南》笔记 mockito简介及源码分析 2017软件开发小结—— 从做功能到做系统 《Apache Kafka源码分析》——clients dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 学习并发 从一个marathon的问题开始的 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 java内存模型 java exception Linux IO学习 network channel network byte buffer 测试环境docker化实践 netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 Go并发机制及语言层工具 mesos深入 Macvlan Linux网络源代码学习——数据包的发送与接收 《docker源码分析》小结 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习——整体介绍 zookeeper三重奏 数据库的一些知识 Spark 泛谈 链式处理的那些套路 netty(六)netty回顾 Thrift基本原理与实践(二) Thrift基本原理与实践(一) 回调 异步执行抽象——Executor与Future Docker0.1.0源码分析 java gc Jedis源码分析 Redis概述 机器学习泛谈 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 向Hadoop学习NIO的使用 以新的角度看数据结构 并发控制相关的硬件与内核支持 systemd 简介 那些有用的sql语句 异构数据库表在线同步 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM内存与执行 git spring rmi和thrift maven/ant/gradle使用 再看tcp mesos简介 缓存系统 java nio的多线程扩展 《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go常用的一些库 Python初学 Goroutine 调度模型 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Kubernetes存储 Kubernetes 其它特性 访问Kubernetes上的Service Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 CoreOS 使用 Go学习 JVM类加载 硬币和扑克牌问题 LRU实现 virtualbox 使用 ThreadLocal小结 docker快速入门

标签


平台支持类系统的几个点

2018年07月06日

简介

此处主要指 客户端版本管理系统、配置中心系统、abtest 系统、上传系统、推送系统等 跟app 业务不直接关联,但仍是每个app 的必要的 “业务” 系统。

平台支持 系统 是笔者用以对这几个系统的统称,他们与一般业务系统有以下不同

  1. 一般业务系统 请求 ==> 响应 ==> 展示给用户 就结束了。而平台支持类系统则是 请求 ==> 响应 ==> 数据存在本地 ==> 在恰当的时机生效。
  2. 客户端 本地数据 需要与服务端 做同步
  3. 如果将app 分为 业务/基础环境 两个层次的话,其运行属于“基础环境”的一部分

下文以一个文件下发系统为例,即app 启动时/某个业务点击时 向服务端拉取 适配客户端的文件,客户端下载文件 并应用在本地。

平台支持类系统 有很多的共同点,这也就意味着,很多共同点可以作为框架抽取出来。

几个基本的功能点

基本构成

后端系统设计

app sdk设计

匹配

  1. 条件匹配,将文件推送符合一定特征的用户

    • 设备信息,比如android/ios 等
    • app 信息,比如版本号、渠道号
    • 用户画像信息,比如儿童、老人等
    • 测试信息,比如请求来自一个内部测试设备,可以访问未发布的文件
  2. 精确匹配,将文件推送给一个hdfs 文件(用户id 列表)

假设client 十几个properties,服务端资源十几个properties, 匹配算法有两个方面

  1. 提高每一个资源匹配的速度
  2. 避免重复计算,一般使用缓存

主要思路

  单个匹配速度 避免重复计算
使用es es解决 es解决
哈希 每个property 依次匹配 将client 十几个properties 组成一个字符串 作为cache key

针对哈希算法,有以下优化的变种

  1. properties 按区分度 分为base 和 optional,使用base 作为cache key做初步过滤,对“漏网之鱼” 用optional 做一一匹配
  2. 将十几个properties 归类,每一列单独组成字符串,作为cache key。对每一类匹配得到的资源做交集,即为符合所有properties 约束的资源。
  3. 针对上一种方式,有些properties 作为基本属性,可以作为每一类 cache key的前缀

下发

下发什么?

  1. 数据
  2. 状态,比如提前终止一个文件下发,除新的请求无法获取文件外,还要通知已获取文件的设备禁用该文件

下发方式:

  1. 全量

    • 适用于一段时间有效文件 数量有限的业务
    • 必然有一个逻辑:select * from resource where status = on,随着时间的推迟,这个sql 会变慢。当然,你可以为每个资源设置 过期时间,查询时排除掉过期的资源。
    • 从更宏观的角度看,全量方式对于客户端来说,客户端是无状态的。
  2. 增量

    • 增量的本质是客户端有一个“版本”,服务端有一个数据版本,客户端与服务端版本增量同步。当然,也有系统采用时间戳 来标记数据版本。
    • 增量的难点是,版本只能“增长”。对客户端逻辑要求比较高,笔者就碰到过状态同步失败,客户端数据不断累积的问题。

增量的两种同步方式:

  1. 明确告诉客户端增删改了哪些东西

     {
         "add":[...],
         "update":[...],
         "delete":[...]
     }
    
  2. 返回给客户端所有变化的数据,由客户端合并。(这个方案更复杂)

     [
         {
             "id":xx,
             "status":xx
         },
         {
             "id":xx,
             "status":xx
         }
     ]
    

    id 和本地资源的一样,则刷新到本地,否则视为新增。

灰度下发

有几种实现方案

  1. 参见abtest 系统设计汇总 “如何扩大客户端abtest 的应用范围” 小节,在具备一个成熟的abtest 系统之后,灰度下发的事情 可以交给abtest 来统一完成。
  2. 已有系统设计中,每一个资源通常都有标记 状态的字段,新增一个“灰度中“状态
  3. 灰度下发与 原来的portal系统 代码不耦合,portal返回 的数据 经过灰度逻辑的 整合后返回给用户

注意:假设对一个资源进行10%的灰度下发,且一个特定用户命中了10%,则其永远命中10%。当灰度增加到20%时,该用户也应该继续命中。

支持多应用

资源一般属于某个应用维度下,这意味着:

  1. 资源的创建要与 应用关联
  2. 系统提供restful api 接口供业务方 代码调用, 调用时应进行权限验证,以避免业务方操作到其它应用的资源
  3. 权限验证最简单的方式是 向业务方提供appkey 和 app secret

支持多环境

  1. 支持多环境的目的是 省去同一个资源配置 在多个环境下的重复操作
  2. 哪些操作可以多环境同步,哪些不能同步,要根据业务区分

数据的组织

对不同的业务 有以下场景:

  1. 获取所有适配的文件
  2. 获取某个类型的文件
  3. 获取某个模块的文件

数据的组织 应该后台界面设计时 有所体现

基本准备

客户端信息 标准化

为了匹配 服务端“资源”,客户端请求是 要携带客户端信息,分为

  1. 设备信息,比如设备id、andriod/ios、wifi/5g 等
  2. 应用信息,比如app version、channel 等
  3. 用户id,以此可以得到用户的画像信息

请求可以采用以下方式携带用户信息

  1. cookie,难以在各个客户端 共用
  2. 请求时 以json 描述用户信息

用户画像数据

通常需要根据用户画像数据 下发不同的资源,比如一个资源只下发给会员/新/儿童/上海用户等,此时需要

  1. 使用统一的枚举类 代表这些画像信息
  2. 画像信息归属于不同的业务组,应提供一个代理服务,聚合各类画像服务

画像数据如何获取?

  1. 画像数据由客户端一次请求(加上必要的同步),持久存储,每次请求时携带用户画像数据
  2. 每次请求时,由系统根据uid 调用画像聚合服务,向请求中注入画像信息,再进行业务匹配

安全

  1. 防篡改

    • 更改返回json
    • 比如返回json 中有一个图片/视频的url,客户端请求拿到图片url 后再下载图片本身,容易被拦截为非法图片。

一般采用签名来防止 篡改,使用签名 要注意 包含转义字符字符串的 转义问题

权限管理

  1. 超级管理员 可以做一切事情
  2. app 管理员 app 范围内可以做一切事情
  3. 文件某个组织维度的管理员 审核
  4. 文件创建者 创建、修改

初级权限系统设计

权限的分配一般有两种方式:

  1. 授权,实现方便,但坑在于:不够直观,开发人员经常被用户打扰要权限。PS:一个典型的维护 影响设计的例子。
  2. 申请/审批

下发进度统计

测试设备管理

  1. 测试设备 可以访问到 未发布状态的资源,以备测试人员测试。
  2. 测试设备信息的 管理可以 在各个项目中共用。

精确到个人的日志记录

后端系统整体设计

后端系统一般由运营人员操作,用来录入数据。有的系统数据简单,对于复杂数据的系统,一般分阶段设计页面,由用户逐步提交数据。比如一个电商业务,采集用户信息,包括:身份信息(姓名,身份证等)、收获地址(可能多个)、兴趣信息等,每个信息一个小的页面/浮动窗口。分阶段提交信息 有几种可能

  1. 每一个阶段的信息 刚好是一个数据库表,则“下一步”往往意味着保存当前数据到数据库
  2. 每一个阶段的信息 与 数据库表 不具备明显的一对一关系,则数据的保存与回显 就有点复杂,这时可以

    • 前端页面 保存所有数据,在最后提交时 将所有数据一起提交
    • 前端每一个阶段都提交一次数据,后端只是保存在缓存里,在最后提交时同步到db。设计思路参见 ddd(三)——controller-service-dao的败笔

整体来说,在实现后端系统时,在有意识的向ddd 靠近,逐步学习ddd的理念。

性能

缓存/打掉缓存

  1. 为提高性能,一般会使用缓存
  2. 当用户后台变更资源及状态时,应打掉缓存
  3. 打掉缓存的方式

    • 较短的缓存时间,到期自动重建缓存
    • backend 变更操作直接打掉缓存,这种方式无法打掉portal 本地缓存
    • backend 通过消息队列通知portal打掉缓存
    • portal 监控db主动感知变化,打掉缓存

推拉

  1. app 启动时,发送请求向portal 同步数据。该http请求一般是异步处理的
  2. 异步带来的问题是:app 启动获取的更改,通常在下次启动之后才会被业务逻辑感知到
  3. 如果app 与 公司后台维护有长连接,则可以通过长连接 通知app 发生变化,触发app 主动向portal 同步数据

维护

数据采集

汇报方式

  1. 直接发http请求。客户端在收到推送时会向服务端发送数据(数据分析依据),但对于全局推送,会导致同一时间所有客户端都在向服务端发送请求,打垮nginx。
  2. 用户行为日志收集

全局关闭开关

警惕app 发版、新数据 对系统的影响

文档

平台支持项目一定要有文档,否则各种新人(不熟悉项目的人)的问题一定“教你做人”,文档包括

  1. 设计文档
  2. 接口文档
  3. 常见错误文档

文档给谁看:

  1. 接入新的应用时的开发人员
  2. 业务使用方
  3. 接管业务的新人
  4. 测试人员,包括测试项目本身,或者业务项目(依赖了支持类项目)的测试

能界面设计上防止出错的,就别指望用户自觉了

文档表述上,可以多使用图片、脑图等表现力强的展现形式。你或许很反感别人老是来问你认为很简单的事情,但那一定说明你文档写的不够好。

项目管理

平台支持项目与 一般业务系统有以下不同:

  1. 业务系统 需求来自产品经理,变动也来自产品经理,进度推进与资源协调 也一般由产品经理负责,技术相对省心
  2. 业务系统 几乎没有 支持工作,平台支持系统多应用的接入 不可避免的会有一些调整及支持
  3. 业务系统有问题,基本上线之后就可以发现,之后基本稳定,因此可以一人维护多个业务系统。支持类系统 则全周期内维护工作比较多,bug、权限授予、辅助数据查询等

也因此,平台支持类系统 在进度管理、人员分配上与 业务系统有所不同。

生命周期

平台支持类的项目,基本在跟着 客户端发版走,所以很多开发节点 要跟得上 客户端发版。

通用架构

前文都是从 单个项目来考虑的,多个类似的项目放在一起,我们就可以发现,其实有很多共用的部分。

一切皆配置

所有业务 以配置形式存在,通过配置中心 下发到客户端,客户端同时拉取 用户画像数据,据此根据 相应的配置数据 进行 匹配,进而产生 相应的业务行为。

各自为政

当然,在实际实施的过程作用,还可以有一个变种:即数据还是走配置中心全量下发,客户端另外发请求 查询匹配的数据(下发和匹配过程分开),此时服务端只返回匹配的id 即可。

对比

两个方案 一个侧重客户端,一个侧重服务端。理论上,第一个更轻省些,但对客户端的能力要求较高。业务架构 影响/被影响 团队架构,笔者第一次有如此切身的体会。

小结

支持类系统的意义

大致一个app,小到一个项目都需要支持类系统

  1. 一个app 需要推送、文件下发、弹屏这类支持系统
  2. 同一类型项目需要业务相关的支持系统,比如本文的系统都用到了匹配与统计等
  3. 一个系统需要用户登录注册、权限 这类支持功能

我总结了一下,希望可以做到

  1. 快速复制一个具备 较强功能的 app 的能力,这体现了一个技术团队的战斗力
  2. 加快实现一个支持类后台系统的速度

一样与不一样

上文提到了一个平台支持类项目的多个环节,针对每一个环节,在实现的时候,在利弊差不多的情况下,我们尽量使用了不同的实现方案。 比如权限控制,在A系统中我们使用了注解方式来描述权限,在B系统中我们在配置文件中描述权限,然后使用一个权限filter 拦截检查所有的请求。

通过这种方式

  1. 一个是看到了不同的风景,切身体会到了方案的优缺点
  2. 促进了优秀方案的沉淀

bug

bug是免不了的

  1. 自己能力有限
  2. 协作带来的认知误差
  3. 环境,比如追求业务速度,负责业务比较多等

如何看待bug

  1. 自测、他测,从源头上消灭bug
  2. 在项目运行阶段,提高项目可观察性,提前预判可能出现的问题
  3. 出现问题后,可一键回退/关闭/清空等。面向故障编程