本文节选自电子书 Netkiller Blockchain 手札
Netkiller Blockchain 手札
本文作者最近在找工作, 有意向致电 13113668890
Mr. Neo Chan, 陈景峯 (BG7NYT)
中国广东省深圳市龙华新区民治街道溪山美地 518131 +86 13113668890 <netkiller@msn.com>
文档始创于 2018-02-10
版权 © 2018 Netkiller(Neo Chan). All rights reserved.
微信订阅号 netkiller-ebook (微信扫描二维码) |
---|
QQ:13721218 请注明 “读者” |
QQ 群:128659835 请注明“读者” |
网站:http://www.netkiller.cn |
内容摘要
这一部关于区块链开发及运维的电子书
为什么会写区块链电子书? 因为 2018 年是区块链年
这本电子书是否会出版 (纸质图书)? 不会, 因为互联网技术更迭太快, 纸质书籍的内容无法实时更新, 一本书动辄百元, 很快就成为垃圾, 你会发现目前市面的上区块链书籍至少是一年前写的, 内容已经过时, 很多例子无法正确运行所以我不会出版, 电子书的内容会追逐技术发展, 及时跟进软件版本的升级, 做到内容最新, 至少是主流
这本电子书与其他区块链书籍有什么不同? 市面上大部分区块链书籍都是用 2/3 去讲区块链原理, 只要不到 1/3 的干货, 干货不够理论来凑, 通篇将理论或是大谈特谈区块链行业, 这些内容更多是头脑风暴, 展望区块链, 均无法落地实施本书与那些书籍完全不同, 不讲理论和原理, 面向应用落地, 注重例子, 均是干货
电子书更新频率? 每天都会有新内容加入, 更新频率最迟不会超过一周, 更新内容请关注 https://github.com/netkiller/netkiller.github.io/commits/master
本文采用碎片化写作, 原文会不定期更新, 请尽量阅读原文
http://www.netkiller.cn/blockchain/index.html
您的打赏是我的写作动力: http://www.netkiller.cn/blockchain/donations.html
==============================
33.2. 食品溯源案例
33.2.1. 背景
需求是通过区块链跟踪产品, 实现产品产地, 生产, 流通等环节溯源
需求归纳, 需要实现下面几点:
产品具备通用的属性, 例如名称, 价格, 重量, 颜色, 体积等等
生产销售链条跟踪
涉及环节, 农产品的供应链是一个非常复杂的过程, 涉及多方, 农业局卫生局药监局工商局环保局等多个部门交织其中
参与者角色, 我们为每个环节的参与者分配一个以太坊账号, 例如每个供应商一个账号, 每个代理商一个账号这样任何一方经手后都会使用自己的账号想合约中添加数据
33.2.2. 安全问题
我将安全划分为六层, 分别是:
+----------+-----------------------------+
| 实体层 | 物 |
+----------+-----------------------------+
| 用户层 | 人 |
+----------+-----------------------------+
| 网络层 | 网络 |
+----------+-----------------------------+
| 应用层 | 操作系统, 应用服务器 |
+----------+-----------------------------+
| 业务逻辑层 | 功能, 业务逻辑 |
+----------+-----------------------------+
| 存储层 | 物理存储, 文件系统, 硬盘 |
+----------+-----------------------------+
并不是实施了区块链技术就安全无忧了, 安全分为很多层, 区块链只能做到存储层的安全区块链无法解决用户层, 应用层, 逻辑层等安全问题, 他只能保证存储在硬盘上的区块不被修改
因为区块链仅仅能解决数据存储层的安全问题, 不能保证上链的数据是真实的, 上链前绝对不会被篡改; 所以仅仅朔源, 不考虑防伪是没有意义的, 防伪仍然是重中之重
33.2.3. 防伪问题
如何做防伪呢, 这个领域很多公司已经探索多年, 各种高科技应用, 武装到牙齿, 但仍没有解决假货问题
区块链的出现很可能是一个突破, 我们只需将现有成熟的防伪技术与区块链结合即可
现在流行的访问技术太多了, 我倾向于采用二维码技术, 二维码与互联网紧密相连
33.2.4. 性能问题
区块链目前的底层只适合做, 低频高价值的业务
区块链的读取性能通常是没有问题的, 但是区块链的写入实际上无论你用多少个服务器节点都不能提升, 因为写入区块需要做共识算法, 这步操作, 会在所有节点上进行, 同时还需要加密运算, 这些操作都是 CPU 密集型操作所以写入操作是存在瓶颈的
解决这个问题, 我想出了几种方案:
性能解决方案
通过消息队列技术异步写入, 将需要写入的区块放入队列, 异步完成上链操作
并行写入, 我们可以建设多个区块链平台多个平台同时服务于业务
为了达到去中心化并行写入, 我们将在客户端通过算法, 匹配服务器而不是在两个平台前面增加负载均衡因为这样又回到了中心化系统
33.2.5. 颗粒度问题
朔源的颗粒度问题, 例如红酒的溯源, 我们是将单位溯源做到箱呢? 还是打, 或是瓶呢?
我们用四象限法则分析
高价值
- o |
- | o
- |
低频率 --------------+------------- 高频率 操作频率
- |
- o | o
- |
低价值
物品价值
通过观察上面图, 我们可以看到可以有四种情况, 低频低价值, 低频高价值, 高频高价值, 高频低价值
我认为对于低频高价值和高频高价值的业务, 尽量做到最小颗粒度
而对于低频低价值和高频低价值的业务, 可以颗粒度更粗
33.2.6. 存储规划
如果是高频低价值的业务, 那么溯源数据源源将会不断的被添加到区块, 以此同时区块的访问率极低迟早会达到一个临界值
所以你要规划存储, 例如溯源数据的过期时间, 对于 hyperledger 可以使用 DelState(key) 删除历史数据
如果是高频高价值的业务是否要考虑永久保留数据呢?
这些问题都是需要考虑的因为目前我们还不知道区块链的存储临界值
33.2.7. 大数据问题
区块链替代不了数据库, 它与数据库是互补关系
对于低频的业务, 通常传统数据库足以应付那么对于高频操作的业务呢? 暂时可能没有问题, 但总有一天会遇到瓶颈
综上所述, 溯源项目数据库规划决不能少同时还要考虑数据仓库和后期数据挖掘因为用户使用微信或者我们的 APP 扫描二维码, 我们可以获得很多有价值的数据
手上没有 Vision 使用文本简单的绘制了一幅图
- +------------------------+
- | User -> QR Code |
- +------------------------+
- | |
- V V
- +---------------+ +---------------+ +---------------+
- | Search Engine |<-- | Microservice | | Microservice |
- +---------------+ +---------------+ +---------------+
- | |
- +----------------------------------+ |
- | | | |
- V V V V
- +----------+ +------------+ +-------------+
- | Database | | Big Data | | Blockchain |
- +----------+ +------------+ +-------------+
- | MySQL | | Hadoop | | Hyperledger |
- | NoSQL | | Hive/Hbase | | Chaincode |
- +----------+ +------------+ +-------------+
- | | ^ ^
- | +------ ETL -----| |
- | |
- +----------- Message Queue ----------o
区块链之外的很多复杂的需求我们需要借助大数据系统和搜索技术
区块链的弱点是无法做复杂的查询, 这里我们会用到搜索引擎技术解决, 实际上搜索引擎角色是给区块链做索引
上图数据写入时, 保存了四份, 分别在搜索引擎, 关系型数据库, 数据仓库和区块的
具体怎么实现, 有很多方式, 这里就不讨论了, 否则就跑题了
33.2.8. BI 商业智能
数据采集, 大数据分析
溯源信息的查询是通过用户手机终端实现, 有几种途径, 微信扫二维码, APP 扫二维码, 微信小程序等等
我们可以收集到很多有价值的数据, 例如地理位置, 手机号码, 性别, 年龄等等......
有了这些数据便可以挖掘出有价值的数据例如用户行为分析, 消费与地理分析的关系, 年龄段与购买力的关系等等....
33.2.9. 采集终端
溯源数据怎么录入呢?
例如我们开发一个设备, 二维码扫描枪, 内置安卓系统
我们不清楚他们的教育背景以及学习能力, 所以设计原则是尽量傻瓜化, 降低数据录入难度和学习难度, 终端开机后互动教学, 走一遍流程即可上手
首先将溯源环节的每个节点通过后台事先写入数据库, 接下来通过 GIS 地理信息系统匹配
UUID -> 二维码 -> 设备扫描二维码激活 -> 入数据库 -> 异步消息队列 -> 上链 > ---+
- ^ |
- | |
+------------------- 追加数据 ------------------+
终端会帮助用户欲录入信息, 用户可以在信息基础上修改或者重写同时终端支持图片, 图像记录上传对于图片还能实现 EXIF 数据保存, 包括图片描述信息, 地理信息等等......
多媒体数据
这里我们需要考虑是否需要记录多媒体数据, 这里的多媒体指图像, 声音, 甚至 3D 扫描数据等等......
对于图片音频与视频, 我们可以将它集成到采集终端上, 然后异步上传到去中心化的分布式文件系统中
去中心化的分布式文件系统能实现, 一张图片一个 hash 值, 通过 hash 值访问图片, 图片被同步到相邻节点实现去中心化, 图片被修改 hash 值随之变化数据便无效
33.2.10. 物流接口
使用物流单好通过物流公司提供的借口获得物流数据, 然后写入到区块
33.2.12. 如何激励用户
防伪技术做了, 区块链溯源也做了, 那么对于用户来说, 他可能懒得去扫你的二维码, 怎么办呢?
这里需要激励用户, 怎样激励用户, 我的方案是送代币
首先代币不仅能够购买物品, 还能交易, 流通, 形成一个小的商业闭环其次目前代币已经泛滥 99% 可能是空气币, 这里我们需要将代币的价值与物品对价, 类似金本位 / 银本位
怎样操作呢? 例如一个代币等于一斤水果, 无论代币怎样炒作, 最终用户不想玩下去了, 就来换水果, 也可以是大米, 食用油等等...
关于怎样使用代币来做积分系统请参考我的另一篇文章使用代币替代传统积分系统, 你可以在搜索引擎中找到, 或者访问 https://cloud.tencent.com/developer/article/1057118
扫描二维码显示溯源防伪信息的同时我们有很多可以操作空间, 可以获取用户地理位置, 手机号码等等信息, 为后面大数据分析埋点
33.2.11. 以太坊解决方案
我们设计一个简单的合约, 模拟上面提到的解决方案
- pragma solidity ^ 0.4.20;
- contract Trace {
- enum State {
- Origin,
- Factory,
- QA,
- Shipping,
- Received,
- Pending
- }
- string name;
- uint price;
- uint weight;
- bool lock = false; // 合约锁
- bool close = false; // 合约状态
- uint number = 1;
- uint attr_number = 1;
- mapping(address =>string) guestbook; // 客户留言本
- struct Attribute {
- address owner; // 供应商
- string name; // 属性的名字
- string date; // 生产日期
- string desc; // 描述信息
- }
- mapping(uint =>Attribute) attribute;
- struct Logistics {
- address owner; // 中转站
- string date; // 转运日期
- State status; // 状态
- string message; // 留言信息
- }
- mapping(uint =>Logistics) stations;
- function Trace(string _name, uint _price, uint _weight) public {
- name = _name;
- price = _price;
- weight = _weight;
- }
- // 名称
- function getName() public view returns(string) {
- return name;
- }
- // 价格
- function getPrice() public view returns(uint) {
- return price;
- }
- // 重量
- function getWeight() public view returns(uint) {
- return weight;
- }
- // 增加商品属性
- function putAttribute(address _owner, string _name, string _date, string _desc) public {
- if (lock == false) {
- Attribute memory item = Attribute(_owner, _name, _date, _desc);
- attribute[attr_number] = item;
- attr_number = attr_number + 1;
- }
- }
- // 获得属性
- function getAttribute(uint _attr_number) public view returns(address, string, string, string) {
- require(_attr_number < attr_number);
- Attribute memory item = attribute[_attr_number];
- return (item.owner, item.name, item.date, item.desc);
- }
- // 增加物流中转信息
- function putLogistics(address _owner, string _date, State _status, string _message) public {
- if (close == false) {
- Logistics memory node = Logistics(_owner, _date, _status, _message);
- stations[number] = node;
- number = number + 1;
- lock = true;
- }
- if (_status == State.Received) {
- close = true;
- }
- }
- // 获得中转信息
- function getLogistics(uint _number) public view returns(address, string, State, string) {
- require(_number < number);
- Logistics memory node = stations[_number];
- return (node.owner, node.date, node.status, node.message);
- }
- // 或者转中站数量
- function getLogisticsCount() public view returns(uint) {
- return number;
- }
- // 客户留言
- function addGuestbook(address _owner, string message) public {
- guestbook[_owner] = message;
- }
- }
怎样使用这个合约呢? 合约部署, 需要输入三个参数, 分别是名称, 价格和装量
Trace(string _name, uint _price, uint _weight)
产品属性可以在出厂前设置, 一旦出厂进入物流阶段就不允许在更改了
33.2.11.1. 应用场景一
调用合约案例一, 这是没有经过深加工的原产品案例例如 Trace("山羊肉", 25, 50)
- var contract;
- Trace.deployed().then(function(instance){contract=instance;});
- contract.getName();
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","颜色", ""," 黑色 ")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","产地", ""," 内蒙古 ")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","出生", "2017-01-12", "XXX 牧场")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","宰杀", "2018-02-12", "XXX 宰杀")
- contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",0,"XXX 牧场");
- contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",1,"XXX 屠宰公司");
- contract.putLogistics("0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef","2018-02-22",2,"XXX 检验检疫");
- contract.putLogistics("0xf17f52151ebef6c7334fad080c5704d77216b732","2018-02-21",3,"XXX 一级经销商");
- contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-23",3,"XXX 二级经销商");
- contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-24",3,"XXX 批发中心");
- contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-25",3,"XXX 超市");
- contract.putLogistics("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-26",4,"用户包裹收到");
- contract.getNode(); // 获得物流经过的转运站数量
33.2.11.2. 应用场景二
调用合约案例二, 这是深加工的产品案例例如 Trace("牦牛肉干", 80, 500)
- var contract;
- Trace.deployed().then(function(instance){contract=instance;});
- contract.getName();
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","调和油", "2016-10-10", "银龙鱼牌")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","辣椒粉", "2016-10-30", "西藏 XXX 公司生产")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","生抽", "2016-01-12", "XXX 生抽, XXX 生产")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","山梨酸钾", "2017-02-12", "XXX 生产")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","防腐剂", "2017-02-12", "XXX 生产")
- contract.putAttribute("0x627306090abab3a6e1400e9345bc60c78a8bef57","牦牛肉", "2017-02-12", "XXX 牧场")
- contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",0,"XXX 牧场");
- contract.putLogistics("0x627306090abab3a6e1400e9345bc60c78a8bef57","2018-02-20",1,"XXX 公司生产");
- contract.putLogistics("0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef","2018-02-22",2,"XXX 通过 QAQC");
- contract.putLogistics("0xf17f52151ebef6c7334fad080c5704d77216b732","2018-02-21",3,"XXX 一级经销商");
- contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-23",3,"XXX 二级经销商");
- contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-24",3,"XXX 批发中心");
- contract.putLogistics("0x821aea9a577a9b44299b9c15c88cf3087f3b5544","2018-02-25",3,"XXX 超市");
- contract.putLogistics("0x0d1d4e623d10f9fba5db95830f7d3839406c6af2","2018-02-26",4,"用户包裹收到");
- contract.getNode(); // 获得物流经过的转运站数量
33.2.11.3. 用户留言
contract.addGuestbook("0x0d1d423e623d10f9d10f9d10f9d10f9d10f9fba5","东西好吃, 下次还买, 给好评");
33.2.12. Hyperledger 解决方案
由于家里在刷墙, 服务器收起来了, 没有开发环境, 只能提供部分参考代码, 给大家一个思路
将代码放到合约中, 使用 PutState 存储即可
- package main
- import "fmt"
- import "encoding/json"
- const (
- Origin = iota // 0
- Factory // 1
- QA // 2
- Shipping // 3
- Received // 4
- Pending // 5
- Supermarket // 6
- )
- type structElement struct {
- Name string `json:"name"`
- Company string `json:"company"`
- Description string `json:"description"`
- }
- type structLogistics struct {
- Stations string `json:"stations"` // 中转站
- Date string `json:"date"` // 转运日期
- Status uint8 `json:"status"` // 状态
- Message string `json:"message"` // 留言信息
- }
- type Trace struct {
- Name string `json:"name"`
- Address string `json:"address"`
- Attribute map[string]string `json:"attribute"`
- Element []structElement `json:"element"`
- Logistics map[string]structLogistics `json:"logistics"`
- }
- func (trace *Trace) setName(_name string) {
- trace.Name = _name
- }
- func (trace *Trace) getName() string {
- return trace.Name
- }
- func (trace *Trace) putAttribute(_key string, _value string) {
- trace.Attribute[_key] = _value
- }
- func (trace *Trace) putLogistics(_key string, _value structLogistics) {
- trace.Logistics[_key] = _value
- }
- func main(){
- trace := &Trace{
- Name: "牦牛肉干",
- Address: "内蒙古呼和浩特",
- Attribute: map[string]string{},
- Element: []structElement{structElement{Name:"塑料袋",Company: "XXX 塑料制品有限公司", Description: "外包装"},structElement{Name:"辣椒粉",Company: "XXX 调味品有限公司", Description: "采摘年份 2016-10-10"},structElement{Name:"调和油",Company: "XXX 调味品有限公司", Description: "生产日期 2016-10-10"}},
- Logistics: map[string]structLogistics{}}
- trace.putAttribute("Color","Red")
- trace.putAttribute("Size","10")
- trace.putAttribute("Weight","100kg")
- trace.putLogistics("1", structLogistics{"呼和浩特","2016-10-15", Origin, "牦牛收购"})
- trace.putLogistics("2", structLogistics{"呼和浩特","2016-10-18", Factory, "牦牛宰杀"})
- trace.putLogistics("3", structLogistics{"呼和浩特","2016-10-15", QA, "经过质检"})
- trace.putLogistics("4", structLogistics{"北京市","2016-10-15", Shipping, "运输中"})
- trace.putLogistics("5", structLogistics{"杭州市","2016-10-15", Shipping, "XXX 冷库"})
- trace.putLogistics("5", structLogistics{"深圳市","2016-10-15", Supermarket, "XXX 超市"})
- trace.putLogistics("5", structLogistics{"龙华区","2016-10-15", Received, "用户签收"})
- traceJson, _ := json.Marshal(trace)
- fmt.Println(string(traceJson))
- }
现在是晚上 12 点, 大脑一片浆糊, 已经无法在继续写下去了, 就写到这里洗洗睡了
来源: https://cloud.tencent.com/developer/article/1059650