首页 > 其他分享 >.net core IOC容器实现(三)--CallSite

.net core IOC容器实现(三)--CallSite

时间:2023-06-28 21:34:45浏览次数:45  
标签:core serviceType CallSite -- callSiteChain descriptor ServiceCallSite callSite n

接着上面一节,这一节主要来看看 callSite 是如何生成的

CallSite 是通过 CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)生成的,CallSiteFactory 是在 ServiceProvider 里实例化的。代码如下

private readonly ConcurrentDictionary<ServiceCacheKey, ServiceCallSite> _callSiteCache 
    = new ConcurrentDictionary<ServiceCacheKey, ServiceCallSite>(); 
//从 _callSiteCache 查找,如果有直接返回,如果没有 调用 CreateCallSite() 方法创建
internal ServiceCallSite? GetCallSite(Type serviceType, CallSiteChain callSiteChain) =>            
    _callSiteCache.TryGetValue(
        new ServiceCacheKey(serviceType, DefaultSlot), out ServiceCallSite? site) 
    ? site :CreateCallSite(serviceType, callSiteChain);

从上述代码可以看出来接下来就是调用了 CreateCallSite方法。

private ServiceCallSite? CreateCallSite(Type serviceType, CallSiteChain callSiteChain)        
{            
    ...
    // We need to lock the resolution process for a single service type at a time:          
    // Consider the following:            
    // C -> D -> A            
    // E -> D -> A  
    // 如上例,当 C,E 同时解析时,为了确保只会生成一个 D 对应的 CallSite,所以需要锁住          
    // 尝试从 _callSiteLocks 获取一个值,没有则添加,返回获取到或者添加的值
    var callsiteLock = _callSiteLocks.GetOrAdd(serviceType, static _ => new object());
    lock (callsiteLock)            
    {            
        //检查serviceType的循环依赖    
        callSiteChain.CheckCircularDependency(serviceType);  
        //根据 serviceType 创建 callSite        
        ServiceCallSite? callSite = 
            //如果 TryCreateExact 为空,那么就说明可能 是泛型,并且注册的时候使用的是 List<> 这种开放泛型,而不是List<int> 这种具体泛型
            //所以需要尝试一下 TryCreateOpenGeneric
            TryCreateExact(serviceType, callSiteChain) 
            ??TryCreateOpenGeneric(serviceType, callSiteChain) 
            ??TryCreateEnumerable(serviceType, callSiteChain);                 
        return callSite;            
    }        
}

这里根据 serviceType 的类型走了不同的分支

  • TryCreateExact (精确匹配,serviceType是一个具体类型)
private ServiceCallSite? TryCreateExact(Type serviceType, CallSiteChain callSiteChain)        
{            
    //判断 服务类型是否在 _descriptorLookup(创建CallSiteFactory时由 ServiceDescriptor 集合 初始化) 里,就是看之前有没有注入过
    if (_descriptorLookup.TryGetValue(serviceType, out ServiceDescriptorCacheItem descriptor)){   
        //因为一个 serviceType 可能会注册多个 ServiceDescriptor 此处取最新的(详情见上populate方法)
        return TryCreateExact(descriptor.Last, serviceType, callSiteChain, DefaultSlot);            
    }             
    return null;        
}

private ServiceCallSite? TryCreateExact(
    ServiceDescriptor descriptor, 
    Type serviceType, 
    CallSiteChain callSiteChain,
    int slot
)        
{        
    // 精确匹配需要获取的类型(serviceType)和注入的类型(descriptor.serviceType)    
    if (serviceType == descriptor.ServiceType)            
    {      
        //用serviceType 和 slot 构建一个 ServiceCacheKey          
        ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, slot);      
        //如果有缓存,直接返回   这里并不能解决循环依赖问题,
        //举例  A依赖B B依赖A  此时 A 还没有注入进去,所以这里不会获得A,依旧会继续构建,导致循环依赖       
        if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))                
        {                    
            return serviceCallSite;                
        }                 
        ServiceCallSite callSite;     
        //使用  lifetime serviceType  slot 构建一个 ResultCache            
        var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);  
        //如果有 实现实例         
        if (descriptor.ImplementationInstance != null)                
        {                    
            //构建一个  ConstantCallSite 实例就是单例模式,不需要lifetime了
            callSite = new ConstantCallSite(descriptor.ServiceType, descriptor.ImplementationInstance);                
        }     
        // 如果有 实现工厂           
        else if (descriptor.ImplementationFactory != null)                
        {        
            //构建一个 FactoryCallSite         
            callSite = new FactoryCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationFactory);                
        }   
        // 如果有实现类型             
        else if (descriptor.ImplementationType != null)                
        {               
            //构建一个 ConstructorCallSite     
            callSite = CreateConstructorCallSite(lifetime, descriptor.ServiceType, descriptor.ImplementationType, callSiteChain);
        }               
        else                
        {                    
            throw new InvalidOperationException(SR.InvalidServiceDescriptor);                
        }      
        // 保存 callSiteKey 和 callSite 到 _callSiteCache 里                   
        return _callSiteCache[callSiteKey] = callSite;            
    }             
    return null;        
}

这里根据注入时指定的实例生成方法生成对应的CallSite, 其中 ConstantCallSite 只能用于Singleton类型的生命周期,剩下的 FactoryCallSiteCreateConstructorCallSite 用于所有生命周期。我们主要分析一下 CreateConstructorCallSite 方法。

private ServiceCallSite CreateConstructorCallSite(
            ResultCache lifetime,// serviceType 生命周期相关
            Type serviceType,// 服务类型
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType,// 实现类型
            CallSiteChain callSiteChain)// 依赖链
{
    try
    {
        // callSiteChain 里添加当前 服务类型 和实现类型
        callSiteChain.Add(serviceType, implementationType);
        // 获取实现类型的 public 构造器
        ConstructorInfo[] constructors = implementationType.GetConstructors();
        // 构造这个类新依赖的类型
        ServiceCallSite[]? parameterCallSites = null;
        // 没有 public 构造器,抛出异常
        if (constructors.Length == 0)
        {
            throw new InvalidOperationException(SR.Format(SR.NoConstructorMatch, implementationType));
        }
        //如果只有一个构造器
        else if (constructors.Length == 1)
        {
            ConstructorInfo constructor = constructors[0];
            ParameterInfo[] parameters = constructor.GetParameters();
            //无参构造器
            if (parameters.Length == 0)
            {
                return new ConstructorCallSite(lifetime, serviceType, constructor);
            }
            //获取参数 CallSite   不允许有没有的CallSite 因为这是唯一一个构造器                      
            parameterCallSites = CreateArgumentCallSites(
                implementationType,
                callSiteChain,
                parameters,
                throwIfCallSiteNotFound: true)!;

            return new ConstructorCallSite(lifetime, serviceType, constructor, parameterCallSites);
        }
        //按照参数数量进行 构造器倒序排序,跟后面挑选bestConstructor有关
        Array.Sort(constructors,
            (a, b) => b.GetParameters().Length.CompareTo(a.GetParameters().Length));

        ConstructorInfo? bestConstructor = null;
        HashSet<Type>? bestConstructorParameterTypes = null;
        for (int i = 0; i < constructors.Length; i++)
        {
            ParameterInfo[] parameters = constructors[i].GetParameters();
            //允许有没有的CallSite 因为这不是唯一一个构造器  
            ServiceCallSite[]? currentParameterCallSites = CreateArgumentCallSites(
                implementationType,
                callSiteChain,
                parameters,
                throwIfCallSiteNotFound: false);

            if (currentParameterCallSites != null)
            { 
                //如果当前没有bestConstructor,这个就是bestConstructor啦
                if (bestConstructor == null)
                {
                    bestConstructor = constructors[i];
                    parameterCallSites = currentParameterCallSites;
                }
                //bestConsturtor 不为null, 之前已经找到了一个可以使用的构造器
                else
                {
                    // Since we're visiting constructors in decreasing order of number of parameters,
                    // we'll only see ambiguities or supersets once we've seen a 'bestConstructor'.
                    //如果bestConstructorParameterTypes为null,使用 bestConstructor 的参数填充 bestConstructorParameterTypes
                    if (bestConstructorParameterTypes == null)
                    {
                        bestConstructorParameterTypes = new HashSet<Type>();
                        foreach (ParameterInfo p in bestConstructor.GetParameters())
                        {
                            bestConstructorParameterTypes.Add(p.ParameterType);
                        }
                    }
                    //如果 bestConstruct 的参数不能完全包含其他构造器的参数,抛出异常
                    //因为是按照构造器参数数量降序排的,所以如果多参数构造器不能包含少参数构造器,就有问题
                    foreach (ParameterInfo p in parameters)
                    {
                        if (!bestConstructorParameterTypes.Contains(p.ParameterType))
                        {
                            // Ambiguous match exception
                            throw new InvalidOperationException(string.Join(
                                Environment.NewLine,
                                SR.Format(SR.AmbiguousConstructorException, implementationType),
                                bestConstructor,
                                constructors[i]));
                        }
                    }
                }
            }
        }
        //没有 构造器,抛异常
        if (bestConstructor == null)
        {
            throw new InvalidOperationException(
                SR.Format(SR.UnableToActivateTypeException, implementationType));
        }
        else
        {
            Debug.Assert(parameterCallSites != null);
            return new ConstructorCallSite(lifetime, serviceType, bestConstructor, parameterCallSites);
        }
    }
    finally
    {
        //从 链中移除当前 类型
        //可能原因:
        //例 : A 依赖 B、D ,B 依赖 C, D 依赖于B
        //A 构建 D,D 构建 B ,B 构建C 如果不移除, 当 A 构建 B 的时候,会发现 CallSiteSite 里有 B 会循环依赖
        //但理论上不会,因为后续构建B的时候是直接从_callSiteCache里取的(CreateArgumentCallSites的GetCallSite),应该不用重新构建 
        // 构建完的依赖从链中移除
        callSiteChain.Remove(serviceType);
    }
}

从上面的代码可以看出服务之间的依赖关系是由CreateArgumentCallSites解决的

private ServiceCallSite[]? CreateArgumentCallSites(
            Type implementationType,    // 实现类型 
            CallSiteChain callSiteChain,//  依赖链
            ParameterInfo[] parameters, // 构造器参数
            bool throwIfCallSiteNotFound) // 找不到是否抛出异常
{
    var parameterCallSites = new ServiceCallSite[parameters.Length];
    for (int index = 0; index < parameters.Length; index++)
    {
        Type parameterType = parameters[index].ParameterType;
        //获取 类型 对应的 CallSite 这里是递归调用 
        ServiceCallSite? callSite = GetCallSite(parameterType, callSiteChain);
        //如果没有 且 该参数类型有默认值 构建 一个 ConstantCallSite
        if (callSite == null && ParameterDefaultValue.TryGetDefaultValue(parameters[index], out object? defaultValue))
        {
            callSite = new ConstantCallSite(parameterType, defaultValue);
        }
        //解析不出来参数的CallSite 抛出异常
        if (callSite == null)
        {
            if (throwIfCallSiteNotFound)
            {
                throw new InvalidOperationException(SR.Format(SR.CannotResolveService,
                    parameterType,
                    implementationType));
            }

            return null;
        }
        //所有参数对应的 callSite (数组下标关联)

        parameterCallSites[index] = callSite;
    }

    return parameterCallSites;
}

  • TryCreateOpenGeneric
//泛型的CallSite注册
private ServiceCallSite? TryCreateOpenGeneric(Type serviceType, CallSiteChain callSiteChain)
{
    // List<>    IsConstructedGenericType false
    // List<int> IsConstructedGenericType true
    // 所以传入的 serviceType 如果是 泛型,就必须是可以构建的 泛型List<int>, 而不是 List<>
    // 所以第二个 条件是判断有没有  对应 构建泛型的 开放泛型 (List<int> 对应 List<>)
    if (serviceType.IsConstructedGenericType
        && _descriptorLookup.TryGetValue(serviceType.GetGenericTypeDefinition(), out ServiceDescriptorCacheItem descriptor))
    {
        return TryCreateOpenGeneric(descriptor.Last, serviceType, callSiteChain, DefaultSlot, true);
    }
    return null;
}

private ServiceCallSite? TryCreateOpenGeneric(
        ServiceDescriptor descriptor, 
        Type serviceType, 
        CallSiteChain callSiteChain, 
        int slot, 
        bool throwOnConstraintViolation)
{
    if (serviceType.IsConstructedGenericType &&
        serviceType.GetGenericTypeDefinition() == descriptor.ServiceType)
    {
        ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, slot);
        // 先看看缓存里有没有
        if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))
        {
            return serviceCallSite;
        }
        Debug.Assert(descriptor.ImplementationType != null, "descriptor.ImplementationType != null");
        var lifetime = new ResultCache(descriptor.Lifetime, serviceType, slot);
        Type closedType;
        try
        {
            //根据 serviceType 的 泛型参数 构建一个确定的类型 List<> 根据 int 构建 List<int>,这个就是 实现             
            closedType = descriptor.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments);
        }
        catch (ArgumentException)
        {
            if (throwOnConstraintViolation)
            {
                throw;
            }
            return null;
        }
        //调用CreateConstructorCallSite 方法,这就跟 TryCreateExtra 一致了
        return _callSiteCache[callSiteKey] = CreateConstructorCallSite(lifetime, serviceType, closedType, callSiteChain);
    }

    return null;
}
  • TryCreateEnumerable
    这个方法的主要用途是注入了多个接口的不同实现,可以通过 GetService<IEnumberable<T>>() 以 IEnumberable 形式获取所有实现
private ServiceCallSite? TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain)
{
    ServiceCacheKey callSiteKey = new ServiceCacheKey(serviceType, DefaultSlot);
    //缓存命中,直接返回
    if (_callSiteCache.TryGetValue(callSiteKey, out ServiceCallSite? serviceCallSite))
    {
        return serviceCallSite;
    }

    try
    {
        callSiteChain.Add(serviceType);
        // 是否是 ConstructedGenericType,并且 GetGenericTypeDefinition 是 IEnumerable  (IEnumerable<int> 这种)
        if (serviceType.IsConstructedGenericType &&
            serviceType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        {
            //获取泛型的具体类型,<> 里面的
            Type itemType = serviceType.GenericTypeArguments[0];
            CallSiteResultCacheLocation cacheLocation = CallSiteResultCacheLocation.Root;

            var callSites = new List<ServiceCallSite>();

            // If item type is not generic we can safely use descriptor cache
            //如果 <> 里不是泛型 并且 _descriptorLookup 里有这种类型(populate 的时候遇到过)
            if (!itemType.IsConstructedGenericType &&
                _descriptorLookup.TryGetValue(itemType, out ServiceDescriptorCacheItem descriptors))
            {
                //遍历 descriptors 里所有 descriptor 
                for (int i = 0; i < descriptors.Count; i++)
                {
                    ServiceDescriptor descriptor = descriptors[i];
                    // Last service should get slot 
                    int slot = descriptors.Count - i - 1;
                    // There may not be any open generics here
                    // 生成每个 descriptor 对应的 callSite
                    ServiceCallSite? callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot);
                    Debug.Assert(callSite != null);
                    // GetCommonCacheLocation = Math.max(a,b) 根据 新的callSite 调整location
                    cacheLocation = GetCommonCacheLocation(cacheLocation, callSite.Cache.Location);
                    callSites.Add(callSite);
                }
            }
            else
            {
                int slot = 0;
                // We are going in reverse so the last service in descriptor list gets slot 0
                //如果没有,遍历 populate 时添加进来的所有 _descriptor
                for (int i = _descriptors.Length - 1; i >= 0; i--)
                {
                    ServiceDescriptor descriptor = _descriptors[i];
                    // 尝试根据 descriptor 和 itemType 生成 callSite , 但是不满足条件的不会生成                    
                    ServiceCallSite? callSite = TryCreateExact(descriptor, itemType, callSiteChain, slot) ??
                                   TryCreateOpenGeneric(descriptor, itemType, callSiteChain, slot, false);

                    if (callSite != null)
                    {
                        // 当前 descriptr 可以生成 itemType slot++ 越后添加的slot越小
                        slot++;
                        // 调整 cacheLocation
                        cacheLocation = GetCommonCacheLocation(cacheLocation, callSite.Cache.Location);
                        callSites.Add(callSite);
                    }
                }
                //callSites 翻转 最新的跑到最前面
                callSites.Reverse();
            }

            ResultCache resultCache = ResultCache.None;
            //生命周期为 scoped 或者 Singleton 时 ,搞一个resultCache
            if (cacheLocation == CallSiteResultCacheLocation.Scope || cacheLocation == CallSiteResultCacheLocation.Root)
            {
                resultCache = new ResultCache(cacheLocation, callSiteKey);
            }
            // 放入缓存
            return _callSiteCache[callSiteKey] = new IEnumerableCallSite(resultCache, itemType, callSites.ToArray());
        }

        return null;
    }
    finally
    {
        callSiteChain.Remove(serviceType);
    }
}

以上就是本节的所有内容了,如有错误,请多指教。
下一节我打算看一看在获得 callSite 之后,是如何根据 callSite 来生成具体实例的相关源码,主要是在 CallSiteRuntimeResolver 类中,感兴趣的朋友可以先看一看。

补充

CallSite的实现类

  1. ServiceCallSite
internal abstract class ServiceCallSite
 {
     // 构建方式  实例,工厂,构造器
     public abstract CallSiteKind Kind { get; }
     //生命周期 和 cacheKey
     public ResultCache Cache { get; }
     //Singleton存放点
      public object? Value { get; set; }
 }
  1. ConstantCallSite
internal class ConstantCallSite : ServiceCallSite
    {        
        internal object DefaultValue { get; }

        public ConstantCallSite(Type serviceType, object defaultValue) : base(ResultCache.None)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException("serviceType");
            }
            this._serviceType = serviceType;
            if (defaultValue != null && !serviceType.IsInstanceOfType(defaultValue))
            {
                throw new ArgumentException(Resources.FormatConstantCantBeConvertedToServiceType(defaultValue.GetType(), serviceType));
            }
            this.DefaultValue = defaultValue;
        }

        public override Type ImplementationType
        {
            get
            {
                object defaultValue = this.DefaultValue;
                return ((defaultValue != null) ? defaultValue.GetType() : null) ?? this._serviceType;
            }
        }

        
        public override CallSiteKind Kind { get; } = 2;

        private readonly Type _serviceType;
    }
}
  1. FactoryCallSite
internal class FactoryCallSite : ServiceCallSite
    {
        public Func<IServiceProvider, object> Factory { get; }
        public FactoryCallSite(ResultCache cache, Type serviceType, Func<IServiceProvider, object> factory) : base(cache)
        {
            this.Factory = factory;
            this.ServiceType = serviceType;
        }

        public override Type ServiceType { get; }
        public override Type ImplementationType
        {
            get
            {
                return null;
            }
        }

        public override CallSiteKind Kind { get; }
    }
  1. IEnumerableCallSite
internal class IEnumerableCallSite : ServiceCallSite
    {
        internal Type ItemType { get; }

        internal ServiceCallSite[] ServiceCallSites { get; }

        public IEnumerableCallSite(ResultCache cache, Type itemType, ServiceCallSite[] serviceCallSites) : base(cache)
        {
            this.ItemType = itemType;
            this.ServiceCallSites = serviceCallSites;
        }
        public override Type ImplementationType
        {
            get
            {
                return this.ItemType.MakeArrayType();
            }
        }

        public override CallSiteKind Kind { get; } = 3;
    }
}
  1. ConstructorCallSite
internal class ConstructorCallSite : ServiceCallSite
    {
        internal ConstructorInfo ConstructorInfo { get; }
        internal ServiceCallSite[] ParameterCallSites { get; }

        public ConstructorCallSite(ResultCache cache, Type serviceType, ConstructorInfo constructorInfo) : this(cache, serviceType, constructorInfo, Array.Empty<ServiceCallSite>())
        {
        }

        public ConstructorCallSite(ResultCache cache, Type serviceType, ConstructorInfo constructorInfo, ServiceCallSite[] parameterCallSites) : base(cache)
        {
            ServiceType = serviceType;
            ConstructorInfo = constructorInfo;
            ParameterCallSites = parameterCallSites;
        }

        public override Type ServiceType { get; }

        public override Type ImplementationType => ConstructorInfo.DeclaringType;
        public override CallSiteKind Kind { get; } = CallSiteKind.Constructor;
    }

PS

关于CallSiteChain的作用,目前我也不是特别清楚,比如为什么要需要在处理callSiteChain 时为什么要在 finally 块里把 type 给 remove 掉。希望知道的大佬可以指导一下。

标签:core,serviceType,CallSite,--,callSiteChain,descriptor,ServiceCallSite,callSite,n
From: https://www.cnblogs.com/ccwzl/p/17512513.html

相关文章

  • CF Gym 102994 Travel Dream
    题意求一张带权无向图中最大的\(k\)元简单环,无解输出impossible。\(1\len,m\le300,k\le10\)。注意\(k\)的范围题解\(k\)很小,存在简单办法对小环小链进行预处理,考虑折半。首先考虑怎么求长度小于等于4的链。长度为\(1,2\)的链可以直接枚举,长度为\(3\)的链......
  • SpringCloud学习-3
    SpringCloud学习第三天使用zuul实现路由代理在父工程下新家gateway(网关)子项目,使用zuul后,以后路由的访问都要经过zuul,想要访问localhost:8081/user/1的服务,需要如下配置<dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spri......
  • 使用exceljs和file-saver导出带图片的excel表格
    参考https://www.swvq.com/article/detail/487https://github.com/exceljs/exceljs/blob/master/README_zh.md#图片importExcelJSfrom'exceljs'importfileSaverfrom'file-saver'letworkbook=nullletworksheet=null//图片转base64constco......
  • Oracle数据库用户密码过期的解决方法
    问题现象:今天在更改数据库数据的时候,程序报错了,如下:ORA-28001:thepasswordhasexpired问题分析:很显然,报错原因就是:密码已过期!所以现在需要做的事情只有两件:1.修改密码的过期时间2.修改/重置密码这里分析一下为什么要这样做:1.修改密码的过期时间:这是因为OracleDatab......
  • Python教程(1)——python环境的下载与安装
    下面是下载并安装Python解释器的具体步骤,非常详细,保姆级别的教程,初学者一步一步的按照操作。下载python运行环境访问官方网站在浏览器中打开Python的官方网站,网址为https://www.python.org不要去其他乱七八糟的地方下啊。当然很多时候可能受限于网速的因素,建议挂个梯子。选择......
  • 7、Kibana图形显示安装配置
    Kibana图形显示安装并配置Kibana可以通过包或者二进制的方式进行安装,可以安装在独立服务器,或者也可以和elasticsearch的主机安装在一起注意:Kibana的版本要和Elasticsearch相同的版本,否则可能会出错下载站点:https://mirrors.tuna.tsinghua.edu.cn/elasticstack/7.x下载:[......
  • 这份Github标星30K的神仙面试笔记 ,包含了所有Android中高级大厂知识面试题!!!
    作为一个Android程序员,你平时总是陷在业务开发里,每天噼里啪啦忙敲着代码,上到系统开发,下到Bug修改,你感觉自己无所不能。然而偶尔的一次聚会,你听说和自己一起出道的同学早已经年薪50万,而自己却囊中羞涩。于是你也想看看新机会,找个新平台,好好发展。但是面试的时候,当那个笑眯眯的......
  • Redis设置为开机自启
    1Redis安装1.1上传redis-3.2.1.tar.gz到节点A服务器的/usr/local/目录下1.2解压redis压缩包[root@hadoop2local]#tar-xvfredis-3.2.1.tar.gz1.3修改包名[root@hadoop2local]#mvredis-3.2.1redis[root@hadoop2local]#cdredis/1.4执行编译[root@hadoop2redis]#makePREF......
  • nginx设置为开机自启
    1NGINX安装1.1使用SCP工具上传nginx-1.16.1.tar.gz安装包到/usr/local/目录1.2安装 (该步骤在有网络的条件下可以执行,离线安装可以配置yum源后安装或下载安装包上传安装)1.3安装依赖[root@hadoop4~]#yum -y installgccgcc-c++pcrepcre-develzlibzlib-developensslo......
  • F5Cloud第一期如何在AWS上部署F5 VE​
    F5Cloud第一期如何在AWS上部署F5VEF5VE初始化:配置VLAN,Selfip,LOCALDNS,NTP,创建设备组,创建信任关系peer,组建双机,同步配置......