go的反射有什么不同

2021年4月10日 11:09

说明

 
go的反射有什么不同, 与动态语言python有什么不同
 

go反射的不同

 
1. 首先, go是静态强类型。再怎么反射它也是静态语言,不支持动态获取类型,例如,通过字符串"struct_name",创建struct_name对象
 
2. go的反射,在于通过对象获取类型信息。例如,通过object,得到Type,然后获取Type的属性
 
3. reflect的入口是TypeOf和ValueOf。一切的前提是先有对象
 
 
 

评论(5) 阅读(836)

gorm模型定义原理借鉴分析

2021年4月10日 10:26

说明

 
python有元类概念,在定义db模型时,相当方便,极大简化代码
 
go中没有元类概念, gorm有模型定义,看看它怎么实现,能否借鉴
 

gorm原理

 
1. gorm运用了结构体标签,通过reflect获取标签内容,这是基本原理,这里不做介绍。
 
2. 关注它如何运用这些特性,借鉴使用
 
3. 直接分析源码太复杂,绕的路径太多。基于gorm模型定义最核心的代码,写一个小例子,展示gorm的用法,这也是我需要借鉴的地方
 
4. gorm所有的接口db.Create, db.Model,...最终都是调用schema.Parse
 
 
package main

import (
"fmt"
"gorm.io/gorm"
"gorm.io/gorm/schema"
"reflect"
"sync"
)

type Product struct {
gorm.Model
Code  string
Price uint
}

func main() {
product, _ := schema.Parse(&Product{}, &sync.Map{}, schema.NamingStrategy{})
fmt.Println(product)
fmt.Println(reflect.TypeOf(product))
}

//打印
//main.Product
//*schema.Schema
 

总结

 
1. 自定义的model,最终都被gorm转为Schema类型
 
2. model仅仅提供字段、标签信息
 
3. gorm并且实现了Schema的String() string方法,让打印看起来是Product
 
4. gorm的模型定义,比较encoding/json简单,比较适合借鉴使用
 
 
 

Tags: gorm
评论(1509) 阅读(8540)

nsq双机集群部署

2021年4月02日 13:27

问题

 
双机nsq如果部署集群,如何保证高可用性
 
1. 单节点nsqlookup故障?
 
2. 单节点nsq故障?
 
3. 消息丢失?
 

环境说明

 
两台机器
 
机器A 192.168.120.1
 
机器B 192.168.120.101
 

部署过程

 
部署方法
 
1. 机器A部署一套nsqlookup+nsqd
 
2. 机器B部署一套nsqlookup+nsqd
 
3. 生产者将消息同时写入两个nsqd
 
3. 消费者监听两个nsqlookup
 
结构图
 
 
机器A
 
nsqlookupd -broadcast-address 192.168.120.1
nsqd -lookupd-tcp-address=192.168.120.1:4160 -lookupd-tcp-address=192.168.120.101:4160 -broadcast-address 192.168.120.1
 
机器B
 
nsqlookupd -broadcast-address 192.168.120.101
nsqd -lookupd-tcp-address=192.168.120.1:4160 -lookupd-tcp-address=192.168.120.101:4160 -broadcast-address 192.168.120.101
 

pynsq测试脚本

 
生产者
 
import nsq
import tornado.ioloop
import time

def pub_message():
    writer.pub('test', str(time.strftime('%H:%M:%S')).encode("utf-8"), finish_pub)

def finish_pub(conn, data):
    print(data)

# 写入两个nsq好处: 1.防止nsqd单点故障  2.防止消息丢失
writer = nsq.Writer(['192.168.120.101:4150', '192.168.120.1:4150'])

tornado.ioloop.PeriodicCallback(pub_message, 1000).start()
nsq.run()
 
消费者
 
import nsq


def handler(message):
    print(message, message.id, message.timestamp, message.attempts, message.body)
    return True

# 防止nsqlookup故障
r = nsq.Reader(message_handler=handler,
               lookupd_http_addresses=['http://192.168.120.1:4161', 'http://192.168.120.101:4161'],
               topic='test', channel='abc', lookupd_poll_interval=15)

nsq.run()
 

总结

 
只要有一个nsqlookup和一个nsqd存活,系统就不会挂
 
 
 

Tags: nsq
评论(5) 阅读(1469)

nsq.reader错误connection closed

2021年4月02日 11:13

描述
 
学习官网例子时,会碰到的一个小错误. 过程描述:
 
1. 按照nsq官网,搭建一个nsq小集群
 
2.使用python客户端pynsq编写测试客户端
 
3. 如果上面过程在一台机子上完成,不会有问题
 
4. 如果客户端与nsq不在一台机子上,会出现下面错误
 

错误内容

 
WARNING:nsq.reader:[localhost.localdomain:4150:test:abc] connection closed
 
 

python消费客户端

 
import nsq

def handler(message):
    print(message, message.body)
    return True


r = nsq.Reader(message_handler=handler,
               lookupd_http_addresses=['http://192.168.120.101:4161'],
               topic='test', channel='abc', lookupd_poll_interval=15)

nsq.run()
 

python生产客户端

import nsq
import tornado.ioloop
import time

def pub_message():
    writer.pub('test', str(time.strftime('%H:%M:%S')).encode("utf-8"), finish_pub)

def finish_pub(conn, data):
    print(data)

writer = nsq.Writer(['127.0.0.1:4150'])

tornado.ioloop.PeriodicCallback(pub_message, 1000).start()
nsq.run()
 

正确方法

 
启动nsqd时,指定nsqd的广播ip,也就是其它机子可以访问的ip,而非默认的localhost。例如:"-broadcast-address 192.168.120.101"
 
 

Tags: nsq
评论(0) 阅读(739)

golang的import原理

2021年3月28日 12:20

描述

 
import是个很重要的东西,golang是怎么import的
 
1. import查找包的顺序?
 
2. import的是什么,包还是路径?
 
3. 自己项目中怎么import
 

golang的包管理历史

 
第一阶段 早期golang,import是直接查找$GOPATH/scr,$GOROOT/scr目录
 
第二阶段 v1.5开始采用vendor模式,每个项目有一个vendor目录,存放依赖包
 
第三阶段 v1.12开始增加了go mod用于管理依赖包
 
部分学习资料比较早,介绍的还是第一、二阶段,从我们直接学习go mod就可以
 

go mod的import顺序

 
1. 在当前项目目录查找module。找到module后,用module_name加相对路径导入。import "module_name/路径xxx..."
 
2. 未找到,则逐级查找上级目录的go.mod
 
3. 未找到,则再找$GOPATH,$GOROOT
 

module和package是怎么回事?

 
go只有package,并没有module的概念, go mod才引入,
 
module可以理解为python的包或者java的jar包。package的集合.
 

import的是什么?

 
import的是目录, 由于一个目录只能有一个package, 实际也是import包
 
例如 import "gin-template/db/api"
 
1. 如果目录名与package名一致。 api.xxx方式使用包
 
2.如果目录名与pakcage名不一致(目录名api,包名abc)。 abc.xxx方式使用
 
3.总结:import的是目录下的package。引用方式是, 包名.xxx
 
 

自己项目中怎么引用

 
import module_name/包的目录路径
 
 

与其它语言比较

 
python中import的是py文件/py文件中的属性
 
java中import的是java文件中的class
 
golang中import的是目录下的package
 
 

评论(9) 阅读(934)

eventlet如何绿化pyserial最好

2021年3月24日 19:19

 

问题

 
pyserial访问windows中的串口,如何绿化?不阻塞协程? 效率最高? 绿化毫无疑问要借用tpool.execute.
 
代码该如何实现才最佳呢?
 

方法一: 直接recv

 
直接recv,会阻塞协程
 
缺点: 协程被阻塞,程序不可用
 

方法二: tpool+in_waiting判断

 
import serial
from eventlet import tpool
class SerialSocket(serial.Serial):
    def recv(self):
        # 读完
        if self.in_waiting:
            return tpool.execute(self.read, self.in_waiting)
        return data

def listen_message():
    ss = SerialSocket("com10")
    buffer = b''
    while True:
        data = ss.recv()
        if data is not None:
            buffer +=data
 
缺点: cpu利用率较高
 

方法三: tpool+in_waiting判断+sleep

 
import serial
from eventlet import tpool, sleep
class SerialSocket(serial.Serial):
    def recv(self):
        # 读完
        if self.in_waiting:
            return tpool.execute(self.read, self.in_waiting)
        return data

def listen_message():
    ss = SerialSocket("com10")
    buffer = b''
    while True:
        data = ss.recv()
        if data is not None:
            buffer +=data
        else:
            sleep(0.01)
 
缺点: 满足大部分需求, 对时间要求比较敏感的程序,还是不行,多了0.01秒
 

方法四(完美):tpool+in_waiting判断+read(1)阻塞

 
利用serial的read()阻塞特性,加上tpool线程,最终线程阻塞转换成协程阻塞
 
import serial
from eventlet import tpool

class SerialSocket(serial.Serial):
    def recv(self):
        # 等待
        data = tpool.execute(self.read, 1)
        # 读完
        while self.in_waiting:
            data += tpool.execute(self.read, self.in_waiting)
        return data

def listen_message():
    ss = SerialSocket("com10")
    buffer = b''
    while True:
        g = evenlet.spawn(ss.recv)
        data = g.wait()
        buffer +=data
 
 

Tags: eventlet pyserial
评论(0) 阅读(1119)

eventlet.monkey_patch是否影响threading

2021年3月24日 13:42

说明

 
evenlet.monkey_patch()默认会绿化thread,并未看到绿化threading,为什么实际中threading被绿化了,如何证明? 测试代码如下
 

修改evenlet,加上标记语句

 
修改eventlet.green.thread.py的start_new_thread方法,加一条打印语句
 
def start_new_thread(function, args=(), kwargs=None):
    ...
    print("hello: ", locals())
    g = greenthread.spawn_n(__thread_body, function, args, kwargs)
    return get_ident(g)
 
 

测试代码

 
# -*- coding:utf-8 -*-
# from eventlet import patcher
# original_threading = patcher.original('threading')
import threading as original_threading
import time
import eventlet
eventlet.monkey_patch()


def thread1_run():
    while True:
        print("thread1:", time.time())
        time.sleep(1)


if __name__ == "__main__":
    thread1 = original_threading.Thread(target=thread1_run, name="thread1", daemon=True)
    print(type(thread1))
    thread1.start()

    e = eventlet.Event()
    e.wait()
 

 

Tags: eventlet
评论(39) 阅读(1694)

python自定义windowsr日志支持文件分割

2021年1月20日 13:48

描述

 
python自带的RotatingFileHandler,在windows中运行, 分割文件rename时会出问题。因为windows不支持rename正在使用的文件
 

解决办法

 
自定义handler, 按大小和日期切割文件
 
原理: handler写文件时调用emit, 其中shouldRollover判断是否要分割, doRollover进行分割. 重写这个两个函数就可以
 
class BaseRotatingHandler(logging.FileHandler):

    def emit(self, record):
        """
        Emit a record.

        Output the record to the file, catering for rollover as described
        in doRollover().
        """
        try:
            if self.shouldRollover(record):
                self.doRollover()
            logging.FileHandler.emit(self, record)
        except Exception:
            self.handleError(record)
 

示例

 
import os
import time
import datetime
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path


class AutumnRotatingFileHandler(RotatingFileHandler):
    """
    日志+大小+支持windows
    """

    def __init__(self, filename, mode='a', maxBytes=0, backupCount=99, encoding=None, delay=False, backupDayCount=30):
        filename = str(filename)
        self.date_format = "%Y%m%d"
        self.create_date = self._now_date()
        self.backupCount = backupCount

        # 保存原始文件名
        self.filename = str(Path(filename).absolute())
        self.backupDayCount = backupDayCount

        RotatingFileHandler.__init__(self, self.last_file_name(filename), mode=mode, maxBytes=maxBytes, backupCount=backupCount, encoding=encoding, delay=delay)

    def _now_date(self):
        return time.strftime(self.date_format, time.localtime())

    def doRollover(self):
        """
        Do a rollover, as described in __init__().
        """
        # 关闭当前文件
        if self.stream:
            self.stream.close()
            self.stream = None

        # 生成最新文件名
        suffix = "."+ self._now_date()
        self.baseFilename = str(self.filename) + suffix
        if self.backupCount > 0:
            for i in range(0, self.backupCount):
                number_suffix = "{:0>2d}".format(i)
                sfn = ".".join([self.baseFilename, number_suffix])
                if os.path.exists(sfn):
                    continue
                else:
                    break
            number_suffix = "{:0>2d}".format(i)
            self.baseFilename = ".".join([self.baseFilename, number_suffix])

        # 删除过期文件
        self.deleteExpiredFiles()

        # 打开
        self.stream = self._open()
        self.create_date = self._now_date()

    def shouldRollover(self, record):
        """
        Determine if rollover should occur.

        Basically, see if the supplied record would cause the file to exceed
        the size limit we have.
        """
        # 文件分割条件1: 日期变化
        if self._now_date() != self.create_date:
            return 1

        if self.stream is None:                 # delay was set...
            self.stream = self._open()

        # 文件分割条件1: 文件大小超过限制
        if self.maxBytes > 0:                   # are we rolling over?
            msg = "%s\n" % self.format(record)
            self.stream.seek(0, 2)  #due to non-posix-compliant Windows feature
            if self.stream.tell() + len(msg) >= self.maxBytes:
                return 1
        return 0

    def deleteExpiredFiles(self):
        """
        删除过期文件
        """
        dead_datetime = datetime.datetime.now() - datetime.timedelta(days=self.backupDayCount)

        filenames = []
        for i in range(1, 3):
            date = dead_datetime - datetime.timedelta(days=1)
            date_str = date.strftime(self.date_format)

            base_name = "%s.%s" % (self.filename, date_str)
            filenames.append(base_name)

            for j in range(1, self.backupCount):
                sfn = "%s.%d" % (base_name, j)
                filenames.append(sfn)

        # delete file
        for filename in filenames:
            if os.path.exists(filename):
                try:
                    os.remove(sfn)
                except:
                    pass

    def last_file_name(self, filename):
        """
        文件名
        """
        suffix = "."+self._now_date()
        base_filename = str(filename) + suffix
        if self.backupCount > 0:
            for i in reversed(range(0, self.backupCount)):
                number_suffix = "{:0>2d}".format(i)
                sfn = ".".join([base_filename, number_suffix])
                # 最近存在的文件
                if os.path.exists(sfn):
                    break
                else:
                    continue
            number_suffix = "{:0>2d}".format(i)
            base_filename = ".".join([base_filename, number_suffix])
        return base_filename
 

评论(3) 阅读(1182)

eventlet模块中select出现ValueError错误

2021年1月02日 10:34

 

问题说明

 
如果socket句柄是-1, select.select()在判断句柄状态时会报错 ValueError: file descriptor cannot be a negative integer (-1)
 
而eventlet的select模块没有处理这种异常
 

出现环境

 
windows、python3.8、eventlet 0.26 在使用eventlet开发socket程序时, 如果客户端强制中断连接,会出现如下错误
 

错误描述

 
exception: [WinError 10054] 远程主机强迫关闭了一个现有的连接。
...
ValueError: file descriptor cannot be a negative integer (-1)
 
### 解决办法
 
修改文件eventlet\hubs\selects.py,然后把eventlet重新打包
 
    def wait(self, seconds=None):
        ....
        try:
            r, w, er = select.select(reader_fds, writer_fds, all_fds, seconds)
        # 捕捉异常并移除句柄-1
        except ValueError as e:
            self.remove_descriptor(-1)
            return
        except select.error as e:
            if support.get_errno(e) == errno.EINTR:
                return
            elif support.get_errno(e) in BAD_SOCK:
                self._remove_bad_fds()
                return
            else:
                raise
 

总结

 
1.很奇怪eventlet怎么没有修复这种兼容性错误?
 
2.模拟错误的方法  select.select([-1], [], [], 0)
 
3. socket中断句柄怎么变成了-1,非法的句柄-1又是如何进入eventlet的?
 
 

Tags: eventlet
评论(1) 阅读(4496)

eventlet绿化和patch原理

2020年11月21日 12:09

说明

 
eventlet是一个必备工具,经常用,绿化原理有点忘记了,重新复习一遍.
 
 

三个主要问题

 
1. 绿化的原理是什么?
 
2. 绿化怎么管理?
 
3. 绿化怎么引入?
 

绿化原理

 
利用select/epolls/kqueue等操作系统提供的非阻塞操作,将阻塞改为非阻塞.
 

引用管理

 
eventlet在import之后,将模块中的属性绿化.
 
用一小段代码来查看看
import sys
import eventlet
# eventlet.monkey_patch()

httplib2 = eventlet.import_patched('httplib2')
print(httplib2)
print(httplib2.socket)

print("================")
for k,v in sys.modules.items():
    if "socket" in k:
        print(k, v)


# 打印内容
<module 'httplib2' from 'D:\\workspace\\venv\\xxx\\lib\\site-packages\\httplib2\\__init__.py'>
<module 'eventlet.green.socket' from 'D:\\workspace\\venv\\xxx\\lib\\site-packages\\eventlet\\green\\socket.py'>
================
_socket <module '_socket' from 'c:\\python\\python36\\DLLs\\_socket.pyd'>
socket <module 'socket' from 'c:\\python\\python36\\lib\\socket.py'>
__original_module_socket <module 'socket' from 'c:\\python\\python36\\lib\\socket.py'>

# 说明了什么?
绿化只是替换httplib2模块中的引用属性socket。并未改变sys.modules中的属性
 

引入绿化方法一:直接import

 
from eventlet.green import socket
 

引入绿化方法二:eventlet.import_patch

 
1.好处: 能绿化模块内部的系统模块
2.只能绿化os, select, socket, thread, time, psycopy, MySQLdb, bultines, subprocess
3.如果是import threading,不会自动绿化, 需要from eventlet.green import threading
 
import eventlet
httplib2 = eventlet.import_patched('httplib2')
print(httplib2)
print(httplib2.socket)

# 打印
<module 'httplib2' from 'D:\\workspace\\venv\\autumn-secs\\lib\\site-packages\\httplib2\\__init__.py'>
<module 'eventlet.green.socket' from 'D:\\workspace\\venv\\autumn-secs\\lib\\site-packages\\eventlet\\green\\socket.py'>
 

引入绿化方法三:eventlet.monkey_patch

 
1.与import_patch不同,会直接修改sys.modules
2.但是也只能绿化os,select等等几个模块,其它模块需要用直接引用绿化
 
 
 

Tags: eventlet
评论(16) 阅读(2040)