前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring的缓存(cache)-(缓存穿透、缓存击穿、缓存雪崩、热点数据)

spring的缓存(cache)-(缓存穿透、缓存击穿、缓存雪崩、热点数据)

作者头像
逍遥壮士
发布2020-09-18 11:41:57
2.2K0
发布2020-09-18 11:41:57
举报
文章被收录于专栏:技术趋势技术趋势

注:本文篇幅有点长,所以建议各位下载源码学习。(如需要请收藏!转载请声明来源,谢谢!)

代码下载:https://gitee.com/hong99/spring/issues/I1N1DF

背景

继上文《spring的缓存(cache)-分布式缓存》;

关于jmeter的配置

jmeter是Apacher应用程序是开源软件,100%纯Java应用而设计的负载测试功能行为和测量性能。它最初是为测试Web应用程序而设计的,但此后已扩展到其他测试功能。

官网:https://jmeter.apache.org/

使用:https://jmeter.apache.org/usermanual/index.html

由缓存引发相关的问题?

分布式缓存,非常高效的提升了系统性能,但是可能引发以下的问题。

什么是热点数据(或者说热key)

突然间有几十甚至几百万的数据同时去请求同一个redis的key,导致流量非常集中,带宽上限,最后导致这台redis服务器宕机。

发现热Key?

1.通过业务手段,平常一些活动提前预知哪些Key可能成为热key;

2.通过收集方式,比如aop+agent监控的形收集客户端和数据层数据进行预知;

3.像一些hash大key可以分拆出来,不要放到全部一个key中可以拆成不同的小Key;

解决方法:

1.重要的key不设过期,要不然可能导致缓存击穿直接打到db去了...

2.建立多级缓,比如分布式用reids,本地用guava来做,加快速度,并且设值对应LFU策略;

3.必要的时候一些无用的服务可以降级和做一些相应限流;(可以参考以下)

参考文章:

https://help.aliyun.com/document_detail/101108.html

总结:redis热key其实只要购买了阿里云或者其他云的服务基本上在面版上面都可以看到相应的参数指标,对于这种突然间几十上千万的请求热key,一般最好是结合业务,在一些经常出现的环节可以加上短信或者监控告警,有利于及时处理反馈。也可以结合下面的限流、服务降级之类的来做。

什么是缓存穿透

查询根据不存在的数据,导致每次都查库,并且qps达到万甚至百万,直接将数据库拉挂了。

模拟缓存穿透

通过jmeter压1万个用户,60秒请求。

com.hong.spring.controller.UserController#findById

/**
 *
 * 功能描述:通过id查询
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/31 17:29
 */
@RequestMapping("findById/{id}")
public DataResponse<User> findById(@PathVariable("id")Integer id){
    if(null==id){
        return DataResponse.BuildFailResponse("参数不能为空!");
    }
    try {
        //定义一个拥有20个并发的线程池对象
        ExecutorService service = Executors.newFixedThreadPool(20);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                userService.findById(id);
            }
        };
        for (int i = 0; i < 10000; i++) {
            //提交线程,并并发执行
            service.submit(runnable);
        }
        return userService.findById(id);
    }catch (Exception e){
        log.error("findById->查询失败{}",e);
        return DataResponse.BuildFailResponse("查询出错请重试!");
    }
}

jmeter配置

结果

进程卡死

找不到...

redis直接挂了..

然后崩了

CPU

解决方案

1.设置空值缓存;

注:当新增该数据的时候需要将原来的id,delete掉再放进去,刷新一下缓存否则会导致缓存数据与数据库不一致场景。

redisCacheManager.set("user_"+id, JSONObject.toJSONString(user));

2.通过nginx配置单秒ip请求次数;

参考:https://www.cnblogs.com/aoniboy/p/4730354.html

https://www.cnblogs.com/my8100/p/8057804.html

3.通过bloomfilter(布隆过滤器)来实现;

com.hong.spring.controller.UserController

// 创建布隆过滤器对象
BloomFilter filter = BloomFilter.create(
        Funnels.integerFunnel(),
        1500,
        0.01);
{
    for(int i=0;i<44;i++){
        filter.put(i);
    }
}
/**
 *
 * 功能描述:通过id查询
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/8/31 17:29
 */
@RequestMapping("findById2/{id}")
public DataResponse<User> findById2(@PathVariable("id")Integer id){
    if(null==id){
        return DataResponse.BuildFailResponse("参数不能为空!");
    }
    try {
        // 判断指定元素是否存在
        log.info("是否包含该id:"+filter.mightContain(id));
        // 将元素添加进布隆过滤器
        if(!filter.mightContain(id)){
            return DataResponse.BuildFailResponse("该数据不存在");
        }
        return userService.findById(id);
    }catch (Exception e){
        log.error("findById->查询失败{}",e);
        return DataResponse.BuildFailResponse("查询出错请重试!");
    }
}

结果

jmeter配置

jmeter全部成功

总结:布隆过滤器基于概率的数据结构,它的时间和空间都远远超过一般的算法,性能非常高,可以非常有效的防止缓存击穿,当然本文用的是guava也有redis及其他实现,可以通过该思路发散,也可以私聊本人。

布隆过滤器的缺点:

误识别率:也就是说可能存在但匹配不到或者不存在匹配到了。

删除困难:这个删除相当麻烦;

参考文章:

https://juejin.im/post/6844903832577654797

https://blog.csdn.net/Revivedsun/article/details/94992323

https://bbs.huaweicloud.com/blogs/136683

什么是缓存击穿

在高并发的情况下,大量的请求同时查询同一个key,刚好这个key失效导致,全部的请求都打到数据库中去了,导致服务挂了,这种称缓存击穿。

模拟缓存击穿

com.hong.spring.service.IUserService#findById2

/**
 *
 * 功能描述:通过id查询(缓存击穿)
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/9/2 14:40
 */
DataResponse<User> findById2(Integer id);

com.hong.spring.service.impl.UserServiceImpl#findById2

@Override
public DataResponse <User> findById2(Integer id) {
    //log.info("缓存击穿,进入数据库查询");
    if(null==id){
        return DataResponse.BuildFailResponse("必传参数不能为空!");
    }
    User user=null;
    if(redisCacheManager.hasKey("user2_"+id)){
        log.info("查询缓存有值");
        String userStr = (String)redisCacheManager.get("user2_" + id);
        if(null!=userStr && !StringUtils.isEmpty(userStr)){
            user = JSONObject.parseObject(userStr, User.class);
        }
    }

    if(null==user){
        log.info("查询数据库!");
        user = userMapper.findById(id);
        if(null!=user){
            redisCacheManager.set("user2_"+id, JSONObject.toJSONString(user),3);
        }
    }

    return DataResponse.BuildSuccessResponse(user);
}

junit测试

com.hong.spring.service.UserServiceTest

// 请求总数
public static int clientTotal = 50000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static int count = 0;

com.hong.spring.service.UserServiceTest#findByUser2

/**
 *
 * 功能描述:模拟缓存击穿
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/9/2 14:45
 */
@Test
public void findByUser2() throws InterruptedException {
    //缓存数据
    userService.findById2(2);

    ExecutorService executorService = Executors.newCachedThreadPool();
    //信号量,此处用于控制并发的线程数
    final Semaphore semaphore = new Semaphore(threadTotal);
    //闭锁,可实现计数器递减
    final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    for (int i = 0; i < clientTotal ; i++) {
        executorService.execute(() -> {
            try {
                userService.findById2(2);
                //执行此方法用于获取执行许可,当总计未释放的许可数不超过200时,
                //允许通行,否则线程阻塞等待,直到获取到许可。
                semaphore.acquire();
                add();
                //释放许可
                semaphore.release();
            } catch (Exception e) {
                //log.error("exception", e);
                e.printStackTrace();
            }
            //闭锁减一
            countDownLatch.countDown();
        });
    }
    countDownLatch.await();//线程阻塞,直到闭锁值为0时,阻塞才释放,继续往下执行
    executorService.shutdown();
    log.info("count:{}", count);
}

private static void add() {
    count++;
}

结果:可以发现服务,瞬间挂掉了....

通过日志发现,第一次查询是正常的,然后放到缓存中

然后缓存过期瞬间所有的请求都打到db中...(非常恐怖)正常的db坑住3000~5000的请求,但是我这个设置了5万...

jmeter模拟测试

配置(5秒钟5万次)

结果

刚开始还没啥问题,后面数据库就直接查询异常了,超时,超过连接数之类的都出现了...

解决方案

1.关键的key不设过期时间(通过功能删除或更新);

2.添加本地缓存(需要考虑一致性问题),当redis失效直接通过本地缓存先坑一波..;

可以参考:spring的缓存(cache)-本地

2.添加锁,分布式考虑redis的setnx分布式锁,而单机可以直接用普通的锁如:(synchronized、Lock)

采用redis分布式锁(手动实现)

com.hong.spring.service.impl.UserServiceImpl 完善代码添加锁

@Override
public DataResponse <User> findById2(Integer id) {
    //log.info("缓存击穿,进入数据库查询");
    if(null==id){
        return DataResponse.BuildFailResponse("必传参数不能为空!");
    }
    String name = Thread.currentThread().getName();
    String key  = "user2_" + id;
    String lockName=key+".lock";
    Boolean lock = redisCacheManager.setNx(lockName, lockName);
    int count=0;
    log.info("线程{}锁状态{}",name,lock);
    User user=null;
    try {
        //睡上100毫秒
        while (!lock && count<10){
            Thread.sleep(100);
            count++;
            log.info("线程{}第{}次获取锁",name,count);
            lock =redisCacheManager.setNx(lockName,lockName);
        }
        if(!lock && count==10){
            log.info("请求超过10次,做业务处理....");
            return DataResponse.BuildFailResponse("请求频繁,请重试!");
        }
        if(redisCacheManager.hasKey("user2_"+id)){
            log.info("线程{}查询缓存有值",name);
            String userStr = (String)redisCacheManager.get("user2_" + id);
            if(null!=userStr && !StringUtils.isEmpty(userStr)){
                user = JSONObject.parseObject(userStr, User.class);
            }
        }
        if(null==user){
            log.info("线程{}开始锁!开始查询数据库",name);
            user = userMapper.findById(id);
            if(null!=user){
                log.info("线程{}将值放到缓存中",name);
                redisCacheManager.set("user2_"+id, JSONObject.toJSONString(user),3);
            }
        }
    }catch (Exception e){
        log.error("异常{}",e);
    }finally {
        log.info("线程{}解锁成功!",name);
        redisCacheManager.unLock(lockName);
    }
    return DataResponse.BuildSuccessResponse(user);
}

jmeter配置(1秒钟请求10次)

结果

通过结果可得,只查一次数据库其他的都从缓存中获取

18:33:28.492 [http-nio-8081-exec-6] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-6锁状态true
18:33:28.492 [http-nio-8081-exec-4] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-4锁状态false
18:33:28.497 [http-nio-8081-exec-6] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-6开始锁!开始查询数据库
18:33:28.564 [http-nio-8081-exec-5] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-5锁状态false
18:33:28.576 [http-nio-8081-exec-6] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-6将值放到缓存中
18:33:28.593 [http-nio-8081-exec-4] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-4第1次获取锁
18:33:28.603 [http-nio-8081-exec-6] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-6解锁成功!
18:33:28.664 [http-nio-8081-exec-5] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-5第1次获取锁
18:33:28.665 [http-nio-8081-exec-5] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-5查询缓存有值
18:33:28.676 [http-nio-8081-exec-7] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-7锁状态false
18:33:28.684 [http-nio-8081-exec-5] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-5解锁成功!
18:33:28.694 [http-nio-8081-exec-4] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-4第2次获取锁
18:33:28.695 [http-nio-8081-exec-4] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-4查询缓存有值
18:33:28.695 [http-nio-8081-exec-4] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-4解锁成功!
18:33:28.777 [http-nio-8081-exec-7] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-7第1次获取锁
18:33:28.778 [http-nio-8081-exec-7] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-7查询缓存有值
18:33:28.779 [http-nio-8081-exec-7] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-7解锁成功!
18:33:28.786 [http-nio-8081-exec-8] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-8锁状态true
18:33:28.787 [http-nio-8081-exec-8] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-8查询缓存有值
18:33:28.787 [http-nio-8081-exec-8] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-8解锁成功!
18:33:28.898 [http-nio-8081-exec-9] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-9锁状态true
18:33:28.898 [http-nio-8081-exec-9] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-9查询缓存有值
18:33:28.899 [http-nio-8081-exec-9] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-9解锁成功!
18:33:29.010 [http-nio-8081-exec-10] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-10锁状态true
18:33:29.011 [http-nio-8081-exec-10] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-10查询缓存有值
18:33:29.012 [http-nio-8081-exec-10] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-10解锁成功!
18:33:29.119 [http-nio-8081-exec-1] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-1锁状态true
18:33:29.119 [http-nio-8081-exec-1] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-1查询缓存有值
18:33:29.120 [http-nio-8081-exec-1] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-1解锁成功!
18:33:29.231 [http-nio-8081-exec-2] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-2锁状态true
18:33:29.231 [http-nio-8081-exec-2] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-2查询缓存有值
18:33:29.232 [http-nio-8081-exec-2] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-2解锁成功!
18:33:29.340 [http-nio-8081-exec-3] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-3锁状态true
18:33:29.341 [http-nio-8081-exec-3] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-3查询缓存有值
18:33:29.342 [http-nio-8081-exec-3] INFO  com.hong.spring.service.impl.UserServiceImpl - 线程http-nio-8081-exec-3解锁成功!

总结:缓存击穿基本可以说很多时候都是没有考虑清楚具体的过期时间,导致刚好此刻用户量非常大的场景刚好失效了,全部的流量都打到db中去了,严重可能直接搞挂db,所以建议是在使用缓存的时候需要考虑并发场景,关键场景统一加上锁。

参考文章:

本地锁解决方案:https://www.jianshu.com/p/87896241343c

分布式锁:

https://jinzhihong.github.io/2019/08/12/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BA-Redis-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E7%8E%B0-%E4%B8%80/

什么是缓存雪崩

当一时刻发生大规模的缓存失效的情况,也就是同一刻大批量的key同时到期,导致所有请求都转到db,db瞬间被压垮。

模拟缓存雪崩

代码实现

新增 com.hong.spring.service.IUserService#findById3

/**
 *
 * 功能描述:通过id查询(缓存雪崩)
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/9/3 10:49
 */
DataResponse<User> findById3(Integer id);
新增 com.hong.spring.service.impl.UserServiceImpl#findById3
@Override
public DataResponse <User> findById3(Integer id) {
    if(null==id){
        return DataResponse.BuildFailResponse("必传参数不能为空!");
    }

    User user;
    if(redisCacheManager.hasKey("user_" + id)){
        String userStr = (String)redisCacheManager.get("user_" + id);
        if(null!=userStr && !StringUtils.isEmpty(userStr)){
            user = JSONObject.parseObject(userStr, User.class);
        }
    }

    user = userMapper.findById(id);
    if(null!=user){
        redisCacheManager.set("user_"+id, JSONObject.toJSONString(user),5000);
    }

    return DataResponse.BuildSuccessResponse(user);
}

junit测试:

com.hong.spring.service.UserServiceTest#findByUser3

@Test
public void findByUser3() throws InterruptedException {
    //缓存数据
    userService.findById3(10);
    userService.findById3(11);

    ExecutorService executorService = Executors.newCachedThreadPool();
    //信号量,此处用于控制并发的线程数
    final Semaphore semaphore = new Semaphore(threadTotal);
    //闭锁,可实现计数器递减
    final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
    for (int i = 0; i < clientTotal ; i++) {
        executorService.execute(() -> {
            try {
                userService.findById3(10);
                userService.findById3(11);
                //执行此方法用于获取执行许可,当总计未释放的许可数不超过200时,
                //允许通行,否则线程阻塞等待,直到获取到许可。
                semaphore.acquire();
                add();
                //释放许可
                semaphore.release();
            } catch (Exception e) {
                //log.error("exception", e);
                e.printStackTrace();
            }
            //闭锁减一
            countDownLatch.countDown();
        });
    }
    countDownLatch.await();//线程阻塞,直到闭锁值为0时,阻塞才释放,继续往下执行
    executorService.shutdown();
    log.info("count:{}", count);
}

结果

redis超时

db直接搞挂

配置jmeter压测模拟

数据库配置(记得要配回来或重启哈~)

set global max_connections=3;

结果

请求转半天,不可用状态

tomcat开始报500,这里候的服务相当于服务挂了

压了半天发现,虽然db没有挂掉,但是正个服务都已经严重不可用状态了....如果在线上就....

mysql挂了

解决方案

1.避免同一时刻过期大量的key,在过期时间上尽量通过随机时间,尽量相隔几分钟以上;

Integer random = Integer.valueOf(RandomStringUtils.randomNumeric(2));
redisCacheManager.set("user_"+id, JSONObject.toJSONString(user),random);

2.熔断降级限流;

降级:一般大型像大型公司,淘宝京东这类,大促都是有对应降级,比如售后或者把一些没闭要的隐藏掉,减少流量,当然在业务层面设计上就需要考虑了;

限流:分为两种nginxsetinal

nginx:演示

下载:http://nginx.org/en/download.html

基础教程:https://www.runoob.com/w3cnote/nginx-setup-intro.html

官网:http://nginx.org/

ng配置(将81和82服务启用然后通过nginx去代理转发,用80端口,限制每秒只能允许一个通过,如果超过就直接转到提示语!)

#user  nobody;
worker_processes  1;


events {
    worker_connections  1024;
}


http {
  # 每秒不同超过1次
  limit_req_zone $binary_remote_addr zone=allips:10m rate=1r/s;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
  upstream tomcat_cluster{
     server localhost:8081;
     server localhost:8082;
  }

    server {
        listen       80;
        server_name  localhost;
        location / {
      #limit_conn one 1;
            limit_req zone=allips burst=1 nodelay;
            proxy_pass http://tomcat_cluster; 
            #设置代理
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
      #proxy_connect_timeout 1s;
            #proxy_read_timeout 36000s;
            #proxy_send_timeout 36000s; 
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    error_page 500 502 503 504 /503;
    location = /503 {
      default_type application/json;    
      add_header Content-Type 'text/html; charset=utf-8';
      return 200 '{"code":0,"msg":"访问高峰期请重试..."}';
        }
    location /status {  
      stub_status on;     #表示开启stubStatus的工作状态统计功能。
      access_log on;     #access_log off; 关闭access_log 日志记录功能。
      #auth_basic "status";                 #auth_basic 是nginx的一种认证机制。
      #auth_basic_user_file conf/htpasswd;  #用来指定密码文件的位置。
    }
    }

}

请求:http://localhost/

{"code":0,"msg":"访问高峰期请重试..."}

nginx总结:nginx很容易就处理了这种拦截,当用户每秒以超过请求次数就直接打回,这样有效的拦截恶意请求,当然还可以配置成当达到多少次后直接给加入黑名单,nginx通常作为第一层过滤一些ddos攻击这类,当然也可以配置成集群,这里就不试了,后续再统一完善。

Sentinel:演示

代码注解实现方式

在pom.xml 引入jar包

<!--引入Sentinel-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.0</version>
</dependency>

com.hong.spring.config.AopConfiguration

#配置初始化限制

package com.hong.spring.config;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * 功能描述:支持sentinel的使用
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/9/4 18:28
 */
@Configuration
public class AopConfiguration {

    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {

        return new SentinelResourceAspect();
    }

    @PostConstruct
    private void initRules() throws Exception {
        FlowRule rule1 = new FlowRule();
        rule1.setResource("findById5");//@sentinelResource的value的名字
        rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);  // 规则类型
        rule1.setCount(1);   // 每秒调用最大次数为 1 次
        List<FlowRule> rules = new ArrayList<>();
        rules.add(rule1);
        // 将控制规则载入到 Sentinel
        FlowRuleManager.loadRules(rules);
    }
}

applicationContext-mybatis_plus_redis_cache.xml 新增如下

<!-- 这个配置要配置在component-scan以后 -->
<aop:aspectj-autoproxy proxy-target-class="true" />

com.hong.spring.service.impl.UserServiceImpl#handlerById

#配置名称和限制。

@Override
@SentinelResource(value = "findById5",blockHandler  = "findById5Fallback")
public DataResponse <User> handlerById(Integer id) {
    if(null==id){
        return DataResponse.BuildFailResponse("必传参数不能为空!");
    }
    User user;
    String userStr = (String)redisCacheManager.get("user_" + id);
    if(null==userStr || StringUtils.isEmpty(userStr)){
      user = userMapper.findById(id);
        if(null!=user){
            redisCacheManager.set("user_"+id, JSONObject.toJSONString(user));
        }
    }else{
        user = JSONObject.parseObject(userStr, User.class);
    }

    return DataResponse.BuildSuccessResponse(user);
}

#限制返回结果

public static DataResponse findById5Fallback(Integer id, BlockException ex){
    log.info("进入限流了!");
    return DataResponse.BuildFailResponse("访问高峰期请重试...");
}

com.hong.spring.controller.UserController#findById5

/**
 *
 * 功能描述:通过sentinel测试
 *
 * @param:
 * @return:
 * @auther: csh
 * @date: 2020/9/4 16:01
 */
@RequestMapping("findById5/{id}")
public DataResponse<User> findById5(@PathVariable("id")Integer id){
    if(null==id){
        return DataResponse.BuildFailResponse("参数不能为空!");
    }
    try {
        return userService.handlerById(id);
    }catch (Exception e){
        log.error("findById->查询失败{}",e);
        return DataResponse.BuildFailResponse("查询出错请重试!");
    }
}

然后启动项目,连续请求两次地址:

http://localhost:8081/user/findById5/4

结果(这种方式代码侵入性太强!)

00:15:28.974 [http-nio-8081-exec-1] INFO  com.hong.spring.controller.IndexController - 首页
00:15:29.007 [http-nio-8081-exec-3] INFO  com.hong.spring.controller.IndexController - 首页
00:30:57.715 [http-nio-8081-exec-4] INFO  com.hong.spring.service.impl.UserServiceImpl - 进入限流了!

控制台方式

下载包:https://github.com/alibaba/Sentinel/releases

或者项目中位置

然后执行:

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar

运行后

pom.xml 引入新jar

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-webmvc-adapter</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-web-servlet</artifactId>
    <version>${sentinel.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-common</artifactId>
    <version>${sentinel.version}</version>
</dependency>

配置tomcat

-Dcsp.sentinel.dashboard.server=127.0.0.1:9000
-Dproject.name=spring-8081

然后运行后,打开控制台。http://localhost:9000/ 第一次需要登陆账号密码都叫:sentinel

发现spring-8081就是我们tomcat上面配的一致!

配置

结果

参考文章:

官网:https://github.com/alibaba/Sentinel/wiki

总结sentinel是一个非常强的大的工具,而且功能也非常多,可以做降级、限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。总之就是非常厉害,而且用起来也很方便,本节只是用到时候冰山一角的功能,后续再深入学习。

3.关键的缓存可以设置不过期,当更新时再同步更新就可以了;(同上)

4.同缓存击穿一样,添加分布式式进行完善;

最后

缓存穿透、缓存击穿、缓存雪崩、热点数据这几个问题在业界还是挺常见的,挺多系统由于开始业务量没那么大加之研发没有考虑到位,导致非常严重的事故,所以针对一些量大的,建议采取合适的方案进行,当然已上只是部分方案借住参考,类似nginx、sentinel、redis这种以集群的方式进行,以免服务没挂但是nginx挂了就真的没达到高可用效果....关于这几个相关的集群后续再统一完善,请持续关注,谢谢!

参照文章:

https://blog.csdn.net/zeb_perfect/article/details/54135506

https://juejin.im/post/6844903807797690376

https://blog.csdn.net/zhengzhaoyang122/article/details/82184029

https://cloud.tencent.com/developer/article/1058203

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 技术趋势 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
云数据库 Redis
腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档