Hyperledger - 超级账本项目:简介,安装,案例
Hyperledger 项目是开源界面向开放、标准区块链技术的首个重要探索,在 Linux 基金会的支持下,吸引了众多科技和金融巨头的参与。
本章将介绍 hyperledger 项目的历史,并以核心的 fabric 项目为例,讲解如何快速安装部署和应用一套区块链平台
简介
历史
区块链已经成为当下最受人关注的开源技术,有人说它将颠覆金融行业的未来。然而对很多人来说,区块链技术难以理解和实现,而且缺乏统一的规范。
2015 年 12 月,Linux 基金 会牵头,联合 30 家初始成员(包括 IBM、Accenture、Intel、J.P.Morgan、R3、DAH、DTCC、FUJITSU、HITACHI、SWIFT、Cisco 等),共同 宣告 了 Hyperledger 项目的成立。该项目试图打造一个透明、公开、去中心化的超级账本项目,作为区块链技术的开源规范和标准,让更多的应用能更容易的建立在区块链技术之上。目前已经有超过 80 家企业和机构(大部分均为各自行业的领导者)宣布加入 Hyperledger 项目,目前包括 13 家来自中国的公司,包括艾亿新融旗下的艾亿数融科技公司(2016.05.19)、Onchain(2016.06.22)、比邻共赢(Belink)信息技术有限公司(2016.06.22)、BitSE(2016.06.22)、布比(2016.07.27)、三一重工(2016.08.30)等。
如果说以比特币为代表的货币区块链技术为 1.0,以以太坊为代表的合同区块链技术为 2.0,那么实现了完备的权限控制和安全保障的 Hyperledger 项目毫无疑问代表着 3.0 时代的到来。
IBM 贡献了数万行已有的 Open Block Chain 代码,Digital Asset 则贡献了企业和开发者相关资源,R3 贡献了新的金融交易架构,Intel 也刚贡献了跟分布式账本相关的代码。
首届技术委员会主席由来自 IBM 开源技术部 CTO 的 Chris Ferris 担任,委员会主席则由来自 Digital Asset Holdings 的 CEO Blythe Masters 担任。另外,自 2016 年 5 月起,Apache 基金会创始人 Brian Behlendorf 担任超级账本项目的首位执行董事。
该项目的出现,实际上宣布区块链技术已经不单纯是一个开源技术了,已经正式被主流机构和市场认可;同时,Hyperledger 首次提出和实现的完备权限管理、创新的一致性算法和可拔插的框架,对于区块链相关技术和产业的发展都将产生深远的影响。
项目官方地址托管在 Linux 基金会网站,代码托管在 Github 上,目前已经获得了不少关注。
目前主要包括两大子项目:
- fabric:包括 fabric 和 fabric-api,目标是区块链的基础核心平台,支持 pbft 等新的 consensus 机制,支持权限管理,最早由 IBM 和 DAH 发起;
- sawtooth Lake:包括 arcade、core、dev-tools、validator、mktplace 等。是 Intel 主要贡献和主导的区块链平台,支持全新的共识机制 Proof of Elapsed Time(PoET)。
目前,所有项目均处于孵化(Incubation)状态。
项目约定共同遵守的 基本原则 为:
- 重视模块化设计,包括交易、合同、一致性、身份、存储等技术场景;
- 代码可读性,保障新功能和模块都可以很容易添加和扩展;
- 演化路线,随着需求的深入和更多的应用场景,不断增加和演化新的项目。
如果你对 Hyperledger 的源码实现感兴趣,可以参考 Hyperledger 源码分析之 Fabric。
安装部署
社区在很长一段时间内并没有推出比较容易上手的安装部署方案,于是笔者设计了基于 Docker 容器的一键式部署方案,该方案推出后在社区受到了不少人的关注和应用。官方在安装部署方面已有了一些改善,具体可以参考代码 doc 目录下内容,但仍然存在一些问题。
如果你是初次接触 hyperledger fabric 项目,推荐采用如下的步骤,基于 Docker-compose 的一键部署。
动手前,建议适当了解一些 Docker 相关知识。
安装 Docker
Docker 支持 Linux 常见的发行版,如 Redhat/Centos/Ubuntu 等。
$ curl -fsSL https://get.docker.com/ | sh
安装成功后,停止默认启动的 Docker 服务。
$ sudo service docker stop
用如下命令手动启动 Docker 服务。
$ sudo docker daemon --api-cors-header="*" -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
安装 docker-compose
首先,安装 python-pip 软件包。
$ sudo aptitude install python-pip
安装 docker-compose。
$ sudo pip install docker-compose
下载镜像
注:官方现在已经定期发布镜像,使用官方镜像则可以跳过这一步
下载相关镜像,并进行配置。
$ docker pull yeasy/hyperledger-fabric:latest
$ docker tag yeasy/hyperledger-fabric:latest hyperledger/fabric-peer:latest
$ docker tag yeasy/hyperledger-fabric:latest hyperledger/fabric-baseimage:latest
$ docker tag yeasy/hyperledger-fabric:latest hyperledger/fabric-membersrvc:latest
注:如果采用某个稳定分支的代码,需要下载对应镜像。例如 0.6-dp 分支,则需要修改下载镜像的 latest 标签为 0.6-dp。
之后,用户可以选择采用不同的一致性机制,包括 noops、pbft 两类。
使用 noops 模式
noops 默认没有采用 consensus 机制,1 个节点即可,可以用来进行快速测试。
$ docker run --name=vp0
--restart=unless-stopped
-it
-p 7050:7050
-p 7051:7051
-v /var/run/docker.sock:/var/run/docker.sock
-e CORE_PEER_ID=vp0
-e CORE_PEER_ADDRESSAUTODETECT=true
-e CORE_NOOPS_BLOCK_WAIT=10
hyperledger/fabric-peer:latest peer node start
使用 PBFT 模式
PBFT 是经典的分布式一致性算法,也是 hyperledger 目前最推荐的算法,该算法至少需要 4 个节点。
首先,下载 compose 文件。
$ git clone https://github.com/yeasy/docker-compose-files
进入 hyperledger 项目,并启动集群。
$ cd docker-compose-files/hyperledger
$ docker-compose up
多物理节点部署
上述方案的典型场景是单物理节点上部署多个 Peer 节点。如果要扩展到多物理节点,需要容器云平台的支持,如 Swarm 等。
当然,用户也可以分别在各个物理节点上通过手动启动容器的方案来实现跨主机组网,每个物理节点作为一个 peer 节点。
首先,以 4 节点下的 PBFT 模式为例,配置 4 台互相连通的物理机,分别按照上述步骤配置 Docker,下载镜像。
4 台物理机分别命名为 vp0 ~ vp3。
vp0
vp0 作为初始的探测节点。
docker run --name=vp0
--net="host"
--restart=unless-stopped
-it --rm
-v /var/run/docker.sock:/var/run/docker.sock
-e CORE_PEER_ID=vp0
-e CORE_PBFT_GENERAL_N=4
-e CORE_LOGGING_LEVEL=debug
-e CORE_PEER_ADDRESSAUTODETECT=true
-e CORE_PEER_NETWORKID=dev
-e CORE_PEER_VALIDATOR_CONSENSUS_PLUGIN=pbft
-e CORE_PBFT_GENERAL_MODE=batch
-e CORE_PBFT_GENERAL_TIMEOUT_REQUEST=10s
hyperledger/fabric-peer:latest peer node start
vp1 ~ vp3
以 vp1 为例,假如 vp0 的地址为 10.0.0.1。
NAME=vp1
ROOT_NODE=10.0.0.1
docker run --name=${NAME}
--net="host"
--restart=unless-stopped
-it --rm
-v /var/run/docker.sock:/var/run/docker.sock
-e CORE_PEER_ID=${NAME}
-e CORE_PBFT_GENERAL_N=4
-e CORE_LOGGING_LEVEL=debug
-e CORE_PEER_ADDRESSAUTODETECT=true
-e CORE_PEER_NETWORKID=dev
-e CORE_PEER_VALIDATOR_CONSENSUS_PLUGIN=pbft
-e CORE_PBFT_GENERAL_MODE=batch
-e CORE_PBFT_GENERAL_TIMEOUT_REQUEST=10s
-e CORE_PEER_DISCOVERY_ROOTNODE=${ROOT_NODE}:7051
hyperledger/fabric-peer:latest peer node start
服务端口
Hyperledger 默认监听的服务端口包括:
- 7050: REST 服务端口,推荐 NVP 节点开放,旧版本中为 5000;
- 7051:peer gRPC 服务监听端口,旧版本中为 30303;
- 7052:peer CLI 端口,旧版本中为 30304;
- 7053:peer 事件服务端口,旧版本中为 31315;
- 7054:eCAP
- 7055:eCAA
- 7056:tCAP
- 7057:tCAA
- 7058:tlsCAP
- 7059:tlsCAA
应用案例
双方交易案例
两方(如 a 和 b)之间进行价值的转移。
集群启动后,进入一个 VP 节点。以 pbft 模式为例,节点名称为 pbft_vp0_1
。
$ docker exec -it pbft_vp0_1 bash
部署 chaincode example02。
$ peer chaincode deploy -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -c '{"Function":"init", "Args": ["a","100", "b", "200"]}'
03:08:44.740 [chaincodeCmd] chaincodeDeploy -> INFO 001 Deploy result: type:GOLANG chaincodeID:<path:"github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02" name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"init" args:"a" args:"100" args:"b" args:"200" >Deploy chaincode: ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
03:08:44.740 [main] main -> INFO 002 Exiting.....
返回 chaincode id 为ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
,后面将用这个 id 来标识这次交易。为了方便,把它记录到环境变量 CC_ID 中。
$ CC_ID="ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539"
部署成功后,系统中会自动生成几个 chaincode 容器,例如
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e86c26bad76f dev-vp1-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539 "/opt/gopath/bin/ee5b" 2 minutes ago Up 2 minutes dev-vp1-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
597ebaf929a0 dev-vp2-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539 "/opt/gopath/bin/ee5b" 2 minutes ago Up 2 minutes dev-vp2-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
8748a3b47312 dev-vp3-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539 "/opt/gopath/bin/ee5b" 2 minutes ago Up 2 minutes dev-vp3-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
cf6e762f6a2e dev-vp0-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539 "/opt/gopath/bin/ee5b" 2 minutes ago Up 2 minutes dev-vp0-ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539
查询 a 手头的价值,为初始值 100。
$ peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}'
03:22:31.420 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully queried transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"query" args:"a" > >
Query Result: 100
03:22:31.420 [main] main -> INFO 002 Exiting.....
a 向 b 转账 10 元。
$ peer chaincode invoke -n ${CC_ID} -c '{"Function": "invoke", "Args": ["a", "b", "10"]}'
03:22:57.345 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully invoked transaction: chaincodeSpec:<type:GOLANG chaincodeID:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539" > ctorMsg:<args:"invoke" args:"a" args:"b" args:"10" > > (fc298ffb-c763-4ed0-9da2-072de2ab20b1)
03:22:57.345 [main] main -> INFO 002 Exiting.....
查询 a 手头的价值,为新的值 90。
```sh $ peer chaincode query -n ${CC_ID} -c '{"Function": "query", "Args": ["a"]}' 03:23:33.045 [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Successfully queried transaction: chaincodeSpec:<type:golang chaincodeid:<name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539"="" style="box-sizing: border-box; -webkit-tap-highlight-color: transparent; -webkit-font-smoothing: antialiased; font-size: inherit;"> ctorMsg:</name:"ee5b24a1f17c356dd5f6e37307922e39ddba12e5d2e203ed93401d7d05eb0dd194fb9070549c5dc31eb63f4e654dbd5a1d86cbb30c48e3ab1812590cd0f78539"="">
权限管理
权限管理机制是 hyperledger fabric 项目的一大特色。下面给出使用权限管理的一个应用案例。
启动集群
首先现在相关镜像。
$ docker pull yeasy/hyperledger:latest
$ docker tag yeasy/hyperledger:latest hyperledger/fabric-baseimage:latest
$ docker pull yeasy/hyperledger-peer:latest
$ docker pull yeasy/hyperledger-membersrvc:latest
进入 hyperledger 项目,启动带成员管理的 PBFT 集群。
$ git clone https://github.com/yeasy/docker-compose-files
$ cd docker-compose-files/hyperledger
$ docker-compose -f docker-compose-with-membersrvc.yml up
用户登陆
当启用了权限管理后,首先需要登录,例如以内置账户 jim 账户登录。
登录 vp0,并执行登录命令。
$ docker exec -it pbft_vp0_1 bash# peer network login jim08:23:13.604 [networkCmd] networkLogin -> INFO 001 CLI client login...08:23:13.604 [networkCmd] networkLogin -> INFO 002 Local data store for client loginToken: /var/hyperledger/production/client/Enter password for user 'jim': 6avZQLwcUe9b
也可以用 REST 方式:
POST HOST:7050/registrar
Request:
{
"enrollId": "jim",
"enrollSecret": "6avZQLwcUe9b"}
Response:
{
"OK": "User jim is already logged in."}
chaincode 部署
登录之后,chaincode 的部署、调用等操作与之前类似,只是需要通过 -u 选项来指定用户名。
在 vp0 上执行命令:
# peer chaincode deploy -u jim -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -c '{"Function":"init", "Args": ["a","100", "b", "200"]}'
也可以通过 REST 方式进行:
POST HOST:7050/chaincode
Request:
{
"jsonrpc": "2.0",
"method": "deploy",
"params": {
"type": 1,
"chaincodeID":{
"path":"github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02"
},
"ctorMsg": {
"function":"init",
"args":["a", "1000", "b", "2000"]
},
"secureContext": "jim"
},
"id": 1}
Response:
{
"jsonrpc": "2.0",
"result": {
"status": "OK",
"message": "980d4bb7f69578592e5775a6da86d81a221887817d7164d3e9d4d4df1c981440abf9a61417eaf8ad6f7fc79893da36de2cf4709131e9af39bca6ebc2e5a1cd9d"
},
"id": 1}
chaincode 调用
在账户 a、b 之间进行转账 10 元的操作。
$ peer chaincode invoke -u jim -n 980d4bb7f69578592e5775a6da86d81a221887817d7164d3e9d4d4df1c981440abf9a61417eaf8ad6f7fc79893da36de2cf4709131e9af39bca6ebc2e5a1cd9d -c '{"Function": "invoke", "Args": ["a", "b", "10"]}'
也可以通过 REST 方式进行:
POST HOST:7050/chaincode
Request:
{
"jsonrpc": "2.0",
"method": "invoke",
"params": {
"type": 1,
"chaincodeID":{
"name":"980d4bb7f69578592e5775a6da86d81a221887817d7164d3e9d4d4df1c981440abf9a61417eaf8ad6f7fc79893da36de2cf4709131e9af39bca6ebc2e5a1cd9d"
},
"ctorMsg": {
"function":"invoke",
"args":["a", "b", "100"]
},
"secureContext": "jim"
},
"id": 3}
Response:
{
"jsonrpc": "2.0",
"result": {
"status": "OK",
"message": "66308740-a2c5-4a60-81f1-778dbed49cc3"
},
"id": 3}
chaincode 查询
查询 a 账户的余额。
也可以通过 REST 方式进行:
POST HOST:7050/chaincode
Request:
{
"jsonrpc": "2.0",
"method": "query",
"params": {
"type": 1,
"chaincodeID":{
"name":"980d4bb7f69578592e5775a6da86d81a221887817d7164d3e9d4d4df1c981440abf9a61417eaf8ad6f7fc79893da36de2cf4709131e9af39bca6ebc2e5a1cd9d"
},
"ctorMsg": {
"function":"query",
"args":["a"]
},
"secureContext": "jim"
},
"id": 5}
Response:
{
"jsonrpc": "2.0",
"result": {
"status": "OK",
"message": "900"
},
"id": 5}
区块信息查询
URL:
GET HOST:7050/chain/blocks/2
Response:
{
"transactions": [
{
"type": 2,
"chaincodeID": "EoABMjhiYjJiMjMxNjE3MWE3MDZiYjI4MTBlYzM1ZDA5NWY0MzA4NzdiZjQ0M2YxMDYxZWYwZjYwYmJlNzUzZWQ0NDA3MDBhNTMxMmMxNjM5MGQzYjMwMTk5ZmU5NDY1YzNiNzVkNTk0NDM1OGNhYWUwMWNhODFlZjI4MTI4YTFiZmI=",
"payload": "Cp0BCAESgwESgAEyOGJiMmIyMzE2MTcxYTcwNmJiMjgxMGVjMzVkMDk1ZjQzMDg3N2JmNDQzZjEwNjFlZjBmNjBiYmU3NTNlZDQ0MDcwMGE1MzEyYzE2MzkwZDNiMzAxOTlmZTk0NjVjM2I3NWQ1OTQ0MzU4Y2FhZTAxY2E4MWVmMjgxMjhhMWJmYhoTCgZpbnZva2USAWESAWISAzEwMA==",
"uuid": "2b3b6cf3-9887-4dd5-8f2e-3634ec9c719a",
"timestamp": {
"seconds": 1466577447,
"nanos": 399637431
},
"nonce": "5AeA6S1odhPIDiGjFTFG8ttcihOoNNsh",
"cert": "MIICPzCCAeSgAwIBAgIRAMndnS+Me0G6gs4J9/fb8HcwCgYIKoZIzj0EAwMwMTELMAkGA1UEBhMCVVMxFDASBgNVBAoTC0h5cGVybGVkZ2VyMQwwCgYDVQQDEwN0Y2EwHhcNMTYwNjIyMDYzMzE4WhcNMTYwOTIwMDYzMzE4WjAxMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLSHlwZXJsZWRnZXIxDDAKBgNVBAMTA2ppbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDLd2W8PxzgB4A85Re2x44BApbOGqP05tnkygbXSctLiqi5HVfwRAACS6znVA9+toni59Yy+XAH3w2offdjFW3mjgdwwgdkwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwDQYDVR0OBAYEBAECAwQwDwYDVR0jBAgwBoAEAQIDBDBNBgYqAwQFBgcBAf8EQAfASTE6bZ0P5mrEzTa5r1UyKFv+dKezBiGU0V3l2iWzk9evlGMvaC2pwhEKfKDdKxs7YSMYe/7cLq/oF++GBVowSgYGKgMEBQYIBEBEO3TKXuORl5Geuco8Gnn5TkoIl4+b96aPGDGvKbmDjMXR9vEBuUXTnsbDL53j7kC8/XQs1kZboC1ojLeUSN03MAoGCCqGSM49BAMDA0kAMEYCIQCZqyANMFcu1WiMe2So0pC7eRU95F0+qUXLAKZsPWv/YQIhALmNaglP7CoMOe2qxehucmffDlu0BRLSYDHyV9xcxmkH",
"signature": "MEYCIQDob3NqdrfwlSGhi+zz+Ypl7S9QQ07RIFr8nV92e8KDNgIhANIljz4tRS8vwQk01hTemNQFJX2zMI6DhSUFZivbbtoR"
}
],
"stateHash": "7YUoVvYnMLHbLf47uTixLtkjF6xM9DuvgSWC92MbOUzk09xhcRBBLZqe5FvJElgZemELBOcuIFnubL0LiGH0yw==",
"previousBlockHash": "On4BlpqCYNpugUKluqvOcbvkr3TAQxmlISLdd6qrONtIgmQ4iUDeWxAA9lUCceZfF8tke8A0Wy7m9tksNpKodw==",
"consensusMetadata": "CAI=",
"nonHashData": {
"localLedgerCommitTimestamp": {
"seconds": 1466577447,
"nanos": 653618964
},
"transactionResults": [
{
"uuid": "2b3b6cf3-9887-4dd5-8f2e-3634ec9c719a"
}
]
}}
Python 客户端
前面应用案例,都是直接通过 HTTP API 来跟 hyperledger 进行交互,操作比较麻烦。
还可以直接通过 hyperledger-py 客户端来进行更方便的操作。
安装
$ pip install hyperledger --upgrade
或直接源码安装
$ git clone https://github.com/yeasy/hyperledger-py.git
$ cd hyperledger-py
$ pip install -r requirements.txt
$ python setup.py install
使用
>>> from hyperledger.client import Client>>> c = Client(base_url="http://127.0.0.1:7050")>>> c.peer_list(){u'peers': [{u'type': 1, u'ID': {u'name': u'vp1'}, u'address': u'172.17.0.2:30303'}, {u'type': 1, u'ID': {u'name': u'vp2'}, u'address': u'172.17.0.3:30303'}]}
更多使用方法,可以参考 API 文档。
- Reverse Linked List II
- python爬虫(六)_urllib2:handle处理器和自定义opener
- Hbase多版本的读写(Shell&Java API版)
- 总结---3
- Mac系统实现git命令自动补全
- [大数据之Yarn]——资源调度浅学
- Mac系统的终端显示git当前分支
- 不掌握这几个人工智能编程语言怎么能说懂AI
- Gulp实现css、js、图片的压缩以及css、js文件的MD5命名
- 大数据之Yarn——Capacity调度器概念以及配置
- 移动端web开发,click touch tap区别
- 大数据学习之路(持续更新中...)
- 如何养成良好的c++编程习惯(1)——内存管理
- 使用jOrgChart插件实现组织架构图的展示
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 基于 HTTP Header 传输签名参数
- 【原创】Java并发编程系列32 | 阻塞队列(下)
- 深入理解swap交换分区理解及扩存
- client-go 之 Reflector 源码分析
- Vue.js点击按钮弹出隐藏菜单的几种方式
- 【Vue.js】Vue.js组件库Element中的单选框、多选框、输入框和计数器
- 【Vue.js】Vue.js组件库Element的基础用法
- 常见的图像处理技术
- docker浅入深出续
- 【21期】你能说说Java中Comparable和Comparator的区别吗
- 昨天晚上熬夜整理的Java注解相关学习笔记
- 对图标的坐标轴进行调整
- Vue 开发技巧总结
- 35.opengl PBR-光照
- 聊聊claudb的list command