后端系统缓存技术分析

缓存有很多种,如CPU 缓存、磁盘缓存、浏览器缓存等;本文主要针对后端系统的缓存,也就是将程序或系统经常要使用的对象存在内存中,以便在使用时可以快速调用,避免加载数据或者创建重复的实例,以达到减少系统开销,提高系统效率的目的。

粉丝福利, 免费领取C/C++ 开发学习资料包、技术视频/代码,1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件,后端开发/音视频开发/Qt开发/游戏开发/Linuxn内核等进阶学习资料和最佳学习路线)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

为什么需要缓存?

我觉得操作系统里面讲高速缓存(CPU Cache)的一句话特别好:

为什么要使用CPU Cache?

因为CPU 和内存访问性能的差距非常大,为了弥补两者之间的性能差异,充分利用 CPU,现代 CPU 中引入了高速缓存(CPU Cache)。

因为我们一般都会把数据存放在关系型数据库中,不管数据库的性能有多么好,受限于磁盘IO性能和远程网络的原因,一个简单的查询也要消耗毫秒级的时间,这样我们的 QPS 就会被数据库的性能所限制。

而内存的性能高于磁盘,也没有远程网络的花费。如果恰好这些数据的数据量不大,不经常变动,而且访问频繁。那么就非常适合引入缓存。

总结一下,缓存使用场景:数据不常变更且查询比较频繁是最好的场景,如果查询量不够大或者数据变动太频繁,缓存使用的意义不大,甚至可能适得其反。

缓存的类型

常用的缓存可以分为内部缓存和外部缓存。

内部缓存是指存放在运行实例内部并使用实例内存的缓存,这种缓存可以使用代码直接访问。

外部缓存是指存放在运行实例外部的缓存,通常是通过网络获取,反序列化后进行访问。

一般来说对于不需要实例间同步的,都更加推荐内部缓存,因为内部缓存有访问方便,性能好的特点;需要实例间同步的数据可以使用外部缓存。

内部缓存

内部缓存有容量的限制,毕竟还是在 JVM 里的,存放的数据总量不能超出内存容量。

本地缓存的优点:

  • 直接使用内存,速度快,通常存取的性能可以达到每秒千万级
  • 可以直接使用 Java 对象存取

本地缓存的缺点:

  • 数据保存在当前实例中,无法共享
  • 重启应用会丢失

最简单内部缓存-Map

对于字典型的数据,在项目启动的时候加载到 Map 中,程序就可以使用了,也很容易更新。

    Map<String, String> configs = Maps.newHashMap();public void reloadConfigs(){configs =  loadConfigFromDB();}public String getConfigs(String key){return configs.getOrDefault(key, "null");}

但是JDK自带的Map做缓存存在一定的问题:

1、hashmap的容量理论上是无上限的(内存无限的情况下),如果缓存太大会严重的占用内存,甚至导致OutOfMemory异常,因此应该限制缓存的容量,容量达到上限后采取合适的淘汰策略。

有准备面试的童鞋应该知道,这就是面试中常见的实现LRU缓存。

实现方式一:可以通过继承LinkedHashMap重写removeEldestEntry()方法实现。

class LRUCache extends LinkedHashMap<Integer, Integer>{private int capacity;public LRUCache(int capacity) {super(capacity, 0.75F, true);this.capacity = capacity;}public int get(int key) {return super.getOrDefault(key, -1);}public void put(int key, int value) {super.put(key, value);}@Overrideprotected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {return size() > capacity; }
}

实现方式二,通过HashMap加双向链表的方式实现。

public class LRUCache {class DLinkedNode {int key;int value;DLinkedNode prev;DLinkedNode next;public DLinkedNode() {}public DLinkedNode(int key, int value) {key = key; value = value;}}private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();private int size;private int capacity;private DLinkedNode head, tail;public LRUCache(int capacity) {this.size = 0;this.capacity = capacity;// 使用伪头部和伪尾部节点head = new DLinkedNode();tail = new DLinkedNode();head.next = tail;tail.prev = head;}public int get(int key) {DLinkedNode node = cache.get(key);if (node == null) {return -1;}// 如果 key 存在,先通过哈希表定位,再移到头部moveToHead(node);return node.value;}public void put(int key, int value) {DLinkedNode node = cache.get(key);if (node == null) {// 如果 key 不存在,创建一个新的节点DLinkedNode newNode = new DLinkedNode(key, value);// 添加进哈希表cache.put(key, newNode);// 添加至双向链表的头部addToHead(newNode);++size;if (size > capacity) {// 如果超出容量,删除双向链表的尾部节点DLinkedNode tail = removeTail();// 删除哈希表中对应的项cache.remove(tail.key);--size;}}else {// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部node.value = value;moveToHead(node);}}private void addToHead(DLinkedNode node) {node.prev = head;node.next = head.next;head.next.prev = node;head.next = node;}private void removeNode(DLinkedNode node) {node.prev.next = node.next;node.next.prev = node.prev;}private void moveToHead(DLinkedNode node) {removeNode(node);addToHead(node);}private DLinkedNode removeTail() {DLinkedNode res = tail.prev;removeNode(res);return res;}
}

2、无法设置过期时间。这一点是非常致命的,我们在大多数场景都希望缓存可以在合理的时间后自动失效。

3、并发安全。HashMap、LinkedHashMap都是非并发安全的集合类型。所以应该使用ConcurrentHashMap。

使用ConcurrentHashMap、ReentrantLock 和ScheduledThreadPoolExecutor可以实现带过期时间线程安全的缓存设计。

4、清除数据时的回调通知。

由于代码篇幅过长,见附录1

功能强大的内部缓存-Guava Cache

如果需要缓存有强大的性能,或者对缓存有更多的控制,可以使用 Guava 里的 Cache 组件。

它是 Guava 中的缓存工具包,是非常简单易用且功能强大的 JVM 内缓存。

1、支持多种缓存过期策略:基于容量回收、定时回收、基于引用回收

2、自动加载:

3、显示清除缓存:个别、批量、全量清除

4、移除监听器

5、统计功能:缓存命中率,加载新值的平均时间,缓存项被回收的总数

    LoadingCache<String, String> configs = CacheBuilder.newBuilder().maximumSize(1000) // 设置最大大小.expireAfterWrite(10, TimeUnit.MINUTES) // 设置过期时间, 10分钟.build(new CacheLoader<String, String>() {// 加载缓存内容public String load(String key) throws Exception {return getConfigFromDB(key);}public Map<String, String> loadAll() throws Exception {return loadConfigFromDB();}});//CacheLoader.loadAll// 获取某个key的值try {return configs.get(key);} catch (ExecutionException e) {throw new OtherException(e.getCause());}// 显式的放入缓存configs.put(key, value)// 个别清除缓存configs.invalidate(key)// 批量清除缓存configs.invalidateAll(keys)// 清除所有缓存项configs.invalidateAll()

Guava Cache的使用

1、创建(加载)cache

两种方法 CacheLoader和Callable。

LoadingCache userCache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(30, TimeUnit.MINUTES).build(new CacheLoader() {@Overridepublic Object load(Object name) throws Exception {return getIdFromDBByName(name);}});
Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(100).expireAfterAccess(30, TimeUnit.MINUTES).build();
String resultVal = cache.get("hello", new Callable<String>() {@Overridepublic String call() throws Exception {return "hello" + "heitao";}
});

以上两种,都实现了一种逻辑:先取缓存——娶不到再执行load 或者call方法来娶。

简单比较,CacheLoader 是按key统一加载,所有娶不到的统一执行一种load逻辑;而callable方法允许在get的时候根据指定key值执行对应的call方法。

在使用缓存前,拍拍自己的四两胸肌,问自己一个问题:有没有【默认方法】来加载或计算与键关联的值?如果有的话,你应当使用CacheLoader。如果没有,或者想要覆盖默认的加载运算。你应该在调用get时传入一个Callable实例。

2、添加、插入key

get : 要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值;

getUnchecked:CacheLoader 会抛异常,定义的CacheLoader没有声明任何检查型异常,则可以 getUnchecked 查找缓存;反之不能;

getAll :方法用来执行批量查询;

put : 向缓存显式插入值,Cache.asMap()也能修改值,但不具原子性;

getIfPresent :该方法只是简单的把Guava Cache当作Map的替代品,不执行load方法;

3、清除key

guava cache 自带 清除机制,但仍旧可以手动清除:

个别清除:Cache.invalidate(key)

批量清除:Cache.invalidateAll(keys)

清除所有缓存项:Cache.invalidateAll()

4 refresh和expire刷新机制

expireAfterAccess(30, TimeUnit.MINUTES ) // 30min内没有被读或写就会被回收 ;

expireAfterWrite(30, TimeUnit.MINUTES ) // 30min内没有没有更新就会被回收 :

refreshAfterAccess(30, TimeUnit.MINUTES) //上一次更新操作30min后再刷新;

注意:和redis的惰性回收机制一样

30min内没有被读或写就会被回收” 不等于 “30min内会被回收” ,因为真正的过期/刷新操作是在key被读或写时发生的。

只有发生“取值”操作,才会执行load,然而为了防止“缓存穿透”,在多线程的环境下,任何时刻只允许一个线程操作执行load操作 。

但在执行load操作这个步骤,expire 与 refresh 的线程机制不同:

expire 在load 阶段——同步机制:当前线程load未完成,其他线程呈阻塞状态,待当前线程load完成,其他线程均需进行”获得锁--获得值--释放锁“的过程。这种方法会让性能有一定的损耗。

refresh 在load阶段——异步机制 :当前线程load未完成,其他线程仍可以取原来的值,等当前线程load完成后,下次某线程再取值时,会判断系统时间间隔是否时超过设定refresh时间,来决定是否设定新值。所以,refresh机制的特点是,设定30分钟刷新,30min后并不一定就是立马就能保证取到新值

expire与refresh 都能控制key得回收,究竟如何选择?

答案是,两个一起来!

只要refresh得时间小于expire时间,就能保证多线程在load取值时不阻塞,也能保证refresh时间到期后,取旧值向新值得平滑过渡,当然,仍旧不能解决取到旧值得问题。

5 监听

在guava cache中移除key可以设置相应得监听操作,以便key被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。监听有同步监听和异步监听两种 :

同步监听:

默认情况下,监听器方法是被同步调用的(在移除缓存的那个线程中执行),执行清理key的操作与执行监听是单线程模式,当然监听器中抛出的任何异常都不会影响其正常使用,顶多把异常写到日记了。

    @Testpublic void testCacheRemovedNotification(){CacheLoader<String, String> loader = CacheLoader.from(String::toUpperCase);RemovalListener<String, String> listener = notification -> {if (notification.wasEvicted()){RemovalCause cause = notification.getCause();System.out.println("remove case is : " + cause.toString());System.out.println("key: "+notification.getKey()+" value:" + notification.getValue());}};LoadingCache<String, String> cache = CacheBuilder.newBuilder().maximumSize(4).removalListener(listener).build(loader);cache.getUnchecked("maomao");cache.getUnchecked("wangwang");cache.getUnchecked("guava");cache.getUnchecked("cache");cache.getUnchecked("hello");}

异步监听:

假如在同步监听模式下,监听方法中的逻辑特别复杂,执行效率慢,那此时如果有大量的key进行清理,会使整个缓存性能变得很低下,所以此时适合用异步监听,移除key与监听key的移除分属2个线程。

    @Testpublic void testCacheRemovedNotification(){CacheLoader<String, String> loader = CacheLoader.from(String::toUpperCase);RemovalListener<String, String> listener = notification -> {if (notification.wasEvicted()){RemovalCause cause = notification.getCause();System.out.println("remove case is : " + cause.toString());System.out.println("key: "+notification.getKey()+" value:" + notification.getValue());}};LoadingCache<String, String> cache = CacheBuilder.newBuilder().maximumSize(4).removalListener(RemovalListeners.asynchronous(listener, Executors.newSingleThreadExecutor())).build(loader);cache.getUnchecked("maomao");cache.getUnchecked("wangwang");cache.getUnchecked("guava");cache.getUnchecked("cache");cache.getUnchecked("hello");}

6 统计

guava cache还有一些其他特性,比如weight 按权重回收资源,统计等,这里列出统计。CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后Cache.stats()方法返回如下统计信息:

  • hitRate():缓存命中率;
  • hitMiss(): 缓存失误率;
  • loadcount() ; 加载次数;
  • averageLoadPenalty():加载新值的平均时间,单位为纳秒;
  • evictionCount():缓存项被回收的总数,不包括显式清除。

Guava Cache 的替代者 Caffeine

外部缓存

最著名的外部缓存 - Redis / Memcached

Redis / Memcached 都是使用内存作为存储,所以性能上要比数据库要好很多,再加上Redis 还支持很多种数据结构,使用起来也挺方便,所以作为很多人的首选。

Redis 确实不错,不过即便是使用内存,也还是需要通过网络来访问,所以网络的性能决定了 Reids 的性能;

在一些性能测试中,在万兆网卡的情况下,对于 Key 和 Value 都是长度为 20 Byte 的字符串的 get 和 set 是每秒10w左右的,如果 Key 或者 Value 的长度更大或者使用数据结构,这个会更慢一些;

作为一般的系统来使用已经绰绰有余了,从目前来看,Redis 确实很适合来做系统中的缓存。

如果考虑多实例或者分布式,可以考虑下面的方式:

  • Jedis 的 ShardedJedis( 调用端自己实现分片 )
  • twemproxy / codis( 第三方组件实现代理 )
  • Redis Cluster( 3.0 之后官方提供的集群方案 )

缓存的异常情况

缓存穿透

指的是对某个一定不存在的数据进行请求,该请求将会穿透缓存到达数据库。

解决方案:

  • 对这些不存在的数据缓存一个空数据;
  • 对这类请求进行过滤。

缓存击穿

指的是由于数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效(过期),又或者缓存服务器宕机,导致大量的请求都到达数据库。

在有缓存的系统中,系统非常依赖于缓存,缓存分担了很大一部分的数据请求。当发生缓存雪崩时,数据库无法处理这么大的请求,导致数据库崩溃。

解决方案:

  • 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现;
  • 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
  • 也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。

缓存一致性

缓存一致性要求数据更新的同时缓存数据也能够实时更新。

解决方案:

  • 在数据更新的同时立即去更新缓存;
  • 在读缓存之前先判断缓存是否是最新的,如果不是最新的先进行更新。

要保证缓存一致性需要付出很大的代价,缓存数据最好是那些对一致性要求不高的数据,允许缓存数据存在一些脏数据。

缓存“无底洞”现象

指的是为了满足业务要求添加了大量缓存节点,但是性能不但没有好转反而下降了的现象。

产生原因:缓存系统通常采用 hash 函数将 key 映射到对应的缓存节点,随着缓存节点数目的增加,键值分布到更多的节点上,导致客户端一次批量操作会涉及多次网络操作,这意味着批量操作的耗时会随着节点数目的增加而不断增大。此外,网络连接数变多,对节点的性能也有一定影响。

解决方案:

  • 优化批量数据操作命令;
  • 减少网络通信次数;
  • 降低接入成本,使用长连接 / 连接池,NIO 等。

附录

附录1 使用ConcurrentHashMap实现带过期时间的缓存

 private static ScheduledExecutorService swapExpiredPool = new ScheduledThreadPoolExecutor(10);private ReentrantLock lock = new ReentrantLock();private ConcurrentHashMap<String, Node> cache = new ConcurrentHashMap<>(1024);/*** 让过期时间最小的数据排在队列前,在清除过期数据时* ,只需查看缓存最近的过期数据,而不用扫描全部缓存** @see Node#compareTo(Node)* @see SwapExpiredNodeWork#run()*/private PriorityQueue<Node> expireQueue = new PriorityQueue<>(1024);public LocalCache() {//使用默认的线程池,每5秒清除一次过期数据//线程池和调用频率 最好是交给调用者去设置。swapExpiredPool.scheduleWithFixedDelay(new SwapExpiredNodeWork(), 5, 5, TimeUnit.SECONDS);}public Object set(String key, Object value, long ttl) {Assert.isTrue(StringUtils.hasLength(key), "key can't be empty");Assert.isTrue(ttl > 0, "ttl must greater than 0");long expireTime = System.currentTimeMillis() + ttl;Node newNode = new Node(key, value, expireTime);lock.lock();try {Node old = cache.put(key, newNode);expireQueue.add(newNode);//如果该key存在数据,还要从过期时间队列删除if (old != null) {expireQueue.remove(old);return old.value;}return null;} finally {lock.unlock();}}/*** 拿到的数据可能是已经过期的数据,可以再次判断一下* if(n.expireTime<System.currentTimeMillis()){* return null;* }* 也可以直接返回整个节点Node ,交给调用者去取舍* <p>* <p>* 无法判断不存在该key,还是该key存的是一个null值,如果需要区分这两种情况* 可以定义一个全局标识,标识key不存在* public static final NOT_EXIST = new Object();* 返回值时* return n==null?NOT_EXIST:n.value;*/public Object get(String key) {Node n = cache.get(key);return n == null ? null : n.value;}/*** 删除KEY,并返回该key对应的数据*/public Object remove(String key) {lock.lock();try {Node n = cache.remove(key);if (n == null) {return null;} else {expireQueue.remove(n);return n.value;}} finally {lock.unlock();}}/*** 删除已经过期的数据*/private class SwapExpiredNodeWork implements Runnable {@Overridepublic void run() {long now = System.currentTimeMillis();while (true) {lock.lock();try {Node node = expireQueue.peek();//没有数据了,或者数据都是没有过期的了if (node == null || node.expireTime > now) {return;}cache.remove(node.key);expireQueue.poll();} finally {lock.unlock();}}}}private static class Node implements Comparable<Node> {private String key;private Object value;private long expireTime;public Node(String key, Object value, long expireTime) {this.value = value;this.key = key;this.expireTime = expireTime;}/*** @see SwapExpiredNodeWork*/@Overridepublic int compareTo(Node o) {long r = this.expireTime - o.expireTime;if (r > 0) {return 1;}if (r < 0) {return -1;}return 0;}}

粉丝福利, 免费领取C/C++ 开发学习资料包、技术视频/代码,1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件,后端开发/音视频开发/Qt开发/游戏开发/Linuxn内核等进阶学习资料和最佳学习路线)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

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

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

相关文章

韩国访问学者申请注意事项

随着国际交流的增加&#xff0c;韩国成为许多学者追求学术深造的热门目的地之一。如果你计划成为一名韩国访问学者&#xff0c;以下是知识人网小编整理的一些需要注意的事项&#xff0c;以确保你的申请顺利进行。 1.详细了解目标学府&#xff1a;在开始申请之前&#xff0c;仔细…

Python进阶——文件及IO操作

一、文件的基本操作 创建文件对象和打开文件对象&#xff1a;open() def my_write():#(1)打开&#xff08;创建&#xff09;文件fileopen(Myqq.txt,w,encodingutf-8) #如果文件不存在&#xff0c;则在当前目录下创建一个文件名字为Myqq.txt#&#xff08;2&#xff09;操作文件f…

redis整合

一.redis的发布订阅 什么 是发布和订阅 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。 1、Redis的发布和订阅 客户端订阅频道发布的消息 频道发布消息 订阅者就可以…

matlab绘图杂谈-stem函数和plot函数

出发点 今天在论文中看到一副这样的图&#xff0c;它既有曲线&#xff0c;又有点&#xff0c;并且对两者都添加了图例。三条曲线应该是用plot函数绘制的&#xff0c;而target哪个绿色的圆圈&#xff0c;我的理解是用stem函数绘制的。它只是1个点&#xff0c;并且没有竖线&…

Linux文件管理技术实践

shell shell的种类(了解) shell是用于和Linux内核进行交互的一个程序&#xff0c;他的功能和window系统下的cmd是一样的。而且shell的种类也有很多常见的有c shell、bash shell、Korn shell等等。而本文就是使用Linux最常见的bash shell对Linux常见指令展开探讨。 内置shell…

ElasticSearch搜索引擎入门到精通

ES 是基于 Lucene 的全文检索引擎,它会对数据进行分词后保存索引,擅长管理大量的数据,相对于 MySQL 来说不擅长经常更新数据及关联查询。这篇文章就是为了进一步了解一下它,到底是如何做到这么高效的查询的。 在学习其他数据库的时候我们知道索引是一个数据库系统极其重要…

【基础配置】Python2/Python3并存安装配置教程

Nx01 产品简介 Python是一种高级的、解释型的、面向对象的通用编程语言&#xff0c;具有简单易学、代码可读性强、功能强大、可移植性好等特点。它可以应用于多种领域&#xff0c;如Web开发、数据科学、人工智能、机器学习、科学计算、自动化测试等。Python由Guido van Rossum于…

如何使用phpStudy软件测试本地PHP及环境搭建

各位同学朋友们大家好&#xff01;我是咕噜铁蛋&#xff01;我们经常需要在本地进行PHP代码的开发和测试。而phpStudy作为一个集成了Apache、MySQL和PHP的软件套装&#xff0c;提供了方便快捷的环境搭建和测试工具。今天铁蛋为大家详细介绍如何使用phpStudy来测试本地PHP及环境…

gradle源

腾讯源 distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-xxx-bin.zip阿里云 https://developer.aliyun.com/mvn/guide maven { url http://maven.aliyun.com/nexus/content/groups/public/ }maven { url https://maven.aliyun.com/repository/google }mave…

【cuda】RuntimeError: Unexpected error from cudaGetDeviceCount()

解决 会发现 nvidia-smi 不管用了。不要立即重启&#xff0c;会黑屏的。赶紧记录使用的驱动版本号&#xff0c;最好找到安装包位置。 直接重装原版驱动&#xff0c;环境还能用。 参考我的安装博客。 已经黑屏就进入安全模式&#xff0c;network模式。卸载可视化桌面和显卡驱动…

雨云服务器部署幻兽帕鲁PalWorld联机服务器详细教程

幻兽帕鲁是Pocketpair开发的一款开放世界生存制作游戏&#xff0c;游戏中&#xff0c;玩家可以在广阔的世界中收集神奇的生物“帕鲁”&#xff0c;派他们进行战斗、建造、做农活&#xff0c;工业生产等。 最近&#xff0c;这款游戏挺火&#xff0c;为了获得更好的游戏体验&…

SpringBoot中Redis解决LocalDateTime序列化与反序列化不一致问题

前言 在SpringBoot应用中&#xff0c;数据的序列化和反序列化是关键环节。然而&#xff0c;对于LocalDateTime类型的数据&#xff0c;有时会遇到序列化与反序列化不一致的问题。这主要是由于不同的时区或格式差异所导致。为了解决这一问题&#xff0c;我们可以借助Redis的强大功…

手搓 国内首个非Attention大模型,训练效率7倍于Transformer

手搓 国内首个非Attention大模型,训练效率7倍于Transformer 非Attention大模型代码解析非Attention大模型代码 import torchclass FeedForward(torch.nn.Module):def __init__(self, hidden_dim):super

爬虫工作量由小到大的思维转变---<第四十章 Scrapy Redis 实现IP代理池管理的最佳实践>

前言: 本篇是要结合上篇一起看的姊妹篇:爬虫工作量由小到大的思维转变---&#xff1c;第三十九章 Scrapy-redis 常用的那个RetryMiddleware&#xff1e;-CSDN博客 IP代理池的管理对于确保爬虫的稳定性和数据抓取的匿名性至关重要。围绕Scrapy-Redis框架和一个具体的IP代理池中…

Vue构建项目断点调试过程问题总结

Vue构建项目断点调试过程问题总结 问题背景 前端开发过程中&#xff0c;碰到问题时需要debug&#xff0c;快速分析和解决问题。一般除了console.log的方式打印日志外&#xff0c;更方便直观的方式就是打断点debug。本文对vue项目debug过程可能碰到的问题进行总结&#xff0c;…

“数据同步大揭秘:Canal工具如何让实时处理变得轻而易举?“

介绍&#xff1a;Canal是一个基于MySQL数据库增量日志解析的开源数据同步工具。 Canal的主要功能是提供增量数据订阅和消费&#xff0c;它通过解析MySQL数据库的增量日志来捕获数据变更事件&#xff0c;并将这些事件转换成数据变更流&#xff0c;供用户订阅和消费。这样&#x…

魔法少女LJJ 题解

推荐在 cnblogs 上阅读 魔法少女LJJ 题解 这题纯属就是迷惑题。。 为什么这么说&#xff1f; 注意数据范围&#xff1a; 对 100% 的数据 0 ≤ m ≤ 400000 0\leq m\leq400000 0≤m≤400000&#xff0c; c ≤ 7 c\leq 7 c≤7。 c ≤ 7 c\leq 7 c≤7&#xff01;&#xff01…

Deepin基本环境查看(四)【硬盘/分区、文件系统、硬连接/软连接】

Linux操作系统(Deepin、Ubuntu&#xff09;操作系统中&#xff0c;硬盘分区的管理与Windows操作系统不同&#xff1b; 在Linux系统中维护着一个统一的文件目录体系&#xff0c;而硬盘和分区是以资源的形式由操作系统挂接和调度&#xff1b;此外Linux系统中连接&#xff08;硬连…

R语言【taxlist】——get_children(),get_parents():检索分类概念的子类群或父类群

Package taxlist version 0.2.4 Description 检索所查询分类单元概念的所有子概念或父概念。 Usage get_children(taxlist, ...)## S3 method for class taxlist get_children(taxlist, ConceptID, ...)get_parents(taxlist, ...)## S3 method for class taxlist get_parents…

NTFS 磁盘管理 :NTFS Disk by Omi NTFS

NTFS Disk by Omi NTFS是一款专为Mac系统设计的NTFS文件系统读写解决方案的工具。它可以帮助Mac用户方便地访问和管理NTFS格式的硬盘、U盘、移动硬盘以及其他存储设备&#xff0c;提供高效稳定的NTFS卷管理功能。 NTFS 磁盘管理 &#xff1a;NTFS Disk by Omi NTFS 该软件的主…