Gin、GORM 中责任链模式的使用

Gin、GORM 中责任链模式的使用

责任链模式在 GoF 的《设计模式》一书中是这样定义的

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

解耦请求的发送和接收,让多个接收对象都有机会处理这个请求。请求在多个串联的对象中传递,直到有对象能处理它。看下面这个例子

package main

import "fmt"

type HandlerFunc func(*Request) bool

type Request struct {
    url      string
    handlers []HandlerFunc
}

func (r *Request) Use(handlerFunc HandlerFunc) {
    r.handlers = append(r.handlers, handlerFunc)
}

func (r *Request) Run() {
    for _, handler := range r.handlers {
        if handler(r) {
            break
        }
    }
}

// 测试
// 输出 2 
func main() {
    r := &Request{}
    r.Use(func(r *Request) bool {
        return false
    })
    r.Use(func(r *Request) bool {
        fmt.Print(2, " ")
        return true
    })
    r.Use(func(r *Request) bool {
        fmt.Print(3, " ")
        return true
    })
    r.Run()
}

例子中定义了一个请求 Request,请求可以被多个 HandlerFunc 处理,如果某个 HandlerFunc 处理成功则结束处理,其中 HandlerFunc 可以通过 Request.Use() 方法进行添加。

阅读全文

Go 函数式编程

Go 函数式编程

函数式编程的一个重要特性是「函数是第一等公民」,本文以 Go 为例解释这个概念,之后介绍高阶函数、匿名函数、闭包等概念,最后介绍一些标准库和项目中的实践例子。

函数是第一等公民

先看一个普通变量赋值的例子

v := 100
fmt.Printf("value %v type %T\n", v, v)

输出

value 100 type int

如果我们将变量改为函数呢,比如

fn := func(x int) int { return x * x }
fmt.Printf("value %v type %T\n", fn, fn)

输出

value 0x10a8da0 type func(int) int

这里说明函数也可以赋值给变量。

所谓「函数是第一等公民」,也就是函数与其他数据类型一样,处于平等地位,可以

  • 赋值给同类型变量
  • 作为入参传递给函数
  • 作为函数的返回结果

阅读全文

聊聊 Python 中的依赖管理工具

聊聊 Python 中的依赖管理工具

相信初学 Python 的伙伴都碰到过这个麻烦事,想装个环境,在搜索引擎上一搜,蹦出来不少让人迷惑的字眼,比如 pip、virtualenv、virtualenvwrapper、venv、pyenv 等。某篇教程可能用的是这个工具,另一篇教程又可能是那个工具,还有的教程可能几个工具都在用。看了几篇后,整个人都懵了,这些工具各自都是干嘛的啊?就算最后环境弄好了,估计心里还在犯嘀咕,这些有什么区别和联系呢?好了,本篇文章就带你捋捋常见的一些包、环境管理工具,让你对这些工具有个相对清晰的认识。

先说下文章的思路,我会以碰到问题、解决问题的方式,引入几个典型工具的简单使用,从而理解它们的定位,然后根据定位梳理认识下现在比较流行的工具。这样一来,每个工具干什么的,该选用哪个工具,自己心里应该也清楚了。

Pyenv

现在的 Unix/Linux 系统基本上都装有 Python 解释器,不过都是 Python 2 版本的,这个版本已经在今年(2020 年)1 月 1 日宣布停止维护了,所以我们学习的话基本上都会选择 Python 3。这就带来一个问题,Python 3 怎么安装?

阅读全文

Scrapy 源码阅读(二):看源码

Scrapy 源码阅读(二):看源码

(接上文 Scrapy 源码阅读(一):Twisted 基础和 Scrapy 数据流

3 看源码

注意:本节内容基于 Scrapy 1.7

不知道大家有没有这样的体会?平时写业务代码的时候,一般是不会去关注各个模块怎么关联起来的。我要写控制器、写服务层、写模型层等,就按部就班的找到相应目录下的文件开始写了。当碰到问题在网上找不到好的解决方法或者闲时想学习一些设计方法的时候,我们就会去关注某些模块的底层实现以及它们的关系。这里我会用一个类似的方式,先给大家了解下 Scrapy 中的主要几个类的关系,在熟悉了这些类后,再根据需要去关注特定的类之间是如何关联的。

阅读全文

Scrapy 源码阅读(一):Twisted 基础和 Scrapy 数据流

Scrapy 源码阅读(一):Twisted 基础和 Scrapy 数据流

本文记录下自己看 Scrapy 源码的一点经验,没有涉及框架的方方面面,更多的是关注数据的流转以及代码的组织。如果你想深入框架的某个细节,那么这篇文字可以给你一个切入点。

阅读源码常规的步骤

  1. 准备好必须的基础知识
  2. 熟悉框架有哪些功能
  3. 看源码,了解代码是怎么组织的,最后按需关注特定部分的实现细节

我们一步一步来。

1 Twisted

Scrapy 基于 Twisted,所以除了要有一定的 Python 基础外,还要对 Twisted 有一些了解。

你可以将 Twisted 和 Asyncio 类比,它们都是为了支持协程而诞生的,只是前者比后者出现的更早。这 2 个技术实现的核心都是事件循环,当程序执行到某个耗时的 IO 操作时,程序的执行权限会被退回给事件循环,事件循环会检测其它准备就绪的协程,然后将执行权限交给它,当之前的协程 IO 操作完毕后,事件循环会将执行权限转给它,继续后面的操作。这样,就能在单线程内实现并发,作用和多线程类似,只是比多线程更轻量。事件循环在 Asyncio 中被叫做 event_loop,在 Twisted 中叫做 reactor。我们看一些简单的例子

阅读全文

解决 Scrapy 并发量高时 DNS 超时的问题

解决 Scrapy 并发量高时 DNS 超时的问题

当 Scrapy 对不同域名抓取的时候,如果并发量太高,经常会有 DNS 超时的问题。当然,这个是同类型爬虫都有的问题,本文只是在 Scrapy 框架的基础上描述下解决这个问题的过程。

分析 Scrapy DNS 解析过程

当我把 CONCURRENT_REQUESTS 调到 600 的时候,爬虫便出现大量的 DNS 超时错误。Scrapy 虽说总体上是以异步的方式实现的,但是在进行 DNS 解析的时候,使用的是多线程。

我们一般使用类似下面的命令运行爬虫

$ scrapy crawl spider_name

这其实都会用到 cmdline.py 中的 execute 方法

# scrapy/cmdline.py

def execute(argv=None, settings=None):
    # ...
    cmd.crawler_process = CrawlerProcess(settings)
    _run_print_help(parser, _run_command, cmd, args, opts)
    sys.exit(cmd.exitcode)

阅读全文

聊聊 Scrapy 分布式的几种方案

聊聊 Scrapy 分布式的几种方案

Scrapy 是一款开源爬虫框架,但是官方对分布式支持并不多,本文聊聊几种简单可行的方案。

需要注意的是,本文的爬虫并非对几个网站的纵向爬取,而是爬取一批不同的地址。如果是其它类型的爬虫,可能不适用,但应该能让你多个思考方向。

网址分批处理

既然是一批不同的地址,那么最直接的方案就是把网址分几个部分,然后起几个进程同时处理就行了。

举个例子,我们要爬取的地址是放到 Mongo 中的,存储格式类似这样

{ "_id" : 1, "url" : "https://www.baidu.com" }
{ "_id" : 2, "url" : "https://www.google.com" }
{ "_id" : 3, "url" : "https://www.csdn.net" }
...

那我们就在编写爬虫的时候,接受 2 个参数(起始的地址主键和结束的主键),就能爬取指定的地址了。之后的工作,便是怎么分配地址了。

阅读全文

使用 Vue+Flask 搭建单页应用

使用 Vue+Flask 搭建单页应用

单页应用,只加载一个主页面,然后通过 AJAX 无刷新加载其它页面片段。表面上看,就只有一个 HTML 文件,所谓单页。开发上,做到了前后端分离,前端专注于渲染模板,而后端只要提供 API 就行,不用自己去套模板了。效果上,页面和共用的 JS、CSS 文件都只加载一次,能减轻服务器压力和节省一定的网络带宽。另外,由于不需要每次都加载页面以及共用的静态文件,响应速度也有一定提高,用户体验比较好。当然,也有一些缺点,比如 SEO 优化不大方便,不过也有相应的解决方案。总的来说,使用单页应用的好处还是远多于坏处,这也是越来越多的人使用单页应用的原因。

构建单页应用的方式有很多,这里我们选择 Flask + Vue 实现。本文以实现一个 CRUD 的 Demo 为主线,在其中穿插必要的技术点进行讲述。里面可能涉及了一些你没接触或者不熟悉的概念,不过不要紧,我会给出相应的参考文章帮助你理解。当然,大牛可忽略这些 :)。看完这篇文章后,相信你也能搭建自己的单页应用了。

阅读全文

聊聊 SQLAlchemy 连接池中的连接失效问题

聊聊 SQLAlchemy 连接池中的连接失效问题

最近项目中事情比较多,也遇到了一些问题,其中有一个是关于连接池的,比较有意思,这里分享下。

一天早上,进入业务系统,点击了一个功能按钮,页面上突然弹出个 MySQL gone away 的错误,我擦,数据库挂了吗,上服务器一看正常的。又点击了一下,又报事务未正常关闭的错误,有点懵。当然,是在测试环境上 :)。仔细想了想,发现是连接池的问题,下面我重现下这个错误并说下自己的一些解决办法。

阅读全文

灵活使用 SQLAlchemy 中的 ORM 查询

灵活使用 SQLAlchemy 中的 ORM 查询

之前做查询一直觉得直接拼 SQL 比较方便,用了 SQLAlchemy 的 ORM 查询之后,发现也还可以,还提高了可读性。

这篇文章主要说说 SQLAlchemy 常用的 ORM 查询方式,偏实践。看了之后,对付开发中的查询需求,我觉得可以满足不少。

为方便说明,假设有如下数据

图书表 books

+----+--------+--------------------------+-------+
| id | cat_id | name                     | price |
+----+--------+--------------------------+-------+
|  1 |      1 | 生死疲劳                 | 40.40 |
|  2 |      1 | 皮囊                     | 31.80 |
|  3 |      2 | 半小时漫画中国史         | 33.60 |
|  4 |      2 | 耶路撒冷三千年           | 55.60 |
|  5 |      2 | 国家宝藏                 | 52.80 |
|  6 |      3 | 时间简史                 | 31.10 |
|  7 |      3 | 宇宙简史                 | 22.10 |
|  8 |      3 | 自然史                   | 26.10 |
|  9 |      3 | 人类简史                 | 40.80 |
| 10 |      3 | 万物简史                 | 33.20 |
+----+--------+--------------------------+-------+

分类表 categories

+----+--------------+
| id | name         |
+----+--------------+
|  1 | 文学         |
|  2 | 人文社科     |
|  3 | 科技         |
+----+--------------+

ORM 对象定义如下


注意:本文 Python 代码在以下环境测试通过

  • Python 3.6.0
  • PyMySQL 0.8.1
  • SQLAlchemy 1.2.8

# coding=utf-8

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Numeric
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
engine = create_engine('mysql+pymysql://username:password'
                       '@127.0.0.1:3306/db_name?charset=utf8')
Session = sessionmaker(bind=engine)

session = Session()

def to_dict(self):
    return {c.name: getattr(self, c.name, None)
            for c in self.__table__.columns}
Base.to_dict = to_dict

class Book(Base):
    __tablename__ = 'books'

    id = Column(Integer, primary_key=True)
    cat_id = Column(Integer)
    name = Column('name', String(120))
    price = Column('price', Numeric)

class Category(Base):
    __tablename__ = 'categories'

    id = Column(Integer, primary_key=True)
    name = Column('name', String(30))

好了,下面进入正题。

1 根据主键获取记录

当我们获取图书的详情时,很容易用到。

book_id = 1
book = session.query(Book).get(book_id)
print(book and book.to_dict())

直接 get(primary_key) 就得到结果

{'id': 1, 'cat_id': 1, 'name': '生死疲劳',
 'price': Decimal('40.40')}

当然,这样也可以

book_id = 1
book = session.query(Book) \
    .filter(Book.id == book_id) \
    .first()
print(book and book.to_dict())

不过,还是前一种方式简洁一些。

阅读全文