Java项目的性能优化样例

这篇具有很好参考价值的文章主要介绍了Java项目的性能优化样例。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

场景一:高并发频繁的数据库访问

解决方案:

  1. 总所周知的是:加缓存,最常见的是:加缓存中间件如 Redis,当然了这里要说的不是这个,增加一个中间件多少有点费事儿;

  2. 通过Java类的方式解决
    POM添加jar包

    //添加依赖
    <dependency>
    		<groupId>com.google.guava</groupId>
    		<artifactId>guava</artifactId>
    		<version>20.0</version>
    	</dependency>
    
    

    //在 service 层或者 DAO 层创建了一个名为 consumerCache 的缓存池,这里用Optional包了一层是因为 Cache.get(Key)方法不能返回和存储null,如果是null会报错,具体原因感兴趣的可以看看 Cache 的源码

    
    //导包路径 import com.google.common.cache.CacheBuilder;
    //		  import com.google.common.cache.Cache;
    private final Cache<Long, Optional<List<PartnerCallbackDef>>> consumerCache = CacheBuilder
            .newBuilder()
            .maximumSize(1000) //最大数量
            .expireAfterWrite(3, TimeUnit.MINUTES) //缓存过期时间和时间单位
            .build();
    
    
    

    在查数据库的时候先尝试从缓存中获取,如果缓存中没有再执行数据库查询,将数据库查询结果缓存并返回,发生异常时会捕获并记录日志,然后返回一个空的Optional对象,在接受方法返回结果进行下一步处理的时候用 isPresent() 判断一下就好了。

     private Optional<List<PartnerCallbackDef>> getConsumerCache(final Long key) {
        try {
            return consumerCache.get(key, () -> Optional.ofNullable(partnerCallbackDefDao.getByTypeAndPartnerId(Constants.CALLBACK_DEF_TYPE_CONSUMER, key)));
        } catch (ExecutionException e) {
            LogUtils.error(log, "读取缓存数据异常", e);
            return Optional.empty();
        }
    }
    

    PartnerCallbackDefDao 中的方法 getByTypeAndPartnerId 没有什么特殊逻辑,就是对数据库的访问

    public List<PartnerCallbackDef> getByTypeAndPartnerId(String type, Long partnerId) {
        return mapper.getByTypeAndPartnerId(type, partnerId);
    }
    

    这样很简单的就实现了对数据库访问的缓存,在调用getConsumerCache方法获取指定ID的数据时,会先从缓存中获取,缓存中获取不到或缓存过期时才会从数据集中从新拉取,并且在 Cache.get(Key) 方法中,只要我们保证方法的入参 Key 是线程安全的,那么方法 Cache.get(Key) 就是线程安全的。

场景二:大数据量的同步

解决方案:
思路:
1. 首先我们可以将数据量进行分割来分批次进行同步,以防止大量的数据一次性打到数据库对数据库造成压力;
2. 在Controller层做校验,用redis存储请求数据,过滤掉重复的请求内容,以实现接口的幂等校验;还可以做请求的数据量校验和限流
3. 根据实际业务需求考虑同步方式是增量同步还是全量同步,其中增量同步的实现方式是将接收到的请求数据进行数据库的过滤,只进行新增和修改操作;而全量同步则是不做过滤处理,将接收到的请求数据全部保存到数据库。

代码:文章来源地址https://www.toymoban.com/news/detail-474957.html

数据分割工具类
import com.google.common.collect.Maps;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @author: luce
 * @Date: 2023/4/20 14:08
 * @Description:
 */
public class SplitUtil {

	/**
     * 按指定大小进行分割
     * @param num 分割大小
     * @param dataList 需要分割的集合
     * @param <T>
     * @return
     */
    public static <T> Map<Integer, List<T>> splitData(Integer num, List<T> dataList) {
        HashMap<Integer, List<T>> map = Maps.newHashMap();
        int size = dataList.size();
        //分割大小
        int count = SplitUtil.countNumBydivisor(size, num);
        for (int i = 0; i < count; i++) {
            int lastNum = num * (i + 1);
            LinkedList<T> linkedList = new LinkedList<>();
            for (int j = num * i; j < lastNum && j < size; j++) {
                linkedList.add(dataList.get(j));
            }
            map.put(i, linkedList);
        }
        return map;
    }


    /**
     * @Description:按除数分割
     * @params: [size , divisor]
     * @return: int
     * @Author: jiangpan
     * @Date: 2018/12/23 20:56
     **/
    public static int countNumBydivisor(int size, int divisor){

        int num = size % divisor;
        if (num > 0) {
            num = size / divisor + 1;
        } else {
            num = size / divisor;
        }
        return num ;
    }
}
使用样例:  Map<Integer, List<UserDTO>> userMap = SplitUtil.splitData(splitSize, userDTOList);
  		  userMap.values().forEach(userList -> {
        	TemResponseDTO<Void> res = temOpenapiHttpClient.syncUser(openapiToken, userList);
        	log.info("人员同步返回结果:{}", res.getErrorDescription());
 		  });
幂等处理参考
/**
   * 获取并过滤redis中的数据
   * @param partnerId 请求的企业ID
   * @param userBaseGroundList 请求参数
   * @return 返回
   */
  public Set<UserBaseGround> getUserBaseGroundMd5Data(Long partnerId, List<UserBaseGround> userBaseGroundList) {
      Set<UserBaseGround> costSet = Sets.newLinkedHashSet();
      //父key
      String groundMd5Key = GROUND_MD5_KEY.replace("{0}", String.valueOf(partnerId));
      userBaseGroundList.forEach(v -> {
          //子key
          String childKey = v.getEmpCode() + v.getCityName() + v.getAddressType();
          String md5xString = DigestUtils.MD5_64bit(JSONObject.toJSONString(v));
          String value = RedisCacheUtils.get(groundMd5Key, childKey);
          if (StringUtils.isEmpty(value) || !Objects.equals(md5xString, value)) {
              costSet.add(v);
              RedisCacheUtils.hdel(groundMd5Key, childKey);
          }
      });
      return costSet;
  }
  
  /**
   * 保存MD5后的人员常驻地入参
   */
  public void setUserBaseGroundMd5Data(Long partnerId, UserBaseGround userBaseGround) {
      String groundMd5Key = GROUND_MD5_KEY.replace("{0}", String.valueOf(partnerId));
      String childKey = userBaseGround.getEmpCode() + userBaseGround.getCityName() + userBaseGround.getAddressType();
      String md5xString = DigestUtils.MD5_64bit(JSONObject.toJSONString(userBaseGround));
      RedisCacheUtils.hSet(groundMd5Key, childKey, md5xString, Integer.parseInt(userMd5Expired));
  }
使用样例:   
        //获取并过滤redis中的组织数据
        Set<UserBaseGround> userBaseGroundMd5Data = userBaseGroundCommon.getUserBaseGroundMd5Data(partnerId, userBaseGroundList);
        if (CollectionUtils.isEmpty(userBaseGroundMd5Data)) {
            LogUtils.info(logger, "hash过滤后无数据,用户常驻地同步成功");
            super.success("用户常驻地同步成功");
            return;
        }

	//将成功保存到数据库的请求数据缓存
    setUserBaseGroundMd5Data(partnerId, userBaseGround);
检验请求数据量
/***
     * 检验请求数据量
     * @param size
     * @param partnerId
     */
    protected void rateLimit(Integer size, Long partnerId) {
        String requestURI = getRequest().getRequestURI();
        String key = REQUEST_CURRENT_LIMIT + requestURI + partnerId;
        Long value = RedisCacheUtils.incr(key, Long.valueOf(size), 60);
        JSONObject resquestConfig = getResquestConCurrentConfig(requestURI);
        if (value > resquestConfig.getIntValue(INTERFACE_CONCURRENT_VALUE)) {
            LogUtils.warn(logger, "请求超过频率限制:请求接口地址:{}, 企业id:{}, 缓存数量:{}, 当前同步数量:{}", requestURI, partnerId, value, size);
            // 去掉才加的数
            RedisCacheUtils.decr(key, value);
            throw new BizException(RATE_LIMITED.name(), RATE_LIMITED.getMessage());
        }
    }

    /**
     * 获取并发限制apollo中的配置
     *
     * @param requestURI
     * @return
     */
    protected JSONObject getResquestConCurrentConfig(String requestURI) {
        List<JSONObject> jsonObjects = JSONArray.parseArray(requestConCurrentLimit, JSONObject.class);
        return jsonObjects.stream()
                .filter(item -> item.getString(INTEFACE_PATH).equals(requestURI) == true)
                .findAny()
                .orElse(null);
    }

接口限流
/***
     * 接口限流
     * @param partnerId
     * @param expinedSecond
     */
    protected void frequencyLimit(Long partnerId, Integer expinedSecond) {
        if (expinedSecond == null) {
            expinedSecond = 60;//默认60s有效时间
        }
        String requestURI = getRequest().getRequestURI();
        String key = REQUEST_CURRENT_LIMIT + partnerId + ":" + requestURI;

        RedisSingleCache redisCache = (RedisSingleCache) RedisCacheUtils.getRedisCache();
        Jedis jedisCluster = null;
        try {
            jedisCluster = redisCache.getJedis();
            long calledTimes = StringUtils.isEmpty(jedisCluster.get(key)) ? 0L : Long.parseLong(jedisCluster.get(key)); //已调用次数
            int limitValue = getFrequencyLimitValue(requestURI); //配置的频率限制次数

            if (calledTimes >= limitValue) {
                LogUtils.warn(logger, "请求超过频率限制:请求接口地址:{}, 企业id:{}, 已调用数量:{},接口限制数量:{}",
                        requestURI, partnerId, calledTimes, limitValue);
                throw new BizException(RATE_LIMITED.name(), RATE_LIMITED.getMessage());
            }
            if (calledTimes != 0L) {
                Long ttl = RedisCacheUtils.ttl(key);
                if (ttl != null && ttl > 0) {
                    expinedSecond = ttl.intValue();
                }
            }
            RedisCacheUtils.incr(key, 1L, expinedSecond);
        } catch (Exception e) {
            LogUtils.warn(logger, "接口限流出现异常:{}", e);
            throw new BizException("请求失败,请联系管理员:{}", e.getMessage());
        } finally {
            if (Objects.nonNull(jedisCluster)) {
                jedisCluster.close();
            }
        }
    }

    //获取接口频率限制值,如果未配置则表示不限,返回Integer.MAX_VALUE
    private int getFrequencyLimitValue(String requestURI) {
        int rs = Integer.MAX_VALUE;
        JSONObject requestConfig = getResquestConCurrentConfig(requestURI);
        int value = requestConfig.getIntValue(INTERFACE_CONCURRENT_VALUE);
        if (value > 0) {
            rs = value;
        }

        return rs;
    }

增量同步实现

更新中....

到了这里,关于Java项目的性能优化样例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 高并发场景下的 HttpClient 优化,QPS 大大提升!

    HttpClient优化思路: 池化 长连接 httpclient和httpget复用 合理的配置参数(最大并发请求数,各种超时时间,重试次数) 异步 6、多读源码 我们有个业务,会调用其他部门提供的一个基于http的服务,日调用量在千万级别。使用了httpclient来完成业务。之前因为qps上不去,就看了一

    2024年02月03日
    浏览(42)
  • 高并发缓存实战RedisSon、性能优化

    对于经常访问的数据保留在redis缓存当中,不用带数据设置超时时间定期删除控制redis的大小 缓存击穿数据库没有被击穿 如果商家是批量导入的数据,呢么就会同时存到redis中,设置固定的时间就会导致缓存在一瞬间失效,用户访问不到就会将流量打到数据库上造成数据库段

    2024年02月13日
    浏览(61)
  • 高并发与性能优化的神奇之旅

    作为公司的架构师或者程序员,你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢?笔者在出道那会为此是吃尽了苦头的,不过也得感谢这段苦,让笔者从头到尾去探索,找寻解决之法。 目录 第一站:超越时间的加速法术 对此有何解决之法呢

    2024年02月14日
    浏览(39)
  • Go并发:使用sync.Pool来性能优化

    在Go提供如何实现对象的缓存池功能?常用一种实现方式是:sync.Pool, 其旨在缓存已分配但未使用的项目以供以后重用,从而减轻垃圾收集器(GC)的压力。 sync.Pool的结构也比较简单,常用的方法有Get、Put 接着,通过一个简单的例子,来看看是如何使用的 在之前的文章中有提

    2024年02月08日
    浏览(40)
  • 5. 一线大厂高并发缓存架构实战与性能优化

    本文是按照自己的理解进行笔记总结,如有不正确的地方,还望大佬多多指点纠正,勿喷。 课程内容: 1、中小公司Redis缓存架构以及线上问题分析 2、大厂线上大规模商品缓存数据冷热分离实战 3、实战解决大规模缓存击穿导致线上数据库压力暴增 4、黑客攻击导致缓存穿透

    2024年02月07日
    浏览(42)
  • 一线大厂Redis高并发缓存架构实战与性能优化

    我们都知道,一般的互联网公司redis部署都是主从结构的,那么复制基本都是异步执行的, 那就存在一个问题,当我们设置分布式锁的时候,还没来得及将key复制到从节点,主节点挂了,那么从节点会成为主节点,但是主节点的分布式锁key就会丢失掉,如果新线程进来执行同

    2024年02月08日
    浏览(43)
  • redis第五第六章-redis并发缓存架构和性能优化

    缓存穿透是指查询一个根本不存在的数据, 缓存层和存储层都不会命中, 通常出于容错的考虑, 如果从存储层查不到数据则不写入缓存层。 缓存穿透将导致不存在的数据每次请求都要到存储层去查询, 失去了缓存保护后端存储的意义。 造成缓存穿透的基本原因有两个:

    2024年02月08日
    浏览(53)
  • 大规模场景下对Istio的性能优化

    当前istio下发xDS使用的是全量下发策略,也就是网格里的所有sidecar(envoy),内存里都会有整个网格内所有的服务发现数据。这样的结果是,每个sidecar内存都会随着网格规模增长而增长。 aeraki-mesh项目下有一个子项目专门用来处理istio配置分发性能问题,我们找到该项目: http

    2024年02月10日
    浏览(44)
  • 高并发场景下大量TCP链接处于time_wait状态原因及优化思路分析

    对一台服务器进行压测(模拟高并发场景),会发现大量 TIME_WAIT 状态的 TCP连接,连接关闭后,这些TIME_WAIT会被系统回收 一般来讲,在高并发的场景中,出现TIME_WAIT连接是正常现象,一旦四次握手连接关闭之后,这些连接也就随之被系统回收了 但是在实际高并发场景中,很

    2024年02月04日
    浏览(72)
  • Redis缓存设计与性能优化【并发创建同一个缓存,解决方案:互斥锁】

    开发人员使用“缓存+过期时间”的策略既可以加速数据读写, 又保证数据的定期更新, 这种模式基本能够满足绝大部分需求。 但是有两个问题如果同时出现, 可能就会对应用造成致命的危害: 当前key是一个热点key(例如一个热门的娱乐新闻),并发量非常大。 重建缓存不

    2024年04月09日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包