JetCache 缓存开源组件设计精要

这篇具有很好参考价值的文章主要介绍了JetCache 缓存开源组件设计精要。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

​作者:张隆 阿里电影演出技术中心团队

本文将为大家介绍JetCache缓存开源组件的前世今生,并剖析了JetCache的工作原理及设计优势。

一、JetCache的前世今生

JetCache 缓存开源组件设计精要

1.1 诞生-阿里彩票JetCache的伊甸园

JetCache 缓存开源组件设计精要

  • 2013年,JetCache诞生于 [ 阿里彩票 ],作者是 [ huangli ] 凭借得天独厚的Tair支持和丰富的Spring生态注解支持,赢得了大家的喜爱。

  • 2015年,随着SpringBoot的大热和集团内PandoraBoot的彻底铺开,JetCache以Starter的形式实现了扩展,优化了配置项,在架构设计和性能上更上一层楼。

  • 2015年同年,JetCache开源至Github,作为alibaba的开源缓存框架,其易用性和设计先进性吸引了大批国内外用户,截止当前在github上累计3.7k star,870 fork。

  • 2018年JetCache最大版本更新,对整体的设计进行了调整,修改了默认的序列化方式,集成支持了SpringData,RedisLettuce,Redisson等更加高效以及功能更加灵活且高级的三方SDK。

1.2 整合-开源界大放异彩

JetCache 缓存开源组件设计精要

  • JetCache原生支持的远程缓存是Tair,但是Tair在集团外并不可用。JetCache为了拥抱开源,实现了时下主流的GuavaCache, CaffeineCache,  Redis,MemCache基本覆盖了国内的主流缓存中间件。

  • 在功能性方面,JetCache满足了用户一行注解解决Method缓存的刚需,同时也能通过叠加注解的方式非常高效的处理缓存穿透,缓存击穿,缓存雪崩,缓存失效等经典分布式缓存的问题,这让用户充分体验到了缓存框架的效率优势和设计先进性。

  • 在扩展性方面,JetCache满足了用户一行注解解决Method缓存的刚需,也提供了优秀的扩展能力。想要实现一个新的Cache类型,只需要实现AbstractEmbeddedCache或者AbstractExternalCache就可以以非常低廉的成本实现一个新的缓存框架。

1.3 挑战-SpringCache江湖地位

  • 在2015年最火的框架是SpringBoot,SpringBoot提供了非常丰富的组件支持以及模块化的组件管理,其中就包括基于JSR-107--JCacheAPI实现的SpringCache框架。

  • SpringCache框架很好的实现了JCacheAPI,在当时占据了非常有力的位置,几乎所有的SpringBoot初创项目,都选择了使用SpringCache来作为他们的第一个缓存框架。但随着软件工程的规模越来越大,分布式场景的经典问题也接踵而至,显然SpringCache在应对分布式环境的经典问题时显得太过于稚嫩。 

    对于分布式场景,缓存穿透,缓存击穿,缓存雪崩 等经典问题,缺少足够成熟的方案。

    高级特性上,如 分布式锁,多级缓存滑动窗口,缓存序列化,异步API支持等实际工作场景经常会需要用到的核心能力,要么没有,要么不够用。

    对于扩展性上,设计的不够开放和正交,很难低成本的完成一些高级功能的扩展。

  • JetCache在这方面做的就不错,并且在迁移缓存方面基本上可以做到换注解平替,所以一旦工程规模达到一定量级,很多架构师会选择从SpringCache的方式切换到JetCache上。

二、JetCache是如何工作的

完整的组件串联文档:

https://app.heptabase.com/w/db02907915c401c6e33ddcc47e4d67a589047a846be16f30de1644501d939787

JetCache 缓存开源组件设计精要

2.1 JSR-107--缓存JCache标准抽象实现

Java在2012的JSR-107协议中新增了关于缓存的抽象设计标准--JCache。

JetCache 缓存开源组件设计精要

2.2 丰富注解-无侵入抽象设计

JetCache 缓存开源组件设计精要

2.3 启动器和配置-Bean方式

JetCache 缓存开源组件设计精要

@Configuration
@EnableMethodCache(basePackages = "com.taobao.film.tfmind")
@EnableCreateCacheAnnotation
public class JetCacheConfig {
    
@Bean
public GlobalCacheConfig config(
  @Qualifier("ldbTairManager") TairManager tairLdbManager,
  @Qualifier("mdbTairManager") TairManager tairMdbManager,
  @Qualifier("rdb3CacheCompose") Rdb3CacheCompose rdb3CacheCompose) {
  Map<String, CacheBuilder> localBuilders = new HashMap<>();
  Map<String, CacheBuilder> remoteBuilders = new HashMap<>();
​
​
  // 本地缓存 CaffeineCache
  EmbeddedCacheBuilder<?> localBuilder = CaffeineCacheBuilder
    .createCaffeineCacheBuilder()
    .keyConvertor(FastjsonKeyConvertor.INSTANCE);
  localBuilders.put(CacheConsts.DEFAULT_AREA, localBuilder);
​
​
  // 远程缓存 LDB
  TairCacheBuilder<?> ldbCacheBuilder = TairCacheBuilder.createTairCacheBuilder()
    .keyConvertor(FastjsonKeyConvertor.INSTANCE)
    .valueEncoder(KryoValueEncoder.INSTANCE)
    .valueDecoder(KryoValueDecoder.INSTANCE)
    .tairManager(tairLdbManager)
    .namespace(SysConstants.NEW_TAIR_AREA)
    .cacheNullValue(true);
  remoteBuilders.put(CacheConsts.DEFAULT_AREA, ldbCacheBuilder);
​
  // 远程缓存 MDB
  TairCacheBuilder<?> ldbCacheBuilder = TairCacheBuilder.createTairCacheBuilder()
    .keyConvertor(FastjsonKeyConvertor.INSTANCE)
    .valueEncoder(KryoValueEncoder.INSTANCE)
    .valueDecoder(KryoValueDecoder.INSTANCE)
    .tairManager(tairLdbManager)
    .namespace(SysConstants.NEW_TAIR_AREA)
    .cacheNullValue(true);
  remoteBuilders.put("MDB", mdbCacheBuilder);
​
  // 远程缓存 RDB
  TairRdb3CacheBuilder<?> rdb3CacheBuilder = TairRdb3CacheBuilder.createRedisCacheBuilder()
    .keyConvertor(FastjsonKeyConvertor.INSTANCE)
    .valueEncoder(KryoValueEncoder.INSTANCE)
    .valueDecoder(KryoValueDecoder.INSTANCE)
    .jedisPool(rdb3CacheCompose.getJedisPool())
    .cacheNullValue(true);
  remoteBuilders.put("RDB3", rdb3CacheBuilder);
​
  // 构建全局缓存配置
  GlobalCacheConfig globalCacheConfig = new GlobalCacheConfig();
  globalCacheConfig.setConfigProvider(springConfigProvider());
  globalCacheConfig.setLocalCacheBuilders(localBuilders);
  globalCacheConfig.setRemoteCacheBuilders(remoteBuilders);
  globalCacheConfig.setStatIntervalMinutes(5);
  globalCacheConfig.setAreaInCacheName(false);
​
  return globalCacheConfig;
}
}

2.4 注解模式-AOP-缓存

基于AOP的方法级缓存,最常用最直观的CacheAside模式。

  public interface UserService {
      @Cached(expire = 3600, cacheType = CacheType.REMOTE)
      User getUserById(long userId);
  }

2.5 注解模式-Cache-API 缓存

基于CacheAPI的缓存形式,复杂场景下最灵活的模式。

  @CreateCache(expire = 3600, cacheType = CacheType.REMOTE)
  private Cache<Long, UserDO> userCache;

2.6 高级API模式-手动创建CacheAPI

  @Autowired
  private CacheManager cacheManager;
  private Cache<String, UserDO> userCache;
  
  public UserDO getTestCacheValue() {
    if(userCache == null) {
      QuickConfig qc = QuickConfig.newBuilder("userCache")
          .expire(Duration.ofSeconds(100))
          .cacheType(CacheType.BOTH)
          .syncLocal(true) // invalidate local cache in all jvm process after update
          .build();
      userCache = cacheManager.getOrCreateCache(qc);
    }
    return userCache.get("TestCacheKey")
  }

2.7 Cache基础缓存操作

  // 数据存储
  void put(K key, V value); // 数据录入
  void putAll(Map<? extends K,? extends V> map); // 批量数据录入
  boolean putIfAbsent(K key, V value); // 卫语句的数据存储
  
  // 数据读取
  V get(K key); // 数据读取
  Map<K,V> getAll(Set<? extends K> keys); // 批量数据读取
  
  // 数据删除
  void remove(K key);
  void removeAll(Set<? extends K> keys);
  
  // 异步高级缓存API
  V computeIfAbsent(K key, Function<K, V> loader);
  V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull);
  V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull, long expire, TimeUnit timeUnit);
  
  // 分布式锁
  AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit);
  boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action);
  
  // 原始缓存API (一般不用)
  CacheGetResult<V> GET(K key);
  MultiGetResult<K, V> GET_ALL(Set<? extends K> keys);
  CacheResult PUT(K key, V value);
  CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
  CacheResult PUT_ALL(Map<? extends K, ? extends V> map);
  CacheResult PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);
  CacheResult REMOVE(K key);
  CacheResult REMOVE_ALL(Set<? extends K> keys);
  CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);

2.8 分布式-缓存穿透

JetCache 缓存开源组件设计精要

  • 分布式场景下的热点数据通常都保存在缓存当中,以减少数据库的压力,提升服务的性能。

  • 缓存击穿是指,攻击者利用随机访问的方式短时间大量的访问不存在的数据,由于数据不存在,所以缓存中查不到,请求越过缓存层直达数据库,造成数据库的压力激增。

  • 通常的解法有:[空值缓存] 及 [布隆过滤器]

  • JetCache使用了较为轻量级的 [空值缓存] 方式,来解决这个问题。

@Cached(cacheNullValue=true)@CreateCache(cacheNullValue=true)

// AbstractCache.class
​
static <K, V> V computeIfAbsentImpl(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,
                                               long expireAfterWrite, TimeUnit timeUnit, Cache<K, V> cache) {
  .......
    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();
        }
     }
  };
  ......
  }

2.9 分布式-缓存击穿

JetCache 缓存开源组件设计精要

  • CacheAside模式的缓存由于本身有淘汰策略,在数据失效后,缓存组件会直接访问数据库尝试重建缓存。

  • 在大规模分布式热点的情况下,一旦热点数据失效,会有大量的请求同时尝试重建缓存,这不但会导致资源浪费,更加危险的是会造成数据库瞬时极大的压力。

  • JetCache通过注解@CachePenetrationProtect实现了JVM内存锁级的击穿保护,使并发重建的请求限制到可控范围。( 如果数据利用率高还可以使用@CacheRefresh的方式来实现基于分布式锁的缓存重建能力 )

// AbstractCache.class
​
static <K, V> V computeIfAbsentImpl(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,
                                               long expireAfterWrite, TimeUnit timeUnit, Cache<K, V> cache) {
  ....
    if (cache.config().isCachePenetrationProtect()) {
      loadedValue = synchronizedLoad(cache.config(), abstractCache, key, newLoader, cacheUpdater);
    } else {
      loadedValue = newLoader.apply(key);
      cacheUpdater.accept(loadedValue);
    }
  ....
}

2.10 分布式-缓存雪崩

JetCache 缓存开源组件设计精要

  • 缓存雪崩与缓存击穿类似,但是情况更为危机后果更为严重,有可能导致整个集群服务瘫痪。

  • 当大量热点缓存同时失效的时候,大量的缓存重建请求会直达数据库,造成服务节点瘫痪形成服务雪崩。

  • 缓存雪崩的处理方式较为复杂,但简单来说: 

    可以建立多级缓存,通过设置不同的过期时间,形成重叠数据滑动窗口。

    通过服务主动维护异步任务的形式,维护一块永固缓存,防止热点失效。

  • JetCache 可以通过多级缓存来避免这种情况。

  • JetCache 还提供了@CacheRefreshCacheLoader的方式,使服务有能力创建内建的时间块任务,来达到维护分布式环境下永固缓存的目的。

// RefreshCache.class
​
public void run() {
  try {
    if (config.getRefreshPolicy() == null || (loader == null && !hasLoader())) {
      cancel();
      return;
    }
    long now = System.currentTimeMillis();
    long stopRefreshAfterLastAccessMillis = config.getRefreshPolicy().getStopRefreshAfterLastAccessMillis();
    if (stopRefreshAfterLastAccessMillis > 0) {
      if (lastAccessTime + stopRefreshAfterLastAccessMillis < now) {
        logger.debug("cancel refresh: {}", key);
        cancel();
        return;
      }
    }
    logger.debug("refresh key: {}", key);
    Cache concreteCache = concreteCache();
    if (concreteCache instanceof AbstractExternalCache) {
      externalLoad(concreteCache, now);
    } else {
      load();
    }
  } catch (Throwable e) {
    logger.error("refresh error: key=" + key, e);
  }
}

2.11 分布式-缓存失效/更新

  • 缓存数据也需要维护,尤其是缓存和实际数据不一致的情况下。

  • 例如用户数据,就非常需要缓存失效和缓存更新的能力,及时的在用户做了数据操作之后更新公共缓存的数据。

  • JetCache通过@CacheInvalid@CacheUpdate提供了这种能力,极大程度的避免了缓存数据不一致的情况,同时也增强了缓存操作的灵活性。

public interface UserService {
    @Cached(name="userCache.", key="#userId", expire = 3600)
    User getUserById(long userId);
​
    @CacheUpdate(name="userCache.", key="#user.userId", value="#user")
    void updateUser(User user);
​
    @CacheInvalidate(name="userCache.", key="#userId")
    void deleteUser(long userId);
}

三、JetCache框架设计剖析优势有哪些?

3.1 支持多种KV序列化方式

JetCache 缓存开源组件设计精要

  • CacheKey Convertor :用来进行缓存Key的加工处理 

    环境隔离: CacheKey在影演使用最广泛方式,抽象实现环境前缀Convertor就可以当前环境进行缓存前缀的拼接,从而达到数据隔离的目的。

    长短缓存: 长短缓存通常使用对象缓存作为Key,为了容灾短缓存和长缓存通常使用了不同的缓存Key。通过实现长短缓存Convertor可以实现相同对象,可以控制长、短缓存的Key使用对象中的不同属性构造,从而达到短缓存提升性能,长缓存降级的目的。

  • ValueEncode、ValueDecode:用来提升缓存性能的绝佳方式

    高性能序列化:选择JavaSerialize、kyro、Kyro5的序列化方式可以极大程度的提升我们系统对性能的要求,很适合应对高并发环境的大流量压力。

    兼容性序列化:选择JSON(FastJson、FastJson2、Jackson)的方式,可以为缓存提供良好的兼容性。在架构设计的初期,完全可以采用这种方式来实现平稳迭代。

    加密序列化:当我们使用外部数据库的时候,我们可以自己实现ValueEncode和ValueDecode来保障我们数据的安全。

3.2 支持多种本地,远程缓存

JetCache 缓存开源组件设计精要

3.3 多级缓存-乐高积木

JetCache 缓存开源组件设计精要

  • 长短缓存:通过多级缓存加上KeyConvertor可以快速构建成本最低效率最高的长短缓存组件。

  • 用户缓存:互动用户数据很多,配合用户路由,可以结合 LocalCache + LDB 的方式既保证数据的可靠性,又能将性能从10ms -> 1ms 级。

  • 自定义多级“缓存”:由于JetCache缓存的实现相当方便,我们甚至可以实现 Mysql,Opensearch 的Cache实现,并且把它组转到多级缓存之中,形成一种结构稳固的数据读写组件。

3.4 高级特性-加载器

JetCache 缓存开源组件设计精要

// AOP 缓存 Example
@Cached(expireTime= 5 * 60)
public Long loadOrderSumFromDatabase(String orderType);
@CreateCache(expireTime= 5 * 60)
private Cache<String, Order> orderSumCache;
​
// 每分钟拉取订单总数,形成持久缓存
@PostConstruct
public void init(){
  RefreshPolicy policy = RefreshPolicy.newPolicy(1, TimeUnit.MINUTES)
    .stopRefreshAfterLastAccess(30, TimeUnit.MINUTES);
  orderSumCache.config().setLoader(this::loadOrderSumFromDatabase);
  orderSumCache.config().setRefreshPolicy(policy);
}

3.5 高级特性-监听器

JetCache 缓存开源组件设计精要

官方实现-数据报告

// 数据报告Monitor的代码实现
public class DefaultCacheMonitor implements CacheMonitor {
    public synchronized void afterOperation(CacheEvent event) {
        if (event instanceof CacheGetEvent) {
            CacheGetEvent e = (CacheGetEvent) event;
            afterGet(e.getMillis(), e.getKey(), e.getResult());
        } else if (event instanceof CachePutEvent) {
            CachePutEvent e = (CachePutEvent) event;
            afterPut(e.getMillis(), e.getKey(), e.getValue(), e.getResult());
        } else if (event instanceof CacheRemoveEvent) {
            CacheRemoveEvent e = (CacheRemoveEvent) event;
            afterRemove(e.getMillis(), e.getKey(), e.getResult());
        } else if (event instanceof CacheLoadEvent) {
            CacheLoadEvent e = (CacheLoadEvent) event;
            afterLoad(e.getMillis(), e.getKey(), e.getLoadedValue(), e.isSuccess());
        } else if (event instanceof CacheGetAllEvent) {
            CacheGetAllEvent e = (CacheGetAllEvent) event;
            afterGetAll(e.getMillis(), e.getKeys(), e.getResult());
        } else if (event instanceof CacheLoadAllEvent) {
            CacheLoadAllEvent e = (CacheLoadAllEvent) event;
            afterLoadAll(e.getMillis(), e.getKeys(), e.getLoadedValue(), e.isSuccess());
        } else if (event instanceof CachePutAllEvent) {
            CachePutAllEvent e = (CachePutAllEvent) event;
            afterPutAll(e.getMillis(), e.getMap(), e.getResult());
        } else if (event instanceof CacheRemoveAllEvent) {
            CacheRemoveAllEvent e = (CacheRemoveAllEvent) event;
            afterRemoveAll(e.getMillis(), e.getKeys(), e.getResult());
        }
    }
}
// 数据报告Monitor 的注册
public void addMonitors(CacheManager cacheManager, Cache cache, QuickConfig quickConfig) {
    if (metricsManager == null) {
        return;
    }
    DefaultCacheMonitor monitor = new DefaultCacheMonitor(quickConfig.getName());
    cache.config().getMonitors().add(monitor);
}

效果:

JetCache 缓存开源组件设计精要

四、影演之路:影演如何发展了JetCache

Jetcache在开源界如此火,离不开它遵循了JSR107标准,遵从于原则的设计和对原则的扩充使得它在学习效率上非常高效,代码结构上也非常优秀,并且它也在开放性和扩展性下足了功夫,真正实现了架构上的 ”正交“。

在电影演出BU内部,由于要应对业务的复杂性,所以需要针对Jetcache做一些比较定制化的扩展,其中有关于核心底层tair的支持,也有关于分布式场景管理的诉求,更有对业务瓶颈挑战的通用设计。

通过这些新的场景设计,我们极大的丰富了Jetcache的应用场景以及让它重新再集团中间件的环境之下,长出了新的分支,非常好的支撑的业务发展。

4.1 通用高并发三级缓存熔断组件

JetCache 缓存开源组件设计精要

4.2 缓存后置写(Cache Write-Back)

JetCache 缓存开源组件设计精要

缓存后置写是一种 Cache Write-Back 模式的实现:

1)缓存后置写由JetCache的Monitor来实现活跃事件的监控以及记录,每当有事件产生,后置写监控器就会被触发。

2)将需要缓存后置写的Cache实例通过Config.Monitor的方式添加好默认后置写监控器。

3)活跃Event 将会被不同的 缓存后置写实现捕获,并会将CacheKey缓存在一个唯一分布式队列中,等待调度。

4)我们通过了 ScheduleX 实现了分布式调度器,每分钟都会进行触发(当然每个后置写实现可能会有不同的触发频率)

目前影演使用缓存后置写实现了非常多的实用应用,包括: 

  • 影演评分数据准实时合并入库,同步至淘票票,大麦三方业务库。( 准实时并发写方案,数据同步方案)

  • 线上、预发缓存准实时同步。 (环境数据一致性)

  • 数据变更对比,趋势数据记录。 ( 数据对账,数据趋势图 )

  • 本地缓存广播器。( 本地缓存一致性,避免数据波动)

4.3 本地缓存广播器(LocalCache Distribute)

JetCache 缓存开源组件设计精要

4.4 稀疏列表缓存实现(MultiListCache)

JetCache 缓存开源组件设计精要

JetCache 缓存开源组件设计精要

五、面向未来:JetCache还有哪些不足

JetCache 缓存开源组件设计精要

JetCache 缓存开源组件设计精要文章来源地址https://www.toymoban.com/news/detail-413727.html

到了这里,关于JetCache 缓存开源组件设计精要的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【分布式缓存】springboot整合jetcache使用详解

    目录 一、前言 二、多级缓存问题 2.1 缓存分类 2.1.1 本地缓存 2.1.2 分布式缓存

    2024年02月17日
    浏览(43)
  • 优化您的Spring应用程序:缓存注解的精要指南

    当我们构建和运行Spring应用程序时,通常面临一个共同的挑战:性能。无论是为了提供更快的响应时间、更高的并发处理能力,还是为了降低数据库或外部服务的负载,性能优化一直是开发人员和系统管理员的关注焦点。 在本篇博客中,我们将深入研究Spring的 @Cacheable 、 @

    2024年02月07日
    浏览(53)
  • 开源多组件数据中台整体框架设计

      亚里士多德的《辩证法》首先提出了“三段论”的论证思维工具,即论据、论点和结论,并指出每个部分都必须具有必要的条件,以确保整个论证的有效性。本文参考该论证思维工具,结合自身的工作经验,来论证下当前为什么“开源多组件数据中台”是政务数据应用的

    2024年02月10日
    浏览(33)
  • 扩散模型DDPM开源代码的剖析【对应公式与作者给的开源项目,diffusion model】

    论文地址:https://proceedings.neurips.cc/paper/2020/hash/4c5bcfec8584af0d967f1ab10179ca4b-Abstract.html 项目地址:

    2023年04月08日
    浏览(38)
  • (汇总篇)语义SLAM相关开源方案| 全球优秀作者与实验室 | SLAM学习资料整理

    以下内容收集也不完整,无法涵盖视觉 SLAM 的所有研究,也欢迎大家有好的方案欢迎留言或者私信。 1.1 Geometric SLAM (26项) 这一类是传统的基于特征点、直接法或半直接法的几何 SLAM。 1. PTAM 论文 :Klein G, Murray D. Parallel tracking and mapping for small AR workspaces [C]//Mixed and Augmented

    2024年02月03日
    浏览(57)
  • 【欢迎您的到来】这里是开源库get_local_info作者的付费专栏

    您好,         我是带剑书生,开源库get_local_info的作者,欢迎您的到来,这里是我的付费专栏,在上一个付费专栏里,用简洁的语言,通俗的话语,帮助您更好的学习了Rust,现在将用本专栏来为您解决学习和工作中遇到的《疑难杂症》,让您带飞项目,反击暴击~    

    2024年01月19日
    浏览(39)
  • 主动打包、高效管理的主题工具:逐浪HMS主题大师1.5-即速推送、华为小组件缓存优化、锁屏提取、资源审计、技术秘笈,移动主题设计利器

    主动打包:逐浪HMS主题大师1.3-即速推送、华为小组件缓存优化、锁屏提取、资源审计、技术秘笈,移动主题设计利器 新版功能 研发背景 软件架构 使用环境 启动使用 联系我们 界面欣赏 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-daQWCR6K-1685

    2024年02月07日
    浏览(85)
  • Layui Vue - 优雅经典、免费开源的 Vue 3 桌面端 UI 组件库,沿用 layui 设计规范,开箱即用,自带 Pear Admin Next 后台管理系统

    经典的 layui 没有停止维护,还出了 Vue 3 版本的,依旧好用,分享给大家。 关于 Layui Vue 在介绍 Layui Vue 之前有必要先介绍一下 Layui。Layui 是一套经典的开源的 Web UI 组件库,作者是大名鼎鼎的前端大神贤心。Layui 采用轻量级模块化规范,遵循原生态的 HTML / CSS / JavaScript 开发模

    2024年02月08日
    浏览(38)
  • 动态组件、组件缓存

    多个组件使用一个挂载点,并动态切换,就是动态组件 需求:完成一个注册功能页面,2个按钮,一个填写注册信息,一个填写用户简介信息  步骤 定义两个组件 UserName.vue,UserInfo.vue 2个组件 引入到 App.vue组件中 data中定义变量来存放要显示的 组件名 要设置挂载点 component,

    2024年02月10日
    浏览(45)
  • Vue组件——动态、缓存、异步组件

    语法:静态:component is=\\\"组件名\\\"/component            动态:component :is=\\\"组件名\\\"/component 可以用来实现页面的切换: 问题:当我们在其中一个组件中写了一个输入框,在输入过一次后,切换到其他页面然后再切换回来时,之前输入的值就没有了。因为切换走当前组件时,该组

    2024年02月09日
    浏览(38)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包