Redis——某马点评day02——商铺缓存

什么是缓存

添加Redis缓存

添加商铺缓存

Controller层中

    /**
     * 根据id查询商铺信息
     * @param id 商铺id
     * @return 商铺详情数据
     */
    @GetMapping("/{id}")
    public Result queryShopById(@PathVariable("id") Long id) {
        return shopService.queryById(id);
    }

Service层中

 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {
        String key="cache:shop:" + id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //5.不存在,返回错误
            return Result.fail("店铺不存在");
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop));
        //7.返回
        return Result.ok(shop);
    }
}

练习添加店铺类型缓存

Controller层中

@RestController
@RequestMapping("/shop-type")
public class ShopTypeController {
    @Resource
    private IShopTypeService typeService;

    @GetMapping("list")
    public Result queryTypeList() {
       return typeService.queryTypeList();
    }
}

Service层中

    @Override
    public Result queryTypeList() {
        String key="cache:shopType";
        //1.从Redis查询缓存
        String shopType = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopType)) {
            //3.存在,直接返回
            List<ShopType> typeList = JSONUtil.toList(shopType, ShopType.class);
            return Result.ok(typeList);
        }
        //4.不存在,查询数据库
        List<ShopType> typeList = query().orderByAsc("sort").list();
        //5.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(typeList));
        //7.返回
        return Result.ok(typeList);
    }

缓存更新策略

 通常选择的方案都是第一种

单体系统可以通过@Transactional注解完成事务。

通常是先操作数据库,再删除缓存,出现问题的几率极小。

 

 实现商铺缓存和数据库的双写一致

第一个地方,写入Redis时加上超时时间。 

  //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);

 第二个地方

controller中

    /**
     * 更新商铺信息
     * @param shop 商铺数据
     * @return 无
     */
    @PutMapping
    public Result updateShop(@RequestBody Shop shop) {
        return shopService.update(shop);
    }

service中

    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }

缓存穿透

布隆过滤器的实现不是真的存储数据,而是用某种Hash算法计算之后用二进制压缩之类的方法保存是否存在。但是,也有可能多个数据hash值相同导致错误结果。

编码解决商铺查询的缓存穿透(缓存空对象做法)

 代码修改

    @Override
    public Result queryById(Long id) {
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return Result.fail("店铺不存在");
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //将空值写入Redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            //5.不存在,返回错误
            return Result.fail("店铺不存在");
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回
        return Result.ok(shop);
    }

 限流可以用sentinel实现.

缓存雪崩

宕机时降级限流也是用sentinel实现。

nginx缓存也是一级缓存.

tmd,一直在说springcloud里面有讲。

缓存击穿

常见解决方案

这里可以参考一下redisson的源码设计思路,设计一个监听通知机制! 

逻辑过期解决方案不会设置ttl过期时间,而是新增一个exprie字段,从redis里面查询发现是过期数据时就需要加锁开启一个新线程去更新缓存,然后直接返回旧数据。有别的线程来获取锁失败时说明已经有线程在进行更新,所以就直接返回过期数据,避免了过多线程等待锁。

 

利用互斥锁解决缓存击穿问题(重点)

这里的锁不能用lock和synchronized进行互斥实现,这两个会一直等待.这里用到Redis的一个命令setnx, 这个是一旦设置之后就不能修改,只能删除,但是如果因为意外原因导致迟迟不能删除会有大问题,所以这里会给锁设置一个有效期.

 代码修改

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop=queryWithPassThrouh(id);
        //互斥锁解决缓存击穿
        Shop shop = queryWithMutex(id);
        if(shop==null){
            return Result.fail("店铺不存在!");
        }
        //7.返回
        return Result.ok(shop);
    }

    public Shop queryWithMutex(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.实现缓存重建
        //4.1获取互斥锁
        String lockkey="lock:shop:"+id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockkey);
            //4.2判断是否获取成功
            if(!isLock){
                //4.3失败,休眠并重试
                Thread.sleep(50);
              return  queryWithMutex(id);   //这里有可能会出现栈溢出的情况。
            }
            //获取成功之后应该再次检查缓存是否存在,有可能别的线程已经重建完了缓存,所以这里就无需再重建缓存
            shopJson = stringRedisTemplate.opsForValue().get(key);
            //再次判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //存在,直接返回
                return JSONUtil.toBean(shopJson, Shop.class);
            }
            //4.4根据id查询数据库
            shop = getById(id);
            //模拟重建的延时
            Thread.sleep(200);
            if (shop==null) {
                //将空值写入Redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                //5.不存在,返回错误
                return shop;
            }
            //6.存在,写入Redis
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unlock(lockkey);
        }
        //8.返回
        return shop;
    }

    public Shop queryWithPassThrouh(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //将空值写入Redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            //5.不存在,返回错误
            return shop;
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回
        return shop;
    }

    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }


    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }
}

这里可以上Jmeter进行压测,上100个线程进行测试

但是最终实际只查询了一次数据库. 

利用逻辑过期解决缓存击穿问题(重点)

 为了能增加一个逻辑过期时间的字段,新建一个对象

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

代码修改

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop=queryWithPassThrouh(id);
        //互斥锁解决缓存击穿
//        Shop shop = queryWithMutex(id);

        //逻辑过期解决缓存击穿问题
        Shop shop = queryWithLogicalExpire(id);
        if(shop==null){
            return Result.fail("店铺不存在!");
        }
        //7.返回
        return Result.ok(shop);
    }


    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    public Shop queryWithLogicalExpire(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(shopJson)) {
            //3.存在,直接返回null
            return null;
        }
        //4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject)redisData.getData(), Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        //5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            //5.1未过期,直接返回店铺信息
            return shop;
        }
        //5.2已过期,需要缓存重建
        //6.缓存重建
        //6.1获取互斥锁
        String lockKey=LOCK_SHOP_KEY+id;
        boolean isLock = tryLock(lockKey);
        //6.2判断是否获取锁成功
        if(isLock){
            //这里应该再次检测缓存是否过期,做双重判断,如果没过期就不需重建了,因为可能别的线程已经重建了
             shopJson = stringRedisTemplate.opsForValue().get(key);
             redisData = JSONUtil.toBean(shopJson, RedisData.class);
            expireTime = redisData.getExpireTime();
            if(expireTime.isAfter(LocalDateTime.now())){
                //返回前先释放锁
                unlock(lockKey);
                //5.1未过期,直接返回店铺信息
                return shop;
            }
            //6.3成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存
                    this.saveShop2Redis(id,20L);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //6.4失败,返回过期商铺信息。
        return shop;
    }

    public Shop queryWithMutex(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);

        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.实现缓存重建
        //4.1获取互斥锁
        String lockkey="lock:shop:"+id;
        Shop shop = null;
        try {
            boolean isLock = tryLock(lockkey);
            //4.2判断是否获取成功
            if(!isLock){
                //4.3失败,休眠并重试
                Thread.sleep(50);
              return  queryWithMutex(id);   //这里有可能会出现栈溢出的情况。
            }
            //获取成功之后应该再次检查缓存是否存在,有可能别的线程已经重建完了缓存,所以这里就无需再重建缓存
            shopJson = stringRedisTemplate.opsForValue().get(key);
            //再次判断是否存在
            if (StrUtil.isNotBlank(shopJson)) {
                //存在,直接返回
                return JSONUtil.toBean(shopJson, Shop.class);
            }
            //4.4根据id查询数据库
            shop = getById(id);
            //模拟重建的延时
            //Thread.sleep(200);
            if (shop==null) {
                //将空值写入Redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                //5.不存在,返回错误
                return shop;
            }
            //6.存在,写入Redis
            stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unlock(lockkey);
        }
        //8.返回
        return shop;
    }

    public Shop queryWithPassThrouh(Long id){
        String key=CACHE_SHOP_KEY+ id;
        //1.从Redis查询缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            //3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return shop;
        }
        //判断命中的是否是空值
        if(shopJson!=null){
            //返回一个错误信息
            return null;
        }
        //4.不存在,根据id查询数据库
        Shop shop = getById(id);
        if (shop==null) {
            //将空值写入Redis
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
            //5.不存在,返回错误
            return shop;
        }
        //6.存在,写入Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);
        //7.返回
        return shop;
    }

    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }

    public void saveShop2Redis(Long id,Long expireSeconds) throws InterruptedException {
        //1.查询店铺数据
        Shop shop = getById(id);
        //模拟延时
//        Thread.sleep(200);
        //2.封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        //3.写入Redis
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));
    }

    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }
}

缓存工具封装(重点)

封装工具类里用到的实体

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

工具类代码

@Slf4j
@Component
public class CacheClient {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    String LOCK_SHOP_KEY="lock:shop:";
    
    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }
    public  void set(String key, Object value, Long time, TimeUnit timeUnit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,timeUnit);
    }

    public  void setWithLogicalExpire(String key, Object value, Long time, TimeUnit timeUnit){
        //设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
        //写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID,R>dbFallback, Long time, TimeUnit timeUnit){
        String key=keyPrefix+id;
        //1.从Redis查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            //3.存在,直接返回
            return  JSONUtil.toBean(json, type);
        }
        //判断命中的是否是空值
        if(json!=null){
            //返回一个错误信息
            return null;
        }
        //4.不存在,根据id查询数据库
        R r=dbFallback.apply(id);
        //5.不存在,返回错误
        if (r==null) {
            //将空值写入Redis
            stringRedisTemplate.opsForValue().set(key,"",2L, TimeUnit.MINUTES);
            return null;
        }
        //6.存在,写入Redis
        this.set(key,r,time,timeUnit);
        //7.返回
        return r;
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR= Executors.newFixedThreadPool(10);
    public <R,ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID,R>dbFallback, Long time, TimeUnit timeUnit){
        String key=keyPrefix+ id;
        //1.从Redis查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(json)) {
            //3.存在,直接返回null
            return null;
        }
        //4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject)redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        //5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            //5.1未过期,直接返回店铺信息
            return r;
        }
        //5.2已过期,需要缓存重建
        //6.缓存重建
        //6.1获取互斥锁
        String lockKey=LOCK_SHOP_KEY+id;
        boolean isLock = tryLock(lockKey);
        //6.2判断是否获取锁成功
        if(isLock){
            //这里应该再次检测缓存是否过期,做双重判断,如果没过期就不需重建了,因为可能别的线程已经重建了
            json = stringRedisTemplate.opsForValue().get(key);
            redisData = JSONUtil.toBean(json, RedisData.class);
            r = JSONUtil.toBean((JSONObject)redisData.getData(), type);
            expireTime = redisData.getExpireTime();
            if(expireTime.isAfter(LocalDateTime.now())){
                //返回前先释放锁
                unlock(lockKey);
                //5.1未过期,直接返回店铺信息
                return r;
            }
            //6.3成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //重建缓存
                    //查询数据库
                    R r1 = dbFallback.apply(id);
                    //写入Redis
                    this.setWithLogicalExpire(key,r1,time,timeUnit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //6.4失败,返回过期信息。
        return r;
    }

    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }
}

Service层修改后代码

里面有缓存穿透的调用,也有缓存击穿的调用.

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private CacheClient cacheClient;
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        Shop shop=cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id, Shop.class,id2->getById(id2),CACHE_SHOP_TTL,TimeUnit.MINUTES);
        //Shop shop=cacheClient.queryWithPassThrough(CACHE_SHOP_KEY,id, Shop.class,this::getById,,CACHE_SHOP_TTL,TimeUnit.MINUTES);
        //互斥锁解决缓存击穿
        //Shop shop = queryWithMutex(id);
        //逻辑过期解决缓存击穿问题
//        Shop shop = cacheClient
//                .queryWithLogicalExpire(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.SECONDS);
        if(shop==null){
            return Result.fail("店铺不存在!");
        }
        //7.返回
        return Result.ok(shop);
    }


    @Override
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id==null){
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY+id);
        return null;
    }
}

内容总结:

去看文档资料里面xmind文档,那个里面总结的很好。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/225161.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

构建socket的客户端和服务端

网络函数 WSAStartup socket bind listen accept connect send recv closesocket WSACleanup 为什么要用WSAStartup初始化&#xff1f; 本函数必须是应用程序或DLL调用的第一个Windows Sockets函数.它允许应用程序或DLL指明Windows Sockets API的版本号及获得特定Windows So…

文件加密软件——支持对任意类型文档加密保护

你是不是经历过这样的场景&#xff1a; 公司的文件随意外发 员工拿U盘随意拷贝文件 公司辛辛苦苦设计的图纸莫名其妙泄露了 标书里的数据不知道什么时候就被竞品公司知道了 …… 一系列的文件泄密事件&#xff0c;让企业主不寒而栗。遂千方百计、好似无头苍蝇似的在市面上…

postgreSql服务的window启动

CMD启动服务&#xff1a; D:\PostgreSQL\bin pg_ctl register -N PostgreSQL -D "D:\PostgreSQL\data # 登录 psql -U postgres # 验证输入 select 1; 拓展&#xff1a;删除服务 sc delete 服务名称 PostgreSQLUSER: postgresPWD: rootPORT: 5432动PostgreSQL服务器 3.1 打…

R语言手册30分钟上手

文章目录 1. 环境&安装1.1. rstudio保存工作空间 2. 创建数据集2.1. 数据集概念2.2. 向量、矩阵2.3. 数据框2.3.1. 创建数据框2.3.2. 创建新变量2.3.3. 变量的重编码2.3.4. 列重命名2.3.5. 缺失值2.3.6. 日期值2.3.7. 数据框排序2.3.8. 数据框合并(合并沪深300和中证500收盘…

Java设计模式:单例模式(饿汉式、懒汉式、枚举实现类)

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

[足式机器人]Part2 Dr. CAN学习笔记-数学基础Ch0-4线性时不变系统中的冲激响应与卷积

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-数学基础Ch0-4线性时不变系统中的冲激响应与卷积 1. LIT System&#xff1a;Linear Time Invariant2. 卷积 Convolution3. 单位冲激 Unit Impulse——Dirac Delta 线性时不变系统 &#xff1a; L…

目标检测综述(待补ing)

文章目录 摘要引言目标检测发展历程目标检测路线图传统检测器基于CNN的两阶段检测器基于CNN的一阶段检测器 目标检测数据集及指标数据集评价指标标注软件 backboneAlexNet&#xff08;2012&#xff09;VGGNet&#xff08;2014&#xff09;GoogleNet&#xff08;2014&#xff09…

使用函数计算,数禾如何实现高效的数据处理?

作者&#xff5c;邱鑫鑫&#xff0c;王彬&#xff0c;牟柏旭 公司背景和业务 数禾科技以大数据和技术为驱动&#xff0c;为金融机构提供高效的智能零售金融解决方案&#xff0c;服务银行、信托、消费金融公司、保险、小贷公司等持牌金融机构&#xff0c;业务涵盖消费信贷、小…

Node.js快速搭建简单的HTTP服务器并发布公网远程访问

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

什么牌子的灯具性价比高?性价比高适合学生的护眼台灯推荐

国家卫生健康委疾控局副局长再那吾东玉山在发布会上介绍&#xff0c;国家卫生健康委2020年9到12月全面开展了近视专项调查&#xff0c;覆盖了全国8604所学校&#xff0c;共筛查247.7万名学生。结果显示&#xff1a;2020年&#xff0c;我国儿童青少年总体近视率为52.7%。其中6岁…

王树森深度强化学习 笔记

本笔记基于王树森的深度强化学习课程 文章目录 王树森深度强化学习 笔记一、基础1. 概率论2. 名词3. Return U t U_t Ut​4. Action-Value Function Q π ( s , a ) Q_\pi(s, a) Qπ​(s,a)5. State-Value Function V π ( s ) V_\pi(s) Vπ​(s) 二、Value-Based Reinforc…

深入理解Sentinel系列-2.Sentinel原理及核心源码分析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

04 ECharts基础入门

文章目录 一、ECharts介绍1. 简介2. 相关网站3. HTML引入方式4. 基本概念 二、常见图表1. 柱状图2. 折线图3. 饼图4. 雷达图5. 地图 三、应用1. 动画2. 交互 一、ECharts介绍 1. 简介 ECharts是一个使用JavaScript实现的开源可视化库&#xff0c;用于生成各种图表和图形。 EC…

RT_Thread_修改为外部晶振及验证

关注两处&#xff1a; 1、stm32f4xx_hal_conf.h&#xff0c;外部晶振频率HSE宏定义 2、drv_clk.c&#xff0c;system_clock_config函数 1、外部晶振频率HSE宏定义 根据实际外部晶振的频率去定义&#xff0c;使用的是8MHz&#xff1b; 2、system_clock_config 开启HSE&#…

改造python3中的http.server为简单的文件上传下载服务

改造 修改python3中的http.server.SimpleHTTPRequestHandler&#xff0c;实现简单的文件上传下载服务 simple_http_file_server.py&#xff1a; # !/usr/bin/env python3import datetime import email import html import http.server import io import mimetypes import os …

Thymeleaf生成pdf表格合并单元格描边不显示

生成pdf后左侧第一列的右描边不显示&#xff0c;但是html显示正常 显示异常时描边的写法 cellpadding“0” cellspacing“0” &#xff0c;td,th描边 .self-table{border:1px solid #000;border-collapse: collapse;width:100%}.self-table th{font-size:12px;border:1px sol…

markdown学习(初学者)

学习途径&#xff1a; 在线markdown编辑器_微信公众号markdown排版工具 Markdown 基本语法 | Markdown 官方教程 标题 要创建标题&#xff0c;请在单词或短语前面添加井号 (#) 。# 的数量代表了标题的级别。例如&#xff0c;添加三个 # 表示创建一个三级标题 (<h3>) (…

【华为网络-配置-023】- 一般企业网架构方案(单节点方案)

要求&#xff1a; 1、防火墙 FW1 G1/0/0 接口使用 PPPoE 拨号获取 IP 地址。 2、FW1 配置信任&#xff08;内网包含服务器&#xff09;和非信任区域&#xff08;Internet 外网&#xff09;。 3、FW1 配置 NAPT 使内网可以上网。 4、核心交换机 LSW1 划分 VLAN 并配置各接口及三…

Python 日期时间模块详解(datetime)

文章目录 1 概述1.1 datetime 类图1.2 类描述 2 常用方法2.1 获取当前日期时间&#xff1a;now()、today()、time()2.2 日期时间格式化&#xff1a;strftime()2.3 日期时间大小比较&#xff1a;>、、<2.4 日期时间间隔&#xff1a;- 3 扩展3.1 Python 中日期时间格式化符…

Java / Scala - Trie 树简介与应用实现

目录 一.引言 二.Tire 树简介 1.树 Tree 2.二叉搜索树 Binary Search Tree 3.字典树 Trie Tree 3.1 基本概念 3.2 额外信息 3.3 结点实现 3.4 查找与存储 三.Trie 树应用 1.应用场景 2.Java / Scala 实现 2.1 Pom 依赖 2.2 关键词匹配 四.总结 一.引言 Trie 树…