IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 多用户并发操作的解决方案同步锁的具体实施方法 -> 正文阅读

[大数据]多用户并发操作的解决方案同步锁的具体实施方法

一:并发操作出现的原因

原因:多个动作在一瞬间同时操作同一数据

现象:

  1. 多人在某一瞬间通过相同的方式操作同一条数据
  2. 多人在某一瞬间通过不同的方式操作同一条数据
  3. 在某一瞬间,同一动作,多次执行

二:并发举例及解决办法

针对上述的三种的情况,分别以实际情况进行举例。

【多人在某一瞬间通过相同的方式操作同一条数据】

1.某仓库系统有一品牌商品A,商品A在数据库中只允许存在一条记录,库存的数量是这条数据的一个字段,现有库存100件,在某一天到货了1000件。由于数量比较大,现在需要10名操作员去处理这1000件商品进行入库,操作的途径都是使用PDA扫描完成后进行入库。我们假设至少存在1名以上的操作员同时进行入库操作。这样就可以满足上述条件【多人在某一瞬间通过相同的方式操作同一条数据】。在这种情况下,如果不进行处理,就会导致数据错乱,错乱的原因简单说就是在双方写数据时没有获取到最新的数据库数据。

解决方法:

方法一: 加锁。加锁是比较常用的方法。从系统的架构上来说,锁被分为单机锁和分布式锁。如果系统只是部署在单一机器上,可以简单通过java提供的各种锁来进行操作。如果系统被部署在多台机器上,可以使用redis来实现分布式加锁。这两种加锁方式从某种意义上来说是悲观锁。上述的问题,我们可以使用商品的唯一属性,比如id或者商品的唯一条码来进行加锁。

方法二:数据库乐观锁。数据库乐观锁几乎适用于所有的并发场景。使用方法:在数据库表中增加一个版本号字段,每一次更新和删除时把当前持有的对象版本号和数据库中最新的版本号进行比对,如果相同则验证通过,不然则操作失败。

方法三:使用消息队列。这种方式在消息过多时,对库存的处理可能不会特别及时。由于库存一般是需要比较及时的可见,所以这种方式并不建议。

【多人在某一瞬间通过不同的方式操作同一条数据】

2. 还是按照上述的背景来说。在这10名操作员进行入库的同时,还有至少1名操作员对A商品进行出库操作。我们假设入库时没有并发问题,但是其中一个入库和一个出库同时操作了A商品的库存,通过两种不同的方式对库存进行操作。如果不进行处理,库存也会出现数据错乱的问题。

解决方法:

方法一: 加锁。这个时候使用普通的单机锁已经没有意义了,可以使用分布式锁,依旧使用唯一属性来进行加锁,尽管方法不同,但关键的key是一样的,这样就可以锁住操作。

方法二:数据库乐观锁。

对于上述的问题,我扩展一下,如果是一批商品,你总不能一个一个进行加锁处理吧,那样效率也太低了。所以这种情况下,简单的加锁已经不能满足现在的需要了。所以数据库乐观锁又重新出现了。在批量更新时,发现其中任何一个商品的版本号不一致,立即报错回滚。
?

本篇文章就是对加锁方案的落地:

创建redis锁工具类:

@Slf4j
@Component
public class RedisLock {

    private final long lockTime = 10;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean tryGetLock(String key, String value) {
        return tryGetLock(key, value, 1000);
    }

    public boolean tryGetLock(String key, String value, long waitMillis) {
        long start = System.currentTimeMillis();
        Preconditions.checkArgument(waitMillis <= lockTime * 1000);
        checkKeyPrefix(key);
        Boolean getLock = redisTemplate.opsForValue().setIfAbsent(key, value, lockTime, TimeUnit.SECONDS);
        if (getLock != null && getLock) {
            return true;
        } else {
            return tryGetLock(key, value, waitMillis, start);
        }
    }

    private boolean tryGetLock(String key, String value, long waitMillis, long start) {
        while (true) {
            long end = System.currentTimeMillis();
            if (end - start > waitMillis) {
                return false;
            }

            Boolean getLock = redisTemplate.opsForValue().setIfAbsent(key, value, lockTime, TimeUnit.SECONDS);
            if (getLock != null && getLock) {
                return true;
            } else {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    log.error("getRedisLock InterruptedException, ", e);
                }
            }
        }
    }

    public void releaseLock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Collections.singletonList(key), value);
    }

    private void checkKeyPrefix(String key) {
        Objects.requireNonNull(key);
        Arrays.stream(RedisLockKeyPrefix.values()).filter(prefix -> key.startsWith(prefix.name())).findFirst().orElseThrow(() ->
            new IllegalArgumentException("请定义锁前缀"));
    }


}

消耗额度,同时只能有一个人操作。

@Override
    @Transactional
    public ResultVO<Object> createSitesTask(CreateSiteTaskArg createSiteTaskArg, User user) {
        String lockKey = SITE_CHOOSE_LOCK_PREFIX + LOCK_KEY_CONCAT + user.getUserName() + LOCK_KEY_CONCAT + createSiteTaskArg.getBrandId()
                + LOCK_KEY_CONCAT + createSiteTaskArg.getSiteChooseWay();
        String lockValue = UUID.randomUUID().toString();
        try {
            //有其他相同账户在操作,则先暂时不让当前用户操作,等5秒钟
            if (!redisLock.tryGetLock(lockKey, lockValue, 2000)) {
                throw new IndustryException(ErrorCode.SYSTEM_BUSY);
            }

            // 校验用户选址剩余次数
            List<IndustryAuthorityPO> authorityPOList = checkAndGetRestSite(createSiteTaskArg, user.getUserName());

            // 扣除用户选址次数
            Map<String, RecordPO> records = reduceRestSites(authorityPOList, createSiteTaskArg.getSites().size(),
                    createSiteTaskArg.getSiteChooseWay(), user.getUserName());

            // 调用openapi发起选址预测
            SiteSelectionDTO siteSelectionDTO = buildSiteSelection(createSiteTaskArg);
            SiteSelectAsynVO siteSelectAsynVO = SdkUtils.getData(openapiClient.execute(siteSelectionDTO)).getData();

            // 保存扣除记录
            SiteChooseRecordPO siteChooseRecordPO = buildSiteChooseRecord(createSiteTaskArg, siteSelectAsynVO.getTaskId(),
                    user.getUserName(), records);
            industrySitesChooseRecordDAO.save(siteChooseRecordPO);

            // 保存选址预测任务信息
            List<SiteChoosePO> siteChoosePOList = buildSiteChooseList(createSiteTaskArg, siteSelectAsynVO, user.getUserName());
            industrySitesChooseDAO.insertAll(siteChoosePOList);
        } finally {
            redisLock.releaseLock(lockKey, lockValue);
        }

        return ResultVO.success(null, "创建分析任务成功");
    }

操作完及时释放锁!

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-02-04 11:06:05  更:2022-02-04 11:07:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:30:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码