JetCache 使用简单案例到源码解析读这一篇就够

这篇具有很好参考价值的文章主要介绍了JetCache 使用简单案例到源码解析读这一篇就够。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

github.com/alibaba/jet…

一、使用方法:

1. 创建项目— 此处使用springboot项目

2. 引入starter

<dependency> 
    <groupId>com.alicp.jetcache</groupId> 
        <artifactId>jetcache-starter-redis</artifactId>
    <version>2.5.14</version> 
</dependency>

3. 配置application.yaml文件

为测试方便,本机先安装redis
brew install redis

启动redis
1、修改conf  --- /opt/homebrew/etc/redis.conf
	daemonize no改成yes 以守护进程的方式启动
2、启动redis
brew services restart redis

3、客户定链接测试



jetcache:
  statIntervalMinutes: 15
  areaInCacheName: false
  local:
    default:
      type: linkedhashmap
      keyConvertor: fastjson
  remote:
    default:
      type: redis
      keyConvertor: fastjson
      valueEncoder: java
      valueDecoder: java
      poolConfig:
        minIdle: 5
        maxIdle: 20
        maxTotal: 50
      host: 127.0.0.1
      port: 6379

4. 配置启动扫描

EnableMethodCache,EnableCreateCacheAnnotation这两个注解分别激活Cached和CreateCache注解,其他和标准的Spring Boot程序是一样的。这个类可以直接main方法运行、

package com.ghq.jetcachelearn;

import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
import com.alicp.jetcache.anno.config.EnableMethodCache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@EnableMethodCache(basePackages = "com.ghq.jetcachelearn")
@EnableCreateCacheAnnotation
public class JetcacheLearnApplication {

	public static void main(String[] args) {
		SpringApplication.run(JetcacheLearnApplication.class, args);
	}

}

5. 使用案例:

User类:

package com.ghq.learn.jetcachelearn.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @author tobedawn 
 * @date 2022/5/13 10:44 上午
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

    private Long id;
    private String name;

}

UserService:

package com.ghq.learn.jetcachelearn.service;

import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.Cached;
import com.ghq.learn.jetcachelearn.entity.User;

/**
 * @author 
 * @date 2022/5/13 10:43 上午
 */
public interface UserService {

    @Cached(expire = 10,cacheType = CacheType.LOCAL)
    User getUser(Long id);
}

UserSercviceImpl

package com.ghq.learn.jetcachelearn.service.impl;

import com.ghq.learn.jetcachelearn.entity.User;
import com.ghq.learn.jetcachelearn.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * @author 
 * @date 2022/5/13 10:43 上午
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Override
    public User getUser(Long id) {
        log.info("userId {}", id);
        return new User(id, "" + id);
    }
}

测试:

package com.ghq.learn.jetcachelearn;

import com.ghq.learn.jetcachelearn.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class JetcacheLearnApplicationTests {

	@Resource
	UserService userService;

	@Test
	void contextLoads() {
	}

	@Test
	void testUser(){
		userService.getUser(1L);
		userService.getUser(1L);
	}

}

6. 注意事项:

  1. 缓存实体必须序列化
  2. 使用过程中可能会出现包冲突:
    1. 解决方法: jetcache-starter-redis 依赖redis 是2.9.0 ,Maven 打包时可能打出来的包时3.7.x 此时启动报错,修改pom文件强制指定redis 版本

        

        <dependency>
 			<groupId>redis.clients</groupId>
 			<artifactId>jedis</artifactId>
 			<version>2.9.0</version>
 		</dependency>
 
 		<dependency>
 			<groupId>com.alicp.jetcache</groupId>
 			<artifactId>jetcache-starter-redis</artifactId>
 			<version>2.5.14</version>
 			<exclusions>
 				<exclusion>
 					<groupId>redis.clients</groupId>
 					<artifactId>jedis</artifactId>
 				</exclusion>
 			</exclusions>
 		</dependency>

源码解析:

相关链接:

jetCache源码地址:github.com/alibaba/jet…

一、初始化过程

1、自动装配入口:

jetcache-autoconfigure

spring.factorys

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alicp.jetcache.autoconfigure.JetCacheAutoConfiguration

2、JetCacheAutoConfiguration :

功能:

  1. 初始化_globalCacheConfig ,最终生成Bean - GlobalCacheConfig;
  2. Import 不同不同类型的AutoConfiguration 对应不同的缓存方式 —AutoConfigurationg对应继承图;

JetCache 使用简单案例到源码解析读这一篇就够

  1. 不同AutoConfiguration对应生成不同的CacheBuilder ,存储在AutoConfigureBeans

    package com.alicp.jetcache.autoconfigure;
    
    import com.alicp.jetcache.AbstractCacheBuilder;
    import com.alicp.jetcache.CacheBuilder;
    import com.alicp.jetcache.anno.support.ConfigProvider;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.env.ConfigurableEnvironment;
    import org.springframework.util.Assert;
    
    import java.util.*;
    
    /**
     * Created on 2016/11/29.
     *
     * @author <a href="mailto:areyouok@gmail.com">huangli</a>
     */
    public abstract class AbstractCacheAutoInit implements InitializingBean {
    
        private static Logger logger = LoggerFactory.getLogger(AbstractCacheAutoInit.class);
    
        @Autowired
        protected ConfigurableEnvironment environment;
    
        @Autowired
        protected AutoConfigureBeans autoConfigureBeans;
    
        @Autowired
        protected ConfigProvider configProvider;
    
        protected String[] typeNames;
    
        private boolean inited = false;
    
        public AbstractCacheAutoInit(String... cacheTypes) {
            Objects.requireNonNull(cacheTypes,"cacheTypes can't be null");
            Assert.isTrue(cacheTypes.length > 0, "cacheTypes length is 0");
            this.typeNames = cacheTypes;
        }
    
        @Override
        public void afterPropertiesSet() {
            if (!inited) {
                synchronized (this) {
                    if (!inited) {
                        process("jetcache.local.", autoConfigureBeans.getLocalCacheBuilders(), true);
                        process("jetcache.remote.", autoConfigureBeans.getRemoteCacheBuilders(), false);
                        inited = true;
                    }
                }
            }
        }
    
        private void process(String prefix, Map cacheBuilders, boolean local) {
            ConfigTree resolver = new ConfigTree(environment, prefix);
            Map<String, Object> m = resolver.getProperties();
            Set<String> cacheAreaNames = resolver.directChildrenKeys();
            for (String cacheArea : cacheAreaNames) {
                final Object configType = m.get(cacheArea + ".type");
                boolean match = Arrays.stream(typeNames).anyMatch((tn) -> tn.equals(configType));
                if (!match) {
                    continue;
                }
                ConfigTree ct = resolver.subTree(cacheArea + ".");
                logger.info("init cache area {} , type= {}", cacheArea, typeNames[0]);
                CacheBuilder c = initCache(ct, local ? "local." + cacheArea : "remote." + cacheArea);
    	//将子类生成的CacheBuilder 放进autoConfigureBeans 中
                cacheBuilders.put(cacheArea, c); 
            }
        }
    
        protected void parseGeneralConfig(CacheBuilder builder, ConfigTree ct) {
            AbstractCacheBuilder acb = (AbstractCacheBuilder) builder;
            acb.keyConvertor(new FunctionWrapper<>(() -> configProvider.parseKeyConvertor(ct.getProperty("keyConvertor"))));
    
            String expireAfterWriteInMillis = ct.getProperty("expireAfterWriteInMillis");
            if (expireAfterWriteInMillis == null) {
                // compatible with 2.1
                expireAfterWriteInMillis = ct.getProperty("defaultExpireInMillis");
            }
            if (expireAfterWriteInMillis != null) {
                acb.setExpireAfterWriteInMillis(Long.parseLong(expireAfterWriteInMillis));
            }
    
            String expireAfterAccessInMillis = ct.getProperty("expireAfterAccessInMillis");
            if (expireAfterAccessInMillis != null) {
                acb.setExpireAfterAccessInMillis(Long.parseLong(expireAfterAccessInMillis));
            }
    
        }
    
        protected abstract CacheBuilder initCache(ConfigTree ct, String cacheAreaWithPrefix);
    }
    

代码:

package com.alicp.jetcache.autoconfigure;

import com.alicp.jetcache.anno.support.GlobalCacheConfig;
import com.alicp.jetcache.anno.support.SpringConfigProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * Created on 2016/11/17.
 *
 * @author <a href="mailto:areyouok@gmail.com">huangli</a>
 */
@Configuration
@ConditionalOnClass(GlobalCacheConfig.class)
@ConditionalOnMissingBean(GlobalCacheConfig.class)
@EnableConfigurationProperties(JetCacheProperties.class)
//此处IMport对应不同类型缓存配置初始化
@Import({RedisAutoConfiguration.class,
        CaffeineAutoConfiguration.class,
        MockRemoteCacheAutoConfiguration.class,
        LinkedHashMapAutoConfiguration.class,
        RedisLettuceAutoConfiguration.class,
        RedisSpringDataAutoConfiguration.class})
public class JetCacheAutoConfiguration {

    public static final String GLOBAL_CACHE_CONFIG_NAME = "globalCacheConfig";

    private SpringConfigProvider _springConfigProvider = new SpringConfigProvider();

    private AutoConfigureBeans _autoConfigureBeans = new AutoConfigureBeans();

    private GlobalCacheConfig _globalCacheConfig;

    @Bean
    @ConditionalOnMissingBean
    public SpringConfigProvider springConfigProvider() {
        return _springConfigProvider;
    }

    @Bean
    public AutoConfigureBeans autoConfigureBeans() {
        return _autoConfigureBeans;
    }

    @Bean
    public static BeanDependencyManager beanDependencyManager(){
        return new BeanDependencyManager();
    }

    @Bean(name = GLOBAL_CACHE_CONFIG_NAME)
    public GlobalCacheConfig globalCacheConfig(AutoConfigureBeans autoConfigureBeans, JetCacheProperties props) {
        if (_globalCacheConfig != null) {
            return _globalCacheConfig;
        }
        _globalCacheConfig = new GlobalCacheConfig();
        _globalCacheConfig.setHiddenPackages(props.getHiddenPackages());
        _globalCacheConfig.setStatIntervalMinutes(props.getStatIntervalMinutes());
        _globalCacheConfig.setAreaInCacheName(props.isAreaInCacheName());
        _globalCacheConfig.setPenetrationProtect(props.isPenetrationProtect());
        _globalCacheConfig.setEnableMethodCache(props.isEnableMethodCache());
        _globalCacheConfig.setLocalCacheBuilders(autoConfigureBeans.getLocalCacheBuilders());
        _globalCacheConfig.setRemoteCacheBuilders(autoConfigureBeans.getRemoteCacheBuilders());
        return _globalCacheConfig;
    }

}

二、Cache解析

Cache作为jetCache缓存最上层的抽象接口,不同类型的的Cache对应一个子类实现。

JetCache 使用简单案例到源码解析读这一篇就够

不同的子类Cache ,对应不同的CacheBuilder生成器,这里需要关注一下MultiLevelCacheBuilder (用于支持多级缓存)

JetCache 使用简单案例到源码解析读这一篇就够

基于上面使用案例可知道JetCache 在接口或者实现接口上增加相应的注解(@Cached,@CachedUpdate 等),缓存就会自动生效,可以想到JetCache使用的是SpringAOP对方法进行了增强,AOP的代码不在此处展示,直接看缓存相关的关键逻辑

AOP相关代码类列在此处,感兴趣可以自行查阅

CacheAdvisor — advisor

CachePointcut —pointCut

JetCacheInterceptor— 真正切面增强逻辑在这里

JetCacheProxyConfiguration — spring config 类

缓存逻辑 (只贴了关键代码):文章来源地址https://www.toymoban.com/news/detail-442229.html

#JetCacheInterceptor
@Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        if (configProvider == null) {
            configProvider = applicationContext.getBean(ConfigProvider.class);
        }
        if (configProvider != null && globalCacheConfig == null) {
            globalCacheConfig = configProvider.getGlobalCacheConfig();
        }
        if (globalCacheConfig == null || !globalCacheConfig.isEnableMethodCache()) {
            return invocation.proceed();
        }

        Method method = invocation.getMethod();
        Object obj = invocation.getThis();
        CacheInvokeConfig cac = null;
        if (obj != null) {
            String key = CachePointcut.getKey(method, obj.getClass());
            cac  = cacheConfigMap.getByMethodInfo(key);
        }

        if (cac == null || cac == CacheInvokeConfig.getNoCacheInvokeConfigInstance()) {
            return invocation.proceed();
        }

				
				//从这里,idea跟进代码到最终实现逻辑
        CacheInvokeContext context = configProvider.getCacheContext().createCacheInvokeContext(cacheConfigMap);
        context.setTargetObject(invocation.getThis());
        context.setInvoker(invocation::proceed);
        context.setMethod(method);
        context.setArgs(invocation.getArguments());
        context.setCacheInvokeConfig(cac);
        context.setHiddenPackages(globalCacheConfig.getHiddenPackages());
        return CacheHandler.invoke(context);
    }
# CacheContext
public CacheInvokeContext createCacheInvokeContext(ConfigMap configMap) {
        CacheInvokeContext c = newCacheInvokeContext();
        c.setCacheFunction((invokeContext, cacheAnnoConfig) -> {
            Cache cache = cacheAnnoConfig.getCache();
            if (cache == null) {
                if (cacheAnnoConfig instanceof CachedAnnoConfig) {
                    cache = createCacheByCachedConfig((CachedAnnoConfig) cacheAnnoConfig, invokeContext);
                } else if ((cacheAnnoConfig instanceof CacheInvalidateAnnoConfig) || (cacheAnnoConfig instanceof CacheUpdateAnnoConfig)) {
                    CacheInvokeConfig cacheDefineConfig = configMap.getByCacheName(cacheAnnoConfig.getArea(), cacheAnnoConfig.getName());
                    if (cacheDefineConfig == null) {
                        String message = "can't find @Cached definition with area=" + cacheAnnoConfig.getArea()
                                + " name=" + cacheAnnoConfig.getName() +
                                ", specified in " + cacheAnnoConfig.getDefineMethod();
                        CacheConfigException e = new CacheConfigException(message);
                        logger.error("Cache operation aborted because can't find @Cached definition", e);
                        return null;
                    }
                    cache = createCacheByCachedConfig(cacheDefineConfig.getCachedAnnoConfig(), invokeContext);
                }
                cacheAnnoConfig.setCache(cache);
            }
            return cache;
        });
        return c;
    }
#CacheHandler
private static Object invokeWithCached(CacheInvokeContext context)
            throws Throwable {
        CacheInvokeConfig cic = context.getCacheInvokeConfig();
        CachedAnnoConfig cac = cic.getCachedAnnoConfig();
	//此处apply BiFunction 的实现逻辑  见 CacheContext.createCacheInvokeContext
        Cache cache = context.getCacheFunction().apply(context, cac);

        if (cache == null) {
            logger.error("no cache with name: " + context.getMethod());
            return invokeOrigin(context);
        }

        Object key = ExpressionUtil.evalKey(context, cic.getCachedAnnoConfig());
        if (key == null) {
            return loadAndCount(context, cache, key);
        }

        if (!ExpressionUtil.evalCondition(context, cic.getCachedAnnoConfig())) {
            return loadAndCount(context, cache, key);
        }

        try {
            CacheLoader loader = new CacheLoader() {
                @Override
                public Object load(Object k) throws Throwable {
                    Object result = invokeOrigin(context);
                    context.setResult(result);
                    return result;
                }

                @Override
                public boolean vetoCacheUpdate() {
                    return !ExpressionUtil.evalPostCondition(context, cic.getCachedAnnoConfig());
                }
            };
            Object result = cache.computeIfAbsent(key, loader);
            return result;
        } catch (CacheInvokeException e) {
            throw e.getCause();
        }
    }
# AbstractCache
static <K, V> V computeIfAbsentImpl(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,
                                               long expireAfterWrite, TimeUnit timeUnit, Cache<K, V> cache) {
        AbstractCache<K, V> abstractCache = CacheUtil.getAbstractCache(cache);
        CacheLoader<K, V> newLoader = CacheUtil.createProxyLoader(cache, loader, abstractCache::notify);
        CacheGetResult<V> r;
	// @CacheRefresh  走这一块
        if (cache instanceof RefreshCache) {
            RefreshCache<K, V> refreshCache = ((RefreshCache<K, V>) cache);
            r = refreshCache.GET(key);
            refreshCache.addOrUpdateRefreshTask(key, newLoader);
        } else {
            r = cache.GET(key);
        }
        if (r.isSuccess()) {
            return r.getValue();
        } else {
            Consumer<V> cacheUpdater = (loadedValue) -> {
                if(needUpdate(loadedValue, cacheNullWhenLoaderReturnNull, newLoader)) {
                    if (timeUnit != null) {
                        cache.PUT(key, loadedValue, expireAfterWrite, timeUnit).waitForResult();
                    } else {
                        cache.PUT(key, loadedValue).waitForResult();
                    }
                }
            };

            V loadedValue;
            if (cache.config().isCachePenetrationProtect()) {
                loadedValue = synchronizedLoad(cache.config(), abstractCache, key, newLoader, cacheUpdater);
            } else {
                loadedValue = newLoader.apply(key);
		//生产者消费者模式,调用上面的cache.PUT 方法
                cacheUpdater.accept(loadedValue);
            }

            return loadedValue;
        }
    }
# 具体Cache  put逻辑,此处使用RedisCache来做案例
@Override
    protected CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
        try (Jedis jedis = config.getJedisPool().getResource()) {
            CacheValueHolder<V> holder = new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));
            byte[] newKey = buildKey(key);
            String rt = jedis.psetex(newKey, timeUnit.toMillis(expireAfterWrite), valueEncoder.apply(holder));
            if ("OK".equals(rt)) {
                return CacheResult.SUCCESS_WITHOUT_MSG;
            } else {
                return new CacheResult(CacheResultCode.FAIL, rt);
            }
        } catch (Exception ex) {
            logError("PUT", key, ex);
            return new CacheResult(ex);
        }
    }

其他

  • jetcache-anno-api:定义jetcache的注解和常量,不传递依赖。如果你想把Cached注解加到接口上,又不希望你的接口jar传递太多依赖,可以让接口jar依赖jetcache-anno-api。
  • jetcache-core:核心api,完全通过编程来配置操作Cache,不依赖Spring。两个内存中的缓存实现LinkedHashMapCacheCaffeineCache也由它提供。
  • jetcache-anno:基于Spring提供@Cached和@CreateCache注解支持。
  • jetcache-redis:使用jedis提供Redis支持。
  • jetcache-redis-lettuce(需要JetCache2.3以上版本):使用lettuce提供Redis支持,实现了JetCache异步访问缓存的的接口。
  • jetcache-starter-redis:Spring Boot方式的Starter,基于Jedis。
  • jetcache-starter-redis-lettuce(需要JetCache2.3以上版本):Spring Boot方式的Starter,基于Lettuce。

到了这里,关于JetCache 使用简单案例到源码解析读这一篇就够的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JavaScript 入门(简单易懂) 看这一篇就够了

    目录 1、什么是JavaScript 1.1、概述 1.2、历史 2、快速入门 2.1、引入引入JavaScript 2.2、基本语法 2.3、数据类型 2.4、严格检查模式 3、数据类型 3.1、字符串 3.2、数组 3.3、对象 3.4、流程控制 3.5、Map和Set 3.6 iterator 3.7数据类型转换字符串类型 3.8数据类型转换数字型(重点) 3.9标识

    2024年02月02日
    浏览(96)
  • 案例分享 | 金融业智能运维AIOps怎么做?看这一篇就够了

    ​ 构建双态IT系统,AIOps已经是必然的选择。 运维数字化转型已是大势所趋,实体业务的逐步线上化对IT系统的稳定与安全提出更高要求,同时随着双态IT等复杂系统的建立, 如何平衡IT运维效率与成本成为区域性银行面临的重要问题, 智能运维AIOps成为主要解决方案。 智能

    2024年02月03日
    浏览(38)
  • 【Java面向对象】多态的详细介绍,简单易懂,看这一篇就够了

    A: 方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承的基础之上的。简单来说,多态是具有表现多种形态的能力的特征。 消除类型之间的耦合关系 可替代性 可扩充性 接口性 灵活性 简化性 重载式多态在编译时已经确定好了。方法名相同而参数

    2024年01月20日
    浏览(61)
  • 还是搞不懂Anaconda是什么?读这一篇文章就够了

    概述 Anaconda ,中文 大蟒蛇 ,是一个开源的Anaconda是专注于数据分析的Python发行版本,包含了conda、Python等190多个科学包及其依赖项。 Anaconda就是可以便捷获取包且对包能够进行管理,包括了python和很多常见的软件库和一个包管理器conda。常见的科学计算类的库都包含在里面了

    2024年02月03日
    浏览(83)
  • Numpy入门看这一篇就够了【史上入门最简单,开袋即食】

    一边学习一边分享,好记性不如烂笔头 目录 一边学习一边分享,好记性不如烂笔头 NumPy问题思考: numpy是什么? 为什么要学习numpy? numpy是怎么组成的?特点是什么? numpy的应用场景有哪些? NumPy介绍: Tensor概念: 1、ndarray数组 1.1、为什么引入ndarray数组 1.2、创建ndarray数组

    2024年02月09日
    浏览(44)
  • FastJson使用详解这一篇就够了

    第一章 FastJson使用详解这一篇就够了 第二章 FastJsonHttpMessageConverter 类的作用与使用详解 第三章 Jackson 使用详解 为什么使用fastJson,如何使用fastJson以及通用的配置和使用方式将在本文进行详细介绍。 FastJson是一种高性能的Java JSON解析库,它采用类似于Jackson和Gson的JSON序列化和

    2024年02月12日
    浏览(31)
  • SourceTree使用看这一篇就够了

     你梦想有一天成为git大师,然而面对复杂的git命令,你感觉TMD这我能记得住吗?你曾经羡慕从命令行敲git命令,才会更加炫酷,然而时间一长,TMD命令我有忘了。那么今天我介绍的这款工具会让你从git命令中解救出来,这就是git可视化工具SourcTree。 事实上Git的功能十分强大

    2024年02月08日
    浏览(55)
  • Elasticsearch的安装及使用,这一篇就够了

      Elasticsearch是一个基于Apache Lucene的开源搜索引擎。Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。 特点: 分布式的实时文件存储,每个字段都被索引并可被搜索 分布式的实时分析搜索引擎–做不规则查询 可以扩展到上百台服务器,处理PB级结

    2024年02月05日
    浏览(48)
  • CompletableFuture使用详解(全网看这一篇就行)

    CompletableFuture是jdk8的新特性。CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步会点、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。 1. supplyAsync supplyAsync是创建带有返回值的异步任务。它有如下两

    2024年02月03日
    浏览(44)
  • Python爬虫案例解析:五个实用案例及代码示例(学习爬虫看这一篇文章就够了)

    导言: Python爬虫是一种强大的工具,可以帮助我们从网页中抓取数据,并进行各种处理和分析。在本篇博客中,我们将介绍五个实用的Python爬虫案例,并提供相应的代码示例和解析。通过这些案例,读者可以了解如何应用Python爬虫来解决不同的数据获取和处理问题,从而进一

    2024年02月16日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包