解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验)

关于高并发时的线程安全问题

  • 在项目开发的过程中,一次偶然的测试(当时网络环境不好),模拟出了高并发的问题。出现了数据的脏读。
  • 虽然项目的定位在实际使用过程中,高并发的情况很少,但是在测试的环境中已经发现了这个问题,还是有必要把这个潜在的问题给解决了。
  • 为了解决这个高并发的问题,前后选择了三种方式:
    • 第1种是通过关键字synchronized来对代码块进行加锁;但是这种方式的加锁性能比较低下,最终也是没有采用;
    • 第2种方式是通过redis来实现;redis的加锁性能是比synchronized的性能好了很多,但是在该微服务中,没有用到redis,考虑到如果只是为了加锁来维护一个redis,不太划算,也就放弃了;最终采用了第3种方式。
    • 第3种方式是通过线程安全的set集合来实现。
使用线程安全的set集合具体的加锁,解锁的解读
  1. 首先在成员变成获取一个私有的线程安全的集合,获取方式可以使用以下两种方式,我在项目中才用的第一种方式获取
  /**
     * 成员位置初始化线程安全的set集合
     */
    //方式1: 该方式是在1.8中出现的,比较新的获取方式
    private Set<String> lock = ConcurrentHashMap.newKeySet();
   //方式2: 该方式是在1.6中出现的,相比上面的一种方式比较老
    private Set<String> oldStyle = Collections.newSetFromMap(new ConcurrentHashMap<>());

2.在你需要加锁的代码前进行加锁,然后将需要加锁处理的集合try起来,保证在该段代码执行发生异常的时候能够将锁释放掉,接下来直接贴代码。

// 加锁 使用线程安全的ConcurrentHashSet加锁来解决高并发问题
        if (!lock.add(ADD_PREFIX + tenantId + parent)) {
            System.out.println(("重复请求" + Thread.currentThread().getName() + "调用失败"));
            return new BaseVO(BaseVO.CONFLICT_CODE, "对不起,目前系统繁忙,请稍后重试");
        } else {
            System.out.println(("线程" + Thread.currentThread().getName() + "调用成功"));
        }

        try {
            // 获取该节点下的所有nodeName
            allNodeName = uniquevalidateClient.getNodeNameOrFieldValueFromTree(tenantId, treeId, "nodeName", parent);
            // 创建新添加节点的nodeName和扩展属性值
            BaseVO uniqueFields = createUniqueFields(tenantId, treeId, nodeName, jsonNode, jsonObjects, newNode);
            // 真正处理新增节点的方法
            BaseVO results = handleAddNode(tenantId, grantId, treeId, sourceNodeId, type, parent, nodeName, fields,
                    allNodeName, id, jsonNode, jsonObjects, uniqueFields);
            // 失败时回滚数据
            if (results.getCode() != BaseVO.SUCCESS_CODE) {
                uniquevalidateClient.deleteFormDocument(tenantId, treeId, "nodeName", id, 0);
            }
            return results;
        } finally {
            // 释放锁
            lock.remove(ADD_PREFIX + tenantId + parent);
        }

主要逻辑:大家都知道,set集合是不允许重复的,我们通过成员变量初始化的线程安全的set集合(在博客中该集合的名称为lock),将能够标识线程唯一性的值添加进该集合。在高并发的环境下,通过往lock中添加线程唯一性标识的返回值来判断锁的获取情况。打个比方在同一个接口出现10个并发,如果第1个请求获取到了锁,其余的9个请求获取锁失败,将该失败信息抛给用户,通过用户的重试来达到削峰的目的。

压力测试(模拟高并发检验set集合实现的锁)
  • 在压测前,介绍一个压测软件(postJson),当然了也是朋友推荐给我的。

    • 官网地址:http://coolaf.com/doc/index
    • 个人网盘地址:链接:https://pan.baidu.com/s/12BsaDRla5-md-f5zmXSfNg
      提取码:y319
  • 下载下来以后解压就可以使用,点击下图中的exe文件启动
    解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验)_第1张图片

    • 启动完成的界面是这样的,然后点击本地post请求与压力测试
      解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验)_第2张图片
  • 切换到压力测试,开始测试
    解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验)_第3张图片

  • 填写接口参数以及并发次数进行压力测试
    解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验)_第4张图片
    在这里我压测的参数填写的时我输入的请求数为200,并发数为20,意思是10个客户端请求,每个客户端20个并发。

  • 以下为接口测试结果

  • 图1解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验)_第5张图片

  • 图 2
    解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验)_第6张图片

  • 图1可知:在压力测试的时候,第十条线程首先获得锁,执行业务逻辑,在执行业务逻辑的郭城成中其他的线程获取锁全部失败。

  • 图2可知:首先获得锁的线程业务处理完毕,释放掉了锁,第20个线程获得锁,此时执行业务逻辑的时候其他线程获取锁失败(比如线程16).第20个线程通过逻辑判断,发现值已经在数据库存在,返回。200个请求全程只有一条数据写入。

以上的内容,是本人在开发中遇到的,实际应用的,如果有些的不周到的地方,恳请指正!
鸣谢:感谢驰哥哥提供的帮助!

你可能感兴趣的:(解决高并发的新姿势(通过线程安全的set集合来实现,使用压力测试模拟器检验))