当前位置: 首页 > news >正文

郑州微信网站建设世界500强企业排名中国名单

郑州微信网站建设,世界500强企业排名中国名单,网站建站,电子商务网站建设卷子Golang实现Redis分布式锁#xff08;Lua脚本可重入自动续期#xff09; 1 概念 应用场景 Golang自带的Lock锁单机版OK#xff08;存储在程序的内存中#xff09;#xff0c;分布式不行 分布式锁#xff1a; 简单版#xff1a;redis setnx》加锁设置过期时间需要保证原…Golang实现Redis分布式锁Lua脚本可重入自动续期 1 概念 应用场景 Golang自带的Lock锁单机版OK存储在程序的内存中分布式不行 分布式锁 简单版redis setnx》加锁设置过期时间需要保证原子性》lua脚本完整版redis Lua脚本实现可重入自动续期》hset结构 应用场景 防止用户重复下单锁住用户id防止商品超卖问题锁住账户防止并发操作 例如我本地启两个端口跑两个相同服务然后通过Nginx反向代理分别将请求均衡打到两个服务模拟分布式微服务最后通过Jmeter模拟高并发场景。同时我在代码里添加上lock锁。 可以看到还是有消费到相同数据出现超卖现象这是因为lock锁是在go程序的内存只能锁住当前程序。如果是分布式的话就需要涉及分布式锁。 注意本地通过MacJmeterIrisNginx模拟分布式场景详情可见https://blog.csdn.net/weixin_45565886/article/details/136635997 package mainimport (contextgithub.com/go-redis/redis/v8github.com/kataras/iris/v12context2 github.com/kataras/iris/v12/contextmyTest/demo_home/redis_demo/distributed_lock/constantservice2 myTest/demo_home/redis_demo/distributed_lock/other_svc/servicesync )func main() {constant.RedisCli redis.NewClient(redis.Options{Addr: localhost:6379,DB: 0,})_, err : constant.RedisCli.Set(context.TODO(), constant.AppleKey, 500, -1).Result()if err ! nil err ! redis.Nil {panic(err)}app : iris.New()xLock2 : new(sync.Mutex)app.Get(/consume, func(c *context2.Context) {xLock2.Lock()defer xLock2.Unlock()service2.GoodsService2.Consume()c.JSON(ok port:9999)})app.Listen(:9999, nil) }分布式锁必备特性 分布式锁需要具备的特性 独占性(排他性)任何时刻有且仅有一个线程持有高可用redis集群情况下不能因为某个节点挂了而出现获取锁失败和释放锁失败的情况防死锁杜绝死锁必须有超时控制机制或撤销操作 Expire key不乱抢防止乱抢。自己只能unlock自己的锁lua脚本保证原子性且只删除自己的锁重入性同一个节点的同一个线程如果获得锁之后它也可以再次获取这个锁 setnx只能解决有无分布式锁hset 解决可重入问题记录加锁次数 hset zyRedisLock uuid:threadID 3 2 思路分析 宕机与过期 如果加锁成功之后某个Redis节点宕机该锁一直得不到释放就会导致其他Redis节点加锁失败。 加锁时需要设置过期时间 //通过lua脚本保证加锁与设置过期时间的原子性func (r *RedisLock) TryLock() bool {//通过lua脚本加锁[hincrby如果key不存在则会主动创建,如果存在则会给count数加1表示又重入一次]lockCmd : if redis.call(exists, KEYS[1]) 0 or redis.call(hexists, KEYS[1], ARGV[1]) 1 then redis.call(hincrby, KEYS[1], ARGV[1], 1) redis.call(expire, KEYS[1], ARGV[2]) return 1 else return 0 endresult, err : r.redisCli.Eval(context.TODO(), lockCmd, []string{r.key}, r.Id, r.expire).Result()if err ! nil {log.Errorf(tryLock %s %v, r.key, err)return false}i : result.(int64)if i 1 {//获取锁成功自动续期go r.reNewExpire()return true}return false }防止误删key 锁过期时间设置30s业务逻辑假如要跑40s。30s后锁自动过期释放了其他线程加锁了。再过10s后业务逻辑走完了去释放锁就会出现把其他人的锁删除。【张冠李戴】 设置key时可带上线程id和uuid我这里以uuid演示。删除key之前要判断是否是自己的锁。如果是则unlock释放不是就return走。 func (r *RedisLock) Unlock() {//通过lua脚本删除锁//1. 查看锁是否存在如果不存在直接返回//2. 如果存在对锁进行hincrby -1操作,当减到0时表明已经unlock完成可以删除keydelCmd : if redis.call(hexists, KEYS[1], ARGV[1]) 0 then return nil elseif redis.call(hincrby, KEYS[1], ARGV[1], -1) 0 then return redis.call(del, KEYS[1]) else return 0 endresp, err : r.redisCli.Eval(context.TODO(), delCmd, []string{r.key}, r.Id).Result()if err ! nil err ! redis.Nil {log.Errorf(unlock %s %v, r.key, err)}if resp nil {fmt.Println(delKey, resp)return} }Lua保证原子性 加锁与设置过期时间需要保证原子性。否则如果加锁成功后还没来得及设置过期时间Redis节点挂掉了就又会出现其他节点一直获取不到锁的问题。 Lua脚本保证原子性 //lock 加锁设置过期时间 if redis.call(exists, KEYS[1]) 0 or redis.call(hexists, KEYS[1], ARGV[1]) 1 then redis.call(hincrby, KEYS[1], ARGV[1], 1) redis.call(expire, KEYS[1], ARGV[2]) return 1 else return 0 end//unlock解锁delCmd : if redis.call(hexists, KEYS[1], ARGV[1]) 0 then return nil elseif redis.call(hincrby, KEYS[1], ARGV[1], -1) 0 then return redis.call(del, KEYS[1]) else return 0 end//自动续期 renewCmd : if redis.call(hexists, KEYS[1], ARGV[1]) 1 then return redis.call(expire, KEYS[1], ARGV[2]) else return 0 end可重入锁 存在一部分业务方法里还需要继续加锁。需要实现锁的可重入记录加锁的次数。Lock几次就unLock几次。 map[string]map[string]int 可通过Redis hset结构实现 # yiRedisLock redis的key # fas421424safsfa:1 uuid线程号 # 5 加锁次数重入次数 hset yiRedisLock fas421424safsfa:1 5//通过hsethincrby 保证可重入记录加锁次数 lockCmd : if redis.call(exists, KEYS[1]) 0 or redis.call(hexists, KEYS[1], ARGV[1]) 1 then redis.call(hincrby, KEYS[1], ARGV[1], 1) redis.call(expire, KEYS[1], ARGV[2]) return 1 else return 0 enddelCmd : if redis.call(hexists, KEYS[1], ARGV[1]) 0 then return nil elseif redis.call(hincrby, KEYS[1], ARGV[1], -1) 0 then return redis.call(del, KEYS[1]) else return 0 end自动续期 相同业务耗时可能因为网络等问题而有所变化。例如我们设置分布式锁超时时间为20s但是业务因为网络问题某次耗时达到了30s这时锁就会被超时释放其他线程就能获取到锁。存在业务风险。 加锁成功之后设置自动续期启一个timer定时任务比如每10s检测一下锁有没有被释放如果没有就自动续期。 // 判断锁是否存在如果存在表明业务还未完成重新设置过期时间自动续期 renewCmd : if redis.call(hexists, KEYS[1], ARGV[1]) 1 then return redis.call(expire, KEYS[1], ARGV[2]) else return 0 end3 代码 3.1 项目结构解析 constant模块定义分布式锁名称、业务Key用于模拟扣减数据库lock模块核心模块实现分布式锁 LockTryLockUnLockNewRedisLock other_svc在其他端口启另外一个服务用于本地模拟分布式service业务类扣减商品数量其中的扣减操作涉及分布式锁main提供iris web服务 3.2 全部代码 注other_svc这里不提供与分布式锁实现无太大关系。同时为了快速演示效果部分项目结构与代码不规范。 感兴趣的朋友可以上Github查看全部代码。 Githubhttps://github.com/ziyifast/ziyifast-code_instruction/tree/main/redis_demo/distributed_lock现象 constant/const.go package constantimport github.com/go-redis/redis/v8var (BizKey XXOOAppleKey appleRedisCli *redis.Client )lock/redis_lock.go package serviceimport (contextgithub.com/go-redis/redis/v8github.com/ziyifast/logmyTest/demo_home/redis_demo/distributed_lock/constantmyTest/demo_home/redis_demo/distributed_lock/lockstrconv )type goodsService struct { }var GoodsService new(goodsService)func (g *goodsService) Consume() {redisLock : lock.NewRedisLock(constant.RedisCli, constant.BizKey)redisLock.Lock()defer redisLock.Unlock()//consume goodsresult, err : constant.RedisCli.Get(context.TODO(), constant.AppleKey).Result()if err ! nil err ! redis.Nil {panic(err)}i, err : strconv.ParseInt(result, 10, 64)if err ! nil {panic(err)}if i 0 {log.Infof(no more apple...)return}_, err constant.RedisCli.Set(context.TODO(), constant.AppleKey, i-1, -1).Result()if err ! nil err ! redis.Nil {panic(err)}log.Infof(consume success...appleID:%d, i) }service/goods_service.go package serviceimport (contextgithub.com/go-redis/redis/v8github.com/ziyifast/logmyTest/demo_home/redis_demo/distributed_lock/constantmyTest/demo_home/redis_demo/distributed_lock/lockstrconv )type goodsService struct { }var GoodsService new(goodsService)func (g *goodsService) Consume() {redisLock : lock.NewRedisLock(constant.RedisCli, constant.BizKey)redisLock.Lock()defer redisLock.Unlock()//consume goodsresult, err : constant.RedisCli.Get(context.TODO(), constant.AppleKey).Result()if err ! nil err ! redis.Nil {panic(err)}i, err : strconv.ParseInt(result, 10, 64)if err ! nil {panic(err)}if i 0 {log.Infof(no more apple...)return}_, err constant.RedisCli.Set(context.TODO(), constant.AppleKey, i-1, -1).Result()if err ! nil err ! redis.Nil {panic(err)}log.Infof(consume success...appleID:%d, i) }main.go package mainimport (contextgithub.com/go-redis/redis/v8github.com/kataras/iris/v12context2 github.com/kataras/iris/v12/contextmyTest/demo_home/redis_demo/distributed_lock/constantmyTest/demo_home/redis_demo/distributed_lock/service )func main() {constant.RedisCli redis.NewClient(redis.Options{Addr: localhost:6379,DB: 0,})_, err : constant.RedisCli.Set(context.TODO(), constant.AppleKey, 500, -1).Result()if err ! nil err ! redis.Nil {panic(err)}app : iris.New()//xLock : new(sync.Mutex)app.Get(/consume, func(c *context2.Context) {//xLock.Lock()//defer xLock.Unlock()service.GoodsService.Consume()c.JSON(ok port:8888)})app.Listen(:8888, nil) }
http://www.pierceye.com/news/239324/

相关文章:

  • 上海注册汽车租赁公司网站模板对seo的影响
  • 上海松江做网站公司wordpress 网站暂停
  • 太仓苏州网站建设网站的规划与建设课程设计
  • 遵义住房城乡建设厅网站电子商务网店毕业设计
  • 惠州市博罗县建设局网站防静电产品东莞网站建设技术支持
  • 茂名整站优化百度一下 你知道首页
  • 郑州网站微信微博维护品牌网站建设流程图
  • 网站建站销售怎么做做门窗安装去哪些网站找生意
  • 太原建站司点击查看荆州网站开发
  • 个人音乐网站开发宁波网站推广工作室电话
  • 建设部网站 造价工程师wordpress忘了秘密
  • 如何分析一个网站建设策划案湖南网络推广公司大全
  • 惠州市建设工程交易中心网站如何制作网页导航栏
  • 保山网站建设哪家好做led视频好的网站
  • 如何利用网站策划做好网站建设申请域名要多少钱
  • 专业做公司logo的网站谷歌网站推广方案
  • 公司网站建设情况说明网站开发用php好吗
  • 网站做关键词库的作用空间怎么做网站
  • 制作网站公司名称网络营销与策划实践报告
  • 哈尔滨手机网站建设价格低长沙网业公司
  • 如皋网站建设公司网站推广优化排名
  • 行业网站方案网站搭建合同
  • 如何用ps做网站图标吉林省长春市建设局网站
  • 北京高端网站建设服务广州百度快速排名优化
  • 电子商务网站开发课程设计网站建设石家庄
  • 好的公司网站有什么用烟台建设集团招聘信息网站
  • 网站制作需要多长时间网站代建设费用
  • 淘宝客网站设计台州建设银行官方网站
  • 婚纱网站建设规划书2023全国企业公司大黄页
  • 网站seo的关键词排名怎么做的wordpress 在线留言