Diagnosing latency issues 诊断延迟问题
源文地址:https://redis.io/docs/reference/optimization/latency/
Finding the causes of slow responses
寻找响应慢的原因。
This document will help you understand what the problem could be if you are experiencing latency problems with Redis.
这个文档将帮你理解Redis延迟问题的原因。
In this context latency is the maximum delay between the time a client issues a command and the time the reply to the command is received by the client. Usually Redis processing time is extremely low, in the sub microsecond range, but there are certain conditions leading to higher latency figures.
时间:
|
阅读:
33 字 ~1分钟
redis性能优化骚操作——绑核 源文地址:https://cloud.tencent.com/developer/news/722546
一、现代CPU模式
现代一个CPU中,可以有多个运行核心(称之为物理核),每个物理核都有自己独立的一级缓存(L1)和二级缓存(L2)。并且每个物理核一般会有两个超线程(称之为逻辑核);同一个物理核下的两个逻辑核同享L1和L2缓存。并且现在机器主流都是多CPU处理器结构(CPU Socket),每个CPU拥有自己的L1和L2以及L3级缓存和自己所管理的内存空间;不同处理器之间通过总线进行连接。一台机器的cpu构造如下图所示:
二、cpu多核对redis的影响
因为我们的cpu存在上下文切换(context switch),每个物理核有自己独立的L1和L2级缓存,程序执行最频繁的指令和数据会被缓存到L1和L2级缓存中,一旦我们的程序被切换到另一个物理核时,对应的指令和数据需要被重新刷到对应的(L1、L2)缓存中,所以频繁进行上下文切换的话,程序就不能很好的利用L1和L2级缓存,就会增加程序的延时性。针对这个频繁切换cpu核进行操作,我们可以采用绑核操作,将一个redis实例只在对应的物理核上执行,不会切换到其他物理核上。
taskset -c 0 ./redis-server 三、多cpu对redis的影响
我们的程序被cpu执行都是通过时间片,一台机器上有多个应用时,某一时刻是有一个程序可以获取cpu去执行程序代码,所以当一个程序在一个CPU socket运行时,如果此时发生了CPU切换,程序被切换到另一个CPU socket上运行,此时程序在运行时读取内存数据时,需要读取到之前的cpu socket所管理的内存,这种访问属于远端内存访问。这种访问 Socket 直接连接的内存相比,远端内存访问会增加应用程序的延迟。
所以多cpu对redis的影响:
因为L1,L2缓存中的指令和数据可以提高访问速度,一旦发生了cpu的切换,同样的指令和数据需要重新加载到L1和L2缓存中,影响redis的性能。
因为每个cpu都有自身控制的内存,一旦发生cpu的切换,就会存在一个cpu需要访问另一个cpu所管理的内存,出现远端内存的访问情况,影响redis的响应时间。
首先我们的redis实例需要很好的网络性能,redis是如何和操作系统的网络中断程序进行交互的呢?
网络中断程序从网卡中读取数据,并将数据写入到操作系统的内核缓冲区中,内核会通过epoll机制触发事件,通知到redis实例,redis就会从内核中拉取数据,复制到自己的内存空间中。这个时候就会存在一个潜在影响性能的点,到redis实例和网络中断处理程序,在不同的CPU Socket中运行时,redis读取网络数据时,就需要跨CPU Socket运行,影响其性能,其流程如下:
所以为了避免redis跨cpu访问网络数据,需要将redis实例和网络中断程序,绑定在同一个CPU Socket中:
四、绑核存在的风险点
当我们将redis实例绑在一个cpu核上时,redis其后台线程和fork的子进程等都会和redis主线程抢占cpu资源,导致redis主线程变慢,影响性能。
针对这个问题:主要的解决方法有两种:
一个就是redis实例对应绑一个物理核,因为我们的cpu一个物理核有两个逻辑核,这样可以把我们的两个逻辑核都给用上,可以在一定程度上缓解cpu的资源竞争。
修改redis源代码,把子进程和后台线程绑到不同的CPU核上。
时间:
|
阅读:
20 字 ~1分钟
Redis性能压测实战 源文地址:https://segmentfault.com/a/1190000042301957
既然准备压测Redis,肯定要准备压测机器,Redis服务器,这里还走了不少弯路,大致情况如下:
Redis单点压测 1.三台Jmeter压5-10台Java服务器,压测后面一台单点的Redis 刚开始压测的时候想着模拟常规用户调用场景于是写了一个简单的Java服务,并且集群式部署,它们连接到一台Redis服务器,见下图:
当然压之前也查看了Redis官网,以及用Redis-bechmark试过Redis极限性能到10万的GPS左右,但是用这样的方式压测根本压不到瓶颈,哪怕把Java服务器与Redis的连接数调大。不停地加Java服务机器,却怎么压不出Redis的极限,此时可以感受到Redis性能的强悍。虽然有官方数据,但是经过这么多机器的压测仍然达不到它的瓶颈,大概知道是压测方式出问题了。
单Java服务器压单台Redis 换另一个方案,Java服务器,写接口多线程压测Redis,性能爆炸,压测数据如下:
这个数据巅峰数值达到了11万,比传言的还要高。
Redis-cluster压测 压完单点Redis服务之后,信心暴增,在求了运维之后,又加了了一些机器,利用6台Linux服务器搭建6主6从的Redis-cluster集群,如下图所示:
于是立马代码,着手压测,压测结果却不尽人意,下面请看数据:
分析了下上面的压测数据,Redis机器添加了6倍,性能突破1倍都没有,肯定是哪里出了问题,于是继续分析代码,原来使用JedisPoll的时候,每次使用完一个链接,又得丢给池子,性能可能损耗在这里,于是立马改代码,将线程不用再丢弃,每个压测线程使用一个Redis的链接,得到的数据非常爆炸。
JedisPool设置了100个链接,且用100个线程同时请求。
又设置了1000个连接
由此发现连接数越多也不是最好的,反而会导致性能下降,这个就是我们平时所谓的性能调优。
总结 1.Redis的单机性能达到10万QPS,集群的性能损失也不是很多,6节点的集群我们压到了50多万,只要合理使用Redis,Redis的性能远远够用。
2.Java服务器与Redis的连接数设置不是越多越好,一定是存在一个性能最好的中间值,这个需要不停的压测与调优才能得到。
时间:
|
阅读:
51 字 ~1分钟
redis进程绑定指定的CPU核 源文地址:https://blog.csdn.net/youlinhuanyan/article/details/99671878
1)查看某服务的pid
$ ps -aux|grep redis 1)显示进程运行的CPU
#命令 $ taskset -p 21184 显示结果:
pid 21184’s current affinity mask: ffffff 注:21184是redis-server运行的pid 显示结果的ffffff实际上是二进制24个低位均为1的bitmask,每一个1对应于1个CPU,表示该进程在24个CPU上运行
2)指定进程运行在某个特定的CPU上
#命令 $ taskset -pc 3 21184 显示结果:
pid 21184’s current affinity list: 0-23 pid 21184’s new affinity list: 3 注:3表示CPU将只会运行在第4个CPU上(从0开始计数)。
3)进程启动时指定CPU
//命令 $ taskset -c 1 ./redis-server .
时间:
|
阅读:
157 字 ~1分钟
在一个阳光明媚的下午,突然生产环境有一个缓存实例发出内存使用率超过90%的告警,然后立刻和小伙伴们一起看是什么情况。
现象是这样的,集群里的一个实例的内存使用率超过了90%,而这个实例的从节点,内存使用率却很低。而且其他分片的内存使用率都很低,只有这个分片高。见下图cachecloud实例状态图。
首先想到的肯定是大key导致的集群倾斜了,于是就先把这个分片的数据dump出来,然后使用rdbtools工具分析下dump文件,把大key找出来,再联系业务人员。
在做rdb分析的同时,也考虑到,会不会是有人偷偷连接了实例,然后使用了monitor忘记关掉,于是另外登录到该实例所在的服务器并连接实例后使用client list命令查看客户端的连接,看是否有带monitor相关命令的。发现并没有。
~]$ redis-cli -p 9999 client list | grep monitor ~]$ 另一方面使用连接实例使用bgsave命令生成dump数据文件的同事也发现,生成的数据文件的大小只有130多M,分析虽然也分析出来了一个大key,但是大小也只有130多M,是一个拥有360多万个元素的List。
但是这应该完全不足以造成9个G的内存使用量。我还以为是bgsave命令没正常执行?我又重新执行了一次bgsave命令,查看执行时间和dump数据确认确实是130多M。使用info命令查看实例的信息,也看不出问题。
一筹莫展之际,同事亮哥从实例的client list命令的结果看出了所以然。
]$ redis-cli -p 9012 client list | grep -v "omem=0" id=11164727 addr=10.4.7.48:50478 fd=458 name= age=4230 idle=303 flags=c db=0 sub=0 psub=0 multi=-1 qbuf=163072064 qbuf-free=0 obl=0 oll=659943 omem=8546641352 events=rw cmd=lrange 这个被筛选出来的客户端链接,omem怎么有8个G。omem是什么值。它是redis服务对客户端连接输出缓存的内存占用量。
查看redis实例的慢日志,可以看到如下的结果,相同List key的lrange查询,参数是0 -1。
经过思考,大概可以这样理解,使用这个缓存的实例有一个List类型的大key,有360多万个元素,大概是130多M,然后客户端频繁的通过 lrange keyname 0 -1的方式全量获取所有元素。缓存服务端接收到指令,耗时700000多微秒(700多毫秒)把对应的数据给扔到输出缓冲区中,然后接着处理别的指令去了,然后因为这个指令调用的很频繁,输出缓冲区的数据就越积越多,但客户端接收数据的速度有限。形象点可以理解为一个池子一边接水一边放水,放水速度小于接水速度,接水的就是Redis服务处理指令获取到的结果,放水的就是客户端接收返回结果,这个池子就是输出缓存区,也是要占用Redis实例的内存的,时间足够,池子迟早会满。这也就是Redis实例内存不断增长的原因。
因为Redis实例内的数据的数量并没有改变,所以该实例的Slave实例的内存并没有变化。
回过头来看,如果master实例的内存暴涨,但是slave实例的内存与master实例的内存差距很大的情况下,就很可能是monitor命令未关或者输出缓冲区积压导致的。
最终我们手动将对应的客户端连接kill掉,然后内存唰的一下就降下来了。
~]$ redis-cli -p 9012 client list | grep -v "omem=0" id=2253936 addr=10.
时间:
|
阅读:
19 字 ~1分钟
缓存击穿:是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库
缓存雪崩:缓存雪崩是因为大面积的缓存失效,打崩了DB。
缓存穿透:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。
Redis雪崩效应的解决方案 分布式锁(本地锁) 对数据库服务请求进行限制,使用锁机制,保证只有一个线程进行数据库的操作访问,多的直接排队等待,(单本版本可以用本地锁ReentrantLock,集群服务使用分布式锁),可以解决雪崩效应,但会影响吞量。
使用消息中间方式 通过消息中间件让访问排队,加载缓存,只要不让并发一下子全部打在数据库上面,就好办,效果是第一次访问返回空数据,刷新或刷新几次后展示数据
使用数据库的二级缓存,使用ehcache实现二级缓存
均摊分配redis key的失效时间,只要不要集中在一点就好,以几分钟的随机数让缓存错峰失效
多缓存并用,可以用多个缓存, 如redis缓存和memcache缓存并用,或者部署两套redis集群。
通过限流等手段
缓存穿透的解决方案 定义参数规则,如果参数不符合规则直接抛异常,常见的如对外提供的接口,参数应该是大于0的,如果一直传小于等于0的参数,直接返回错误 数据库中查不到数据的时候,向缓存放一个空对象,再从缓存中获取数据,获取的就是空对象从而不继续走库 缓存击穿的解决方案 限流
时间:
|
阅读:
700 字 ~4分钟
springboot event线程池总结 事件是达到解藕目的的手段之一。
最近项目出了一个线上故障,就是因事件引起的,有必要进行一次对事件知识点的梳理
现在公司都是使用的spring全家桶,所以技术面都会在spring boot,这方面我也是新手了,学习与总结并进,新旧知识连贯
此文包含几个知识点
事件实现原理 事件使用的几种方式事件,达到解藕目的的手段之一 最近项目出了一个线上故障,就是因事件引起的,有必要进行一次对事件知识点的梳理
事件是引子,背后还是线程池的知识点
说来也搞笑,在面试时,几轮面试官都问了线程池问题,说明此公司肯定有过线程池的事故,要么大家统一对线程池有了高度认知,却不然遇到的第一个故障就是线程池引起的
可见面试与实现差距有多大
现在公司都是使用的spring全家桶,所以技术面都会在spring boot,这方面我也是新手了,学习与总结并进,新旧知识连贯
此文包含几个知识点
事件实现原理 事件使用的几种方式 异步事件 故障始末 原理 事件实现原理其实就是个观察者模式,所以也没什么好说的
实现方式 此处实现都以spring boot为基础,约定大于配置,所以在spring boot项目中,配置文件大大减少,相对以前写大量代码、配置文件还真是不习惯,感觉苦日子过惯了,还真是不习惯,感觉心里不踏实
达到一个事件,需要三件东西:event、publisher、listener
而在spring boot中,使用几个注解就可以
注解方式 最常见最普通的事件
事件 一个简单的类
@Data public class DemoEvent { public DemoEvent(String data){ this.eventData = data; } private String eventData; } 监听者 一个简单的bean,再加上一个@EventListener,还有所在方法的参数是所需要监听的事件
@Component @Slf4j public class DemoEventListener { @EventListener public void demoEventListener(DemoEvent demoEvent){ log.info("demoevent listener,eventValue:{},order=2",demoEvent.getEventData()); } } 发布者 spring已经有了现成的ApplicationEventPublisher,而ApplicationContext就是实现者,因此只要是spring容器,都会有个context
时间:
|
阅读:
540 字 ~3分钟
SpringBoot 启动扩展点 源文地址:https://mp.weixin.qq.com/s/4Ggb0QfvhcHEAe3F4gC2YQ
1.背景 Spring的核心思想就是容器,当容器refresh的时候,外部看上去风平浪静,其实内部则是一片惊涛骇浪,汪洋一片。Springboot更是封装了Spring,遵循约定大于配置,加上自动装配的机制。很多时候我们只要引用了一个依赖,几乎是零配置就能完成一个功能的装配。
我非常喜欢这种自动装配的机制,所以在自己开发中间件和公共依赖工具的时候也会用到这个特性。让使用者以最小的代价接入。想要把自动装配玩的转,就必须要了解spring对于bean的构造生命周期以及各个扩展接口。当然了解了bean的各个生命周期也能促进我们加深对spring的理解。业务代码也能合理利用这些扩展点写出更加漂亮的代码。
在网上搜索spring扩展点,发现很少有博文说的很全的,只有一些常用的扩展点的说明。
所以在这篇文章里,我总结了几乎Spring & Springboot所有的扩展接口,以及各个扩展点的使用场景。并且整理出了一个bean在spring内部从被加载到最后初始化完成所有可扩展点的顺序调用图。从而我们也能窥探到bean是如何一步步加载到spring容器中的。
2.可扩展的接口启动调用顺序图 以下是我整理的spring容器中Bean的生命周期内所有可扩展的点的调用顺序,下面会一个个分析
3.ApplicationContextInitializer org.springframework.context.ApplicationContextInitializer
这是整个spring容器在刷新之前初始化ConfigurableApplicationContext的回调接口,简单来说,就是在容器刷新之前调用此类的initialize方法。这个点允许被用户自己扩展。用户可以在整个spring容器还没被初始化之前做一些事情。
可以想到的场景可能为,在最开始激活一些配置,或者利用这时候class还没被类加载器加载的时机,进行动态字节码注入等操作。
扩展方式为:
public class TestApplicationContextInitializer implements ApplicationContextInitializer { @Override public void initialize(ConfigurableApplicationContext applicationContext) { System.out.println("[ApplicationContextInitializer]"); } } 因为这时候spring容器还没被初始化,所以想要自己的扩展的生效,有以下三种方式:
在启动类中用springApplication.addInitializers(new TestApplicationContextInitializer())语句加入 配置文件配置context.initializer.classes=com.example.demo.TestApplicationContextInitializer Spring SPI扩展,在spring.factories中加入org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializer 4.BeanDefinitionRegistryPostProcessor org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
这个接口在读取项目中的beanDefinition之后执行,提供一个补充的扩展点
使用场景:你可以在这里动态注册自己的beanDefinition,可以加载classpath之外的bean
扩展方式为:
public class TestBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { System.out.println("[BeanDefinitionRegistryPostProcessor] postProcessBeanDefinitionRegistry"); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { System.