岁月不居,时节如流,在***的新年致辞中我们又涨了一岁,转眼间做程序员已经六年时间了,这些年做了很多项目也有很多收获,自己也总结了一套理论。下面就结合自己的工作经验,学习认知及实际实践来谈下怎么才能构造一个高性能完备的技术架构。
谈到高性能大家首先想到的是应用服务的qps,并发数,响应时间。其实一个系统的性能每一个指标都可能影响整体系统的性能,并成为瓶颈,所以要打造一个高性能的系统架构就需要一个完整的架构方案,解决好每个方面的痛点。以下每个点都有可能成为系统瓶颈。
缓存
一个高性能的服务离不开缓存的助力,缓存大体可以分为本地缓存、分布式缓存。
本地缓存主要存在本地memory中,使用本地缓存需要注意jvm参数配置垃圾回收算法配置,根据不同的使用场景缓存大小配置适当的回收算法和heap分配策略。
分布式缓存目前比较流行的是redis,redis又分为几种模式,有单点模式、主从模式(master/slaver)、哨兵模式(sentinel)。
单点模式:单节点模式就是单个redis实例,适合对性能要求不强,对服务持续输出能力不敏感的应用。
主从模式(master/slaver):主从模式是使用一个redis实例作为主机,多个实例作为备份机,即一个master主节点,多个slaver从节点。master节点可以进行读写操作,slaver则是同步master主机的数据,slaver节点提供读取操作。主从模式很好的解决了数据备份问题,实现了读写分离。且某个slaver节点挂掉后不影响其他slaver节点读操作和master节点读写操作,master节点挂掉后,slaver不会竞选成为master,redis不在提供写服务,直到master节点恢复。
Sentinel(哨兵模式):Sentinel模式是在主从模式的基础上加入了哨兵机制,一个master节点,多个slaver节点,多个sentinel节点。每个sentinel节点其实就是一个redis实例,但与master节点和salver节点不同的是sentinel节点用来监控redis数据节点,当master节点挂点之后,sentinel会在slaver节点中选择一个作为master,保证了redis能持续对外提供服务。
以上三种模式只是解决了数据备份问题,但要高性能输出还要解决资源分配、负载均衡问题,值得高兴的是目前已有几种分案可以解决此问题,redis cluster、代理分片(proxy)、客户端分片这三种redis集群解决方案都得到了广泛应用。
客户端分片
客户端采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上
代理分片
代理分片(proxy)处于客户端和服务器的中间,将客户端发来的请求,先进行处理(如sharding),再根据规则转发给后端真正的Redis服务器。客户端不直接访问Redis服务器,而是通过proxy代理中间件间接访问。
Redis cluster
官方集群方案,Redis3.0版本以上开始支持,它把多个Redis实例整合在一起,形成一个集群。集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽的其中一个。集群是一个无中心的结构,每个节点都保存数据和整个集群的状态。每个节点都会保存其它节点的信息,知道其它节点所负责的槽,客户端可以连接任意一个node进行操作,当客户端操作的key没有分配到该node上时,Redis会返回转向指令,指向正确的node。
选择好合适的模式集群后还要注意缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题。
数据库调优
数据库优化是多方面的,可以从表设计,索引创建,程序代码逻辑,链接资源管理,分库分表分区等多方面进行优化。
分库方案有sharding-jdbc(shardingsphere)、mycat。mycat是一个中间件的第三方应用,它基于Proxy复写了MySQL协议,将Mycat Server伪装成一个MySQL数据库。sharding-jdbc是一个jar包,它在Java的JDBC层以对业务应用零侵入的方式额外提供分片,读写分离,柔性事务和分布式治理能力。使用mycat时不需要改代码,而使用sharding-jdbc时需要修改代码。
引入分库分表解决了查表性能问题同时也要注意带来的新问题,例如分布式事务的如何处理,夸节点Join 的问题,跨节点合并排序分页等聚合类SQL问题,多数据源管理问题,扩容缩容,数据迁移等问题。
监控
硬件监控可以使用zabbix,java诊断工具Arthas、pinpoint链路跟踪、监控系统性能,jvm监控Prometheus,cat服务接口监控,除了这些常用的工具对服务进行监控,还需开发自己业务的监控,制定自定义监控指标,做好数据的采集、加工、做到全链路数据监控。
分布式统一调度中心
为什么要使用分布式统一调度中心,有以下几点。
- 分布式调度解决定时任务统一调度管理,降低开发和运维成本。
- 保证任务只执行一次,实现任务高可用,可伸缩和负载均衡,提高容错。
- 通过控制台部署和管理定时任务,方便灵感高效。
- 有完善的任务失败重做机制和详细的任务跟踪及告警策略。
下面是几个主流解决方案的对比
feature | quartz | elastic-job-cloud | xxl-job | antares | opencron |
依赖 | mysql | jdk1.7+, zookeeper 3.4.6+ ,maven3.0.4+ ,mesos | mysql ,jdk1.7+ , maven3.0+ | jdk 1.7+ , redis , zookeeper | jdk1.7+ , Tomcat8.0+ |
HA | 多节点部署,通过竞争数据库锁来保证只有一个节点执行任务 | 通过zookeeper的注册与发现,可以动态的添加服务器。 支持水平扩容 | 集群部署 | 集群部署 | — |
任务分片 | — | 支持 | 支持 | 支持 | — |
文档完善 | 完善 | 完善 | 完善 | 文档略少 | 文档略少 |
管理界面 | 无 | 支持 | 支持 | 支持 | 支持 |
难易程度 | 简单 | 较复杂 | 简单 | 一般 | 一般 |
公司 | OpenSymphony | 当当网 | 个人 | 个人 | 个人 |
高级功能 | — | 弹性扩容,多种作业模式,失效转移,运行状态收集,多线程处理数据,幂等性,容错处理,spring命名空间支持 | 弹性扩容,分片广播,故障转移,Rolling实时日志,GLUE(支持在线编辑代码,免发布),任务进度监控,任务依赖,数据加密,邮件报警,运行报表,国际化 | 任务分片, 失效转移,弹性扩容 , | 时间规则支持quartz和crontab ,kill任务, 现场执行,查询任务运行状态 |
缺点 | 没有管理界面,以及不支持任务分片等。不适用于分布式场景 | 需要引入zookeeper , mesos, 增加系统复杂度, 学习成本较高 | 调度中心通过获取 DB锁来保证集群中执行任务的唯一性, 如果短任务很多,随着调度中心集群数量增加,那么数据库的锁竞争会比较厉害,性能不好。 | 不支持动态添加任务 | 不适用于分布式场景 |
使用企业 | 大众化产品,对分布式调度要求不高的公司大面积使用 | 36氪,当当网,国美,金柚网,联想,唯品会,亚信,平安,猪八戒 | 大众点评,运满满,优信二手车,拍拍贷 | — | — |
分布式统一配置中心
分布式统一配置中心将配置从代码中分离出来,实现热更新,统一管控,目前解决方案主要有spring-cloud-config、appllo、disconf。
服务熔断限流降级
一个良好的服务离不开服务熔断、限流降级措施。服务熔断非常重要,它保证了服务在出现问题例如流量高峰、服务雪崩、缓存击穿等,保证服务能持续对外提供服务。Hystrix是Netflix开源的一款容错框架,包含常用的容错方法:线程池隔离、信号量隔离、熔断、降级回退。在高并发访问下,系统所依赖的服务的稳定性对系统的影响非常大,依赖有很多不可控的因素,比如网络连接变慢,资源突然繁忙,暂时不可用,服务脱机等。构建一套稳定、可靠的分布式系统,就必须要有这样一套容错方法。
服务降级是在服务器压力陡增的情况下,可利用资源有限,根据当前业务情况,关闭某些服务接口或者页面,以此释放服务器资源以保证核心任务的正常运行。
服务限流是对并发请求进行限速或者同一个时间窗口内的请求进行限速来保护系统,一旦达到限速标准则可以拒绝服务或排队。常见的限流算法有令牌桶、漏桶、计数器。
池化技术
服务中有些资源是比较稀缺或者比较占用资源,需要限制使用,这时候就需要池化技术来限制总资源数,比如连接池、线程池、资源池GenericObjectPool。保证资源得到有效利用且不浪费资源,同时获取资源速度快。
微服务化
微服务就是分离业务,可以按照业务不同或者服务功能相同对服务进行分离整合,服务间经过http或dubbo进行通讯,多个微服务组合一起提供完整的服务。微服务实现方式可以根据自身业务量身定制,贴合自己业务的就是最好的。具体怎么实现目前没有唯一标准但是却能带来以下这些好处。
- 模块即是服务,业务解耦,降低业务风险
- 独立开发、测试、发布便于发部维护且进行敏捷开发
- 单独配置资源、熔断、降级,做到最优利用资源
- 服务共享,轻量级通讯(如dubbo)减少重复工作
- 跨语言,不在局限于某一种语言开发业务
异步化
系统架构在怎么优化,某些场景的请求处理也不能做到实时响应,异步执行可以很好的解决这一问题,可以通过mq、多线程来实现异步,但是异步也带来了系统的复杂性。