如何底层调用最快地复制OPC数据到关系数据库

计算机上的二大应用,一是从WEB服务器上获得数据,另一种是向关系数据库中写入数据。在上集我已提出了一个从WEB上获得OPC数据的独创方法,现在谈谈第二种如何快速地把OPC数据写进到数据库中,这也是Calssic OPC最典型的一个应用场景。

使用基金会提供的基于.NET的ADO.NET无疑不是一个最快最有效率的办法,原因是显而易见的。要想速度快,必然要考虑到原生的基于COM的数据库技术,比如OLE DB,ADO或者ODBC。根据《ADO ActiveX Data Objects》一书描述的三者架构关系图,

显然,ADO是对OLE DB技术的上一层封装,是对当时OLE DB技术上的繁琐和难以找到熟悉COM的开发人员的一种妥协。自然,ADO的表现要比OLE DB逊色一些。不同于微软私有的ADO/OLE DB技术,ODBC是一个国际标准,有通用的接口,但性能上还是比OLE DB差了些。原因有三:第一,ODBC诞生在1992年,OLE DB出现在1996年,当年微软是想用它代替ODBC的,所以OLE DB在设计上有后发优势。第二,ODBC和OLEDB都有BIND的功能,比如ODBC有SQLBindCol()函数调用,而OLE DB不一样,要自己亲手写BIND,看上去很繁琐。其实也正是这样的繁琐保证了它性能上的优越。第三,最重要的一点,ODBC是工作在不同的查询语句上的,比如INSERT,UPDATE等,所以服务端需要进行解析。OLE DB可以使用查询语句,也可以不使用查询语句而完成INSERT、UPDATE等操作——没有了服务端的解析,自然就快了许多。有人做过测试,用ODBC的INSERT语句完成十万行的插入,而OLE DB没有使用任何INSERT语句,OLE DB比ODBC快了至少一倍以上。再多聊一些OLE DB的历史,当年没能成功替代ODBC,微软宣布准备让它退出底层的原生数据库编程应用,但是有众多厂家反对再加上OLE DB自身的性能优势,非常符合云时代的要求。所以在2017年微软宣布重新支持OLE DB的编程技术并发布了新一代的OLE DB驱动程序。新的驱动加上了加密功能,更能适应于云生时代。

虽然OLE DB性能优越,但繁琐的code让人望而生却,有没有办法?答案是 Active Template Library(ATL),它封装了很多繁琐的OLE DB底层调用,即起到防止内存泄漏,又帮你写出又快又好的程序。

本样本程序使用最新的OLE DB驱动程序,给出一个有INSERT语句的完整演示,完成快速地把OPC数据复制到数据库中,然后再展现出存贮在数据库中的所有数据。

int main(int argc, CHAR* argv[]) {CoInitializeEx(NULL, COINIT_MULTITHREADED);{CDataSource dataSource;CSession session;const WCHAR szUDLFile[] = L"OPCDA.udl";HRESULT hr = dataSource.OpenFromFileName(szUDLFile);if (FAILED(hr)) {printf("OpenFromFileName() failed\n");goto END;}hr = session.Open(dataSource);if (FAILED(hr)){printf("Open() failed\n");dataSource.Close();goto END;}CLSID cidOpcServer;if (FAILED(listServers(cidOpcServer))){printf("listServers() failed\n");dataSource.Close();goto END;}if (FAILED(DA(cidOpcServer, session))) {printf("DA() failed\n");dataSource.Close();goto END;}printf("\nretrieving rows from database...\n\n");displayResult(session);dataSource.Close();}system("pause");END:CoUninitialize();return(EXIT_SUCCESS);
}

这是主程序,运行在多线程状态,这样后面OPC的DataCallBack可以运行在另一个单独的线程中,否则全部都使用一个主线程。

接下来是根据UDL的文件设定来连接数据库。这个UDL的文件如下,

[oledb]
; Everything after this line is an OLE DB initstring
Provider=MSOLEDBSQL19.1;Integrated Security=SSPI;Persist Security Info=False;Initial Catalog=TEST;Data Source=localhost;Use Encryption for Data=Optional;

可以看到,使用了最新的19版本OLE DB的驱动(对应的是msoledbsql19.dll),指定了相应的数据库名和服务器名,不使用用户名和密码作为身份验证手段,同时不要求数据进行加密。有一点要注意的是,当通过UDL界面保存设置时,可能会有很多的属性存在这个UDL文件中,会造成OpenFromFileName()的失败,所以只要留最少的如上属性即可。

OpenFromFileName()是ATL提供的API,帮助获得第一个基于IDataInitialize接口的实例,然后根据UDL连接属性建立和数据库的连接,然后初始化基于IDBInitialize的实例如下,

HRESULT OpenFromFileName(_In_z_ LPCOLESTR szFileName) throw()
{CComPtr<IDataInitialize> spDataInit;CComHeapPtr<OLECHAR>     spszInitString;HRESULT hr = CoCreateInstance(__uuidof(MSDAINITIALIZE), NULL, CLSCTX_INPROC_SERVER,__uuidof(IDataInitialize), (void**)&spDataInit);if (FAILED(hr))return hr;hr = spDataInit->LoadStringFromStorage(szFileName, &spszInitString);if (FAILED(hr))return hr;return OpenFromInitializationString(spszInitString);
}
// Open the datasource specified by the passed initialization string
HRESULT OpenFromInitializationString(_In_z_ LPCOLESTR szInitializationString,_In_ bool fPromptForInfo = false) throw()
{CComPtr<IDataInitialize> spDataInit;HRESULT hr = CoCreateInstance(__uuidof(MSDAINITIALIZE), NULL, CLSCTX_INPROC_SERVER,__uuidof(IDataInitialize), (void**)&spDataInit);if (FAILED(hr))return hr;hr = spDataInit->GetDataSource(NULL, CLSCTX_INPROC_SERVER, szInitializationString,__uuidof(IDBInitialize), (IUnknown**)&m_spInit);if (FAILED(hr))return hr;if( fPromptForInfo ){CComPtr<IDBProperties> spIDBProperties;hr = m_spInit->QueryInterface( &spIDBProperties );DBPROP rgProperties[1];DBPROPSET rgPropertySets[1];VariantInit(&rgProperties[0].vValue);rgProperties[0].dwOptions = DBPROPOPTIONS_REQUIRED;rgProperties[0].colid = DB_NULLID;rgProperties[0].dwPropertyID = DBPROP_INIT_PROMPT;rgProperties[0].vValue.vt = VT_I2;rgProperties[0].vValue.lVal = DBPROMPT_COMPLETEREQUIRED;rgPropertySets[0].rgProperties = rgProperties;rgPropertySets[0].cProperties = 1;rgPropertySets[0].guidPropertySet = DBPROPSET_DBINIT;hr = spIDBProperties->SetProperties( 1, rgPropertySets );if (FAILED(hr))return hr;}return m_spInit->Initialize();
}

注意下这里CLSID用的是MSDAINITIALIZE,搜索注册表显示的是

也就是从oledb32.dll的地址空间中先获得IDataInitialize的实例,再调用GetDataSource()来获得IDBInitialize的指针,所以这个m_spInit指针也是在oledb32.dll的地址空间中,只不过它同时也加载了msoledbsql19.dll中的相应接口。

回到主程序,session.Open()也是ATL的API,主要是为了获得IOpenRowset的指针如下,

HRESULT Open(_In_ const CDataSource& ds,_Inout_updates_opt_(ulPropSets) DBPROPSET *pPropSet = NULL,_In_ ULONG ulPropSets = 0) throw()
{CComPtr<IDBCreateSession> spSession;// Check we have connected to the databaseATLASSERT(ds.m_spInit != NULL);HRESULT hr = ds.m_spInit->QueryInterface(__uuidof(IDBCreateSession), (void**)&spSession);if (FAILED(hr))return hr;hr = spSession->CreateSession(NULL, __uuidof(IOpenRowset), (IUnknown**)&m_spOpenRowset);if( pPropSet != NULL && SUCCEEDED(hr) && m_spOpenRowset != NULL ){// If the user didn't specify the default parameter, use oneif (pPropSet != NULL && ulPropSets == 0)ulPropSets = 1;CComPtr<ISessionProperties> spSessionProperties;hr = m_spOpenRowset->QueryInterface(__uuidof(ISessionProperties), (void**)&spSessionProperties);if(FAILED(hr))return hr;hr = spSessionProperties->SetProperties( ulPropSets, pPropSet );}return hr;
}

接下来的主程序是关于OPC的操作,listServers()是为了获得本机上OPC DA的CLSID,如下,

HRESULT listServers(CLSID& cidOpcServer)
{ULONG fetched = 0;HRESULT hr = S_OK;CComHeapPtr<OLECHAR> bsProgID, lpszUserType, lpszVerIndProgID;CATID arrcatid[3] = { NULL };arrcatid[0] = __uuidof(CATID_OPCDAServer10);arrcatid[1] = __uuidof(CATID_OPCDAServer20);arrcatid[2] = __uuidof(CATID_OPCDAServer30);CComPtr<IOPCServerList2> spIOPCServerList2;if (FAILED(hr = spIOPCServerList2.CoCreateInstance(__uuidof(OpcServerList), spIOPCServerList2, CLSCTX_ALL))){printf("CoCreateInstance() for IOPCServerList2 failed\n");return hr;}CComPtr<IOPCEnumGUID> spEnum;hr = spIOPCServerList2->EnumClassesOfCategories(sizeof arrcatid / sizeof CATID, arrcatid, 0, NULL, &spEnum);if (spEnum.p){while ((hr = spEnum->Next(1, &cidOpcServer, &fetched)) == S_OK){hr = spIOPCServerList2->GetClassDetails(cidOpcServer, &bsProgID, &lpszUserType, &lpszVerIndProgID);if (FAILED(hr)) {_tprintf(_T("GetClassDetails() failed\n"));return hr;}break;}}return hr;
}

此段程序也不复杂,获得一个IOPCServerList2的实例,然后对相应的OPC类别进行枚举,再在枚举中循环得到本机的OPC DA的CLSID。

有了DA的CLSID后,开始对DA进行操作,比如创建一个实例,建立一个新组,创建一个回调函数,通知服务端,加入感兴趣的TAG,暂停等待回调函数的结束。具体见下,

HRESULT DA(CLSID& cidOpcServer, CSession& session) {CComPtr<IOPCServer> pIOPCServer;HRESULT hr = pIOPCServer.CoCreateInstance(cidOpcServer, pIOPCServer, CLSCTX_ALL);if (FAILED(hr)) {printf("CoCreateInstance() for IOPCServer failed\n");return E_FAIL;}DWORD dwRevisedUpdateRate = 0;OPCHANDLE hGroup = 0;CComPtr<IOPCItemMgt> pOPCItemMgt;hr = pIOPCServer->AddGroup(L"", TRUE, 1000, NULL, NULL, NULL, LOCALE_SYSTEM_DEFAULT, &hGroup, &dwRevisedUpdateRate, __uuidof(IOPCItemMgt), (LPUNKNOWN*)&pOPCItemMgt);if (FAILED(hr)) {printf("AddGroup() failed\n");return E_FAIL;}DataCallback* pDataCallback = new DataCallback(session);pDataCallback->AddRef();DWORD m_dwCookie;AtlAdvise(pOPCItemMgt, pDataCallback, __uuidof(IOPCDataCallback), &m_dwCookie);hr = addItems(pOPCItemMgt);if (FAILED(hr)) {printf("addItems() failed\n");return E_FAIL;}printf("\npress any key to complete inserting rows to database\n");getchar();AtlUnadvise(pOPCItemMgt, __uuidof(IOPCDataCallback), m_dwCookie);pDataCallback->Release();return S_OK;
}

下面具体看下回调函数,它的作用是当TAG的值有变化时,此函数被唤醒在另一线程执行,返回的参数包括TAG的值,时间戳和状态,如下,

STDMETHODIMP OnDataChange(DWORD       dwTransid,OPCHANDLE   hGroup,HRESULT     hrMasterquality,HRESULT     hrMastererror,DWORD       dwCount,OPCHANDLE* phClientItems,VARIANT* pvValues,WORD* pwQualities,FILETIME* pftTimeStamps,HRESULT* pErrors
)
{CCommand<CManualAccessor> command;CComVariant vVariant[4];vVariant[0].vt = VT_BSTR;vVariant[1].vt = VT_R4;vVariant[2].vt = VT_DATE;vVariant[3].vt = VT_UINT;hr = command.CreateParameterAccessor(4, vVariant, sizeof vVariant); if (FAILED(hr)) {printf("command.CreateParameterAccessor() failed");return hr;}for (DWORD ii = 0; ii < dwCount; ii++){CComVariant vValue;WORD quality = pwQualities[ii] & OPC_QUALITY_MASK;COleDateTime oleTime = COleDateTime(pftTimeStamps[ii]);SYSTEMTIME st;oleTime.GetAsSystemTime(st);if (phClientItems[ii] == 0)CComBSTR("Random.Int1").CopyTo(&vVariant[0].bstrVal);if (phClientItems[ii] == 1)CComBSTR("Random.Int2").CopyTo(&vVariant[0].bstrVal);else if (phClientItems[ii] == 2)CComBSTR("Random.Real8").CopyTo(&vVariant[0].bstrVal);vVariant[1].fltVal = (FLOAT)pvValues[ii].dblVal;vVariant[2].date = oleTime;vVariant[3].iVal = quality;command.m_nCurrentParameter = 0;command.AddParameterEntry(1, DBTYPE_BSTR, NULL, &vVariant[0].bstrVal);command.AddParameterEntry(2, DBTYPE_R4, NULL, &vVariant[1].fltVal);command.AddParameterEntry(3, DBTYPE_DATE, NULL, &vVariant[2].date);command.AddParameterEntry(4, DBTYPE_UI2, NULL, &vVariant[3].iVal);/*This is not the most efficient and fastest way to insert a row to database due to query building/parsing and commit each time.To bulk insert, interface of IRowsetFastLoad has to be used and it is quite different from this code example.Contact developer to have a code example using IRowsetFastLoad, so you can completely understand the big difference between IDBInitialize and IDataInitialize interfaces when trying to get a pointer to IRowsetFastLoad.*/hr = command.Open(session, "insert into OPCDA (Tag, Value, Time, Quality) Values (?,?,?,?)", NULL, NULL);if (FAILED(hr)) {printf("command.Open() failed");break;}elseprintf("\nOnDataChange: %S (%f, %s.%d, %s)", vVariant[0].bstrVal, vVariant[1].fltVal, oleTime.Format("%F %T").GetString(), st.wMilliseconds, quality == OPC_QUALITY_GOOD ? "good" : "bad");SysFreeString(vVariant[0].bstrVal);}return hr;
}

这段程序中使用了ATL提供的CCommand,然后用CreateParameterAccessor()构建一个关于查询语句参数的存取器。这也是个ATL的函数,不再展开讨论,主要是执行有关参数的BIND,具体可以参见它的源代码。然后根据OPC提供的返回值的数目进行循环,取出每一个TAG的值、时间戳和状态,结合TAG名称来满足INSERT语句四个参数的要求,最后使用ATL的Open()完成INSERT语句的执行。

回到主程序,完成了INSERT的操作,下一步是从数据库中把刚才插入的数据取出来展示,

void displayResult(CSession &session) {CCommand<CManualAccessor> command;const USHORT uColumns = 4;CComVariant vValues[uColumns]{};HRESULT hr = command.CreateAccessor(uColumns, vValues, sizeof vValues);if (FAILED(hr)){printf("CreateAccessor() failed\n");return;}for (ULONG l = 0; l < uColumns; l++){command.AddBindEntry(l + 1, DBTYPE_VARIANT, NULL, &vValues[l], NULL, NULL);}hr = command.Open(session, "select * from OPCDA", NULL, NULL);if (FAILED(hr)){printf("command.Open() failed\n");return;}ULONG count = 0;while (command.MoveNext() == S_OK) {CComVariant* pBind = (CComVariant*)command.m_pBuffer;count++;COleDateTime dateTime(pBind[2].date);printf("%S (%f, %s, %s)\n", pBind[0].bstrVal, pBind[1].fltVal, dateTime.Format("%F %T").GetString(), pBind[3].iVal == OPC_QUALITY_GOOD ? "good" : "bad");;}printf("\nTotal rows: %d\n", count);
}

这段的所有操作都是调用ATL的API,先是CreateAccessor()构建个无参数的存取器,也就是建立一个BIND,供返回的数据存在内存中用。一个Open()语句完成数据的获得,再进行个循环依次展示获得的值。注意一点,返回的是一行的值,有四列。

运行后的结果如下,

综观这一程序,由于有了ATL的加持OLE DB的编程不再那么困难。ATL带来了便利,但也掩盖了对底层OLE DB的理解。每次的INSERT操作都伴随着COMMIT,显然不是最快和最有效率的OLE DB编程方法。也是基于此微软当年(2012年)在新版的Native Client 驱动中引入了IRowsetFastLoad接口,专门进行批量插入。此接口也非常简单只有二个函数,InsertRow()和Commit(),即多次调用InsertRow(),然后一次性地Commit()。为了深入理解更底层的OLE DB编程,我又独自开发了基于IRowsetFastLoad的OPC范例。本以为和这个程序差不多,没想到却被打脸。在开发过程中让我体会到使用IDataInitialize和IDBInitialize实例来获取IRowsetFastLoad指针的巨大不同,对老版的OLE DB驱动sqloledb.dll,老版的Native Client驱动sqlncli11.dll和新版的OLE DB驱动msoledbsql19.dll三者之间的关系有了进一步的了解。在进行完整的BIND过程中也领会到最原始的底层ORM的美(相对于高级语言的ORM,如Hibernate或Entity Framework),这种底层ORM和内存布局直接呼应,没有任何INSERT语句却能快速地完成批量插入,真是“不著一字,尽得风流”。感兴趣的同学可以邮箱联系我获取一份范例,在关键处我都加了注释来加深对OLE DB和COM的编程理解,确保获益满满。

本范例已经在GITHUB开源,下载在此。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023.12.25 关于 Redis 数据类型 Hash 常用命令、内部编码、应用场景

目录 Hash 数据类型 Hash 操作命令 HSET HGET HEXISTS HDEL HKEYS HVALS HGETALL HMGET HLEN HSETNX HINCRBY HINCRBYFLOAT HSTRLEN Hash 编码方式 理解什么是压缩 Hash 实际应用 Cache 缓存 Hash 数据类型 整体上来说 Redis 是键值对结构&#xff0c;其中 …

【深度学习目标检测】十一、基于深度学习的电网绝缘子缺陷识别(python,目标检测,yolov8)

YOLOv8是一种物体检测算法&#xff0c;是YOLO系列算法的最新版本。 YOLO&#xff08;You Only Look Once&#xff09;是一种实时物体检测算法&#xff0c;其优势在于快速且准确的检测结果。YOLOv8在之前的版本基础上进行了一系列改进和优化&#xff0c;提高了检测速度和准确性。…

物联网协议Coap之Californium CoapServer解析

目录 前言 一、CoapServer对象 1、类对象定义 2、ServerInterface接口 3、CoapServer对象 二、CoapServer服务运行分析 1、CoapServer对象实例化 1.1 调用构造方法 1.2 生成全局配置 1.3 创建Resource对象 1.4-1.8、配置消息传递器、添加CoapResource 1.9-1.12 创建线…

文献研读|Prompt窃取与保护综述

本文介绍与「Prompt窃取与保护」相关的几篇工作。 目录 1. Prompt Stealing Attacks Against Text-to-Image Generation Models&#xff08;PromptStealer&#xff09;2. Hard Prompts Made Easy: Gradient-Based Discrete Optimization for Prompt Tuning and Discovery&#…

说说 Spring Boot 实现接口幂等性有哪几种方案?

一、什么是幂等性 幂等是一个数学与计算机学概念&#xff0c;在数学中某一元运算为幂等时&#xff0c;其作用在任一元素两次后会和其作用一次的结果相同。 在计算机中编程中&#xff0c;一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数或幂等…

人工智能_机器学习076_Kmeans聚类算法_体验_亚洲国家队自动划分类别---人工智能工作笔记0116

我们开始来看聚类算法 可以看到,聚类算法,其实就是发现事物之间的,潜在的关联,把 有关联的数据分为一类 我们先启动jupyter notebook,然后 我们看到这里我们需要两个测试文件 AsiaFootball.txt里面记录了,3年的,亚洲足球队的成绩

【微服务核心】Spring Boot

Spring Boot 文章目录 Spring Boot1. 简介2. 开发步骤3. 配置文件4. 整合 Spring MVC 功能5. 整合 Druid 和 Mybatis6. 使用声明式事务7. AOP整合配置8. SpringBoot项目打包和运行 1. 简介 SpringBoot&#xff0c;开箱即用&#xff0c;设置合理的默认值&#xff0c;同时也可以…

ActiveMQ漏洞合集

目录 介绍CVE-2015-5254&#xff1a;Apache ActiveMQ任意代码执行漏洞漏洞介绍 & 环境准备漏洞发现Nuclei❌Vulmap✅漏洞验证漏洞利用 CVE-2016-3088&#xff1a;Apache ActiveMQ Fileserver远程代码执行漏洞漏洞发现Nuclei✅Vulmap✅MSF✅第三方工具1&#xff08;漏洞探测…

机器学习硬件十年:性能变迁与趋势

本文分析了机器学习硬件性能的最新趋势&#xff0c;重点关注不同GPU和加速器的计算性能、内存、互连带宽、性价比和能效等指标。这篇分析旨在提供关于ML硬件能力及其瓶颈的全面视图。本文作者来自调研机构Epoch&#xff0c;致力于研究AI发展轨迹与治理的关键问题和趋势。 &…

Typora Mac激活

首先去官网选择mac版本下载安装 typora下载 然后打开typora包内容找到 /Applications/Typora.app/Contents/Resources/TypeMark/page-dist 找到/static/js/Licen..如下图 编辑器打开上面文件夹 输入 hasActivated"true"e.hasActivated 进行搜索 将它改为 hasA…

算法基础之数字三角形

数字三角形 核心思想&#xff1a;线性dp 集合的定义为 f[i][j] –> 到i j点的最大距离 从下往上传值 父节点f[i][j] max(f[i1][j] , f[i1][j1]) w[i][j] 初始化最后一层 f w #include <bits/stdc.h>using namespace std;const int N 510;int w[N][N],f[N][…

12.26

key_it.c #include"key_it.h" void led_init() {// 设置GPIOE/GPIOF时钟使能RCC->MP_AHB4ENSETR | (0x3 << 4);// 设置PE10/PE8/PF10为输出模式GPIOE->MODER & (~(0x3 << 20));GPIOE->MODER | (0x1 << 20);GPIOE->MODER & (~…

Java之遍历树状菜单

&#x1f607;作者介绍&#xff1a;一个有梦想、有理想、有目标的&#xff0c;且渴望能够学有所成的追梦人。 &#x1f386;学习格言&#xff1a;不读书的人,思想就会停止。——狄德罗 ⛪️个人主页&#xff1a;进入博主主页 &#x1f5fc;专栏系列&#xff1a;无 &#x1f33c…

【小白专用】Apache下禁止显示网站目录结构的方法 更新23.12.25

给我一个网站地址&#xff0c;我点开后显示的是目录格式&#xff0c;把网站的目录结构全部显示出来了 这个显示结果不正确&#xff0c;不应该让用户看到我们的目录结构 配置文件的问题,apache配置文件里有一项可以禁止显示网站目录的配置项&#xff0c;禁止掉就好了 在apache…

alertmanage调用企业微信告警(k8s内部署)

一、前言 alertmanage调用企业微信应用告警会比直接使用钉钉告警更麻烦一点&#xff0c;调用企业微信应用告警需要在应用内配置企业可信ip&#xff0c;不然调用企业微信接口就会报错&#xff0c;提示ip地址有风险 二、部署 先自行创建企业微信&#xff0c;再使用管理后台创建应…

【GitHub精选项目】抖音/ TikTok 视频下载:TikTokDownloader 操作指南

前言 本文为大家带来的是 JoeanAmier 开发的 TikTokDownloader 项目&#xff0c;这是一个高效的下载 抖音/ TikTok 视频的开源工具。特别适合用户们保存他们喜欢的视频或分享给其他人。 TikTokDownloader 是一个专门设计用于下载 TikTok 视频的工具&#xff0c;旨在为用户提供一…

阿里云自建官方Docker仓库镜像提交拉取方法

文章目录 发布镜像到DockerHub发布镜像到自建Docker仓库(Harbor)修改配置文件在Linux服务器中登录Docker打TAGPUSH提交镜像PULL拉取镜像 发布镜像到阿里云容器服务在Linux服务器中登录DockerPUSH提交镜像PULL拉取镜像 发布镜像到DockerHub 本地我们镜像命名可能会不规范&#…

设计模式-单例模式(结合JVM基础知识)

1.定义介绍 所谓单例模式&#xff0c;是指在程序运行时&#xff0c;整个JVM中只有一个该类的实例对象 2. 单例模式的优点 复用性高&#xff0c;节省内存资源。类的加载、连接、初始化、使用都要占用虚拟机内存空间&#xff0c;因此&#xff0c;频繁创建对象会造成资源浪费&a…

从企业级负载均衡到云原生,深入解读F5

上世纪九十年代&#xff0c;Internet快速发展催生了大量在线网站&#xff0c;Web访问量迅速提升。在互联网泡沫破灭前&#xff0c;这个领域基本是围绕如何对Web网站进行负载均衡与优化。从1997年F5发布了BIG-IP&#xff0c;到快速地形成完整ADC产品线&#xff0c;企业级负载均衡…

家校互通小程序实战开发02首页搭建

目录 1 创建应用2 搭建首页总结 我们上一篇介绍了家校互通小程序的需求&#xff0c;创建了对应的数据源。有了这个基础的分析之后&#xff0c;我们就可以进入到开发阶段了。开发小程序&#xff0c;先需要创建应用。 1 创建应用 登录控制台&#xff0c;点击创建应用&#xff0c…