Fabric中的Transient Data与Private Data

在Hyperledger Fabric中有两个相关的概念:私有数据(Private Data)和暂态数据(Transient Data)。本文提供四个示例程序,分别对应私有数据和暂态数据的四种组合使用方式,并通过观察账本的交易以及世界状态数据库,理解为什么在使用私有数据时应当采用暂态数据作为输入。

从技术上讲,私有数据和暂态数据是两个不同的概念。私有数据是考虑如何在通道的部分机构之间共享数据,而暂态数据则是使用私有数据时的一种输入方法。有趣的是,这两者并没有直接的关系,虽然在现实中,当我们需要安全的使用私有数据时,通常都应当使用暂态数据作为输入。

Hyperledger Fabric区块链开发教程: Fabric Node.js开发详解 | Fabric Java开发详解 | Fabric Golang开发详解

1、基本概念

先让我们重温一下在演示程序中将要用到的一些核心概念。

账本:在Hyperledger Fabric中,当一个peer节点加入通道后,就会维护一个账本的副本。账本包含一个用于保存区块的区块链数据结构,以及一个用于保存最新状态的世界状态数据库。当peer节点从排序服务收到一个新的区块并且验证成功后,peer节点就将区块提交进账本,并根据区块中每个交易的
RWSet来更新相应的世界状态。

基于共识机制,在一个通道中,不同的peer节点上的账本的大部分都是一致的。不过有一个例外,Fabric支持在部分通道之间存在的私有数据。

私有数据:在一个通道内有时会需要仅在部分机构之间共享数据。Hyperledger Fabric引入私有数据的目的就是满足这一需求。通过定义数据集,我们可以声明私有数据实现的机构子集,同时所有节点(包括在机构子集之外的其他节点)都会保存私有数据哈希的记录以便作为数据存在的证据或者用于审计目的。

可以利用链码API来使用私有数据。我们在示例代码中使用PutPrivateData和GetPrivateData这两个API。作为对比,我们使用PutState和GetState完成对公共状态的读写。

在Fabric 2.0中,会为每个机构准备一个隐含的数据集。本教程将利用这个隐含的数据集,因此我们不需要单独的数据集定义文件。

Fabric的账本结构以及私有数据的位置如下图所示,在后面的演示代码中,我们将查看交易以及世界状态数据库的内容:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DKpjv69B-1584356332391)(fabric-private-transient/ledger-arch.png)]

暂态数据:许多链码函数在被调用时需要额外的输入数据。在大多数情况下我们会在调用函数时传入一组参数,而链码参数,包括函数名和函数参数,都会作为有效交易的一部分保存在区块内,因此将永久性的存在于账本中。如果出于某种原因我们不希望在链上永久保存参数列表,我们就可以使用暂态数据。暂态数据是一种可以向链码函数传参但不需要将其保存在交易记录中的输入方法。当使用暂态数据时,需要一个特殊的链码API即GetRansient方法来读取暂态数据。我们接下来将在演示代码中看到。

因此,链码的设计需要根据业务需求设计链码,决定哪些数据应当作为正常参数输入并记录在交易中,哪些数据应当作为暂态数据输入而不必记录在链上。

私有数据与暂态数据的关系:私有数据与暂态数据并不是直接相关的,我们可以只使用私有数据而不利用暂态数据作为输入,也可以在非私有数据中使用暂态数据。因此我们可以得到示例中的四种应用场景并观察每种场景下的结果。

私有数据与暂态数据的关系如下图所示:

输入方法的选择以及是否是否私有数据依赖于具体的业务需求,因为链码函数反应了真实的业务交易。我们可以选择普通的参数列表、暂态数据或者同时使用两种方式的输入,也可以向公共状态或者私有数据集写入数据。我们需要的是正确地选择需要使用的链码API。

2、应用场景概述

如前所述,我们将私有数据和暂态数据进行2X2的组合,概述如下:

场景1:不使用私有数据,不输入暂态数据

在这种场景下,数据被写入账本中的公开状态部分,所有的peer节点将保存相同的账本数据。在链码中使用PutState和GetState来访问这部分数据。当我们调用链码时,使用普通的参数列表指定输入数据。

当通道中的所有机构都需要相同的数据时,适合采用这种方式。

场景2:使用私有数据,不输入暂态数据

在这种场景下,数据被写入账本中的私有数据部分,并且只有在私有数据集定义的机构的peer节点上会保存数据。在链码中我们使用PutPrivateData和GetPrivateData来访问私有数据集。当我们调用链码时,使用普通的参数列表指定输入数据。

当应用中存在对部分数据的隐私需求,而对于输入数据不敏感时,可以采用这种方式。

场景3:使用私有数据,输入暂态数据

类似于场景2,数据被写入账本中的私有数据部分,只有在私有数据集定义中的那些peer节点会保存这部分私有数据。在链码中,我们使用PutPrivateData和GetPrivateData来访问数据集。当我们调用链码时,采用暂态数据作为输入,因此在链码中我们需要使用GetTransient来处理输入数据。

场景4:不使用私有数据,输入暂态数据

这是一个想象的场景,只是用来表明在不使用私有数据时,也可以使用暂态数据作为输入。我们在链码中使用PutState和GetState来将数据保存到账本的公共状态,而采用暂态数据作为链码调用的输入参数。和之前一样,我们在链码中使用GetTransient方法来处理输入数据。

3、演示环境搭建

在这些演示中我们使用Fabric 2.0,使用First Network作为Fabric网络。我们采用CouchDB选项启动网络,以便于查看世界状态数据库的内容。我们将重点关注peer0.org1.example.com (couchdb port 5984) 和 peer0.org2.example.com (couchdb port 7984) 以便查看两个机构中的节点的行为。

在私有数据部分,我们使用Org1内置的隐含私有数据集(_implicit_org_Org1MSP)。只有Org1中的peer节点可以保存私有数据,而Org1和Org2中的节点都可以保存数据哈希。

我们修改了fabric-samples中的SACC链码。SACC链码有两个函数set和get。为了展示私有数据和暂态数据,我们创建以下函数:

  • setPrivate:使用相同的参数列表,数据保存在Org1隐含的私有数据集
  • setPrivateTransient:使用暂态数据输入,数据保存在Org1隐含的私有数据集
  • setTransient:使用暂态数据输入,数据保存在公共状态
  • getPrivate:提取保存在Org1隐含的私有数据集中的数据

修改后的SACC链码如下:

/** Copyright IBM Corp All Rights Reserved** SPDX-License-Identifier: Apache-2.0*/package mainimport ("encoding/json""fmt""github.com/hyperledger/fabric-chaincode-go/shim""github.com/hyperledger/fabric-protos-go/peer"
)// SimpleAsset implements a simple chaincode to manage an asset
type SimpleAsset struct {
}// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {// Get the args from the transaction proposalargs := stub.GetStringArgs()if len(args) != 2 {return shim.Error("Incorrect arguments. Expecting a key and a value")}// Set up any variables or assets here by calling stub.PutState()// We store the key and the value on the ledgererr := stub.PutState(args[0], []byte(args[1]))if err != nil {return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))}return shim.Success(nil)
}// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {// Extract the function and args from the transaction proposalfn, args := stub.GetFunctionAndParameters()var result stringvar err errorif fn == "set" {result, err = set(stub, args)} else if fn == "setPrivate" {result, err = setPrivate(stub, args)} else if fn == "setTransient" {result, err = setTransient(stub, args)} else if fn == "setPrivateTransient" {result, err = setPrivateTransient(stub, args)} else if fn == "getPrivate" {result, err = getPrivate(stub, args)} else { // assume 'get' even if fn is nilresult, err = get(stub, args)}if err != nil {return shim.Error(err.Error())}// Return the result as success payloadreturn shim.Success([]byte(result))
}// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {if len(args) != 2 {return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")}err := stub.PutState(args[0], []byte(args[1]))if err != nil {return "", fmt.Errorf("Failed to set asset: %s", args[0])}return args[1], nil
}func setPrivate(stub shim.ChaincodeStubInterface, args []string) (string, error) {if len(args) != 2 {return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")}err := stub.PutPrivateData("_implicit_org_Org1MSP", args[0], []byte(args[1]))if err != nil {return "", fmt.Errorf("Failed to set asset: %s", args[0])}return args[1], nil
}func setTransient(stub shim.ChaincodeStubInterface, args []string) (string, error) {type keyValueTransientInput struct {Key   string `json:"key"`Value string `json:"value"`}if len(args) != 0 {return "", fmt.Errorf("Incorrect arguments. Expecting no data when using transient")}transMap, err := stub.GetTransient()if err != nil {return "", fmt.Errorf("Failed to get transient")}// assuming only "name" is processedkeyValueAsBytes, ok := transMap["keyvalue"]if !ok {return "", fmt.Errorf("key must be keyvalue")}var keyValueInput keyValueTransientInputerr = json.Unmarshal(keyValueAsBytes, &keyValueInput)if err != nil {return "", fmt.Errorf("Failed to decode JSON")}err = stub.PutState(keyValueInput.Key, []byte(keyValueInput.Value))if err != nil {return "", fmt.Errorf("Failed to set asset")}return keyValueInput.Value, nil
}func setPrivateTransient(stub shim.ChaincodeStubInterface, args []string) (string, error) {type keyValueTransientInput struct {Key   string `json:"key"`Value string `json:"value"`}if len(args) != 0 {return "", fmt.Errorf("Incorrect arguments. Expecting no data when using transient")}transMap, err := stub.GetTransient()if err != nil {return "", fmt.Errorf("Failed to get transient")}// assuming only "name" is processedkeyValueAsBytes, ok := transMap["keyvalue"]if !ok {return "", fmt.Errorf("key must be keyvalue")}var keyValueInput keyValueTransientInputerr = json.Unmarshal(keyValueAsBytes, &keyValueInput)if err != nil {return "", fmt.Errorf("Failed to decode JSON")}err = stub.PutPrivateData("_implicit_org_Org1MSP", keyValueInput.Key, []byte(keyValueInput.Value))if err != nil {return "", fmt.Errorf("Failed to set asset")}return keyValueInput.Value, nil
}// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {if len(args) != 1 {return "", fmt.Errorf("Incorrect arguments. Expecting a key")}value, err := stub.GetState(args[0])if err != nil {return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)}if value == nil {return "", fmt.Errorf("Asset not found: %s", args[0])}return string(value), nil
}// Get returns the value of the specified asset key
func getPrivate(stub shim.ChaincodeStubInterface, args []string) (string, error) {if len(args) != 1 {return "", fmt.Errorf("Incorrect arguments. Expecting a key")}value, err := stub.GetPrivateData("_implicit_org_Org1MSP", args[0])if err != nil {return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)}if value == nil {return "", fmt.Errorf("Asset not found: %s", args[0])}return string(value), nil
}// main function starts up the chaincode in the container during instantiate
func main() {if err := shim.Start(new(SimpleAsset)); err != nil {fmt.Printf("Error starting SimpleAsset chaincode: %s", err)}
}

4、Fabric私有数据和暂态数据演示

首先启动First Network,不要部署默认链码,启用CouchDB选项:

cd fabric-samples/first-network
./byfn.sh up -n -s couchdb

当看到所有容器(5个排序节点,4个peer节点,4个couchdb,一个CLI)启动后:

创建一个新的链码目录:

cd fabric-samples/chaincode
cp -r sacc sacc_privatetransientdemo
cd sacc_privatetransientdemo

然后使用上面的链码替换sacc.go。

在第一次运行之前我们需要先加载依赖的模块:

GO111MODULE=on go mod vendor

最后我们使用lifecycle chaincode命令部署这个链码。

5、场景1演示:不使用Fabric私有数据和暂态数据输入

场景1时最常用的一种:使用普通的参数列表作为链码方法的输入,然后将其保存在公共状态中,所有peer节点持有完全相同的数据。我们调用链码的set和get方法。

docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls \--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \--peerAddresses peer0.org1.example.com:7051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \--peerAddresses peer0.org2.example.com:9051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \-C mychannel -n mycc -c '{"Args":["set","name","alice"]}'docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'

结果如下:

在这里插入图片描述

我们首先查看世界状态。这个数据同时保存在peer0.org1.example.com 和peer0.org2.example.com的公共状态中(mychannel_mycc)。

在这里插入图片描述

当查看区块链中的交易记录时,我么看到WriteSet中的键/值对是:name/alice,采用base64编码。

我们也可以看到调用链码时的参数列表,3个base64编码的参数分别是:set、name和alice。

在这里插入图片描述

和预期一样,RWSet更新了公开状态,输入参数被记录在交易中。

6、场景2演示:使用私有数据,不适用暂态数据输入

在场景2中,链码调用还是采用普通的参数列表,数据则保存在Org1的私有数据集。我们使用setPrivate和getPrivate访问链码

docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls \--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \--peerAddresses peer0.org1.example.com:7051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \--peerAddresses peer0.org2.example.com:9051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \-C mychannel -n mycc -c '{"Args":["setPrivate","name","bob"]}'docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["getPrivate","name"]}'

在这里插入图片描述

我们首先查看世界状态。在peer0.org1.example.com中我们可以看到数据保存为私有数据,
创建了两个数据库:一个用于实际数据,一个用于数据哈希。在peer0.org2.example.com
上,我们看到只有哈希文件。

在这里插入图片描述
内容的哈希在两个机构的节点上都是一样的。此外,在peer0.org1.example.com中我们可以
看到调用链码时输入的数据。

在这里插入图片描述

当查看区块链中的交易记录时,我们看到没有RWSet。相反我们看到数据被应用于
Org1隐含的数据集,它指向已经保存在peer中的数据,通过hash得以保护这部分数据的隐私。

在这里插入图片描述

我们可以看到调用链码时的参数列表。3个base64编码的参数分别是:setPrivate、name、bob。

在这里插入图片描述

如果我们关系数据隐私,这可能存在问题。一方面数据保存在私有数据集中,这样只有限定的机构节点可以保存。另一方面,这部分隐私数据的链码输入却还是公开可见的,并且永久保存在所有peer节点的区块链中。如果这不是你期望的,那么我们还需要将输入数据隐藏掉。这就是使用暂态数据输入的原因。

6、场景3演示:使用私有数据和暂态数据输入

如果你希望确保数据输入不会保存在链上,那么场景3是推荐的方式。在这种场景下,采用暂态数据输入,并且数据保存在Org1的私有数据集。我们使用setPrivateTransient和getPrivate方法访问链码:

在我们的链码中,我们实现函数时将暂态数据编码为特定的JSON格式 {“key”:”some key”, “value”: “some value”} (链码134–137行)。我们也要求暂态数据包含一个keyvalue键(链码149行)。为了在命令行调用
中使用暂态数据,我们需要首先将其进行base64编码。

export KEYVALUE=$(echo -n "{\"key\":\"name\",\"value\":\"charlie\"}" | base64 | tr -d \\n)docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls \--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \--peerAddresses peer0.org1.example.com:7051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \--peerAddresses peer0.org2.example.com:9051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \-C mychannel -n mycc -c '{"Args":["setPrivateTransient"]}' \--transient "{\"keyvalue\":\"$KEYVALUE\"}"docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["getPrivate","name"]}'

结果如下:

在这里插入图片描述

同样,我们首先查看世界状态。这类似于我们在场景2中看到的内容。实际的数据仅在peer0.org1.example.com保存,而哈希则在两个peer节点中都有保存。注意修订版本的值目前是2,而在场景2中的第一次修订的值是1。是链码调用促成了对数据的修订。

在这里插入图片描述

类似于场景2,在区块链上的交易记录中,我们可以看到没有Write Set。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jvftr4Hq-1584356332402)(fabric-private-transient/scenario-3-tx.png)]

我们也可以看到没有调用链码的参数列表。唯一的参数是链码函数名,setPrivateTransient。
具体的调用数据{“key”:”name”, “value”:”charlie”}没有出现在区块链上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aEJjnRi4-1584356332403)(fabric-private-transient/scenario-3-args.png)]

我们看到私有数据和隐私数据的组合提供了某种程度的数据隐私。

7、场景4演示:不适用私有数据,使用暂态数据输入

最后我们看一下想象的这个场景的演示。在场景3中,采用暂态数据作为数据,
然后保存在账本的公开状态。我们使用setTransient和get访问链码:

export KEYVALUE=$(echo -n "{\"key\":\"name\",\"value\":\"david\"}" | base64 | tr -d \\n)docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls \--cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \--peerAddresses peer0.org1.example.com:7051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \--peerAddresses peer0.org2.example.com:9051 \--tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \-C mychannel -n mycc -c '{"Args":["setTransient"]}' \--transient "{\"keyvalue\":\"$KEYVALUE\"}"docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'

结果如下:

在这里插入图片描述
可以看到公开状态被更新,两个节点目前有同样的数据。注意修订版本更新为2.

在这里插入图片描述

我们看到Write Set中的键/值对是name和david,base64编码。

在这里插入图片描述

我们没有看到参数中的输入数据,只看到调用的方法名setTransient。

在这里插入图片描述


原文链接:Hyperledger Fabric私有数据与暂态数据 — 汇智网

原文链接
本文为云栖社区原创内容,未经允许不得转载。

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

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

相关文章

窃隐私、放高利贷,输入法的骚操作真不少!

来源 | 编程技术宇宙责编 | 李雪敬封图 | CSDN 付费下载自视觉中国光说隐私泄露,人们总觉得似乎离自己很远,然而它早已像一个“地雷”,悄悄埋进了我们的生活中,不是不爆,时候未到。别认为自己只是社会中的一个小透明&a…

快速迁移 Next.js 应用到函数计算

首先介绍下在本文出现的几个比较重要的概念: 函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源&#xff…

为什么字节跳动、腾讯、阿里都在用Python??

Python 作为一种解释型技术脚本语言,越来越被认可为程序员新时代的风口语言。 无论是刚入门的程序员,还是年薪百万的 BATJ 的技术大牛都无可否认:Python的应用能力是成为一名码农大神的必要项。 而作为Python初学者来讲,最大的问题…

Need to upgrade docker package to 17.06.0+. Docker升级到最新版本

文章目录1. 现象2. 查找3. 在线卸载4. 升级docker5. 重启Docker6. 设置Docker开机自启7. 查看版本背景: 在搭建docker私有仓库的时候出现以下错误,版本太低 1. 现象 Need to upgrade docker package to 17.06.0.2. 查找 查找主机上关于Docker的软件包 …

云数据库RDS基础版的优势及适用场景

云栖号快速入门:【点击查看更多云产品快速入门】 不知道怎么入门?这里分分钟解决新手入门等基础问题,可快速完成产品配置操作! 阿里云的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版的相关信息…

docker Harbor2.3.4 http 搭建镜像仓库

文章目录一、环境准备1. 环境要求2. 节点总览3. 安装docker-compose二、安装harbor2.1. 下载2.2. 解压2.3. 调整配置2.5. 安装 harbor2.6. 效果验证三、Docker开启远程API3.1. 修改配置3.2. 支持http3.3. 重新启动Docker服务3.4. 防火墙管理3.5. 重新启动3.6. 监控状态四、测试…

下一代 IDE:Eclipse Che 究竟有什么奥秘?

来源 | CSDN(ID:CSDNnews)Eclipse Che被Eclipse官方称为下一代IDE,作为老牌的IDE,被其寄予厚望的Eclipse Che到底有什么特点,在这篇文章中我们来一探究竟。开发团队的Kuberentes原生IDEEclipse Che对开发团…

【开发者成长】阿里代码缺陷检测探索与实践

目前PRECFIX技术已经在阿里巴巴集团内部落地并获得好评;关于“PRECFIX”技术的论文被国际软件工程大会(ICSE)收录。 张昕东(别象) 阿里巴巴 云研发事业部 算法工程师 【以下为别象分享实录】 阿里巴巴在缺陷检测技术方面遇到的三个挑战 编码…

docker Harbor2.3.4 https 搭建镜像仓库

文章目录一、环境准备1. 环境要求2. 节点总览3. 安装docker-compose二、安装harbor2.1. 下载2.2. 解压2.3. 认证2.4. 调整配置2.5. 安装 harbor2.6. 配置host2.7. 效果验证三、客户端3.1. 证书保存3.2. 新建配置3.3. 登录harbor四、基本操作4.1. 下线4.2. 监控状态4.3. 重新部署…

OPPO实时数仓揭秘:从顶层设计实现离线与实时的平滑迁移

一、建设背景 关于 OPPO 移动互联网业务 大家都认为 OPPO 是一家手机公司,但大家可能并不清楚,其实 OPPO 也会做与移动互联网相关的业务。在 2019 年 12 月,OPPO 发布了自己定制的手机操作系统 ColorOS 7.0 版本。目前包括海外市场在内&…

十年技术骨干面试被开出一万五薪资,直呼 “这是对我的侮辱”

老周是我十多年前认识的同事,2012年前后,老周到北京工作的第一个任务就是为公司的产品开发IOS APP。2012年底,老周已经能熟练的驾驭苹果的cocoatouch和android核心组件。也正因为如此,老周的薪水直接翻倍,当时已经拿到…

登录 Harbor response from daemon: Get “https://192.168.92.129/v2/“: x509: cannot validate certificate

文章目录1. 现象2. 解决方案3. 重新登陆1. 现象 [rootlocalhost harbor]# docker login 192.168.92.129 Username: admin Password: Error response from daemon: Get "https://192.168.92.129/v2/": x509: cannot validate certificate for 192.168.92.129 becaus…

基于Flink的超大规模在线实时反欺诈系统的建设与实践

作者:关贺宇 在大数据时代,金融科技公司通常借助消费数据来综合评估用户的信用和还款能力。这个过程中,某些中介机构会搜集大量的号并进行“养号”工作,即在一年周期里让这些号形成正常的消费、通讯记录,目的是将这些…

别再被 Python 洗脑了!!

Python 作为一种解释型技术脚本语言,越来越被认可为程序员新时代的风口语言。无论是刚入门的程序员,还是年薪百万的 BATJ 的技术大牛都无可否认:Python的应用能力是成为一名码农大神的必要项。 而作为Python初学者来讲,最大的问题…

任务不再等待!玩转DataWorks资源组

引言 DataWorks提供了三种资源组的能力:独享资源组、自定义资源组和默认资源组,很多开发者在使用资源组时经常会碰到各类情况,到时候任务运行失败或者延迟,例如:1. 正在使用默认资源组,任务经常要等待2.购…

Docker Harbor 2.3.4 集群 双主复制高可用镜像仓库

下面操作大部分是双节点同时执行一样的命令,不同的地方我会进行标注和特殊说明 文章目录一、环境准备1. 环境要求2. 节点总览3. 安装docker-compose二、安装harbor2.1. 下载2.2. 解压2.3. 调整配置2.4. 安装 harbor2.5. 效果验证三、Docker开启远程API3.1. 修改配置…

如何用Chrome读懂网站监测Cookie

作者 | 朱顺意责编 | 李雪敬出品 | CSDN云计算(ID:CSDNcloud)网站监测工具用于标识用户的 Cookie 分为第1方 Cookie 和第3方 Cookie,这两者本质上没有什么区别,只是身份不同。Cookie 有 Domain 属性,当 Coo…

CPU有个禁区,内核权限也无法进入!

来源 | 编程技术宇宙封图 | CSDN 下载自视觉中国神秘项目我是CPU一号车间的阿Q,是的,我又来了。最近一段时间,我几次下班约隔壁二号车间虎子,他都推脱没有时间,不过也没看见他在忙个啥。前几天,我又去找他&…

防删库实用指南 | 只需一步,快速召回被误删的表

数据库的一些非常不错的企业级功能都是“养兵千日,用兵一时”,比如Oracle 10g中的回收站(Recycle Bin)功能,可以在特殊情况下发挥特种兵的功能,比如当你删除一个表空间、一个用户(Schema)时&…

智能化中的控制与自动化中的控制不同

智能化中的控制相对于自动化中的控制更加灵活、智能、综合和学习能力强。智能化控制系统能够根据实际情况进行自主决策和优化,适用范围更广,效果更好。 首先,智能化控制系统能够根据外部环境的变化和实时数据的反馈来自主调整和优化控制策略&…