高并发缓存实战RedisSon、性能优化

news2024/10/5 18:32:17

高并发缓存实战RedisSon、性能优化

分布式锁性能提升

1.数据冷热分离

对于经常访问的数据保留在redis缓存当中,不用带数据设置超时时间定期删除控制redis的大小

 String productStr = redisUtil.get(productCacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            product = JSON.parseObject(productStr, Product.class);
            redisUtil.expire(productCacheKey, 30000, TimeUnit.SECONDS); //读延期
        }

2.缓存击穿(失效)

缓存击穿数据库没有被击穿

redisUtil.expire(productCacheKey, 30000, TimeUnit.SECONDS); 

如果商家是批量导入的数据,呢么就会同时存到redis中,设置固定的时间就会导致缓存在一瞬间失效,用户访问不到就会将流量打到数据库上造成数据库段时间内抖动。

解决办法:

redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
private Integer genProductCacheTimeout() {
  //过期时间是不一样的
        return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;
    }

3.缓存穿透

缓存和数据库都没有结果

缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,导致请求直接访问数据库或后端服务,从而影响系统性能甚至崩溃。其发生的原因可能是恶意攻击、不合理的业务逻辑和数据分布等。

例如,一个ID为负数或者非法字符的请求,即使被缓存,也是无效的,每次都需要访问数据库,造成了服务器资源的浪费。如果遇到大量的这种请求,就有可能导致缓存失效,直接访问数据库,甚至造成服务器瘫痪。

为了防止缓存穿透,我们可以采取以下措施:

  1. 在业务层面对恶意攻击进行限制,如合法字符过滤、请求频率限制等。
  2. 对于查询结果为空的情况,在缓存中设置一个空对象。
  3. 使用布隆过滤器(Bloom Filter)等技术来预先过滤掉不合法的请求,减轻数据库的压力。
  4. 将热点数据(频繁访问的数据)预先加载到缓存中,以提高缓存命中率。
  5. 设置合理的缓存过期时间,防止缓存中一直存在无效数据。

通过以上措施,可以有效地减少缓存穿透问题的发生,提高系统的性能和稳定性。

3.1对于查询结果为空的情况,在缓存中设置一个空对象

对于数据库也无法访问到数据的,首次访问后设置“{}”到缓存中防止每次都访问数据库增加数据库的io压力,第二次直接可以命中缓存,针对黑客使用多个商品id对redis进行攻击的状况,我们可以设置一个超时时间并延期,时间设置60-90s,防止大量的id撑大redis的存储

   product = getProductFromCache(productCacheKey);
            if (product != null) {
                return product;
            }
 if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product),
                            genProductCacheTimeout(), TimeUnit.SECONDS);
                    productMap.put(productCacheKey, product);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                }
  private Integer genEmptyCacheTimeout() {
        return 60 + new Random().nextInt(30);
    }

4.DCL解决突发热点的新增缓存

        product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }
        //DCL
        RLock hotCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
        hotCacheLock.lock();
        //boolean result = hotCacheLock.tryLock(3, TimeUnit.SECONDS);
        try {
            product = getProductFromCache(productCacheKey);
            if (product != null) {
                return product;
            }
            try {
                product = productDao.get(productId);
                if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product),
                            genProductCacheTimeout(), TimeUnit.SECONDS);
                    productMap.put(productCacheKey, product);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                }
            } finally {
                rLock.unlock();
            }
        } finally {
            hotCacheLock.unlock();
        }

不能使用synchronized,原因:

1.只在单节点的jvm中生效,每个节点都会新增一次(非主要问题)

2.this会锁住这个类,当product0001需要加锁新建缓存的时候,product0001的所有进程都必须要等待,这没有问题,但是product0003、product0004。。。也都需要等待,这就导致了业务被阻塞了,效率低下

所以需要分布式锁

5.缓存数据库双些不一致

image-20230616170740853

    product = getProductFromCache(productCacheKey);
    if (product != null) {
        return product;
    }
    //DCL
    RLock hotCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
    hotCacheLock.lock();
    //boolean result = hotCacheLock.tryLock(3, TimeUnit.SECONDS);
    try {
        product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }

       RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
      //synchronized (this)  不是分布式的只能锁住当前jvm进程
        //synchronized (this){
        try {
            product = productDao.get(productId);
            if (product != null) {
                redisUtil.set(productCacheKey, JSON.toJSONString(product),
                        genProductCacheTimeout(), TimeUnit.SECONDS);
                productMap.put(productCacheKey, product);
            } else {
                redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
            }
        } finally {
            rLock.unlock();
        }
    } finally {
        hotCacheLock.unlock();
    }
image-20230616171752532

加锁限制

控制查和写的中间不能插入执行其他的逻辑,在查询和修改的代码中都需要加锁

6.代码的复杂度

其实大部分情况下只有很小一块会被执行,大部分代码块是在完善各种逻辑,但是也是在取舍寻找一种最合适的方案

7.锁优化

1.分段锁

库存1000 则可分为 produc_1 - produc_10 10个线程可以同时执行这段逻辑

2.读写锁

@Transactional
    public Product update(Product product) {
        Product productResult = null;
        //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RLock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        try {
            productResult = productDao.update(product);
            redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                    genProductCacheTimeout(), TimeUnit.SECONDS);
            productMap.put(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), product);
        } finally {
            writeLock.unlock();
        }
        return productResult;
    }

    public Product get(Long productId) throws InterruptedException {
        Product product = null;
        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;

        product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }
        //DCL 加分布式锁解决热点缓存并发重建问题
        RLock hotCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
        hotCacheLock.lock();
        //boolean result = hotCacheLock.tryLock(3, TimeUnit.SECONDS);
        try {
            product = getProductFromCache(productCacheKey);
            if (product != null) {
                return product;
            }

            //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RLock rLock = readWriteLock.readLock();
            rLock.lock();
            //synchronized (this)  不是分布式的只能锁住当前jvm进程
            //synchronized (this){
            try {
                product = productDao.get(productId);
                if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product),
                            genProductCacheTimeout(), TimeUnit.SECONDS);
                    productMap.put(productCacheKey, product);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                }
            } finally {
                rLock.unlock();
            }
        } finally {
            hotCacheLock.unlock();
        }
        return product;
    }

对同一个id的商品加上同一把锁,读操作加读锁 ,写操作加鞋锁,读锁遇到读锁不会阻断

读写锁是一种用于并发编程中的同步机制。与普通锁相比,读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这样就可以提高并发性和系统吞吐量。

读写锁通常由读锁和写锁两部分组成。当一个线程想要读取共享资源时,它必须获取读锁,并且当没有写线程占用锁时,可以同时有多个读线程同时获取读锁。当一个线程想要修改共享资源时,它必须获取写锁,而不允许其他任何读或写线程获取锁,直到它释放写锁。

使用读写锁可以有效地减少锁竞争,提高程序性能,特别是在多读少写的情况下。但是,过度使用读写锁也会导致一些问题,例如读线程饥饿、写线程优先级过低等。因此,在使用时需要谨慎考虑锁的使用场景和锁的粒度。

  1. 读操作时,每个线程可以直接获取锁,不需要等待其他线程释放读锁,因为读操作是并发执行的,不会破坏数据的完整性。
  2. 写操作时,只有一个线程可以获取写锁,其他线程需要等待写锁释放后才能继续执行。Redisson中通过分布式锁的方式来实现写锁的互斥执行。当一个线程请求获取写锁时,Redisson会在Redis服务器上创建一个对应的分布式锁,只有该线程能够成功获取该分布式锁,其他线程则需要等待。

image-20230619090846138

  @Override
    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);

        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                                "local mode = redis.call('hget', KEYS[1], 'mode'); " +
                                "if (mode == false) then " +
                                  "redis.call('hset', KEYS[1], 'mode', 'read'); " +
                                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                                  "redis.call('set', KEYS[2] .. ':1', 1); " +
                                  "redis.call('pexpire', KEYS[2] .. ':1', ARGV[1]); " +
                                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                  "return nil; " +
                                "end; " +
                                "if (mode == 'read') or (mode == 'write' and redis.call('hexists', KEYS[1], ARGV[3]) == 1) then " +
                                  "local ind = redis.call('hincrby', KEYS[1], ARGV[2], 1); " + 
                                  "local key = KEYS[2] .. ':' .. ind;" +
                                  "redis.call('set', key, 1); " +
                                  "redis.call('pexpire', key, ARGV[1]); " +
                                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                  "return nil; " +
                                "end;" +
                                "return redis.call('pttl', KEYS[1]);",
                        Arrays.<Object>asList(getName(), getReadWriteTimeoutNamePrefix(threadId)), 
                        internalLockLeaseTime, getLockName(threadId), getWriteLockName(threadId));
    }
  1. 首先获取指定键(KEYS[1])的值,并获取该键对应哈希表中mode字段的值,判断是否存在。如果不存在,则设置mode为read,表明当前锁状态为读锁;并将当前线程加入到指定键对应的哈希表中,并创建用于记录当前线程的计数器的哈希表,并设置过期时间(ARGV[1])。最后返回nil,表示当前线程成功获取读锁。

  2. 如果mode字段的值为read,或者为write并且当前线程已经获取了该锁,则将哈希表中该线程的计数器加1,并在单独的哈希表中创建一个记录当前线程持有读锁的键值对,同样设置过期时间。最后返回nil,表示当前线程成功续订读锁。否则,等待其他线程释放读锁。

  3. 如果mode字段的值为write但是当前线程没有获取该锁,则表明当前锁状态为写锁,不能获取读锁。此时返回当前锁的剩余过期时间(即当前线程等待获取锁的时间),以便客户端等待。

    @Override
    <T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
        internalLockLeaseTime = unit.toMillis(leaseTime);
    
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
                            "local mode = redis.call('hget', KEYS[1], 'mode'); " +
                            "if (mode == false) then " +
                                  "redis.call('hset', KEYS[1], 'mode', 'write'); " +
                                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                                  "return nil; " +
                              "end; " +
                              "if (mode == 'write') then " +
                                  "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                                      "redis.call('hincrby', KEYS[1], ARGV[2], 1); " + 
                                      "local currentExpire = redis.call('pttl', KEYS[1]); " +
                                      "redis.call('pexpire', KEYS[1], currentExpire + ARGV[1]); " +
                                      "return nil; " +
                                  "end; " +
                                "end;" +
                                "return redis.call('pttl', KEYS[1]);",
                        Arrays.<Object>asList(getName()), 
                        internalLockLeaseTime, getLockName(threadId));
    }
    
  4. 首先获取指定键(KEYS[1])的值,并获取该键对应哈希表中mode字段的值,判断是否存在。如果不存在,则设置mode为write,表明当前锁状态为写锁;并将当前线程加入到指定键对应的哈希表中,并设置过期时间(ARGV[1])。最后返回nil,表示当前线程成功获取写锁。

  5. 如果mode字段的值为write,则说明当前锁状态为写锁,需要判断当前线程是否已经获取了该锁。如果已经获取了该锁,则将哈希表中该线程的计数器加1,并更新锁的过期时间;最后返回nil,表示当前线程成功续订写锁。否则,等待其他线程释放写锁。

  6. 如果mode字段的值为非write,则说明当前锁状态为读锁,不能获取写锁。此时返回当前锁的剩余过期时间(即当前线程等待获取锁的时间),以便客户端等待。

    后续需要研究读写锁的性能问题

3.串行转并发

  RLock hotCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
        hotCacheLock.lock();
        //boolean result = hotCacheLock.tryLock(3, TimeUnit.SECONDS);

等待1s之后,锁返回false,如果缓存此时已经将需要的数据加载到缓存当中则可以直接命中缓存返回

但是如果1s内没有完成数据的加载会导致缓存击穿

8.redis多级缓存

一个key会存在redis的一个单节点上,一个redis节点优化后可抗10万并发,会导致各个微服务,功能出现问题,导致大规模的缓存问题,

可以进行限流、降级控制,如果限流出现问题,代码也可以做响应的处理,—多级缓存

JVM的抗并发100万级别,需要不同节点同步(队列) 会出现段时间的数据不一致

Redis多级缓存指的是将Redis和其他缓存系统(如本地内存缓存、Memcached等)结合使用,实现分层缓存的机制。多级缓存通常由两个或多个层次组成:

  1. 一级缓存:本地内存缓存,主要用于缓存热点数据,可以快速响应客户端请求,减轻Redis服务器的压力。
  2. 二级缓存:Redis缓存,主要用于持久化缓存数据,保证数据的可靠性,并将数据分布在多个节点上,提高缓存的并发访问能力。

使用多级缓存的好处在于可以充分利用各个缓存的优势,快速响应客户端请求,提高访问效率。具体操作步骤如下:

  1. 对于读取数据的请求,先从本地内存缓存中读取数据,如果没有则从Redis缓存中读取,并更新本地内存缓存,以便下一次请求时直接从本地内存缓存中获取。
  2. 对于写入数据的请求,先更新本地内存缓存,再同步更新Redis缓存中的数据。

需要注意的是,在使用多级缓存的过程中,需要保证缓存数据的一致性和可靠性。当二级缓存中的数据发生变化时,需要及时更新一级缓存中的数据,否则会导致数据不一致。此外,需要考虑数据的失效策略和缓存节点选择的问题,避免缓存雪崩或缓存穿透等问题的发生。

对于这种热点中的热点商品,需要一个专门的系统进行实时的维护

9.完整代码

@Service
public class ProductService {

    @Autowired
    private ProductDao productDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private Redisson redisson;

    public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;
    public static final String EMPTY_CACHE = "{}";
    public static final String LOCK_PRODUCT_HOT_CACHE_PREFIX = "lock:product:hot_cache:";
    public static final String LOCK_PRODUCT_UPDATE_PREFIX = "lock:product:update:";
    //Map 伪代码 图一乐 真实使用多级缓存框架 热点系统
    public static Map<String, Product> productMap = new ConcurrentHashMap<>();

    @Transactional
    public Product create(Product product) {
        Product productResult = productDao.create(product);
        redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                genProductCacheTimeout(), TimeUnit.SECONDS);
        return productResult;
    }

    @Transactional
    public Product update(Product product) {
        Product productResult = null;
        //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RLock writeLock = readWriteLock.writeLock();
        writeLock.lock();
        try {
            productResult = productDao.update(product);
            redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                    genProductCacheTimeout(), TimeUnit.SECONDS);
            productMap.put(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), product);
        } finally {
            writeLock.unlock();
        }
        return productResult;
    }

    public Product get(Long productId) throws InterruptedException {
        Product product = null;
        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;

        product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }
        //DCL 加分布式锁解决热点缓存并发重建问题
        RLock hotCacheLock = redisson.getLock(LOCK_PRODUCT_HOT_CACHE_PREFIX + productId);
        hotCacheLock.lock();
        //boolean result = hotCacheLock.tryLock(3, TimeUnit.SECONDS);
        try {
            product = getProductFromCache(productCacheKey);
            if (product != null) {
                return product;
            }

            //RLock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RLock rLock = readWriteLock.readLock();
            rLock.lock();
            //synchronized (this)  不是分布式的只能锁住当前jvm进程
            //synchronized (this){
            try {
                product = productDao.get(productId);
                if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product),
                            genProductCacheTimeout(), TimeUnit.SECONDS);
                    productMap.put(productCacheKey, product);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                }
            } finally {
                rLock.unlock();
            }
        } finally {
            hotCacheLock.unlock();
        }
        return product;
    }


    private Integer genProductCacheTimeout() {
        return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;
    }

    public static void main(String[] args) {
       System.out.println( new Random().nextInt(5) * 60 * 60);
    }

    private Integer genEmptyCacheTimeout() {
        return 60 + new Random().nextInt(30);
    }

    private Product getProductFromCache(String productCacheKey) {
        Product product = productMap.get(productCacheKey);
        if (product != null) {
            return product;
        }

        String productStr = redisUtil.get(productCacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            if (EMPTY_CACHE.equals(productStr)) {
                redisUtil.expire(productCacheKey, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                return new Product();
            }
            product = JSON.parseObject(productStr, Product.class);
            redisUtil.expire(productCacheKey, genProductCacheTimeout(), TimeUnit.SECONDS); //读延期
        }
        return product;
    }

}

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

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

相关文章

docker搭建nginx

一、安装Docker 1、安装&#xff1a; yum install docker 2、启动/停止/重启docker服务 service docker start service docker stop service docker restart 3、查看docker版本信息 docker version 4、查看所有docker镜像 docker images 二、安装Nginx 1、拉取Nginx镜像…

关于POL网络中的ODN部署方案,这些你都了解吗?

近年来&#xff0c;行业的智能化和信息化呈现加速发展趋势&#xff0c;高清视频会议、云服务、移动办公等应用几乎成为企业标配。与此同时&#xff0c;带宽的接入、升级和物联网融合等网络新要求也变得越来越迫切&#xff0c;网络架构升级成为企业解决网络难题的一个新选择。 …

Python基础(10)——Python条件语句

Python基础&#xff08;10&#xff09;——Python条件语句 文章目录 Python基础&#xff08;10&#xff09;——Python条件语句目标一. 了解条件语句二. if 语法2.1 语法2.2 快速体验 三. 实例&#xff1a;上网3.1 简单版3.2 进阶版 四. if...else...4.1 语法4.2 实用版&#x…

KSM01.2B-061C-35N-M1-HP2-SE-NN –D7-NN-FW

​ KSM01.2B-061C-35N-M1-HP2-SE-NN –D7-NN-FW KSM01.2B-061C-35N-M1-HP2-SE-NN –D7-NN-FW 集散控制的基本思想是集中管理&#xff0c;分散控制。即&#xff1a;将流程工业的自动控制过程与操作管理人员对自动控制过程的管理过程相对分离&#xff1b;流程工业的自动控制过程…

Dump寄存器使用、解析

前人种树&#xff0c;后人乘凉&#xff1b;创造不易&#xff0c;请勿迁移~ author daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主daisy.skye擅长嵌入式,Qt,Linux,等方面的知识https://blog.csdn.net/qq_40715266?t…

git的安装与配置教程-超详细版

一、git的安装 1、下载git git官网地址&#xff1a;https://git-scm.com/download/win/ 选择所需要的版本&#xff0c;进行下载。 2、下载完成之后&#xff0c;双击下载好的exe文件进行安装。 3、默认是C盘&#xff0c;推荐修改一下路径&#xff08;非中文并且没有空格&…

2021电工杯数学建模B题解题思路

目录 一、前言 二、问题背景 三、具体问题 四、解题思路 &#xff08;一&#xff09;整体思路 &#xff08;二&#xff09;问题一 &#xff08;三&#xff09;问题二 &#xff08;四&#xff09;问题三 &#xff08;五&#xff09;问题四 &#xff08;六&#xff09;…

浅谈kubernetes部署:UI部署

UI部署 镜像制作 登录私服 以阿里云docker私服举例 sudodockerlogin—usernameregistry.cn-beijing.aliyuncs.com 制作UI和静态页镜像 参考&#xff1a; 《前端镜像制作》 《openresty镜像制作》 修改yaml文件 vi/opt/kubernetes/ui.yaml 修改相应image值为您的镜像目录 部…

VTK学习之边缘检测(梯度算子)

参考博客&#xff1a;VTK修炼之道32&#xff1a;边缘检测_梯度算子_基于梯度的边缘检测算子_沈子恒的博客-CSDN博客 直接上源码&#xff1a; #include <vtkAutoInit.h> #include <vtkSmartPointer.h> #include <vtkJPEGReader.h> #include <vtkImageGra…

屏幕录制没有声音?快看看这2个方法!

案例&#xff1a;我今天尝试在电脑上进行屏幕录制&#xff0c;一开始一切都挺正常的。直到结束后&#xff0c;查看刚刚录制的视频发现没有声音。 【录屏只有画面没有声音&#xff0c;会影响视频的观感体验&#xff0c;甚至你根本不知道视频想表达的意思。那录屏的同时如何录制…

计算机基础--->网络(1)【分层模型、网络协议、HTTP等】

文章目录 网络分层模型OSI七层模型及其作用TCP/IP四层模型及作用为什么网络需要分层&#xff1f; 常见的网络协议应用层常见的协议传输层常见的协议网络层常见协议 从输入URL到页面展示的过程HTTP常见的状态码HTTP与HTTPS的区别HTTP是不保存状态的协议&#xff0c;如何保存用户…

华硕电脑怎么用U盘重装系统Win10?

华硕电脑怎么用U盘重装系统Win10&#xff1f;用户想用U盘给华硕电脑重装Win10系统&#xff0c;但不知道要怎么操作&#xff0c;这时候用户需要准备一台能够正常联网的华硕电脑&#xff0c;还有一个8G以上的U盘&#xff0c;最后根据小编分享的华硕电脑用U盘重装Win10系统教程操作…

MQTT Broker 规则引擎入门:快速指南

引言 规则引擎是一种能够根据输入数据按照预设规则进行决策或执行动作的软件系统。本文将向您介绍 EMQX MQTT Broker 的规则引擎功能&#xff0c;并阐述其在 MQTT 消息转换和数据集成方面的重要作用。同时&#xff0c;我们还将提供一份快速入门指南&#xff0c;通过实例帮助您…

【Spring Cloud】Gateway的配置与使用

Gateway其实是springcloud 原生的东西&#xff0c;但是我还是想放在这里讲&#xff0c;因为我们使用nacos时&#xff0c;前端调用服务之后&#xff0c;一般会调用到我们的网关上面&#xff0c;然后网关选择我们的nacos服务&#xff0c;再调用后端的服务 文章目录 &#x1f30f;…

极客故事|AI Hackathon:从每一个微小的时刻开始

上周末&#xff0c;由 SegmentFault 思否和 ONES 主办&#xff0c;SegmentFault AI Hackathon 杭州站暨思否 11 周年特别活动在杭州圆满结束。大赛延续 Hack with AI, Rebuild Everything with AI 的主题&#xff0c;鼓励开发者使用 Generative AI 技术构建创新应用&#xff0c…

UDP和TCP网络编程

UDP和TCP网络编程 UDP网络编程UDP通信流程&#xff08;回显服务&#xff09;测试扩展《UDP字典查找单词》 TCP网络编程TCP互相通信测试 缓存区和缓存 UDP网络编程 特点&#xff1a; 无连接&#xff1a;发送数据前不需要建立连接。不可靠&#xff1a;没有重发机制&#xff0c;无…

2023下半年杭州/广州/深圳软考(中/高级)认证,近期开班

软考是全国计算机技术与软件专业技术资格&#xff08;水平&#xff09;考试&#xff08;简称软考&#xff09;项目&#xff0c;是由国家人力资源和社会保障部、工业和信息化部共同组织的国家级考试&#xff0c;既属于国家职业资格考试&#xff0c;又是职称资格考试。 系统集成…

一分钟让你轻松拿捏 求解斐波那契数列!

文章目录 斐波那契数列的概念递归求解第N个斐波那契数迭代求解第N个斐波那契数递归法和迭代法的比较 斐波那契数列的概念 斐波那契数列&#xff08;Fibonacci sequence&#xff09;&#xff0c;又称黄金分割数列&#xff0c;因数学家莱昂纳多斐波那契&#xff08;LeonardodaFib…

windows10企业版安装西门子博途V15---01准备环境

网上看到了很多博途安装的文章或视频&#xff0c;一大部分都是你抄抄&#xff0c;我抄抄&#xff0c;滥鱼充饥&#xff0c;一是文章思路不清晰&#xff0c;二是具体安装环境不一致&#xff0c;三是视频讲解混乱&#xff0c;视频不清楚&#xff0c;操作有错误&#xff0c;其中不…

mac外接硬盘在哪里打开 mac外接硬盘用什么格式

Mac电脑具有出色的兼容性和高度的易用性&#xff0c;使得连接外接硬盘变得非常简单。但是&#xff0c;如果你不知道如何打开外接硬盘或者外接硬盘应该使用哪种格式&#xff0c;那么这将成为你使用Mac电脑过程中的一个难题。本篇文章将会向你介绍在Mac上如何打开外接硬盘&#x…