功能 -> 性能✔ -> 智能
可靠性:可扩展性、服务降级、负载均衡、灰度
性能:缓存、并发、池化、异步
一、可靠性
1、应用扩展
- 垂直扩展(scala up)
方式:提升机器硬件
缺点:成本昂贵,扩展能力有限
- 水平扩展(scala out)
方式:增加节点(增加机器数量)
优点:升级过程平滑透明,硬件成本低,理论上可无限扩展
缺点:会增加系统的复杂度,维护成本高,服务须无状态(比如多台机器无法使用Session),可分布式的
2、数据库扩展
- 垂直拆分
原先:一个库数据量太大,将业务紧密,表间关联密切的表划分在一起
分库:将数据表拆分,可以提高性能、隔离故障
-
水平拆分
一个表的数据量太大,一表拆多表,根据查询使用情况确定拆分规则;
MySQL单表最大记录数不要超过5000W;
带来的问题:
(1)如何从多张表中定位到数据所在的表?
自定义映射规则
为某个字段计算hash从而对应到相应的表中
例子: 用户聊天信息表拆分为message_00,message_01,message_02..........message_98,message_99, 然后根据用户的ID来判断这个用户的聊天信息放到哪张表里面,可以用hash的方式来获得, 可以用求余的方式来获得,方法很多
(2)如何使代码优雅(代码层不牵扯数据库分表)
Spring动态数据源 -->> 连接不同的库
分表组件:如TSharding,是一个简易 sharding 组件,也是一个 Mybatis 分库分表组件。
Mybatis 分库分表组件 TSharding-Client
- 数据库扩展考虑点
- 数据量:现有/未来数据量会有多大?
- 增长速度:增长速度影响数据量,提前考虑分表(如何评估?周期不要超过半年,不可靠)
- QPS:每秒查询量
- 切分规则:根据具体业务、表内容、需求等进行合理分片
3、负载均衡
方案
Nginx反向代理服务器(推荐): 用户请求到达Nginx服务器,按照一定策略http转发到具体的机器
HTTP重定向: 302
DNS轮询解析
LVS: 网络4层,内核协议栈
HAProxy: 4/7层
分发策略
Random:随机分发(请求次数公平)
RoundRobin:RR,轮询分发(请求次数公平)
LeastActive:最少活跃,根据服务器当前处理请求的能力(处理能力公平)
Hash:如根据ip做hash、根据内容(请求内容)做hash
健康检查
将空的健康检查文件HealthyCheck.html放在各个服务器上,以供Nginx等服务器检查应用服务器的运行状态。
4、服务降级
- 服务分级
对提供的服务进行分级,核心服务具有更高的优先级,频率低、不重要的服务级别低 - 功能开关
- 全流程开关
- config
二、性能
1、并发
Servlet非线程安全
多线程环境下,需要使用线程安全的集合、共享资源加锁等。
并发锁
synchronized/ReentrantLock
可重入锁:
互斥:同一时刻只能有一个线程持有(相对的是共享锁)
读写锁
ReadWriterLock:有timeout机制,超时不等待
读锁:是共享锁
写锁:是排它锁,互斥
锁不能升级(读锁->写锁),只能降级(写锁->读锁);
写锁要等待所有的读锁释放乐观锁
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
悲观锁
正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系 统不会修改数据)。
2、缓存
-
本地缓存
HashMap
ConcurrentHashMap(并发环境)
Guava Cache
-
memcached
Key-Value
username_zhangsan -> {"username":zhangsan,"nickname":"张全蛋"}LRU
Least Recently Used近期最少使用分布式
一致性Hash
client实现-
Client
xmemcached
spymemcached
-
redis
数据结构丰富
Hash
List
Set
...操作丰富
可持久化:定期将内存数据保存到磁盘,关机后可load到内存
单线程、多实例
3、序列化
概念
将对象的状态信息转换为可以存储或传输形式的过程
方式
- Json:将对象信息转换为json数据
- Java serialization
java序列化一定要应该注意**的6个事项
1、如果子类实现Serializable接口而父类未实现时,父类不会被序列化,但此时父类必须有个无参构造方法,否则会抛InvalidClassException异常。
2、静态变量不会被序列化,那是类的“菜”,不是对象的。
3、transient关键字修饰变量可以限制序列化。
4、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致,就是 private static final long serialVersionUID = 1L。
5、Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用。反序列化时,恢复引用关系。
6、序列化到同一个文件时,如第二次修改了相同对象属性值再次保存时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,因此只保存第二次写的引用,所以读取时,都是第一次保存的对象。读者在使用一个文件多次 writeObject 需要特别注意这个问题(基于第5点)。
- Hessian
- Protobuf
- Kryo
4、池化技术
场景
- 可复用资源
- 创建代价大
类型
- 线程池
- Executor
- 连接池
- tomcat-jdbc
- dbpc
- c3p0
- 对象池
- Spring Bean 管理
5、异步
前端轮询、后端异步
- Futrue/CountDownLatch
消息队列
- QMQ/Kafka/AMQ/rabbitmq
HTTP
- async-http-client
- Apache HttpComponents
Dubbo
- 异步调用、参数回调