首页
Search
1
JAVA垃圾回收
993 阅读
2
Kafka、RocketMQ消息队列总结
981 阅读
3
Flink on Kubernetes 计算和存储分离落地实践
970 阅读
4
Linux免密登陆-ubuntu
904 阅读
5
Redis集群部署方案
890 阅读
大数据
Flink
后端
Java
笔记
运维
游客
Search
标签搜索
大数据
Flink
离线
实时
Redis
OpenJDK
Java
笔记
JVM
Elasticsearch
GC
Hadoop
Hudi
Flink CDC
K8S
数据湖
TOTC
累计撰写
307
篇文章
累计阅读
104.3万
次
首页
栏目
大数据
Flink
后端
Java
笔记
运维
页面
搜索到
6
篇与
Java
的结果
返回首页
2024-12-13
构建智能灵活的 Ribbon 负载均衡策略
分布式系统架构中,负载均衡是确保系统高可用性和性能的关键环节。以线上服务为例,某个服务原本部署了4个实例来应对大量的请求流量。然而,意外情况发生,其中一个实例所在的机房出现故障,导致其响应速度变得极为缓慢,但是仍然和Nacos注册中心保持着心跳。而当时所采用的负载均衡策略是轮询策略 RoundRobinRule,这一策略在正常情况下能够较为均匀地分配请求,但在面对这种异常情况时,却暴露出了明显的局限性。由于轮询策略的特性,它不会根据实例的实际响应情况进行动态调整,这就使得故障实例上仍然会有大量请求持续堆积。随着时间的推移,发现该实例所在机器的 close_wait 连接数急剧增加,导致整个机器负载加重。为了解决这一问题,调研了一些传统的应对策略:其一,配置超时失败重试机制 ... httpclient: response-timeout: 30s。故障实例响应慢时,自动失败路由到其他实例进行重试,从而使上游的请求最终能够成功。但故障服务实例的流量并没有得到有效的控制和调整。这意味着故障实例和所在机器仍然在承受着巨大的压力。其二,采用熔断策略 Sentinel、Resilience4J、Hystrix。在响应时间/出错百分比/线程数等达到阈值时进行降级、熔断,以保护其他服务实例不受影响。然而,在该场景中,由于还有 3/4 的实例处于正常可用状态,直接进行熔断操作显得过于激进。其三,考虑使用权重轮询策略 WeightedResponseTimeRule。根据服务实例的性能表现动态地分配权重,性能好的实例会被分配更多的请求,而性能差的实例则会逐渐减少请求分配。但该场景下,故障机器的响应时间与正常服务相比已经不在一个数量级,其 QPS 却依然很高。这就导致在权重轮询策略下,故障机器的服务权重会迅速降低,几乎不再接收请求。而且由于我们的配置是在网关层面,当故障机器恢复后,系统无法自动重新计算权重,使得分配到故障机器的流量很少,其权重也很难再次提升上去。基于以上困境,决定对权重轮询策略进行二次开发,使其更加智能,以最大限度地减小请求端的影响。首先增加过滤器RibbonResponseFilter。这个过滤器的主要作用是计算每个服务实例的响应时间,并将其记录到 ServerStats 中。同时,它还会记录请求的返回状态,如果返回状态不是 200,就将其转化为请求超时,并相应地减小该服务的权重。@Component @Slf4j public class RibbonResponseFilter implements GlobalFilter, Ordered { @Autowired protected final SpringClientFactory springClientFactory; public static final String RQUEST_START_TIME = "RequestStartTime"; public static final double TIME_WEIGHT = 30000; public RibbonResponseFilter(SpringClientFactory springClientFactory) { this.springClientFactory = springClientFactory; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { exchange.getAttributes().put(RQUEST_START_TIME, System.currentTimeMillis()); return chain.filter(exchange).then(Mono.fromRunnable(() -> { URI requestUrl = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR); Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); LoadBalancerContext loadBalancerContext = this.springClientFactory.getLoadBalancerContext(route.getUri().getHost()); ServerStats stats = loadBalancerContext.getServerStats(new Server(requestUrl.getHost(), requestUrl.getPort())); long orgStartTime = exchange.getAttribute(RQUEST_START_TIME); long time = System.currentTimeMillis() - orgStartTime; // 响应时间超过 5s 或者服务异常时,减小权重 if (exchange.getResponse().getStatusCode().value()!= 200 || time > 5000) { log.info("The abnormal response will lead to a decrease in weight : {} ", requestUrl.getHost()); stats.noteResponseTime(TIME_WEIGHT); } })); } @Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }增加这个过滤器的原因在于,无论是使用自定义的负载均衡策略,还是内置的 WeightedResponseTimeRule,都无法自动获取到每个服务实例的总请求次数、异常请求次数以及响应时间等关键参数。通过这个过滤器,能够有效地收集这些信息,为后续的权重计算和调整提供有力的数据支持。在注册权重更新 Timer(默认 30s)的同时,同时注册了一个权重重置 Timer(5m)。这样一来,当故障服务实例恢复后,在 5 分钟内,它就能够重新参与到负载均衡的分配中。以下是相关的代码片段:void resetWeight() { if (resetWeightTimer!= null) { resetWeightTimer.cancel(); } resetWeightTimer = new Timer("NFLoadBalancer-AutoRobinRule-resetWeightTimer-" + name, true); resetWeightTimer.schedule(new ResetServerWeightTask(), 0, 60 * 1000 * 5); ResetServerWeight rsw = new ResetServerWeight(); rsw.maintainWeights(); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { public void run() { logger.info("Stopping NFLoadBalancer-AutoRobinRule-ResetWeightTimer-" + name); resetWeightTimer.cancel(); } })); } public void maintainWeights() { ILoadBalancer lb = getLoadBalancer(); if (lb == null) { return; } if (!resetServerWeightAssignmentInProgress.compareAndSet(false, true)) { return; } try { logger.info("Reset weight job started"); AbstractLoadBalancer nlb = (AbstractLoadBalancer) lb; LoadBalancerStats stats = nlb.getLoadBalancerStats(); if (stats == null) { return; } Double weightSoFar = 0.0; List<Double> finalWeights = new ArrayList<Double>(); for (Server server : nlb.getAllServers()) { finalWeights.add(weightSoFar); } setWeights(finalWeights); } catch (Exception e) { logger.error("Error reset server weights", e); } finally { resetServerWeightAssignmentInProgress.set(false); } }在采用此负载均衡策略时,若重置权重后服务仍未修复,由于配置了超时重试机制,请求端可毫无察觉。与此同时,该服务实例的权重会迅速在短时间内再次降至极低水平,如此循环,直至实例恢复正常。此策略有效地处理了线上服务可能遭遇的各类异常状况。
2024年12月13日
77 阅读
1 点赞
2020-01-11
JVM垃圾收集行为分析方法
尽管某些监控工具,如:jvisualvm,可以实时提供垃圾收集图表和指标,但它们并没有提供GC行为完整的详细信息,GC日志是研究垃圾收集行为的最佳信息来源。启用GC日志 可以通过指定以下JVM参数来启用GC日志:Java 8及以下版本-XX:+PrintGCDetails -Xloggc:<gc-log-file-path> Example: -XX:+PrintGCDetails -Xloggc:/opt/tmp/myapp-gc.logJava 9及以上版本-Xlog:gc*:file=<gc-log-file-path> Example: -Xlog:gc*:file=/opt/tmp/myapp-gc.log注意事项 一般需要观察24小时的GC日志,这样就会同时看到高流量和低流量的情况。 建议从生产环境中收集GC日志,因为垃圾收集行为受流量模式的影响很大,在测试环境中很难模拟生产流量。我们进行2次测试:基线测试——使用JMeter工具在没有启用垃圾收集GC日志的情况下运行应用程序20分钟,同时有200个并发用户。GC日志启用测试——使用相同的JMeter脚本运行应用程序并启用垃圾收集GC日志,持续时间为20分钟,同时有200个并发用户。对比结果如下:如图所示,CPU和内存消耗没有明显差异,同样,平均响应和事务吞吐量也没有明显差异,通过实验可以看出,GC日志在生产服务器中增加的开销可以忽略不计。分析工具 捕获GC日志后,可以使用以下免费工具之一来分析GC日志:1.GCeasy 2.IBM GC & Memory visualizer 3.HP Jmeter4.Garbage Cat
2020年01月11日
665 阅读
13 点赞
2019-01-06
JAVA垃圾回收
垃圾回收算法 标记-清除算法 :产生不连续的内存碎片,为较大对象分配内存时无法找到足够连续的内存,不得不提前触发垃圾收集动作。CMS垃圾回收器。标记-整理算法 :存活的对象向一端移动,存活率高时,效率远高于复制算法。G1、ParallelGC(Paraller Old)。复制算法 :Eden、s0、s1,比例是8:1:1,10%被浪费,JDK8,ParallelGC(Parallel Scavenge)。分代收集算法 。{lamp/}垃圾回收器 CMS收集器 :发标记、并发清除、内存碎片、最短回收停顿时间为目标,与用户线程可以并发执行,牺牲一定的吞吐量;可开启内存碎片整理,会停顿用户线程。ParallelGC :(JDK8)新生代(Parallel Scavenge复制算法),老年代(Paraller Old标记整理算法)。G1垃圾收集器 :吞吐量高,可预测的停顿;整体基于标记-整理算法,从局部(两个Region)是基于复制算法;设置了新生代大小相当于放弃了G1为我们做的自动调优。G1官方的建议——实时数据占用了超过半数的堆空间;对象分配率或“晋升”的速度变化明显;期望消除耗时较长的GC或停顿(超过0.5——1秒)。ZGC :着色指针(在对象的引用上标注了对象的信息),读屏障(把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速),Stop-The-World 只会在根对象扫描阶段发生,所以GC暂停时间并不会随着堆和存活对象的数量而增加。TB 级别的堆内存管理;最大 GC Pause 不高于 10ms;最大的吞吐率(Throughput)损耗不高于 15%。ParallelGC 吞吐量优先,后台运算而不需要太多交互的任务。CMS 响应速度优先,集中在互联网站或B/S系统服务端上的Java应用。G1 响应速度优先,面向服务端应用,将来替换CMS。{lamp/}内存划分为 JVM内存划分为堆内存和非堆内存,堆内存分为年轻代、老年代,非堆内存就一个永久代。在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是元空间并不在JVM中,而是使用本地内存。{lamp/}Full GC条件 a.发生minor gc之前会判断老年代最大可用连续空间是否大于新生代的所有对象总空间,如果大于直接发生minor gc,如果小于发生full gc。b.老年代空间不足时,创建一个大对象时Ended放不下,会直接保存到老年代中,如果老年代空间不足,就会触发full gc。c.显示调用System.gc()方法,可能会发生full gc。{lamp/}引用类型 强引用 :永远不会回收软引用SoftReference :内存不足时才会回收该对象。比如网页缓存、图片缓存等。弱引用WeakReference :每次垃圾回收。虚引用PhantomReference :虚引用必须和引用队列 (ReferenceQueue)联合使用,主要用来跟踪对象被垃圾回收器回收的活动。假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。
2019年01月06日
993 阅读
21 点赞
2017-12-03
金融项目微调OpenJDK,编译源码
系统版本:Ubuntu 16.04 LTS系统类型:64位操作系统编译出来的是64位的JDK,可以通过设置参数--with-target-bits=32生成32位编译结果。OpenJDK源码下载 https://hg.openjdk.java.net/jdk8/jdk8,点击左侧zip下载到到本地直接解压即可。微调源码使该JDK编译打包后的jar其他JDK无法识别,应用系统必须和JDK配套部署,例如银行自己开发的一套企业金融资产管理系统,交付第三方财务公司使用时,同时提供jar包和JDK,这样就增强了系统的安全性。安装GCC或CLang,例如,安装GCC的命令为:sudo apt-get install build-essential在编译过程中需要依赖FreeType、CUPS等若干第三方库,OpenJDK全部的依赖库如下:OpenJDK除了使用C、C++编写外,还使用了Java语言,因此还需要一个编译期可用的小版本JDK(如JDK7),官方称为“Bootstrap JDK”。编译命令:1.执行bash configure --enable-debug --with-jvm-variants=serverconfigure会检查依赖项、参数配置和构建输出目录结构等。2.执行make images编译整个OpenJDK,images(product-images)是编译出整个JDK镜像,其他参数还有:hotspot:只编译HotSpot虚拟机 hotspot-<variant>:只编译特定模式的Hot Spot虚拟机 docs-image:产生JDK的文档镜像 test-image:产生JDK的测试镜像 all-images:相当于连续调用product、docs、test三个编译目标 bootcycle-images:编译两次JDK,其中第二次使用第一次的编译结果作为Bootstrap JDK clean:清理make命令产生的临时文件 dist-clean:清理make和configure命令产生的临时文件编译后产生的JDK路径:build/配置名称/jdk,把它复制到JAVA_HOME目录,就可以作为一个完整的JDK来使用。重新编译前先执行make clean和makedist-clean命令清理目录。
2017年12月03日
1,306 阅读
43 点赞
2014-01-04
“锁”相关总结
synchronized 1.JDK 6之后,synchronized目前锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。偏向锁,一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。轻量级锁,是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。重量级锁,若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。重量级锁,除了拥有锁的线程以外的线程都阻塞。{lamp/}ReentrantLock Synchronized,Java虚拟机完成,不公平锁,ReentrantLock和synchronized都是可重入锁。ReentrantLock,类层面的实现,户自己去操作,可以指定锁是公平的还是非公平的。NonReentrantLock,不可重入锁。ReentrantLock更灵活,在Hashtable和ConcurrentHashMap中体现得淋漓尽致synchronized的话,锁的范围是整个方法或synchronized块部分;而Lock因为是方法调用,可以跨方法,灵活性更大。和synchronized相比,ReentrantLock提供给用户多种方法用于锁信息的获取,比如可以知道lock是否被当前线程获取、lock被同一个线程调用了几次、lock是否被任意线程获取等等。ReentrantLock的某些方法可以决定多长时间内尝试获取锁,如果获取不到就抛异常,这样就可以一定程度上减轻死锁的可能性,如果锁被另一个线程占据了,synchronized只会一直等待,很容易错序死锁。之后,synchronized做了诸多优化,效率上synchronized和ReentrantLock应该是差不多。{lamp/}公平锁 VS 非公平锁 公平锁:线程直接进入队列中排队,等待队列中除第一个线程以外的所有线程都会阻塞,优点是等待锁的线程不会饿死,缺点是整体吞吐效率相对非公平锁要低。非公平锁:如果此时锁刚好可用,线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程,缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。{lamp/}乐观锁 VS 悲观锁 {lamp/}自旋锁 VS 适应性自旋锁 自旋锁:自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。适应性自旋锁:自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。{lamp/}可重入锁 VS 非可重入锁 可重入锁,又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。{lamp/}独享锁 VS 共享锁 ReentrantLock和ReentrantReadWriteLock{lamp/}volatile 当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量强制刷新到主内存中去,导致其他线程中的缓存无效。volatile是一种轻量级的同步机制,一是保证共享变量对所有线程的可见性;二是禁止指令重排序优化。volatile对于单个的共享变量的读/写具有原子性,但是像num++这种复合操作,volatile无法保证其原子性。可以用num.incrementAndGet()。
2014年01月04日
741 阅读
52 点赞
1
2