Java Web问题杂谈

如何构建springboot服务:

1、使用idea spring构建项目
2、构建不同的profile test pre prd等
3、使用mybatis代码生成器
4、本地构建代码后 上传jar包到maven私服的配置
5、引用本地的jar包 xxx system
6、junit测试代码编写 引用少、粒度小、明确的输出(assert判断)
7、@Slf4j、logback日志框架
8、把依赖的包都打包到生成的jar包中

如何集成rocketmq到springboot中

1、pom依赖
2、生产者代码
3、消费者代码
4、no route的解决办法 没有权限创建topic、服务器地址错误、fastjson依赖等

更多:
RocketMQ No route info of this topic错误(原因版本不一致)
RocketMQ 常见异常处理
RocketMQ 解决 No route info of this topic 异常步骤

如何集成activemq到springboot

1、pom依赖
2、生产者、消费者代码
3、与Spring集成的一些坑 不能直接引用activemq-all

拒绝代码的坏味道

1、数据库字段加上gmt_created、gmt_modified、memory,日期字段可直接使用string类型
2、乐观锁更新数据库,利用数据库中现有的每次更新都会更改的,能唯一标识本次更新的字段(如,每次更新都增加的字段)。优先使用目前现有的字段,无法标识则新增version、status等标识
3、传递的变量用对象,需要扩展的、或者为了提高性能的框架代码中使用Map。
4、日志不要重复打印,info打印少的信息,warn、error打印尽可能多的信息。MDC用于trace日志的。
5、变量命名等使用业务含义,使用insertOrUpdate这种业务含义不清晰。

数据库查询

1、分表解决查询效率问题(一般水平分表),分库解决存储问题。
2、Mysql更新并发压力测试,QPS:2000左右

多线程更新数据库

场景:

数据库中,当天无记录则插入,有记录则更新新的记录到已经存在的记录中(累加新值到现有的数据库记录中)

并发问题:

两个线程并发可能出现同时插入(插入重复数据)、或者同时更新(基于同一份旧数据,导致其中的一次数据更新丢失)的并发问题。

并发解决方案:

CAS更新数据库,查询当天有无记录,无则插入,有则把新纪录累加到对应记录中
插入是并发怎么解决:利用数据库的唯一键特性,根据不同场景确定是否有唯一属性,或者新增唯一属性即可。如此场景中,当天的值累加,则标识当天日期的day属性即为唯一的,设置为unique key后,同时插入数据,就会异常,触发异常流程的线程进入重试逻辑即可,直到insert语句返回成功标识。

更新并发场景如何解决:

结合场景分析,是否需要添加新字段,唯一标识一次更新动作,可利用现有的递增、递减字段,状态值变更字段,新加入version字段。因为此场景是val=addVal+oldVal,即val只有递增场景,我们说CAS更新时,常常使用version作为标识,其实是利用了version只会递增的特性,version值的变更可以唯一标识出一次更新,同样的,某些状态变化场景通过引入status状态值字段,其变化也可以达到标识更新动作的作用。因为,此处使用update [tableName] set val=newVal, gmt_modified=systemdate where val=oldVal and id=xxx,即可实现CAS更新。如果此update(如借助于mybatis框架实现,update返回受影响的行数)返回0,说明没有找到对应的记录,已经被其他线程抢先一步更新,则重试直到此更新返回1即可。

多线程扫描数据库处理数据:

场景:

需要定时(如1分钟)扫描数据库,处理数据库中的每一条未处理过的数据。

并发问题:

多线程扫描,扫描到的数据有交集,导致数据重复处理。
并发解决方案:每条数据记录要有gmt_created创建时间、is_done是否处理完毕(CAS处理数据需要)两个字段。每次扫描,扫描当天0点到当前时刻的未处理的数据(可在gmt_created列建立索引,加快查询速度)
(当天前的数据一直没有被正确处理, 任务是bug或者非法数据,此处不考虑),具体流程为:扫描数据-逐条数据处理(先更新数据处理状态,再进行业务处理(某条数据状态更新成功后,业务处理失败,可放入队列,由单独线程消费这批数据,不断重试))。
其中更新数据处理状态为cas更新,update [tableName] set is_done=‘Y’ where id=xxx and is_done=‘N’。

拒绝代码的坏味道

1、多个数据库的连续操作要考虑关联到个事务里面,如业务场景为,操作数据库中的A数据,成功后操作B数据,之后发送HTTP请求或RPC调用。这样,如果操作A数据后宕机了,服务重新起来后就不会再操作这条数据了,B数据也不会在进行相应的更新,这条数据就丢了。此时,要把A、B数据的更新绑定到一个事务里面,A操作后宕机,数据会回滚。
为什么不能把之后的HTTP请求或RPC调用也绑定到事务里面?因为HTTP请求或者RPC调用都是依赖远程连接,如果响应慢或异常,就会把数据库的session撑满,把数据库拖垮。
怎么保证A、B数据更新成功后,HTTP请求会发送?数据库中添加一个字段标识,HTTP请求是否发送即可。
怎么保证HTTP请求发送成功了?数据库中再加一个字段标识,标识发送的请求是否已经被正确处理即可。轮询时,如果HTTP请求已经发送,需要明确HTTP请求确实发送失败了,才重试发送消息。
2、后面业务不会有大扩展的地方,没有必要用工厂或者其他设计模式来搞,尽可能简单即可。要考虑,后面来新业务的话,后面真正改动的是不是需要很小的改动就可以了,有时候不需要必须用设计模式才能达到扩展、可维护性好的效果。可以画下时序图,时序图画完了,看看是否有(可能)频繁变化的模块/需求,如果有,请务必使用设计模式,最好画出UML类图;如果没有频繁变化的模块/需求,请尽量不要使用设计模式,因为会增加代码的嵌套层次,不易理解。
3、主业务方法,尽量第一眼就能清晰的看到整个业务处理过程,以别人很简单的就能读懂业务为目标,清晰简单的代码才是好代码。
4、当发现一处设计很别扭,但是改造方法也有限的时候,可能是自己给这块的定位,入参什么的不合理,新的模块可以对外暴露,而不需要详细解释参数的含义与使用上下文。梳理下整体的上下文,看下这块有没有必要抽象出来,放到系统更上面一点或者下沉一些儿,看下是不是更合理。
5、乐观锁更新的方式,如果使用了while true循环,最好加上循环的次数上限,防止意外拖垮整个服务。

参考:
美团点评智能支付核心交易系统的可用性实践-1.2事务中不包含外部调用

事务问题

1、方法处于一个事务时,各数据库的连接应该基于同一个session,否则后面的数据库查询依赖于之前的数据库更新时,无法查询到刚才更新的结果,解决措施是使用相同的session,或者在前面的session数据库操作全部结束之后,统一再执行后面的数据库查询。如果是基于Spring事务,session是在ThreadLocal中维护的,在ThreadLocal里头,放的是一个Map。key是dataSource,value才是connection。
2、注意使用 boolean && 执行业务时,注重&&的短路特性,当前面的boolean值为false时,无法执行后面的代码。
3、多表联合查询时,注重sql的性能,可以优化为单表查询,尤其是业务表,以达到sql耗时<5ms的水平

参考:
Spring的统一事务模型

git问题解决

1、https方式拉取代码,mac sourcetree拉取代码不停的要求输入密码?
代码目录下执行: git config credential.helper store 或 git config --global credential.helper store
git pull
输入账号密码即可

2、使用ssh方式配置sourcetree

  • 在.ssh目录下生成公钥和私钥,公钥配置到gitlab上
  • .ssh目录下创建config文件,配置类似
Host xxx
  Hostname xxx
  Port xxx
  UseKeychain yes
  AddKeysToAgent yes
  IdentityFile ~/.ssh/xxx

参考:
Mac SourceTree配置SSH

Spring RestTemplate.postForObject报错PKIX path building failed.

使用RestTemplate.postForObject访问一个https的网址时报错,具体错误如下:

org.springframework.web.client.ResourceAccessException: I/O error: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453) ~[spring-web-3.0.5.RELEASE.jar:3.0.5.RELEASE]
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:401) ~[spring-web-3.0.5.RELEASE.jar:3.0.5.RELEASE]
	at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:279) ~[spring-web-3.0.5.RELEASE.jar:3.0.5.RELEASE]

问题出现的原因是:访问的https网址向客户端要证书。
对于服务端没有强制要求证书,或者必须校验证书时,可以在客户端请求时,跳过证书校验。
解决:
创建RestTemplate对象时,关闭证书校验。

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;

@Slf4j
public final class RestTemplateUtils {
    private static final TrustManager[] UNQUESTIONING_TRUST_MANAGER = new TrustManager[]{
        new X509TrustManager() {
            public X509Certificate[] getAcceptedIssuers(){
                return null;
            }
            public void checkClientTrusted(X509Certificate[] certs, String authType) {}
            public void checkServerTrusted(X509Certificate[] certs, String authType) {}
        }
    };

    private RestTemplateUtils() {}

    /**
     * 创建RestTemplate,屏蔽证书验证部分
     * @return RestTemplate
     */
    public static RestTemplate createRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();

        try {
            turnOffSslChecking();
        } catch (NoSuchAlgorithmException e) {
            log.error("Create rest template occurs NoSuchAlgorithmException.", e);
        } catch (KeyManagementException e) {
            log.error("Create rest template occurs KeyManagementException.", e);
        }

        final HostnameVerifier hostnameVerifier = new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession sslSession) {
                return true;
            }
        };
        HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

        return restTemplate;
    }

    private static void turnOffSslChecking() throws NoSuchAlgorithmException, KeyManagementException {
        // Install the all-trusting trust manager
        final SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, UNQUESTIONING_TRUST_MANAGER, null);
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
}

logback乱码问题

方案一:标签中指定charset为UTF-8,添加 UTF-8
方案二:服务启动脚本,例如Tomacat启动服务,在catalina.bat,在 Set JAVA_OPTS=%JAVA_OPTS% 如:Set JAVA_OPTS=%JAVA_OPTS% -Dfile.encoding=UTF-8

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章