分布式锁和事务的优先级问题

如果service层中某个方法需要用到事务,且需要加分布式锁,则一般事务在内层,锁在外层,

避免出现已经解锁了但是事务还没提交,从而出现优惠券超领的情况

所以一般都是事务在service层中添加,但是分布式锁在controller层中添加

  @ApiOperation("领取优惠券")
    @GetMapping("/add/promotion/{coupon_id}")
    public JsonData addPromotionCoupon(
            @ApiParam(value = "优惠券id",required = true)
            @PathVariable("coupon_id") long couponId){

        LoginUser loginUser = LoginInterceptor.threadLocal.get();

        // 通过控制key来控制锁的粒度,key里面加上userid后将锁粒度缩小到每个用户
        String lockKey = "lock:coupon:"+couponId+":"+loginUser.getId();

        RLock rlock = redissonClient.getLock(lockKey);
        // 多个线程进入会阻塞,等待锁释放
        rlock.lock();
        // 调用 service中的方法含有事务,需要将事务放在锁里面
        
        try {
            JsonData jsonData = couponService.addCoupon(couponId, CouponCategoryEnum.PROMOTION);
            return JsonData.buildSuccess(jsonData);
        }finally {
            rlock.unlock();
            log.info("解锁成功");
        }
  
       
    }

缓存

1、springcache缓存框架

        框架提供了注解的方式来实现缓存。在提供事务的回滚的同时,缓存也自动回滚。

核心是:一个是Cache接口,缓存操作的api,一个是cachemanage管理各类缓存,有多个缓存缓存框架的实现。

使用:

1、在conmoon项目里面添加依赖

     <!--springcache 依赖包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

2、在主配置文件里面配置缓存类型  (spring的下一级)

  cache:
    type: redis

3、启动类开启缓存可用

@EnableCaching

4、定义序列化器      放在common的config里面


/**
 * 缓存的序列化器
 */

@Configuration
@EnableCaching
public class CustomRedisCacheManager extends CachingConfigurerSupport {
    
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(){
        Jackson2JsonRedisSerializer<Object>
                jackson2JsonRedisSerializer = new
                Jackson2JsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration
                =
                RedisCacheConfiguration.defaultCacheConfig();
        
        configuration = configuration.serializeValuesWith(
                RedisSerializationContext.SerializationPair
                        // TODO Duration.ofMinutes(1)是指定缓存的过期时间
                        .fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofMinutes(1));
        return configuration;
    }
}

5、cache中常用的几个注解

@CachePut   // 应用到写数据的方法上,如新增、修改方法,调用方法时会自动把响应的数据放入缓存中
@CacheEvict  // 情况对应的缓存
@Cacheable  //  读取的数据放到缓存里面
@Caching   //组合上面的三个注解进行使用

 

 

 @Cacheable(value = {"coupon"},key = "#root.methodName+#page+'_'+#size")

其中key是通过获取方法中的参数来设置key,key里面拼接字符串需要用单引号

缓存的使用

缓存和数据库是一定会有不一致的,这一点无法避免。因此是不是用缓存,需要看业务是否能接受短暂的不一致出现。因此只能是基于base理论达到最终的一致性。

微服务只读接口使用了缓存:

使用缓存一般需要在内网进行压测,暂停其他高负载进程,这样可以避免带宽受限的影响。

压测结果:基于内网压测,在4核8G的设备中,(对于链路不长的接口)比如商品列表、优惠券列表、个人中心 这些只读情况下的接口,单机qps可以达到2~3万;

其他的写接口:下单接口,Tps在100~400之间;支持rancher自动化扩容,对突发流量支持扩容,恢复正常后是会缩容。不能无限扩容的原因,商品、用户、优惠券这些微服务可以水平扩容,但是数据库不能水平扩容,优化的方式是可以实现一个读写分离,一主一从的架构,来实现数据库的优化。

下单接口涉及到优惠券、商品库存、发货地、优惠券抵扣、支付、上下游依赖这些内容,可以利用线程池或者异步的操作进行优化,但是整体性能还是有限。

          注册接口:一般是1000,如果对这个服务部署了集群,也可达到上万。

         登录、收藏、加购物车(走redis缓存),在1万左右,这些服务都支持自动化扩容和监控的思想,实现自动化扩容是Nginx的通过监控PV、RT、UV这些指标,然后采集这部分日志,然后输送到监控平台,监控平台通过同比、环比会触发扩容或者缩容,让rancher调度扩容和缩容的服务。(文档574页)

<think>好的,我现在需要帮助用户了解分布式锁的实现方式技术方案。首先,用户已经提供了一些站内引用,里面提到了基于Redis的实现、RedLock以及Zookeeper的方案,还有Redisson库的使用。我需要结合这些信息,以及用户之前的问题回答,来构建一个全面的回答。 首先,我需要确定用户的需求。用户之前询问了调度工具,现在转向分布式锁,可能是在系统设计中遇到了协调多个服务或节点的问题。用户可能是在开发分布式系统,需要确保资源的互斥访问,所以需要了解不同的分布式锁实现方法及其优缺点。 接下来,我要回顾之前的引用内容。引用[1]提到Redis分布式锁的基本实现,使用setnx指令,但存在锁无法释放的问题,因此需要设置过期时间。引用[2]详细比较了Redis单实例、RedLockZookeeper方案,强调了Redis的AP属性Zookeeper的CP属性。引用[3]则展示了Redisson库的使用,手动设置过期时间的问题,以及可能存在的锁过期导致的问题。 用户现在想了解实现方式技术方案,我需要将这些内容整合起来,分门别类地介绍不同的实现方案,并比较它们的优缺点。同时,根据之前的回答结构,用户可能期望清晰的分类,比如基于数据库、Redis、Zookeeper等,以及各自的原理、实现步骤、优缺点适用场景。 还需要注意用户提到的技术选型建议,比如根据一致性需求选择CP或AP系统,性能要求等。此外,用户可能对如何选择适合自己场景的方案感兴趣,所以需要给出选型建议,帮助用户决策。 另外,用户之前的回答中有相关问题部分,我需要生成相关问题,引导用户进一步思考,比如不同方案的对比,具体实现细节,或者与其他技术的结合使用等。 需要确保回答结构清晰,使用适当的格式,比如分点、加粗标题、代码示例等,但根据用户的要求,避免使用markdown格式。不过用户给出的示例中使用了markdown,但当前问题中用户要求用中文回复,并且之前的回答也用了markdown,可能用户希望保持一致的格式。不过需要确认是否允许使用markdown,但根据当前指令,用户可能希望避免复杂的格式,所以可能需要简化。 最后,需要检查引用是否正确,是否在适当的位置添加引用标识,比如在提到Redis锁的问题时引用[1][2],在Redisson时引用[3],以及在Zookeeper部分引用[2]。 现在开始组织内容: 1. 概述分布式锁的作用核心要求。 2. 分点介绍不同实现方案:基于数据库、Redis、Zookeeper、etcd,以及Redisson的高级实现。 3. 每个方案详细说明原理、实现步骤、优缺点、适用场景。 4. 技术选型建议,对比不同方案的优缺点,帮助用户选择。 5. 相关问题部分,生成相关问题供用户进一步探索。 需要确保每个部分的信息准确,特别是引用中的内容,如RedLock的问题,Zookeeper的CP特性,以及Redisson的看门狗机制。同时,注意用户之前的问题中提到的调度工具,可能分布式锁在任务调度中有应用,可以适当提及,但主要聚焦在锁的实现上。 可能还需要补充一些其他实现方案,如基于etcd,因为用户之前的回答提到了etcd作为分布式协调工具,但引用中没有提到,所以需要根据知识库添加。 最后,确保语言简洁明了,结构清晰,符合用户要求的“结构清晰,帮助用户逐步解决问题,保证回答真实可靠”。</think>### **分布式锁实现方案深度解析** --- #### **一、核心需求与挑战** 分布式锁需满足以下基本要求: - **互斥性**:同一时刻仅一个客户端持有锁 - **容错性**:锁服务节点宕机时仍能正常运作 - **可重入性**:同一客户端可多次获取锁 - **自动续期**:避免任务未完成时锁过期 - **高可用性**:避免单点故障导致锁服务不可用[^1] --- #### **二、主流实现方案对比** --- ##### **1. 基于数据库的实现** **原理**:利用数据库唯一约束或行锁实现 **实现步骤**: 1. 创建锁表,定义`lock_name`为唯一索引 2. 获取锁:`INSERT INTO locks(lock_name) VALUES ('order_lock')` 3. 释放锁:`DELETE FROM locks WHERE lock_name='order_lock'` **优点**:实现简单,无需额外中间件 **缺点**: - 性能低(数据库IO延迟高) - 无自动过期机制,需手动清理死锁 - 高并发场景下数据库压力大 **适用场景**:低频操作或技术验证阶段 --- ##### **2. 基于Redis的实现** **核心指令**:`SET resource_name random_value NX EX 30` **优化方案**: - **看门狗机制**:Redisson客户端自动续期(默认30秒检测,续期至30秒)[^3] - **Lua脚本保证原子性**: ```lua if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end ``` **架构选择**: - **单节点Redis**:高性能但存在单点故障风险 - **RedLock算法**:需半数以上节点加锁成功,牺牲部分性能换取可用性[^2] **典型问题**: - 主从切换时可能重复获得锁 - 时钟漂移导致锁过期时间计算错误 - 锁提前释放导致数据竞争(需验证锁标识)[^2] **适用场景**:高并发AP系统(如电商库存扣减) --- ##### **3. 基于ZooKeeper的实现** **实现原理**:通过顺序临时节点实现 **操作流程**: 1. 创建临时顺序节点:`create -e -s /lock/order-` 2. 检查是否为最小序号节点 3. 注册Watcher监听前序节点 4. 删除节点释放锁 **优势**: - 天然支持锁自动释放(会话断开时节点自动删除) - 严格保证先来后到的顺序性 - 无锁过期时间管理负担[^2] **性能瓶颈**: - 写操作需半数以上节点确认 - Watcher通知存在延迟(通常1-2秒) **适用场景**:CP系统(如金融交易系统) --- ##### **4. 基于etcd的实现** **核心机制**:利用租约(Lease)事务(Txn) **操作示例**: ```bash # 创建租约(有效期20秒) etcdctl lease grant 20 # 原子化事务加锁 etcdctl txn --compares= 'mod("/mylock")="0"' --successs= 'put /mylock owner1 --lease=12345' --failure= 'get /mylock' ``` **特性对比**: | 特性 | etcd | ZooKeeper | |---------------------|---------------|---------------| | 一致性协议 | Raft | ZAB | | 数据模型 | 键值存储 | 树形节点 | | 锁实现复杂度 | 中 | 高 | | 读写性能 | 10k+ QPS | 5k-10k QPS | --- ##### **5. Redisson高级实现** **核心功能**: - 自动续期:通过`WatchDog`线程每10秒检查锁状态 - 锁重试机制:通过`Pub/Sub`监听锁释放事件 - 公平锁实现:基于Redis队列维护请求顺序 **锁类型对比**: | 锁类型 | 特性 | 适用场景 | |--------------|-------------------------------|-----------------------| | 普通锁 | 非公平竞争 | 常规并发控制 | | 公平锁 | 按请求顺序获取 | 需严格顺序的场景 | | 联锁(MultiLock) | 需所有子锁同时获得 | 多资源原子操作 | | 红锁(RedLock) | 跨多个Redis实例加锁 | 高可用环境 | --- #### **三、技术选型建议** 1. **一致性优先级**: - **CP系统**:选择ZooKeeper或etcd(如银行转账系统) - **AP系统**:选择Redis(如社交平台点赞计数) 2. **性能需求**: - 10万级QPS:Redis/etcd - 万级QPS:ZooKeeper 3. **运维成本**: - Redis集群维护成本低于ZooKeeper - etcd天然适合Kubernetes生态 --- ### **相关问题** 1. RedLock算法在跨时区部署时可能遇到什么问题? 2. 如何设计ZooKeeper分布式锁的容灾恢复机制? 3. etcd与Consul在分布式锁实现上的差异是什么? 4. 在Serverless架构中如何实现无状态服务的分布式锁? [^1]: 基于数据库的锁实现存在性能瓶颈与死锁风险 : Redis与ZooKeeper分别代表AP/CP两种设计哲学 [^3]: Redisson通过看门狗机制优化了锁续期问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zero _s

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值