杨 发布的文章

这个报错是因为将 map[string]decimal.Decimal 定义为 decimal.Decimal 类型,而实际上在 map 中赋值的是未命名的浮点型数值 '0.0001'。Go 语言中,未命名的浮点数值默认是 float64 类型,而 decimal.Decimal 类型是不能直接将 float64 类型的值赋给它的。

要解决这个问题,可以将 map[string]decimal.Decimal 中的 float64 类型的值使用 decimal.NewFromFloat() 函数转换为 decimal.Decimal 类型的值。

例如:

var FilterJunkTrans = map[string]decimal.Decimal{

"USDT": decimal.NewFromFloat(1),
"DAI": decimal.NewFromFloat(1),
"USDC": decimal.NewFromFloat(1),
"TUSD": decimal.NewFromFloat(1),
"HUSD": decimal.NewFromFloat(1),
"BUSD": decimal.NewFromFloat(1),
"TRX": decimal.NewFromFloat(1),
"ETH": decimal.NewFromFloat(0.00001),
"BNB": decimal.NewFromFloat(0.0001),
"WBNB": decimal.NewFromFloat(0.0001),

}
这样就可以将 float64 类型的值转换为 decimal.Decimal 类型的值,解决报错问题。

比较运算
greaterThan

当在循环内开启 goroutine 时,一定要非常小心地处理循环变量,避免它们在 goroutine 之间发生竞争,这是常见的 Go 并发问题之一。在当前给出的代码中,开启 goroutine 时使用的 tmpChain 变量就是循环变量 chain 的一个副本,但是它的值只会在循环开始时被赋值,而不会在每次迭代时重新赋值。因此,在你的代码里,如果开启了一些长时间运行的 goroutine 并且在迭代 chainCurrencyAddrMap 之后执行了它们,它们将会看到那个过时的 tmpChain 变量,而不是它们应该看到的当前值。这将导致 goroutine 处理的信息混乱,导致错误。

为了修复这个问题,一种解决方法是将循环变量传递给 goroutine 的参数中,这将确保在 goroutine 中使用的始终是变量的当前值。可以通过将 tmpChain := chain 改为 使用函数参数传递来实现。修改后的代码如下:

func getAddrDistInfoPatch(c * gin.Context,

rawData *graph_raw.GraphRawData, 
chainCurrencyAddrMap map[string]map[string][]string,
column []string) (inFlowMap map[string]map[string]map[string]aModel.AddressInfo, err error) {

mu := &sync.Mutex{}
wg := &sync.WaitGroup{}

inFlowMap = make(map[string]map[string]map[string]aModel.AddressInfo)

for chain, currencyInfo := range chainCurrencyAddrMap {
    for currency, addrs := range currencyInfo {
        limit := make(chan bool, 50)
        for i := 0; i < len(addrs); i += 50 {
            start := i
            wg.Add(1)
            limit <- true
            go func(c string, cr string) {
                defer wg.Done()
                group := collections.SliceSubRange(addrs, start, 50)
                req := aModel.AddressInfoReq{
                    Direction: contract.LeftDirection,
                    Chain:     c,
                    Currency:  cr,
                    MinAmount: rawData.SessionInfo.MinAmount,
                    Column:    column,
                    Addr:      group,
                    BeginTime: rawData.SessionInfo.BeginTime,
                    EndTime:   rawData.SessionInfo.EndTime,
                }

                tmp := map[string]aModel.AddressInfo{}
                resp := safeis.Call(c, "address", "getDistCurrencyAddrInfo",
                    http.MethodPost, nil, req, nil)
                if resp.Code != serror.Success {
                    slog.Errorf(c, "call address/getDistCurrencyAddrInfo failed, resp:%+v", resp)
                    return
                }
                _ = resp.ToStruct(&tmp)
                <-limit

                mu.Lock()
                defer mu.Unlock()

                for k, v := range tmp {
                    if _, ok := inFlowMap[k]; !ok {
                        inFlowMap[k] = make(map[string]map[string]aModel.AddressInfo)
                    }
                    if _, ok := inFlowMap[k][chain]; !ok {
                        inFlowMap[k][chain] = make(map[string]aModel.AddressInfo)
                    }
                    inFlowMap[k][chain][currency] = v
                }
            }(chain, currency)
        }
    }
}

wg.Wait()
return inFlowMap, nil

}
在上述代码中,通过将 tmpChain 赋值替换为将 chain 和 currency 作为参数传递给匿名函数,解决了迭代过程中 goroutine 错误描述的问题。

在Linux系统中,抢占式调度是通过内核的调度器来实现的,它在内核中的实现是一种基于时间片轮转(Round-Robin)和优先级的调度策略。Linux 2.6内核版本之后,引入了完全抢占式内核模型,这意味着即使在内核中运行的代码,也可以在各种情况下被中断或抢占。以下是Linux如何进行抢占式调度的大体步骤:

1.在Linux系统中,每个进程都有一个优先级,通常从0到139。进程的优先级可以通过nice或renice命令来调整。

2.Linux内核具有多个运行队列,每个队列都包含具有相同优先级的进程。这可以确保高优先级进程拥有更好的CPU运行时间,并且不会一直阻塞在等待队列中。

3.内核启动后,将由特殊进程init启动的第一个进程(通常是/bin/bash)添加到运行队列中。

4.运行队列被周期性地遍历,内核为每个队列中的进程分配一个时间片。

5.当进程的时间片耗尽时,内核将终止该进程的运行,并将其从当前队列中删除。

6.如果在一个队列中运行的进程需要等待I/O或其他资源(例如磁盘、网络等),那么该进程将被放入相应的等待队列中。当等待的资源变为可用时,内核将重新将该进程添加到适当的运行队列中。

7.如果有挂起的高优先级进程,内核通过抢占机制将该进程插入到当前进程的时间片中,并为其提供CPU时间片来运行。此时,内核将挂起原来运行的进程,并继续运行更高优先级的进程。

通过上述步骤,Linux内核实现了一种抢占式调度的机制,它可以在实时响应和非实时任务之间找到平衡点,并按照优先级给予适当的CPU时间。

锁机制

在抢占式调度中,为了确保多个进程之间的协调运行,通常需要对一些共享资源进行加锁。锁的机制可以防止出现一些并发问题,比如竞争条件、死锁等等,保护进程的完整性。

在Linux内核中,抢占式调度使用了自旋锁和读写锁(spinlock和rwlock)来确保临界区的互斥,而不会出现睡眠锁(sleep lock)的情况,以免影响响应时间。自旋锁允许一个持有锁的进程能够自旋在一个循环中,等待其他进程放弃锁,而不是进入等待队列或挂起状态。自旋锁不能用于保护长时间运行的临界区,否则会导致缺乏可调度性(优先级反转问题,优先级比自旋锁持有者高的进程被阻塞在自旋锁上)。而读写锁则允许许多进程同时读取资源,但只允许一个进程对资源进行写入操作。

因此,在实现抢占式调度时,锁的使用是必须的。在Linux内核中,锁机制被集成在调度器中,用于保护多个进程之间的协调运行,以确保实时响应任务的正确性和及时性。

区块链目前有多少种虚拟机

区块链目前有多种虚拟机,其中最流行和常用的是以太坊虚拟机(Ethereum Virtual Machine,EVM),其他主要的区块链虚拟机包括NEO虚拟机(NEO Virtual Machine,NeoVM)、EOS虚拟机(EOS Virtual Machine,EVM)和Cardano虚拟机等。不同的区块链平台选择不同的虚拟机,以适应其应用场景和开发要求。

波场用的是什么虚拟机

波场(TRON)使用的是TRON虚拟机(TVM,TRON Virtual Machine)。TVM是TRON生态系统的核心组件,它提供了一种基于Solidity语言的智能合约执行环境,与以太坊虚拟机(EVM)类似,但在性能和资源消耗方面有所优化。TVM的出现,可以帮助开发者更方便地部署智能合约,并且可以支持高性能、高并发的区块链应用场景。

以下是一些当前较为主流的加密货币:

比特币(Bitcoin, BTC):比特币是第一种加密货币,也是最普及的一种加密货币。它以去中心化为核心,使用点对点技术实现交易和发行新的货币单位。

以太坊(Ethereum, ETH):以太坊是最重要的加密货币之一,它不仅是数字货币也是一种智能合约平台。以太坊区块链使用自己的加密货币以太币,也是支持许多 ERC-20 代币和其他分叉硬币的平台。

瑞波币(Ripple, XRP):Ripple是一种实时的毫秒级交易的加密货币,它不同于比特币和以太坊的点对点虚拟货币,由一家叫 Ripple 的公司发行。

比特币现金(Bitcoin Cash, BCH):比特币现金是比特币的一个硬分叉,它的目标是提高区块大小限制,从而提高比特币的交易速度。

Litecoin(LTC):Litecoin是比特币的一个分叉,但独立发展自己的市场。与比特币相比,Litecoin的交易过程更加迅速、更便宜、更加去中心化等等。

PromQL 是 Prometheus Query Language 的缩写,是一种用于查询 Prometheus 监控数据的查询语言。以下是一些有用的示例:

显示某个指标的平均值:
avg(metric_name)
这将计算指定指标的所有采样值的平均值。

显示指标的最大值:
max(metric_name)
这会显示指定指标的最大值。

显示过去 5 分钟内某个指标的总和:
sum_over_time(metric_name[5m])
这将计算指定指标在过去 5 分钟内的所有采样值之和。

显示某个指标在某个时间范围内的百分比:
avg_over_time(metric_name[1h]) / ignoring (instance) group_left sum(avg_over_time(metric_name[1h])) * 100
这将计算在过去一小时内,指定指标的平均值与该指标所有实例的平均值之比,并将结果转换为百分比。

Prometheus 官方文档中的 PromQL 部分。

编程语言:Solidity
以太坊智能合约的 demo,实现了一个简单的投票系统,具体代码如下:

pragma solidity >=0.4.22 <0.7.0;

contract Voting {

    mapping (bytes32 => uint8) public votesReceived;

    bytes32[] public candidateList;

    constructor(bytes32[] memory candidateNames) public {
        candidateList = candidateNames;
    }

    function totalVotesFor(bytes32 candidate) view public returns (uint8) {
        require(validCandidate(candidate));
        return votesReceived[candidate];
    }

    function voteForCandidate(bytes32 candidate) public {
        require(validCandidate(candidate));
        votesReceived[candidate] += 1;
    }

    function validCandidate(bytes32 candidate) view public returns (bool) {
        for(uint i = 0; i < candidateList.length; i++) {
            if (candidateList[i] == candidate) {
                return true;
            }
        }
        return false;
    }
}

上述智能合约实现了一个简单的投票系统。构造函数用于初始化候选人列表,通过 mapping 映射将候选人名称与其得票数建立关联。通过函数 totalVotesFor 可以返回指定候选人的当选票数,函数 voteForCandidate 可以将某位候选人的得票数加一,函数 validCandidate 用于验证候选人名称是否在候选人列表中。

这只是一个简单的 demo,实际应用的智能合约需要更加完善的逻辑和安全性检查。同时实际开发中还需要考虑 gas 费用、合约存储空间等问题。

Gas费用是以太坊网络中用于支付交易费用的一种计量单位,类似于比特币网络中的交易手续费。Gas本质上是以太坊网络中的计算资源,每一个操作都需要消耗一定的Gas,不同的操作需要消耗不同数量的Gas。例如,一个简单的转账交易需要消耗的Gas费用相对较低,而部署智能合约或执行复杂的智能合约操作需要消耗更多的Gas费用。

以太坊网络中的Gas价格是动态的,由市场供需关系决定,通常以Gwei为单位。Gwei是一种以Ether为基础单位的十亿分之一,因此1 Gwei = 0.000000001 Ether。当用户发起一笔交易时,需要指定所愿意支付的Gas价格(Gwei/Gas),以及愿意支付的Gas数量,这两个参数决定了交易的总费用。如果交易费用太低,可能会导致交易长时间无法被确认;如果交易费用过高,则会浪费用户的资金。

Gas费用的收取主要是为了防止恶意用户利用以太坊网络的计算资源进行攻击,同时也是为了激励矿工验证和打包交易,维护以太坊网络的正常运转。

以太坊区块包含以下属性:

区块头(Block Header):区块头是一个包含区块元数据的数据结构,包括了区块的版本号、难度值、时间戳、区块奖励、上一个区块的哈希值、默克尔根等信息。

交易记录(Transactions):交易记录是指在以太坊网络中发生的所有交易,包括转账交易、部署智能合约、调用智能合约等操作。

祖先区块哈希值(Parent Block Hash):指当前区块的前一个区块的哈希值,用于链接区块链中的每个区块。

默克尔根(Merkle Root):指区块中所有交易记录的哈希值所构成的默克尔树的根节点哈希值,用于验证区块中交易的完整性。

随机数(Nonce):是一个32位的随机数,用于挖矿计算区块的哈希值。

区块哈希值(Block Hash):是指整个区块的哈希值,由区块头和交易记录共同组成,用于验证区块的唯一性和完整性。

总之,以太坊区块包含区块头、交易记录、祖先区块哈希值、默克尔根、随机数和区块哈希值等属性,这些属性共同构成了以太坊区块链的数据结构。

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将不兼容的接口转换为另一个接口,以满足客户端的需求。在 Golang 中实现适配器模式可以使用接口和组合来实现。

下面是一个使用 Golang 实现适配器模式的示例代码:

package main

import "fmt"

type Target interface {
    Request()
}

type Adaptee struct {}

func (a *Adaptee) SpecificRequest() {
    fmt.Println("Adaptee.SpecificRequest()")
}

type Adapter struct {
    adaptee *Adaptee
}

func (a *Adapter) Request() {
    a.adaptee.SpecificRequest()
}

func main() {
    adaptee := &Adaptee{}
    adapter := &Adapter{adaptee}

    adapter.Request()
}

在上面的代码中,我们定义了一个 Target 接口,它包含一个 Request 方法,用于执行请求。我们还定义了一个 Adaptee 结构体,它包含一个 SpecificRequest 方法,用于执行特定的请求。

接着,我们定义了一个 Adapter 结构体,它包含一个指向 Adaptee 的指针,并实现了 Target 接口的 Request 方法。该方法会调用 Adaptee 的 SpecificRequest 方法,将不兼容的接口转换为 Target 接口。

最后,在 main 函数中,我们创建了一个 Adaptee 实例,并将其传递给 Adapter 实例。我们调用 adapter.Request() 方法,实际上是调用了 Adaptee.SpecificRequest() 方法,但由于适配器的存在,客户端无需直接调用 Adaptee 的方法,从而实现了接口的兼容。