JAVA系统架构学习

系统架构的演变历程

①:
简单WEB
②:
深化查询
③:
增加返回后操作
④:
拓展系统的请求来源的多样性
⑤:
数据处理增加数据裁剪
⑥:
丰富
⑦:
细化查询不同组件

系统复杂凸显的问题

  1. 由于数据源较多,所以会初始化大量的HTTP客户端,容易导致JVM GC;
  2. 查询索引构建不合理,导致查询结果不符合预期;
  3. 数据请求外部功能性API一样会产生大量客户端初始化;
  4. 查询链路过长导致执行时间过长(最终结果是触发504错误;
  5. 实时计算和离线计算的性能问题(Bolt数据量积压、算子分配不合理)

系统优化

  • 前端数据渲染优化:冷热数据分离;
  • I/O请求/数据存储优化:ES优化、实时计算引擎优化、合理调整APIs的请求频率、数据分级存储;
  • CPU调度/内存模型优化:异步、并发、多线程、优化JVM的参数。

数据读取与前端渲染

  • 热数据使用KV数据库存储:标签数据、监控数据、索引、数据关联关系等;
  • 冷数据使用HBase存储:pDNS数据、历史Whois数据;
  • 监控数据使用实时计算引擎(Storm/Flink):流量数据、边界资产数据、主机终端数据;
  • 需要检索的数据使用ES做检索。

ES索引与检索优化

  • 内存优化:
    对ES来说,缓存(caches)与缓冲(buffers)是提高索引(index)和搜索(query)性能的关键因素。
    在优化es之前,我们必须时刻牢记一点,es需要足够多的内存,越多越好。但是,也不能把所有的内存都分配给es。分配给es的内存最好是保持在物理内存的50-60%左右,因为os也需要内存支持用户进程,比如分配线程,io缓存等。但是,物理内存的50-60%也不是唯一标准。假如你的内存有256G,即便和OS预留10%的内存,也有25G,足够操作系统使用。另外,最好设置Xmx和Xms一样大,避免heap size的resizing。
    做性能测试时,在相同的情形下,测试结果应该是可以重现的。你做的任何参数的修改,都应该使用进行性能测试,看性能是否有所提高。以性能测试为检验标准,是我们进行优化的必要前提。

  • 数据结构优化:
    尽量减少不需要的字段:ES中存储的数据是用于搜索服务,因此其他一些不需要用于搜索的字段最好不存到ES中,这样即节省空间,同时在相同的数据量下,也能提高搜索性能。 routing值的设置:通常情况下,往ES服务器添加索引数据时,是无需指定routing值。ES会根据索引Id,将该条数据存储到ES集群中的一个shard中。而当指定了routing值为accountId(用户Id),则ES会将相同accountId的多个数据都存放到同一个shard中,后续查询的时候,在指定routing值后,ES只需要查询一个shard就能得到所有需要的数据,而不用再去查询所有的shard,从而大大提供了搜索性能。

  • 分片数和副本数:
    分片数,与检索速度非常相关的的指标,如果分片数过少或过多都会导致检索比较慢。分片数过多会导致检索时打开比较多的文件别外也会导致多台服务器之间通讯。而分片数过少为导至单个分片索引过大,所以检索速度慢。 计算逻辑:索引分片数=数据总量/单分片数
    副本数与索引的稳定性有比较大的关系,怎么说,如果ES在非正常挂了,经常会导致分片丢失,为了保证这些数据的完整性,可以通过副本来解决这个问题。建议在建完索引后在执行Optimize后,马上将副本数调整过来。
    大家经常有一个误区:副本越多,检索越快,这是不对的,副本对于检索速度其它是减无增的我曾做过实现,随副本数的增加检索速度会有微量的下降,所以大家在设置副本数时,需要找一个平衡值。
    另外设置副本后,大家有可能会出现两次相同检索,出现出现不同值的情况,这里可能是由于tranlog没有平衡、或是分片路由的问题,可以通过?preference=_primary 让检索在主片分上进行。

  • 分词
    大家越许认为词库的越多,分词效果越好,索引质量越好,其实不然。分词有很多算法,大部分基于词表进行分词。也就是说词表的大小决定索引大小。所以分词与索引膨涨率有直接链接。词表不应很多,而对文档相关特征性较强的即可。索引大小减少了,那么检索速度也就提高了。

  • 索引段
    索引段即lucene中的segments概念,ES索引过程中会refresh和tranlog也就是说我们在索引过程中segments number不至一个。而segments number与检索是有直接联系的,segments number越多检索越慢,而将segments numbers 有可能的情况下保证为1这将可以提到将近一半的检索速度。

  • 删除文档
    在Lucene中删除文档,数据不会马上进行硬盘上除去,而进在lucene索引中产生一个.del的文件,而在检索过程中这部分数据也会参与检索,lucene在检索过程会判断是否删除了,如果删除了在过滤掉。这样也会降低检索效率。所以可以执行清除删除文档。

  • 管理索引优化
    多线程程序插入,可以根据服务器情况开启多个线程index
    如果有多台机器,可以以每台设置n个shards的方式,根据业务情况,可以考虑取消replias ;
    提高ES占用内存:内存适当调大,初始是256M, 最大1G,调大后,最小和最大一样,避免GC, 并根据机器情况,设置内存大小
    减少shard刷新间隔 ;
    设置一个shard的段segment最大数:可以减少段文件数,提高查询速度
    去掉mapping中_all域:ndex中默认会有_all的域,这个会给查询带来方便,但是会增加索引时间和索引尺寸 ;
    设置source为压缩模式或者disable:compress=true这个能大大减少index的尺寸,disable将直接没有_source域 ;
    增加merge.policy.merge_factor数:设置merge.policy.merge_factor到30,初始是10 ,增加这个数需要更多的内存,bulk index可以调大这个值.
    修改Client获得方式:Client相比transport client更快 ;
    Warmers:通常情况下,warmer包含的请求需要载入大量的索引数据(例如在数据搜索中需要针对特定字段的排序操作,或者用到一些聚合sum,min,max函数的查询等),这样才能达到预热的效果。

  • 设置Filter cache:
    Es的filter cache有两种,一种是node级别的cache(filter cache默认类型),一种是index级别的filter cache。Node级别的cache被整个node共享,并且可以使用百分比设置,对应的属性为 indices.cache.filter.size,这个属性的值可以是百分比,也可以是具体的大小。Index级别的cache,顾名思义,就是针对单个索引的大小。Es官方并不推荐使用这种设置,因为谁也无法预测索引级别的缓存到底有多大(可能非常大,超过了node的对内存),一个索引可能分布在多个node上面,而多个node的结果如果汇总到一个node上,其结果可想而知。

  • 设置Field cache:
    可以通过设置 indices.fielddata.cache.size为具体的大小,比如2GB,或者可用内存的百分比,比如40%。请注意,这个属性是node级别(不是index级别的).当这个缓存不够用时,为了跟新的缓存对象腾出空间,原来缓存的字段会被挤出来,这会导致系统性能下降。所以,请保证这个值足够大,能够满足业务需求。另外,如果你没有设置这个值,es默认缓存可以无限大。所以,在生产环境注意要设置这个值。

  • 设置circuit breaker:
    这个和field cache有关系。断路器可以估算待加载的field的大小。通过断路器,可以防止将特别大的field加载到内存,导致内存溢出。断路器发现待加载的filed超过java的对内存时,会产生一个异常,防止field的继续加载,从而起到保护系统的作用。有两个属性可以设置断路器,一个是 indices.fielddata.breaker.limit,这个值默认是80%。这个值可以动态修改,通过集群设置的api就可以修改。80%就是说,当待加载的field超过es可用堆内存的80%时,就会抛一个异常。

  • 设置Index Buffers:
    indices.memory.index_buffer _size,这个值默认为10%,即堆内存的10%会被用作index时的缓存,这个值可以设置百分比也可以是固定的大小。缓冲自然是越大越好。但记得千万不能超过可用的对内存,并且要跟filter cache和filed cache保证在一个合理的比例。

  • 设置Index Refresh rate(索引刷新频率):
    index.refresh_interval,默认为1秒。即被索引的数据,1秒之后才能够被搜索到。这个时间越小,搜索和索引的性能就越低。这个时间越大,索引和搜索的性能就越高。es建议,在bulk index非常大的索引数据时,将此值设置为-1,索引完毕之后再将此值修改回来。

实时计算模型优化

  1. 集群运行稳定;
  2. Topology能够高效利用CPU/Memory资源,高效处理数据;
  3. 首先要做的是确定你的数据流的主要瓶颈是啥。常见的主要瓶颈有:
    IO:包括磁盘IO或者网络IO;
    CPU:大量计算型任务;
    Memory:大量使用内存进行计算的任务;
    外部资源访问并发度限制:主要是受限于访问外部资源的并发度,比如爬虫;
    外部资源访问本身能力限制:主要是指访问外部资源的本身能力限制,如数据库的最大写入速度;
  4. 常用Topology潜规则:
    每个Topology在每台机器上只设置一个worker:storm同一个worker之间的组件在传输数据时使用直接发送的方式,不走网络,速度会比较快;
    设置acker的数量等于worker的数量;
    设置KafkaSpout的数量等于Kafka中topic的partition数;
    对CPU密集型应用,设置bolt的线程数等于或略小于CPU core数;并且不要随便调整并发度;

内存模型优化-JVM优化

内存问题原因
  1. YoungGC次数过多(JVM参数设置不当、代码不合理);
  2. FullGC次数过多:代码中某个位置读取数据量较大,导致系统内存耗尽,从而导致 Full GC 次数过多,系统缓慢;
  3. CPU占用率高:代码中有比较耗 CPU 的操作,导致 CPU 过高,系统运行缓慢;
  4. FullGC时间过长:对象创建的速度过高、Young区过小、选择合适的GC算法、进程被交换(Swap)出内存、GC线程数过少、I/O负载重、手动调用了System.gc()、堆内存过大、GC任务分配不均;
  5. Perm Space回收频繁或者OOM;
内存优化措施
  1. 多数的Java应用不需要在服务器上进行GC优化; 多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;
  2. 在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合); 减少创建对象的数量; 减少使用全局变量和大对象;
  3. GC优化是到最后不得已才采用的手段; 在实际使用中,分析GC情况优化代码比优化GC参数要多得多;
CPU问题原因
  1. 重复查询泛滥;
  2. 锁竞争或者死锁;
  3. 连接池占用过多;
CPU优化措施-配置

Java线程池有几个重要的配置参数:

  • corePoolSize:核心线程数(最新线程数)
  • maximumPoolSize:最大线程数,超过这个数量的任务会被拒绝,用户可以通过RejectedExecutionHandler接口自定义处理方式;
  • keepAliveTime:线程保持活动的时间

Java线程池需要传入一个Queue参数(workQueue)用来存放执行的任务,而对Queue的不同选择,线程池有完全不同的行为:

  1. SynchronousQueue: 一个无容量的等待队列,一个线程的insert操作必须等待另一线程的remove操作,采用这个Queue线程池将会为每个任务分配一个新线程;
  2. LinkedBlockingQueue : 无界队列,采用该Queue,线程池将忽略 maximumPoolSize参数,仅用corePoolSize的线程处理所有的任务,未处理的任务便在LinkedBlockingQueue中排队;
  3. ArrayBlockingQueue: 有界队列,在有界队列和 maximumPoolSize的作用下,程序将很难被调优:更大的Queue和小的maximumPoolSize将导致CPU的低负载;小的Queue和大的池,Queue就没起动应有的作用。

其实我们的要求很简单,希望线程池能跟连接池一样,能设置最小线程数、最大线程数,当最小数<任务<最大数时,应该分配新的线程处理;当任务>最大数时,应该等待有空闲线程再处理该任务。

CPU优化措施-异步优化
  1. 按照先后关系进行查询调度;
  2. 使用线程池管理查询结果,写入全局HashMap(注意线程安全);
  3. 使用异步返回关键信息,前端做ajax动态渲染;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值