1、json Unmarshal
struct中如果变量首字母是小写的,那么unmarshal后的值都是空的,所以struct的变量首字母必须大写。
package main
import (
"encoding/json"
"fmt"
)
type data struct {
Age int `json:"age"`
Name string `json:"name"`
EngineType string `json:"engineType"`
ConnectType string `json:"connectType"`
id int `json:"id"`
}
func main() {
testStr := `{"age":1,"name":"john","engineType":"MUBU","size":"json", "id": 2}`
var err error
mydata := data{}
err = json.Unmarshal([]byte(testStr), &mydata)
if err != nil {
fmt.Printf("unmarshal err:%s", err)
}
fmt.Printf("mydata:\n%+v\n", mydata)
}
打印结果
mydata:
{Age:1 Name:john EngineType:MUBU ConnectType: id:0}
所以在json反序列化为struct时,struct中多一些变量或者json中多一些变量都没有关系,不会报错,unmarshal只会把符合定义的规则的变量反序列化出来塞进struct中
2、go get命令
使用场景:对于没有vendor的工程,编译发现缺少很多编译包,可以采用go get -u -v "xxxx"(填入工程地址)来自动更新依赖包,这句命令会自动拉取这个工程的包以及它所依赖的所有其他包。
3、幂等性
接口的幂等性:使用相同参数对同一资源重复调用某个接口的结果与调用一次的结果相同。
幂等性用数学公式可以表示为:f(x) = f (f(x)),意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。在分布式系统中,设计这种性质接口的原因是在调用接口发生异常并且重复尝试时,总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。
实现幂等的常用思路
(1)MVCC(Multi-Version Concurrency Control)多版本并发控制
在数据更新时需要去比较持有数据的版本号,版本号不一致的操作无法成功。例如博客点赞次数自动+1的接口。
update blogTable set count=count+1, version=version+1 where id=321 and version=123
每一个version只有一次执行成功的机会,一旦失败必须重新获取版本号。
(2)去重表
利用数据库的特性来实现幂等,常用的是构建唯一性索引,保证某一类数据写入后,后续同样的请求无法再写入。例如博客点赞,将博客id和用户id绑定建立唯一索引,这样重复点赞的数据无法写入数据库。
(3)Token机制
核心思想是:为每一次操作生成一个唯一性的凭证token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。
以电商平台为例,订单id就可以作为token,当用户下单时,会经历多个环节,比如生成订单,减库存,减优惠等等。每一个环节执行时都先检测一下该订单id是否已经执行过这一步骤,对未执行的请求,执行操作并缓存结果,而对已经执行过的id,则直接返回之前的执行结果,不做任何操作。这样可以在最大程度上避免操作的重复执行问题,缓存起来的执行结果也能用于事务的控制。
4、退避算法
退避算法就是网络上的节点在发送数据冲突后,等待一定时间后再发,等待时间是随指数增长,从而避免频繁的触发冲突。
在计算机网络中,二进制指数退避算法常常作为避免网络堵塞的一部分用于同一数据块的重发策略。发生n次冲突后,等待时间在0~2^n-1个间隙时间(slot times)之间任意选择,以此类推,随着冲突次数的增加,发送方的等待时间将会有成倍增加的可能性。
具体的退避算法如下:
(1)确定基本退避时间(基数),也就是一个争用期时间(总线上的单程端到端传播时延记为x,以太网的端到端往返时间2x,2x是一个争用期),对于以太网就是51.2μs。
(2)从离散的整数集合[0, 1, ...,]中随机取出一个数,计为r。重传应推后的时间就是r倍的争用期。定义一个参数K,为重传次数,K=min[重传次数,10],可见k<=10。当重传次数超过10时,k就不再增大而一直等于10。
(3)当重传次数超过16次发送失败,则丢弃传输的帧,并向高层发送错误报告。
例如,在第1次重传时,k=1,随机数r从整数{0, 1}中选一个数。因此重传推迟的时间是0或争用期,在这两个时间中随机选择一个。若再发生碰撞,k=2,随机数r就从整数{0, 1, 2, 3}中选一个数。因此重传推迟的时间是在0,2x,4x和6x这4个时间中随机抽取一个。同样,若再次发生碰撞,k=3,随机数r就从整数{0, 1, 2, 3, 4, 5, 6, 7}中选一个数。以此类推,若连续多次发生冲突,就表明可能有较多的站参与争用信道。使用退避算法可使重传需要推迟的平均时间随重传次数而增大(这也称为动态退避),因而减小发生碰撞的概率,有利于整个系统的稳定。
退避算法在计算机网路中应用很广泛,下面介绍两个场景:
(1)接入第三方支付服务,在三方支付提供的接入接口规范中,服务方交易结束结果通知和商户主动查询交易结果都用到重发机制。
(2)在app应用中,很多场景会遇到轮询一类的问题。一般的轮询对于app性能和电量的消耗是个巨大的灾难。使用指数退避算法来减少更新频率,从而节省资源和减少电的消耗。
5、拜占庭将军问题
拜占庭将军问题是一个协议问题,拜占庭帝国派出10支军队攻击某一支敌军。这支敌军至少需要6支军队(一半以上)才能打败,任一支军队单独进攻都毫无胜算。但是10支军队分散在敌国的四周,依靠通信兵骑马相互通信来协商进攻意向及进攻时间。这10支军队中可能存在将军是叛徒,叛徒可能擅自变更进攻意向或者进攻时间。在这种状态下,拜占庭将军们要如何保证有多于6支军队在同一时间一起发起进攻,从而赢取战斗?
针对拜占庭问题,目前科学家们得到一个结论:如果叛徒的数量大于或等于1/3,拜占庭问题不可解。
参考文档:
(1)中本聪与拜占庭将军问题:https://www.jianshu.com/p/5fea30b25f0a
(2)什么是拜占庭将军问题:https://learnblockchain.cn/2018/02/05/bitcoin-byzantine/
6、领域驱动设计(DDD)
DDD的全称是Domain-driven Design,即领域驱动设计。指开发在设计系统时,会分为若干层,每一层都有自己专门负责的事情,即术业有专攻。这样做的好处是,系统的扩展性比较高。
参考文档:
(1)看完这篇,你就会了解什么是DDD
https://www.jianshu.com/p/1f9fea5a988d
(2)浅谈我对DDD领域驱动设计的理解
https://www.cnblogs.com/netfocus/p/5548025.html
7、非对称加密算法
一直不太理解非对称加密,公钥和秘钥是如何工作的。终于看到一篇解释通俗的文章,用可以撞的锁来比喻真是太容易懂了。下面我稍微总结一下
大家应该都知道这种圆形锁。假设现在所有的锁都需要用钥匙才能锁上,而这个钥匙只有你有,如果你家有装修工人在装修,而你要出门,但是如果你不在没有钥匙,一旦装修工人走了,就锁不了门。此时你就需要一把这样的圆形锁,只要不在里面按上撞门,就不会锁上。如果装修完了,装修工人要锁门,就在里面按上然后撞上门即可。不需要主人一定要守着用钥匙才能锁上。
在通信中,假设A和B通信,A有一个保险箱,这个保险箱只有A身上的一把钥匙才能打开,A可以先把保险箱打开,然后给B,B把数据放到保险箱里面然后锁上回给A,A身上有钥匙可以打开箱子就能拿到数据了。当然这只是简单的原理,中间涉及到很多安全性问题。
因此在非对称加密中,有一把公钥和一把私钥。公钥是对外公布的,私钥只有自己知道。还是上面的通信例子,A和B通信,这时A对外公布A的公钥,B拿到这个公钥对数据进行加密,然后发给A,A通过私钥解开。公钥是用来加密信息的,确保只有特定的人(用谁的公钥就是谁)才能解密信息。
参考文档:
如何用通俗易懂的话来解释非对称加密
https://www.zhihu.com/question/33645891
8、RSA非对称加解密
常用的非对称加密是RSA算法
下面是用golang实现RSA加解密的一个例子:
import (
"fmt"
"encoding/pem"
"crypto/x509"
"crypto/rsa"
"crypto/rand"
"github.com/pkg/errors"
"os"
)
// 加密
func RsaEncrypt(origData []byte) ([]byte, error) {
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, errors.New("public key error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
pub := pubInterface.(*rsa.PublicKey)
return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}
// 解密
func RsaDecrypt(ciphertext []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, errors.New("private key error!")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}
func main() {
data, err := RsaEncrypt([]byte("hello xiaoquanquan"))
if err != nil {
panic(err)
}
fmt.Printf("加密后的数据:%s\n", string(data))
origData, err := RsaDecrypt(data)
if err != nil {
panic(err)
}
fmt.Printf("解密后的数据:%s\n", string(origData))
//rsa 密钥文件产生
//GenRsaKey(1024)
}
// 公钥和私钥可以从文件中读取
var privateKey = []byte(`
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDEoduylYLkfKvwMQmPovhFEIzHaNA6aeAwBK3DxgkLEDNe0e7n
nlWrUSfv3pHlPc4DRCDPZy43FmTXzuM0Isjud8/FGsAVm+UucXC45EuCiq2VYj0N
feXveWKHgL5ZMnjTQ9wWCEmipOqbU3hTgdPy/Mj8i9ZAiZsITamGlIMl8QIDAQAB
AoGALjR6k7ReVaKWJJLhVEdPX6tL6W/PvmoyrBXtXxuh9F+SMq3SHcsvBlrDr7H1
RpEHX+0aaKIuUfOJLs0GxrGF1VlZDTmMoMdGZ9Y3NWqNarDLkzdZypgd6rVkSHp1
YgZsk7Us1mbBTRBADIKqCHeEFnkWOTdPKNaSzVmMt0jw8aECQQDHrPmwp0rz8h/n
1VonTFDN5gJmt77fQClB58qFE/58iTL+A1onEtyxXNznUqYWnFESazFFiWuWEsKe
xpqIQUDHAkEA/BkcOjDS1hwIE8vZlshRBOMrNDkSvmxG5KUyTGyS5qHtHDZuJ9XD
IgOfylOBgeHjqHWxhCIB8Ee1UeTnKpMbhwJAR+XC6pbyaDBwyJuMIMZb+ieuCapz
8hM6R97cj1qJsfH9CHXQim1CRiL379tUrqOOlaE/VpiKwrjylxVQI2vLlQJACwsy
kF4JLnYF6JAalR9rZTJqdxw09QCbTnLqNCzhdpcLYxwDUV1s8fWEr4FfAAicR4n4
X9h5i0Qo7lB/y/6rywJALGE0nMQNMYgIKo6hVlvPV64Dut3HBsbhoyAKhQMiYGIr
O+1bLhQctgNdElnRWB4vqhquxpa3LIO3vp0whlKWpQ==
-----END RSA PRIVATE KEY-----
`)
var publicKey = []byte(`
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEoduylYLkfKvwMQmPovhFEIzH
aNA6aeAwBK3DxgkLEDNe0e7nnlWrUSfv3pHlPc4DRCDPZy43FmTXzuM0Isjud8/F
GsAVm+UucXC45EuCiq2VYj0NfeXveWKHgL5ZMnjTQ9wWCEmipOqbU3hTgdPy/Mj8
i9ZAiZsITamGlIMl8QIDAQAB
-----END PUBLIC KEY-----
`)
生成私钥密钥的代码如下
func main(){
GenRsaKey(1024)
}
//RSA公钥私钥产生
func GenRsaKey(bits int) error {
// 生成私钥文件
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return err
}
derStream := x509.MarshalPKCS1PrivateKey(privateKey)
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: derStream,
}
file, err := os.Create("private.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
// 生成公钥文件
publicKey := &privateKey.PublicKey
derPkix, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return err
}
block = &pem.Block{
Type: "PUBLIC KEY",
Bytes: derPkix,
}
file, err = os.Create("public.pem")
if err != nil {
return err
}
err = pem.Encode(file, block)
if err != nil {
return err
}
return nil
}
但是RSA对加密的长度有限制,因此常见的做法是RSA结合对称算法(例如AES)一起使用。即使用RSA加密AES的密钥,然后用AES算法加密数据。
9、AES加解密
AES算法又分很多模式,下面用golang来实现CBC模式和ECB模式
CBC模式可以直接使用go里面的标准库的函数实现。
package main
import (
"bytes"
"crypto/cipher"
"crypto/aes"
"encoding/base64"
"fmt"
)
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext) % blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesCBCEncrypt(origData, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
origData = PKCS7Padding(origData, blockSize)
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
return crypted, nil
}
func AesCBCDecrypt(crypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS7UnPadding(origData)
return origData, nil
}
func main() {
key := []byte("0123456789abcdef")
result, err := AesCBCEncrypt([]byte("hello world"), key)
if err != nil {
panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(result))
origData, err := AesCBCDecrypt(result, key)
if err != nil {
panic(err)
}
fmt.Println(string(origData))
}
ECB模式在go的标准库中没有现成的可用的函数,需要自己写。已经有人写了ECB的实现代码,只是还没有合进去。地址:https://codereview.appspot.com/7860047 。用golang实现的例子如下:
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
)
func main(){
key := []byte("0123456789abcdef")
dataRaw := []byte("hello world")
crypted, err := AesECBEncrypt(dataRaw, key)
if err != nil{
fmt.Printf("加密失败, err:%s\n", err)
return
}
fmt.Printf("加密后的数据:%s\n", base64.StdEncoding.EncodeToString(crypted))
//解密
result, err := AesECBDecrypt(crypted, key)
if err != nil{
fmt.Printf("解密失败, err:%s\n", err)
return
}
fmt.Printf("解密后的数据:%s\n", string(result))
}
func PKCS5Padding(ciphertext []byte, blockSize int) []byte{
padding := blockSize - len(ciphertext) % blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func PKCS5UnPadding(origData []byte) []byte{
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
func AesECBEncrypt(data, key []byte) ([]byte, error){
block, err := aes.NewCipher(key)
if err != nil{
return nil, err
}
blockSize := block.BlockSize()
origData := PKCS5Padding(data, blockSize)
ecb := NewECBEncrypter(block)
crypted := make([]byte, len(origData))
ecb.CryptBlocks(crypted, origData)
return crypted, nil
}
func AesECBDecrypt(crypted, key []byte) ([]byte, error){
block, err := aes.NewCipher(key)
if err != nil{
return nil, err
}
blockMode := NewECBDecrypter(block)
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS5UnPadding(origData)
return origData, nil
}
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
// NewECBEncrypter returns a BlockMode which encrypts in electronic code book
// mode, using the given Block.
func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
type ecbDecrypter ecb
// NewECBDecrypter returns a BlockMode which decrypts in electronic code book
// mode, using the given Block.
func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int { return x.blockSize }
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
10、定时器
NewTimer是一次性的,不重新Reset就不会继续下去,并且会死锁
例子:
func TestTimer(){
input := make(chan interface{})
// produce the messages
go func(){
for i:=0;i<5;i++{
input <- i
}
input <- "hello, world"
}()
t1 := time.NewTimer(time.Second * 5)
t2 := time.NewTimer(time.Second * 10)
for {
select {
// consume the message
case msg := <-input:
fmt.Println(msg)
case <- t1.C:
println("5s timer")
t1.Reset(time.Second * 5)
case <- t2.C:
println("10s timer")
t2.Reset(time.Second * 10)
}
}
}
ticker是连续性的,不发送stop信号使用ticker.Stop就不会停下来
func TestTicker(){
ticker := time.NewTicker(5 *time.Second)
quit := make(chan int)
var wg sync.WaitGroup
wg.Add(1)
go func(){
defer wg.Done()
fmt.Println("child goroutine start")
for {
select{
case <- ticker.C:
fmt.Println("ticker .")
case <- quit:
fmt.Println("work well .")
ticker.Stop()
return
}
}
fmt.Println("child goroutine end")
}()
go func(){
fmt.Println("others")
}()
time.Sleep(10 * time.Second)
quit <- 1
wg.Wait()
}
11、数据库的主从模式
对于大型的项目数据库一般不只是一台,而是会用到数据库集群。其中会经常用到主从模式,这个模式是指将一台机器的数据库作为Master服务器,负责写操作(增、删、改),其他的都是Slave服务器,负责读操作。Master与Slave之间会有心跳数据包(一般数据库服务会提供配置),当Master有数据写入时,Master会将数据同步到各Slave上。