Object's Blog

10月-11月踩坑记录(2019)

字数统计: 4.3k阅读时长: 16 min
2019/11/29 分享

前言

时间过的很快,又是两个月的时间。回顾这两个月,我做过微服务开发、集群搭建、传统项目coding、前端coding、项目的部署等等等等(感觉自己实习结束可以做一个集运维、测试、前端、后端于一身的全栈码农。可是没办法,谁让我爱Java爱的深沉),接触了在上学期间很多没有机会接触到的技术,期间遇到了很多的问题,解决了很多的问题,这里集中做一个记录,完善自身,同时希望可以帮助到更多的人。

第一个坑:搭建Nginx后访问,遇到403Forbbiden

  • 问题描述

    在搭建Nginx之后,并且在Nginx的配置文件中已经对需要代理的项目做好了反向代理的配置(保证配置确认无误的情况下),在本地访问Nginx,出现403Forbbiden,提示没有权限访问。

    出现这样的问题实际上就从返回的值出发,进行判断,403本就是HTTP返回的一个没有权限的状态码,所以我们应该直接从权限角度出发。(当然还有一种可能,就是访问的路径下没有index.html或者index.php,当然这个问题比较好解决就不作为这次的讨论话题)对问题进行处理,下面说三个解决方案。

  • 解决方案一

    在Linux系统中每个进程执行的时候都是带着用户的。这么说可能比较抽象,打个比方,使用root用户执行一条命令和使用普通用户执行一条命令,该命令所携带的权限是不一样的。那么进程也一样,Nginx就是一个进程,而默认执行Nginx这个进程的用户就是nginx。那么当我用别的用户部署一个项目在服务器上,并且权限设置为其他用户不可读不可写的时候,nginx就会返回403错误,说到头还是权限的锅。所以第一种解决方案,将nginx的执行用户改为部署项目的用户或者直接改为root。

    在nginx的配置文件中就可以很方便地进行修改:

    打开 conf/nginx.conf,第一行就是默认用户的设置:

    nginx403

    我这里把他注释掉了,配置的时候需要去掉#

    为什么把它注释了呢,因为配置root,相当于Nginx有了服务器的超级管理员权限,我在操作服务器的时候,导师跟我说的第一个重点就是root要慎用,它相当于系统中的superman,连删除系统的权限都具备,如果配置上了这个权限,也就意味着Nginx这个进程有了系统中的所有权限,这在某种情况下是非常危险的,不提黑客这类发生率不高的情况,如果某个开发人员写错了某个业务逻辑,导致删错了某个重要的系统文件或者数据文件,这将会造成损失。

  • 解决方案二

    解决方案二可以算是解决方案一的一种互补方案,它的针对点不再是具有权限的用户,而是文件本身,当一个nginx没有权限访问某个文件或目录,那么就改变这个文件或者目录的权限即可。

    命令:

    1
    chmod -R 权限 需要赋权的文件或目录

    例如:

    1
    chmod -R 755 /website

    其中,-R代表递归地往下赋权。最终这个目录的下级目录和文件都会带有这个权限。

    这样nginx就可以正常访问到需要访问的文件了。

  • 解决方案三

    方案二的缺点就是,赋权之后一个群体的用户都有了该目录的权限,如果你的项目需要权限要求十分苛刻,不想给一整个用户组或者组外其它用户提供权限,那么可以用一个特殊的方式,既给了nginx权限,又没有给其他用户权限,我使用的就是这种方案。

    嗯,百度一下 linux ACL权限。

    这是一种给某个特定用户赋权的方式。

    命令:

    1
    2
    getfacl 文件或目录 #获取acl权限
    setfacl -m u:nginx:rwx 文件或目录 #将某个目录给nginx设置rwx权限

    这样就可以既不给用户群开放权限,又能解决403问题。

第二个坑:Redis Cluster集群搭建一直显示Waiting for the cluster to join…..

  • 问题描述

    这个问题是在搭建RedisCluster集群时出现的问题,当时的我一度以为需要等待,所以就一直等一直等,等了大概十分钟吧,果断翻官网文档。

    在文档中发现了这么一段话:

    Note that for a Redis Cluster to work properly you need, for each node:

    1. The normal client communication port (usually 6379) used to communicate with clients to be open to all the clients that need to reach the cluster, plus all the other cluster nodes (that use the client port for keys migrations).
    2. The cluster bus port (the client port + 10000) must be reachable from all the other cluster nodes.

    大概的意思是,集群总线端口要比redis本身的端口+10000,例如redis默认端口为6379那么总线端口就是16379,而正好这个端口被我的防火墙拦住了,而集群中的节点互相无法访问,就造成了一直等待的结果。

  • 解决方案

    在防火墙策略中放行这个端口,然后重启防火墙即可。

    命令:

    1
    2
    3
    firewall-cmd --zone=public --add-port=16379/tcp --permanent  #开启16379端口
    systemctl restart firewalld.service #重启防火墙
    firewall-cmd --list-ports #查看放行的所有端口

    关于如何搭建redis cluster集群,可以看这里:手动搭建Redis集群和MySQL主从同步(非Docker)

第三个坑:搭建redis集群,set无法重定向到其他节点存储数据

  • 问题描述

    redis集群实际上就是通过槽节点算法将你要存入的数据放到不同的节点中,当你往一个节点中set数据时,这个数据通过槽节点算法得出应该存到另一个节点时,redis集群应该自动重定向到那个节点,但是我在执行这个操作的时候报错了,没有办法进行自动跳转。

    报错信息:

    (error) MOVED 11469 192.168.43.202:7002

  • 解决方案

    这是由于在使用redis客户端连接redis集群时,没有加-c参数。

    连接单机redis:

    1
    ./redis-cli -h 127.0.0.1 -p 6379

    连接集群redis:

    1
    ./redis-cli -c -h 127.0.0.1 -p 6379

    -c代表cluster,也就是客户端连接的是redis的集群模式,所以不加-c就无法进行重定向。

    这里插一个redis的小坑,不想单独写了

    如果redis出现无法访问的情况,就检查一下配置文件中的bind,如果是127.0.0.1,那么表示只能本机访问,其他机器无法访问。所以只需要注释即可。

第四个坑:公司生产环境下没有root权限,如何修改环境变量?

  • 问题描述

    在我们自己学习Linux的时候,通常都是拿root权限在各种尝试各种命令,所有的命令都能跑起来,没有什么问题,但是在公司的环境下一般不会给到root权限。

    可是在某些时候,我们在生产环境下安装某个软件,如jdk这种,需要配置一个环境变量,学习linux的时候一般是修改/etc/profile这个文件,但是在非root用户下我们无法修改这个文件。

  • 解决方案

    在每个用户的家目录下,都存在一个环境变量文件,修改这个文件同样可以达到环境变量的效果,但是只对当前用户生效

    1575017137495

    所以在我们没有root权限的状况下,只需要修改.bash_profile这个文件,就可以达到同样的效果,但这种效果只对我们这个用户生效,其他用户登录不会有这个环境变量存在。

第五个坑:使用MyCat来完成MySQL主从的读写分离,出现密码错误

  • 问题描述

    MyCat作为一个数据库中间件,具有数据库集群的分库分表、读写分离、故障转移等功能,但是在我搭建数据库集群的读写分离时,使用mysql连接mycat,总是出现密码错误,但是我肯定密码是没有错的。

    环境:MySQL8.0、MyCat最新版本。

  • 解决方案

    这个坑的原因不是我们可以手动解决的,这是因为MySQL8.0之后,其加密方式改变造成的。

    在MySQL8.0版本之前,MySQL使用的加密方式是“mysql_native_passowrd“,在MySQL8.0之后,加密的方式改为“caching_sha2_password”,从字面意思理解,后者的加密方式应该更高效,以便提升客户端的连接速度。

    而MyCat目前不支持MySQL8.0以上的版本,当然我也看到过可以让它适配的方式,需要替换jar包,修改配置等,由于在生产环境下,这么改配置有可能会出现各种兼容性问题,所以就不这么尝试了。

    最终的解决方案就是,在MyCat做读写分离的前提下,不使用MySQL8.0以上的版本,将版本替换为5.7,至此问题解决。

第六个坑:MySQL主从同步IO线程一直处在Connecting状态,或者SQL线程为NO状态

  • 问题描述

    第一次搭建集群真的太多坑了,但是其实只要搭建过一次之后,遇到的问题就不那么多了。这个坑也是我头一回搭建主从同步时遇到的。

    首先说第一个,IO线程处在Connecting状态。这个比较好解决,因为在之前我就有看过MySQL主从同步的原理,IO线程主要是用于将二进制日志转存为中继日志的线程,那么这个线程出现connecting状态,也就是说它无法获取二进制日志。那么问题到这里就好解决了,获取不到二进制日志无非三种情况:1.密码不正确。2.网络不通。3.slave节点指向的binlog日志名并不是master节点的binlog日志。

    IO线程Connecting

    第二个问题:SQL线程处于NO状态。

    SQL线程在主从同步中主要是用来执行中继日志中的SQL语句,那么它为NO,就是说无法正确执行文件中的SQL。针对我遇到的状况来说,就是主库和从库的状态不一致。因为这我是在往从库中写入了数据,才出现这种情况。

    Slave_SQL_Running: No

  • 解决方案

    第一个问题:根据上述思路。

    第一、互ping,证明相互网络是通畅的。

    第二、仔细查看密码是否正确。

    第三、查看master节点的binlog日志名是否和slave节点相同。

    第二个问题

    在手动使主从库状态一致之后,使用set GLOBAL SQL_SLAVE_SKIP_COUNTER=1;,将指针下移一个位置,重启slave,此时SQL线程就为yes状态了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #命令记录
    mysql> slave stop;
    Query OK, 0 rows affected (0.01 sec)

    mysql> set GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
    Query OK, 0 rows affected (0.00 sec)

    mysql> slave start;
    Query OK, 0 rows affected (0.00 sec)



    mysql> show slave status\G;

    Slave_IO_Running: Yes
    Slave_SQL_Running: Yes

第七个坑:MyBatis中对与Date属性的判空(动态SQL)

  • 问题描述

    MyBatis可以使用动态SQL来对数据库进行操作,而我们常用的一般是判空操作。

    通常是这么写的

    1
    <if test="xxxxx!=null and xxxxx!=''">

    而如果你的xxxxx是Date类型的数据,就会遇到:

    Error querying database. Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String

  • 解决方案

    后来得知,mybatis 3.3.0中对于时间参数进行比较有一个bug. 如果拿传入的时间类型参数与空字符串 ‘’ 进行对比判断,则会引发异常. 所以在上面的代码中去该该判断, 只保留非空判断就正常了。

    1
    <if test="xxxxx!=null">

第八个坑:Date类型的数据经过JSON发送到前端之后变为Tue Nov 20 00:00:00 CST 2019 这样的数据

  • 问题描述

    在我使用Date属性然后通过SpringMVC返回到前端时,在前端展示的不是yyyy-MM-dd这种类型的数据,而是像Tue Nov 20 00:00:00 CST 2019 这样的数据。

  • 解决方案

    这种问题我也头一回遇到,因为以前的工程中我也经常使用Date类型的数据,能够很好的返回到前端展示,估计又和Spring版本或者数据库(因为这次使用的Oracle,以前用的都是MySQL)有关吧。直接说解决方案。

    只需要在实体类的Date属性字段上加

    @JsonFormat(pattern=”yyyy-MM-dd”)

    像这样:

    1
    2
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date createTime;

    这样返回给前端的就都是正常格式的日期了。

第九个坑:SpringBoot环境下下载Excel文件导致文件损坏

  • 问题描述

    需要做一个Excel模板下载的功能,但是在测试的时候,下载下来的Excel模板是损坏的。

    排查

    1.查看原文件,可以正常打开。说明源文件不存在损坏的问题。

    2.单步调试,可以正常跑通,并且在程序中不存在对文件的任何压缩或者修改拓展名之类的操作,排除程序的问题。

    3.查看编译后的文件,发现编译后的文件是损坏的。

    结论:可以得知,这并不是程序或者源文件的问题,程序在经过编译后,在target文件夹中存在的文件已经是损坏的。那么可以将问题出现位置锁定在项目编译过程中。

  • 解决方案

    通过google后得知,maven在经过编译之后会将excel文件进行压缩,造成excel的损坏,那么只需要在pom.xml中让maven在编译时不压缩excel文件即可。

    导入插件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <version>2.6</version>
    <artifactId>maven-resources-plugin</artifactId>
    <configuration>
    <encoding>UTF-8</encoding>
    <nonFilteredFileExtensions>
    <nonFilteredFileExtension>xlsx</nonFilteredFileExtension>
    </nonFilteredFileExtensions>
    </configuration>
    </plugin>

    完美解决。

第十个坑:SpringBoot打成jar包下载功能失效的问题

  • 问题描述

    这是最近遇到的一个最让我头疼的坑,直到现在我也不知道问题出现在哪。如果有大神知道可以私信我一下。

    问题大概是这样的,我需要做一个下载模板的功能,由于我考虑到了打成jar包之后路径会改变的情况,所以我使用流的形式来读取文件,最后使用response,把整个流输出到页面上,完成下载的功能。

    思路很简单,很清晰,但是在实际测试时出现了问题。

    在IDE环境下测试完全没问题,可以下载。

    但是SpringBoot项目是要打成jar的,而且下载这种操作,我不可能不在jar包的环境下再测试一遍。这么一测问题就出来了。

    在使用jar包启动项目的时候,出现访问下载的那个接口阻塞的状况。

    排查:在各个关键点打上日志,最后发现在流的对拷那一部分出现死循环。导致整个应用的阻塞。

    相关源代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    try {
    bis = new BufferedInputStream(inStream);
    bos = new BufferedOutputStream(response.getOutputStream());
    /*********这句话有问题 会造成之后的死循环*************/
    //byte[] buffer = new byte[bis.available()];
    //logger.info(String.valueOf(bis.available()));
    /**************************************************/
    byte[] buffer = new byte[1024];
    int len = 0;
    while((len = bis.read(buffer))!=-1) {
    bos.write(buffer,0,len);
    }
    } catch (IOException e) {
    logger.error("流转换失败");
    logger.error("异常信息:"+e.getMessage());
    }finally {
    try {
    bis.close();
    bos.flush();
    bos.close();
    } catch (IOException e) {
    logger.error("关闭流失败");
    logger.error("异常信息:"+e.getMessage());
    }
    }

    解释一下代码:通常我们写缓冲区的大小都是byte[] buffer = new byte[1024],但是那样可能会导致大文件时下面的循环要循环多次,所以我的本意是想通过insrteam.available()方法,直接获取到这个流剩余多少可读字节,将这个数直接作为构造buffer缓冲区的大小,那么在下文的while循环中只需要循环一次就可以写出整个流,而不需要循环多次。

    出现的问题:在IDE环境下,logger.info输出的available()的值为8000+,和我的文件大小匹配。但是打成jar包之后,输出的值就变成了0,这就有很大问题,在下面的while循环的循环体中,就执行着这样的一条代码,

    bos.write(buffer,0,0)。从0写到0,那么必然是死循环了。

    原因不详,一直想不通为什么打成jar包之后读出来的长度就是0

  • 解决方案

    将缓冲区改为以前的传统写法byte[] buffer = new byte[1024]写死缓冲区,这样就不会造成死循环的问题了,但是依旧会有多次循环的问题。

结语

看看我的头发还剩几根?

欢迎大家访问我的个人博客:Object’s Blog

原文作者:Object

原文链接:http://blog.objectspace.cn/2019/11/29/10月-11月踩坑记录(2019)/

发表日期:2019 November 29th, 3:05:38 pm

更新日期:2019 November 29th, 6:03:30 pm

版权声明:未经作者授权请勿转载

目录
  1. 1. 前言
  2. 2. 第一个坑:搭建Nginx后访问,遇到403Forbbiden
    1. 2.1. 问题描述
    2. 2.2. 解决方案一
    3. 2.3. 解决方案二
      1. 2.3.1. 解决方案三
  3. 3. 第二个坑:Redis Cluster集群搭建一直显示Waiting for the cluster to join…..
    1. 3.1. 问题描述
    2. 3.2. 解决方案
  4. 4. 第三个坑:搭建redis集群,set无法重定向到其他节点存储数据
    1. 4.1. 问题描述
    2. 4.2. 解决方案
  5. 5. 第四个坑:公司生产环境下没有root权限,如何修改环境变量?
    1. 5.1. 问题描述
    2. 5.2. 解决方案
  6. 6. 第五个坑:使用MyCat来完成MySQL主从的读写分离,出现密码错误
    1. 6.1. 问题描述
    2. 6.2. 解决方案
  7. 7. 第六个坑:MySQL主从同步IO线程一直处在Connecting状态,或者SQL线程为NO状态
    1. 7.1. 问题描述
    2. 7.2. 解决方案
  8. 8. 第七个坑:MyBatis中对与Date属性的判空(动态SQL)
    1. 8.1. 问题描述
    2. 8.2. 解决方案
  9. 9. 第八个坑:Date类型的数据经过JSON发送到前端之后变为Tue Nov 20 00:00:00 CST 2019 这样的数据
    1. 9.1. 问题描述
    2. 9.2. 解决方案
  10. 10. 第九个坑:SpringBoot环境下下载Excel文件导致文件损坏
    1. 10.1. 问题描述
    2. 10.2. 解决方案
  11. 11. 第十个坑:SpringBoot打成jar包下载功能失效的问题
    1. 11.1. 问题描述
    2. 11.2. 解决方案
  12. 12. 结语