Skip to content

Commit ac1e4b7

Browse files
committed
fix workflow
1 parent d8e2656 commit ac1e4b7

File tree

13 files changed

+704
-0
lines changed

13 files changed

+704
-0
lines changed

go.mod

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
module github.com/minicloudsky/leetcode-algorithm
22

33
go 1.21
4+
5+
require (
6+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
7+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
8+
github.com/go-redis/redis/v8 v8.11.5 // indirect
9+
)

interviews/coupon-system/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# 题目1:电商优惠券系统设计
2+
业务场景:
3+
设计一个电商平台的优惠券系统,需满足:
4+
1. 支持多种优惠类型(满减、折扣、赠品)。
5+
2. 优惠券可叠加使用,但需校验适用范围(如特定商品/用户等级)。
6+
3. 需记录优惠券领取、使用记录,并支持运营按“用户领取量”和“券使用率”分析数据。
7+
8+
考察重点:
9+
1. 关系型数据库设计
10+
2. 面向对象设计(要素+设计模式)

interviews/coupon-system/main.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package main
2+
3+
import (
4+
"strconv"
5+
"time"
6+
)
7+
8+
// 题目1:电商优惠券系统设计
9+
//业务场景:
10+
//设计一个电商平台的优惠券系统,需满足:
11+
//1. 支持多种优惠类型(满减、折扣、赠品)。
12+
//2. 优惠券可叠加使用,但需校验适用范围(如特定商品/用户等级)。
13+
//3. 需记录优惠券领取、使用记录,并支持运营按“用户领取量”和“券使用率”分析数据。
14+
//
15+
//考察重点:
16+
//1. 关系型数据库设计
17+
//2. 面向对象设计(要素+设计模式)
18+
19+
type CouponInter interface {
20+
Consume(couponId, uid string)
21+
Expire(couponId string)
22+
Use(couponId string)
23+
}
24+
25+
type DiscountCoupon struct {
26+
}
27+
28+
func (d *DiscountCoupon) Consume(couponId, uid string) {
29+
return
30+
}
31+
32+
func (d *DiscountCoupon) Expire(couponId string) {
33+
return
34+
}
35+
func (d *DiscountCoupon) Use(couponId string) {
36+
return
37+
}
38+
39+
type CouponType int
40+
41+
const (
42+
CouponTypeAbove = iota + 1
43+
ConponTypeDiscount
44+
CouponTypeGift
45+
)
46+
47+
type CouponSatifyScene string
48+
49+
const (
50+
CouponSatifySceneGoods = "goods"
51+
CouponSatifyUserRank = "user_rank"
52+
)
53+
54+
type Coupon struct {
55+
Id int64 `json:"id"`
56+
CouponName string `json:"coupon_name"`
57+
CouponType CouponType `json:"coupon_type"`
58+
CouponSatifyScene CouponSatifyScene `json:"coupon_satify_scene"`
59+
CouponSatifyVal string `json:"coupon_satify_val"`
60+
CouponSatifyCond string `json:"coupon_satify_cond"` // 满足的条件,json类型
61+
CouponCount int16
62+
ExpireTime time.Time
63+
}
64+
65+
//{
66+
//"price_above": xxx
67+
//}
68+
69+
type User struct {
70+
NickName string
71+
Uid string
72+
Level int16
73+
}
74+
75+
func (c *Coupon) Satify(user User, goodsId string) bool {
76+
switch c.CouponSatifyScene {
77+
case CouponSatifySceneGoods:
78+
if goodsId == c.CouponSatifyVal {
79+
return true
80+
}
81+
case CouponSatifyUserRank:
82+
rank, _ := strconv.Atoi(c.CouponSatifyCond)
83+
if int(user.Level) > rank {
84+
return true
85+
}
86+
}
87+
return false
88+
}
89+
90+
type CouponConsumeHistory struct {
91+
Id int64
92+
UID string
93+
ConponId int64
94+
CouponType CouponType
95+
}
96+
type CouponUseHistory struct {
97+
Id int64
98+
UID string
99+
ConponId int64
100+
CouponType CouponType
101+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# 分布式锁实现
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package redis_distribute_lock_conflict
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/go-redis/redis/v8"
7+
"math/rand"
8+
"time"
9+
)
10+
11+
const (
12+
lockKey = "concurrent_resource_lock"
13+
redisAddress = "localhost:6379"
14+
)
15+
16+
// RedisLock 改进版分布式锁(含冲突处理)
17+
type RedisLock struct {
18+
client *redis.Client
19+
lockKey string
20+
uniqueValue string
21+
expiration time.Duration
22+
retryInterval time.Duration
23+
maxRetries int
24+
}
25+
26+
func NewRedisLock(client *redis.Client, lockKey string, expiration time.Duration) *RedisLock {
27+
return &RedisLock{
28+
client: client,
29+
lockKey: lockKey,
30+
uniqueValue: generateUniqueValue(),
31+
expiration: expiration,
32+
retryInterval: 100 * time.Millisecond,
33+
maxRetries: 5,
34+
}
35+
}
36+
37+
func processWithLock(rdb *redis.Client, clientID int) {
38+
lock := NewRedisLock(rdb, lockKey, 5*time.Second)
39+
ctx := context.Background()
40+
41+
// 带重试机制的加锁
42+
acquired := false
43+
for attempt := 1; attempt <= lock.maxRetries; attempt++ {
44+
ok, err := lock.TryLock(ctx)
45+
if err != nil {
46+
fmt.Printf("Client %d attempt %d: Lock error - %v\n", clientID, attempt, err)
47+
continue
48+
}
49+
if ok {
50+
acquired = true
51+
break
52+
}
53+
fmt.Printf("Client %d attempt %d: Lock occupied, retrying...\n", clientID, attempt)
54+
time.Sleep(lock.retryInterval)
55+
}
56+
57+
if !acquired {
58+
fmt.Printf("Client %d: Failed to acquire lock after %d attempts\n", clientID, lock.maxRetries)
59+
return
60+
}
61+
62+
defer func() {
63+
if err := lock.Unlock(ctx); err != nil {
64+
fmt.Printf("Client %d unlock error: %v\n", clientID, err)
65+
}
66+
}()
67+
68+
// 模拟业务处理
69+
fmt.Printf("Client %d acquired lock, processing...\n", clientID)
70+
processTime := time.Duration(2+rand.Intn(3)) * time.Second
71+
time.Sleep(processTime)
72+
fmt.Printf("Client %d completed processing\n", clientID)
73+
}
74+
75+
// TryLock 带冲突检测的加锁方法
76+
func (l *RedisLock) TryLock(ctx context.Context) (bool, error) {
77+
result, err := l.client.SetNX(ctx, l.lockKey, l.uniqueValue, l.expiration).Result()
78+
if err != nil {
79+
return false, fmt.Errorf("redis setnx error: %w", err)
80+
}
81+
82+
// 加锁成功时启动自动续期
83+
if result {
84+
go l.autoRenew(ctx)
85+
}
86+
return result, nil
87+
}
88+
89+
// 自动续期机制
90+
func (l *RedisLock) autoRenew(ctx context.Context) {
91+
ticker := time.NewTicker(l.expiration / 2)
92+
defer ticker.Stop()
93+
94+
for {
95+
select {
96+
case <-ticker.C:
97+
// 使用 Lua 脚本保证原子性续期
98+
success, err := l.client.Eval(ctx, `
99+
if redis.call("GET", KEYS[1]) == ARGV[1] then
100+
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
101+
else
102+
return 0
103+
end
104+
`, []string{l.lockKey}, l.uniqueValue, l.expiration.Milliseconds()).Bool()
105+
106+
if err != nil || !success {
107+
return
108+
}
109+
case <-ctx.Done():
110+
return
111+
}
112+
}
113+
}
114+
115+
// Unlock 安全解锁
116+
func (l *RedisLock) Unlock(ctx context.Context) error {
117+
script := `
118+
if redis.call("GET", KEYS[1]) == ARGV[1] then
119+
return redis.call("DEL", KEYS[1])
120+
else
121+
return 0
122+
end
123+
`
124+
result, err := l.client.Eval(ctx, script, []string{l.lockKey}, l.uniqueValue).Int64()
125+
if err != nil {
126+
return fmt.Errorf("unlock script failed: %w", err)
127+
}
128+
if result == 0 {
129+
return fmt.Errorf("unlock failed: lock not exists or value mismatch")
130+
}
131+
return nil
132+
}
133+
134+
func generateUniqueValue() string {
135+
return fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Intn(1000))
136+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package redis_distribute_lock_conflict
2+
3+
import (
4+
"github.com/go-redis/redis/v8"
5+
"math/rand"
6+
"sync"
7+
"testing"
8+
"time"
9+
)
10+
11+
// 模拟资源竞争的并发场景
12+
func TestLockConflict(t *testing.T) {
13+
rand.Seed(time.Now().UnixNano())
14+
rdb := redis.NewClient(&redis.Options{Addr: redisAddress})
15+
16+
var wg sync.WaitGroup
17+
// 启动 3 个并发客户端竞争锁
18+
for i := 1; i <= 3; i++ {
19+
wg.Add(1)
20+
go func(clientID int) {
21+
defer wg.Done()
22+
processWithLock(rdb, clientID)
23+
}(i)
24+
}
25+
wg.Wait()
26+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package redis_distribute_lock
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/go-redis/redis/v8"
7+
"math/rand"
8+
"time"
9+
)
10+
11+
type RedisLock struct {
12+
client *redis.Client
13+
key string
14+
value string
15+
expiration time.Duration
16+
unlockCh chan struct{}
17+
}
18+
19+
func NewRedisLock(client *redis.Client, key string, expiration time.Duration) *RedisLock {
20+
return &RedisLock{
21+
client: client,
22+
key: key,
23+
value: generateUniqueValue(),
24+
expiration: expiration,
25+
unlockCh: make(chan struct{}),
26+
}
27+
}
28+
29+
// Lock 加锁(含自动续期)
30+
func (l *RedisLock) Lock(ctx context.Context) (bool, error) {
31+
ok, err := l.client.SetNX(ctx, l.key, l.value, l.expiration).Result()
32+
if err != nil || !ok {
33+
return false, err
34+
}
35+
36+
// 启动看门狗自动续期
37+
go l.watchDog(ctx)
38+
return true, nil
39+
}
40+
41+
// 自动续期
42+
func (l *RedisLock) watchDog(ctx context.Context) {
43+
ticker := time.NewTicker(l.expiration / 3)
44+
defer ticker.Stop()
45+
46+
for {
47+
select {
48+
case <-ticker.C:
49+
// 续期:仅当锁仍属于当前客户端时延长过期时间
50+
success, err := l.client.Eval(ctx, `
51+
if redis.call("GET", KEYS[1]) == ARGV[1] then
52+
return redis.call("PEXPIRE", KEYS[1], ARGV[2])
53+
else
54+
return 0
55+
end
56+
`, []string{l.key}, l.value, l.expiration.Milliseconds()).Bool()
57+
58+
if err != nil || !success {
59+
return
60+
}
61+
case <-l.unlockCh:
62+
return
63+
}
64+
}
65+
}
66+
67+
// Unlock 解锁
68+
func (l *RedisLock) Unlock(ctx context.Context) error {
69+
defer close(l.unlockCh)
70+
71+
script := `
72+
if redis.call("GET", KEYS[1]) == ARGV[1] then
73+
return redis.call("DEL", KEYS[1])
74+
else
75+
return 0
76+
end
77+
`
78+
79+
result, err := l.client.Eval(ctx, script, []string{l.key}, l.value).Result()
80+
if err != nil {
81+
return err
82+
}
83+
if result == 0 {
84+
return fmt.Errorf("unlock failed: lock not exists or value mismatch")
85+
}
86+
return nil
87+
}
88+
89+
func generateUniqueValue() string {
90+
rand.New(rand.NewSource(100000)).Int()
91+
return fmt.Sprintf("%d-%d", time.Now().UnixNano(), rand.Int())
92+
}

0 commit comments

Comments
 (0)