经验、总结

  1. spring事务问题
    1. rocketmq + 事务一起使用
    2. compareTo
  2. lambda
  3. optional
  4. UML结构图
  5. https证书配置
  6. redis分布式锁问题
  • docker
    1. springboot 应用无缘无故被kill
  • google guava
  • spring事务问题

    https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247571139&idx=1&sn=86d4139ebfb75bcbdfc20270ecc93d73&chksm=fba43ec0ccd3b7d64658477ecea45e579f7bb76b6728dab7f2215c94932e16c6091ddf139cfd&scene=126&&sessionid=1670288650#rd

    1. 如果想使 Propagation.REQUIRES_NEW 生效,则需要把testB这个方法单独放到别的类中,或者通过aop contxt 获取对象,进行调用

    说明:同一个service中的两个方法互相调用都开启事务,被调用方的事务是不会生效的,是属于调用方的事务,不会开启新的事务

    1. 同一个service类中的两个方法互相调用,都开启事务,遇到错误两个都会回滚
    2. 同一个service类中的两个方法互相调用,调用方开启事务,被调用方不开启事务,出现异常都会回滚
    3. 同一个service类中的两个方法互相调用,调用方开启事务,被调用方开启事务,调用方catch被调用方异常,则两个都不会回滚,
    4. 同一个service类中的两个方法互相调用,调用方不开启事务,被调用方开启事务,出现异常不会回滚,通过aop contxt 获取对象,进行调用可以解决

    备注: 主要是当同一个service类中的两个方法互相调用,被调用的service没有被aop增强代理,不会开启事务

    1. 私有方法不生效

    2. 方法用final修饰

    3. 其实,我们在使用@Transactional注解时,是可以指定propagation参数的。

      该参数的作用是指定事务的传播特性,spring目前支持7种传播特性:

      • REQUIRED 如果当前上下文中存在事务,那么加入该事务,如果不存在事务,创建一个事务,这是默认的传播属性值。
      • SUPPORTS 如果当前上下文存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
      • MANDATORY 如果当前上下文中存在事务,否则抛出异常。
      • REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
      • NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
      • NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
      • NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
    4. Transaction rolled back because it has been marked as rollback-only 问题:

      https://www.cnblogs.com/nizuimeiabc1/p/14774125.html

    rocketmq + 事务一起使用

    rocketmq消费时开启事务,并且捕获异常,try的方法也开启事务注解,这样最终try的方法报错,rocketmq还是会重试5次,正确的应该是报错后就不再执行,消息消费完成,日志记录即可,但是没有,重试了5次
    原因:在外层开启了事务,内部也开启了事务,如果没有指定其他参数,则也会加入已经开启的事务中,内部执行报错,会被记录下来,在Spring的REQUIRED中,只要异常被Spring捕获到过,那么Spring最终就会回滚整个事务,即使自己在业务中已经捕获!

    为什么会影响到rocketmq进行重试?

    事务处理时链路有一个异常则最终会抛出异常,mq监听异常后返回重试状态

    jy

    解决方式:

    1. 调用方不开启事务,这样调用方捕获异常后,不会影响整个链路
    2. 被调用方整个方法trycatch 不抛出异常

    具体结合需求处理

    https://juejin.cn/post/7052607119606382600

    compareTo

    compareTo大于的话返回的是正整数,等于是0,小于的话就是负整数

    lambda

    分组功能 并且有序

    List pageList = storeInfoMapper.getPageList(page, patrolInfo);

    for (Map.Entry<Boolean, List> entrySet : pageList.stream().collect(Collectors.groupingBy(PatrolParentDTO::getIsParent, LinkedHashMap::new, Collectors.toList())).entrySet()) {

    }

    Map<String, List<String>> collect = storePointRelList.stream().collect(Collectors.groupingBy(MapperStorePointRel::getStoreId,
                    Collectors.mapping(MapperStorePointRel::getStoreId, Collectors.toList())));
    Map<String, LinkedBlockingQueue<String>> queueMap = storePointRelList.stream()
            .collect(Collectors.groupingBy(MapperStorePointRel::getStoreId,
                    Collectors.mapping(MapperStorePointRel::getStoreId, Collectors.toCollection(LinkedBlockingQueue::new))));
    Map<String, String> assetMap = astSgassetService.lambdaQuery().select(AstSgasset::getId, AstSgasset::getName).list().stream()
    .collect(Collectors.toMap(AstSgasset::getId, AstSgasset::getName, (v1, v2) -> v2));
    Map<String, TargetPointInfoDTO> targetPointInfoDTOMap = pointInfoDTOList.stream()
                    .collect(Collectors.toMap(TargetPointInfoDTO::getId, item -> item, (v1, v2) -> v2));
    Map<String, AstSgassetDTO> assetMap = astSgassetManager.getAstSgassetListByIds()
      .stream().collect(Collectors.groupingBy(AstSgassetDTO::getId, Collectors.collectingAndThen(Collectors.toList(), item -> item.get(0))));

    filter会将false的数据过滤掉

    optional

    • Optional.ofNullable(m1()).orElse(m2())

      m1结果非空还是会执行m2!

      所以,如果orElse()中的计算或其他处理业务很多时,推荐使用orElseGet()

    • map使用替换ifelse

    Integer tableId = order.getTableNumberConfigId();
    String tableName = Optional.ofNullable(tableId).map(item -> tableNumberConfigService.getById(tableId)).map(TableNumberConfig::getName).orElse("");

    UML结构图

    https证书配置

    使用acme.sh 可以自动管理证书有效期自动续订

    案例:

    user  root;
    worker_processes  1;
    
    #error_log  logs/error.log;
    #error_log  logs/error.log  notice;
    #error_log  logs/error.log  info;
    
    #pid        logs/nginx.pid;
    
    events {
        worker_connections  1024;
    }
    
    
    http {
        include       mime.types;
        default_type  application/octet-stream;
        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';
            client_max_body_size 100m;
    
        #access_log  logs/access.log  main;
    
        sendfile        on;
        #tcp_nopush     on;
        #keepalive_timeout  0;
        keepalive_timeout  65;
    
        gzip  on;
    
    
        server {
            listen       80;
            server_name  localhost;
            client_header_buffer_size 128k;
            large_client_header_buffers 4 128k;
            #charset koi8-r;
    
    
            location / {
    
                access_log  logs/newwap_pay.access.log ;
                proxy_pass  http://36.133.252.148;
            }
    
            #access_log  logs/host.access.log  main;
    
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    
        }
    # 酒吧小程序
        server {
            listen       80;
            server_name  xd.wenhuizh.cn;
            client_header_buffer_size 128k;
            large_client_header_buffers 4 128k;
            #charset koi8-r;
    
    
            location / {
                root html;
            }
            location /cms-api/ {
    
                access_log  logs/cms-api.access.log ;
                proxy_pass  http://127.0.0.1:9226/;
            }
    
            location /h5-api/ {
    
                access_log  logs/h5-api.access.log ;
                proxy_pass  http://127.0.0.1:9239/;
            }
    
            location /barcms/ {
                alias /usr/local/project/bar-order-static/;
                index index.html;
            }
    
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    
        }
    # 证书配置
    
        server {
                    listen 443 ssl;
                server_name localhost;
                ssl_certificate /root/software/openresty/nginx/conf/8991846_wenhuizh.cn.pem; 
                ssl_certificate_key /root/software/openresty/nginx/conf/8991846_wenhuizh.cn.key;
    
        location / {
    
                access_log  logs/newwap_pay.access.log ;
                proxy_pass  http://36.133.252.148;
            }
    
    
        }
    # 酒吧小程序
        server {
                listen 443 ssl;
                server_name xd.wenhuizh.cn;
                ssl_certificate /root/software/openresty/nginx/conf/xd.wenhuizh.cn/cert.pem; 
                ssl_certificate_key /root/software/openresty/nginx/conf/xd.wenhuizh.cn/key.pem;
    
            location /cms-api/ {
    
                access_log  logs/cms-api.access.log ;
                proxy_pass  http://127.0.0.1:9226/;
            }
    
    
            location /h5-api/ {
    
                access_log  logs/h5-api.access.log ;
                proxy_pass  http://127.0.0.1:9239/;
            }
    
    
            location /barcms/ {
                alias /usr/local/project/bar-order-static/;
                index index.html;
            }
    
        }
    
    
    }
    
    

    redis分布式锁问题

    1. redis本身是不具备可重入锁功能,需要借助其他方式如redisson

    2. 通过下面方式加锁时,如果key和value一样,线程1加锁成功,线程2是会等待的
      
      public boolean lock(String key, String requestId, long expire, long waitTimeout) {
              long startTime = System.currentTimeMillis();
              while (System.currentTimeMillis() - startTime <= waitTimeout) {
                  boolean locked = stringRedisTemplate.execute((RedisCallback<Boolean>) connection ->
                          connection.set(key.getBytes(), requestId.getBytes(), Expiration.seconds(expire), RedisStringCommands.SetOption.ifAbsent()));
                  if (locked) {
                      return true;
                  }
                  try {
                      Thread.sleep(100);
                  } catch (InterruptedException e) {
                      log.error("获取锁设置过期时间和获取锁时间", e);
                      Thread.currentThread().interrupt();
                  }
              }
              return false;
          }
    3. 下面加锁方式如果value值一样,会出现,     线程1加锁成功,线程2等待但是等待5s之后就返回失败了,没有加锁成功,就会抛出异常,最后走finally,将线程1的锁给解锁了的情况,所以还是要指定value值不同的uuid
      
      String redisId = String.valueOf(uidGenerator.getUID());
              try {
                  boolean lock = redisLockUtils.lock(RedisKeyPrefix.patrolCycleLock, redisId);
                  if (!lock) {
                      throw new ServiceException(AppConstant.Status.REDIS_LOCK_ERROR);
                  }
                  info.getIds().forEach(id -> {
                      PatrolStoreInfo storeInfo = validStore(id);
                      // 删除redis数据
                      redisAppUtil.deleteForHash(RedisKeyPrefix.patrolCycleList, id);
                      this.update(Wrappers.<PatrolStoreInfo>lambdaUpdate().set(PatrolStoreInfo::getCycleStatus, NO.getState()).set(PatrolStoreInfo::getUpdateTime, new Date())
                              .eq(PatrolStoreInfo::getId, id));
                      voList.add(storeInfo.getTaskName() + "停止巡视日常成功!");
                      log.info("任务id:{},名称:{},停止巡视日常成功", id, storeInfo.getTaskName());
                  });
              } finally {
                  redisLockUtils.unlock(RedisKeyPrefix.patrolCycleLock, redisId);
              }

    docker

    docker-compose方式启动可以在同级目录制定.env文件指定名称,这样是为了避免

    内容为:

       COMPOSE_PROJECT_NAME=名称

    springboot 应用无缘无故被kill

    现象:应用部署在docker中,无法访问,进入容器也找不到java进程,并且没有异常日志,没有oom

    原因:系统内存不足,被系统直接kill,通过查看/var/log/messages 日志过滤kill关键字发现被系统杀死

    解决:调整内存大小

    google guava

    key一样时会将值添加到value的list集合中

    MultiValueMap<String, String> stringMultiValueMap = new LinkedMultiValueMap<>();

    rocketmq无法消费问题

    规定是一个主题对应一个消费者组,如果一个消费者组下面有多个主题会出现无法消费问题 4.8版本