redis 缓存雪崩 指缓存中数据大批量到过期时间,而查询数据量巨大,请求都直接访问数据库,引起数据库压力过大甚至down机。 于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如采用一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。 Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群啦。 缓存穿透 :指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。 如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。 如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有有写请求进来的话,需要更新缓存哈,以保证缓存一致性,同时,最后给缓存设置适当的过期时间。(业务上比较常用,简单有效) 使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。 布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,然后查的时候如果特定的这几个位置都为1,那么布隆过滤器判断该key存在。 缓存击穿 指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。 缓存击穿看着有点像,其实它两区别是,缓存雪奔是指数据库压力过大甚至down机,缓存击穿只是大量并发请求到了DB数据库层面。可以认为击穿是缓存雪奔的一个子集吧。有些文章认为它俩区别,是区别在于击穿针对某一热点key缓存,雪奔则是很多key。 1.使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。 2. “永不过期”,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。
超时机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制, 一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽的问题。
服务限流
隔离
原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级 处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看 到系统崩溃。
信号隔离: 信号隔离也可以用于限制并发访问,防止阻塞扩散, 与线程隔离最大不同在于执行依赖代码的线程依然是请求线程(该线程需要通过信号申请, 如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。信号量的大小可以动态调整, 线程池大小不可以。
服务熔断
远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。 现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路 被烧毁。 软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一定阈值,就“跳闸”,断路 器打开——此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状 态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不 成功,断路器继续回到打开状态,过段时间再进入半开状态尝试——通过”跳闸“,应用可以保护自己,而且避免浪费资 源;而通过半开的设计,可实现应用的“自我修复“。 所以,同样的道理,当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源。比如 我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必 要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。
服务降级
有服务熔断,必然要有服务降级。 所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调, 返回一个缺省值。 例如:(备用接口/缓存/mock数据) 。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.1</version> </dependency>
第二步配置yml
server: port: 12004 spring: application: name: nacossentinel
第三步配置controller
@RestController @RequestMapping("/sent-serv") public class InitCtrl { private static final String RESOURCE_NAME_FIND="find"; @RequestMapping("/find") public String find(){ try { //在这里设置一个资源(流量)管理的监听器(名字随便) //人话:就是加个监听器 Entry entry = SphU.entry(RESOURCE_NAME_FIND); return "hello , world"; } catch (BlockException e) { return "我被限流了。。。。"; }catch (Exception e){ return "我的服务要降级了。。。。"; } } //在控制器对象创建时 先创建资源管理监听器的触发规则 //人话: 触发监听器 @PostConstruct private static void init(){ ArrayList<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //填放被管理的资源 rule.setResource(RESOURCE_NAME_FIND); //设置流量规则 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //限制每秒访问次数 rule.setCount(1); rules.add(rule); //讲规则集合存放到规则管理器中 FlowRuleManager.loadRules(rules); } }
第四步启动 测试
http://localhost:12004/sent-serv/find
bean的生命周期 实例化 初始化 服务 销毁 导入pom spring-core spring-context spring-beans 写一个服务类 public class MyService{ priavate String name; } 配置spring.xml <beans> <bean id="ms" class="com.kgc.MyService" init-method="init"></bean> </beans>
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-annotation-aspectj</artifactId> <version>1.8.1</version> </dependency>
@Configuration public class SentinelConfiguration { @Bean public SentinelResourceAspect sentinelResourceAspect(){ return new SentinelResourceAspect(); } }
第三步配置controller
@RestController @RequestMapping("/sent-serv") public class InitCtrl { private static final String RESOURCE_NAME_FIND="find"; // @RequestMapping("/find") // public String find(){ // // try { // //在这里设置一个资源(流量)管理的监听器(名字随便) // //人话:就是加个监听器 // Entry entry = SphU.entry(RESOURCE_NAME_FIND); // return "hello , world"; // } catch (BlockException e) { // return "我被限流了。。。。"; // }catch (Exception e){ // return "我的服务要降级了。。。。"; // } // } @RequestMapping("/find") @SentinelResource(value = "find",blockHandler = "bkHandler") public String find(){ return "Hello World!"; } //异常处理的方法 public String bkHandler(BlockException e){ return "我又被限流了o(╥﹏╥)o"; } //在控制器对象创建时 先创建资源管理监听器的触发规则 //人话: 触发监听器 @PostConstruct private static void init(){ ArrayList<FlowRule> rules = new ArrayList<>(); FlowRule rule = new FlowRule(); //填放被管理的资源 rule.setResource(RESOURCE_NAME_FIND); //设置流量规则 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //限制每秒访问次数 rule.setCount(2); rules.add(rule); //讲规则集合存放到规则管理器中 FlowRuleManager.loadRules(rules); } }
第四步测试