php网站开发实例教程 课件,做网站域名怎么选有利于seo,陕西 餐饮 网站建设,宝华路桥建设集团网站作者 | Mark_MMXI来源 | CSDN博客#xff0c;责编 | 夕颜出品 | CSDN#xff08;ID:CSDNnews#xff09;缓存的存在是为了在高并发情形下#xff0c;缓解DB压力#xff0c;提高业务系统体验。业务系统访问数据#xff0c;先去缓存中进行查询#xff0c;假如缓存存在数据… 作者 | Mark_MMXI来源 | CSDN博客责编 | 夕颜出品 | CSDNID:CSDNnews缓存的存在是为了在高并发情形下缓解DB压力提高业务系统体验。业务系统访问数据先去缓存中进行查询假如缓存存在数据直接返回缓存数据否则就去查询数据库再返回值。Redis是一种缓存工具是一种缓存解决方案但是引入Redis又有可能出现缓存穿透、缓存击穿、缓存雪崩等问题。本文就对缓存雪崩问题进行较深入剖析并通过场景模型加深理解基于场景使用对应的解决方案尝试解决。缓存原理及Redis解决方案首先我们来看一下缓存的工作原理图Redis 本质上是一个 Key-Value 类型的内存数据库。因为是纯内存操作Redis 的性能非常出色每秒可以处理超过 10、万次读写操作。Redis 还有一个优势就是是支持保存多种数据结构例如 String、List、Set、Sorted Set、hash等。缓存雪崩2.1 缓存雪崩解释缓存雪崩的情况是说当某一时刻发生大规模的缓存失效的情况比如你的缓存服务宕机了DB直接负载大量请求压力导致挂掉。2.2 模拟缓存雪崩按照缓存雪崩的解释其实我们要模拟只需要达到以下几个点同一时刻大规模缓存失效。失效的时刻有大量的查询请求冲击DB Testpublic void testQuery(){ExecutorService es Executors.newFixedThreadPool(10);int loop 1000;int init2000;//查询1k个key放进缓存for (int i init; i loopinit; i) {userService.queryById(i);}//缓存过期时间为1s,等待1s同时过期try {Thread.sleep(1000);}catch (Exception e){e.printStackTrace();}//开始了使用多线程疯狂查询for (int i 0; i 100; i) {es.execute(() - {for (int k init; k loopinit; k) {userService.queryById(k);}});}}
为了加快崩坏的速度把数据库的最大连接数调整成5同时增大数据库表的数据量达到百万级别。然后执行测试程序很快程序就报错并停止详细错误如下Exception in thread pool-1-thread-12 org.springframework.data.redis.RedisSystemException: Redis exception; nested exception is io.lettuce.core.RedisException: Connection is closed
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:74)
at org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert(LettuceExceptionConverter.java:41)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate(FallbackExceptionTranslationStrategy.java:42)
at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException(LettuceConnection.java:270)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException(LettuceStringCommands.java:799)
at org.springframework.data.redis.connection.lettuce.LettuceStringCommands.get(LettuceStringCommands.java:68)
at org.springframework.data.redis.connection.DefaultedRedisConnection.get(DefaultedRedisConnection.java:260)
at org.springframework.data.redis.cache.DefaultRedisCacheWriter.lambda$get$1(DefaultRedisCacheWriter.java:109)
at org.springframework.data.redis.cache.DefaultRedisCacheWriter.execute(DefaultRedisCacheWriter.java:242)
at org.springframework.data.redis.cache.DefaultRedisCacheWriter.get(DefaultRedisCacheWriter.java:109)
at org.springframework.data.redis.cache.RedisCache.lookup(RedisCache.java:88)
at org.springframework.cache.support.AbstractValueAdaptingCache.get(AbstractValueAdaptingCache.java:58)
at org.springframework.cache.interceptor.AbstractCacheInvoker.doGet(AbstractCacheInvoker.java:73)
at org.springframework.cache.interceptor.CacheAspectSupport.findInCaches(CacheAspectSupport.java:554)
at org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem(CacheAspectSupport.java:519)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:401)
at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345)
at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)
at com.example.demo.user.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$ba6638d2.queryById(generated)
at com.example.demo.DemoApplicationTests$1.run(DemoApplicationTests.java:55)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: io.lettuce.core.RedisException: Connection is closed
at io.lettuce.core.protocol.DefaultEndpoint.validateWrite(DefaultEndpoint.java:195)
at io.lettuce.core.protocol.DefaultEndpoint.write(DefaultEndpoint.java:137)
at io.lettuce.core.protocol.CommandExpiryWriter.write(CommandExpiryWriter.java:112)
2020-03-08 22:31:14.432 ERROR 37892 --- [eate-1895102622] com.alibaba.druid.pool.DruidDataSource : create connection SQLException, url: jdbc:mysql://localhost:3306/redis_demo?useUnicodetruecharacterEncodingUTF-8allowMultiQueriestrueuseJDBCCompliantTimezoneShifttrueuseLegacyDatetimeCodefalseserverTimezoneUTC, errorCode 1040, state 08004java.sql.SQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: Too many connections
主要问题出在数据库连接已经满了无法获取数据库连接进行查询这个现象是就是缓存雪崩的效果。‘2.3 解决缓存雪崩2.3.1 分析雪崩场景用图来说实际上就是没有了redis这层担着上层流量压力其实从这张图来看对于我们一般的应用客户端去访问应用到数据库的整个链路过程其实在面临大流量的时候我们一般是以倒三角模型进行流量缓冲什么是“倒三角”模型通过倒三角模型按照并发需要优化系统在面临雪崩这种情形可以按照“倒三角”模型进行优化注意雪崩是理论上没办法彻底解决的可能到最终得提高硬件配置。2.3.1 雪崩优化方案经过分析得解决雪崩方案1.随机缓存过期时间能一定程度缓解雪崩
2.使用锁或队列、设置过期标志更新缓存
3.添加本地缓存实现多级缓存
4.添加熔断降级限流缓冲压力
2.3.1.1 随机缓存时间随机缓存时间意在避免大量热点key同时失效。接下来我们基于RedisSpringBootSpringCache基础项目搭建这个项目继续进行实践。由于是使用了SpringCache我们最优的方案就是直接在Cacheable等注解上面加参数比如像表达式之类的让数据放进缓存的时候按照表达式/参数值定义过期时间。因此我们先查看原有的RedisCache是怎么样的put逻辑RedisCacheManager创建Cacheprotected RedisCache createRedisCache(String name, Nullable RedisCacheConfiguration cacheConfig) {return new RedisCache(name, this.cacheWriter, cacheConfig ! null ? cacheConfig : this.defaultCacheConfig);}
打开RedisCache.class查看put 方法如下 public void put(Object key, Nullable Object value) {Object cacheValue this.preProcessCacheValue(value);if (!this.isAllowNullValues() cacheValue null) {throw new IllegalArgumentException(String.format(Cache %s does not allow null values. Avoid storing null via Cacheable(unless\#result null\) or configure RedisCache to allow null via RedisCacheConfiguration., this.name));} else {this.cacheWriter.put(this.name, this.createAndConvertCacheKey(key), this.serializeCacheValue(cacheValue), this.cacheConfig.getTtl());}}
这里this.cacheConfig.getTtl() 就是缓存的过期时间可以看到数据的缓存过期时间是从全局缓存配置里面获取的过期时间配置的而我需要实现的是让某个cache下每个key随机时间过期因此我们需要改动这里 this.cacheConfig.getTtl(),我们在createRedisCache的时候改变这个值就行了。1. 基于java动态执行字符串代码返回过期时间。实现基于Spring.expression的ExpressService/*** title: ExpressUtil* projectName redisdemo* description: 动态执行字符串代码* author lps* date 2020/3/912:01*/
Slf4j
public class ExpressService {private ExpressionParser spelExpressionParser;private ParserContext parserContext;// 表达式解析上下文private StandardEvaluationContext evaluationContext;public static enum ExpressType {/*** ${}表达式格式*/TYPE_FIRST,/*** #{}表达式格式*/TYPE_SECOND}private static final String PRE_TYPE_1 ${;private static final String PRE_TYPE_2 #{;private static final String SUF_STR };private ExpressService(String pre, String suf) {spelExpressionParser new SpelExpressionParser();log.debug(表达式前缀{},表达式后缀{}, pre, suf);evaluationContext new StandardEvaluationContext();// 增加map解析方案evaluationContext.addPropertyAccessor(new MapAccessor());parserContext new TemplateParserContext(pre, suf);}/**** p* 创建表达式处理服务对象 默认为创建#{}格式表达式 通过ExpressType指定表达式格式现有两种${}和#{}* /p*** param type* 表达式格式类型* return 表达式解析对象*/public static ExpressService createExpressService(ExpressType type) {if (type ExpressType.TYPE_FIRST) {log.debug(生成表达式表达式前缀{}, PRE_TYPE_1);return new ExpressService(PRE_TYPE_1, SUF_STR);} else {return new ExpressService(PRE_TYPE_2, SUF_STR);}}public Object expressParse(String express, Object data) throws Exception {log.debug(解析表达式信息{}, express);Expression expression spelExpressionParser.parseExpression(express, this.parserContext);return expression.getValue(evaluationContext, data);}}
测试调用 Testpublic void testExpress(){ExpressService express ExpressService.createExpressService(null);try {//固定超时时间System.out.println(ttlexpress.expressParse(#{60}, null));//调用方法生成随机过期时间System.out.println(ttlexpress.expressParse(#{T(org.apache.commons.lang3.RandomUtils).nextInt(60,200)}, null));} catch (Exception e) {e.printStackTrace();}}
2. 设计name拼接ttl规则由于createRedisCache只有两个参数name以及cacheConfig而只有name是对于单个cache来说的cacheConfig是对于全局cache来说因此我们需要设计name参数中指定cache的name以及过期时间的规则。name赋值规则name|ttlFuneg: Cacheable(cacheNametest|#{T(org.apache.commons.lang3.RandomUtils).nextInt(60,200)})Cacheable(cacheNametest|#{60})
3. 编写解析name代码 /*** 分隔符|*/private static final String SEPERATE_LINE |;public MyRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {super(cacheWriter, defaultCacheConfiguration);}protected RedisCache createRedisCache(String name, Nullable RedisCacheConfiguration cacheConfig) {
// name赋值规则name|ttlFun if(name.contains(SEPERATE_LINE)){String cacheName name.substring(0,name.indexOf(SEPERATE_LINE));String expression name.substring(name.indexOf(SEPERATE_LINE)1);try{ExpressService express ExpressService.createExpressService(null);long ttl Long.parseLong(express.expressParse(expression, null).toString());cacheConfig cacheConfig.entryTtl(Duration.ofSeconds(ttl));return super.createRedisCache(cacheName, cacheConfig);}catch (Exception e){e.printStackTrace();return super.createRedisCache(name, cacheConfig);}}return super.createRedisCache(name, cacheConfig);}
4. 修改CacheConfig将原本的RedisManager替换成#3编写的MyRedisManager /*** 配置缓存管理器*/Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {//关键点spring cache 的注解使用的序列化都从这来没有这个配置的话使用的jdk自己的序列化实际上不影响使用只是打印出来不适合人眼识别RedisCacheConfiguration cacheConfig RedisCacheConfiguration.defaultCacheConfig()// 将 key 序列化成字符串.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))// 将 value 序列化成 json.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))// 设置缓存过期时间单位秒.entryTtl(Duration.ofSeconds(cacheExpireTime))// 不缓存空值.disableCachingNullValues();
/* RedisCacheManager redisCacheManager RedisCacheManager.builder(factory).cacheDefaults(cacheConfig).build();*///修改RedisCacheManager 为MyRedisCacheManager MyRedisCacheManager redisCacheManager new MyRedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(factory), cacheConfig);return redisCacheManager;}
5. 测试编写单元测试 Testpublic void testQueryIdWithExpress(){Assert.assertNotNull(userService.queryById(3333));}
重新定义查询的cache定义 OverrideCacheable( value ca1|#{60},key #id ,unless#result null)
// Cacheable( value ca1|#{T(org.apache.commons.lang3.RandomUtils).nextInt(100,200)},key #id ,unless#result null)public User queryById(int id) {return this.userDao.queryById(id);}
当valueca1|#{60}的时候通过查看Redis的TTL 剩余为58s当valueca1|#{T(org.apache.commons.lang3.RandomUtils).nextInt(100,200)}的时候随机100-220范围内秒数通过查看Redis的TTL 剩余为107s这时候使用random的方式就可以实现随机过期时间了随机数最好选择符合高斯(正态)分布的会比较好。new Random().nextGaussian()
2.3.1.2 互斥锁排队业界比价普遍的一种做法即根据key获取value值为空时锁上从数据库中load数据后再释放锁。若其它线程获取锁失败则等待一段时间后重试。这里要注意分布式环境中要使用分布式锁单机的话用普通的锁synchronized、Lock就够了。这样做思路比较清晰也从一定程度上减轻数据库压力但是锁机制使得逻辑的复杂度增加吞吐量也降低了有点治标不治本。1.使用setnx的方式设置互斥锁 public User queryById(int id) {try {if (redisTemplate.hasKey(id)) {return (User) redisTemplate.opsForValue().get(id);} else {//获取锁if(lock(id)){// 数据库查询User user userDao.queryById(id);redisTemplate.opsForValue().set(id,user, Duration.ofSeconds(3000));//释放锁redisTemplate.delete(LOCK_PREFIX id);}}} catch (Exception e) {e.printStackTrace();}return (User) redisTemplate.opsForValue().get(id);}private static String LOCK_PREFIX prefix;private static long LOCK_EXPIRE 3000;/*** 互斥锁实现*/public boolean lock(String key) {String lock LOCK_PREFIX key;return (Boolean) redisTemplate.execute((RedisCallback) connection - {long expireAt System.currentTimeMillis() LOCK_EXPIRE 1;//SETNXBoolean acquire connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());if (acquire) {return true;} else {byte[] value connection.get(lock.getBytes());if (Objects.nonNull(value) value.length 0) {long expireTime Long.parseLong(new String(value)); //判断锁是否过期 if (expireTime System.currentTimeMillis()) {byte[] oldValue connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() LOCK_EXPIRE 1).getBytes());return Long.parseLong(new String(oldValue)) System.currentTimeMillis();}}}return false;});}
2.3.1.3 设置过期标志更新缓存定时更新缓存阻塞部分请求达到缓冲作用也可以设置key永不过期2.3.1.4 多级缓存这个方案主要在redis宕机或者key在更新进缓存的中间可以响应业务应用减轻压力2.3.1.5 熔断降级限流这个方案是直接在业务应用之上进行请求流量控制减轻下层压力原文链接https://blog.csdn.net/qq_28540443/article/details/104746655同时欢迎所有开发者扫描下方二维码填写《开发者与AI大调研》只需2分钟便可收获价值299元的「AI开发者万人大会」在线直播门票!推荐阅读小网站的容器化(下)网站容器化的各种姿势先跟着撸一波代码再说
你知道吗其实 Oracle 直方图自动统计算法存在这些缺陷附验证步骤
详解以太坊虚拟机EVM的数据存储机制
比特币当赎金WannaRen 勒索病毒二度来袭平台抗住日访问量 7 亿次研发品控流程全公开“手把手撕LeetCode题目扒各种算法套路的裤子”北京四环堵车引发的智能交通大构想从Ngin到Pandownload程序员如何避免面向监狱编程
真香朕在看了