作为一份比较low的check list,高大上的话不多说了,直接开始。
嗯,还是先关注下我再开始。
1.总原则
一些正确但稍显废话的原则,但能指导后面每个章节的优化,所以还是要啰嗦一次。
-
可扩展性架构,堆机器能不能解决问题是最最优先考虑的问题
-
去中心化的点对点通信,优于通过中心代理的通信
-
池化的长连接,优于短连接
-
二进制数据,优于文本数据
-
尽量减少交互,一次调用的
粗粒度聚合接口 优于 多次调用的细粒度接口
-
尽量减少交互,批量接口 优于 循环调用
-
尽量只交互必要的数据
-
尽量就近访问
-
尽量使用缓存
-
总是设定超时
-
在合适的场景,并行化执行
-
在合适的场景,异步化执行
2.环境准备
保证符合自家各种规范
(没有的话赶紧回家写一个)
,尤其线下压测服务器的配置要与生产环境一致。
2.1 操作系统
2.2 JVM与应用服务器
2.3 周边依赖系统
2.4 后台辅助程序
2.5 测试程序
2.6 流量模型
大家在心里都有这么一个大概的模型,但很少认真写出来。
行文到此,大家大概可以感受到这份checklist的风格,都是大家明白的道理,但可能一时也会忘掉的,这里啰啰嗦嗦的給写下来。
3.数据库
仅以MySQL举例,特别鸣谢,我司DBA,
如聂超大侠,
文中大量观点均来自他们。
3.1 拓扑
根据扩展性原则考虑:
3.2 Schema
自家规范应包含:
3.3 SQL
1. 自家规范应包含:
-
禁止多于3表的join,禁用子查询
-
禁止where子句中对字段施加函数使索引失效,如to_date(add_time)>xxxxx
-
不建议使用%前缀模糊查询影响索引使用,模糊查询较多时建议使用ElasticSearch
-
避免隐式类型转化,如ISENDED=1 与 ISENDED='1'
根据尽量少数据原则与尽量少交互的原则来设计SQL:
-
禁止select *
-
复杂度合理的SQL语句,减少交互次数。
根据扩展性原则,将负载放在更容易伸缩的应用服务实例上:
-
尽量不要做数学运算,函数运算, 或者输出格式转换等非必要操作
-
避免count(*),计数统计实时要求较强使用memcache或者redis,非实时统计使用
定时更新的
单独统计表。
-
甚至排序都是不鼓励的,尽量在应用侧进行。另外避免多余的排序,使用GROUP BY 时,默认会进行排序,当你不需要排序时,可以使用order by null。
2. 联系DBA进行MySQL统计的慢查询的Review,解析SQL查询计划时尽量避免
extra列出现:Using File Sort,Using Temporary
3.4 DAO框架
-
根据尽量少交互与尽量少数据的原则,需使用对SQL完全可控的DAO框架,建议为MyBatis 或 Spring JDBC Template。
-
必须使用prepareStatement,提升性能与防注入。
-
根据一切皆有超时的原则,配置SQL执行的超时。可在连接池里设置default值,可在MyBatis的Mapper定义里设置每个请求的超时,可惜规范是秒级的。
-
JDBC driver 规范本身不支持异步模式,如果一定要异步,可以像Quasar那样把请求封装成Callable交给另外的线程池执行,但注意其额外开销。
3.5 事务
3.6 连接池
选型:
连接池的配置
:
4.缓存
4.1 多级缓存
-
根据缓存原则, 缓存 > 数据库/远程调用
-
根据就近原则, 堆内缓存 > 堆外缓存 > 集中式缓存
-
堆内缓存受大小限制,并影响GC
-
堆内缓存与堆外缓存,分布在每一台应用服务器上,所以刷新方式比集中式的复杂
-
堆外缓存与集中式缓存,需要序列化/反序列化对象
-
集中式缓存,有网络传输的成本,特别是数据超过一个网络包的大小。如果一次获取多个键时,在有分区的情况下,更需要收发多个网络包。
使用上述条件选择合适的缓存方案,或同时使用多级缓存,逐层回源。
4.2 公共
-
需要对回源进行并发控制,当key失效时,只有单一线程对该key回源。
-
基于二进制优于文本数据的原则,
JSON的序列化方案较通用与更高的可读性。
而较大,结构较复杂的对象,基于Kyro,PB,Thrift的二进制序列化方案的性能更高,见后面的序列化方案部分。
4.3 堆内缓存
选型:
GuavaCache:
4.4 堆外缓存
选型:
-
推荐Cassandra的OHC 或者 OpenHFT的
Chronical map2。
-
OHC够简单,其实R大不喜欢Chronical,玩的太深,换个JDK都可能跑不起来。
-
Chronical map3的license则较不友好,复杂度高且要求JDK8。
-
其他如Ehcache的Terracota Offheap 一向不喜欢。
4.5
Memcached
客户端:
-
基于点对点通信优于网关的原则,使用客户端一致性哈希分区。
-
推荐Spymemcached。 XMemcached 太久没更新,Folsom知名度不高。
-
注意Spymemcached为单线程单连接架构(一个SpyClient只有一条IO线程,与每个Memcached只有一条连接),必要时可多建几个SpyClient随机选择,但不要用Commons Pool去封装它,把Spy原本的设计一笔抹杀。
-
根据在合适场景使用并发的原则,Spymemcached支持异步API。
-
根据一切皆设超时的原则,可在ConnectionFactory中设置最大超时数,默认值两秒半太长。
数据结构
:
4.6 Redis as Cache
Redis拓扑,基于点对点通信优于网关的原则:
服务端:
客户端:
-
Jedis基于Apache Commons Pool进行了多连接的封装,正确配置总连接数不超过Redis Server的允许连接数。
-
性能考虑,空闲连接检查不要过于频繁(建议30秒以上),另不要打开testOnBorrow等测试参数。
-
根据一切皆有超时的原则,设定统一的调用超时(默认2秒),获取
连接的最长等待时间参数,重试次数。
-
根据在合适的地方异步的原则,Jedis本身没有异步API,只在PipleLine模式下支持。
数据结构:
命令:
-
慎用的命令:LANGE(0, -1), HGETALL,
SMEMBER
-
高复杂度的命令: ZINTERSTORE, SINTERSTORE, ZUNIONSTORE, ZREM
-
尽量使用多参数的命令:MGET/MSET,HMGET/HMSET, LPUSH/RPUSH等
-
根据减少交互的原则,
尽量使用pipeline
-
根据减少交互的原则,必要时可使用Redis的Lua脚本
5.服务调用
5.1 接口设计
1. 尽量少交互的原则:
支持
批量接口,最大的批量,综合考虑调用者的需求与 后端存储的能力。
支持粗粒度接口,在支持原子细粒度接口的同时,支持粗粒度接口/聚合层接口,将多个数据源的获取,多个动作,合并成一个粗粒度接口。
2. 尽量少数据的原则:
在提供返回所有数据的大接口的同时,提供只提供满足部分调用者需要的轻量接口。
最好再提供能定制返回字段的接口。
3. 二进制数据优于文本数据
同样是一个简单通用性,与性能的选择,特别是大数据量时。
5.2 RESTful
仅以Apache HttpClient为例,大部分Restful框架都是对
Apache HttpClient的封装。
另外OkHttp也值得看看。
-
不要重复创建ApacheClient实例,
使用连接池,正确配置连接池的连接数。
-
连接池总是有锁,针对不同的服务,使用不同的Apache HttpClient实例,将锁分散开来。在高并发时比使用全局单例的ApacheClient,有很大的性能提升。
-
根据一切调用皆有超时的原则,每次调用均设置超时时间。RequestConfig里共有Connect Timeout, Socket Timout 和 从Pool中获取连接的Timeout三种超时。
-
需要异步或并行的场景,使用Apache AsyncHttpClient项目。但要注意
AsyncHttpClient项目,检查调用超时的周期默认为1秒。
5.3 自家RPC框架
每家的RPC框架特性不同,但考虑点都类似。
6.消息异步
6.1 选型
-
根据就近原则,可以先尝试用JVM内的队列来解决,然后再考虑中央消息系统。
-
可靠性要求极高的选择RabbitMQ,可支持单条消息确认。
-
海量消息场景,允许极端情况下少量丢失则使用Kafka。
6.2 Kafka
-
根据扩展性原则,RabbitMQ本身没有分片功能,但可以在客户端自行分片。
-
在同步和异步之间做好权衡,异步批量发送可以极大的提高发送的速度。
-
关注消费者如下参数:commitInterval(自动提交offset间隔),prefetchSize(指单次从服务器批量拉取消息的大小),过大和过小都会影响性能,建议保持默认。
6.3 RabbitMQ