凡客诚品网站,惠州外发加工网,百度渠道开户,进行seo网站建设代办
AQS
Concurrenthashmap
投递公司
公司状态链接时间腾讯美团#xff08;张云峰#xff09;笔试完成https://zhaopin.meituan.com/web/personalCenter/deliveryRecord字节https://jobs.bytedance.com/experienced/position/application阿里小红书腾讯云智待面试4.5腾讯…代办
AQS
Concurrenthashmap
投递公司
公司状态链接时间腾讯美团张云峰笔试完成https://zhaopin.meituan.com/web/personalCenter/deliveryRecord字节https://jobs.bytedance.com/experienced/position/application阿里小红书腾讯云智待面试4.5腾讯音乐笔试完成京东测评完成https://campus.jd.com/#/myDeliver?typepresent微众银行一起触发更多可能 (webank.com)阅文集团https://www.nowcoder.com/careers/yuewen/122324中国银行104573https://applyjob.chinahr.com/page/job/success?projectId63f47e7255cbed088c78eed1中国邮政https://xiaoyuan.zhaopin.com/scrd/postprocess?cid58805pid101739970productId7channelIdnulltaskIdnull蚂蚁挂到蚂蚁集团https://talent.antgroup.com/personal/campus-application特斯拉https://app.mokahr.com/campus-recruitment/tesla/91939#/candidateHome/applications58https://campus.58.com/Portal/Apply/Index立得空间https://www.showmebug.com/written_pads/HRUHZN恒生https://campus.hundsun.com/personal/deliveryRecordwind银泰百货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/applicationsciderhttps://ciderglobal.jobs.feishu.cn/504718/position/application360运维开发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/applicationsbiliblihttps://jobs.bilibili.com/campus/recordsgeekhttps://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?sourceTokend895a22a006b8a6da61313d9b4091850#/candidateHome/applications虎牙https://app.mokahr.com/campus_apply/huya/4112?edit1#/candidateHome/applicationsoppohttps://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、JWTtokenredis
用户jwt鉴权流程
jwt 有三部分组成A.B.C
AHeader{“type”:“JWT”,“alg”:“HS256”} 固定 定义生成签名算法以及Token的类型
Bplayload存放实际需要传递的信息比如用户id过期时间等等可以被解密不能存放敏感信息
C: 签证A和B加上秘钥 加密而成只要秘钥不丢失可以认为是安全的。
jwt 验证主要就是验证C部分 是否合法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9p98Egvg-1681383922609)(https://raw.githubusercontent.com/viacheung/img/main/image/image-20230320181343651.png)] 用户使用用户名密码来请求服务器 服务器进行验证用户的信息 3.服务器通过验证发送给用户一个tokenheader.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天然支持设置过期时长【setkey,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,getremove的时候会被清除。
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) x1但是要注意的是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_PO3GGRGNR.png)]
这么设置也挺好你的最佳核心线程数应该在cpuNum~cpuNum2 之间呢cpuNum2 设置的太多了cpu负载过大速度反而会慢下来cpuNum给的太少也不能充分利用性能利用队列满了之后溢出来的任务被备用线程也就是核心线程之外的那些线程给处理了的这种机制让他自动找补一下。
关于异步 如果是注册这类的功能不适合异步肯定要同步的注册成功才返回成功。我这个上传视频到OSS要异步的原因是因为用户要先把视频发到我的云服务器中云服务器再去接收到的视频放到阿里云OSS中是两个步骤。如果要两个操作成功才返回给用户上传成功的话太久了。所以在用户上传视频到云服务器成功后就可以返回成功信息给用户了后面服务器再自己把视频上传到OSS这个过程挺慢的。具体逻辑还不够完善主要是钱没到位。不是什么情况下都可以用异步处理的。要一个操作设计多个步骤并且后续的步骤都和用户没什么关系的情况下才会考虑要不要异步处理可以提供用户体验不用一直等。比如银行的某些代付交易在上游的数据检查完成正常后就会直接返回上游交易成功用户直接就看到了交易结果后续的调用银行核心记账这些操作其实还在跑可能几秒甚至几分钟后才是真正的交易完成。
步骤
1.配置线程池ThreadPoolTaskExecutor 2.自定义一个异步任务管理器 3.自定义任务 4.指定地点处调用执行任务管理器传入指定的任务
execute() vs submit()
都是提交任务到线程池
execute() 方法用于提交不需要返回值的任务所以无法判断任务是否被线程池执行成功与否submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象通过这个future对象可以判断任务是否执行成功并且可以通过future的get()方法来获取返回值get()方法会阻塞当前线程直到任务完成而使用 getlong timeoutTimeUnit unit方法则会阻塞当前线程一段时间后立即返回这时候有可能任务没有执行完。
为什么要用线程池
资源消耗–重复利用 响应速度—立即执行 可管理性—线程池统一分配
降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。 当任务到达时任务可以不需要的等到线程创建就能立即执行。提高线程的可管理性。 线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。
如何创建线程池
1、构造⽅法
2、通过 Executor 框架的⼯具类 Executors 来实现
三种ThreadPoolExecutor:
fixedThreadPool() 固定线程数
SingleThreadExecutor 只有一个线程
CachedThreadPool 根据情况调整 数量不固定
ThreadPoolExecutor 类分析
ThreadPoolExecutor 构造函数七大参数分析 corePoolSize 核心线程大小。线程池一直运行核心线程就不会停止。 maximumPoolSize 线程池最大线程数量。非核心线程数量maximumPoolSize-corePoolSize keepAliveTime 非核心线程的心跳时间。如果非核心线程在keepAliveTime内没有运行任务非核心线程会消亡。 workQueue 阻塞队列。ArrayBlockingQueueLinkedBlockingQueue等用来存放线程任务。 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核CPU8/ (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_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-124343310-blog-125651961.pc_relevant_recovery_v2spm1001.2101.3001.4242.1utm_relevant_index3
出现缓存不一致问题
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: myHost3、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机制的。
2RabbitMQ弄丢了数据-开启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发送消息给MQMQ将消息持久化后发送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)
MEDIUMTEXTint最大值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 psdocker 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() 实例化 getDeclaredMethodpublic方法 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 查询条件构造器 lamda表达式 链式查询