Golang RabbitMQ MQ
2021-06-18
- 2014/07/11, RabbitMQ & AMQP
- 2015/02/12, Python RabbitMQ
- 2015/04/01, RabbitMQ 管理命令
- 2015/05/04, pika 01: 基础
- 2015/05/04, pika 02: BlockingConnection
- 2015/05/05, pika 03: SelectConnection
- 2015/05/06, pika 04: TornadoConnection
- 2016/02/12, Java RabbitMQ
- 2018/01/31, RabbitMQ 集群
- 2018/04/01, 面试题:RabbitMQ 相关
- 2018/10/02, pika: AsyncioConnection
- 2018/10/06, 使用 RabbitMQ 遇到的一个小问题
- 2019/01/12, 延迟队列
- 2020/10/03, 常见的消息队列
- 2021/06/18, Golang RabbitMQ
RabbitMQ 是啥就不说了,怎么安装部署也不说了,就记录一下 RabbitMQ 在 Golang 开发中的应用。
说明:采用 github.com/streadway/amqp
库。
func (ch *Channel) Publish(exchange, key string, mandatory, immediate bool, msg Publishing) error
func (ch *Channel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error)
生产者
生产者基本流程
生产者:连接
amqp.Dial
-> amqp.Connection
amqp.Connection.Channel
-> amqp.Channel
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatalf("Failed to connect to RabbitMQ: %s", err)
}
ch, err := conn.Channel()
if err != nil {
log.Fatalf("Failed to open a channel: %s", err)
}
生产者:配置(可选)
事先把 MQ 配好就行,但从稳妥起见,还是在连接时加上比较好。
amqp.Channel.QueueDeclare
amqp.Channel.ExchangeDeclare
amqp.Channel.QueueBind
q, err := ch.QueueDeclare(
"hello", // 队列名称
true, // 持久化
false, // 自动删除
false, // 独占
false, // 等待服务器回应
nil, // 额外参数
)
if err != nil {
log.Fatalf("Failed to declare a queue: %s", err)
}
生产者:发送
amqp.Channel.Publish
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte("hello world"),
})
if err != nil {
log.Fatalf("Failed to publish a message: %s", err)
}
生产者:收尾
amqp.Connection.Close
amqp.Channel.Close
消费者
消费者基本流程
和生产者基本一致。只是调用的的是 chan.Consume
而不是 chan.Publish
。
然后就是配置阶段,消费者只用关心队列在不在。
参考资料与拓展阅读
UUID
2021-06-17
Universally Unique Identifier 通用唯一识别码
是 ISO/IEC 标准,也定义在 IETF 的 RFC4122 中。
128 位,也就是 16 字节,通常使用 32 位 16 进制数字,以 8-4-4-4-12
的形式表示,例如:d09abf7e-3e39-11ec-9dbc-b1755772e461
NanoID UUID
2021-06-17
最近听说一种新的 ID 生成器,叫做 NanoID,很多地方那个都把它拿来和 UUID 做对比。
我的结论:NanoID 没啥了不起,就是个随机字符串,取代不了 UUID。
开发者 微服务
2021-06-14
我反对的微服务,不是这种思想,而是大家炒热的各种技术框架。
Python PythonSourceCode
2021-06-09
开始看 Python 源码,啃一啃这块硬骨头,在学习 Python 底层的同时,也可以提升自己的 C 语言能力。
Golang Redis
2021-06-05
package main
import (
"log"
"time"
"github.com/go-redis/redis"
)
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
var err error
// 使用 Get/Set/Del =====================================
err = client.Set("key", "value", 0).Err()
if err != nil {
panic(err)
}
val, err := client.Get("key").Result()
if err != nil {
panic(err)
}
log.Println("key", val)
err = client.Del("key").Err()
if err != nil {
panic(err)
}
// 使用 Pipeline ========================================
pipeline := client.Pipeline()
pipeline.Set("key1", "value1", time.Minute*5)
pipeline.Set("key2", "value2", time.Minute*5)
pipeline.Set("key3", "value3", time.Minute*5)
_, err = pipeline.Exec()
if err != nil {
panic(err)
}
pipeline.Get("key1")
pipeline.Get("key2")
pipeline.Get("key3")
vals, err := pipeline.Exec()
if err != nil {
panic(err)
}
val1, _ := vals[0].(*redis.StringCmd).Result()
val2, _ := vals[1].(*redis.StringCmd).Result()
val3, _ := vals[2].(*redis.StringCmd).Result()
log.Println("key1", val1)
log.Println("key2", val2)
log.Println("key3", val3)
pipeline.Del("key1", "key2", "key3")
_, err = pipeline.Exec()
if err != nil {
panic(err)
}
}
package main
import (
"log"
"github.com/go-redis/redis"
)
func main() {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
})
log.Println(client)
var err error
var items []string
// List ===============================================
{
err = client.RPush("mylist", "item1", "item2", "item3").Err()
if err != nil {
panic(err)
}
listLen, err := client.LLen("mylist").Result()
if err != nil {
panic(err)
}
log.Println("Length of mylist:", listLen)
items, err = client.LRange("mylist", 0, -1).Result()
if err != nil {
panic(err)
}
log.Println("===== Items in mylist:", items)
}
// Hash ===============================================
{
err = client.HSet("myhash", "field1", "value1").Err()
if err != nil {
panic(err)
}
value1, err := client.HGet("myhash", "field1").Result()
if err != nil {
panic(err)
}
log.Println("Value of field1:", value1)
allFields, err := client.HGetAll("myhash").Result()
if err != nil {
panic(err)
}
log.Println("===== All fields in myhash:", allFields)
}
// Set ================================================
{
err = client.SAdd("myset", "item1", "item2", "item3").Err()
if err != nil {
panic(err)
}
setLen, err := client.SCard("myset").Result()
if err != nil {
panic(err)
}
log.Println("Length of myset:", setLen)
items, err = client.SMembers("myset").Result()
if err != nil {
panic(err)
}
log.Println("===== Items in myset:", items)
}
// ZSet ===============================================
{
err = client.ZAdd("myzset", redis.Z{Score: 1.0, Member: "one"}, redis.Z{Score: 2.0, Member: "two"}).Err()
if err != nil {
panic(err)
}
setLen, err := client.ZCard("myzset").Result()
if err != nil {
panic(err)
}
log.Println("Length of myzset:", setLen)
items, err = client.ZRange("myzset", 0, -1).Result()
if err != nil {
panic(err)
}
log.Println("===== Items in myzset:", items)
}
}
哨兵
这样理论上来说,肯定会有性能损耗,毕竟增加了和哨兵的通信。
具体能差多少,还得实验。
package main
import (
"fmt"
"github.com/go-redis/redis/v8"
)
func main() {
failoverClient := redis.NewFailoverClient(&redis.FailoverOptions{
SentinelAddrs: []string{"sentinel1:26379", "sentinel2:26379", "sentinel3:26379"},
MasterName: "mymaster",
})
pong, err := failoverClient.Ping().Result()
if err != nil {
panic(err)
}
fmt.Println(pong)
}
集群
package main
import (
"fmt"
"github.com/go-redis/redis/v8"
)
func main() {
clusterClient := redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"redis1:6379", "redis2:6379", "redis3:6379"},
})
pong, err := clusterClient.Ping().Result()
if err != nil {
panic(err)
}
fmt.Println(pong)
}
Python OpenCV 霍夫变换 图像处理
2021-06-04
原图(内容成 15° 娇):

原理
里面设计很多知识点,我一个都不知道,涉及图像处理,数学计算,信号变换等多个方面。
但我查到的资料似乎和下面两个概念相关。
如果看到这篇文章的人了解其中原理能够跟我讲讲 (ninedoors#126),万分感激!
傅立叶变换, Fourier Transform, FT
傅里叶变换是一种线性积分变换,用于信号在时域(或空域)和频域之间的变换,在物理学和工程学中有许多应用。
我的理解是用多个三角函数来表示一个变化曲线。
霍夫变换, Hough Transform
霍夫变换是一种特征提取,被广泛应用在图像分析、电脑视觉以及数位影像处理。霍夫变换是用来辨别找出物件中的特征,例如:线条。他的算法流程大致如下,给定一个物件、要辨别的形状的种类,算法会在参数空间中执行投票来决定物体的形状,而这是由累加空间(accumulator space)里的局部最大值来决定。
维基百科说的很清楚了,用来分析图片特征,识别出其中的形状。
步骤
- 原图(灰度处理 + 尺寸裁剪),减少无关数据,减少计算量
- 傅里叶变换,得到频域图像
- 利用霍夫变换做直线检测
- 计算倾斜角度
- 旋转校正
代码
import logging
import math
import cv2
import numpy as np
from numpy.lib.function_base import average
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s [%(name)s:%(funcName)s#%(lineno)s] %(message)s')
LOG = logging.getLogger(__name__)
# 1、灰度化读取文件
filepath = '/tmp/rotated.png'
img = cv2.imread(filepath, 0)
if img is None:
LOG.error('image not exists!')
exit()
# 2、图像延扩
h, w = img.shape[:2]
new_h = cv2.getOptimalDFTSize(h) # 傅里叶最优尺寸
new_w = cv2.getOptimalDFTSize(w)
right = new_w - w
bottom = new_h - h
# 边界扩充 cv2.copyMakeBorder(src, top, bottom, left, right, borderType, dst=None)
# BORDER_CONSTANT 常量,增加的变量通通为 value
# BORDER_REFLICATE 用边界的颜色填充
# BORDER_REFLECT 镜像
# BORDER_REFLECT_101 倒映
# BORDER_WRAP 没有规律
nimg = cv2.copyMakeBorder(img, 0, bottom, 0, right, borderType=cv2.BORDER_CONSTANT, value=0)
cv2.imshow('new image', nimg)
# 3、傅里叶变换,获到频域图像
f = np.fft.fft2(nimg)
fshift = np.fft.fftshift(f)
magnitude = np.log(np.abs(fshift))
LOG.info(magnitude)
# 二值化
magnitude_uint = magnitude.astype(np.uint8)
ret, thresh = cv2.threshold(magnitude_uint, 11, 255, cv2.THRESH_BINARY)
LOG.info(ret)
cv2.imshow('thresh', thresh)
LOG.info(thresh.dtype)
# 4、霍夫直线变换
lines = cv2.HoughLinesP(thresh, 2, np.pi/180, 30, minLineLength=40, maxLineGap=100)
LOG.info('line number: %d', len(lines))
# 创建一个新图像,标注直线
lineimg = np.ones(nimg.shape, dtype=np.uint8)
lineimg = lineimg * 255
for index, line in enumerate(lines):
LOG.info('draw line#%d: %s', index, line)
x1, y1, x2, y2 = line[0]
cv2.line(lineimg, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imshow('line image', lineimg)
# 5、计算倾斜角度
piThresh = np.pi / 180
pi2 = np.pi / 2
angles = []
for line in lines:
LOG.info('line#%d: %s <===============', index, line)
x1, y1, x2, y2 = line[0]
if x2 - x1 == 0:
LOG.debug('skip 1')
continue
theta = (y2 - y1) / (x2 - x1)
LOG.debug('theta: %r', theta)
if abs(theta) < piThresh or abs(theta - pi2) < piThresh:
LOG.debug('skip 2: %r', theta)
continue
angle = math.atan(theta)
LOG.info('angle 1: %r', angle)
angle = angle * (180 / np.pi)
LOG.info('angle 2: %r', angle)
angle = (angle - 90)/(w/h)
LOG.info('angle 3: %r', angle)
angles.append(angle)
if not angles:
LOG.info('图片挺正的了,别折腾!')
else:
# from numpy.lib.function_base import average
# angle = average(angles)
LOG.info(' 方差: %r', np.array(angles).var())
LOG.info('标准差: %r', np.array(angles).std())
# angle = np.mean(angles)
import statistics
# LOG.debug(statistics.multimode(angles))
# angle = statistics.mode(angles)
# statistics.StatisticsError: geometric mean requires a non-empty dataset containing positive numbers
# statistics.StatisticsError: harmonic mean does not support negative values
angle = statistics.median(angles)
if 180 > angle > 90:
angle = 180 - angle
elif -180 < angle < -90:
angle = 180 + angle
LOG.info('==> %r, %r', angles, angle)
# 6、旋转
center = (w//2, h//2)
M = cv2.getRotationMatrix2D(center, angle, 1.0)
LOG.debug('=========== RotationMatrix2D ===========')
for i in M:
LOG.debug(i)
LOG.debug('^======================================^')
rotated = cv2.warpAffine(img, M, (w, h), flags=cv2.INTER_CUBIC, borderMode=cv2.BORDER_REPLICATE)
cv2.imshow('rotated', rotated)
cv2.waitKey(0)
cv2.destroyAllWindows()
参考资料与拓展阅读
Golang DB MySQL
2021-06-04
测试表:
CREATE TABLE `users` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`password` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`nickname` VARCHAR(50) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`email` VARCHAR(100) NOT NULL DEFAULT '' COLLATE 'utf8mb4_unicode_ci',
`birthday` VARCHAR(10) NOT NULL DEFAULT '0000-00-00' COLLATE 'utf8mb4_unicode_ci',
`age` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
`level` TINYINT(3) NOT NULL DEFAULT '0',
`disabled` TINYINT(1) NOT NULL DEFAULT '0',
`created_at` DATETIME NOT NULL DEFAULT current_timestamp(),
`updated_at` DATETIME NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username` (`username`) USING BTREE
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB;
连接
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB //全局变量client
func initMySQL() (err error) {
dsn := "root:123456@tcp(127.0.0.1:3306)/test"
db, err = sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
err = db.Ping() //检测是否连接成功
if err != nil {
return
}
db.SetMaxOpenConns(200) //最大连接数
db.SetMaxIdleConns(10) //连接池里最大空闲连接数。必须要比maxOpenConns小
db.SetConnMaxLifetime(time.Second * 10) //最大存活保持时间
db.SetConnMaxIdleTime(time.Second * 10) //最大空闲保持时间
return
}
func main() {
if err := initMySQL(); err != nil {
fmt.Printf("connect to db failed,err:%v\n", err)
} else {
fmt.Println("connect to db success")
}
sqlStr := "SELECT id, name FROM sys_user WHERE id=?"
var u user
//非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放
err := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.name)
if err != nil {
fmt.Printf("scan failed, err: %v\n", err)
return
}
fmt.Printf("id:%d,name:%s,age:%d\n", u.id, u.name)
defer db.Close()
}
//user结构体
type user struct {
id int
name string
}
Insert 增
Update 改
Delete 删
Select 查
Golang
2021-06-02
不像我们常见的一些语言,实现接口需要显式声明,比如 PHP:
interface Animal {
public function move();
public function makeSound();
}
class Dog implements Animal {
public function move() {
echo "Dog is moving\n";
}
public function makeSound() {
echo "Woof\n";
}
}
Go 不需要声明,说到不如做到,实现了所有接口方法就行了。
PS:我还是觉得写出来比较好一些,阅读代码的时候更加一目了然,也没有什么副作用。
示例
package main
import "fmt"
type Animal interface {
move() string
makeSound() string
}
type Dog struct {
name string
}
func (d Dog) move() string {
return fmt.Sprintf("%s is running", d.name)
}
func (d Dog) makeSound() string {
return "woof"
}
func main() {
dog := Dog{name: "Fido"}
// 判断接口实现关系
_, ok := interface{}(dog).(Animal)
if ok {
fmt.Printf("%T is Animal\n", dog)
} else {
fmt.Printf("%T is NOT Animal\n", dog)
}
fmt.Println(dog.move())
fmt.Println(dog.makeSound())
}
interface{}
interface{}
says nothing.
Go 谚语第六条是什么意思?
我的理解:
interface{}
可以表示任意类型,有时使用起来会很方便。
但是必须注意这意味着对类型没有任何约束,无论从程序的严谨度还是代码可读性上讲,都是有害的。
如何可以的话,编码中应当尽量采用范围小一些的接口,就比如到处可见的 io.Writer
接口,它有各类实现:
bytes.Buffer
bufio.Writer
os.File
compress/gzip.Writer
crypto/cipher.StreamWriter
encoding/csv.Writer
net/http.ResponseWriter
archive/zip.Writer
image/png.Encoder
Golang JSON
2021-06-02
Golang 有一个 encoding/json
标准库,在多数情况下已经够用了。
注意:因为 Golang 是一种静态类型的语言,所以解析 JSON 字符串必须了解里面的数据结构。
如果是用惯了 JS、Python、PHP 等动态语言的话,到这里会有些郁闷,怎么解析一个 JSON 还这么复杂。