redis锁

mysql 数据800gb,日志300gb

es10个索引,每个索引120gb

redis 6主6从,3gb

自定义注解

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface RefundRedisLock {
String key(); // 示例: orderId, param.orderId ()
String keyPrefix() default StringUtils.EMPTY; // 示例:order.state (biz prefix string)
long limitWaitTimeSeconds() default 3; // 示例 3秒有限等待
long expireSeconds() default 30; // 示例:30秒自动释放}

@Pointcut(“@annotation(com.kuaishou.kwaishop.refund.annotation.RefundRedisLock)”)切入点
public void pointcut() {}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Around("pointcut()")
public Object doSurround(ProceedingJoinPoint proceedingJoinPoint执行点) throws Throwable {MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
RefundRedisLock refundRedisLock = methodSignature.getMethod().getAnnotation(RefundRedisLock.class);
locked = refundRedisLockService.tryLock(lockKey, limitWaitTimeSeconds, expireSeconds);
判断当前线程是否已经获得分布式锁,重入次数+1
retry < maxRetryTimes
计算重试获取锁的睡眠等待时间,Min(锁最大释放时间,默认重试间隔时间,获取锁的最大等待时间)
key-refund.application.service.lock.2319100000001864(orderid)
value-ip+threadname+id+时间
if (System.currentTimeMillis() > startTimeMillis + limitWaitTimeSeconds * 1000)
break;
return proceedingJoinPoint.proceed();
if (locked) {
boolean unlocked = refundRedisLockService.unLock(lockKey);
判断当前线程是否已经获得分布式锁,重入次数-1
StringUtils.isEmpty(currentLockValue)
return false;
校验目前锁的值是否当前线程持有
}
}

机器断电,锁没释放,造成死锁

加过期时间,不是和设置锁是原子性的

业务执行时间过长,锁自动过期了,释放掉了别人的锁

设置uuid,删除锁的时候判断下是不是自己的

判断和删除锁要设置成原子性

最终用redission框架,超时时间默认是30秒,看门狗每隔10s就会进行一次续期,把锁重置成30秒

加锁其实是通过一段 lua 脚本实现的,这里 KEYS[1] 代表的是你加锁的 key,比如你自己设置了加锁的那个锁 key 就是 “myLock, ARGV[1] 代表的是锁 key 的默认生存时间,默认 30 秒。ARGV[2] 代表的是加锁的客户端的 ID,类似于下面这样:285475da-9152-4c83-822a-67ee2f116a79:52。至于最后面的一个 1 是为了后面可重入做的计数统计,此时,如果客户端 2 来尝试加锁,会如何呢?首先,第一个 if 判断会执行 exists myLock,发现 myLock 这个锁 key 已经存在了。接着第二个 if 判断,判断一下,myLock 锁 key 的 hash 数据结构中,是否包含客户端 2 的 ID,这里明显不是,因为那里包含的是客户端 1 的 ID。所以,客户端 2 会执行:

1
return redis.call('pttl', KEYS[1]);

返回的一个数字,这个数字代表了 myLock 这个锁 key 的剩余生存时间。返回 null 则说明加锁成功。

Watch Dog 机制其实就是一个后台定时任务线程,获取锁成功之后,会将持有锁的线程放入到一个 RedissonLock.EXPIRATION_RENEWAL_MAP里面,然后每隔 10 秒 (internalLockLeaseTime / 3) 检查一下,如果客户端 1 还持有锁 key(判断客户端是否还持有 key,其实就是遍历 EXPIRATION_RENEWAL_MAP 里面线程 id 然后根据线程 id 去 Redis 中查,如果存在就会延长 key 的时间),那么就会不断的延长锁 key 的生存时间

DynamicRateLimiter 对 RateLimiter 封装了一层,底层实现还是 RateLimiter。与 Semaphore 不同的是:Semaphore 限制并发访问的次数,而 RateLimiter 限制并发访问速率。

RateLimiter

参考了令牌桶算法。漏桶算法的实现往往依赖于队列,请求到达如果队列未满则直接放入队列,然后有一个处理器按照固定频率从队列头取出请求进行处理。如果请求量大,则会导致队列满,那么新来的请求就会被抛弃。令牌桶算法则是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。桶中存放的令牌数有最大上限,超出之后就被丢弃或者拒绝。当流量或者网络请求到达时,每个请求都要获取一个令牌,如果能够获取到,则直接处理,并且令牌桶删除一个令牌。如果获取不到,该请求就要被限流,要么直接丢弃,要么在缓冲区等待。

令牌桶限制的是平均流入速率,允许突发请求,只要有令牌就可以处理,支持一次拿多个令牌;漏桶限制的是常量流出速率,即流出速率是一个固定常量值,从而平滑突发流入速率

ddd

代码角度:

  • 瘦实体模型:只起到数据类的作用,业务逻辑散落到service,可维护性越来越差;

  • 面向数据库表编程,而非模型编程;

  • 实体类之间的关系是复杂的网状结构,成为大泥球,牵一发而动全身,导致不敢轻易改代码;

  • service类承接的所有的业务逻辑,越来越臃肿,很容易出现几千行的service类;

分为限界和上下文。限界就是领域的边界,而上下文则是语义环境。 通过领域的限界上下文,我们就可以在统一的领域边界内用统一的语言 进行交流,引入限界上下文(Bounded Context)对问题域进行合理的分 解,识别出核心领域(Core Domain)与子领域(SubDomain),并确定领域的边界以及它 们之间的关系,维持模型的完整性。 2.架构方面:通过分层架构来隔离关注点,尤其是将领域实现独立出来,能够更利于领域 模型的单一性与稳定性

  • 精细化运营
  • 提供可复用的原子能力,后续的换货、投诉等可以复用这些能力,无需重复开发

资金域

资金域是为了解决在逆向交易过程中交易资金处理。其核心的作用是收敛各种退资金、退营销的链路以及业务规则。避免资金的处理细节逻辑耦合进入基础退换货逻辑。同时沉淀基础的中台交易逆向资金基础能力,为各种极致保障售后权益提供丰富的资金能力

协商(域)

这里特指当买家发起某项售后服务后(比如申请退款),需要卖家同意买家的申请,才能退款给买家,如果中间卖家对申请有疑问,可以拒绝买家的申请,或者提起平台介入,进入到买家家,平台三方的纠纷流程,最终完整整个售后服务的过程。

诉求(域)

指代售后过程中买卖家想要达成的内容,比如针对退款,买家想不退货并且拿回全额货款(100元)。但卖家可以建议是退货才拿起全部退款,或者是不退货,只退部分货款, 沉淀在电商服务中消费者的诉求点,助力业务识别售后过程中消费者的问题点是什么,问题的解决是否顺畅,从而能够在细分场景帮助业务去优化快手电商的整体服务体验

image-20230821230613406

1. 用户接口层(Controller层)

用户接口层负责向用户显示信息和解释用户指令。

2. 应用层(Service层)

应用层是很薄的一层,理论上不应该由业务规则或逻辑,主要面向用例和流程相关的操作。也可以完成

  • 编排多个聚合服务和领域对象完成业务操作;
  • 调用其他微服务的应用服务,完成微服务之间服务的组合和编排;

应用服务是在应用层的,它负责服务的组合、编排和转发,负责处理业务用例的执行顺序以及结果的拼装,以粗粒度的服务通过 API 网关向前端发布。还有,应用服务还可以进行安全认证、权限校验、事务控制、发送或订阅领域事件等。

3. 领域层(domain层)

领域层的作用是实现企业核心业务逻辑,通过各种校验手段保证业务的正确性。领域层主要体现领域模型的业务能力,它用来表达业务概念,业务状态和业务规则;

领域层包含聚合根,实体,值对象。领域服务等领域模型中的领域对象;

领域模型的业务逻辑主要是由实体和领域服务来实现的:

  • 实体会采用充血模型来实现所有与之相关的业务功能。
  • 实体和领域对象在实现业务逻辑上是同级的,当领域中的某些功能,单一实体(或者值对象)不能实现时,领域服务就会出马他可以组合聚合内的多个实体(或者值对象),实现复杂的业务逻辑。

4. 基础层(Repository)

基础层是贯穿所有层的,它的作用就是为其它各层提供通用的技术和基础服务,包括第三方工具、驱动、消息中间件、网关、文件、缓存以及数据库等。比较常见的功能还是提供数据库持久化。

基础层包含基础服务,它采用依赖倒置设计,封装基础资源服务,实现应用层、领域层与基础层的解耦,降低外部资源变化对应用的影响。

在传统架构设计中,由于上层应用对数据库的强耦合,很多公司在架构演进中最担忧的可能就是换数据库了,因为一旦更换数据库,就可能需要重写大部分的代码,这对应用来说是致命的。那采用依赖倒置的设计以后,应用层就可以通过解耦来保持独立的核心业务逻辑。当数据库变更时,我们只需要更换数据库基础服务就可以了,这样就将资源变更对应用的影响降到了最低。

public abstract class AbstractEvent {

/**

  • 执行
    */
    @Override
    public void doAction(Context context) {
    // 参数/幂等校验
    boolean validation = validation(context);
    if (!validation) {
    return;
    }
    //参数构建
    buildParam(context);
    // 执行业务逻辑
    execute(context);
    afterExecute(context);
    }
    }
    public class QueryFundEvent extends AbstractEvent {

@Override
public void doAction(QueryFundContext context) {
super.doAction(context);
}
//重写三个方法
}

策略模式的核心思想是对算法进行封装,委派给不同对象来管理。这样,我们就可以定义一系列算法,将每个算法 封装到具有公共接口的一系列具体策略类中,从而使它们可以灵活替换,并让算法可以在不影响到客户端的情况下发生变化。同时,策略模式仅仅封装算法(包括添加、删除),但其并不决定在何时使用何种算法,算法的选择由客户端来决定

  • 具体策略类之间可自由切换,由于具体策略类都实现同一个抽象策略接口,所以它们之间可以自由切换。

  • 符合“开闭原则”,扩展增加一个新策略时只需添加一个具体策略类即可,不需要改变原有的代码。

  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想

    缺点

  • 客户端必须知道所有的具体策略类,并理解不同具体策略的区别、自行决定使用哪一个策略类。(这一点可以通过本文介绍的方案解决)

  • 策略模式将产生很多具体策略类,在一定程度上增加了系统中类的个数