一招解决Redis缓存穿透,缓存雪崩,缓存击穿问题【超详细版】

news2024/9/8 10:13:01

文章目录

    • 小故事
    • 一、为什么要使用缓存?
    • 二、什么是缓存穿透?怎么解决?
      • 2.1解决方案
      • 2.2代码实现
    • 三、什么是缓存击穿?怎么解决?
      • 3.1解决方案
      • 3.2代码实现
    • 四、什么是缓存雪崩?怎么解决?
      • 4.1解决方案
    • 五、Redis缓存工具类【可解决缓存穿透,缓存击穿和缓存雪崩】

🌈你好呀!我是 山顶风景独好
💝欢迎来到我的博客,很高兴能够在这里和您见面!
💝希望您在这里可以感受到一份轻松愉快的氛围!
💝不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
🚀 欢迎一起踏上探险之旅,挖掘无限可能,共同成长!

小故事

  • 在一个繁华的电商世界里,有一家名叫“快购”的在线商城。为了提升用户体验和响应速度,快购商城采用了Redis作为缓存层来缓存热门商品信息、用户数据等关键数据。然而,随着用户量的增长和交易量的激增,Redis缓存层开始面临一些挑战,其中就包括缓存穿透、缓存击穿和缓存雪崩这三个问题。
    缓存穿透
  • 有一天,快购商城的客服团队接到了一位顾客的投诉,说他在搜索某个不存在的商品编号时,系统响应特别慢。技术团队的小明迅速定位到了问题所在:这个不存在的商品编号在Redis缓存中不存在,而每次查询时都会直接访问数据库,导致数据库压力骤增。这就是所谓的“缓存穿透”。
  • 小明决定采取一些措施来解决这个问题。他首先引入了布隆过滤器,在查询Redis之前先判断该商品编号是否可能存在于Redis中。如果不存在,则直接返回结果,避免了对数据库的无效查询。
    缓存击穿
  • 不久后,快购商城的某个热门商品即将进行限时促销活动。然而,在这个关键时刻,该商品的缓存却突然失效了。由于这个商品被大量用户频繁访问,导致大量请求直接冲击到了数据库,系统性能急剧下降。这就是“缓存击穿”现象。
  • 为了避免这种情况再次发生,小明设计了一个缓存重建策略。当某个商品的缓存失效时,他会先使用互斥锁来确保只有一个线程能够访问数据库并重建缓存。其他线程则会等待缓存重建完成后再从Redis中获取数据。这样,就避免了大量请求同时冲击数据库的问题。
    缓存雪崩
  • 随着双十一购物节的临近,快购商城的流量达到了历史峰值。然而,就在这时,Redis中的大量缓存突然同时失效了。由于这些缓存失效的key对应的数据都是热门商品和用户数据等关键信息,导致大量请求直接冲击到了数据库,系统几乎陷入了瘫痪状态。这就是“缓存雪崩”现象。
  • 为了应对这个问题,小明采取了一系列措施。首先,他重新评估了缓存的过期时间设置,确保不会出现大量缓存同时失效的情况。其次,他引入了Redis集群来分担单个Redis节点的压力,提高了系统的可用性和容错能力。最后,他还为数据库层添加了限流和降级策略,确保在Redis缓存失效时,数据库层也能够保持稳定运行。
  • 经过一系列的努力和改进,快购商城成功应对了缓存穿透、缓存击穿和缓存雪崩这三个挑战。在双十一购物节期间,系统保持了稳定高效的运行状态,为用户提供了流畅的购物体验。小明和他的技术团队也因此获得了公司的表彰和用户的赞誉。

一、为什么要使用缓存?

提高性能:

  • 缓存存储了数据的副本,当应用需要读取数据时,它首先会尝试从缓存中获取,而不是直接访问数据库或后端服务。由于缓存通常位于内存中,其读取速度远远快于磁盘或网络,因此可以显著提高应用程序的响应速度。

减轻数据库压力:

  • 在高并发的场景下,如果所有请求都直接访问数据库,将会导致数据库负载急剧上升,甚至可能引发数据库崩溃。通过使用缓存,可以将大部分读请求转移到缓存层处理,从而显著减轻数据库的压力。

增强系统的可扩展性:

  • 缓存层可以作为数据库和应用程序之间的缓冲层,通过扩展缓存集群的规模,可以轻松应对不断增长的用户请求和数据量。同时,由于缓存层与应用程序之间的耦合度较低,可以方便地进行水平扩展和升级。

优化用户体验:

  • 缓存能够减少用户等待时间,提升页面的加载速度和响应速度,从而优化用户体验。对于需要快速响应的互联网应用来说,这一点至关重要。

降低网络开销:

  • 在分布式系统中,应用程序可能需要从远程服务或数据库获取数据。如果频繁进行网络请求,将会产生大量的网络开销。通过使用缓存,可以减少不必要的网络请求,降低网络带宽的使用和传输延迟。

实现数据预加载和预热:

  • 通过将预期会被频繁访问的数据预先加载到缓存中,可以避免在实际访问时产生延迟。这种预加载和预热策略可以进一步提高系统的响应速度和性能。

支持高并发和实时性要求:

  • 对于一些需要支持高并发和实时性要求的场景(如在线交易、社交应用等),缓存可以提供快速、低延迟的数据访问能力,确保系统能够满足用户的需求。

二、什么是缓存穿透?怎么解决?

缓存穿透(Cache Penetration)的概念是指在查询一个不存在的数据时,由于缓存中不存在这个数据,导致查询请求直接到达了数据库层,而数据库层也没有这个数据,因此无法将结果写入缓存。这样一来,每次针对这个不存在的数据的查询都会直接请求到数据库层,造成数据库的压力增大,甚至可能引发数据库宕机。

2.1解决方案

  • 数据预热:在系统启动或数据更新时,将热点数据预先加载到缓存中。
  • 布隆过滤器:在查询缓存之前,先使用布隆过滤器(Bloom Filter)来判断这个数据是否可能存在于缓存中。如果布隆过滤器判断该数据不存在,则直接返回,不再查询缓存和数据库。
  • 空值缓存:当查询到一个不存在的数据时,可以将这个空值或特定标识(如“NULL”或“NOT_FOUND”)缓存起来,并设置一个较短的过期时间。这样,在后续的查询中,就可以直接从缓存中返回空值或特定标识,避免了对数据库的无效查询。
  • 接口层校验:在接口层对请求的参数进行校验,如果参数不符合业务规则(如ID小于0),则直接返回错误,避免无效请求到达数据库层。
  • 监控和报警:对缓存穿透的情况进行监控,当发现大量无效请求时,及时报警并采取相应的措施。

2.2代码实现

空值缓存: 如果数据库的查询结果为空,我们仍然将这个结果进行一个缓存,减轻数据库的压力时间最长不超过五分钟​​在这里插入图片描述

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

布隆过滤器:在查询缓存之前,先使用布隆过滤器(Bloom Filter)来判断这个数据是否可能存在于缓存中。如果布隆过滤器判断该数据不存在,则直接返回,不再查询缓存和数据库。
在这里插入图片描述
pom.xml添加依赖

 <!-- 添加Guava的BloomFilter依赖 -->
<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>30.1-jre</version>
</dependency>

添加配置application.yml

//布隆过滤器配置
bloom-filter:
   expected-insertions: 1000000  # 期望插入的元素数量
   fpp: 0.01  # 误判率

配置类中创建bean

@Configuration
public class BloomFilterConfig {
 
    @Value("${bloom-filter.expected-insertions}")
    private int expectedInsertions;
 
    @Value("${bloom-filter.fpp}")
    private double falsePositiveProbability;
 
    @Bean
    public BloomFilter<String> bloomFilter() {
        return BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, falsePositiveProbability);
    }
}

三、什么是缓存击穿?怎么解决?

缓存击穿(Cache Breakdown)的概念是:当某个热点数据(即被频繁访问的数据)在缓存中过期失效时,由于这个数据被大量用户或系统并发访问,导致大量请求直接穿透缓存层,直接访问数据库层,从而造成数据库压力骤增的现象。

3.1解决方案

  • 热点数据永久缓存:如果热点数据基本不会发生变化,可以考虑将其设置为永不过期,从而避免缓存击穿的发生。
  • 分布式锁:在缓存失效时,使用分布式锁(如基于Redis、ZooKeeper的锁)确保只有一个线程能够访问数据库并重新构建缓存,其他线程则等待锁释放后从缓存中获取数据。这样可以避免大量请求同时冲击数据库。
  • 定时重建缓存:对于更新频率较低但访问量大的数据,可以设置定时任务在缓存失效前主动重新构建缓存,确保缓存中始终有可用的数据。
  • 缓存降级:在缓存击穿发生时,可以考虑暂时将部分请求降级处理,如返回默认数据或提示用户稍后再试,以减轻数据库压力。

3.2代码实现

逻辑过期解决缓存击穿:
数据类:

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}
// 定义一个固定大小的线程池,用于异步重建缓存  
private static final ExecutorService rebuild_executor = Executors.newFixedThreadPool(10);  
  
/**  
 * 使用逻辑过期时间的查询方法  
 *  
 * @param keyPrefix Redis key的前缀  
 * @param id        数据的唯一标识  
 * @param type      返回数据的类型  
 * @param dbFallback 数据库查询的回调方法  
 * @param time      缓存的过期时间长度  
 * @param unit      缓存的过期时间单位  
 * @param <R>       返回值的泛型类型  
 * @param <ID>      ID的泛型类型  
 * @return 查询到的数据  
 */  
public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {  
    // 构建Redis的key  
    String key = keyPrefix + id;  
    // 从Redis中获取数据  
    String json = stringRedisTemplate.opsForValue().get(key);  
  
    // 这里有逻辑错误,如果json为空,应该进入下面的逻辑,但当前判断是如果json不为空则直接返回null,这是不对的  
    // 应改为 if (StrUtil.isBlank(json)) { ... }  
    if (StrUtil.isNotBlank(json)) {  
        return null; // 这里应该返回反序列化的对象,但json为空时的处理应该在这里之前  
    }  
  
    // 如果json为空,则下面的代码不会执行(由于上面的逻辑错误),但假设json不为空  
    // 尝试将json转换为RedisData对象  
    RedisData redisData = JSONUtil.toBean(json, RedisData.class);  
    // 从RedisData对象中提取数据并转换为指定类型R  
    R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);  
    // 获取数据的逻辑过期时间  
    LocalDateTime expireTime = redisData.getExpireTime();  
  
    // 判断是否过期  
    if (expireTime.isAfter(LocalDateTime.now())) {  
        // 数据未过期,直接返回  
        return r;  
    }  
  
    // 数据已过期,开始缓存重建  
    // 构建锁的key  
    String lockKey = "lock_key" + id;  
    // 尝试获取锁  
    boolean isLock = tryLock(lockKey);  
  
    if (isLock) {  
        // 成功获取锁,提交线程到线程池进行缓存重建  
        rebuild_executor.submit(() -> {  
            try {  
                // 调用数据库查询的回调方法,获取最新数据  
                R apply = dbFallback.apply(id);  
                // 将数据写入Redis,并设置逻辑过期时间  
                this.setWithLogicalExpire(key, apply, time, unit);  
            } catch (Exception e) {  
                throw new RuntimeException(); // 这里最好捕获具体的异常并处理,而不是直接抛出RuntimeException  
            } finally {  
                // 释放锁  
                unLock(lockKey);  
            }  
        });  
    }  
  
    // 注意:由于缓存重建是异步的,这里可能返回过期或null的数据,直到新的数据被加载到缓存中  
    return r; // 这里返回的可能是过期数据,因为重建是异步的  
}  
  
/**  
 * 尝试获取锁  
 *  
 * @param key 锁的key  
 * @return 是否成功获取锁  
 */  
private boolean tryLock(String key) {  
    // 使用setIfAbsent方法尝试在Redis中设置key的值,如果key不存在则设置成功并返回true,否则返回false  
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);  
    return BooleanUtil.isTrue(flag); // 判断是否成功获取锁  
}  
  
/**  
 * 释放锁  
 *  
 * @param key 锁的key  
 */  
private void unLock(String key) {  
    // 直接删除Redis中的key来释放锁,但这种方式存在一些问题,比如锁被误删或锁过期后删除导致的竞态条件  
    // 在实际应用中,通常会使用更复杂的锁机制,如Redis的RedLock算法  
    stringRedisTemplate.delete(key);  
}

四、什么是缓存雪崩?怎么解决?

  • 缓存雪崩是指当大量缓存同时失效或过期后,引起系统性能急剧下降的情况。
  • 具体来说,当缓存中的数据大批量到达过期时间,而此时的查询数据量又非常巨大时,系统需要再次访问数据库以重新生成缓存。
  • 由于这个处理步骤耗时较长,可能会达到上百毫秒甚至更长时间,对于高并发的系统来说,这意味着在缓存重建期间,大量的请求都会直接访问数据库,对数据库造成巨大的压力和不必要的性能损耗。
  • 这种对数据库的访问压力又会进一步拖慢整个系统,严重时甚至可能导致数据库宕机,进而形成一系列连锁反应,造成整个系统崩溃。
    缓存雪崩的原因主要包括:
  • 缓存大面积的同时失效:这可能是由于为缓存设置了相近的有效期,导致大量缓存在同一时间失效。
  • 对热点数据的持续高并发访问:在缓存失效后,大量的请求会同时访问数据库以获取数据,进一步加剧了系统的压力。

4.1解决方案

均匀设置过期时间:

  • 为避免大量缓存数据在同一时间过期,可以在设置缓存过期时间时加上一个随机数,确保数据不会在同一时间失效。
  • 例如,如果一个缓存键原本应该设置1小时的过期时间,可以改为在1小时±一个较小的时间范围内随机设置过期时间。

使用互斥锁:

  • 当业务线程发现访问的数据不在Redis中时,可以使用互斥锁(如Redis的SETNX命令)来确保同一时间只有一个线程去数据库查询并更新缓存。
  • 未能获取互斥锁的线程可以选择等待锁释放后重新读取缓存,或者返回空值或默认值。

后台更新缓存:

  • 缓存的更新工作交由后台线程定时或根据业务需求触发,而不是由业务线程在访问时发现缓存失效后直接更新。
  • 这样可以避免大量请求同时去数据库查询数据并更新缓存,减轻数据库压力。

缓存预热:

  • 在系统启动或低峰时段,预先将热点数据加载到Redis缓存中,确保在高峰时段用户访问时数据已经在缓存中。

使用Redis主从复制和哨兵机制:

  • 通过主从复制,将数据复制到多个从服务器,确保在主服务器出现问题时,从服务器可以继续提供服务。
  • 哨兵机制负责监控Redis主从服务器的健康状态,一旦主服务器出现问题,哨兵将自动切换到备份服务器作为新的主服务器,保障数据的持续可用。

增加缓存容量:

  • 根据业务需求,可以通过增加Redis节点数量或增大单个节点的容量来提高缓存容量,从而避免缓存雪崩。

优化数据库连接:

  • 确保数据库连接池配置合理,避免在缓存失效时大量请求同时创建数据库连接导致数据库性能下降。

使用限流和降级策略:

  • 在系统层面使用限流策略,如令牌桶、漏桶算法等,限制对Redis和数据库的访问频率。
  • 当Redis缓存失效时,可以采用降级策略,如返回默认数据或提示用户稍后重试,避免大量请求直接访问数据库。

五、Redis缓存工具类【可解决缓存穿透,缓存击穿和缓存雪崩】

Redis中缓存的逻辑过期数据类

package com.org.utils;
 
import lombok.Data;
 
import java.time.LocalDateTime;
 
@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

Redis缓存工具类

package com.org.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 缓存客户端工具类,用于操作Redis缓存,支持普通缓存设置与逻辑过期缓存处理。
 */
@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 设置普通缓存。
     * @param key 缓存键
     * @param value 缓存值
     * @param time 过期时间
     * @param unit 时间单位
     */
    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 设置逻辑过期缓存。
     * 使用RedisData包装实际数据和过期时间。
     * @param key 缓存键
     * @param value 缓存值
     * @param time 过期时间
     * @param unit 时间单位
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    /**
     * 查询缓存并处理缓存穿透问题。
     * 如果数据库中也不存在,则将空值写入Redis一段时间。
     * @param keyPrefix 缓存键前缀
     * @param id 数据唯一标识
     * @param type 结果对象类型
     * @param dbFallback 数据库查询回调函数
     * @param time 缓存过期时间
     * @param unit 时间单位
     * @param <R> 结果类型
     * @param <ID> ID类型
     * @return 查询结果
     */
    public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            return JSONUtil.toBean(json, type);
        }
        // 命中空值表示之前查询过且数据库中也无此数据
        if (json != null) {
            return null;
        }
        R r = dbFallback.apply(id);
        if (r == null) {
            stringRedisTemplate.opsForValue().set(key, "", 2L, TimeUnit.MINUTES); // 写入空值避免缓存穿透
            return null;
        }
        set(key, r, time, unit);
        return r;
    }

    private static final ExecutorService rebuild_executor = Executors.newFixedThreadPool(10);

    /**
     * 查询逻辑过期缓存,如果过期则异步重建缓存。
     * 使用分布式锁防止重建缓存的并发问题。
     * @param keyPrefix 缓存键前缀
     * @param id 数据唯一标识
     * @param type 结果对象类型
     * @param dbFallback 数据库查询回调函数
     * @param time 缓存过期时间
     * @param unit 时间单位
     * @param <R> 结果类型
     * @param <ID> ID类型
     * @return 查询结果
     */
    public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback ,Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(json)) {
            return null; // 缓存中没有该key,直接返回null
        }
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        if (!expireTime.isAfter(LocalDateTime.now())) {
            // 缓存已过期,异步重建
            String lockKey = "lock_key" + id;
            if (tryLock(lockKey)) {
                rebuild_executor.submit(() -> {
                    try {
                        R apply = dbFallback.apply(id);
                        setWithLogicalExpire(key, apply, time, unit);
                    } catch (Exception e) {
                        throw new RuntimeException("缓存重建失败", e);
                    } finally {
                        unLock(lockKey);
                    }
                });
            }
        } else {
            // 未过期,直接返回
            return r;
        }
        return r; // 即使过期,在等待重建期间仍返回旧值(直至重建完成)
    }

    /**
     * 尝试获取分布式锁。
     * @param key 锁的键
     * @return 是否成功获取锁
     */
    private boolean tryLock(String key) {
        return BooleanUtil.isTrue(stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS));
    }

    /**
     * 释放分布式锁。
     * @param key 锁的键
     */
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

气泡水位计的安装方法详解(二)

气泡水位计的安装方法详解&#xff08;二&#xff09; 产品简介 气泡式水位计ZL-BWL-013是一款适用于水文、水利信息化建设领域的新一代水位测量类设备&#xff0c;产品执行GB/T 11828.2-2022标准。ZL-BWL-013气泡水位计&#xff0c;具有安装方便、易于操作&#xff0c;高精度…

VM中Ubuntu16.04的下载以及ROS—kinetic的版本下载

一、Ubuntu镜像地址 转载备份一下&#xff1b; 官方下载地址&#xff08;不推荐&#xff09; https://www.ubuntu.com/downloadhttps://www.ubuntu.com/download 中科大源 Index of /ubuntu-releases/16.04/http://mirrors.ustc.edu.cn/ubuntu-releases/16.04/ 阿里云开…

音视频开发9 FFmpeg 解复用框架说明,重要知识点

一&#xff0c;播放器框架 二 常用音视频术语 容器&#xff0f;文件&#xff08;Conainer/File&#xff09;&#xff1a; 即特定格式的多媒体文件&#xff0c; 比如mp4、flv、mkv等。 媒体流&#xff08;Stream&#xff09;&#xff1a; 表示时间轴上的一段连续数据&#xff0…

JVM学习-javap解析Class文件

解析字节码的作用 通过反编译生成字节码文件&#xff0c;可以深入了解Java工作机制&#xff0c;但自己分析类文件结构太麻烦&#xff0c;除了第三方的jclasslib工具外&#xff0c;官方提供了javapjavap是jdk自带的反解析工具&#xff0c;它的作用是根据class字节码文件&#x…

【GateWay】自定义RoutePredicateFactory

需求&#xff1a;对于本次请求的cookie中&#xff0c;如果userType不是vip的身份&#xff0c;不予访问 思路&#xff1a;因为要按照cookie参数进行判断&#xff0c;所以根据官方自带的CookieRoutePredicateFactory进行改造 创建自己的断言类&#xff0c;命名必须符合 xxxRout…

在virtualbox中ubuntu如何利用mobaxterm来拖拽文件

首先得先利用ssh、ubuntu的ip 一、开启ssh 安装 openssh-server sudo apt-get install openssh-server 检查 ssh 服务是否启动成功 sudo ps -e | grep ssh 如果有 sshd 则说明 ssh 服务已启动&#xff0c;如果没有启动&#xff0c;输入下边命令启动 ssh 服务 sudo servi…

东子哥:从来不拼搏的人,不是我的兄弟!新一轮裁员潮即将来临!

今年初&#xff0c;包括微软、亚马逊、谷歌母公司Alphabet等在内的巨头先后宣布裁员计划&#xff0c;曾掀起了一轮裁员潮。 进入年中阶段&#xff0c;特斯拉、理想汽车、TikTok、安德玛等知名巨头&#xff0c;也先后宣布裁员计划&#xff0c;难道&#xff0c;新一轮裁员潮已经…

长江电力:“你们随意,我躺赢”

“只要长江不断流&#xff0c;我们就躺着挣钱。”这是某股股吧里的股东们喊出的。 今天说的这个公司“没什么意思”&#xff0c;十年来股价一直涨一直涨&#xff0c;涨了5倍&#xff0c; &#xff08;最&#xff09;重要的是&#xff0c;持有体验特别好&#xff0c;几乎没有什…

HTTP -- HTTP概述

HTTP概述 HTTP使用的是可靠的数据传输协议。 web内容都是存储在web服务器上的&#xff0c;web服务器所使用的是http协议&#xff0c;故被称为http服务器。 web服务器是web资源的宿主&#xff0c;web资源是web内容的源头。 因特网上有数以千种的数据类型&#xff0c;http仔细的…

在Bash中解析命令行参数的两种样例脚本

文章目录 问题回答以空格分隔选项和参数以等号分隔选项和参数 参考 问题 假设&#xff0c;我有一个脚本&#xff0c;它会被这样一行调用: ./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile或者这个&#xff1a; ./myscript -v -f -d -o /fizz/someOtherFile ./fo…

C++ ─── string的模拟实现

本博客将简单实现来模拟实现string类&#xff0c;最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。 下期我们继续讲解完整版string的模拟实现&#xff08;将不再会是浅拷贝了&#xff09; 说明&#xff1a;下述string类没有显式定义其拷贝构造函数与赋值运…

30秒学会一个ChatGpt-4o小技巧 --- 照片漫画化

文章目录 选择照片修改图片 选择照片 先选择自己的一张照片 当然首先你得能够访问ChatGpt-4o, 图片生成能力只有ChatGpt-4才有 所以我们先登录到 国内能够使用的ChatGpt网站: 我要超级GPT 然后把图片上传&#xff0c;再写提示词&#xff1a;请帮我把这种照片按照日系动漫风…

【quarkus系列】构建可执行文件native image

目录 序言为什么选择 Quarkus Native Image&#xff1f;性能优势便捷的云原生部署 搭建项目构建可执行文件方式一&#xff1a;配置GraalVM方式二&#xff1a;容器运行错误示例构建过程分析 创建docker镜像基于可执行文件命令式构建基于dockerfile构建方式一&#xff1a;构建mic…

fpga系列 HDL 00 : 可编程逻辑器件原理

一次性可编程器件&#xff08;融保险丝实现&#xff09; 一次性可编程器件&#xff08;One-Time Programmable Device&#xff0c;简称 OTP&#xff09;是一种在制造后仅能编程一次的存储设备。OTP器件在编程后数据不可更改。这些器件在很多应用场景中具有独特的优势和用途。 …

重生之我要精通JAVA--第六周笔记

File 路径 相对路径 路径1&#xff1a;“a.txt” 路径2&#xff1a;“abc\\a.txt” 绝对路径 路径1&#xff1a;“c:\\a.txt” 路径2&#xff1a;“c:\\abc\\a.txt” File对象就表示一个路径&#xff0c;可以是文件的路径、也可以是文件夹的路径这个路径可以是存在的&…

c++ (命名空间 字符串)

思维导图&#xff1a; 定义自己得命名空间myspace,在myspace中定义string类型变量s1,再定义一个函数完成字符串逆置 #include <iostream> #include <cstring> //定义自己得命名空间myspace,在myspace中定义string类型变量s1,再定义一个函数完成字符串逆置 using n…

西门子smart line触摸屏软件安装 WinCC Flexible Smart V4SP1 V3

提示&#xff1a;Wincc flexible smart软件为西门子Smart line系列触摸屏的专用组态软件&#xff0c;这款屏不能用博途来组态&#xff0c;只能用这个软件来组态。西门子Smart line系列触摸屏的常用型号为SMART 700 IE V3/V4&#xff0c;SMART 1000 IE V3/V4。 Wincc flexible …

引力为什么会让时间变慢,给你通俗的解读

爱因斯坦的狭义相对论表明&#xff0c;速度会让时间变慢&#xff0c;速度越快时间就越慢。而广义相对论告诉我们&#xff0c;引力同样会让时间变慢&#xff0c;引力越强时间就越慢。 时间膨胀 速度对时间的影响就先不解释了&#xff0c;之前的科普文章介绍了很多&#xff0c;今…

Android开发 -- JNI开发

1.配置JNI环境 创建JNI文件夹 在项目的主目录中创建一个名为 JNI 的文件夹。这个文件夹将包含所有的本地源代码和配置文件。 编写Android.mk文件 这个文件是一个 Makefile&#xff0c;用来指导 NDK 如何编译和构建本地代码。 #清除之前定义的变量&#xff0c;确保每个模块的…

电流采样(分流器与霍尔传感器)

在对于电信号采集的设计中&#xff0c;其中对电流信号的采集是非常常见的&#xff0c;根据电流信号的属性&#xff0c;如信号大小、信号周期等因素&#xff0c;以及采样的需求指标不一样&#xff0c;往往需要选择不同的采样方式进行采样。 下面主要介绍分流器和霍尔元件采样电…