这是一个以甲方发布业务需求项目,由后台管理员审核报名导师,然后由导师审核报名学生的,然后按照需求进行开发并在截止日期前交付的平台。
还兼有内推招聘搜索功能,学生导师作品集,项目评论区功能等。
使用到了较为完整的springColud分布式架构。使用到的微服务组件有:nacos,job,knife4j-gateway,search,mbg-plus,monitor等。
● 技术选型包括但不限于:
JDK 1.8,mysql, redis, mongdb, Elasticsearch, logstash, kibana, nginx, RabbitMq;
springCloud, mybatis-plus, minio, seata, portainer, jenkins, kubemetes,docker;
vue,vue-router,vuex,axios,element, v-charts;
● 人员分组分为架构组和业务组,业务组分为三组。架构和业务我都有参与,普通组实际编写代码的人约为五人。
我既参与了技术架构代码编写也参与了业务代码编写,就做个学习总结。
分为两个部分:
一个是日常习惯,或者说常识,或者说内功心法的总结。
一个是技术实现,或者说中间件,或者说武功招式的总结。
本次学习中,我被纠正的一些很重要的点有:
● 不要再dao层进行多表联查,首先是容易形成屎山代码,更重要的是,你一个查询就占有多个表锁,这就太霸道了些,显然不利大流量的查表性能,更不利于多数据源查表性能。
查表多多少少是要查多表的,但是可以扔到server层调用着来查。
● 业务需求的分析写的好,基本业务代码不涉及技术的,就都不用发愁了。
● 不止代码编写的时候要注意阿里公约的规范,数据库建表的时候也应该注意。
● 代码无论是前后端都应该该验空,验证数据可用否,都应该设置异常处理的处理方法,哪怕是抛出,也要处理。用比较通俗的话来讲就是,前端验证是防用户瞎输入,后端验证是为了防止错误的数据占用户,一来防前端bug,而来防恶意攻击。
● 验空这种比较简单的验证功能,尽可能使用hutool-all依赖包里的,比如StriUtil,ObjectUtil,ArrayUtil里的各种验空工具来验。
首要的,当然是去除重复代码的编写,以及消除常数类代码的编写。
但重要的是,这些Util里面会有类似isNotBlank这种防空异常,除去空格的个性化验空功能。大大提升了代码的复用性的同时简化了代码的冗余。
● 通过多组合作,我更深刻的理解了多分支下,通过gitee管理项目,PR分支到master中去的过程。
● 使用ApiFox这个软件,共同管理接口,这样你需要什么接口,别人需要什么接口,都能清楚很多,前后端返回参数也能更好协调,组员分工追责也能更清楚。
● 上传代码之前,好好检查代码能不能运行。我自从下载了别人的代码一直异常,错都不知道错在哪,也走不了测试,只能等对方修改,之后我就对不测试便上传代码的行为深恶痛绝。
技术功能要点归纳:
● job-server是一个定时的代码执行器,我能想到的各类应用场景有,比如股票的定时刷新,不想要在前端写后端代码的,可以通过这个实现。还有就是打印日志功能,可以通过admin服务去可视化客户端化的在网页部署各种job的执行器。
最后我实现使用的是2.3.0版本的,期间我发现如果admin和executor版本号不同的话,注解是无效的,也就是说高版本的没有低版本里的注解,也就是不能向下兼容,2.1都兼不了。说实话有点坑。
2.3.0版本在完成初始的admin和executor里的XxlJobConfig的编写后,就只需考虑yml文件和执行器的业务代码编写了。代码写完了在方法上写个@XxlJob就可以解决了,相当的方便。老版本还要在类上面写东西,以至于我的记忆已经淘汰了它。(实际上就是忘了,哈哈)
需要记录一下的是XxlJobConfig的配置类和yml文件格式
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppname(appName);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
}
server:
port: 8081
spring:
application:
name: job-server
logging:
config: classpath:logback.xml
xxl:
job:
accessToken:
admin:
addresses: localhost:8080/xxl-job-admin
executor:
appname: job-server
#我就不在这里写ip地址和端口号了
ip:
port: 9999
logpath: /data/applog/xxl-job/jobhandler
logretentiondays: -1
address: localhost:8081/
●minio是一个对象式存储上传文件的中间件,功能和代码编写两个方面我觉得都比fastdfs好,看来我很快也会遗忘fastdfs。
实现起来还是在关系数据库内存文件地址,然后到中间件的数据存储里找这个地址下的文件。但minio在分布式部署上采用了纠错码和集群的方法,保证了数据的高可用和一致性,同时由于有了可视化的操作终端,检查管理起来也更方便。
实现的时候我使用的是7.0.2的版本,没有使用8.0。首先就是8.0由于分离了调度中心和功能本体,导致linux里部署要部两个端口,然后是默认的用户名和密码的名称export的是 MINIO_ROOT_USER和MINIO_ROOT_PASSWORD。而不是accessKey和secretKey,需要注意。
在代码编写方面,7.0.2的编写方法也比较简短,只需要注入配置文件内容后,在工具类中写代码就好了,写的过程中也是以调用方法的形式居多。而8.0则是要先创建实体类之后才能进行后续方法的方法调用(至少在写法上,我感觉是多次一举)
性能上我没感觉出差距,所以个人感觉选择7.0.2比较好。
在此记录一下7.0.2的配置写法:
实体类
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class Minio {
//minio的地址
String endpoint;
//访问桶的名称
String buketName;
//访问的key
String accessKey;
//访问秘钥
String secretKey;
}
配置类
@Configuration
public class MinioConfig {
@Autowired
private Minio minio;
@Bean
public MinioClient minioClient () throws InvalidPortException, InvalidEndpointException {
return new MinioClient(minio.getEndpoint(),minio.getAccessKey(),minio.getSecretKey());
}
}
配置文件
minio:
endpoint: http://localhost:端口号 #MinIO服务所在地址,我这里就不写ip和端口号了
bucketName: images #存储桶名称
accessKey: admin #访问的key
secretKey: admin123456 #访问的秘钥
#默认图片url,没有图片时使用此图片地址
defaultImageUrl: http://localhost:9000/images/defaultImage.jpg
●Logstash + Elasticsearch + Kibana
其实就是管理日志的,Logstash 收集日志,Elasticsearch做数据处理查询,Kibana辅助可视化管理
Elasticsearch 使用了倒排索引查找关键字,可以理解为是查value,而不是查key的关系型数据库,整体结构和mysql相差不大(Type这层要是淘汰了差别就大一点点)。
在springboot的框架下,使用springdata里的工具类@Repository标注Dao类,然后像用mysql时那样,创建一个要有对应数据的注解的ES实体类,之后照常写server和controller,实现起来的效果和mybatis那套差不多。
(我已经把他的基础语句忘的差不多了,经典内存筛选后永久化到磁盘后清理缓存。这波啊,这波是人脑追求实用高效。)
●redis缓存里,我学到了很多新东西,比如:
●布隆过滤器:
通过x个hash算法(crc32,crc16)把要处理的数据进行算法计算后,变成多个,放到长度为Y的数组中的不同点位去。
然后通过同样的多套算法在查找数据的时候,通过算法把要查的值算出成多个不同的hash值,然后去查这些值在数组中的点位是1还是0,如果有一个不为1,即有一个为0,则这个数据就一定不存在,就不需要在查数据库了。如果所有计算后的值的位点都是1,则可能存在,再去查数据库里到底存不存在这个值。
上图比较好理解,在此记录一下:
●setbit实现点赞功能(bitmaps):
这是一个很巧的方法,但是实现起来的要求条件也比较苛刻,想实现微信朋友圈这种点赞功能就不太好实现了,但是只是单纯的统计数量,用户Id又比较简单的点赞功能,这种方法性能确实不错。(毕竟是内存容嘛,不寒碜,不寒碜)
原理就是利用存储时数据是二进制的10101这种东西,然后根据位数去设置是或否。比如id为1和为3的用户点赞了,id为2和4的用户没点赞,bit位的数据就是0101,因为是动态扩容的,所以所占空间极小。最多存到42亿个不同用户点赞,也才占512MB。
暂时想要归纳的就这些,花了不少时间,但是确实学到不少东西,甚至有点喜欢上了代码批斗环节。开会批斗完,下去改一波代码,瞬间觉得不止代码升级了,我也升级了。