架构必知:后端服务实战之性能优化

2023-09-06 0 258

责任编辑简单介绍b0d3fb端服务项目开发中常见的许多操控性强化思路。

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

查看JVM默认的配置:java -XX:+PrintFlagsFinal -version | grep -iE HeapSize|PermSize|ThreadStackSize

jps:用来输出JVM中运行的进程状态信息。

jstack:用来查看某个Java进程内的线程堆栈信息。

jmap:用来查看堆内存采用状况。采用jmap -heap pid查看进程堆内存采用情况,包括采用的GC演算法、堆配置模块和各代中堆内存采用情况。

查看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 2)arthas

强化方向

比如,JVM的堆大小(Xms、Xmx),垃圾回收思路等。要进行JVM微观的Listary,须要对JVM的执行基本原理有一定的了解,如内存的结构,GC的种类等,然后根据应用流程的特点增设合理的JVM參数,但是GC tuning is the last task to be done.

架构必知:后端服务实战之性能优化 

出处:https://www.cnblogs.com/luxiaoxun/p/11755177.html

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务