网站建设栏目图片怎样建立一个网络销售平台
代办
AQS
Concurrenthashmap
投递公司
| 公司 | 状态 | 链接 | 时间 | 
|---|---|---|---|
| 腾讯 | |||
| 美团(张云峰) | 笔试完成 | https://zhaopin.meituan.com/web/personalCenter/deliveryRecord | |
| 字节 | https://jobs.bytedance.com/experienced/position/application | ||
| 阿里 | |||
| 小红书 | |||
| 腾讯云智 | 待面试 | 4.5 | |
| 腾讯音乐 | 笔试完成 | ||
| 京东 | 测评完成 | https://campus.jd.com/#/myDeliver?type=present | |
| 微众银行 | 一起触发更多可能 (webank.com) | ||
| 阅文集团 | https://www.nowcoder.com/careers/yuewen/122324 | ||
| 中国银行104573 | https://applyjob.chinahr.com/page/job/success?projectId=63f47e7255cbed088c78eed1 | ||
| 中国邮政 | https://xiaoyuan.zhaopin.com/scrd/postprocess?cid=58805&pid=101739970&productId=7&channelId=null&taskId=null | ||
| 蚂蚁 | 挂到蚂蚁集团 | https://talent.antgroup.com/personal/campus-application | |
| 特斯拉 | https://app.mokahr.com/campus-recruitment/tesla/91939#/candidateHome/applications | ||
| 58 | https://campus.58.com/Portal/Apply/Index | ||
| 立得空间 | https://www.showmebug.com/written_pads/HRUHZN | ||
| 恒生 | https://campus.hundsun.com/personal/deliveryRecord | ||
| wind | ![]()  | ||
| 银泰百货 | https://app.mokahr.com/m/candidate/applications/deliver-query/intime | ||
| 旷世 | https://app.mokahr.com/campus-recruitment/megviihr/38642#/candidateHome/applications | ||
| 腾讯音乐 | https://join.tencentmusic.com/deliver | ||
| 完美世界 | https://app.mokahr.com/campus-recruitment/pwrd/45131#/candidateHome/applications | ||
| 七牛云 | https://app.mokahr.com/campus-recruitment/qiniuyun/73989#/candidateHome/applications | ||
| cider | https://ciderglobal.jobs.feishu.cn/504718/position/application | ||
| 360(运维开发) | https://360campus.zhiye.com/personal/deliveryRecord | ||
| 知乎(改简历) | https://app.mokahr.com/campus_apply/zhihu/68321#/candidateHome/applications | ||
| 猿辅导 | https://hr.yuanfudao.com/campus-recruitment/fenbi/47742/#/candidateHome/applications | ||
| 爱奇艺 | https://careers.iqiyi.com/apply/iqiyi/39117#/jobs | ||
| 百词斩 | https://join.baicizhan.com/campus | ||
| 昆仑万维 | https://app.mokahr.com/campus-recruitment/klww/67963#/candidateHome/applications | ||
| bilibli | https://jobs.bilibili.com/campus/records | ||
| geek | https://app.mokahr.com/campus_apply/geekplus/98039#/candidateHome/applications | 
| 顺丰 | https://campus.sf-express.com/index.html#/personalCenter | |
|---|---|---|
| 众安 | https://app.mokahr.com/campus-recruitment/zhongan/71908?sourceToken=d895a22a006b8a6da61313d9b4091850#/candidateHome/applications | |
| 虎牙 | https://app.mokahr.com/campus_apply/huya/4112?edit=1#/candidateHome/applications | |
| oppo | https://careers.oppo.com/campus/record | |
| 第四范式 | https://app.mokahr.com/campus-recruitment/4paradigm/58145#/candidateHome/applications | |
代投:商汤 geek 雷火 oppo vivo 字节 阿里 腾讯 联想 西山居
自我介绍
我叫张维阳,是中国地质大学武汉的一名研二学生,以下是我的自我介绍:
在项目方面,为了河南高校魔方爱好者之间更好地交流与分享经验 ,为河南高校魔方联盟搭建了一个基于SpringBoot的魔方经验分享及管理平台,主要功能包括用户注册和登录、魔方教程和经验分享、论坛和社区互动、管理和监控等。 在这其中加入了一些中间件进行优化,最后做了上线部署。得到了魔方爱好者的一致认可。同时为了更好的了解RPC,自己手写了一个RPC框架,主要内容包括注册中心、网络传输、序列化与反序列化、动态代理、负载均衡等 ,从而加深了对RPC框架的理解。
比赛方面,参加过几次数学建模竞赛和数学竞赛,获得过第十二届全国大学生数学竞赛一等奖以及高教社全国大学生数学建模二等奖。学习之余,也喜欢通过博客整理分享自己所学知识,以上就是我的自我介绍。
优缺点
缺点: 缺少相关专业的实践、工作经验;遇事不够沉着冷静,容易紧张;
优点:有团队精神意识,善于沟通。大三参加数学建模时,协调组内其他成员,对编程遇到困难的组员提供帮助 ,最终顺利完成比赛并获得奖项 。
你还有什么要问的?
- 部门的主营业务是什么?
 - 部门使用的技术栈、编程语言是什么、使用哪些框架、中间件?表达下自己对技术的好奇
 
项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PPOD9POX-1681383922602)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230413011206981.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KKm7TkAK-1681383922605)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230404225216774.png)]
魔方天地
介绍一下项目?
该项目是一个致力于魔方爱好者之间交流、分享经验的在线平台,主要功能包括用户注册和登录、魔方教程的
 展示分享、魔方论坛互动、比赛和活动组织、用户管理等。
以下是该项目的主要特点和功能:
- 从用户系统:支持用户注册、登录,用户可以发布教程帖子、评论和点赞。
 - 帖子管理:支持帖子的发布、编辑和删除等操作,帖子内容可以包含图片(七牛云)。
 - 分类和标签:帖子可以被分配到不同的分类和标签,方便用户查找和阅读。
 - 搜索和推荐:支持关键词搜索和帖子推荐功能,提高用户阅读体验。
 - 安全和性能:采用Spring Security框架进行用户认证和授权
 
以上就是我项目的主要内容
 
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RI90XXJc-1681383922608)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230322005426563.png)]
1、JWT+token+redis
用户jwt鉴权流程
jwt 有三部分组成:A.B.C
A:Header,{“type”:“JWT”,“alg”:“HS256”} 固定 定义生成签名算法以及Token的类型
B:playload,存放实际需要传递的信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
C: 签证,A和B加上秘钥 加密而成,只要秘钥不丢失,可以认为是安全的。
jwt 验证,主要就是验证C部分 是否合法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9p98Egvg-1681383922609)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230320181343651.png)]
-  
用户使用用户名密码来请求服务器
 -  
服务器进行验证用户的信息
3.服务器通过验证,发送给用户一个token(header.payload(id 电话号码啥的).签证)
4.客户端存储token,并在每次请求时附送上这个token值(Authorization里面)
 -  
服务端验证token值,并返回数据,从中也可获取用户相关信息
 
jwt token生成和校验
使用私钥加密生成token 公钥解密获取token中的信息
jwt退出登录/修改密码时如何使原来的token失效
删除redis里面的token即可
redis or jwt?
然后呢,JWT是个啥?其实就是把用户的用户信息,用密钥加密防篡改,然后放在请求头里。用户请求的时候呢,再解密。服务器就不需要保存用户的信息了。简单来说,这个加密之后的信息写的是啥,服务器就认为用户是啥。
美其名曰,无状态,不需要消耗服务器的存储,减轻服务器压力。但是反过来,却带来了无法注销,请求头体积大,加解密效率等其他问题。然后为了解决这些问题,把redis的那一套解决方案,再引过来,两种方式结合在一起。。。讲道理,我吐了。强行混着来,jwt的意义何在呢?
用redis和不用的区别
1、用redis可以直接使jwt失效或者延期,方便
2、多个子系统可以访问redis数据库
就单点登录里的token而言,单点登录是为了实现一次登录其他相互信任的系统不用登陆就可访问的效果,既然如此就不止一个系统,每个系统的数据存在不同的数据库中,A系统不可以访问B系统的数据库,若将token存在于A系统数据库中,B系统登录时就访问不到token,但是所有系统都可以访问redis缓存数据库,而且token具有时效性,而redis天然支持设置过期时长【set(key,value,毫秒值)】
3、而且redis响应速度很快
登录时把jwt存进redis,设置成2倍的jwt有效期。登出删除redis保存的jwt。
如何防止jwt token被窃取
1、采用更安全的传输协议https
2、加密传输
3、代码层面也可以做安全检测,比如ip地址发生变化,MAC地址发生变化等等,可以要求重新登录
4、使用私钥加密生成token 公钥解密获取token中的信息
1)系统不能把jwt作为唯一的身份识别条件,不然被别人拿到了jwt就相当于获得了所有相关账户的权限,但对于这一点我还在学习中。2)存储jwt、传输的方式应该再加强。
private static final String slat = "mszlu!@#";//加密盐 因为数据库不能给人看密码  每次都用这一个字符串用来加密
 
token过期了怎么办?
我的项目是token不过期、redis记录过期
https://juejin.cn/post/7126708538440679460
如何踢人下线
直接把token和存入redis里,验证有无token即可。踢人下线直接清除redis中的token。和笔者的思路是类似的。
2、ThreadLocal保存用户信息
目的
方便鉴权,查询是否在线,减少数据库读操作
token:sysUser
不想用session(服务器存储 分布式)
文章发布 需要用户id,直接拿,评论也是一样,方便
request获取? 但是从设计上、代码分层上来说,
并发问题
降低redis使用?
redis中可以获取用户信息,但是因为redis中的key是token,要先拿到token才能拿到用户信息,但是token不是每个类中都存在,想在每个类都获取到用户信息
一般我们需要获取当前用户信息,放到缓存?每次解析token,然后传递?我们可以使用ThreadLocal来解决。将用户信息保存在线程中,当请求结束后我们在把保存的信息清除掉。这样我们才开发的时候就可以直接从全局的ThreadLocal中很方便的获取用户信息。当前线程在任何地方需要时,都可以使用
步骤
- 创建ThreadLocal类,在其中设置相关的添加、获取以及删除方法。
 - 创建登录拦截器,重新其中的
preHandle()(ThreadLocal.put())和afterCompletion()(不用了手动remove)方法。 - webmvcConfig里面注册拦截器(告诉springmvc我们要拦截谁) 排除登录注册这种就行 其他都需要登录
 
原理
Thread类中,有个ThreadLocal.ThreadLocalMap 的成员变量。ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型对象值
并发多线程场景下,每个线程Thread,在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而可以实现了线程隔离。
为什么不直接用线程id作为ThreadLocalMap的key呢?
用了两个ThreadLocal成员变量的话。如果用线程id作为ThreadLocalMap的key,怎么区分哪个ThreadLocal成员变量呢?因此还是需要使用ThreadLocal作为Key来使用。每个ThreadLocal对象,都可以由threadLocalHashCode属性唯一区分的,每一个ThreadLocal对象都可以由这个对象的名字唯一区分
 
弱引用导致的内存泄漏呢?
ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal变量被手动设置为null,即一个ThreadLocal没有外部强引用来引用它,当系统GC时,ThreadLocal一定会被回收。这样的话,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如线程池的核心线程),这些key为null的Entry的value就会一直存在一条强引用链:Thread变量 -> Thread对象 -> ThreaLocalMap -> Entry -> value -> Object 永远无法回收,造成内存泄漏。
实际上,ThreadLocalMap的设计中已经考虑到这种情况。所以也加上了一些防护措施:即在ThreadLocal的get,set,remove方法,都会清除线程ThreadLocalMap里所有key为null的value。
key是弱引用,GC回收会影响ThreadLocal的正常工作嘛?
不会的,因为有ThreadLocal变量引用着它,是不会被GC回收的,除非手动把ThreadLocal变量设置为null
ThreadLocal内存泄漏的demo
用线程池,一直往里面放对象
因为我们使用了线程池,线程池有很长的生命周期,因此线程池会一直持有tianLuoClass(ThreadLocal泛型值)对象的value值,即使设置tianLuoClass = null;引用还是存在的
为什么弱引用
当ThreadLocal的对象被回收了,因为ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动remove删除,ThreadLocal也会被回收。value则在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
InheritableThreadLocal保证父子线程间的共享数据
在子线程中,是可以获取到父线程的 InheritableThreadLocal 类型变量的值,但是不能获取到 ThreadLocal 类型变量的值(因为ThreadLocal是线程隔离)。
在Thread类中,除了成员变量threadLocals之外,还有另一个成员变量:inheritableThreadLocals。
当parent的inheritableThreadLocals不为null时,就会将parent的inheritableThreadLocals,赋值给前线程的inheritableThreadLocals。说白了,就是如果当前线程的inheritableThreadLocals不为null,就从父线程哪里拷贝过来一个过来,类似于另外一个ThreadLocal,但是数据从父线程那里来的。有兴趣的小伙伴们可以在去研究研究源码~
ThreadLocal的应用场景和使用注意点
ThreadLocal的很重要一个注意点,就是使用完,要手动调用remove()。
而ThreadLocal的应用场景主要有以下这几种:
- 使用日期工具类,当用到
SimpleDateFormat,使用ThreadLocal保证线性安全 - 全局存储用户信息(用户信息存入
ThreadLocal,那么当前线程在任何地方需要时,都可以使用) - 保证同一个线程,获取的数据库连接
Connection是同一个,使用ThreadLocal来解决线程安全的问题 - 使用
MDC保存日志信息。 
ThreadLocal特点
线程并发:在多线程并发场景下使用
**传递数据:**可以通过ThreadLocal在同一线程,不同组件中传递公共变量(保存每个线程的数据,在需要的地方可以直接获取, 避免参数直接传递带来的代码耦合问题)
线程隔离:每个线程的变量都是独立的, 不会互相影响
ThreadLocal 和Synchronized
ThreadLocal模式与Synchronized关键字都用于处理多线程并发访问变量的问题
ThreadLocal:以空间换取时间的思想, 为每一个线程都提供了一份变量的副本, 从而实现同访问而 互相不干扰。 多线程中让每个线程之间的数据相互隔离
Synchronized:以时间换取空间的思想,只提供了一份变量, 让不同的线程排队访问。多个线程之间访问资源的同步
ThreadLocal不能解决共享变量的线程安全问题
子线程访问父线程的共享变量时候,是“引用传递”,多个子线程访问的话所以线程不安全
- 每个线程独享一份new出来的实例 -> 线程安全
 - 多个线程共享一份“引用类型”实例 -> 线程不安全
 
ThreadLocal与Thread同步机制的比较
 
线性探测法
线性探测法顾名思义,就是解决冲突的函数是一个线性函数,最直接的就是在TreadLocal的代码中也用的是这样一个解决冲突的函数。
 f(x)= x+1
 
但是要注意的是TreadLocal中,是一个环状的探测,如果到达边界就会直接跨越边界到另一头去。
线性探测法的优点:
- 不用额外的空间(对比拉链法,需要额外链表)
 - 探测序列具有局部性,可以利用系统缓存,减少IO(连续的内存地址)
 
缺点:
- 耗费时间>O(1)(最差O(n))
 - 冲突增多——以往的冲突会导致后续的连环冲突(时间复杂度趋近O(n))
 
之前我们说过,线性探测法有个问题是,一旦发生碰撞,很可能之后每次都会产生碰撞,导致连环撞车。而使用0x61c88647这个值做一个hash的增长值就可以从一定程度上解决这个问题让生成出来的值较为均匀地分布在2的幂大小的数组中。也就是说当我们用0x61c88647作为步长累加为每个ThreadLocal分配各自的ID也就是threadLocalHashCode再与2的幂取模,得到的结果分布很均匀。
0x61c88647选取其实是与斐波那契散列有关,这个就是数学知识了,这里不展开。
3、日志记录放入线程池
原因
不能让记录日志出现失误影响用户的登录
为了出现错误时候的排查
记录日志录入数据库时,脱离主线程,实现异步插入,这样不会拖延主线程的执行时间
 ps:记录日志,可以写在业务逻辑中,也可以利用aop自动记录。
我的线程池参数?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DmLQyJqn-1681383922611)(https://raw.githubusercontent.com/viacheung/img/main/image/QFR%7D%7D83L%60%255T_PO3GGRG@NR.png)]
这么设置也挺好,你的最佳核心线程数应该在cpuNum~cpuNum2 之间呢,cpuNum2 设置的太多了,cpu负载过大,速度反而会慢下来,cpuNum给的太少,也不能充分利用性能,利用队列满了之后溢出来的任务,被备用线程也就是核心线程之外的那些线程给处理了的这种机制,让他自动找补一下。
关于异步
 
如果是注册这类的功能不适合异步,肯定要同步的,注册成功才返回成功。我这个上传视频到OSS要异步的原因是因为用户要先把视频发到我的云服务器中,云服务器再去接收到的视频放到阿里云OSS中,是两个步骤。如果要两个操作成功才返回给用户上传成功的话太久了。所以在用户上传视频到云服务器成功后就可以返回成功信息给用户了,后面服务器再自己把视频上传到OSS(这个过程挺慢的)。具体逻辑还不够完善(主要是钱没到位)。不是什么情况下都可以用异步处理的。要一个操作设计多个步骤,并且后续的步骤都和用户没什么关系的情况下才会考虑要不要异步处理(可以提供用户体验,不用一直等)。比如银行的某些代付交易,在上游的数据检查完成正常后就会直接返回上游交易成功,用户直接就看到了交易结果,后续的调用银行核心记账这些操作其实还在跑,可能几秒甚至几分钟后才是真正的交易完成。
步骤
1.配置线程池(ThreadPoolTaskExecutor)
 2.自定义一个异步任务管理器
 3.自定义任务
 4.指定地点处,调用执行任务管理器,传入指定的任务
execute() vs submit()
都是提交任务到线程池
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;- submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 
get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 
为什么要用线程池?
资源消耗–重复利用 响应速度—立即执行 可管理性—线程池统一分配
- 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
 - 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
 - 提高线程的可管理性。 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
 
如何创建线程池
1、构造⽅法
2、通过 Executor 框架的⼯具类 Executors 来实现
三种ThreadPoolExecutor:
fixedThreadPool() 固定线程数
SingleThreadExecutor 只有一个线程
CachedThreadPool 根据情况调整 数量不固定
ThreadPoolExecutor 类分析
ThreadPoolExecutor 构造函数七大参数分析
-  
corePoolSize : 核心线程大小。线程池一直运行,核心线程就不会停止。
 -  
maximumPoolSize :线程池最大线程数量。非核心线程数量=maximumPoolSize-corePoolSize
 -  
keepAliveTime :非核心线程的心跳时间。如果非核心线程在keepAliveTime内没有运行任务,非核心线程会消亡。
 -  
workQueue :阻塞队列。ArrayBlockingQueue,LinkedBlockingQueue等,用来存放线程任务。
 -  
defaultHandler :饱和策略。ThreadPoolExecutor类中一共有4种饱和策略。通过实现
RejectedExecutionHandler
接口。
饱和策略
- AbortPolicy : 线程任务丢弃报错。默认饱和策略。
 - DiscardPolicy : 线程任务直接丢弃不报错。
 - DiscardOldestPolicy : 将workQueue队首任务丢弃,将最新线程任务重新加入队列执行。
 - CallerRunsPolicy :线程池之外的线程直接调用run方法执行。
 
 -  
ThreadFactory :线程工厂。新建线程工厂。
 
线程池原理分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JL9eoruj-1681383922612)(https://raw.githubusercontent.com/viacheung/img/main/image/1460000039258685)]
- 线程池执行execute/submit方法向线程池添加任务,当任务小于核心线程数corePoolSize,线程池中肯定可以创建新的线程。
 - 当任务大于核心线程数corePoolSize,看看阻塞队列满了没,没满的话,就向阻塞队列添加任务,如果满的话,看看任务数和最大线程数的关系,如果还小于最大线程数的话,那我创建非核心线程。
 - 如果线程数量大于maximumPoolSize,说明当前设置线程池中线程已经处理不了了,就会执行饱和策略。
 
常见java线程池
1、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
不足:这种方式虽然可以根据业务场景自动的扩展线程数来处理我们的业务,但是最多需要多少个线程同时处理缺是我们无法控制的;
优点:如果当第二个任务开始,第一个任务已经执行结束,那么第二个任务会复用第一个任务创建的线程,并不会重新创建新的线程,提高了线程的复用率;
2、newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
**缺点:**它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
优点:newFixedThreadPool的线程数是可以进行控制的,因此我们可以通过控制最大线程来使我们的服务器打到最大的使用率,同事又可以保证及时流量突然增大也不会占用服务器过多的资源。
3、newSingleThreadExecutor
创建一个单线程化的Executor,只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
4、newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
线程池常用的阻塞队列有哪些?
 
表格左侧是线程池,右侧为它们对应的阻塞队列,可以看到 5 种线程池对应了 3 种阻塞队列
-  
LinkedBlockingQueue 对于 FixedThreadPool 和 SingleThreadExector 而言,它们使用的阻塞队列是容量为 Integer.MAX_VALUE 的 LinkedBlockingQueue,可以认为是无界队列。由于 FixedThreadPool 线程池的线程数是固定的,所以没有办法增加特别多的线程来处理任务,因此需要这样一个没有容量限制的阻塞队列来存放任务。
由于线程池的任务队列永远不会放满,所以线程池只会创建核心线程数量的线程,所以此时的最大线程数对线程池来说没有意义,因为并不会触发生成多于核心线程数的线程。
 -  
SynchronousQueue 第二种阻塞队列是 SynchronousQueue,对应的线程池是 CachedThreadPool。线程池 CachedThreadPool 的最大线程数是 Integer 的最大值,可以理解为线程数是可以无限扩展的。CachedThreadPool 和上一种线程池 FixedThreadPool 的情况恰恰相反,FixedThreadPool 的情况是阻塞队列的容量是无限的,而这里 CachedThreadPool 是线程数可以无限扩展,所以 CachedThreadPool 线程池并不需要一个任务队列来存储任务,因为一旦有任务被提交就直接转发给线程或者创建新线程来执行,而不需要另外保存它们。 我们自己创建使用 SynchronousQueue 的线程池时,如果不希望任务被拒绝,那么就需要注意设置最大线程数要尽可能大一些,以免发生任务数大于最大线程数时,没办法把任务放到队列中也没有足够线程来执行任务的情况。
 -  
DelayedWorkQueue 第三种阻塞队列是DelayedWorkQueue,它对应的线程池分别是 ScheduledThreadPool 和 SingleThreadScheduledExecutor,这两种线程池的最大特点就是可以延迟执行任务,比如说一定时间后执行任务或是每隔一定的时间执行一次任务。
 
DelayedWorkQueue 的特点是内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择 DelayedWorkQueue,是因为它们本身正是基于时间执行任务的,而延迟队列正好可以把任务按时间进行排序,方便任务的执行。
源码中线程池是怎么复用线程的?
源码中ThreadPoolExecutor中有个内置对象Worker,每个worker都是一个线程,worker线程数量和参数有关,每个worker会while死循环从阻塞队列中取数据,通过置换worker中Runnable对象,运行其run方法起到线程置换的效果,这样做的好处是避免多线程频繁线程切换,提高程序运行性能。
如何合理配置线程池参数
选择的关键点是:
- 尽量减少线程切换和管理的开支
 - 最大化利用cpu
 
- 并发高 耗时短:
 
-  
这种场景适合线程尽量少,因为如果线程太多,任务执行时间段很快就执行完了,有可能出现线程切换和管理多耗费的时间,大于任务执行的时间,这样效率就低了。线程池线程数可以设置为CPU核数+1
2.并发比较低,耗时比较长的任务:
 
需要我们自己配置最大线程数 maximumPoolSize ,为了高效的并发运行,这时需要看我们的业务是IO密集型还是CPU密集型。
CPU密集型 CPU密集的意思是该任务需要最大的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才能得到加速(通过多线程)。而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那么多。一般公式:CPU核数 + 1个线程数
IO密集型 IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上这种加速主要就是利用了被浪费掉的阻塞时间。
IO 密集型时,大部分线程都阻塞,故需要多配制线程数。公式为:
CPU核数*2
CPU核数/(1-阻塞系数) 阻塞系数在0.8~0.9之间
查看CPU核数:
System.out.println(Runtime.getRuntime().availableProcessors());
例如:8核CPU:8/ (1 - 0.9) = 80个线程数注:IO密集型(某大厂实践经验)核心线程数 = CPU核数 / (1-阻塞系数)
或着
CPU密集型:核心线程数 = CPU核数 + 1
IO密集型:核心线程数 = CPU核数 * 2 
也有说配置为cpu核数
Executor和Executors的区别?
Executors :按照需求创建了不同的线程池,来满足业务的需求。
Executor :执行线程任务 获得任务执行的状态并且可以获取任务的返回值。
使用ThreadPoolExecutor 可以创建自定义线程池。Future 表示异步计算的结果,他提供了检查计算是否完成的方法,以等待计算的完成,并可以使用get()方法获取计算的结果
线程池应用场景
1、异步发送邮件通知
 发送一个任务,然后注入到线程池中异步发送。
2、心跳请求任务
 创建一个任务,然后定时发送请求到线程池中。
3、如果用户量比较大,导致占用过多的资源,可能会导致我们的服务由于资源不足而宕机;
3.1 线程池中线程的使用率提升,减少对象的创建、销毁;
3.2 线程池可以控制线程数,有效的提升服务器的使用资源,避免由于资源不足而发生宕机等问题;
4、Elasticsearch搜索 +同步数据到索引
步骤
1、导包 做配置
2、配置Document
先要配置实体和ES的映射,通过在实体类中加入注解的方式来自动映射跟索引,我这里是配置了product索引和实体的映射
3、使用ElasticsearchRestTemplate
在Spring启动的时候自动注入了该Bean,它封装了操作Elasticsearch的增删改查API
完全匹配查询条件 --> 按照阅读量排序—>高亮显示—>分页
es刷新时间
1s 为什么要延长 es主要业务写日志
5、阅读评论数放入Redis中
查看完文章了,新增阅读数,做了一个更新操作,更新时加写锁,阻塞其他的读操作,性能就会比较低(没办法解决,增加阅读数必然要加锁)
更新增加了此次接口的耗时(考虑减少耗时)如果一旦更新出问题,不能影响查看操作
 线程池 可以把更新操作扔到 线程池中去执行和主线程就不相关了
 threadService.updateArticleViewCount(articleMapper, article);
在这里我们采用redis incr自增实现
redis定时任务自增实现阅读数和评论数更新
阅读数和评论数 ,考虑把阅读数和评论数 增加的时候 放入redis incr自增,使用定时任务 定时把数据固话到数据库当中
定时任务 :遍历redis中前缀是VIEW_COUNT的所有key,通过subString方法获取文章id,获取key存储的阅读数,把文章id和阅读数放入ViewCountQuery对象中,对象放入list集合中,批量更新
@Scheduled(cron = "0 30 4 ? * *")//每天凌晨四点半触发
 
https://blog.csdn.net/m0_52914401/article/details/124343310?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-124343310-blog-125651961.pc_relevant_recovery_v2&spm=1001.2101.3001.4242.1&utm_relevant_index=3
出现缓存不一致问题?
1、先删除缓存,后更新数据库
答案一:延时双删
(1)先淘汰缓存
(2)再写数据库
(3)休眠1秒,再次淘汰缓存,这么做,可以将1秒内所造成的缓存脏数据,再次删除。确保读请求结束,写请求可以删除读请求造成的缓存脏数据。自行评估自己的项目的读数据业务逻辑的耗时,写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。
(我的理解:请求A先删缓存再往DB写数据,就算这时B来查数据库,缓存没数据,然后查DB,此时查到的是旧数据,写到缓存,A等待B写完之和再删缓存,这样就缓存一致)
如果使用的是 Mysql 的读写分离的架构的话,那么其实主从同步之间也会有时间差。
 
此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)
- 请求 A 更新操作,删除了 Redis
 - 请求主库进行更新操作,主库与从库进行同步数据的操作
 - 请 B 查询操作,发现 Redis 中没有数据
 - 去从库中拿去数据
 - 此时同步数据(binlog没写完)还未完成,拿到的数据是旧数据
 
此时的解决办法就是如果是对 Redis 进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询。
 
答案二: 更新与读取操作进行异步串行化
采用更新与读取操作进行异步串行化
异步串行化
我在系统内部维护n个内存队列,更新数据的时候,根据数据的唯一标识,将该操作路由之后,发送到其中一个jvm内部的内存队列中(对同一数据的请求发送到同一个队列)。读取数据的时候,如果发现数据不在缓存中,并且此时队列里有更新库存的操作,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也将发送到同一个jvm内部的内存队列中。然后每个队列对应一个工作线程,每个工作线程串行地拿到对应的操作,然后一条一条的执行。
这样的话,一个数据变更的操作,先执行删除缓存,然后再去更新数据库,但是还没完成更新的时候,如果此时一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,排在刚才更新库的操作之后,然后同步等待缓存更新完成,再读库。
读操作去重
多个读库更新缓存的请求串在同一个队列中是没意义的,因此可以做过滤,如果发现队列中已经有了该数据的更新缓存的请求了,那么就不用再放进去了,直接等待前面的更新操作请求完成即可,待那个队列对应的工作线程完成了上一个操作(数据库的修改)之后,才会去执行下一个操作(读库更新缓存),此时会从数据库中读取最新的值,然后写入缓存中。
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。(返回旧值不是又导致缓存和数据库不一致了么?那至少可以减少这个情况发生,因为等待超时也不是每次都是,几率很小吧。这里我想的是,如果超时了就直接读旧值,这时候仅仅是读库后返回而不放缓存)
2、先更新数据库,后删除缓存
这一种情况也会出现问题,比如更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ygWZDQKt-1681383922614)(https://raw.githubusercontent.com/viacheung/img/main/image/1735bb5881fb4a1b~tplv-t2oaga2asx-watermark.awebp)]
此时解决方案就是利用消息队列进行删除的补偿。具体的业务逻辑用语言描述如下:
- 请求 A 先对数据库进行更新操作
 - 在对 Redis 进行删除操作的时候发现报错,删除失败
 - 此时将Redis 的 key 作为消息体发送到消息队列中
 - 系统接收到消息队列发送的消息后再次对 Redis 进行删除操作
 
但是这个方案会有一个缺点就是会对业务代码造成大量的侵入,深深的耦合在一起,所以这时会有一个优化的方案,我们知道对 Mysql 数据库更新操作后在binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3LQvY5Dt-1681383922616)(https://raw.githubusercontent.com/viacheung/img/main/image/1735bb588215b298~tplv-t2oaga2asx-watermark.awebp)]
6、RabbitMQ消息队列实现ES和数据库的数据同步
主要是增删改帖子
这里用的Direct队列
- Fanout交换机将消息路由给每一个与之绑定的队列
 - Direct交换机根据RoutingKey判断路由给哪个队列
 - 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
 
步骤
1、导包:spring-boot-starter-amqp
SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。
2、添加配置
spring:
rabbitmq:
host: localhost
port: 5672
username: admin
password: 123456
virtual-host: myHost
 
3、RabbitMQ的配置 声明Exchange、Queue、RoutingKey
4、生产者对应的增删改里面发送消息
所谓的生产者就是我们数据库的服务方,当我们对数据库的数据进行增删改的时候,我们应该像消息队列发送消息来通知ES我们进行了增删改操作,以便ES进行数据的同步。
5、消费者MQListener监听消息
所谓的消费者就是ES服务的操作方,通过实时的对消息队列的监听,通过消息队列对应的key值来进行选择服务的调用,不同的key调用不同的服务,获取服务方传输的数据,然后进行数据的同步。
7、死信交换机
目的
实现消息的延迟投递,避免消息丢失或无限制的重试
概念
当你在消费消息时,如果队列里的消息出现以下情况
 1,消息被否定确认,使用 channel.basicNack 或channel.basicReject ,并且此时requeue 属性被设置为false。
 2,消息在队列的存活时间超过设置的TTL时间。
 3,消息队列的消息数量已经超过最大队列长度。
那么该消息将成为“死信”。“死信”消息会被RabbitMQ进行特殊处理,如果配置了死信队列信息,那么该消息将会被丢进死信队列中,如果没有配置,则该消息将会被丢弃。
步骤
大概可以分为以下步骤:
1,配置业务队列,绑定到业务交换机上
 2,为业务队列配置死信交换机和路由key
 3,为死信交换机配置死信队列
为每个需要使用死信的业务队列配置一个死信交换机,这里同一个项目的死信交换机可以共用一个,然后为每个业务队列分配一个单独的路由key。
有了死信交换机和路由key后,接下来,就像配置业务队列一样,配置死信队列,然后绑定在死信交换机上。也就是说,死信队列并不是什么特殊的队列,只不过是绑定在死信交换机上的队列。死信交换机也不是什么特殊的交换机,只不过是用来接受死信的交换机,所以可以为任何类型【Direct、Fanout、Topic】。一般来说,会为每个业务队列分配一个独有的路由key,并对应的配置一个死信队列进行监听,也就是说,一般会为每个重要的业务队列配置一个死信队列。
死信消息变化
如果队列配置了参数 x-dead-letter-routing-key 的话,“死信”的路由key将会被替换成该参数对应的值。如果没有设置,则保留该消息原有的路由key。
应用场景
确保未被正确消费的消息不被丢弃(更新删除修改帖子)
发生消费异常可能原因:
- 消息信息本身存在错误
 - 处理过程中参数校验异常
 - 网络波动导致的查询异常等等
 
当发生异常时,当然不能每次通过日志来获取原消息,然后让运维帮忙重新投递消息(没错,以前就是这么干的= =)。通过配置死信队列,可以让未正确处理的消息暂存到另一个队列中,待后续排查清楚问题后,编写相应的处理代码来处理死信消息,这样比手工恢复数据要好太多了。
总结
死信队列其实并没有什么神秘的地方,不过是绑定在死信交换机上的普通队列,而死信交换机也只是一个普通的交换机,不过是用来专门处理死信的交换机。
总结一下死信消息的生命周期:
1,业务消息被投入业务队列
 2,消费者消费业务队列的消息,由于处理过程中发生异常,于是进行了nck或者reject操作
 3,被nck或reject的消息由RabbitMQ投递到死信交换机中
 4,死信交换机将消息投入相应的死信队列
 5,死信队列的消费者消费死信消息
 ———————————————
8、RabbitMQ如何处理消息丢失
1)生产者弄丢了数据
生产者将数据发送到rabbitmq的时候,可能因为网络问题导致数据就在半路给搞丢了。
1.1 使用事务(不推荐)
生产者发送数据前开启事务,然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)。但是问题是,开始rabbitmq事务机制,基本上吞吐量会下来,因为太耗性能。
1.2 发送回执确认(推荐)
在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。
但如果RabbitMQ服务端正常接收到了,把ack信息发送给生产者,结果这时网断了:
可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。(消费者就要处理幂等问题,多次接收到同一条消息)
区别
事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息rabbitmq接收了之后会异步回调你一个接口通知你这个消息接收到了。
所以一般在生产者这块避免数据丢失,都是用confirm机制的。
2)RabbitMQ弄丢了数据-开启RabbitMQ的数据持久化
数据持久化:rabbitmq自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,rabbitmq还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。
使用:跟生产者那边的confirm机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者ack了,所以哪怕是在持久化到磁盘之前,rabbitmq挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。
步骤: 第一个是创建queue的时候将其设置为持久化,这样就可以保证rabbitmq持久化queue的元数据。
第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的
这样abbitmq哪怕是挂了,再次重启,也会从磁盘上重启恢复queue,恢复这个queue里的数据。
3)消费端弄丢了数据
主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了比如重启了,那么就尴尬了,RabbitMQ认为你都消费了,这数据就丢了。或者消费者拿到数据之后挂了,这时候需要MQ重新指派另一个消费者去执行任务
这个时候得用RabbitMQ提供的ack机制,也是一种处理完成发送回执确认的机制。如果MQ等待一段时间后你没有发送过来处理完成 那么RabbitMQ就认为你还没处理完,这个时候RabbitMQ会把这个消费分配给别的consumer去处理,消息是不会丢的。
https://segmentfault.com/a/1190000019125512
RabbitMQ相关知识
多线程vsMQ
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZDHh4XB-1681383922618)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230325004307581.png)]
为什么使用MQ?
使用MQ的场景很多,主要有三个:解耦、异步、削峰。
- 解耦:假设现在,日志不光要插入到数据库里,还要在硬盘中增加文件类型的日志,同时,一些关键日志还要通过邮件的方式发送给指定的人。那么,如果按照原来的逻辑,A可能就需要在原来的代码上做扩展,除了B服务,还要加上日志文件的存储和日志邮件的发送。但是,如果你使用了MQ,那么,A服务是不需要做更改的,它还是将消息放到MQ中即可,其它的服务,无论是原来的B服务还是新增的日志文件存储服务或日志邮件发送服务,都直接从MQ中获取消息并处理即可。这就是解耦,它的好处是提高系统灵活性,扩展性。
 
 
- 异步:可以将一些非核心流程,如日志,短信,邮件等,通过MQ的方式异步去处理。这样做的好处是缩短主流程的响应时间,提升用户体验。
 
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vk2gmra-1681383922619)(https://raw.githubusercontent.com/viacheung/img/main/image/727602-20200108091722601-747710174.png)]
- 削峰:MQ的本质就是业务的排队。所以,面对突然到来的高并发,MQ也可以不用慌忙,先排好队,不要着急,一个一个来。削峰的好处就是避免高并发压垮系统的关键组件,如某个核心服务或数据库等。
 
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bq0Xl4Fe-1681383922620)(https://raw.githubusercontent.com/viacheung/img/main/image/727602-20200108091915241-1598228624.png)]
后面请求积压在MQ里面 不过是短暂的
消息队列的缺点
1、 系统可用性降低
系统引入的外部依赖越多,越容易挂掉。
2、 系统复杂度提高
加入了消息队列,要多考虑很多方面的问题,比如:一致性问题、如何保证消息不被重复消费、如何保证消息可靠性传输等。因此,需要考虑的东西更多,复杂性增大。
3、 一致性问题
A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD 两个系统写库成功了,结果 C 系统写库失败了,这就数据不一致了。
生产者消息运转的流程
Producer先连接到Broker,建立连接Connection,开启一个信道(Channel)。Producer声明一个交换器并设置好相关属性。Producer声明一个队列并设置好相关属性。Producer通过路由键将交换器和队列绑定起来。Producer发送消息到Broker,其中包含路由键、交换器等信息。- 相应的交换器根据接收到的路由键查找匹配的队列。
 - 如果找到,将消息存入对应的队列,如果没有找到,会根据生产者的配置丢弃或者退回给生产者。
 - 关闭信道。
 - 管理连接。
 
消费者接收消息过程?
Consumer先连接到Broker,建立连接Connection,开启一个信道(Channel)。- 向
Broker请求消费响应的队列中消息,可能会设置响应的回调函数。 - 等待
Broker回应并投递相应队列中的消息,接收消息。 - 消费者确认收到的消息,
ack。 RabbitMq从队列中删除已经确定的消息。- 关闭信道。
 - 关闭连接。
 
生产者如何将消息可靠投递到RabbitMQ?
Producer发送消息给MQ- MQ将消息持久化后,发送Ack消息给
Producer,此处有可能因为网络问题导致Ack消息无法发送到Producer,那么Producer在等待超时后,会重传消息; Producer收到Ack消息后,认为消息已经投递成功
RabbitMQ如何将消息可靠投递到消费者?
- MQ将消息push给
Consumer(或Consumer来pull消息) Consumer得到消息并做完业务逻辑Consumer发送Ack消息给MQ,通知MQ删除该消息,此处有可能因为网络问题导致Ack失败,那么Consumer会重复消息,这里就引出消费幂等的问题;- MQ将已消费的消息删除。
 
消息幂等
根据业务特性,选取业务中唯一的某个属性,比如订单号作为区分消息是否重复的属性。在进行插入订单之前,先从数据库查询一下该订单号的数据是否存在,如果存在说明是重复消费,如果不存在则插入。伪代码如下:
我遇到的问题
1、前后端交互问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ODbMiyUn-1681383922622)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230322145324052.png)]
其他问题
1、TEXT 存储文章内容
TINYTEXT(255长度)
TEXT(65535)
MEDIUMTEXT(int最大值16M)
LONGTEXT(long最大值4G)
2、Docker
Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题?
- Docker允许开发中将应用、依赖、函数库、配置一起打包,形成可移植镜像
 - Docker应用运行在容器中,使用沙箱机制,相互隔离
 
Docker如何解决开发、测试、生产环境有差异的问题?
- Docker镜像中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行
 
Docker是一个快速交付应用、运行应用的技术,具备下列优势:
- 可以将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任意Linux操作系统
 - 运行时利用沙箱机制形成隔离容器,各个应用互不干扰
 - 启动、移除都可以通过一行命令完成,方便快捷
 
Docker和虚拟机的差异:
- docker是一个系统进程;虚拟机是在操作系统中的操作系统
 - docker体积小、启动速度快、性能好;虚拟机体积大、启动速度慢、性能一般
 
基本概念
镜像:
- 将应用程序及其依赖、环境、配置打包在一起
 
容器:
- 镜像运行起来就是容器,一个镜像可以运行多个容器 容器有自己独立的cpu 内存 文件系统 避免污染镜像
 
Docker结构:
-  
服务端:接收命令或远程请求,操作镜像或容器
 -  
客户端:发送命令或者请求到Docker服务端
 
DockerHub:
- 一个镜像托管的服务器,类似的还有阿里云镜像服务,统称为DockerRegistry
 
命令
docker run命令的常见参数有哪些?
- –name:指定容器名称
 - -p:指定端口映射
 - -d:让容器后台运行
 
查看容器日志的命令:
- docker logs
 - 添加 -f 参数可以持续查看日志
 
查看容器状态:
- docker ps
 - docker ps -a 查看所有容器,包括已经停止的
 
数据卷
数据卷的作用:
- 将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全
 
数据卷操作:
- docker volume create:创建数据卷
 - docker volume ls:查看所有数据卷
 - docker volume inspect:查看数据卷详细信息,包括关联的宿主机目录位置
 - docker volume rm:删除指定数据卷
 - docker volume prune:删除所有未使用的数据卷
 
docker run的命令中通过 -v 参数挂载文件或目录到容器中:
- -v volume名称:容器内目录
 - -v 宿主机文件:容器内文件
 - -v 宿主机目录:容器内目录
 
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aErY5ZA0-1681383922623)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230403002812590.png)]
数据卷挂载与目录直接挂载的
- 数据卷挂载耦合度低,由docker来管理目录,但是目录较深,不好找
 - 目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看、
 
dockerfile
- Dockerfile的本质是一个文件,通过指令描述镜像的构建过程
 - Dockerfile的第一行必须是FROM,从一个基础镜像来构建
 - 基础镜像可以是基本操作系统,如Ubuntu。也可以是其他人制作好的镜像,例如:java:8-alpine
 
dockercompose
Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器!
3、linux命令
RPC
反射:newInstance() (实例化) getDeclaredMethod(public方法) invoke(方法输出)
蘑菇识别
上传图片到根目录:根目录 input文件夹存一下这个图片,然后python identify.py 路径+图片名称 识别出结果 各个种类的预测百分率(字符串) 对字符串进行一个处理
取前三,返回一个封装结果,同时还支持详细查询蘑菇的描述
论坛知识点(业务)
文章列表
通过传入的PageParams对象进行查询 调用service -> mapper ->xml->sql的select查询语句 ,传参包括分类,标签,年月(查询范围)返回IPage对象,copyList转化格式
最热标签
SELECT tag_id FROM ms_article_tag group by tag_id order by count(*) desc limit #{limit}
 
找ms_article_tag表查出对应article最多的tagid 然后再根据tagid查tag,封装到tagvo里面 返回名称+头像
最热文章
sevice层里面直接调用LambdaQueryWrapper

