下列归纳了许多后端服务项目合作开发中常见的操控性强化思路。
1、标识符
强化标识符同时实现是首位的,不光是许多片面的繁杂同时实现。假如紧密结合市场需求能从标识符同时实现的视角,采用更高效率的演算法或计划同时实现,从而补救,那是最简单有效率的。
2、资料库
资料库的强化,总体上有3个各方面:
1) SQLListary:除掌控SQL基本上的强化方式,采用慢笔记功能定位到具体内容难题SQL,采用explain、profile等辅助工具来逐渐Listary。
2) 连接池Listary:优先选择高效率适用于的连接池,紧密结合现阶段采用连接池的基本上原理、具体内容的连接池监视统计数据和现阶段的业务规模作两个综合性的推论,透过反反复复的数次增容获得最后的Listary模块。
3) 构架微观:主要包括随机存取分立、characterization库阻抗平衡、水准和横向科艾麻该户等各方面,通常须要的更动非常大,须要从总体构架各方面综合性考量。
3、内存
进行分类
邻近地区内存(HashMap/ConcurrentHashMap、Ehcache、RocksDB、Guava Cache等)。
内存服务项目(Redis/Tair/Memcache等)。
结构设计关键性点
1、什么时候更新内存?如何保障更新的可靠性和实时性?
更新内存的思路,须要具体内容难题具体内容分析。基本上的更新思路有两个:
1) 接收变更的消息,准实时更新。
2) 给每两个内存统计数据设置5分钟的过期时间,过期后从DB加载再回设到DB。这个思路是对第两个思路的有力补充,解决了手动变更DB不发消息、接收消息更新程序临时出错等难题导致的第两个思路失效的难题。透过这种双保险机制,有效率地保证了内存统计数据的可靠性和实时性。
2、内存是否会满,内存满了怎么办?
对于两个内存服务项目,理论上来说,随着内存统计数据的日益增多,在容量有限的情况下,内存肯定有一天会满的。如何应对?
1) 给内存服务,优先选择合适的内存逐出演算法,比如最常见的LRU。
2) 针对现阶段设置的容量,设置适当的警戒值,比如10G的内存,当内存统计数据达到8G的时候,就开始发出报警,提前排查难题或者扩容。
3) 给许多没有必要长期保存的key,尽量设置过期时间。
3、内存是否允许丢失?丢失了怎么办?
根据业务场景推论,是否允许丢失。假如不允许,就须要带持久化功能的内存服务项目来支持,比如Redis或者Tair。更细节的话,可以根据业务对丢失时间的容忍度,还可以优先选择更具体内容的持久化思路,比如Redis的RDB或者AOF。
内存难题
1、内存穿透
描述:内存穿透是指内存和资料库中都没有的统计数据,而用户不断发起请求,如发起为id为“-1”的统计数据或id为不光大不存在的统计数据。这时的用户很可能是攻击者,攻击会导致资料库压力过大。
解决计划:
1) 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2) 从内存取不到的统计数据,在资料库中也没有取到,这时也可以将key-value对写为key-null,内存有效率时间可以设置短一点,如30秒(设置太长会导致正常情况也没法采用)。这样可以防止攻击用户反反复复用同两个id暴力攻击。
2、内存击穿
描述:内存击穿是指内存中没有但资料库中有的统计数据(通常是内存时间到期),这时由于并发用户不光多,同时读内存没读到统计数据,又同时去资料库去取统计数据,引起资料库压力瞬间增大,造成过大压力。
解决计划:
1) 设置热点数据永远不过期。
2) 加互斥锁,业界比较常见的做法,是采用mutex。简单地来说,就是在内存失效的时候(推论拿出来的值为空),不是立即去load db,而是先采用内存辅助工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set两个mutex key,当操作返回成功时,再进行load db的操作并回设内存;否则,就重试整个get内存的方法。类似下面的标识符:
public String get(key) { String value = redis.get(key); if (value == null) { //代表内存值过期 //设置3min的超时,防止del操作失败的时候,下次内存过期一直不能load db if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功 value = db.get(key); redis.set(key, value, expire_secs); redis.del(key_mutex); }else { sleep(50); get(key); //重试 } } else { return value; } }3、内存雪崩
描述:内存雪崩是指内存中统计数据大批量到过期时间,而查询统计数据量巨大,引起资料库压力过大甚至down机。
和内存击穿不同的是,内存击穿是并发查同一条统计数据,内存雪崩是不同统计数据都过期了,很多统计数据都查不到从而查资料库。
解决计划:
1)内存统计数据的过期时间设置随机,防止同一时间大量统计数据过期现象发生。
2)假如内存系统是分布式部署,将热点统计数据均匀分布在不同的内存节点中。
3)设置热点统计数据永远不过期。
4、内存更新
Cache Aside 模式:这是最常见最常见的pattern了。其具体内容逻辑如下:
失效:应用程序先从cache取统计数据,没有获得,则从资料库中取统计数据,成功后,放到内存中。
命中:应用程序从cache中取统计数据,取到后返回。
更新:先把统计数据存到资料库中,成功后,再让内存失效。
4、异步
采用场景
针对某些客户端的请求,在服务项目端可能需要针对这些请求做许多附属额外的事情,这些事情其实用户并不关心或者不须要立即拿到这些事情的处理结果,这种情况就比较适合用异步的方式去处理。
作用
异步处理的好处:
1) 缩短接口响应时间,采用户的请求快速返回,用户体验更好。
2) 避免线程长时间处于运行状态,这样会引起服务项目线程池的可用线程长时间不够用,从而引起线程池任务队列长度增大,从而阻塞更多请求任务,使得更多请求得不到及时处理。
3) 提升服务项目的处理操控性。
同时实现方式
1、线程(线程池)
采用额外开辟两个线程或者采用线程池的做法,在IO线程(处理请求响应)之外的线程来处理相应的任务,在IO线程中让response先返回。
假如异步线程处理的任务结构设计的统计数据量非常大,那么可以引入阻塞队列BlockingQueue作进一步的强化。具体内容做法是让一批异步线程不断地往阻塞队列里添加要处理的统计数据,然后额外起两个或一批处理线程,循环批量从队列里拿预设大小的统计数据,来进行批处理,这样进一步提高了操控性。
2、消息队列(MQ)
采用消息队列(MQ)中间件服务项目,MQ天生就是异步的。许多额外的任务,可能不须要这个系统来处理,但是须要其他系统来处理。这个时候可以先把它封装成两个消息,扔到消息队列里面,透过消息中间件的可靠性保证把消息投递到关心它的系统,然后让其他系统来做相应的处理。
5、NoSQL
和内存的区别
这里介绍的NoSQL和内存不一样,虽然可能会采用一样的统计数据存储计划(比如Redis或者Tair),但是采用的方式不一样,把它作为DB来用。假如当作DB来用,须要有效率保证统计数据存储计划的可用性、可靠性。
采用场景
须要紧密结合具体内容的业务场景,看这块业务涉及的统计数据是否适合用NoSQL来存储,对统计数据的操作方式是否适合用NoSQL的方式来操作,或者是否须要用到NoSQL的许多额外特性(比如原子加减等)。
假如业务统计数据不须要和其他统计数据作关联,不须要事务或者外键之类的支持,而且有可能写入会异常频繁,这个时候就比较适合用NoSQL(比如HBase)。监视类、笔记类系统通常会采集大量的时序统计数据,这类时序指标统计数据往往都是“读少写多”的类型,可以采用Elasticsearch、OpenTSDB等。
6、多线程与分布式
采用场景
离线任务、异步任务、大统计数据任务、耗时较长任务的运行,适当地利用,可达到加速的效果。
注意:线上对响应时间要求较高的场合,尽量少用多线程,尤其是服务项目线程须要等待任务线程的场合(很多重大事故就是和这个息息相关),假如一定要用,可以对服务项目线程设置两个最大等待时间。
常见做法
假如单机的处理能力可以满足实际业务的市场需求,那么尽可能地采用单机多线程的处理方式,减少繁杂性;反之,则须要采用多机多线程的方式。
对于单机多线程,可以引入线程池的机制,作用有二:
1) 提高操控性,节省线程创建和销毁的开销。
2) 限流,给线程池两个固定的容量,达到这个容量值后再有任务进来,就进入队列进行排队,保障机器极限压力下的稳定处理能力在采用JDK自带的线程池时,一定要仔细理解构造方法的各个模块的含义,如core pool size、max pool size、keepAliveTime、worker queue等,在理解的基础上透过不断地测试调整这些模块值达到最优效果。
假如单机的处理能力不能满足市场需求,这个时候须要采用多机多线程的方式。这个时候就须要许多分布式系统的知识了,可以选用许多开源成熟的分布式任务调度系统如xxl-job。
7、JVM强化
个人主要的后端语言是JAVA,对JVM进行强化也能一定程度上的提升JAVA程序的操控性。JVM通常能够在软件合作开发后期进行,如在合作开发完毕或者是软件合作开发的某一里程碑阶段,JVM的各项模块将会直接影响JAVA程序的操控性。
操控性指标
查看java进程GC状态:jstat -gcutil {pid} 1000
查看java进程CPU高原因:
2) 分析是哪个线程占用率过高:top -H -p ‘PID’
3) 线程id转换为16进制:printf “%x\n” ‘NID’
4) Jstack查看线程堆栈:jstack PID | grep NID -C行数 –color
推荐2个java辅助工具:
1)show-busy-java-threads
https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#-show-busy-java-threads
2)arthas
https://alibaba.github.io/arthas/index.html
强化方向
比如,JVM的堆大小(Xms、Xmx),垃圾回收思路等。
要进行JVM微观的Listary,须要对JVM的执行基本上原理有一定的了解,如内存的结构,GC的种类等,然后根据应用程序的特点设置合理的JVM模块,但是GC tuning is the last task to be done.
知识在于分享,转发这篇文章,