go sync包之singleflight原理

singlefight是什么 singlefight 直译为"单飞"(雅名到底是啥我也不知道), 顾名思义就是只有一个跑了, 是用来对同一资源控制并发 多个goroutine访问同一个资源时,只有一个goroutine真正的进行访问,其他goroutine等待这一个goroutine返回后共享返回结果 为什么出现singlefight 这个包 上面是什么中已经交代,是为了控制访问同一个资源的并发数,举个例子:假设有个接口访问数据库中id为1的一条数据,如果我们没有控制并发,那么来一百个并发访问这个数据,那么这一百个请求全部取请求数据库(即使有缓存也是全部请求缓存) 如果我们使用了singlefight那么,100个并发讲只有一个请求去数据库,其他99个全部共享那1个返回的结果 怎么用 var g = singleflight.Group{} //初始化了一个singleflight func SharedRes(id int) (int, error) { key := fmt.Sprintf("id:%d", id) //同一个group上,相同key的,只会执行一次,也就是说用key标识一个共享资源 ret, err, _ := g.Do(key, func() (interface{}, error) { //调用共享资源 time.Sleep(time.Second) //这里睡1s是模拟资源执行的延迟 fmt.Println("xxxx") return 1, nil }) return ret.(int), err } func SingleFlight() { wg := sync.WaitGroup{} //为了等100个goroutine执行完,开启了一个WaitGroup for i := 0; i < 100; i++ { wg.Add(1) go func() { //模拟并发 ret, err := SharedRes(1) fmt.Println("ret", ret, err) wg.Done() }() } wg.Wait() //等待100个goroutine执行完 } 看一下打印结果 xxxx <----这里只输出了一次xxxx ret 1 <nil> <----这里是返回结果 ret 1 <nil> ret 1 <nil> ret 1 <nil> .... 我们从现象可以看出,真正的业务逻辑执行只输出了一次,其他的goroutine也都返回了结果 源代码分析 Group结构 type Group struct { mu sync.Mutex // 互斥锁 m map[string]*call // 用来保存共享的调用结构,等会分析这个的作用 } call结构 type call struct { wg sync.WaitGroup //用来让共享groutine等待用的 //我们传入Do的函数签名是 func() (interface{}, error) val interface{} //函数返回结果 err error //函数返回错误 forgotten bool //调用Forget会forgotten=true,并从group.m 中删除这个key对应的call dups int //有几个一起共享结果的 chans []chan<- Result //通过DoChan配合使用,通过返回chan的方式返回结果 } Do函数分析,这个是这个包中最精华部分一之 ...

2022-06-19 22:02:23    |    3 分钟    |    469 字    |    Fengbin

redis分布式锁

redis分布式锁网上方案很多,这里简单的介绍一种 加锁步骤 1.创建锁对象,内部创建一个随机数 2.使用SET KEY VALUE NX EX xxxSecond, 如果成功创建了KEY 则证明加锁成功VALUE 就是第一步创建的随机数 3.如果未能成功加锁需要不断取重试,直到超时或者获取锁 解锁步骤 解锁需要去判断KEY对应的值是否是创建时的随机数,如果不是就不能删除,只有是的时候才能删除,因为如果不是自己的随机数可能是因为锁过期被别人加锁了,不能去删除别人的锁, 检查和删除必须是原子操作,所以我们可以使用lua脚本保证原子操作 if redis.call("GET", KEYS[1])==ARGV[1] then redis.call("DEL", KEYS[1]) return true else return false end 上面的lua脚本很容易懂,就是用来判断key对应的值是否是参数的值,如果是就删除key并返回成功,否则返回失败;失败的情况就是上述说的锁过期被其他程序加锁 优化 加锁是需要不断去重试,访问次数过多可能会给redis造成压力,比如100ms抢一次,一个线程1s钟要请求redis 10次, 如果是10线程抢锁,那么1s就是100次,抢锁的越多就会将redis请求数放大10倍 应对这种情况我们可以考虑,进程内部先去加互斥锁,解锁的时候去解互斥锁, 然后抢到锁的线程再去抢redis锁 优点: 这样如果有两个进程,各有5个线程去抢锁,则实际只有两个线程去访问redis,抢到锁后只有另一个进程的1个线程继续抢,这种已经在生产环境中得到实践 缺点: 造成锁竞争的不公平,同一个进程其他线程更容易抢到锁,因为互斥锁解锁同一个进程的其他线程可以更快的感知 还有一种想法未得到验证,通过redis的发布订阅来改进锁性能 锁过期问题,没有个安全的方法去估计过期时间 针对这种情况,可以考虑锁续期逻辑,比如默认过期时间是30s,我们到20s的时候去延长过期时间 可以考虑使用下面的续期逻辑 if redis.call("GET", KEYS[1]) == ARGV[1] then redis.call("EXPIRE", KEYS[1], ARGV[2]) return true else return false end 先去判断锁是否是自己的,如果是则进行续期 参考资料 redis set命令 从2.6.12版本开始,redis为SET命令增加了一系列选项 EX seconds – 设置键key的过期时间单位时秒 PX milliseconds – 设置键key的过期时间单位时毫秒 NX – 只有键key不存在的时候才会设置key的值 XX – 只有键key存在的时候才会设置key的值 redlock redlock 和 普通的redis lock的区别就是,redlock需要使用多个redis(奇数个),采用大多数原则, 锁住大多数redis就算上锁成功 ...

2022-06-18 21:24:37    |    1 分钟    |    83 字    |    Fengbin

树莓派 连接WIFI

ubuntu 22.04 进入 /etc/netplan/ 文件夹 cd /etc/netplan/ 编辑里面唯一一个文件,大概是:50-cloud-init.yaml sudo vim 50-cloud-init.yaml, 需要使用sudo,因为这个文件是root用户的问题件,或者把文件选项改成可写的 添加WIFI配置 network: ethernets: eth0: dhcp4: true optional: true wifis: # <----添加wifi配置节点 wlan0: dhcp4: true optional: true access-points: "wifi_name": #<---- 这里填写填写你要连接的wifi名称 password: "xxxxx" #<-------这里填写wifi密码 version: 2 执行命令,生成网络配置sudo netplan generate 使网络配置生效sudo netplan apply 树莓派系统 在命令行中输入sudo raspi-config根选项配置即可(注意,可能不支持5G信号)

2022-06-18 21:15:30    |    1 分钟    |    46 字    |    Fengbin

Mac用网线连接树莓派

首先 打开mac的系统偏好设置->共享->互联网共享(USB 10/100/1000 LAN) 并打开共享 打开终端查看树莓派分配的IP 使用arp -a 里面有个带有bridgeXXX的 IP 使用ssh命令 远程连接

2022-06-18 16:12:36    |    1 分钟    |    12 字    |    Fengbin