CaffeineCache Api介绍以及与Guava Cache性能对比| 京东物流技术团队

news/2024/11/13 12:30:11

一、简单介绍:

CaffeineCache和Guava的Cache是应用广泛的本地缓存。

在开发中,为了达到降低依赖、提高访问速度的目的。会使用它存储一些维表接口的返回值和数据库查询结果,在有些场景下也会在分布式缓存上再加上一层本地缓存,用来减少对远程服务和数据库的请求次数。

CaffeineCache是以Guava Cache为原型库开发和扩展的一种本地缓存,并且适配Guava Cache的Api,但是CaffeineCache的性能更好。

二、CaffeineCache的使用:

CaffeineCache官方介绍有提供一些例子,不过这些例子不能直接运行。

下面围绕比较常用的API介绍下CaffeineCache的使用,列举一些可直接执行的Demo,看起来明了一些。

1.存数据:

Caffeine提供了四种缓存添加策略:手动加载,自动加载,手动异步加载和自动异步加载。

1.1手动加载:

        Cache<String, String> cache = Caffeine.newBuilder()//过期时间 .expireAfterWrite(10, TimeUnit.MINUTES)//最大容量 .maximumSize(10_000).build();String key = "test";// 查找一个缓存元素, 没有查找到的时候返回null String res = cache.get(key, k -> createValue(key));// 添加或者更新一个缓存元素 cache.put(key, "testValue"); // 移除一个缓存元素cache.invalidate(key);}// 模拟从外部数据源加载数据的逻辑 static String createValue(String key) {return "value";}

推荐使用 get(K var1, Function<? super K, ? extends V> var2);

get方法可以在缓存中不存在该key对应的值时进行计算,生成并直接写入至缓存内,最后将结果返回,而当该key对应的值存在时将会直接返回值。

注意到createValue方法有可能会出现异常,根据官网所说:“当缓存的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败,cache.get 也许会返回 null ”,那么实际情况怎么样呢?我们来试一下。

public class TestCaffeineCache {public static void main(String[] args) {Cache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).build();String key = "test";String res = cache.get(key, k -> createValue(key));System.out.println(res);}// 模拟从外部数据源加载数据的逻辑 static String createValue(String key) {//模拟异常情况int a = 1/0;return "";}
}

运行结果:

 


 

可以看到,执行cache.get时,在生成结果的过程中如果出现异常了,cache.get不会返回null,仍会直接报错。

1.2自动加载

public class TestCaffeineCache {public static void main(String[] args) {LoadingCache<String, String> cache = Caffeine.newBuilder().build(new CacheLoader<String, String>() {@Overridepublic String load(String key) {return getValue(key);}});String value = cache.get("key");System.out.println(value);}// 模拟从外部数据源加载数据的逻辑private static String getValue(String key) {// 实际情况下,这里会有从数据库、远程服务加载数据的逻辑return "value";}
}

可以用lambda简化它:

public class TestCaffeineCache {public static void main(String[] args) {LoadingCache<String, String> cache = Caffeine.newBuilder().build(key -> getValue(key));String value = cache.get("key");System.out.println(value);}// 模拟从外部数据源加载数据的逻辑private static String getValue(String key) {return "value";}
}

上面的示例中, build方法传入的CacheLoader定义了加载缓存的逻辑。调用cache.get("key")时,如果缓存中不存在对应的值,CacheLoader会调用load方法来加载和缓存值。

可以通过重写和CacheLoader.load和loadAll并手动调用,在LoadingCache创建之前提前加载一些数据。

    public static void main(String[] args) throws Exception {CacheLoader loader = new CacheLoader<String,String>() {@Overridepublic String load( String s) throws Exception {return getValue(s);}@Overridepublic Map<String, String> loadAll(Iterable<? extends String> keys) throws Exception {Map currentMap = new HashMap<String,String>();for (String key : keys) {currentMap.put(key, getValue(key));}return currentMap;}};loader.load("key1");loader.loadAll(new ArrayList( Arrays.asList("key2","key3")));LoadingCache<String, String> cache = Caffeine.newBuilder().build(loader);String value = cache.get("key1");String value2 = cache.get("key2");System.out.println(value+value2);}// 模拟从外部数据源加载数据的逻辑private static String getValue(String key) {return "value";}

1.3手动异步加载:

    public static void main(String[] args) throws Exception {AsyncCache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000).buildAsync();String key ="test";CompletableFuture<String> res = cache.get(key,k-> getValue(key));res.thenAccept(result -> System.out.println(result));}// 模拟从外部数据源加载数据的逻辑private static String getValue(String key) {return "value";}

异步加载使用的类是AsyncCache,使用方法和Cache类似。cache.get(key, k -> getValue(key))将会返回一个CompletableFuture,这一步骤会在一个异步任务中执行,而不会阻塞主线程。res.thenAccept方法将在数据加载完成后得到结果。

1.4自动异步加载:

public static void main(String[] args) throws Exception {Executor executor = Executors.newFixedThreadPool(5);AsyncLoadingCache<String, String> cache = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).maximumSize(10_000)//异步的封装一段同步操作来生成缓存元素.buildAsync(key -> getValue(key))//OR建一个异步缓存元素操作并返回一个future.buildAsync((key,executor1) -> getValue(key,executor));String key = "test";CompletableFuture<String> res = cache.get(key);res.thenAccept(result -> System.out.println(result));}// 模拟从外部数据源加载数据的逻辑private static CompletableFuture<String> getValue(String key,Executor executor) {return CompletableFuture.supplyAsync(() -> "value for " + key, executor);}private static String getValue(String key) { return "value"; }

自动异步加载使用方法和手动异步加载类似,getValue可接收一个Executor对象,用于自定义执行异步操作的线程池。

2.驱逐:

2.1基于容量:

Cache<String, String> cache = Caffeine.newBuilder().maximumSize(10_000).build();

最常用的驱逐策略了,Caffeine提供多种算法根据最近使用频率和使用时间来驱逐元素 ref:Window-TinyLFU

2.2基于权重:

class Product {private String name;private int weight;public Product(String s, int i) {name=s;weight=i;}public String getName() {return name;}public int getWeight() {return weight;}@Overridepublic String toString(){return getName();}
}public class TestCaffeineCache {public static void main(String[] args) {Cache<String, Product> cache = Caffeine.newBuilder().maximumWeight(1000).weigher((String key, Product value) -> value.getWeight())//使用当前线程进行驱逐和刷新.executor(runnable -> runnable.run())//监听器,如果有元素被驱逐则会输出.removalListener(((key, value, cause) -> {System.out.printf("Key %s was evicted (%s)%n", key, cause);})).build();// 向缓存中添加商品信息cache.put("product1", new Product("Product 1", 200));cache.put("product2", new Product("Product 2", 400));cache.put("product3",newProduct("Product 3",500));// 获取缓存中的商品信息System.out.println(cache.getIfPresent("product1"));System.out.println(cache.getIfPresent("product2"));System.out.println(cache.getIfPresent("product3"));}}

 


 

.weigher((String key, Product value) -> value.getWeight()) 制定了一个权重计算器,Product对象的getWeight()方法来计算权重。

通过示例中的返回结果可以看到,当product3被put后,总容量超过了1000,product1就被驱逐了。

2.3基于时间:

附上官方的例子:

// 基于固定的过期时间驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(key -> createExpensiveGraph(key));// 基于不同的过期驱逐策略
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().expireAfter(new Expiry<Key, Graph>() {public long expireAfterCreate(Key key, Graph graph, long currentTime) {// Use wall clock time, rather than nanotime, if from an external resourcelong seconds = graph.creationDate().plusHours(5).minus(System.currentTimeMillis(), MILLIS).toEpochSecond();return TimeUnit.SECONDS.toNanos(seconds);}public long expireAfterUpdate(Key key, Graph graph, long currentTime, long currentDuration) {return currentDuration;}public long expireAfterRead(Key key, Graph graph,long currentTime, long currentDuration) {return currentDuration;}}).build(key -> createExpensiveGraph(key));

Caffeine提供了三种方法进行基于时间的驱逐——官方的解释:

expireAfterAccess(long, TimeUnit): 一个元素在上一次读写操作后一段时间之后,在指定的时间后没有被再次访问将会被认定为过期项。在当被缓存的元素时被绑定在一个session上时,当session因为不活跃而使元素过期的情况下,这是理想的选择。

expireAfterWrite(long, TimeUnit): 一个元素将会在其创建或者最近一次被更新之后的一段时间后被认定为过期项。在对被缓存的元素的时效性存在要求的场景下,这是理想的选择。

expireAfter(Expiry): 一个元素将会在指定的时间后被认定为过期项。当被缓存的元素过期时间收到外部资源影响的时候,这是理想的选择。

写一个Demo举例expireAfterAccess和expireAfterWrite

    public static void main(String[] args) {//模拟时间,使用的com.google.common.testing.FakeTicker;FakeTicker ticker = new FakeTicker();Cache<String, String> cache = Caffeine.newBuilder()//创建20分钟后元素被删除.expireAfterWrite(20, TimeUnit.MINUTES)//没有读取10分钟后元素被删除 .expireAfterAccess(10, TimeUnit.MINUTES).executor(Runnable::run).ticker(ticker::read).build();cache.put("key1","value1");cache.put("key2","value2");ticker.advance(5, TimeUnit.MINUTES);System.out.println("5分钟都不删除,访问一次key2:"+cache.getIfPresent("key2"));ticker.advance(5, TimeUnit.MINUTES);System.out.println("10分钟key1被删除,因为它已经10分钟没有被访问过了:"+cache.getIfPresent("key1"));System.out.println("10分钟key2没有被删除,因为它在5分钟时被访问过了:"+cache.getIfPresent("key2"));ticker.advance(10, TimeUnit.MINUTES);System.out.println("20分钟key2也被删除:"+cache.getIfPresent("key2"));}

这个例子设定元素创建20分钟或者没有读取10分钟后被删除。

key1和key2在创建后。5分钟时访问一次key2,十分钟时key1被删除,key2没有被删除,20分钟时key2也被删除。

运行结果正如我们期待的:

 


 

举例expireAfter:

    public static void main(String[] args) {//模拟时间,使用的com.google.common.testing.FakeTicker;FakeTicker ticker = new FakeTicker();Cache<String, String> cache = Caffeine.newBuilder().expireAfter(new Expiry<String, String>() {public long expireAfterCreate(String key, String value, long currentTime) {// 在创建后的24小时后过期return TimeUnit.HOURS.toNanos(24);}public long expireAfterUpdate(String key, String value, long currentTime, long currentDuration) {// 在更新后如果值为"1234",则立马过期if("1234".equals(value)){return 0;}// 在更新后的1小时后过期return TimeUnit.HOURS.toNanos(1);}public long expireAfterRead(String key, String value, long currentTime, long currentDuration) {// 在读取后的20小时后过期return TimeUnit.HOURS.toNanos(20);}}).executor(Runnable::run).ticker(ticker::read).build();cache.put("AfterCreateKey","AfterCreate");cache.put("AfterUpdate1234Key","1234key");cache.put("AfterUpdateKey","AfterUpdate");cache.put("AfterReadKey","AfterRead");//AfterUpdate1234Key值更新为1234cache.put("AfterUpdate1234Key","1234");System.out.println("AfterUpdate1234Key在更新后值为1234,立马过期:"+cache.getIfPresent("AfterUpdate1234Key"));System.out.println("AfterReadKey读取一次:"+cache.getIfPresent("AfterReadKey"));//AfterUpdateKey更新一次cache.put("AfterUpdateKey","AfterUpdate");ticker.advance(1,TimeUnit.HOURS);System.out.println("AfterUpdateKey更新了一个小时了,被删除:"+cache.getIfPresent("AfterUpdateKey"));ticker.advance(19,TimeUnit.HOURS);System.out.println("AfterReadKey再读取一次已经删除了,因为上一次读取已经过了20小时:"+cache.getIfPresent("AfterReadKey"));ticker.advance(4,TimeUnit.HOURS);System.out.println("AfterCreateKey被删除了,距离创建已经24小时了:"+cache.getIfPresent("AfterCreateKey"));}

这个例子设定了元素在以下四种情况会过期:

创建后的24小时
更新后值为"1234"
更新后的1小时
在读取后的20小时

以下是运行结果

 


 

2.4基于引用:

基于引用的过期驱逐策略不常用,这里附上官方的例子和解释:

// 当key和缓存元素都不再存在其他强引用的时候驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().weakKeys().weakValues().build(key -> createExpensiveGraph(key));// 当进行GC的时候进行驱逐
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder().softValues().build(key -> createExpensiveGraph(key));

Caffeine 允许你配置缓存,以便GC去帮助清理缓存当中的元素,其中key支持弱引用,而value则支持弱引用和软引用。AsyncCache不支持软引用和弱引用。

Caffeine.weakKeys() 在保存key的时候将会进行弱引用。这允许在GC的过程中,当key没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行key之间的比较。

Caffeine.weakValues()在保存value的时候将会使用弱引用。这允许在GC的过程中,当value没有被任何强引用指向的时候去将缓存元素回收。由于GC只依赖于引用相等性。这导致在这个情况下,缓存将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。

Caffeine.softValues()在保存value的时候将会使用软引用。为了相应内存的需要,在GC过程中被软引用的对象将会被通过LRU算法回收。由于使用软引用可能会影响整体性能,我们还是建议通过使用基于缓存容量的驱逐策略代替软引用的使用。同样的,使用 softValues() 将会通过引用相等(==)而不是对象相等 equals()去进行value之间的比较。

3.移除:

3.1移除的三个方法:

可以调用以下三个方法移除缓存中的元素

// 失效key
void invalidate(@CompatibleWith("K") @NonNull Object var1);
// 批量失效key
void invalidateAll(@NonNull Iterable<?> var1);
// 失效所有的key
void invalidateAll();

 

3.2监听器removalListener和evictionListener

当元素从缓存中被移除时,这两个监听器可以进行指定的操作,具体有什么区别呢?先上例子:

        Cache<String, String> cache = Caffeine.newBuilder().maximumSize(2).executor(Runnable::run)//驱逐,删除key时会输出.removalListener(((key, value, cause) -> {System.out.printf("removalListener—>Key %s was evicted (%s)%n", key, cause);}))//驱逐key时会输出 .evictionListener(((key, value, cause) -> {System.out.printf("evictionListener->Key %s was evicted (%s)%n", key, cause);})).build();// 向缓存中添加商品信息cache.put("product1", "product1");cache.put("product2", "product2");cache.put("product3", "product3");// 获取缓存中的商品信息System.out.println(cache.getIfPresent("product1"));System.out.println(cache.getIfPresent("product2"));System.out.println(cache.getIfPresent("product3"));cache.invalidateAll();

结果:

 


 

可以发现,当元素被驱逐,或者被手动移除时,removalListener都会执行指定的操作。而evictionListener只会在元素被驱逐时执行指定的操作。

4.刷新:

4.1自动刷新:

可以使用refreshAfterWrite在元素写入一段时间后刷新元素,先上代码

    public static void main(String[] args) {FakeTicker ticker = new FakeTicker();LoadingCache<String, String> cache = Caffeine.newBuilder().refreshAfterWrite(5, TimeUnit.SECONDS) // 在写入后5秒钟自动刷新.ticker(ticker::read).executor(Runnable::run).build(key -> getVale(key)); // 提供加载方法System.out.println("Initial value for key1: " + cache.get("key1"));// 超过自动刷新时间ticker.advance(7, TimeUnit.SECONDS);System.out.println(cache.get("key1")); // 真正执行刷新System.out.println(cache.get("key1")); // 输出自动刷新后的值}private static String getVale(String key) {// 这里简单地返回一个当前时间的字符串return "loaded value for " + key + " at " + System.currentTimeMillis();}

输出结果:

 


 

可以发现过了刷新时间后,第一次访问key1并没有返回新值,第二次访问key1时才会将刷新后的数据返回,官方的解释是元素过了刷新时间不会立即刷新,而是在在访问时才会刷新,并且没有刷新完毕,其旧值将仍被返回,直到该元素的刷新完毕后结束后才会返回刷新后的新值。

4.2手动刷新:

可以使用refresh(Key)方法进行手动刷新

    public static void main(String[] args) {LoadingCache<String, String> cache = Caffeine.newBuilder().build(key -> getVale(key)); // 提供加载方法System.out.println("Initial value for key1: " + cache.get("key1"));cache.refresh("key1");System.out.println(cache.get("key1")); // 输出自动刷新后的值}private static String getVale(String key) {// 这里简单地返回一个当前时间的字符串return "loaded value for " + key + " at " + System.currentTimeMillis();}

 


 

4.3刷新自定义处理:

可以使用CacheLoader.reload(K, V)来自定义刷新前后值的处理,下面这个例子重写了reload方法,将新值和旧值用"|"分开

    public static void main(String[] args) {FakeTicker ticker = new FakeTicker();LoadingCache<String, String> cache = Caffeine.newBuilder().refreshAfterWrite(5, TimeUnit.SECONDS) // 在写入后5秒钟自动刷新.ticker(ticker::read).build(new CacheLoader<String, String>() {@Overridepublic String load(String s) throws Exception {return getVale(s);}//将刷新前后的数据都获取出来了@Overridepublic String reload(String s,String v){return getVale(s)+"|"+v;}}); // 提供加载方法System.out.println("Initial value for key1: " + cache.get("key1"));// 等待超过自动刷新时间ticker.advance(7, TimeUnit.SECONDS);cache.get("key1");System.out.println(cache.get("key1")); // 输出自动刷新后的值}private static String getVale(String key) {// 这里简单地返回一个当前时间的字符串return "loaded value for " + key + " at " + System.currentTimeMillis();}

结果:

 


 

 

三、性能对比:

我们知道,Caffeine的性能比Guava Cache要好,可以写一个demo简单对比一下:

1.Caffeine Demo:

package test;import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;public class CaffeineCacheTest {public static void main(String[] args) throws Exception {Cache<Integer, Integer> loadingCache = Caffeine.newBuilder().build();// 开始时间Long start = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {loadingCache.put(i, i);}// 存完成时间Long writeFinishTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {loadingCache.getIfPresent(i);}// 读取完成时间Long readFinishTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {loadingCache.invalidate(i);}// 删除完成时间Long deleteFinishTime = System.currentTimeMillis();System.out.println("CaffeineCache存用时:" + (writeFinishTime - start));System.out.println("CaffeineCache读用时:" + (readFinishTime - writeFinishTime));System.out.println("CaffeineCache删用时:"+(deleteFinishTime - readFinishTime));}}

运行结果:

 


 

2.Guava Cache Demo:

使用几乎一致的API,换成Guava Cache再试一次:


package test;import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;public class GuavaCacheTest {public static void main(String[] args) throws Exception {Cache<Integer, Integer> loadingCache = CacheBuilder.newBuilder().build();// 开始时间Long start = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {loadingCache.put(i, i);}// 存完成时间Long writeFinishTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {loadingCache.getIfPresent(i);}// 读取完成时间Long readFinishTime = System.currentTimeMillis();for (int i = 0; i < 1000000; i++) {loadingCache.invalidate(i);}// 删除完成时间Long deleteFinishTime = System.currentTimeMillis();System.out.println("GuavaCache存用时:"+(writeFinishTime-start));System.out.println("GuavaCache取用时:"+(readFinishTime-writeFinishTime));System.out.println("GuavaCache删用时:"+(deleteFinishTime-readFinishTime));}}

运行结果:

 


 

3.多组测试结果:

运行环境:处理器:Apple M3 ,内存:18 GB,JDK1.8

更改循环次数,多组测试结果如下(单位ms):

  Caffeine Guava Cache Caffeine Guava Cache Caffeine Guava Cache Caffeine Guava Cache
次数 100 100 10000 10000 1000000 1000000 5000000 5000000
存用时 1 2 17 51 113 279 3802 2458
取用时 0 0 9 16 25 141 47 531
删用时 0 11 7 25 35 176 89 1073

可以看出Caffeine的总体性能是比Guava Cache要好的。

当然,基于本地单机的简单测试,结果受处理器,线程,内存等影响较大。可以参考下官方的测试,有更高的参考意义:官方测试。

四、总结:

本文举了很多的例子,介绍了Caffeine支持的多种基础的操作,包括存、取、删等。以及异步、监听、刷新等更多拓展的操作,能够覆盖大部分需要本地缓存的开发场景。

Caffeine的性能比Guava Cache更好,并列举了一个性能测试demo,Caffeine兼容Guava Cache的API,所以从Guava Cache迁移至Caffeine也比较容易

最后附上Caffeine的官方网址:官方网址(中文)。

作者:京东物流 殷世杰

来源:京东云开发者社区

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

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

相关文章

nps内网穿透使用

原版的https://github.com/ehang-io/nps已经停止更新 新版的地址 https://github.com/yisier/nps 一、安装 可以下载已经编译好的程序安装。网上有很多教程。 也可以下载源码编译,需要注意的是如果到cmd/nps下面编译,运行的时候,需要把conf目录拷贝到nps目录下才能运行,缺少…

(二)Redis 数据类型与结构

Redis 数据类型与结构1、值的数据类型 Redis “快”取决于两方面,一方面,它是内存数据库,另一方面,则是高效的数据结构。Redis 键值对中值的数据类型,也就是数据的保存形式有5种:String(字符串)、List(列表)、Hash(哈希)、Set(集合)和 Sorted Set(有序集合)。这…

高薪线下周末班马上开班,手把手带你提升职业技能

管理学大师彼得德鲁克说“终身学习是现在社会的生存法则”,而现实中,很少有人能清醒地意识到这一点,人们总是习惯在舒适区兜圈,重复做已经掌握的事情,对真正需要突破的职业困境视而不见。 偶尔看到同事跳槽涨薪,技术越来越娴熟,自己也期望着可以跟他们一样,幻想着有一天…

dapr离线初始化

打开地址: https://github.com/dapr/installer-bundle/releases 下载 daprbundle_windows_amd64.zip 解压以后,放到此目录下,注意放的是daprbundle文件夹下内容 D:\daprbundle_v1.13.2指定目录进行初始化: dapr init --from-dir D:\daprbundle_v1.13.2 最后初始化完成,如下图…

LDAP数据备份与恢复

一、命令形式 以整体备份(迁移)来示范 1.1 说明使用命令:导出---slapcat 导入--- slapaddopenldap的数据目录是/var/lib/ldap/备份可以通过直接备份/var/lib/ldap/文件夹,恢复也是直接使用备份的文件夹替换就行,此处不做演示下面使用命令行形式进行演示 1.2 导出数据文件 …

pyqt5 子线程如何操作主线程GUI

一.简介 在使用pyqt5编写gui时遇到两个问题,会导致界面崩溃,今天就围绕这两个问题来简单说明和改进。 1.在主线程中使用while无限循环会导致界面崩溃 2.在子线程中操作主线程gui会导致界面崩溃 二.步骤说明 1.在主线程中使用while无限循环会导致界面崩溃 1)错误代码import s…

BOSHIDA AC/DC电源模块的设计与优化

BOSHIDA AC/DC电源模块的设计与优化 AC/DC电源模块是一种将交流电转换为直流电的设备,广泛应用于各种电子设备中。其设计与优化对于提高电源的效率、稳定性以及可靠性非常重要。在设计与优化AC/DC电源模块时,需要考虑以下几个方面。 首先,设计AC/DC电源模块需要选择合适的开…

SSRF攻击

SSRF服务端请求伪造目录SSRF攻击原理漏洞利用redis未授权访问访问漏洞进行攻击SSRF绕过方式SSRF中URL的伪协议SSRF漏防御手段 SSRF攻击 服务端请求伪造 原理 事故多发地在url分享、收藏、网址翻译功能。 参数通常是:(其实就是参数值是另一个url的地方就有可能有SSRF) share li…