Laravel 如何在预加载中指定查询的字段

预加载指定列

并不是总需要获取关系的每一列。在这种情况下,Eloquent 允许你为关联指定想要获取的列:

1
2
3
4
$result = User::with('topics:user_id,body,title')
    ->where('name', 'Summer')
    ->get()
    ->toArray();

注意:在使用这个特性时,一定要在要获取的列的列表中包含 id 列。『这里的 id 列是指关联字段列 user_id

NSQ-分布式实时消息处理平台

NSQ 架构

NSQ 简介

NSQ 是一个基于 Go 语言的分布式实时消息处理平台,它基于 MIT 开源协议发布,由 bitly 公司开源出来的一款简单易用的消息中间件。其设计的目的是用来大规模地处理每天数以十亿计级别的消息。

ElasticStack

Elastic 产品生态

推荐架构

Elastic Stack

“ELK”是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch、Logstash 和 Kibana。

Elasticsearch 是一个搜索和分析引擎,可扩展的高速体验,涵盖搜索、分析和存储。

缓存之雪崩、击穿、穿透

缓存雪崩

缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重导致雪崩。

解决方案

缓存失效时的雪崩效应对底层系统的冲击非常可怕。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。这里分享一个简单方案,就是将缓存失效时间分散开,比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

Go defer 使用

golang defer 这个使用的执行流程一直很绕,所以决定写一篇文记录一下。

规则一:当defer被声明时,其参数就会被实时解析

案例一

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
    "fmt"
)

func main() {
    test()
}

func test() {
    defer f1(f2())

    fmt.Println("2")
    return
}

func f1(i int) int {
    return i
}

func f2() int {
    fmt.Println("1")
    return 1
}

输出:12

Mysql 经典案例

too many connection 报错可能原因

  1. 机器负载飙升,导致SQL执行效率下降,导致连接堆积

  2. 业务访问量突增(或者有SQL注入现象),导致连接数打满

  3. 出现“死锁”或者锁竞争严重,导致大量SQL堆积【如事务没有正确关闭、频繁更新导致资源争用等】

Go 常用库

标准库

time

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// 获取当前时间,这是个奇葩,必须是这个时间点, 据说是go诞生之日, 记忆方法:6-1-2-3-4-5
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))

// 获取当天的年月日时间
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
nowDayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
nowDayEnd := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 59, time.Local)

// 获取时间戳
timeUnix := time.Now().Unix()              //单位秒,10 位数
timeUnixMill := time.Now().UnixNano()/1e6  //单位毫秒,13 位数
timeUnixMicro := time.Now().UnixNano()/1e3 //单位微秒,16 位数
timeUnixNano := time.Now().UnixNano()      //单位纳秒,19 位数

// 时间戳转为日期格式
timestamp := time.Unix(timestamp, 0).Format("2006-01-02 15:04:05")     // 秒级时间戳转换成日期
timestamp := time.Unix(int64(timestamp/1e3), 0).Format("2006-01-02 15:04:05") // 毫秒级时间戳转换成日期

// 获取7月1日的time
month7Start = time.Date(now.Year(), 7, 1, 0, 0, 0, 0, time.Local)

// ISO8601标准日期格式
time.Now().UTC().Format(time.RFC3339) // 世界统一时间:2024-09-20T07:01:44Z
time.Now().Format(time.RFC3339)       // 本地时区时间:2024-09-20T15:01:44+08:00

// 时间字符串转为 time.Time,再转时间戳 timeUnix := tTime.Unix()
startTime, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-02-28 10:24:26", time.Local)
func StringToTime(t string) (time.Time, error) {
    location, err := time.LoadLocation("Asia/Shanghai")
    if err != nil {
        return time.Time{}, err
    }
    tTime, _ := time.ParseInLocation("2006-01-02 15:04:05", t, location)
    return tTime, nil
}

// time.Time 转为时间字符串
func FormatTime(time time.Time) string {
    return time.Format("2006-01-02 15:04:05")
}

// time.RFC3339 时间格式化
func RFC3339ToTime(value string) (time.Time, error) {
    return time.ParseInLocation(time.RFC3339, value, time.Local)
}

// 秒级时间戳转为 time.Time
datetime := time.Unix(int64(1629107978), 0)

// 未来第30天
lastDay := time.Now().AddDate(0, 0, 30).Format("2006-01-02")

// 获取5分钟前的时间
time.Now().Add(-time.Minute * 5)

// 获取 1 小时之前的时间
m, _ := time.ParseDuration("-1h")
result := currentTime.Add(m)

// 睡眠 1 秒和 0.5 秒
time.Sleep(time.Second)
time.Sleep(time.Microsecond * 500)

// 两个时间点相差的天数
now := time.Now()
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
futureDay := today.AddDate(0, 0, 1)
fmt.Printf("两个时间点相差的天数:%d\n", int(futureDay.Sub(today).Hours()/24)+1)

// 时间大小比较
now := time.Now()
a, _ := time.Parse("2006-01-02 15:04:05", "2019-03-10 11:00:00")
b, _ := time.Parse("2006-01-02 15:04:05", "2015-03-10 16:00:00")
fmt.Println("now a After: ",now.After(a))
fmt.Println("now a Before: ",now.Before(a))

// 时区设置,方法一:
var zone, _ = time.LoadLocation("Asia/Shanghai") // 地区标准时
fmt.Println(time.Now().In(zone).Format("2006-01-02 15:04:05"))

// 时区设置,方法二:
var zone = time.FixedZone("CST", 8*3600) // 东八区
fmt.Println(time.Now().In(zone).Format("2006-01-02 15:04:05"))

// 定时器: time.NewTimer 和 time.NewTicker
NewTimer: 只执行一次到时间执行
NewTicker: 循环执行只要定义完成从此刻开始计时不需要任何其他的操作每隔固定时间都会触发。『相比 sleep资源占用少
func timer() {
   timer := time.NewTimer(3 * time.Second)
   select {
   case <-timer.C:
      fmt.Println("3秒执行任务")
   }
   timer.Stop() // 这里来提高 timer 的回收
}
func ticker(activityId, userId uint64) (bool, error) {
    key := "keyxxx"
    var times uint32
    ticker := time.NewTicker(100 * time.Millisecond) // 定时器,定时往 channel 追加数据
    defer ticker.Stop()
    for {
        isLock := db.Redis(db.Default).SetNX(key, userId, 1*time.Minute).Val() // 利用 redis 加锁
        if isLock { // 是否是自身上锁
            return true, nil
        }

        // 避免用户等待时间过长
        times++
        if times > 100 {
            return false, errors.New("抽奖失败")
        }

        <-ticker.C // 定时器时间未到,阻塞,相当于 sleep
    }
}

encoding/json

1
2
3
4
5
6
7
8
// 输出json后的数据
data, _ := json.Marshal(userDevice)
fmt.Println(string(data))

// json解析成map并输出
var ret interface{}
json.Unmarshal(data, &ret)
fmt.Println(ret)

sync

  • sync.Mutex & sync.RWMutex
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# sync.Mutex互斥锁
type Mutex
    func (m *Mutex) Lock()
    func (m *Mutex) Unlock()

sync.RWMutex: 读写锁一般用在大量读操作少量写操作的情况

1. 同时只能有一个 goroutine 能够获得写锁定
2. 同时可以有任意多个 gorouinte 获得读锁定
3. 同时只能存在写锁定或读锁定读和写互斥)。

Mutex在大量并发的情况下会造成锁等待对性能的影响比较大而读写锁可以提高性能如果某个读操作的协程加了锁其他的协程没必要处于等待状态可以并发地访问共享变量这样能让读操作并行提高读性能

# Mutex 本身不支持可重入锁不要 copy 已使用的 Mutex避免死锁

可重入锁(或递归锁)这是 Java 并发包中非常常用的一个同步原语它的基本行为和互斥锁相同但是加了一些扩展功能当一个线程获取锁时如果没有其它线程拥有这个锁那么这个线程就成功获取到这个锁之后如果其它线程再请求这个锁就会处于阻塞等待的状态但是如果拥有这把锁的线程再请求这把锁的话不会阻塞而是成功返回所以叫可重入锁有时候也叫做递归锁)。只要你拥有这把锁你可以可着劲儿地调用比如通过递归实现一些算法调用者不会阻塞或者死锁 `划重点了:Mutex 不是可重入的锁。`
type Counter struct {
    sync.Mutex
    Count int
}

func main() {
    var c Counter
    c.Lock()
    defer c.Unlock()
    c.Count++
    foo(c) // 复制锁
}

// 这里Counter的参数是通过复制的方式传入的,导致死锁
func foo(c Counter) {
    c.Lock()
    defer c.Unlock()
    fmt.Println("in foo")
}

# 静态代码分析工具|死锁检测工具使用go自带的vet工具或者是第三方的工具比如go-deadlockgo-tools
go vet xxx.go
go tool vet source/directory/*.go // 一个包下所有源文件进行检测
go vet ./source/repository // 利用vet对一个package进行检查,当然传入的包名必须是 相对路径 或者完整package。
  • sync/atomic
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
原子锁Go 语言的 sync/atomic 包提供了对原子操作的支持用于同步访问整数和指针

之所以叫原子操作是因为一个原子在执行的时候其它线程不会看到执行一半的操作结果在其它线程看来原子操作要么执行完了要么还没有执行就像一个最小的粒子 - 原子一样不可分割

* Go语言提供的原子操作都是非入侵式的
* 这些函数提供的原子操作共有五种增减比较并交换载入存储交换eg: atomic.AddUint32atomic.CompareAndSwapUint32
* 原子操作支持的类型类型包括int32int64uint32uint64uintptrunsafe.Pointer

# 原子操作与互斥锁的比较原子操作->乐观锁互斥锁->悲观锁
原子操作的做法趋于乐观总是假设被操作值未曾被改变并一旦确认这个假设的真实性就立即进行值操作那么在被操作值被频繁变更的情况下原子操作并不那么容易成功而使用互斥锁的做法则趋于悲观我们总假设会有并发的操作要修改被操作的值并使用锁将相关操作放入临界区中加以保护

# 应用场景
不涉及到对资源复杂的竞争逻辑只是会并发地读写这个标志这类场景就适合使用 atomic 的原子操作如定时任务是否运行的标志计数器

package main

import (
    "sync/atomic"
)

// 定时任务标识
var isRunning int32
func crontab() string {
    if !atomic.CompareAndSwapInt32(&isRunning, 0, 1) {
        return "该定时任务正在运行中..."
    } else {
        return "该定时任务空闲..."
    }
    defer func() {
        _ = atomic.CompareAndSwapInt32(&isRunning, 1, 0)
    }
}

// 计数器
func countAdd() {
    var ops uint64 = 0

    wg := sync.WaitGroup{}

    for i := 0; i < 50000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            atomic.AddUint64(&ops, 1)
        }()
    }

    wg.Wait()

    opsFinal := atomic.LoadUint64(&ops)
    fmt.Println("opsFinal:", opsFinal)
}
  • sync.WaitGroup
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 使用sync.WaitGroup方式保证所有goroutine都运行完毕
func main() {
    wg := sync.WaitGroup{} // 初始化等待组
    wg.Add(100) // 初始化计数器,设置为100
    for i := 0; i < 100, i++ {
        go func(i int) {
            defer wg.Done() // 计数器-1
            fmt.Println(i)
        }(i)
    }
    wg.Wait() // 进程阻塞
}

# sync.WaitGroup对象不是一个引用类型在通过函数传值的时候需要使用地址
func main() {
    wg := sync.WaitGroup{}
    wg.Add(100)
    for i := 0; i < 100; i++ {
        go f(i, &wg)
    }
    wg.Wait()
}
// 一定要通过指针传值,不然进程会进入死锁状态
func f(i int, wg *sync.WaitGroup) {
    fmt.Println(i)
    wg.Done()
}
  • sync.Map「并发安全」

使用原因:map是不支持并发操作的,只读是线程安全的,同时写线程不安全,所以为了并发安全 & 高效,请使用 sync.Map。「需要并发读写时,一般的做法是加锁,但这样性能并不高,故采用 sync.Map」

LB 集群

简介

LB 集群是 Load Balance 集群的简写,翻译成中文就是负载均衡集群.

负载均衡的应用场景为高访问量的业务,提高应用程序的可用性和可靠性。

原理

负载均衡的原理就是当用户的请求过来时,会直接发到分发器上,然后它把用户的请求根据预先设置好的算法,均衡的分发到后端真正的服务器(Real Server)上。

MacOs 之 brew

brew 和 brew cask 区别

  • brew主要是用于安装没UI的软件。
  • brew cask主要用于安装有UI的软件。
  • brew 是 MacOs 下的包管理工具,通过 Github 托管适合 Mac 的编译配置以及 Patch,可以方便的安装开发工具。

常用命令

  • brew list # 显示所有的已安装的软件
  • brew update –verbose # 更新brew可安装包,建议每次执行一下
  • brew search php55 # 搜索本地远程仓库的软件,已安装会显示绿色的勾
  • brew install php55 # 安装php5.5
  • brew uninstall php55 # 卸载php5.5
  • brew upgrade php55 # 升级php5.5
  • brew options php55 # 查看php5.5安装选项
  • brew info php55 # 查看php5.5相关信息
  • brew home php55 # 访问php5.5官方网站
  • brew services start/stop your-service # 管理服务,没怎么用它,ELK都有自己的启动脚本在安装目录的bin/下面,且基本上都会携带参数启动
  • brew switch xxx@version # 多版本切换 (https://learnku.com/articles/51190)
  • brew cleanup -n # 列出需要清理的内容
  • brew cleanup # 清理所有的过时软件
  • brew outdated # 检测已经过时的软件

相同程序不同版本切换命令

cask常用命令

  • brew cask search #列出所有可以被安装的软件
  • brew cask search php #查找所有和php相关的应用
  • brew cask list #列出所有通过cask安装的软件
  • brew cask info phpstorm #查看 phpstorm 的信息
  • brew cask uninstall qq #卸载 QQ

查看当前源

1
2
cd /usr/local/Homebrew
git remote -v

install php7.2

  • brew install php@7.2
  • 启动:
  • To have launchd start php@7.2 now and restart at login:
  • brew services start php@7.2
  • Or, if you don’t want/need a background service you can just run:
  • php-fpm
  • echo ’export PATH="/usr/local/opt/php@7.2/bin:$PATH"’ » ~/.zshrc
  • echo ’export PATH="/usr/local/opt/php@7.2/sbin:$PATH"’ » ~/.zshrc
1
2
# php.ini 路径
/usr/local/etc/php/7.2/php.ini

install redis

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
brew services start redis

brew services restart redis

// if you don't want/need a background service you can just run:
redis-server /usr/local/etc/redis.conf

// 取消redis密码登录
vi /usr/local/etc/redis.conf
# requirepass foobared // 搜索 requirepass,注释后重启redis服务即可

MacOs 使用心得

MacBook 键盘符号和修饰键说明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
⌘ ——> Command
⇧ ——> Shift
⌥ ——> Option
⌃ ——> Control
↩︎ ——> Return/Enter
⌫ ——> Delete
⌦ ——> 向前删除键(Fn + Delete)
↑ ——> 上箭头
↓ ——> 下箭头
← ——> 左箭头
→ ——> 右箭头
⇞ ——> Page Up(Fn + ↑)
⇟ ——> Page Down(Fn + ↓)
⇥ ——> 右制表符(Tab键)
⇤ ——> 左制表符(Shift + Tab)
⎋ ——> Escape(Esc)
End ——> Fn + →
Home ——> Fn + ←

快捷键

  1. command+option+i: 打开 iterm2
  2. command+option+c: 打开chrome控制台 【可自行修改为F12】
  3. command+N: 打开第二个qq,command+T: 打开第二个其他应用
  4. cmd+shift+backspace: chrome清除缓存快捷键
  5. cmd+`: 同一应用多窗口切换
  6. cmd+tab: 不同应用切换
  7. cmd+空格: 聚焦搜索
  8. 文字拖拽:光标移到开头后,按下shift键再移动光标,即选中一段文字

常用操作

  1. 删除启动台的一些顽固图标