2024年8月

为什么要用ConcurrentHashMap?

ConcurrentHashMap
是JUC包下的一个线程安全的
HashMap
类,我们都知道多线程的场景下要用
ConcurrentHashMap
来代替
HashMap
使用,有没有想过为什么不能用
HashMap
,为什么能用
ConcurrentHashMap
呢?下面我通过走源码的方式,带大家看一看其中的一些细节!

HashMap

map
数组的一种,
JDK1.8
中的
HashMap
以数组+链表/红黑树的形式存在,这里不做过多解释

当我们在执行多线程任务时,若是操作的资源为
HashMap
类型时就可能会导致程序出现并发异常,如图

点到
nextNode
这个方法中去看源码,原因很明显,如图

那么,if判断中的两个变量是干什么的呢,为什么这俩变量不相同就要抛出一个异常?

首先,我们看一下我们从创建
Hashmap
变量到抛出异常这段时间内都做了些什么事情

整个流程的代码如下

public static void main(String[] args) {
        HashMap<String,String> map = new HashMap<>();
        for (int i = 0;i < 20; i++){
            new Thread(()-> {
                map.put(Thread.currentThread().getName(),new Date().toString());
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }

我们点进
HashMap
的方法中的构造方法能看到,这里只给了
loadFactor
这个变量一个初始值,也就是我们熟知的加载因子0.75

我们再去看一下put方法都干了些什么

put
->
putVal

点开后发现,这不就是我们上面看到的if判断中的第一个变量吗?我们点到这个变量的定义的地方可以看到,这是一个int类型的且并未赋值

我们通过这点能得到一个初步的结论,当我们每执行一次put方法的时候,这个值就会进行一次+1的操作

大家可以看到下面还有一个叫
afterNodeInsertion
的方法,我们点开后发现是一个空方法,再看上边的注释,大概意思是给
LinkedHashMap
提供的回调的方法,
LinkedHashMap

HashMap
的一个子类,我们不必理会

然后我们点开
println
方法

println->valueOf->toString

找到我们
HashMap
重写的
toString
方法,我们发现在
HashMap
中并没有找到被重写的
toString
方法,那么我们直接去搜一下他的父类

搜查过程中,如果用Ctrl+f搜索整个类,会看到有一条toString查询记录,此处为HashMap的内部类Node的方法,并非HashMap的

发现
AbstractMap
类中果然重写了
toString
方法

此处我们终于看到了迭代器,我们再返回
HashMap
类去看一下
entrySet
方法都干了啥

因为我们走过了put方法,所以此时
HashMap
中的
entrySet
是有内容的,可以看到这里是直接把
entrySet
返回了

entrySet
实际上就是一个Set集合,将我们
HashMap
中的存储单元Entry放到了里面(HashMap.put)

然后我们通过泛型找到实现集合接口中迭代器方法的位置,又是一个
HashMap
的内部类,
EntrySet

发现又一个没见过的类,我们顺势点开

点开
nextNode
方法后发现,哎,这不是报错的地儿吗,到现在我们
modCount
也有了,报错的节点也找到了,还差一个
expectedModCount
没找到呢,于是在一通检查后,突然想到,会不会在某个构造方法中,回去看了一下
EntryIterator
也没有自己写构造方法呀,于是打开了他的父类,一下子豁然开朗,直接看图吧

到这,这俩哥们总算找齐了,那么这里问题又来了,给
expectedModCount
赋的就是
modCount
的值呀,这俩哥们咋还能不一样呢

答案也浮出水面了,单线程下肯定是不会出问题,可是我们是多线程操作呀,如果A线程刚给完值,B线程跑
putVal
方法去了,跑完
modCount
+1了,然后A线程紧接着走到了
nextNode
方法中,一对比就不对了,然后就抛异常了

所以说
HashMap
在并发场景下还是很容易出问题的

ConcurrentHashMap

HashMap
在多线程的场景下不能用了,不安全呀,于是适配多线程的线程安全的HashMap:
ConcurrentHashMap
应运而生

然后我们把原代码中的
HashMap
换成
ConcurrentHashMap

public static void main(String[] args) {
        ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0;i < 20; i++){
            new Thread(()-> {
                map.put(Thread.currentThread().getName(),new Date().toString());
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }

执行了N遍,确实不报错了,每次循环都能打印成功,如此神奇的东西,让我们看看为什么他不报错了

首先点开这个类进去看看

发现这个类也继承了
AbstractMap
类,说明与
HashMap
是师出同门的呀,然后我们再去找上边那俩兄弟的时候发现,不见了,然后去找迭代器和next方法的时候发现,完全换了一套,所以自然不会像这样报错,那么他是怎么处理多线程操作的场景的呢

ConcurrentHashMap
是通过
synchronized
+
CAS
算法来实现线程安全的

如果去看源码的话,你会发现
ConcurrentHashMap
里面有很多
Unsafe.compareAndSwap
+数据类型的写法,这种写法就是利用CAS算法实现无锁化的修改值操作,此算法可以很大程度的减少加锁过程中造成的性能损耗

这个算法大概就是不断地去用内存中的变量值与代码预期的变量值是否相同,如果是一样的就会修改成功,如果不一样就会拒绝执行修改,用这种方式去判断当前线程中是否是最新的值,若不是则可能会覆盖其他线程的结果

正因此算法的判断方式,如果某个线程将值修改然后又改回去了,该算法仍然会认为这是最新值没有被改过

而通过观察源码发现,在操作
Node
相关对象时,会用
synchronized
将对象锁住

官网提供的按需引入方法为全量按需引入,在打包分离中,仍旧存在使用不到的图表被打包进去。

例如:组件A使用了折线图、柱状图,组件B只用到了折线图,但是打包组件B的时候,柱状图也会被打包进去。

本文提供一种动态按需引入的思路,使得只用到折线图的组件B,打包的时候只打包折线图,不会将组件A用到的柱状图也打包进去。

目录结构:

Index.ts 如下:

//引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as ECHARTS from "echarts/core";
import type { ComposeOption } from
"echarts/core";
import CORE, { CORE_ECOption } from
"./Core";
import { LineChart_ECOption } from
"./LineChart";
import { BarChart_ECOption } from
"./BarChart";
import { RadarChart_ECOption } from
"./RadarChart";//通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型 export type ECOption = ComposeOption<CORE_ECOption | LineChart_ECOption | BarChart_ECOption | RadarChart_ECOption>;

class Echarts {
public echarts: any;
constructor(type: string[], callback: any) {
//注册必须的组件 ECHARTS.use([...CORE]);

const charts: any
=[];

type
!.map((item: any) =>{
const res: any
= import(/*webpackChunkName: "echarts"*/ `./${item}`); charts.push(res);
});

Promise.all(charts).then((res: any)
=>{
res.map((item: any)
=>{
ECHARTS.use(item.
default);
});
callback(ECHARTS);
});
}
}

export
default Echarts;

Core.ts 如下:

//引入标题,提示框,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent,
LegendComponent
} from
"echarts/components";//标签自动布局、全局过渡动画等特性 import { LabelLayout, UniversalTransition } from "echarts/features";//组件类型的定义后缀都为 ComponentOption import type {
TitleComponentOption,
TooltipComponentOption,
GridComponentOption,
DatasetComponentOption,
ToolboxComponentOption,
LegendComponentOption
} from
"echarts/components";//引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步 import { CanvasRenderer } from "echarts/renderers";//通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型 export type CORE_ECOption = |TitleComponentOption|TooltipComponentOption|GridComponentOption|DatasetComponentOption|ToolboxComponentOption|LegendComponentOption;

const CORE
=[
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
ToolboxComponent,
LegendComponent,
LabelLayout,
UniversalTransition,
CanvasRenderer
];

export
default CORE;

 

BarChart.ts 如下:

import { BarChart } from "echarts/charts";//系列类型的定义后缀都为 SeriesOption
import type { BarSeriesOption } from "echarts/charts";

export type BarChart_ECOption
=BarSeriesOption;

export
default BarChart;

LineChart.ts 如下:

import { LineChart } from "echarts/charts";//系列类型的定义后缀都为 SeriesOption
import type { LineSeriesOption } from "echarts/charts";//通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type LineChart_ECOption =LineSeriesOption;

export
default LineChart;

 

RadarChart.ts 如下:

import { RadarChart } from "echarts/charts";//系列类型的定义后缀都为 SeriesOption
import type { RadarSeriesOption } from "echarts/charts";//组件类型的定义后缀都为 ComponentOption
import type { RadarComponentOption } from "echarts/components";//通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type RadarChart_ECOption = RadarComponentOption |RadarSeriesOption;

export
default RadarChart;

 

如果项目中还用到其他图表类型,则按照具体需要引入即可。

可以根据具体业务进行调用封装,下面是封装的示例 util.ts:

import Echarts from "/echarts/Index";

export const renderEcharts
= async (type: string[], dom: string, option: any = {}, callback?: any) =>{if (!dom) return;new Echarts(type, (echart: any) =>{
const _dom: any
=document.querySelector(dom);

const echarts_instance
=echart.init(_dom);

echarts_instance.setOption(option);

window.addEventListener(
"resize", function() {
echarts_instance.resize();
echarts_instance.clear();
echarts_instance.setOption(option);
});

_dom
?.addEventListener("touchend", () =>{
setTimeout(()
=>{
echarts_instance.dispatchAction({
type:
"hideTip"});
echarts_instance.dispatchAction({
type:
"updateAxisPointer"});
},
1000);
});

callback
&&callback(echarts_instance);
});
};

具体调用:

let echarts_instance = null;
const options
= {//具体的option } renderEcharts(["LineChart", "BarChart"], "#echarts_box",options, (instance) =>{
echarts_instance
=instance;
});

昨天晚上18点左右,我们在
https://cnblogs.vip/
上线了终身会员——终身PLUS会员与终身VIP会员。

上线后,我们在会员微信群里发了群公告,企业微信发了朋友圈,在「.NET社群」微信群中发了一条消息。

终身会员已上线,限256人
https://cnblogs.vip/

到昨天晚上23:30,一共有12位园友成为终身会员,做二十年梦也没想到一个晚上竟然会有这么多终身会员,这种信任与支持的力量超出我们的想象。

惊喜的同时,我们突然意识到一个“严重问题”,我们被逼上了“绝路”,既然有了终身会员,那我们就绝没有任何退路,只能走坚持到底、永不放弃的前进之路,才能对得起终身会员的终身支持。

非常感谢这13位园友的鼎力支持!(就在我们准备发这篇博文时,又有一位园友成为终身PLUS会员。)

终身PLUS会员

1)
顾星河

2)
Jnetart

3)
thaoeu

终身VIP会员

1)
GiveCookies

2)
Lucus_Taylor

3)
xiofee

4)
dellbeat

5)
LilaMorgen

6)
stonewu666

  • 园龄:5年8个月
  • 园号:1550401
  • 会员号:5760

7)
雁闪雷冥

  • 园龄:11个月
  • 园号:3280083
  • 会员号:1166

8)
JeffWong

9)
MicNZh

  • 园龄:5年9个月
  • 园号:1539850
  • 会员号:1035

10)
hank_gao

VMware 前不久发布了 VMware Cloud Foundation 5.2 版本,并带来了许多功能的升级,比如支持 vSAN Max 分解存储,管理工作负载域支持 vSAN ESA 延伸集群,通过 VCF Import 工具将现有环境中的 vSphere/vSAN 集群直接转换成管理域或者导入为 VI 工作负载域等,这应该是 VMware by Broadcom 首次针对各项产品的调整以及各个部门的整合后的一次重量级更新,有关更多功能介绍和内容消息请查看 VMware Cloud Foundation 5.2
发行说明
以及
发布介绍
。VMware Cloud Foundation 5.2 版本的 BOM 物料清单如下图所示,集合了 VMware 所发布的最新主要产品,如 vSphere 8 U3、vSAN 8 U3 以及 NSX 4.2 等。

VMware Cloud Foundation(简称 VCF)支持标准和整合部署架构,之前(“
VMware Cloud Foundation Part 05:部署 SDDC 管理域。
”和“
VMware Cloud Foundation Part 06:部署 VI 工作负载域。
”)分享了标准架构的部署过程,现在正好借 VCF 5.2 的发布,让我们来部署一套整合架构的 SDDC。其实,我感觉标准架构和整合架构的部署过程并没有太大区别,关于这两种架构的功能和用途,完全也是你作为用户对于使用理念上的界定。在部署配置后,整合架构的 VCF 会将管理相关组件和工作负载虚拟机放在同一个工作负载域里并通过资源池的方式进行隔离,而在标准架构中,VI 域专门用来承载工作负载虚拟机。其他的并没有什么太大分别,而且你可以随时将整合架构调整为标准架构,只需要新部署一个 VI 工作负载域,然后将管理域上面的工作负载迁移到这个 VI 域皆可。

一、环境准备

不管是任何一种部署架构的 VCF,前期的环境准备都是一样的。安装工作负载域中的 ESXi 主机并配置 ESXi 主机的相关基础服务,规划 VCF 解决方案的网络拓扑和存储架构并准备相关部署参数工作簿,最后安装自动化部署工具 VMware Cloud Builder 后,即可开始你的 VCF 构建过程。

1)DNS/NTP 服务器

DNS 和 NTP 服务器对于 VCF 环境来说非常重要,请根据实际环境的情况,规划好管理相关组件地址信息后,在 DNS 服务器上配置好正反向域名解析。如果这些基础服务有哪个地方不对,可能会导致部署失败以及环境的不稳定。

组件名称 主机名 IP地址 子网掩码 网关 DNS/NTP 服务器
ESXi vcf-mgmt01-esxi01.mulab.local 192.168.32.61 255.255.255.0 192.168.32.254 192.168.32.3
vcf-mgmt01-esxi02.mulab.local 192.168.32.62 255.255.255.0 192.168.32.254 192.168.32.3
vcf-mgmt01-esxi03.mulab.local 192.168.32.63 255.255.255.0 192.168.32.254 192.168.32.3
vcf-mgmt01-esxi04.mulab.local 192.168.32.64 255.255.255.0 192.168.32.254 192.168.32.3
vCenter Server vcf-mgmt01-vcsa01.mulab.local 192.168.32.65 255.255.255.0 192.168.32.254 192.168.32.3
NSX Manager vcf-mgmt01-nsx01.mulab.local(VIP) 192.168.32.66 255.255.255.0 192.168.32.254 192.168.32.3
vcf-mgmt01-nsx01a.mulab.local 192.168.32.67 255.255.255.0 192.168.32.254 192.168.32.3
SDDC Manager vcf-mgmt01-sddc01.mulab.local 192.168.32.70 255.255.255.0 192.168.32.254 192.168.32.3

2)ESXi 主机

根据 VCF 5.2 BOM 清单要求准备用于部署 VCF 管理域的 ESXi 主机。由于是嵌套虚拟化环境,如果是部署 vSAN ESA 存储架构,请根据实际环境创建
自定义 vSAN ESA HCL 文件
。更多内容和细节请查看:
VMware Cloud Foundation Part 04:准备 ESXi 主机。

3)JSON 配置文件

根据实际环境情况准备用于构建 VCF 管理域的 JSON 配置文件。License 许可证已经过处理,VCF 5.1.1 版本开始在未指定许可证的情况下以评估模式部署后支持 60 天试用期。请注意 SDDC Manager 用户的密码复杂度最低要求提高至 15 位字符。更多内容和细节请查看:
VMware Cloud Foundation Part 03:准备 Excel 参数表。

{
    "deployWithoutLicenseKeys": false,
  "skipEsxThumbprintValidation": true,
  "managementPoolName": "vcf-mgmt01-np01",
  "sddcManagerSpec": {
    "secondUserCredentials": {
      "username": "vcf",
      "password": "Vcf520@password"
    },
    "ipAddress": "192.168.32.70",
    "hostname": "vcf-mgmt01-sddc01",
    "rootUserCredentials": {
      "username": "root",
      "password": "Vcf520@password"
    },
    "localUserPassword": "Vcf520@password"
  },
  "sddcId": "vcf-mgmt01",
  "esxLicense": "00000-00000-00000-00000-00000",
  "taskName": "workflowconfig/workflowspec-ems.json",
  "ceipEnabled": false,
  "fipsEnabled": false,
  "ntpServers": ["192.168.32.3"],
  "dnsSpec": {
    "subdomain": "mulab.local",
    "domain": "mulab.local",
    "nameserver": "192.168.32.3"
  },
  "networkSpecs": [
    {
      "networkType": "MANAGEMENT",
      "subnet": "192.168.32.0/24",
      "gateway": "192.168.32.254",
      "vlanId": "0",
      "mtu": "1500",
      "portGroupKey": "vcf-mgmt01-vds01-pg-mgmt",
      "standbyUplinks":[],
      "activeUplinks":[
        "uplink1",
        "uplink2"
      ]
    },
    {
      "networkType": "VMOTION",
      "subnet": "192.168.40.0/24",
      "gateway": "192.168.40.254",
      "vlanId": "40",
      "mtu": "9000",
      "portGroupKey": "vcf-mgmt01-vds01-pg-vmotion",
      "includeIpAddressRanges": [{"endIpAddress": "192.168.40.4", "startIpAddress": "192.168.40.1"}],
      "standbyUplinks":[],
      "activeUplinks":[
        "uplink1",
        "uplink2"
      ]
    },
    {
      "networkType": "VSAN",
      "subnet": "192.168.41.0/24",
      "gateway": "192.168.41.254",
      "vlanId": "41",
      "mtu": "9000",
      "portGroupKey": "vcf-mgmt01-vds02-pg-vsan",
      "includeIpAddressRanges": [{"endIpAddress": "192.168.41.4", "startIpAddress": "192.168.41.1"}],
      "standbyUplinks":[],
      "activeUplinks":[
        "uplink1",
        "uplink2"
      ]
    },
    {
      "networkType": "VM_MANAGEMENT",
      "subnet": "192.168.32.0/24",
      "gateway": "192.168.32.254",
      "vlanId": "0",
      "mtu": "1500",
      "portGroupKey": "vcf-mgmt01-vds01-pg-vm-mgmt",
      "standbyUplinks":[],
      "activeUplinks":[
        "uplink1",
        "uplink2"
      ]
    }
  ],
  "nsxtSpec":
  {
    "nsxtManagerSize": "medium",
    "nsxtManagers": [
      {
          "hostname": "vcf-mgmt01-nsx01a",
          "ip": "192.168.32.67"
      }
    ],
    "rootNsxtManagerPassword": "Vcf520@password",
    "nsxtAdminPassword": "Vcf520@password",
    "nsxtAuditPassword": "Vcf520@password",
    "vip": "192.168.32.66",
    "vipFqdn": "vcf-mgmt01-nsx01",
    "nsxtLicense": "33333-33333-33333-33333-33333",
    "transportVlanId": 42,
	    "ipAddressPoolSpec": {
       "name": "vcf01-mgmt01-tep01",
       "description": "ESXi Host Overlay TEP IP Pool",
       "subnets":[
          {
             "ipAddressPoolRanges":[
                {
                   "start": "192.168.42.1",
                   "end": "192.168.42.8"
                }
             ],
             "cidr": "192.168.42.0/24",
             "gateway": "192.168.42.254"
          }
       ]
    }
  },
  "vsanSpec": {
      "licenseFile": "11111-11111-11111-11111-11111",
      "vsanDedup": "false",
      "esaConfig": {
        "enabled": true
      },
	  "hclFile": "/opt/vmware/bringup/tmp/all.json",
      "datastoreName": "vcf-mgmt01-vsan-esa-datastore01"
  },
  "dvsSpecs": [
    {
      "dvsName": "vcf-mgmt01-vds01",
      "vmnics": [
        "vmnic0",
        "vmnic1"
      ],
      "mtu": 9000,
      "networks":[
        "MANAGEMENT",
        "VMOTION",
        "VM_MANAGEMENT"
      ],
      "niocSpecs":[
        {
          "trafficType":"VSAN",
          "value":"HIGH"
        },
        {
          "trafficType":"VMOTION",
          "value":"LOW"
        },
        {
          "trafficType":"VDP",
          "value":"LOW"
        },
        {
          "trafficType":"VIRTUALMACHINE",
          "value":"HIGH"
        },
        {
          "trafficType":"MANAGEMENT",
          "value":"NORMAL"
        },
        {
          "trafficType":"NFS",
          "value":"LOW"
        },
        {
          "trafficType":"HBR",
          "value":"LOW"
        },
        {
          "trafficType":"FAULTTOLERANCE",
          "value":"LOW"
        },
        {
          "trafficType":"ISCSI",
          "value":"LOW"
        }
      ],
      "nsxtSwitchConfig": {
        "transportZones": [ 
		{
          "name": "vcf-mgmt01-tz-vlan01",
          "transportType": "VLAN"
        }
        ]
      }
    },
    {
      "dvsName": "vcf-mgmt01-vds02",
      "vmnics": [
        "vmnic2",
        "vmnic3"
      ],
      "mtu": 9000,
      "networks":[
        "VSAN"
      ],
      "nsxtSwitchConfig": {
        "transportZones": [ {
          "name": "vcf-mgmt01-tz-overlay01",
          "transportType": "OVERLAY"
        },
        {
          "name": "vcf-mgmt01-tz-vlan02",
          "transportType": "VLAN"
        }
        ]
      }
    }
  ],
  "clusterSpec":
  {
    "clusterName": "vcf-mgmt01-cluster01",
    "clusterEvcMode": "",
    "clusterImageEnabled": true,
    "vmFolders": {
      "MANAGEMENT": "vcf-mgmt01-fd-mgmt",
      "NETWORKING": "vcf-mgmt01-fd-nsx",
      "EDGENODES": "vcf-mgmt01-fd-edge"
    },
    "resourcePoolSpecs": [{
      "name": "vcf-mgmt01-cluster01-rp-sddc-mgmt",
      "type": "management",
      "cpuReservationPercentage": 0,
      "cpuLimit": -1,
      "cpuReservationExpandable": true,
      "cpuSharesLevel": "normal",
      "cpuSharesValue": 0,
      "memoryReservationMb": 0,
      "memoryLimit": -1,
      "memoryReservationExpandable": true,
      "memorySharesLevel": "normal",
      "memorySharesValue": 0
    }, {
      "name": "vcf-mgmt01-cluster01-rp-sddc-edge",
      "type": "network",
      "cpuReservationPercentage": 0,
      "cpuLimit": -1,
      "cpuReservationExpandable": true,
      "cpuSharesLevel": "normal",
      "cpuSharesValue": 0,
      "memoryReservationPercentage": 0,
      "memoryLimit": -1,
      "memoryReservationExpandable": true,
      "memorySharesLevel": "normal",
      "memorySharesValue": 0
    }, {
      "name": "vcf-mgmt01-cluster01-rp-user-edge",
      "type": "compute",
      "cpuReservationPercentage": 0,
      "cpuLimit": -1,
      "cpuReservationExpandable": true,
      "cpuSharesLevel": "normal",
      "cpuSharesValue": 0,
      "memoryReservationPercentage": 0,
      "memoryLimit": -1,
      "memoryReservationExpandable": true,
      "memorySharesLevel": "normal",
      "memorySharesValue": 0
    }, {
      "name": "vcf-mgmt01-cluster01-rp-user-vm",
      "type": "compute",
      "cpuReservationPercentage": 0,
      "cpuLimit": -1,
      "cpuReservationExpandable": true,
      "cpuSharesLevel": "normal",
      "cpuSharesValue": 0,
      "memoryReservationPercentage": 0,
      "memoryLimit": -1,
      "memoryReservationExpandable": true,
      "memorySharesLevel": "normal",
      "memorySharesValue": 0
    }]
  },
  "pscSpecs": [
    {
      "adminUserSsoPassword": "Vcf520@password",
      "pscSsoSpec": {
        "ssoDomain": "vsphere.local"
      }
    }
  ],
  "vcenterSpec": {
      "vcenterIp": "192.168.32.65",
      "vcenterHostname": "vcf-mgmt01-vcsa01",
      "licenseFile": "22222-22222-22222-22222-22222",
      "vmSize": "small",
      "storageSize": "",
      "rootVcenterPassword": "Vcf520@password"
  },
  "hostSpecs": [
    {
      "association": "vcf-mgmt01-datacenter01",
      "ipAddressPrivate": {
        "ipAddress": "192.168.32.61"
      },
      "hostname": "vcf-mgmt01-esxi01",
      "credentials": {
        "username": "root",
        "password": "Vcf5@password"
      },
      "vSwitch": "vSwitch0"
    },
    {
      "association": "vcf-mgmt01-datacenter01",
      "ipAddressPrivate": {
        "ipAddress": "192.168.32.62"
      },
      "hostname": "vcf-mgmt01-esxi02",
      "credentials": {
        "username": "root",
        "password": "Vcf5@password"
      },
      "vSwitch": "vSwitch0"
    },
    {
      "association": "vcf-mgmt01-datacenter01",
      "ipAddressPrivate": {
        "ipAddress": "192.168.32.63"
      },
      "hostname": "vcf-mgmt01-esxi03",
      "credentials": {
        "username": "root",
        "password": "Vcf5@password"
      },
      "vSwitch": "vSwitch0"
    },
    {
      "association": "vcf-mgmt01-datacenter01",
      "ipAddressPrivate": {
        "ipAddress": "192.168.32.64"
      },
      "hostname": "vcf-mgmt01-esxi04",
      "credentials": {
        "username": "root",
        "password": "Vcf5@password"
      },
      "vSwitch": "vSwitch0"
    }
  ]
}

4)Cloud Builder

安装 VMware Cloud Builder 工具,用于部署 VMware Cloud Foundation 的第一个初始工作负载域(管理域),请注意 Cloud Builder 用户的密码复杂度最低要求提高至 15 位字符。更多内容和细节请查看:
VMware Cloud Foundation Part 02:部署 Cloud Builder。

二、部署过程

如果环境一切准备就绪,可以开始 VCF 的构建过程。登录到部署工具 VMware Cloud Builder UI。

选择 VMware Cloud Foundation。

已知晓并点击 NEXT。

已完成并点击 NEXT。

上传本地 JSON 配置文件。

验证配置文件与部署环境。

点击确定部署 SDDC。

开始构建过程(Bring-up)。

部署成功。如果遇到 NSX Manager 因为 CPU 负载过高导致任务失败,可以多重试(RETRY)几次应该就好了。

点击登录 SDDC Manager。

已成功部署 VCF 5.2 。

三、环境信息

1)SDDC Manager

  • SDDC Manager 仪表板。

  • 工作负载域清单。

  • 管理域摘要信息。

  • SDDC Manager 主机清单。

  • SDDC Manager 支持独立更新。

  • VCF 发行版本。

  • 支持脱机库设置。

2)vCenter Server

  • 域中的主机和集群。

  • vSAN ESA 存储架构。

  • 域管理组件虚拟机。

  • vSAN ESA 存储配置。

  • VDS 分布式交换机。

3)NSX Manager

  • NSX Manager 系统概览。

  • NSX Manager 部署节点。

  • NSX 传输节点主机。

poi的excel导出

这个导出依赖于模板文件,可便捷设置表头样式。 也可以不使用模板,直接创建。

1.引入poi依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>

2.准备模板文件

image

3.编写导出代码

测试代码直接导出到本地,如需要在浏览器中导出将输出流交给浏览器即可

public class GenerateExcel {
    public static void main(String[] args) throws IOException {
        GenerateExcelXlsx();
    }


   static void GenerateExcelXlsx() throws IOException {

       List<Student> students = new Student().stuAll();
       InputStream inputStream = GenerateExcel.class.getClassLoader().getResourceAsStream("static/测试填充excel.xlsx");
        XSSFWorkbook workbook = new XSSFWorkbook(inputStream);
        // 获取第一个sheet
        Sheet sheet = workbook.getSheetAt(0);
        int index = 2;
        for (Student item : students) {
            Row row = sheet.createRow(index);
            row.createCell(0).setCellValue(item.getName());
            row.createCell(1).setCellValue(item.getSex().toString());
            row.createCell(2).setCellValue(item.getAge());
            index += 1;
        }

       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\pyb\\Desktop\\a.xlsx");

        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();

    }


    static class Student{
        private String name;
        private Integer age;
        private Character sex;

        public Student() {
        }

        public Student(String name, Character sex, Integer age) {
            this.name = name;
            this.sex = sex;
            this.age = age;
        }

        List<Student> stuAll(){
             ArrayList<Student> list = new ArrayList<>();
             for (int i = 0; i < 5; i++) {
                 Student student = new Student("张三" + i, '男' ,18 );
                 list.add(student);
             }
             return list;
         }

        public String getName() {
            return name;
        }

        public Integer getAge() {
            return age;
        }

        public Character getSex() {
            return sex;
        }
    }

}

4.运行后效果图

image

5.不依赖模板直接导出

这种方式表头内容需要自己手动写,

public class GenerateExcel {
    public static void main(String[] args) throws IOException {
        GenerateExcelXlsx2();
    }

   static void GenerateExcelXlsx2() throws IOException {

        List<Student> students = new Student().stuAll();
        XSSFWorkbook workbook = new XSSFWorkbook();
        // 创建一个sheet,这里面形参是内部名称,不可见
        Sheet sheet = workbook.createSheet();
        // 设置为第几个sheet,并设置用户可见名称
        workbook.setSheetName(0,"测试sheet");

        int index = 0;
        for (Student item : students) {
            Row row = sheet.createRow(index);
            row.createCell(0).setCellValue(item.getName());
            row.createCell(1).setCellValue(item.getSex().toString());
            row.createCell(2).setCellValue(item.getAge());
            index += 1;
        }

       FileOutputStream outputStream = new FileOutputStream("C:\\Users\\pyb\\Desktop\\b.xlsx");

        workbook.write(outputStream);
        outputStream.flush();
        outputStream.close();

    }


    static class Student{
        private String name;
        private Integer age;
        private Character sex;

        public Student() {
        }

        public Student(String name, Character sex, Integer age) {
            this.name = name;
            this.sex = sex;
            this.age = age;
        }

        List<Student> stuAll(){
             ArrayList<Student> list = new ArrayList<>();
             for (int i = 0; i < 5; i++) {
                 Student student = new Student("张三" + i, '男' ,18 );
                 list.add(student);
             }
             return list;
         }

        public String getName() {
            return name;
        }

        public Integer getAge() {
            return age;
        }

        public Character getSex() {
            return sex;
        }
    }

}

应用模板表格中的格式

一般应用与固定位置填充完毕的计算,动态的没测试过

数据塞入完毕后加入以下代码


 // 计算公式
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
evaluator.evaluateAll();

总结

获取某行或者某列时候,尽量用创建方式,除非确定行和列都能被获取到(行列不做操作、修改格式的情况下内容是空,会报空指针)