整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

有状态部署慢?使用 openkruise 实现容器应

有状态部署慢?使用 openkruise 实现容器应用固定 id




景说明

我们在业务上容器的过程中遇到了如下问题:

  1. 以 deployment 部署的应用 pod,由于 id 经常变更,服务重启,监控变得难以维护。这里只是以监控为切入点,事实上,还有诸多应用需要与id强绑定。
  2. statefulset 可以解决上面的问题,但是引入一个新的问题就是 statefulset 本身为了维护有状态的应用,所有的应用 Pod 启动是有严格的先后顺序,也就是串行启动,对于大规模的应用 pod 来讲,启动消耗时间太长,这是无法忍受的。

为解决以上问题,我们在容器平台当中引入了 openkruise。

openkruise简介

项目地址:https://github.com/openkruise/kruise

详细的说明可以参考这篇文章:https://yq.aliyun.com/articles/706442

从当前 github 上的文档来看,目前 OpenKruise 支持五种改进的控制器:

  • CloneSet: CloneSet is a workload that mainly focuses on managing stateless applications. It provides full features for more efficient, deterministic and controlled deployment, such as inplace update, specified pod deletion, configurable priority/scatter update, preUpdate/postUpdate hooks.
  • Advanced StatefulSet: An enhanced version of default StatefulSet with extra functionalities such as inplace-update, pause and MaxUnavailable.
  • SidecarSet: A controller that injects sidecar containers into the Pod spec based on selectors and also is able to upgrade the sidecar containers.
  • UnitedDeployment: This controller manages application pods spread in multiple fault domains by using multiple workloads.
  • BroadcastJob: A job that runs Pods to completion across all the nodes in the cluster.

UnitedDeployment 是在 StatefulSet 基础上的更高级抽象,通过一个资源描述可以管理多个 StatefulSet 的实例组,可实现多实例组的灰度发布与滚动升级。

Broadcast Job 实际上就是以 DaemonSet 的方式在所有节点上运行一次性 JobSidercarSet 用于 Sidercar 注入及管理

而我们要使用到的正是其 Advanced StatefulSet 的特性。关于Advanced StatefulSet更详细的描述如下:

  • 在kubernetes官方的statefulSet上做了功能扩展,更新策略由原来的只支持recreate,扩展为同时支持recreate和rollingupdate。rollingupdate还支持两种策略,一种是InPlaceIfPossible,另一种是InPlaceOnly。InPlaceIfPossible会尽可能的保证应用在原地升级(只支持镜像的升级,如果修改了yaml中的其他配置项,则无法保证);InPlaceOnly会保证应用一定在原地升级,但是它也只支持镜像的升级,如果修改了yaml中的其他配置项,会直接抛出异常。另外,原生的StatefulSet只能做到串行启动,Advanced StatefulSet可以做到并行启动。


部署openkruise

官方的安装文档可以直接参考这里:
https://github.com/openkruise/kruise/tree/master/docs/tutorial

我简单写下安装步骤:

wget https://github.com/openkruise/kruise/releases/download/v0.4.0/kruise-chart.tgz
tar xf kruise-chart.tgz
cd kruise
helm install openkruise ./ -n kube-system

目前openkruise已经更新到了v0.5.0的版本。也可以直接通过阿里云的应用目录来完成其安装。

下面说一下更详细的安装过程:

1、获取helm包

helm repo add incubator  http://aliacs-k8s-cn-beijing.oss-cn-beijing.aliyuncs.com/app/charts-incubator/
helm search repo ack-kruise
helm fetch incubator/ack-kruise
tar xf ack-kruise-0.5.0.tgz
cd ack-kruise

修改values.yml文件如下:

# Default values for kruise.

revisionHistoryLimit: 3

manager:
  # settings for log print
  log:
    # log level for kruise-manager
    level: "4"

  # image settings
  image:
    # repository for kruise-manager image
    repository: hub.example.com/library/kruise-manager
    # tag for kruise-manager image
    tag: v0.5.0

  # resources of kruise-manager container
  resources:
    limits:
      cpu: 500m
      memory: 1Gi
    requests:
      cpu: 500m
      memory: 1Gi

  metrics:
    addr: localhost
    port: 8080

  custom_resource_enable: StatefulSet

其实这里就改了两个东西:

  • image:默认是docker hub上的地址,我这里改到了私有镜像仓库
  • custom_resource_enable:用于指定启用哪几种资源,如果不指定的话,openkruise支持的五种资源会全部启用,我这里只用到了StatefulSet,所以这里只启用了这一种资源

然后执行安装操作:

helm install ack-kruise -n kube-system ./

安装完后,会生成以下五种crd:

# kubectl get crds |grep kruise
broadcastjobs.apps.kruise.io            2020-04-26T10:29:28Z
clonesets.apps.kruise.io                2020-04-26T10:29:28Z
sidecarsets.apps.kruise.io              2020-04-26T10:29:28Z
statefulsets.apps.kruise.io             2020-04-26T10:29:28Z
uniteddeployments.apps.kruise.io        2020-04-26T10:29:28Z

同时会创建一个 kruise-system 的命名空间,并在里面生成一个 pod:

# kubectl get pods -n kruise-system
NAME                          READY   STATUS    RESTARTS   AGE
kruise-controller-manager-0   1/1     Running   0          55m

验证 statefulset 资源的 webhook 是否被正常创建:

# kubectl get mutatingwebhookconfiguration -o yaml

apiVersion: v1
items:
- apiVersion: admissionregistration.k8s.io/v1
  kind: MutatingWebhookConfiguration
  metadata:
    creationTimestamp: "2020-04-26T10:29:28Z"
    generation: 3
    name: kruise-mutating-webhook-configuration
    resourceVersion: "622944921"
    selfLink: /apis/admissionregistration.k8s.io/v1/mutatingwebhookconfigurations/kruise-mutating-webhook-configuration
    uid: 303a7b7f-3a62-49d7-8ef6-082ea288eeb2
  webhooks:
  - admissionReviewVersions:
    - v1beta1
    clientConfig:
      caBundle: xxxxx
      service:
        name: kruise-webhook-server-service
        namespace: kruise-system
        path: /mutating-create-update-statefulset
        port: 443
    failurePolicy: Fail
    matchPolicy: Exact
    name: mutating-create-update-statefulset.kruise.io
    namespaceSelector:
      matchExpressions:
      - key: control-plane
        operator: DoesNotExist
    objectSelector: {}
    reinvocationPolicy: Never
    rules:
    - apiGroups:
      - apps.kruise.io
      apiVersions:
      - v1alpha1
      operations:
      - CREATE
      - UPDATE
      resources:
      - statefulsets
      scope: '*'
    sideEffects: Unknown
    timeoutSeconds: 30
......

也是确保其他未用到的相关 mutatingwebhook 是关闭的。。在实际测试中,SidecarSet 资源的 mutatingwebhook 可能会导致创建的 pod 出不来。

这些webhook本质上都是kubernetes的admissioncontrol,只要你安装了,哪怕没有使用,当你在执行相关操作时,都需要被所有的adminssioncontrol检测,如果admissioncontrol本身出了问题,就会导致请求无法响应的状态。同时这些webhook类型的adminssioncontrol也会拖慢响应速度。

用法示例

下面是官方提供的一个基于 openkruise 提供的 statefulset 资源的部署文件示例:

apiVersion: apps.kruise.io/v1alpha1
kind: StatefulSet
metadata:
  name: demo-v1-guestbook-kruise
  labels:
    app.kubernetes.io/name: guestbook-kruise
    app.kubernetes.io/instance: demo-v1
spec:
  replicas: 3
  serviceName: demo-v1-guestbook-kruise
  selector:
    matchLabels:
      app.kubernetes.io/name: guestbook-kruise
      app.kubernetes.io/instance: demo-v1
  template:
    metadata:
      labels:
        app.kubernetes.io/name: guestbook-kruise
        app.kubernetes.io/instance: demo-v1
    spec:
      readinessGates:
        # A new condition that ensures the pod remains at NotReady state while the in-place update is happening
      - conditionType: InPlaceUpdateReady
      containers:
      - name: guestbook-kruise
        image: openkruise/guestbook:v1
        imagePullPolicy: Always
        ports:
        - name: http-server
          containerPort: 3000
  podManagementPolicy: Parallel  # allow parallel updates, works together with maxUnavailable
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      # Do in-place update if possible, currently only image update is supported for in-place update
      podUpdatePolicy: InPlaceIfPossible
      # Allow parallel updates with max number of unavailable instances equals to 2
      maxUnavailable: 3

执行部署之后,启动 pod 示例如下:

# kubectl get pods  |grep demo-v1
demo-v1-guestbook-kruise-0                                 1/1     Running   0          62s
demo-v1-guestbook-kruise-1                                 1/1     Running   0          62s
demo-v1-guestbook-kruise-2                                 1/1     Running   0          62s

也可通过如下操作查看资源状态:

# kubectl get sts.apps.kruise.io
NAME                       DESIRED   CURRENT   UPDATED   READY   AGE
demo-v1-guestbook-kruise   3         3         3         3       56s

openkruise提供的statefulset的资源名为sts.apps.kruise.io

更详细的用法可参考:

  • Advanced StatefulSet具体的使用方法:https://github.com/openkruise/kruise/blob/master/docs/concepts/astatefulset/README.md
  • Advanced StatefulSet示例文件:https://github.com/openkruise/kruise/blob/master/docs/tutorial/v1/guestbook-statefulset.yaml
  • UnitedDeployment具体的使用方法:https://github.com/openkruise/kruise/blob/master/docs/tutorial/uniteddeployment.md
  • UnitedDeployment示例文件:https://raw.githubusercontent.com/kruiseio/kruise/master/docs/tutorial/v1/uniteddeployment.yaml

来源:https://www.cnblogs.com/breezey/p/12925103.html

篇挺好的文章,可以细品下

内容摘要

这一部关于区块链开发及运维的电子书。

为什么会写区块链电子书?因为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扫二维码,微信小程序等等。

我们可以收集到很多有价值的数据,例如地理位置,手机号码,性别,年龄等等......

有了这些数据便可以挖掘出有价值的数据,甚至可以将数据提供给生产企业作参考。

大数据能做什么?

  1. 用户行为分析,用户的喜好,这些数据能为后面精准推送提供支持。
  2. 消费与地理分析的关系
  3. 年龄段与购买力的关系
  4. 区域产品的存量,例如:用户扫描了一次二维码,可能用户就已经使用了改产品。我们就知道该地区投放的1000件商品被消耗了意见。
  5. 性别与消费习惯
  6. 两次间隔消费时间
  7. 活跃用户和沉睡用户


33.2.9. 采集终端

溯源数据怎么录入呢?例如我们开发一个设备,二维码扫描枪,内置安卓系统。

我们不清楚他们的教育背景以及学习能力,所以设计原则是尽量傻瓜化,降低数据录入难度和学习难度,终端开机后互动教学,走一遍流程即可上手。

首先将溯源环节的每个节点通过后台事先写入数据库,接下来通过GIS地理信息系统匹配。

UUID -> 二维码 -> 设备扫描二维码激活-> 入数据库 -> 异步消息队列 -> 上链 > ---+^ || |+------------------- 追加数据 -----------------+

终端会帮助用户欲录入信息,用户可以在信息基础上修改或者重写。同时终端支持图片,图像记录上传。

对于图片还能实现 EXIF 数据保存,包括图片描述信息,地理信息等等......

33.2.10. 多媒体数据

这里我们需要考虑是否需要记录多媒体数据,这里的多媒体指图像,声音,甚至3D扫描数据等等......

对于图片、音频与视频,我们可以将它集成到采集终端上,然后异步上传到去中心化的分布式文件系统中。

去中心化的分布式文件系统能实现,一张图片一个hash值,通过hash值访问图片,图片被同步到相邻节点实现去中心化,图片被修改hash值随之变化数据便无效。

33.2.11. 物流接口

使用物流单好通过物流公司提供的借口获得物流数据,然后写入到区块。

33.2.12. 如何激励用户

防伪技术做了,区块链溯源也做了,那么对于用户来说,他可能懒得去扫你的二维码,怎么办呢?

这里需要激励用户,怎样激励用户,我的方案是送代币。

首先代币不仅能够购买物品,还能交易,流通,形成一个小的商业闭环。其次目前代币已经泛滥 99% 可能是空气币,这里我们需要将代币的价值与物品对价,类似金本位/银本位。

怎样操作呢?例如一个代币等于一斤水果,无论代币怎样炒作,最终用户不想玩下去了,就来换水果,也可以是大米,食用油等等...

关于怎样使用代币来做积分系统请参考我的另一篇文章 《使用代币替代传统积分系统》 ,你可以在搜索引擎中找到

根据业务需要,可以发行布置一套币,例如水果币,流量币,话费币,每种币的功能不同,这些币可以在交易所中撮合交易,例如卖出水果币,换成流量币等等。

由于国家的法规问题,代币系统设计原则一定是代币只能用来购买商城中的物品,不能直接兑换成RMB,否则会触碰到国家的红线。但是通过交易所,币币之间兑换我们就控制不了了。

另外扫描二维码显示溯源防伪信息的同时我们有很多可以操作空间,可以获取用户地理位置,手机号码等等信息,为后面大数据分析埋点。

用户激励手段

  1. 分享激励
  2. 好评激励
  3. 用户等级激励
  4. 代币激励
  5. 用户排名,PK排行榜
  6. 成就勋章
  7. 身份标签,黄马甲:)


等等,手段众多,目的是让用户查询溯源信息,手机用户数据,鼓励代币消费等等.......

33.2.13. 上链

并不是所有数据都上链,哪些数据上链呢?

产地(出生、生长)、采购、加工(检疫、屠宰)、库存、运输、销售、配送等等......

33.2.14. 以太坊解决方案

我们设计一个简单的合约,模拟上面提到的解决方案

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.14.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.14.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通过QA、QC");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.14.3. 用户留言

contract.addGuestbook("0x0d1d423e623d10f9d10f9d10f9d10f9d10f9fba5","东西好吃,下次还买,给好评");

33.2.15. Hyperledger 解决方案

由于家里在刷墙,服务器收起来了,没有开发环境,只能提供部分参考代码,无法提供合约完整代码,只是给大家一个思路,原理很上面以太坊的合约类似。

33.2.15.1. 溯源合约涉及

package mainimport "fmt"import "encoding/json"const (Origin=iota // 0Factory // 1QA // 2Shipping // 3Received // 4Pending // 5Supermarket // 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))}

33.2.15.1.1. 食品安全朔源

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, "用户签收"})

33.2.15.1.2. 水平移植

这个方案可以水平移植到其他领域,例如 药品安全溯源

trace :=&Trace{Name: "强身大力丸",Address: "深圳是XXX制药有限公司",Attribute: map[string]string{},Element: []structElement{structElement{Name:"枸杞",Company: "宁夏XXX农业有限公司", Description: "采摘年份2016-10-10,10g"},structElement{Name:"茯苓",Company: "河南XXX农业有限公司", Description: "采摘年份2016-10-10,20kg"},structElement{Name:"XXX",Company: "XXX有限公司", Description: "生产日期2016-10-10"},structElement{Name:"XXX",Company: "XXX有限公司", Description: "生产日期2016-10-10"},......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("3", structLogistics{"XXX市药品监督局","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, "用户签收"})

合约落地,还需要做一些调整已适应实际场景。但基本思路是通的。

33.2.15.2. 积分通正(代币)

我发现用以太坊思维,将以太坊代币合约搬到 hyperledger 上,一样可以实现代币的功能,这个代币除了不能上交易所,基本满足我们替代积分系统的需求,下面是我写了这样一个合约,在超级账本上实现类似以太坊的代币转账功能。

package mainimport ("bytes""encoding/json""fmt""strconv""http://github.com/hyperledger/fabric/core/chaincode/shim"sc "http://github.com/hyperledger/fabric/protos/peer")// Define the Smart Contract structuretype SmartContract struct {}type Token struct {Owner string `json:"Owner"`TotalSupply uint `json:"TotalSupply"`TokenName string `json:"TokenName"`TokenSymbol string `json:"TokenSymbol"`BalanceOf map[string]uint `json:"BalanceOf"`}func (token *Token) initialSupply(){token.BalanceOf[token.Owner]=token.TotalSupply;}func (token *Token) transfer (_from string, _to string, _value uint){if(token.BalanceOf[_from] >=_value){token.BalanceOf[_from] -=_value;token.BalanceOf[_to] +=_value;}}func (token *Token) balance (_from string) uint{return token.BalanceOf[_from]}func (token *Token) burn(_value uint) {if(token.BalanceOf[token.Owner] >=_value){token.BalanceOf[token.Owner] -=_value;token.TotalSupply -=_value;}}func (token *Token) burnFrom(_from string, _value uint) {if(token.BalanceOf[_from] >=_value){token.BalanceOf[_from] -=_value;token.TotalSupply -=_value;}}func (token *Token) mint(_value uint) {token.BalanceOf[token.Owner] +=_value;token.TotalSupply +=_value;}func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) sc.Response {return shim.Success(nil)}func (s *SmartContract) initLedger(stub shim.ChaincodeStubInterface) sc.Response {token :=&Token{Owner: "netkiller",TotalSupply: 10000,TokenName: "代币通正",TokenSymbol: "COIN",BalanceOf: map[string]uint{}}token.initialSupply()tokenAsBytes, _ :=json.Marshal(token)stub.PutState("Token", tokenAsBytes)fmt.Println("Added", tokenAsBytes)return shim.Success(nil)}func (s *SmartContract) transferToken(stub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) !=3 {return shim.Error("Incorrect number of arguments. Expecting 2")}tokenAsBytes, _ :=stub.GetState(args[0])token :=Token{}json.Unmarshal(tokenAsBytes, &token)token.transfer(args[1],args[2],args[3])tokenAsBytes, _=json.Marshal(token)stub.PutState(args[0], tokenAsBytes)return shim.Success(nil)}func (s *SmartContract) balanceToken(stub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) !=1 {return shim.Error("Incorrect number of arguments. Expecting 1")}tokenAsBytes, _ :=stub.GetState(args[0])token :=Token{}json.Unmarshal(tokenAsBytes, &token)amount :=token.balance(args[1])return shim.Success(amount)}func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) sc.Response {// Retrieve the requested Smart Contract function and argumentsfunction, args :=stub.GetFunctionAndParameters()// Route to the appropriate handler function to interact with the ledger appropriatelyif function=="balanceToken" {return s.balanceToken(stub, args)} else if function=="initLedger" {return s.initLedger(stub)} else if function=="transferToken" {return s.transferToken(stub, args)}return shim.Error("Invalid Smart Contract function name.")}// The main function is only relevant in unit test mode. Only included here for completeness.func main() {// Create a new Smart Contracterr :=shim.Start(new(SmartContract))if err !=nil {fmt.Printf("Error creating new Smart Contract: %s", err)}}

合约代码的测试

func main(){token :=&Token{Owner: "netkiller", // 代币管理者TotalSupply: 10000, // 代币发行总量TokenName: "积分连", // 代币名称TokenSymbol: "NEO", // 代币符号 NEOBalanceOf: map[string]uint{}}token.initialSupply() // 初始化代币fmt.Println(token.balance("netkiller")) // 查询余额token.transfer("netkiller","neo", 100) // 转账,这里账号使用用户ID,没有使用以太坊钱包那样的哈希值,因为哈希值不便于记忆。fmt.Println(token.balance("netkiller"))fmt.Println(token.balance("neo"))}

我们可以建立很多套这样的比,例如水果币,蔬菜币,流量币...

开发一个小型交易所难度也不大,让用户在交易所中交易这些币。

篇挺好的文章,可以细品下

内容摘要

这一部关于区块链开发及运维的电子书。

为什么会写区块链电子书?因为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扫二维码,微信小程序等等。

我们可以收集到很多有价值的数据,例如地理位置,手机号码,性别,年龄等等......

有了这些数据便可以挖掘出有价值的数据,甚至可以将数据提供给生产企业作参考。

大数据能做什么?

  1. 用户行为分析,用户的喜好,这些数据能为后面精准推送提供支持。
  2. 消费与地理分析的关系
  3. 年龄段与购买力的关系
  4. 区域产品的存量,例如:用户扫描了一次二维码,可能用户就已经使用了改产品。我们就知道该地区投放的1000件商品被消耗了意见。
  5. 性别与消费习惯
  6. 两次间隔消费时间
  7. 活跃用户和沉睡用户


33.2.9. 采集终端

溯源数据怎么录入呢?例如我们开发一个设备,二维码扫描枪,内置安卓系统。

我们不清楚他们的教育背景以及学习能力,所以设计原则是尽量傻瓜化,降低数据录入难度和学习难度,终端开机后互动教学,走一遍流程即可上手。

首先将溯源环节的每个节点通过后台事先写入数据库,接下来通过GIS地理信息系统匹配。

UUID -> 二维码 -> 设备扫描二维码激活-> 入数据库 -> 异步消息队列 -> 上链 > ---+^ || |+------------------- 追加数据 -----------------+

终端会帮助用户欲录入信息,用户可以在信息基础上修改或者重写。同时终端支持图片,图像记录上传。

对于图片还能实现 EXIF 数据保存,包括图片描述信息,地理信息等等......

33.2.10. 多媒体数据

这里我们需要考虑是否需要记录多媒体数据,这里的多媒体指图像,声音,甚至3D扫描数据等等......

对于图片、音频与视频,我们可以将它集成到采集终端上,然后异步上传到去中心化的分布式文件系统中。

去中心化的分布式文件系统能实现,一张图片一个hash值,通过hash值访问图片,图片被同步到相邻节点实现去中心化,图片被修改hash值随之变化数据便无效。

33.2.11. 物流接口

使用物流单好通过物流公司提供的借口获得物流数据,然后写入到区块。

33.2.12. 如何激励用户

防伪技术做了,区块链溯源也做了,那么对于用户来说,他可能懒得去扫你的二维码,怎么办呢?

这里需要激励用户,怎样激励用户,我的方案是送代币。

首先代币不仅能够购买物品,还能交易,流通,形成一个小的商业闭环。其次目前代币已经泛滥 99% 可能是空气币,这里我们需要将代币的价值与物品对价,类似金本位/银本位。

怎样操作呢?例如一个代币等于一斤水果,无论代币怎样炒作,最终用户不想玩下去了,就来换水果,也可以是大米,食用油等等...

关于怎样使用代币来做积分系统请参考我的另一篇文章 《使用代币替代传统积分系统》 ,你可以在搜索引擎中找到

根据业务需要,可以发行布置一套币,例如水果币,流量币,话费币,每种币的功能不同,这些币可以在交易所中撮合交易,例如卖出水果币,换成流量币等等。

由于国家的法规问题,代币系统设计原则一定是代币只能用来购买商城中的物品,不能直接兑换成RMB,否则会触碰到国家的红线。但是通过交易所,币币之间兑换我们就控制不了了。

另外扫描二维码显示溯源防伪信息的同时我们有很多可以操作空间,可以获取用户地理位置,手机号码等等信息,为后面大数据分析埋点。

用户激励手段

  1. 分享激励
  2. 好评激励
  3. 用户等级激励
  4. 代币激励
  5. 用户排名,PK排行榜
  6. 成就勋章
  7. 身份标签,黄马甲:)


等等,手段众多,目的是让用户查询溯源信息,手机用户数据,鼓励代币消费等等.......

33.2.13. 上链

并不是所有数据都上链,哪些数据上链呢?

产地(出生、生长)、采购、加工(检疫、屠宰)、库存、运输、销售、配送等等......

33.2.14. 以太坊解决方案

我们设计一个简单的合约,模拟上面提到的解决方案

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.14.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.14.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通过QA、QC");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.14.3. 用户留言

contract.addGuestbook("0x0d1d423e623d10f9d10f9d10f9d10f9d10f9fba5","东西好吃,下次还买,给好评");

33.2.15. Hyperledger 解决方案

由于家里在刷墙,服务器收起来了,没有开发环境,只能提供部分参考代码,无法提供合约完整代码,只是给大家一个思路,原理很上面以太坊的合约类似。

33.2.15.1. 溯源合约涉及

package mainimport "fmt"import "encoding/json"const (Origin=iota // 0Factory // 1QA // 2Shipping // 3Received // 4Pending // 5Supermarket // 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))}

33.2.15.1.1. 食品安全朔源

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, "用户签收"})

33.2.15.1.2. 水平移植

这个方案可以水平移植到其他领域,例如 药品安全溯源

trace :=&Trace{Name: "强身大力丸",Address: "深圳是XXX制药有限公司",Attribute: map[string]string{},Element: []structElement{structElement{Name:"枸杞",Company: "宁夏XXX农业有限公司", Description: "采摘年份2016-10-10,10g"},structElement{Name:"茯苓",Company: "河南XXX农业有限公司", Description: "采摘年份2016-10-10,20kg"},structElement{Name:"XXX",Company: "XXX有限公司", Description: "生产日期2016-10-10"},structElement{Name:"XXX",Company: "XXX有限公司", Description: "生产日期2016-10-10"},......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("3", structLogistics{"XXX市药品监督局","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, "用户签收"})

合约落地,还需要做一些调整已适应实际场景。但基本思路是通的。

33.2.15.2. 积分通正(代币)

我发现用以太坊思维,将以太坊代币合约搬到 hyperledger 上,一样可以实现代币的功能,这个代币除了不能上交易所,基本满足我们替代积分系统的需求,下面是我写了这样一个合约,在超级账本上实现类似以太坊的代币转账功能。

package mainimport ("bytes""encoding/json""fmt""strconv""http://github.com/hyperledger/fabric/core/chaincode/shim"sc "http://github.com/hyperledger/fabric/protos/peer")// Define the Smart Contract structuretype SmartContract struct {}type Token struct {Owner string `json:"Owner"`TotalSupply uint `json:"TotalSupply"`TokenName string `json:"TokenName"`TokenSymbol string `json:"TokenSymbol"`BalanceOf map[string]uint `json:"BalanceOf"`}func (token *Token) initialSupply(){token.BalanceOf[token.Owner]=token.TotalSupply;}func (token *Token) transfer (_from string, _to string, _value uint){if(token.BalanceOf[_from] >=_value){token.BalanceOf[_from] -=_value;token.BalanceOf[_to] +=_value;}}func (token *Token) balance (_from string) uint{return token.BalanceOf[_from]}func (token *Token) burn(_value uint) {if(token.BalanceOf[token.Owner] >=_value){token.BalanceOf[token.Owner] -=_value;token.TotalSupply -=_value;}}func (token *Token) burnFrom(_from string, _value uint) {if(token.BalanceOf[_from] >=_value){token.BalanceOf[_from] -=_value;token.TotalSupply -=_value;}}func (token *Token) mint(_value uint) {token.BalanceOf[token.Owner] +=_value;token.TotalSupply +=_value;}func (s *SmartContract) Init(stub shim.ChaincodeStubInterface) sc.Response {return shim.Success(nil)}func (s *SmartContract) initLedger(stub shim.ChaincodeStubInterface) sc.Response {token :=&Token{Owner: "netkiller",TotalSupply: 10000,TokenName: "代币通正",TokenSymbol: "COIN",BalanceOf: map[string]uint{}}token.initialSupply()tokenAsBytes, _ :=json.Marshal(token)stub.PutState("Token", tokenAsBytes)fmt.Println("Added", tokenAsBytes)return shim.Success(nil)}func (s *SmartContract) transferToken(stub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) !=3 {return shim.Error("Incorrect number of arguments. Expecting 2")}tokenAsBytes, _ :=stub.GetState(args[0])token :=Token{}json.Unmarshal(tokenAsBytes, &token)token.transfer(args[1],args[2],args[3])tokenAsBytes, _=json.Marshal(token)stub.PutState(args[0], tokenAsBytes)return shim.Success(nil)}func (s *SmartContract) balanceToken(stub shim.ChaincodeStubInterface, args []string) sc.Response {if len(args) !=1 {return shim.Error("Incorrect number of arguments. Expecting 1")}tokenAsBytes, _ :=stub.GetState(args[0])token :=Token{}json.Unmarshal(tokenAsBytes, &token)amount :=token.balance(args[1])return shim.Success(amount)}func (s *SmartContract) Invoke(stub shim.ChaincodeStubInterface) sc.Response {// Retrieve the requested Smart Contract function and argumentsfunction, args :=stub.GetFunctionAndParameters()// Route to the appropriate handler function to interact with the ledger appropriatelyif function=="balanceToken" {return s.balanceToken(stub, args)} else if function=="initLedger" {return s.initLedger(stub)} else if function=="transferToken" {return s.transferToken(stub, args)}return shim.Error("Invalid Smart Contract function name.")}// The main function is only relevant in unit test mode. Only included here for completeness.func main() {// Create a new Smart Contracterr :=shim.Start(new(SmartContract))if err !=nil {fmt.Printf("Error creating new Smart Contract: %s", err)}}

合约代码的测试

func main(){token :=&Token{Owner: "netkiller", // 代币管理者TotalSupply: 10000, // 代币发行总量TokenName: "积分连", // 代币名称TokenSymbol: "NEO", // 代币符号 NEOBalanceOf: map[string]uint{}}token.initialSupply() // 初始化代币fmt.Println(token.balance("netkiller")) // 查询余额token.transfer("netkiller","neo", 100) // 转账,这里账号使用用户ID,没有使用以太坊钱包那样的哈希值,因为哈希值不便于记忆。fmt.Println(token.balance("netkiller"))fmt.Println(token.balance("neo"))}

我们可以建立很多套这样的比,例如水果币,蔬菜币,流量币...

开发一个小型交易所难度也不大,让用户在交易所中交易这些币。