ServiceComb场景及其原理

news2024/12/31 4:20:41

文章目录

  • Java-Chassis
    • @EnableServiceComb初始化SCB
    • SPIServiceUtils自定义SPI加载器
    • 职责链管理器FilterChainsManager/ConsumerHandlerManager
    • @RpcSchema
    • 注册服务如何保活?
    • @RpcReference
    • PropertySourcesPlaceholderConfigurer
    • ThreadPoolExecutorEx/LinkedBlockingQueueEx
    • ConcurrentHashMapEx
    • AopProxyUtils/BeanUtils
    • ReflectionUtils
    • StringValueResolver

Java-Chassis

ServiceComb中是实现RPC的框架类似Dubbo,又称Java底座。

@EnableServiceComb初始化SCB

通过@ImportResource的方式导入ServiceComb的核心Bean定义文件"classpath*:META-INF/spring/scb-core-bean.xml";和"classpath*:META-INF/spring/*.bean.xml";,注入如下Bean定义。

  • ConfigurationSpringInitializer

  • CseApplicationListener,ContextRefreshedEvent事件监听器

    • 初始化相关的连接

      if (this.applicationContext == applicationContext) {
            // same object. avoid initialize many times.
            return;
          }
          this.applicationContext = applicationContext;
          BeanUtils.setContext(applicationContext);
          HttpClients.load(); // Http相关的
          RegistrationManager.INSTANCE.init(); // 服务注册 管理初始化,通过SPI获取。
          DiscoveryManager.INSTANCE.init(); // 服务发现 管理初始化,通过SPI获取。
      
    • ContextRefreshedEvent Bean实例化完事件触达,初始化ServiceComb引擎SCBEngine

      @Override
        public void onApplicationEvent(ApplicationEvent event) {
          if (initEventClass.isInstance(event)) {
            if (applicationContext instanceof AbstractApplicationContext) {
              ((AbstractApplicationContext) applicationContext).registerShutdownHook();
            }
      
            SCBEngine scbEngine = SCBEngine.getInstance(); // 获取单例
            //SCBEngine init first, hence we do not need worry that when other beans need use the
            //producer microserviceMeta, the SCBEngine is not inited.
      //        String serviceName = RegistryUtils.getMicroservice().getServiceName();
      //        SCBEngine.getInstance().setProducerMicroserviceMeta(new MicroserviceMeta(serviceName).setConsumer(false));
      //        SCBEngine.getInstance().setProducerProviderManager(applicationContext.getBean(ProducerProviderManager.class));
      //        SCBEngine.getInstance().setConsumerProviderManager(applicationContext.getBean(ConsumerProviderManager.class));
      //        SCBEngine.getInstance().setTransportManager(applicationContext.getBean(TransportManager.class));
            scbEngine.setApplicationContext(applicationContext);
            scbEngine.setPriorityPropertyManager(applicationContext.getBean(PriorityPropertyManager.class));
            scbEngine.setFilterChainsManager(applicationContext.getBean(FilterChainsManager.class)); // 指责链管理类
            scbEngine.getConsumerProviderManager().getConsumerProviderList()
                .addAll(applicationContext.getBeansOfType(ConsumerProvider.class).values()); // 获取支持的Consumer形式,如Rest/Pojo
            scbEngine.getProducerProviderManager().getProducerProviderList()
                .addAll(applicationContext.getBeansOfType(ProducerProvider.class).values()); // // 获取Provider注册的元信息,支持从Rest/Pojo中获取
            scbEngine.addBootListeners(applicationContext.getBeansOfType(BootListener.class).values()); // 获取全量SCB初始化监听器BootListener,用于监听SCB在生命周期内的各种事件。
      
            scbEngine.run();
          } else if (event instanceof ContextClosedEvent) {
            if (SCBEngine.getInstance() != null) {
              SCBEngine.getInstance().destroy();
            }
          }
        }
      
  • org.apache.servicecomb.core.executor.GroupExecutor

SPIServiceUtils.getOrLoadSortedService(Registration.class)

SPIServiceUtils自定义SPI加载器

ServiceComb大量使用SPI动态扩展对应的实现,SPI定义的使能及优先级,从全部jar包中

META-INF/services的路径文件中加载扩展类,文件名为接口类的全路径里面内容为接口实现。

  • SPIOrder,加载SPI的顺序,排序从小到大,order数值越小,优先级越高,调用越靠前。

  • SPIEnabled,当前SPI是否使能。

  • SPIServiceUtils,代理到JDK自带的ServiceLoader动态加载SPI类,并使用ReflectionUtils用于获取SPIOrder排序对应的实现类

    // 加载SPI全部类
    public static <T> List<T> loadSortedService(Class<T> serviceType) {
        List<Entry<Integer, T>> serviceEntries = new ArrayList<>();
        ServiceLoader<T> serviceLoader = ServiceLoader.load(serviceType); // 代理到JDK
        serviceLoader.forEach(service -> {
          int serviceOrder = 0;
          Method getOrder = ReflectionUtils.findMethod(service.getClass(), "getOrder"); // 默认为0,如果获取方法
          if (getOrder != null) {
            serviceOrder = (int) ReflectionUtils.invokeMethod(getOrder, service); // 触发方法
          }
    
          Entry<Integer, T> entry = new SimpleEntry<>(serviceOrder, service);
          serviceEntries.add(entry);
        });
    
        List<T> services = serviceEntries.stream()
            .sorted(Comparator.comparingInt(Entry::getKey))
            .map(Entry::getValue)
            .collect(Collectors.toList()); // 根据 getOrder 排序,并返回全部的serviceType。
    
        LOGGER.info("Found SPI service {}, count={}.", serviceType.getName(), services.size());
        for (int idx = 0; idx < services.size(); idx++) {
          T service = services.get(idx);
          LOGGER.info("  {}. {}.", idx, service.getClass().getName());
        } // 输出加载的日志及其顺序,方便定位调试功能
    
        return services;
      }
    // Map<Class<?>, List<Object>> cache = new ConcurrentHashMap<>();
    // loadSortedService 并非线程安全,因此添加类锁
    public static <T> List<T> getOrLoadSortedService(Class<T> serviceType) {
        List<Object> services = cache.get(serviceType);
        if (services == null) {
          synchronized (LOCK) {
            services = cache.get(serviceType);
            if (services == null) {
              services = (List<Object>) loadSortedService(serviceType);
              cache.put(serviceType, services);
            }
          }
        }
    
    

SPI加载类的全量日志:


在这里插入图片描述
在这里插入图片描述

职责链管理器FilterChainsManager/ConsumerHandlerManager

不论在服务的提供方或者在服务的调用方,在触发请求或者响应请求的时候,都会通过职责链的方式顺序调用各种Handler,框架动态从handler.xml文件中读取Handler算子,支持在yaml文件中通过配置文件动态装配,非常灵活。

  • 实现了Handler仓库HandlerConfigUtils。PaaSResourceUtils继承Spring ResourceUtils,读取资源文件

    private static Config loadConfig() throws Exception {
        Config config = new Config();
    	// 1、读取配置文件 classpath* 代表在全量jar包中寻找
    	// 2、classpath*:config/cse.handler.xml   classpath*:config/cse.*.handler.xml
    	// 3、PathMatchingResourcePatternResolver 加载全部的Resource资源路径
        // 4、排序资源的路径(xxxx.handler.cse,根据名字xxx进行排序)
        List<Resource> resList =
            PaaSResourceUtils.getSortedResources("classpath*:config/cse.handler.xml", ".handler.xml");
        for (Resource res : resList) {
          // 5. 通过XmlMapper读取handler.xml文件映射到Config类中
          Config tmpConfig = XmlLoaderUtils.load(res, Config.class);
          config.mergeFrom(tmpConfig);
        }
    
  • Handler算子的编排,使用ConsumerHandlerManager和ProducerHandlerManager读取yaml中的配置文件,通过名字对Handler进行编排,Handler并未实现Order接口,调用顺序交给编排方。

    • Consumer/Provider支持自定义ConsumerHandlerManager/ProducerHandlerManager类生成职责链列表,最后的Consumer Handler-TransportClientHandler.INSTANCE, 最后的Provider Handler–ProducerOperationHandler.INSTANCE

      以下动态创建职责链配置:

      // 职责链Key,配置在yaml文件中
      protected List<Handler> create(String microserviceName) {
          // 是否定义服务对应的handler编排servicecomb.handler.chain.Provider.service.calculator
          // 没有则使用默认的 servicecomb.handler.chain.Provider.default
          String handlerChainKey = "servicecomb.handler.chain." + getName() + ".service." + microserviceName;
          String chainDef = DynamicPropertyFactory.getInstance()
              .getStringProperty(handlerChainKey,
                  defaultChainDef)
              .get();
          LOGGER.info("get handler chain for [{}]: [{}]", handlerChainKey, chainDef);
          return createHandlerChain(chainDef);
        }
      

BootListener

PojoInvocation保存List<Handler>职责链,并实现

@RpcSchema

@RpcSchema(schemaId = "hello")
public class HelloImpl implements Hello {

  @Override
  public String sayHi(String name) {
    return "Hello " + name;
  }

  @Override
  public String sayHello(Person person) {
    return "Hello person " + person.getName();
  }
}
  • 注解会被PojoProducers这个BeanPostProcessor处理,用于往注册中心注册服务。

    protected void processProvider(String beanName, Object bean) {
        // 1、aop后,新的实例的父类可能是原class,也可能只是个proxy,父类不是原class,所以,需要先取出原class,再取标注
        // 调用 AopProxyUtils.ultimateTargetClass
        Class<?> beanCls = BeanUtils.getImplClassFromBean(bean);
        if (beanCls == null) {
          return;
        }
        RpcSchema rpcSchema = beanCls.getAnnotation(RpcSchema.class);
        if (rpcSchema == null) {
          return;
        }
    
        // 2、获取schemaId,如果没有传递则获取接口名
        String schemaId = rpcSchema.schemaId();
        if (StringUtils.isEmpty(schemaId)) {
          Class<?>[] intfs = beanCls.getInterfaces();
          if (intfs.length == 1) {
            schemaId = intfs[0].getName();
          } else {
            throw new Error("Must be schemaId or implements only one interface");
          }
        }
    
        // 3、注册producer元信息
        PojoProducerMeta pojoProducerMeta = new PojoProducerMeta();
        pojoProducerMeta.setSchemaId(schemaId);
        pojoProducerMeta.setSchemaInterface(rpcSchema.schemaInterface());
        pojoProducerMeta.setInstance(bean);
    
        registerPojoProducer(pojoProducerMeta);
      }
    
  • 在SCB启动的时候,scbEngine.getProducerProviderManager会获取PojoProducerProvider -> 读取PojoProducers中注册的全部元信息。

    public List<ProducerMeta> init() {
        // for some test cases, there is no spring context
        if (BeanUtils.getContext() == null) {
          return Collections.emptyList();
        }
    
        PojoProducers pojoProducers = BeanUtils.getContext().getBean(PojoProducers.class);
        for (ProducerMeta producerMeta : pojoProducers.getProducerMetas()) {
          PojoProducerMeta pojoProducerMeta = (PojoProducerMeta) producerMeta;
          initPojoProducerMeta(pojoProducerMeta);
        }
    
        return pojoProducers.getProducerMetas();
      }
    
  • SCB.Run的时候,会通过获取到的元信息,将其注册到注册中心上对外提供服务。

注册服务如何保活?

@RpcReference

@RpcReference(microserviceName = "hello", schemaId = "hello")
private static Hello hello;

Pojo形式的Rpc消费方的注解,将自动从注册中心拉取对应的服务名,动态代理Compute并注入代理类。

  • 注解会被RpcReferenceProcessor这个BeanPostProcessor处理,动态代理增强Hello。十分注意StringValueResolver支持动态解析占位符。

    public class RpcReferenceProcessor implements BeanPostProcessor, EmbeddedValueResolverAware {
      private StringValueResolver resolver;
     @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        // 扫描所有field,处理扩展的field标注
        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
          public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
            processConsumerField(bean, field);
          }
        });
    
        return bean;
      }
      protected void processConsumerField(Object bean, Field field) {
        RpcReference reference = field.getAnnotation(RpcReference.class);
        if (reference == null) {
          return;
        }
    
        handleReferenceField(bean, field, reference);
      }
      private void handleReferenceField(Object obj, Field field,
          RpcReference reference) {
        String microserviceName = reference.microserviceName();
        microserviceName = resolver.resolveStringValue(microserviceName);
    
        PojoReferenceMeta pojoReference = new PojoReferenceMeta();
        pojoReference.setMicroserviceName(microserviceName);
        pojoReference.setSchemaId(reference.schemaId());
        pojoReference.setConsumerIntf(field.getType());
    
    	// proxy = Invoker.createProxy(microserviceName, schemaId, consumerIntf); 动态代理
        pojoReference.afterPropertiesSet();
    
        ReflectionUtils.makeAccessible(field);
        ReflectionUtils.setField(field, obj, pojoReference.getProxy());
      }
     
     
     }
    

PropertySourcesPlaceholderConfigurer

BeanFactoryPostProcessor,用于解析Bean对应的Property。

  • @Value注解,会被AutowiredAnnotationBeanPostProcessor解析,并替换占位符注入合适的值。
  • XML配置如果含有属性注入配置,则PropertySourcesPlaceholderConfigurer#postProcessBeanFactory会被替换占位符注入合适的值。

ThreadPoolExecutorEx/LinkedBlockingQueueEx

ThreadPoolExecutorEx

背景:扩展的ThreadPool,在coreSize到达的时候,并发继续增加,会优先创建Thread直到达到maxSize,才会放入BlockQueue,Dubbo和Tomcat都扩展了类似的功能,当BlockQueue的size为无限大的时候,避免Thread永远无法到达maxSize。

实现:默认ThreadPool按照如下逻辑执行Task。

public void execute(Runnable command) {		
		int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) { // 1、workTask小于coreSize,则直接创建新的Thread执行command。
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) { // 2、已达到coreSize,则放入BlockQueue调用offer方法。
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false)) // 3、BlockQueue放满,则继续添加worker,直到达到最大。
            reject(command);
}

如上要点在第二步,只需要修改workQueue的offer逻辑,就可以优先创建Thead,可使用扩展的LinkedBlockingQueueEx。

在offer的时候,获取TheadPool的size,当小于最大的时候,直接创建新的Thead,执行Task。

public class LinkedBlockingQueueEx extends LinkedBlockingQueue<Runnable> {
  private transient volatile ThreadPoolExecutorEx owner = null;
  public void setOwner(ThreadPoolExecutorEx owner) {
    this.owner = owner;
  }

  @Override
  public boolean offer(Runnable runnable) {
    // task can come before owner available
    if (owner == null) {
      return super.offer(runnable);
    }
    // can not create more thread, just queue the task
    if (owner.getPoolSize() == owner.getMaximumPoolSize()) {
      return super.offer(runnable);
    }
    // no need to create more thread, just queue the task
    if (owner.getNotFinished() <= owner.getPoolSize()) {
      return super.offer(runnable);
    }
    // all threads are busy, and can create new thread, not queue the task
    if (owner.getPoolSize() < owner.getMaximumPoolSize()) {
      return false;
    }
    return super.offer(runnable);
  }

  /*
   * when task is rejected (thread pool if full), force the item onto queue.
   */
  public boolean force(Runnable runnable) {
    if (owner == null || owner.isShutdown()) {
      throw new RejectedExecutionException("queue is not running.");
    }
    return super.offer(runnable);
  }
}

相应的扩展的TheadPool也override部分方法,用于当前提交的任务总数,完成的人数总数,拒绝的任务总数。

public class ThreadPoolExecutorEx extends ThreadPoolExecutor {
  private AtomicInteger submittedCount = new AtomicInteger();

  private AtomicInteger finishedCount = new AtomicInteger();

  private AtomicInteger rejectedCount = new AtomicInteger();

  public ThreadPoolExecutorEx(int coreThreads, int maxThreads, int maxIdleInSecond, TimeUnit timeUnit,
      BlockingQueue<Runnable> queue, ThreadFactory threadFactory) {
    super(coreThreads, maxThreads, maxIdleInSecond, timeUnit, queue, threadFactory);
    if (queue instanceof LinkedBlockingQueueEx) {
      ((LinkedBlockingQueueEx) queue).setOwner(this);
    }
    setRejectedExecutionHandler(this::rejectedExecution);
  }

  @Override
  public void execute(Runnable command) {
    submittedCount.incrementAndGet();
    try {
      super.execute(command);
    } catch (RejectedExecutionException e) {
      if (getQueue() instanceof LinkedBlockingQueueEx) {
        final LinkedBlockingQueueEx queue = (LinkedBlockingQueueEx) getQueue();
        if (!queue.force(command)) {
          throw new RejectedExecutionException("thread pool queue is full");
        }
      } else {
        throw e;
      }
    }
  }

  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    rejectedCount.incrementAndGet();
    finishedCount.incrementAndGet();

    throw new RejectedExecutionException("Task " + r.toString() +
        " rejected from " +
        e.toString());
  }

  @Override
  protected void afterExecute(Runnable r, Throwable t) {
    super.afterExecute(r, t);
    finishedCount.incrementAndGet();
  }

  public int getNotFinished() {
    return submittedCount.get() - finishedCount.get();
  }

  public int getRejectedCount() {
    return rejectedCount.get();
  }
}

ConcurrentHashMapEx

默认的ConcurrentHashMap当Key都在同一个桶的时候,调用computeIfAbsent的时候,都会对当前的桶加锁(分段锁),当在高并发读多余写的场景,这里的加锁会影响单个桶的性能,因此可以优先通过CAS获取下元素,然后再调用默认方法,扩展性能。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dyFvn88b-1670145579656)(images/image-20221204161851865.png)]

  // ConcurrentHashMap.computeIfAbsent always do "synchronized" operation
  // so we wrap it to improve performance
  @Override
  public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
    V value = get(key); // CAS GET
    if (value != null) {
      return value;
    }

    return super.computeIfAbsent(key, mappingFunction);
  }

AopProxyUtils/BeanUtils

ReflectionUtils

StringValueResolver

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

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

相关文章

深刻理解JAVA并发中的有序性问题和解决之道

问题 Java并发情况下总是会遇到各种意向不到的问题&#xff0c;比如下面的代码&#xff1a; int num 0;boolean ready false; // 线程1 执行此方法 public void actor1(I_Result r) {if(ready) {r.r1 num num;} else {r.r1 1;} } // 线程2 执行此方法 public void actor…

Clickhouse 使用DBeaver连接

ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。 据处理大致可以分成两大类&#xff1a;联机事务处理OLTP&#xff08;on-line transaction processing&#xff09;、联机分析处理OLAP&#xff08;On-Line Analytical Processing&#xff09;。 OLTP是传统的…

JavaWeb--JDBC核心技术

JavaWeb--JDBC核心技术JDBC核心技术第1章&#xff1a;JDBC概述1.1 数据的持久化1.2 Java中的数据存储技术1.3 JDBC介绍1.4 JDBC体系结构1.5 JDBC程序编写步骤第2章&#xff1a;获取数据库连接2.1 要素一&#xff1a;Driver接口实现类2.1.1 Driver接口介绍2.1.2 加载与注册JDBC驱…

Redis学习笔记(六)

哨兵 哨兵时一个分布式系统&#xff0c;用于对主从结构中的每台服务器进行监控&#xff0c;当出现故障时通过投票机制选择新的master&#xff0c;并将所有slave连接到新的master哨兵的作用 监控 不断检查master和slave是否正常运行master存活检测、master与slave运行情况检测 通…

Linux调度(三)——抢占式调度

目录 抢占式场景一&#xff1a; 抢占式场景二 抢占的时机 用户态的抢占时机 抢占式机一&#xff1a; 抢占时机二&#xff1a; 内核态的抢占时机 时机一 时机二 总结 之前讲了主动式调度&#xff0c;就是进程运行到一半&#xff0c;因为等待I/O等操作而主动让出CPU&a…

动态规划算法(3)(不同方案数问题+拆分问题)

文章目录不同路径不同路径II整数拆分不同的二叉搜索树动态规划解题五步走&#xff1a; 确定dp数组以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 不同路径 力扣传送门&#xff1a; https://leetcode.cn/problems/unique-paths/description/ 确定dp…

[附源码]JAVA毕业设计酒店订房系统(系统+LW)

[附源码]JAVA毕业设计酒店订房系统&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&…

基于opencv答题卡识别基本处理_1

文章目录1.读取图片2.图片预处理2.1 原图转换为灰度图2.2 高斯滤波处理&#xff0c;去除噪声点2.3 增强亮度2.4 自适应二值化2.5 图片可视化3. 添加边框3.1 使用copyMakeBorder添加边框3.2 可视化图片查看效果3.3 手动截取答题卡区域1.读取图片 img cv2.imread(images/5.png)…

Nx C++程序使用spdlog库进行日志存储

1 spdlog简介 spdlog是一个开源的日志库&#xff0c;在github上有。代码见这里&#xff0c;文档这里 C语言的&#xff0c;支持Linux、windows等系统。 csdn上也有许多介绍&#xff0c;这里列举两个&#xff1a;1、2 2 使用 2.1下载编译链接 有多种使用方式&#xff0c;这里…

(三)沟通管理风险管理采购管理@相关方管理

沟通管理目录概述需求&#xff1a;设计思路实现思路分析1.沟通管理绩效报告提供资源2.管理沟通3.监督沟通风险管理规划风险管理识别风险定性风险分析&#xff1a;定量分析风险规划风险应对实施分享应对监督风险采购管理&#xff1a;12.1 规划采购的管理12.2 实施采购控制采购相…

ResNet网络的改进版:ResNeXt

之前的文章讲过ResNet网络的基本架构&#xff0c;其本质就是让网络的学习目的从学习转为学习&#xff0c;也就是学习输入和输出之间的残差信息&#xff0c;从而缓解了梯度消失和网络退化问题。 本文讲下ResNet网络的改进版&#xff1a;ResNeXt。 架构 下面是ResNet和ResNeXt的架…

9.2、面向对象高级特性(类方法和静态方法、property类属性、单例模式)

文章目录类方法和静态方法property类属性单例模式基于装饰器实现使用_ _ new _ _方法实现面向对象总结类方法和静态方法 类里面定义的方法称为实例方法&#xff0c;python解释器会自动将对象&#xff08;或实例&#xff09;传入方法【pycharm中会自动将self传入&#xff0c;se…

【机器学习】支持向量机【下】软间隔与核函数

有任何的书写错误、排版错误、概念错误等&#xff0c;希望大家包含指正。 在阅读本篇之前建议先学习&#xff1a; 【机器学习】拉格朗日对偶性 【机器学习】核函数 由于字数限制&#xff0c;分成两篇博客。 【机器学习】支持向量机【上】硬间隔 【机器学习】支持向量机【下】…

研发效能工程实践-利用Superset快速打造大数据BI平台

大数据BI平台自研之殇 随着互联网发展&#xff0c;现在随便哪个公司都手握大量数据。如何利用这些数据为公司商业带来价值&#xff0c;触使各个公司投入大量人力财力去做商业智能。 早期的BI可能就是公司Leader叫开发小哥写几句SQL导出数据&#xff0c;然后导入到Excel里绘制几…

echarts:nuxt项目使用echarts

一、项目环境 nuxt 2.X vue2.X vuex webpack 二、安装 yarn add echarts 三、使用 3.1、plugins目录下创建echarts.js import Vue from vue import * as echarts from echarts // 引入echarts Vue.prototype.$echarts echarts // 引入组件&#xff08;将echarts注册为全…

认证服务-----技术点及亮点

大技术 Nacos做注册中心 把新建的微服务注册到Nacos上去 两个步骤 在配置文件中配置应用名称、nacos的发现注册ip地址&#xff0c;端口号在启动类上用EnableDiscoveryClient注解开启注册功能 使用Redis存验证码信息 加入依赖配置地址和端口号即可 直接注入StringRedisTempla…

HTML静态网页作业——关于我的家乡介绍安庆景点

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

Armbian搭建本地Gitea服务器

Armbian搭建本地Gitea服务器 1 安装Docker Docker 是一个用于开发、发布和运行应用程序的开放平台。 Docker 是一个开源的应用容器引擎&#xff0c;Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&…

R语言中的prophet预测时间序列数据模型

本文 将针对R进行的几次建模练习的结果&#xff0c;以魁北克数据为依据&#xff0c;分为13年的训练和1年的测试。prophet与基本线性模型&#xff08;lm&#xff09;&#xff0c;一般加性模型&#xff08;gam&#xff09;和随机森林&#xff08;randomForest&#xff09;进行了比…

ES6:ES6 的新增语法

什么是 ES6 ? ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 年份 版本 2015年6月 ES2015 2016年6月 ES2016 2017年6月 ES2017 2018年6月 ES2018 … … ES6 实际上是一个泛指&#xff0c;泛指 ES2015 及后续的版本。 …