缓存与分布式锁

一、Springboot整合Redis

1.引入依赖
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.在yaml配置基本信息
spring:
  redis:
    port: 6379
    host: 192.168.220.131
3.调用StringRedisTemplate来操作

简单实例1

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
public void test3(){
   stringRedisTemplate.opsForValue().set("username","Alan");
   String alan = stringRedisTemplate.opsForValue().get("username");
   System.out.println(alan);
}

简单实例2

@Autowired
private RedisTemplate redisTemplate;

@Test
public void test2(){
   AttrEntity attr = new AttrEntity();
   attr.setAttrId(8L);
   redisTemplate.opsForValue().set("attrEntity",attr);
   AttrEntity attrEntity = (AttrEntity) redisTemplate.opsForValue().get("attrEntity");
   System.out.println(attrEntity.getAttrId());
}
4.产生对外内存溢出
  1. springboot2.0以后默认lettuce作为操作redis的客户端,它使用netty进行网罗通信
  2. lettuce的bug导致netty堆外内存溢出, -Xmx300m;netty如果没有指定堆外内存,默认使用-Xmx300m

解决方案

  • 升级lettuce客户端
  • 切换使用jedis
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
   <exclusions>
		<exclusion>
             <groupId>io.lettue</groupId>
              <artifactId>lettuce-core</artifactId>
        </exclusion>
   </exclusions>
</dependency>

<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
</dependency>

二.高并发下缓存失效问题

缓存穿透

查询一个不存在的数据,由于缓存是不命中,将去查询数据库,但数据库无此记录,我们没有将这次查询的null写入缓存,这将导致这个不不存在数据每次请求都要到存储层查询,失去了缓存的意义

  • 风险 利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃
  • 解决 null结果缓存,并加入短暂过期时间

缓存雪崩

我们在设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重血崩

  • 解决 原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存过期时间的重复率就会降低,就很难引发集体失效事件。

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某一时间点被超高并发的访问,是一种非常“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有,对这个key的数据查询都落在db,我们成为缓存击穿

  • 解决 加锁,大量并发只让一个人去查,其他人等待,查到以后释放所,其他人获得锁,先查缓存,就会有数据,不用去db
1.加锁解决缓存击穿问题
  • 本地锁

    代码示例

     public Map<String, List<Catalog2VO>> getCatalogJsonFromDb() {
    
         synchronized (this){
             //TODO 得到锁后应该再去缓存确认一次
             String catalogJSON = redisTemplate.opsForValue().get("catalogJson");
             if(!StringUtils.isEmpty(catalogJSON)){
                 Map<String, List<Catalog2VO>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>(){});
                 System.out.println("查询缓存" + "***********");
                 return result;
             }
    

            System.out.println("查询数据库" + "**************");
    
            //查询三级分类,并封装三级分类为map
            List<CategoryEntity> category3Entities = this.list(new QueryWrapper<CategoryEntity>().eq("cat_level", 3));
            Map<Long, List<Catalog2VO.Catalog3VO>> cat3Map = new HashMap<>();
            for (CategoryEntity category3Entity : category3Entities) {
    
                Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO();
                catalog3VO.setCatelog2Id(category3Entity.getParentCid().toString());
                catalog3VO.setId(category3Entity.getCatId().toString());
                catalog3VO.setName(category3Entity.getName());
    
                List<Catalog2VO.Catalog3VO> entites = cat3Map.get(category3Entity.getParentCid());
    
                if(CollectionUtils.isEmpty(entites)){
                    List<Catalog2VO.Catalog3VO> list = new ArrayList<>();
                    list.add(catalog3VO);
                    cat3Map.put(category3Entity.getParentCid(), list);
    
                }else {
                    entites.add(catalog3VO);
                }
            }
    
            //查询二级分类
            List<CategoryEntity> category2Entities = this.list(new QueryWrapper<CategoryEntity>().eq("cat_level", 2));
            //封装cat2Map
            Map<String, List<Catalog2VO>> cat2Map = new HashMap<>();
            for (CategoryEntity category2Entity : category2Entities) {
    
                Catalog2VO catalog2VO = new Catalog2VO();
                catalog2VO.setCatelog1Id(category2Entity.getParentCid().toString());
                catalog2VO.setId(category2Entity.getCatId().toString());
                catalog2VO.setName(category2Entity.getName());
                List<Catalog2VO.Catalog3VO> catalog3VOList = cat3Map.get(category2Entity.getCatId());
                if(!CollectionUtils.isEmpty(catalog3VOList))
                    catalog2VO.setCatalog3List(catalog3VOList);
    
                List<Catalog2VO> entites = cat2Map.get(category2Entity.getParentCid().toString());
                if(CollectionUtils.isEmpty(entites)){
                    List<Catalog2VO> list = new ArrayList<>();
                    list.add(catalog2VO);
                    cat2Map.put(category2Entity.getParentCid().toString(), list);
                }else{
                    entites.add(catalog2VO);
                }
            }
    
            String jsonString = JSON.toJSONString(cat2Map);
            redisTemplate.opsForValue().set("catalogJson",jsonString);
    
            return cat2Map;
        }
    }

缺点:在分布式中同步锁不共享,每个微服务都会在开始时查询一次数据库

2.分布式锁

通过redis实现

简单示例

 public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithReidsLock() {
     //1、占分布式锁 去redis占坑
     Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");
     if(lock){
         //加锁成功。。。 执行业务
         Map<String, List<Catalog2VO>> catalogJsonFromDb = this.getCatalogJsonFromDb();
         redisTemplate.delete("lock");
         return catalogJsonFromDb;
     }else{
         //加锁失败重试
         return getCatalogJsonFromDbWithReidsLock();//自旋的方式
     }
 }

缺点: setnx占好了坑位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁

解决: 设置锁的自动过期,即使没有删除,会自动删除

Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111", 300, TimeUnit.SECONDS);

但这还是不行

缺点: 由于业务时间很长,锁自己已经过期了,我们直接删除,有可能把别人正在持有的锁删除

解决: 占锁的时候,值指定为uuid,每个人匹配是自己的锁才删除

    public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithReidsLock() {
        //1、占分布式锁 去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if(lock){
            //加锁成功。。。 执行业务
            Map<String, List<Catalog2VO>> catalogJsonFromDb = this.getCatalogJsonFromDb();
    
            String lockValue = redisTemplate.opsForValue().get("lock");
            if(uuid.equals(lockValue)){
                redisTemplate.delete("lock");
            }
            return catalogJsonFromDb;
        }else{
            //加锁失败重试
            return getCatalogJsonFromDbWithReidsLock();//自旋的方式
        }
    }

这还有问题

如果正好判断是当前值,正当要删除锁的时候,锁已经过期,别人已经设置到新的值、那我们删除的是别人的锁

解决:删除锁必须保证原子性。使用redis+Lua脚本完成

执行脚本

if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end

保证加锁[占位+过期时间]和删除锁[判断+删除]的原子性。更难的事情,锁的自动续期。

代码如下

   public Map<String, List<Catalog2VO>> getCatalogJsonFromDbWithReidsLock() {
        //1、占分布式锁 去redis占坑
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
        if(lock){
            //加锁成功。。。 执行业务
            Map<String, List<Catalog2VO>> catalogJsonFromDb;
            try{
                catalogJsonFromDb = this.getCatalogJsonFromDb();
            }finally {
                //lua脚本
                String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                //删除锁, Integer为方法的返回值
                Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }

            return catalogJsonFromDb;
    
        }else{
            System.out.println("获取分布式失败。。。等待重试");
            try{
                Thread.sleep(200);
            }catch (Exception e){
    
            }
    
            //加锁失败重试
            return getCatalogJsonFromDbWithReidsLock();//自旋的方式
        }
    }

三、Redisson

1.java整合redisson

1.导入依赖

<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.4</version>
        </dependency>

2.配置

@Configuration
public class MyRedissonConfig {

    @Bean(destroyMethod="shutdown")
    RedissonClient redisson() throws IOException {
        //1、创建配置
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.220.131:6379");
    
        //2、根据Config床建出RedissonClient实例
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

3.简单实例

    @Autowired
    private RedissonClient redisson;
    
    @ResponseBody
    @GetMapping("/hello")
    public String hello(){
        //1.获取一把锁,只要锁的名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");
    
        //2.加锁
        lock.lock();//阻塞式等待,默认是30s,超过自动续期
    
        try{
            System.out.println("加锁成功,执行业务...." + Thread.currentThread().getId());
            Thread.sleep(30000);
        }catch (Exception e){
    
        }finally {
            //3.解锁
            System.out.println("解放锁..." + Thread.currentThread().getId());
            lock.unlock();
        }
    
        return "hello";
    }

最佳实战:

lock.lock(10,TimeUnit.SECONDS);//不会自动续期
2.读写锁

代码示例

    
    @Autowired
    private StringRedisTemplate redisTemplate;


	//保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁)。读锁是一个共享锁
    //写锁没释放读就必须等待
    @GetMapping("/write")
    @ResponseBody
    public String writeValue(){
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s ="";
        RLock rLock = lock.writeLock();
        rLock.lock();
        try{
            s = UUID.randomUUID().toString();
            Thread.sleep(30000);
            redisTemplate.opsForValue().set("writeValue",s);
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }

        return s;
    }

    @GetMapping("/read")
    @ResponseBody
    public String readValue(){
        RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
        String s = "";
        RLock rLock = lock.readLock();
        rLock.lock();
        try{
            s = redisTemplate.opsForValue().get("writeValue");
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }

        return s;
    }

读 --> 读: 相当于无锁,并发读,只会在redis中记录好,所有当前的读锁。他们都会同时加载成功

写 --> 读: 等待写锁释放

写 --> 写: 阻塞方式

读 --> 写: 有读锁。写也需要等待

3.信号量
    //可看作停车问题,可作分布式限流
    //此为停车方法
    @GetMapping("/park")
    @ResponseBody
    public String park() throws InterruptedException{
        RSemaphore park = redisson.getSemaphore("park");

        //查看是否有空余的信号(车位)
        boolean b = park.tryAcquire();
        if(b){
            //获取一个信号()
            park.acquire();
        }else {
            return "error";
        }
        return "ok";
    }

	//此为离开方法
    @GetMapping("/go")
    @ResponseBody
    public String go() throws InterruptedException {
        RSemaphore park = redisson.getSemaphore("park");
        park.release();

        return "ok";
    }
4.闭锁测试
    @GetMapping("/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException{
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();

        return "放假了。。。。";
    }

    @GetMapping("/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Long id){
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown();//计数减一

        return id + "班的人都走了";
    }
5.缓存一致性(数据更新时)

双写模式

双写模式是指修改数据后写到数据库,接着写到缓存

image-20210407234924604

由于卡顿等原因,导致写缓存2在最前,写缓存1在后面就出现数据不一致的问题

脏数据问题:

这是暂时性脏数据的问题,但是在数据稳定,缓存期以后,又能得到新的正确数据

失效模式

失效模式值数据更新写入数据库,删除缓存数据,下次查时再存入缓存;

image-20210407235239138

解决方案

  • 无论是双写模式,都会存在缓存不一致问题,即多个实例同时更新会出事
    • 如果用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这种问题,缓存数据加上过期时间,每隔一段时间主动触发读的主动更新即可
    • 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式
    • 缓存数据+过期时间也足够解决大部分对于缓存的要求
    • 通过加锁保证并发读写,写写的时候按照顺序排队。读读无所谓、所以适合用读写锁。(业务不关心脏数据,允许脏数据可以忽略)
  • 总结
    • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可;
    • 我们不应该过度设计,增加系统的复杂性;
    • 遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。

我们系统一致性解决方案:

1.缓存的所有数据都有过期时间,数据过期下一次查询触发主动更新

2.读写数据的时候,加上分布式的读写锁 (偶尔写经常读)

<think>嗯,我现在要回答用户关于“3216封装钽电容16v全名”的问题。首先,我需要确认用户的问题具体指的是什么。看起来用户想知道这种钽电容的完整名称,可能包括封装尺寸、电压额定值以及其他相关参数。 首先,我应该回忆一下钽电容的基本知识。钽电容通常以它们的封装尺寸、电压、容值等参数来命名。常见的封装类如3216,这应该是指尺寸代码,但需要确认是否正确。钽电容的封装代码通常采用EIA标准,比如3216对应的是EIA的尺寸代码,转换成毫米的话,3216可能代表3.2mm×1.6mm的尺寸,但有时候可能有不同的表示方式,比如另一种命名方式中的“A”或“B”封装,不过可能需要进一步核实。 接下来,电压16V应该是额定电压,也就是电容器能承受的最大电压。钽电容的电压等级通常有多个,比如6.3V、10V、16V、25V等,所以16V是一个常见的规格。 然后,全名可能包括品牌、系列、容值、公差、封装、电压等。例如,一个完整的号可能是像KEMET的T491系列,或者其他制造商的类似产品。例如,T491B106K016AT,其中各部分可能代表:T491是系列,B是封装代码,106是容值(10µF),K是公差(±10%),016是电压(16V),A可能代表包装方式,T是包装形式。 不过用户可能不需要具体的品牌号,而是通用的命名方式。因此,全名可能包括封装尺寸(3216)、结构(钽电容)、电压(16V),以及其他参数如容值和公差,但用户问题中未提到容值,所以可能需要解释这些部分如何构成全名。 另外,需要注意的是,3216封装可能对应不同的代码,比如EIA的3216-18(3.2mm×1.6mm),而有时候也被称为“B”封装,例如在AVX的钽电容中,3216封装对应的是B case。因此,全名可能包括封装代码,如“3216 (B)封装”。 总结一下,全名可能结构为:封装尺寸(3216,即EIA 3216-18或B)+ 钽电容(固体钽电解电容器)+ 额定电压(16V)+ 其他参数如容值和公差(但用户未提供,所以可能需要指出这些参数通常存在)。 需要确认是否3216封装对应正确的尺寸,避免混淆。例如,3216可能有两种解释:一种是EIA的3216(3.2mm×1.6mm),另一种是公制尺寸,但通常钽电容的封装代码如A、B、C对应不同尺寸。例如,AVX的B是3.5mm×2.8mm,而可能3216是另一种标准,或者用户可能误用了封装代码。不过,常见的钽电容封装代码如A、B、C、D等,例如,B对应EIA 3528-21(3.5mm×2.8mm),这可能意味着用户提到的3216可能不是标准钽电容封装代码,或者可能存在不同厂家的命名差异。 因此,可能需要指出,3216封装可能是指尺寸代码,但不同制造商可能有不同的命名方式,所以需要确认具体的制造商号。例如,某些厂家可能将3216作为封装代码,而其他厂家可能使用不同的代码。在这种情况下,全名可能包括制造商的系列名称、封装代码、电压、容值等,但用户只提供了封装和电压,所以可能需要一般性的解释,并建议用户查阅具体的数据手册以获取完整号。 另外,钽电容的全名可能还包括极性(钽电容是有极性的)、材料(固体或液体电解质,这里应该是固体钽电容),以及是否符合特定标准,如AEC-Q200用于汽车应用等,但可能用户不需要这些细节。 综上所述,用户的问题可能需要拆解为几个部分:封装、类、电压,然后解释各部分的意义,并指出完整的号通常还包括容值、公差、系列等,但基于用户提供的信息,只能给出部分名称,并建议补充其他参数以获得准确号。</think>关于“3216封装钽电容16V”的全名,以下是逐步解析: --- ### **1. 基本定义** 这是一种**表面贴装固体钽电解电容器**,其核心特征包括: - **封装尺寸**:3216(公制代码,对应3.2mm×1.6mm的尺寸)。 - **额定电压**:16V。 - **极性**:有极性(需注意正负极方向)。 --- ### **2. 全名组成** 完整名称通常包含以下参数(以通用格式为例): ``` [封装代码]-[电容类]-[额定电压]-[电容值]-[公差]-[附加代码] ``` 例如: $$ \text{3216B-TANT-16V-10µF-K} $$ - **3216**:封装尺寸(3.2mm×1.6mm)。 - **B**:封装类(部分厂商用字母表示,如B对应3216)。 - **TANT**:钽电容标识。 - **16V**:额定电压。 - **10µF**:电容值(需根据具体号填写)。 - **K**:公差(如K=±10%)。 --- ### **3. 关键参数说明** - **封装代码**: - 3216是**公制尺寸**,对应英制代码为**1206**(长×宽:0.12英寸×0.06英寸)。 - 部分厂商可能用字母表示(如AVX的B封装对应3.5mm×2.8mm,需注意区分)。 - **电压与容值**: - 16V为**额定电压**,实际使用需降额(通常按50%~80%设计)。 - 容值(如10µF、22µF)需根据具体号确定。 --- ### **4. 典号示例** 以厂商**KEMET**或**AVX**的号为例: - **KEMET T491B106K016AT**: - T491:系列名称(低ESR固体钽电容)。 - B:封装代码(对应3216尺寸)。 - 106:容值(10×10⁶ pF = 10µF)。 - K:公差(±10%)。 - 016:额定电压(16V)。 - A:包装形式(卷带包装)。 --- ### **5. 注意事项** - **封装混淆**:3216可能与其他标准冲突(如贴片电阻的3216封装),需通过“钽电容”标识区分。 - **选建议**:需结合**容值、耐压、ESR、纹波电流**等参数,并参考厂商手册。 --- ### **总结** 用户提到的“3216封装钽电容16V”全名需补充具体参数(如容值、公差)才能确定唯一号。通用名称可描述为: **3216封装(3.2mm×1.6mm)表面贴装固体钽电解电容器,额定电压16V**。 建议提供更多参数或查阅厂商手册以获取完整号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值