Java本地高性能缓存实践

news2025/2/26 0:24:22

Java缓存技术可分为远端缓存和本地缓存,远端缓存常用的方案有著名的redis和memcache,而本地缓存的代表技术主要有HashMap,Guava Cache,Caffeine和Encahche。远端缓存将在后面的博文中进行深入探讨,此处挖个坑,因此本篇博文仅覆盖了本地缓存,且突出探讨高性能的本地缓存。

本篇博文将首先介绍常见的本地缓存技术,对本地缓存有个大概的了解;其次介绍本地缓存中号称性能最好的Cache,可以探讨看看到底有多好?怎么做到这么好?最后通过几个实战样例,在日常工作中应用高性能的本地缓存。

一、 Java本地缓存技术介绍

1.1 HashMap

通过Map的底层方式,直接将需要缓存的对象放在内存中。

  • 优点:简单粗暴,不需要引入第三方包,比较适合一些比较简单的场景。

public class LRUCache extends LinkedHashMap {/**     * 可重入读写锁,保证并发读写安全性     */    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();    private Lock readLock = readWriteLock.readLock();    private Lock writeLock = readWriteLock.writeLock();  /**     * 缓存大小限制     */    private int maxSize;   public LRUCache(int maxSize) {        super(maxSize + 1, 1.0f, true);        this.maxSize = maxSize;    }  @Override    public Object get(Object key) {        readLock.lock();        try {            return super.get(key);        } finally {            readLock.unlock();        }    } @Override    public Object put(Object key, Object value) {        writeLock.lock();        try {            return super.put(key, value);        } finally {            writeLock.unlock();        }    }    @Override    protected boolean removeEldestEntry(Map.Entry eldest) {        return this.size() > maxSize;    }}

1.2 Guava Cache

Guava Cache是由Google开源的基于LRU替换算法的缓存技术。但Guava Cache由于被下面即将介绍的Caffeine全面超越而被取代,因此不特意编写示例代码了,有兴趣的读者可以访问Guava Cache主页。

  • 优点:支持最大容量限制,两种过期删除策略(插入时间和访问时间),支持简单的统计功能。

  • 缺点:springboot2和spring5都放弃了对Guava Cache的支持。

1.3 Caffeine

Caffeine采用了W-TinyLFU(LUR和LFU的优点结合)开源的缓存技术。缓存性能接近理论最优,属于是Guava Cache的增强版。

public class CaffeineCacheTest {
    public static void main(String[] args) throws Exception {        //创建guava cache        Cache<String, String> loadingCache = Caffeine.newBuilder()                //cache的初始容量                .initialCapacity(5)                //cache最大缓存数                .maximumSize(10)                //设置写缓存后n秒钟过期                .expireAfterWrite(17, TimeUnit.SECONDS)                //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite                //.expireAfterAccess(17, TimeUnit.SECONDS)                .build();        String key = "key";        // 往缓存写数据        loadingCache.put(key, "v");      // 获取value的值,如果key不存在,获取value后再返回        String value = loadingCache.get(key, CaffeineCacheTest::getValueFromDB);   // 删除key        loadingCache.invalidate(key);    }    private static String getValueFromDB(String key) {        return "v";    }}

1.4 Encache

Ehcache是一个纯java的进程内缓存框架,具有快速、精干的特点。是hibernate默认的cacheprovider。

  • 优点:支持多种缓存淘汰算法,包括LFU,LRU和FIFO;缓存支持堆内缓存,堆外缓存和磁盘缓存;支持多种集群方案,解决数据共享问题。

  • 缺点:性能比Caffeine差

public class EncacheTest {    public static void main(String[] args) throws Exception {        // 声明一个cacheBuilder        CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()                .withCache("encacheInstance", CacheConfigurationBuilder                        //声明一个容量为20的堆内缓存                        .newCacheConfigurationBuilder(String.class,String.class, ResourcePoolsBuilder.heap(20)))                .build(true);        // 获取Cache实例        Cache<String,String> myCache =  cacheManager.getCache("encacheInstance", String.class, String.class);        // 写缓存        myCache.put("key","v");        // 读缓存        String value = myCache.get("key");        // 移除换粗        cacheManager.removeCache("myCache");        cacheManager.close();    }}

在Caffeine的官网介绍中,Caffeine在性能和功能上都与其他几种方案相比具有优势,因此接下来主要探讨Caffeine的性能和实现原理。

二、高性能缓存Caffeine

2.1 缓存类型

2.1.1 Cache

Cache<Key, Graph> cache = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .maximumSize(10_000)    .build();// 查找一个缓存元素, 没有查找到的时候返回nullGraph graph = cache.getIfPresent(key);// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回nullgraph = cache.get(key, k -> createExpensiveGraph(key));// 添加或者更新一个缓存元素cache.put(key, graph);// 移除一个缓存元素cache.invalidate(key);

Cache 接口提供了显式搜索查找、更新和移除缓存元素的能力。当缓存的元素无法生成或者在生成的过程中抛出异常而导致生成元素失败,cache.get 也许会返回 null 。

2.1.2 Loading Cache

LoadingCache<Key, Graph> cache = Caffeine.newBuilder()    .maximumSize(10_000)    .expireAfterWrite(10, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));// 查找缓存,如果缓存不存在则生成缓存元素,  如果无法生成则返回nullGraph graph = cache.get(key);// 批量查找缓存,如果缓存不存在则生成缓存元素Map<Key, Graph> graphs = cache.getAll(keys);

一个LoadingCache是一个Cache 附加上 CacheLoader能力之后的缓存实现。
如果缓存不错在,则会通过CacheLoader.load来生成对应的缓存元素。

2.1.3 Async Cache​​​​​​​

AsyncCache<Key, Graph> cache = Caffeine.newBuilder()    .expireAfterWrite(10, TimeUnit.MINUTES)    .maximumSize(10_000)    .buildAsync();// 查找一个缓存元素, 没有查找到的时候返回nullCompletableFuture<Graph> graph = cache.getIfPresent(key);// 查找缓存元素,如果不存在,则异步生成graph = cache.get(key, k -> createExpensiveGraph(key));// 添加或者更新一个缓存元素cache.put(key, graph);// 移除一个缓存元素cache.synchronous().invalidate(key);

AsyncCache就是Cache的异步形式,提供了Executor生成缓存元素并返回CompletableFuture的能力。默认的线程池实现是 ForkJoinPool.commonPool() ,当然你也可以通过覆盖并实现 Caffeine.executor(Executor)方法来自定义你的线程池选择。

2.1.4 Async Loading Cache​​​​​​​

AsyncLoadingCache<Key, Graph> cache = Caffeine.newBuilder()    .maximumSize(10_000)    .expireAfterWrite(10, TimeUnit.MINUTES)    // 你可以选择: 去异步的封装一段同步操作来生成缓存元素    .buildAsync(key -> createExpensiveGraph(key));    // 你也可以选择: 构建一个异步缓存元素操作并返回一个future    .buildAsync((key, executor) -> createExpensiveGraphAsync(key, executor));// 查找缓存元素,如果其不存在,将会异步进行生成CompletableFuture<Graph> graph = cache.get(key);// 批量查找缓存元素,如果其不存在,将会异步进行生成CompletableFuture<Map<Key, Graph>> graphs = cache.getAll(keys);

AsyncLoadingCache就是LoadingCache的异步形式,提供了异步load生成缓存元素的功能。

2.2 驱逐策略

  • 基于容量

// 基于缓存内的元素个数进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumSize(10_000)    .build(key -> createExpensiveGraph(key));// 基于缓存内元素权重进行驱逐LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumWeight(10_000)    .weigher((Key key, Graph graph) -> graph.vertices().size())    .build(key -> createExpensiveGraph(key));
  • 基于时间

// 基于固定的过期时间驱逐策略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 resource        long 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));
  • 基于引用

// 当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));

2.3 刷新机制​​​​​​​

LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumSize(10_000)    .refreshAfterWrite(1, TimeUnit.MINUTES)    .build(key -> createExpensiveGraph(key));

只有在LoadingCache中可以使用刷新策略,与驱逐不同的是,在刷新的时候如果查询缓存元素,其旧值将仍被返回,直到该元素的刷新完毕后结束后才会返回刷新后的新值。

2.4 统计​​​​​​​

Cache<Key, Graph> graphs = Caffeine.newBuilder()    .maximumSize(10_000)    .recordStats()    .build();

通过使用Caffeine.recordStats()方法可以打开数据收集功能。Cache.stats()方法将会返回一个CacheStats对象,其将会含有一些统计指标,比如:

  • hitRate(): 查询缓存的命中率

  • evictionCount(): 被驱逐的缓存数量

  • averageLoadPenalty(): 新值被载入的平均耗时

配合SpringBoot提供的RESTful Controller,能很方便的查询Cache的使用情况。

三、Caffeine在SpringBoot的实战

按照Caffeine Github官网文档的描述,Caffeine是基于Java8的高性能缓存库。并且在Spring5(SpringBoot2.x)官方放弃了Guava,而使用了性能更优秀的Caffeine作为默认的缓存方案。

SpringBoot使用Caffeine有两种方式:

  • 方式一:直接引入Caffeine依赖,然后使用Caffeine的函数实现缓存

  • 方式二:引入Caffeine和Spring Cache依赖,使用SpringCache注解方法实现缓存
    下面分别介绍两种使用方式。

方式一:使用Caffeine依赖

首先引入maven相关依赖:​​​​​​​

<dependency>    <groupId>com.github.ben-manes.caffeine</groupId>      <artifactId>caffeine</artifactId>  </dependency>

其次,设置缓存的配置选项​​​​​​​​​​​​

@Configurationpublic class CacheConfig {    @Bean    public Cache<String, Object> caffeineCache() {        return Caffeine.newBuilder()                // 设置最后一次写入或访问后经过固定时间过期                .expireAfterWrite(60, TimeUnit.SECONDS)                // 初始的缓存空间大小                .initialCapacity(100)                // 缓存的最大条数                .maximumSize(1000)                .build();    }}
最后给服务添加缓存功能​​​​​​​
@Slf4j@Servicepublic class UserInfoServiceImpl {    /**     * 模拟数据库存储数据     */    private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();    @Autowired    Cache<String, Object> caffeineCache;    public void addUserInfo(UserInfo userInfo) {        userInfoMap.put(userInfo.getId(), userInfo);        // 加入缓存        caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);    }    public UserInfo getByName(Integer id) {        // 先从缓存读取        caffeineCache.getIfPresent(id);        UserInfo userInfo = (UserInfo) caffeineCache.asMap().get(String.valueOf(id));        if (userInfo != null){            return userInfo;        }        // 如果缓存中不存在,则从库中查找        userInfo = userInfoMap.get(id);        // 如果用户信息不为空,则加入缓存        if (userInfo != null){            caffeineCache.put(String.valueOf(userInfo.getId()),userInfo);        }        return userInfo;    } public UserInfo updateUserInfo(UserInfo userInfo) {        if (!userInfoMap.containsKey(userInfo.getId())) {            return null;        }        // 取旧的值        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());        // 替换内容        if (!StringUtils.isEmpty(oldUserInfo.getAge())) {            oldUserInfo.setAge(userInfo.getAge());        }        if (!StringUtils.isEmpty(oldUserInfo.getName())) {            oldUserInfo.setName(userInfo.getName());        }        if (!StringUtils.isEmpty(oldUserInfo.getSex())) {            oldUserInfo.setSex(userInfo.getSex());        }        // 将新的对象存储,更新旧对象信息        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);        // 替换缓存中的值        caffeineCache.put(String.valueOf(oldUserInfo.getId()),oldUserInfo);        return oldUserInfo;    }  @Override    public void deleteById(Integer id) {        userInfoMap.remove(id);        // 从缓存中删除        caffeineCache.asMap().remove(String.valueOf(id));    }
}

方式二:使用Spring Cache注解

首先引入maven相关依赖

<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-cache</artifactId></dependency><dependency>    <groupId>com.github.ben-manes.caffeine</groupId>    <artifactId>caffeine</artifactId></dependency>

其次,配置缓存管理类

@Configuration  public class CacheConfig {        /**       * 配置缓存管理器       *       * @return 缓存管理器       */      @Bean("caffeineCacheManager")      public CacheManager cacheManager() {          CaffeineCacheManager cacheManager = new CaffeineCacheManager();          cacheManager.setCaffeine(Caffeine.newBuilder()                  // 设置最后一次写入或访问后经过固定时间过期                  .expireAfterAccess(60, TimeUnit.SECONDS)                  // 初始的缓存空间大小                  .initialCapacity(100)                  // 缓存的最大条数                  .maximumSize(1000));          return cacheManager;      }    }

最后给服务添加缓存功能​​​​​​​

@Slf4j@Service@CacheConfig(cacheNames = "caffeineCacheManager")public class UserInfoServiceImpl {  /**     * 模拟数据库存储数据     */    private HashMap<Integer, UserInfo> userInfoMap = new HashMap<>();  @CachePut(key = "#userInfo.id")    public void addUserInfo(UserInfo userInfo) {        userInfoMap.put(userInfo.getId(), userInfo);    }  @Cacheable(key = "#id")    public UserInfo getByName(Integer id) {        return userInfoMap.get(id);    }  @CachePut(key = "#userInfo.id")    public UserInfo updateUserInfo(UserInfo userInfo) {        if (!userInfoMap.containsKey(userInfo.getId())) {            return null;        }        // 取旧的值        UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());        // 替换内容        if (!StringUtils.isEmpty(oldUserInfo.getAge())) {            oldUserInfo.setAge(userInfo.getAge());        }        if (!StringUtils.isEmpty(oldUserInfo.getName())) {            oldUserInfo.setName(userInfo.getName());        }        if (!StringUtils.isEmpty(oldUserInfo.getSex())) {            oldUserInfo.setSex(userInfo.getSex());        }        // 将新的对象存储,更新旧对象信息        userInfoMap.put(oldUserInfo.getId(), oldUserInfo);        // 返回新对象信息        return oldUserInfo;    } @CacheEvict(key = "#id")    public void deleteById(Integer id) {        userInfoMap.remove(id);    }
}

四、Caffeine在Reactor的实战

Caffeine和Reactor的结合是通过CacheMono和CacheFlux来使用的,Caffine会存储一个Flux或Mono作为缓存的结果。

首先定义Caffeine的缓存:​​​​​​​

final Cache<String, String> caffeineCache = Caffeine.newBuilder()      .expireAfterWrite(Duration.ofSeconds(30))      .recordStats()      .build();

CacheMono​​​​​​​

final Mono<String> cachedMonoCaffeine = CacheMono      .lookup(          k -> Mono.justOrEmpty(caffeineCache.getIfPresent(k)).map(Signal::next),          key      )      .onCacheMissResume(this.handleCacheMiss(key))      .andWriteWith((k, sig) -> Mono.fromRunnable(() ->          caffeineCache.put(k, Objects.requireNonNull(sig.get()))      ));

lookup方法查询cache中是否已存在,如果不存在,则通过onCacheMissResume重新生成一个Mono,并通过andWriteWith方法将结果存入缓存中。

CacheFlux

final Flux<Integer> cachedFluxCaffeine = CacheFlux      .lookup(          k -> {            final List<Integer> cached = caffeineCache.getIfPresent(k);             if (cached == null) {              return Mono.empty();            }             return Mono.just(cached)                .flatMapMany(Flux::fromIterable)                .map(Signal::next)                .collectList();          },          key      )      .onCacheMissResume(this.handleCacheMiss(key))      .andWriteWith((k, sig) -> Mono.fromRunnable(() ->          caffeineCache.put(              k,              sig.stream()                  .filter(signal -> signal.getType() == SignalType.ON_NEXT)                  .map(Signal::get)                  .collect(Collectors.toList())          )      ));

同理CacheFlux的用法也类似。

 

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

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

相关文章

【013】基于Vue的酒店客房管理系统(含管理员、普通用户两种身份(附源码数据库、课设报告)

这里写目录标题一、系统详细介绍二、系统部分设计思路三、项目获取一、系统详细介绍 前言&#xff1a; 这次带来的是基于NodejsVueMysql的酒店客房管理系统&#xff0c;含非常非常详细的课设报告&#xff0c;觉得物超所值&#xff01;文末附源码数据库、论文百度云链接 系统登…

高空简易水果采摘装置设计(CAD+proe)

目 录 摘 要 I Abstract II 1 绪论 1 1.1 选题背景及意义 1 1.2研究现状 1 1.2.1国外果园采摘机械现状 1 1.2.2国内果园采摘机械现状 4 1.2.3果园机械存在问题 5 1.2.4果园采摘机械的发展趋势 6 1.3研究主要内容 7 2 高空简易水果采摘装置原理 8 2.1 水果实采摘方式的选择 8 2.…

零基础入门JavaWeb——HTML相关知识

一、HTML概念 HTML是Hyper Text Markup Language的缩写。意思是超文本标记语言。它的作用是搭建网页结构&#xff0c;在网页上展示内容。 1.1 超文本 HTML文件本质上是文本文件&#xff0c;而普通的文本文件只能显示字符。但是HTML技术通过HTML标签把其他网页、图片、音频、…

微信公众号留言如何实时提醒

几乎每个官方品牌都有一个微信公众号&#xff0c;并且会不定期发布和品牌、产品相关的内容&#xff0c;也经常会收到一些用户的留言。但并不是运营人员每时每刻都登录在公众号后台&#xff0c;查看并回复这些用户的问题&#xff0c;如果我希望有用户给我们公众号留言后&#xf…

运动酒店,如何“奇袭”文旅产业精准蓝海赛道——缤跃酒店

近日&#xff0c;缤跃酒店战略牵手昌泰高速&#xff0c;进驻雷公坳文体产业园&#xff0c;共建江西大健康产业重点基地。作为锦江酒店(中国区)旗下360度健康运动中高端生活方式品牌&#xff0c;缤跃高调与南昌雷公坳文体产业园达成战略合作&#xff0c;强势入驻体育产业园&…

Python读取复杂电子表格(CSV)数据小技巧一则

关于CSV格式 逗号分隔值&#xff08;Comma-Separated Values&#xff0c;CSV&#xff0c;有时也称为字符分隔值&#xff0c;因为分隔字符也可以不是逗号&#xff09;&#xff0c;其文件以纯文本形式存储表格数据&#xff08;数字和文本&#xff09;。“CSV”并不是一种单一的、…

竞赛——【蓝桥杯】2022年12月第十四届蓝桥杯模拟赛第二期Java

1、最小的2022 问题描述 请找到一个大于 2022 的最小数&#xff0c;这个数转换成二进制之后&#xff0c;最低的 6 个二进制为全为 0 。 请将这个数的十进制形式作为答案提交。 答案提交 这是一道结果填空的题&#xff0c;你只需要算出结果后提交即可。本题的结果为一个整数…

VWware-安装AD域服务

AD域控就是基于轻型目录访问协议将企业网络中的资源&#xff08;包括用户、计算机、服务器、数据库、共享文件、共享打印机等&#xff09;合理、安全、有效的管理起来。通俗来说就是&#xff1a;解决了单点登录&#xff0c;简化了身份认证&#xff0c;完成了不同用户资源之间的…

Python连接Clickhouse遇坑篇,耗时一天成功连接!

首先&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;不要看网上那些乱七八糟的使用clickhouse-driver连接了&#xff0c;真tm难用&#xff0c;端口能搞死你那种&#xff0c;超级烦&#xff01; 推荐直接看官方…

建筑材料企业如何进行采购价格管理?SCM系统助力企业灵活控制采购价格

如今的“广厦千万间”早已矗立在中华大地的各个角落&#xff0c;建筑行业早已经成为中国国民经济发展的重要支柱性产业。同时随着近几年数字经济的兴起&#xff0c;给传统建筑材料行业带来了巨大挑战&#xff0c;如何优化生产运营、保障设备稳定运行、提高和稳定产品质量是每个…

string类的常用接口说明

STL六大组件&#xff1a; 容器 算法 配接器 迭代器 仿函数 空间配置器 温馨提示&#xff1a;只讲常用接口&#xff0c;使用方法说明详见代码注释 目录 一、string类对象的常见构造 二、string类对象的容量操作 三、类对象的访问及遍历操作 四、string类对象的修改操…

万古霉素修饰银纳米粒/磁性纳米微球/负载万古霉素PLGA缓释微球/硅包银纳米三角片

小编今天给大家带来的科研知识是万古霉素修饰银纳米粒/磁性纳米微球/负载万古霉素PLGA缓释微球/硅包银纳米三角片&#xff0c;来看&#xff01; 万古霉素修饰磁性纳米微球的制备: 磁性纳米粒子具有纳米粒子一般的特性外还具有超顺磁性,而且通过表面修饰可以连接上不同的生物功…

Windows10安装配置allure

1、allure官方文档&#xff1a; https://docs.qameta.io/allure/#_about 官方文档中&#xff0c;windows部署allure步骤&#xff1a; 奈何提示scoop不是內部命令 2、安装scoop scoop官方文档&#xff1a;https://scoop.sh/ 需要打开power shell&#xff0c;执行提示的两条…

【SQL Server + MySQL二 】SQL: DDL数据定义【定义、修改、删除基本表】,DML【憎删改查】,DCL数据控制语言

极其感动&#xff01;&#xff01;&#xff01;当时学数据库的时候&#xff0c;没白学&#xff01;&#xff01; 时隔很长时间回去看数据库的笔记都能看懂&#xff0c;每次都靠这份笔记巩固真的是语雀分享要花钱&#xff0c;要不一定把笔记给贴出来(;༎ຶД༎ຶ) &#xff0c;除…

SpringCloud搭建微服务之OAuth2实现SSO单点登录

SSO单点登录实现方式有多种&#xff0c;在这里不介绍理论&#xff0c;本文只讨论采用spring-security-oauth2来实现&#xff0c;本文共有三个服务&#xff0c;一个权限认证中心&#xff0c;两个客户端 1. 认证服务搭建 1.1. 引入核心依赖 <dependency><groupId>…

网络服务---OSI七层参考模型及各层工作原理详解

OSI网络模型概念 OSI模型&#xff08;Open System Interconnection/Reference Model&#xff09;是指国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连为网络的标准框架&#xff0c;简称OSI。1981年&#xff0c;为了解决不同体系结构的网络的互联问题&#xff…

在IDEA中配置MySQL数据库连接以及在使用mybatis时设置sql语句的代码提示功能

在IDEA中配置MySQL数据库连接以及在使用mybatis时设置sql语句的代码提示功能 一&#xff1a;在IDEA中配置MySQL数据库连接 第一步&#xff1a;在IDEA右侧区域有database选项&#xff0c;点击进去 第二步&#xff1a;database -> data soucre -> mysql 第三步&#xf…

只看优点,这2款可视化产品你更心水谁?

现代的数据可视化设计一般喜欢追求更加高效的工具&#xff0c;我们在选择可视化工具的时候&#xff0c;一定会被繁多的可视化产品晃得眼花缭乱。今天给大家推荐2款我用过的可视化软件&#xff0c;不谈缺陷&#xff0c;只看优点&#xff0c;看看哪款更和你的心意吧&#xff01; …

Acrel-2000M马达保护与监控系统解决方案具有保护、遥控功能可实现无人或少人值守

安科瑞 李可欣 具体可咨询&#xff1a;Acrel_lkx Acrel-2000M马达保护与监控系统&#xff0c;是根据马达监控系统自动化及无人值守的要求&#xff0c;总结国内外的研究和生产的先进经验&#xff0c;专门研制出的新一代马达监控系统。本系统具有保护、遥测、遥信、遥脉、遥调、…

瑞吉外卖(五) 全局异常处理

全局异常处理如何进行全局异常处理&#xff1f;效果展示**ControllerAdvice**如何进行全局异常处理&#xff1f; 效果展示 ControllerAdvice 本质上就是Component&#xff0c;然后&#xff0c;我们来看一下此类的注释&#xff1a; 这个类是为那些声明了&#xff08;ExceptionH…