获得徽章 14
- #青训营笔记创作活动#
1月27日 打卡day48
今日学习
今天的内容关于Traceld。
先分析一下,我们想实现的核心功能是搜索,必然是用 ES 实现,那问题就转换成如何将日志收集并存储到 ES。
日志大家都不陌生了,可以在控制台打印,也可以存入文件,那能不能直接输入 ES 呢,好像没听说过。
这里就要用到 Logstash 来收集日志,Spring 默认的日志框架 Logback 已经对其提供了支持,我们要做的只是编写配置文件。
Logstash 有个问题就是非常占用内存,所以本文后面会介绍另一个比较轻量级的日志收集工具 FileBeat ,由 Go 语言编写。
同时对于真实的线上环境为了保证吞吐量和可靠性,都会引入 Kafka 进行解耦。
安装 ES 的可视化工具。浏览器访问:http://localhost:5601/。下载logstash。配置 boot 应用的日志输出。启动应用,观看 Logstash 的控制台,会跟着打印日志,再打开 ES ,创建我们配置好的查询索引,神奇的事情发生了,日志一条一条的展示出来。下载 FileBeat 。Filebeat 是基于监控日志文件有没有新增来同步数据的,所以需要配置日志文件的目录。可以直接输出到 ES ,也可以输出到 Logstash 。启动项目,发现日志已写入文件。进入 ES 查询,同样查询到日志。
展开赞过评论1 - #青训营笔记创作活动#
1月26日 打卡day47
今日学习
今天的内容关于SSO单点登录,包括具体实现和安全优化。
SSO单点登录具体实现通过token+redis或通过token+各个子系统一个redis。
token+redis需要授权的ticket和子系统token。
token+redis代码实现分为服务端实现和客户端实现。
客户端实现包括后台拦截器和页面拦截器。
后台拦截器包含局部会话token为空和局部绘画存在则直接放行。
全局会话:在SSO登录页面登录后,我们就认为建立起了全局会话
判定标志:SSO页面的session存在且未过期
局部会话:在各个子系统,是否已经登录过,这个我们称为局部会话
判定标志:子系统存在可行的token【未过期且有效】
ticket:SSO系统颁发给子系统的凭证,有此凭证且有效的话,表明SSO系统允许子系统去建立局部会话【生成token】
token:子系统的访问凭证,各个子系统的token是不一样的,具体视业务而定,子系统也需要配置相应的拦截器来检测token
重放攻击就是:黑客拦截了我们的请求,获得我们发给服务端的一个合法请求,然后重复的发送该合法请求,若该请求耗时过长,极端调用可能会搞崩我们的系统
一次HTTP请求从发出到到达服务器的时间是不会超过60s的,当你发送一个请求时必须携带一个时间戳timestamp,假设值为20 。
当请求发送到服务器之后,服务器会取出服务器的当前时间, 假设为 now =100, 很明显 now -timestamp>60s,那么服务器就认为请求是不合法的。
展开赞过评论1 - #青训营笔记创作活动#
1月25日 打卡day46
今日学习
今天的内容关于MySQL底层原理。
死锁是指两个或两个以上的线程(或进程)在运行过程中,因为资源竞争而造成相互等待、相互僵持的现象。一般当程序中出现死锁问题后,若无外力介入,则不会解除“僵持”状态,它们之间会一直相互等待下去。当死锁问题出现时,MySQL会自动检测并介入,强制回滚结束一个“死锁的参与者(事务)”,从而打破死锁的僵局,让另一个事务能继续执行。在InnoDB中其实提供了锁的超时机制,也就是一个事务在长时间内无法获取到锁时,就会主动放弃等待,抛出相关的错误码及信息,然后返回给客户端。默认的锁超时时间是50s,也就是在50s内未获取到锁的事务,会自动结束并返回。那也就意味着当死锁情况出现时,这个死锁过程最多持续50s,然后其中就会有一个事务主动退出竞争,释放持有的锁资源。每当一个事务需要阻塞等待某个锁时,就会触发一次wait-for graph算法,该算法会以当前事务作为起点,然后从「锁的信息链表」中找到对应中锁信息,再去根据锁的持有者(事务),在「事务等待链表」中进行查找,看看持有锁的事务是否在等待获取其他锁,如果是,则再去看看另一个持有锁的事务,是否在等待其他锁,经过一系列的判断后,再看看是否会出现闭环,出现的话则介入破坏。在业务允许的情况下,尽量缩短一个事务持有锁的时间、减小锁的粒度以及锁的数量。
RU/读未提交级别:要求该隔离级别下解决脏写问题。写操作加排他锁,读操作不加锁。
RC/读已提交级别:要求该隔离级别下解决脏读问题。写操作加排他锁,读操作加共享锁。
RR/可重复读级别:要求该隔离级别下解决不可重复读问题。写操作加排他锁,对读操作依旧采用MVCC机制,但RR级别中,一个事务中只有首次select会生成ReadView快照。
Serializable/序列化级别:要求在该隔离级别下解决幻读问题。读-写和写-读串行执行。
展开赞过评论1 - #青训营笔记创作活动#
1月24日 打卡day45
今日学习
今天的内容关于日志查看的一些基本操作。
cat a.txt
查看文件
cat -n ./log_all.log
查看文件内容行数显示文件内容
cat ./log_all.log|head -n 10
展示文件前十行
cat ./log_all.log|tail -n 10
展示文件后十行
cat ./log_all.log|grep 程序 --color=auto
模糊匹配文件关键字,并高亮关键字
tail在日常开发中也是经常使用的一个命令,它是一个动态追加查看日志文件的一个命令。
有光标是指会一直挂起并输出后续内容进来匹配的内容,可以用ctrl+C退出。
展开赞过评论1 - #青训营笔记创作活动#
1月23日 打卡day44
今日学习
今天的内容关于SQL语句。
面对一个较为复杂或较难实现的业务需求时,就可以按照需求进行逐步拆分,化繁为简后逐步实现。其实对于这个道理很多人都懂,但往往在实际编写SQL时却想着一步到位,这也是我接触很多程序员后发现的问题:经验尚未丰富的开发,面对一个需求时通常都想着从头写到尾。但这样写就很容易卡壳,对于简单的业务需求可以这样做,但面对复杂业务时一定要先拆解需求后再逐步实现。
一个查询条件会依赖于另一条SQL的执行结果来决定,很多人在这种情况下会直接组合起来一起写,但这会导致编写SQL的复杂度再次提升,因此在这种情况下,可以先用指定值作为条件去查询,例如xx = "xxx",后面等整体SQL完成后,再套入SQL。
每条SQL的执行效率也要可控。
查询时尽量不要使用*,会导致分析成本变高,网络开销变大,内存占用变高,维护性变差。
连表查询时尽量不要关联太多表。
多表查询时一定要以小驱大。
不要使用like左模糊和全模糊查询。
查询时尽量不要对字段做空值判断。
不要在条件查询=前对字段做任何运算。
!=、!<>、not in、not like、or...要慎用。
必要情况下可以强制指定索引。
避免频繁创建、销毁临时表。
尽量将大事务拆分为小事务执行。
从业务设计层面减少大量数据返回的情况。
尽量避免深分页的情况出现。
SQL务必要写完整,不要使用缩写法。
基于联合索引查询时请务必确保字段的顺序性。
客户端的一些操作可以批量化完成。
明确仅返回一条数据的语句可以使用limit 1。
客户端访问时,能够在1s内得到响应,用户会觉得系统响应很快,体验非常好。 客户端访问时,1~3秒内得到响应,处于可以接受的阶段,其体验感还算不错。 客户端访问时,需要等待3~5秒时才可响应,这是用户就感觉比较慢了,体验有点糟糕。 客户端访问时,一旦响应超过5秒,用户体验感特别糟糕,通常会选择离开或刷新重试。
改善SQL的写法,理解一些SQL导致索引失效的场景,以及撰写SQL时的一些技巧,就能写出一手优质SQL。
展开赞过评论1 - #青训营笔记创作活动#
1月22日 打卡day43
今日学习
今天的内容关于Hertz提PR。
先找到一个感兴趣的开源库,查看Issues,主动申请,Fork 目标仓库,clone到本地,新创建一个分支进行开发(git checkout -b [BRANCH NAME]),开发并推送远端分支(git status)(git add [YOUR FILE])(git commit -m [YOUR COMMIT MESSAGE])(git push origin [YOUR BRANCH])。结合github action进行单测流水线化,目录中会有.github/workflows/xxx.yml等文件,通过这些文件可以在提出pr、push等阶段进行校验。填写关于PR的信息。添加原始仓库作为upstream仓库(git remote add upstream [HTTPS]),获取原始仓库的变更(git fetch upstream),合并更改(git merge upstream/main),解决冲突并push到自己的开发分支,解决维护人的建议,等待原仓库的人进行approve以及merge。
展开赞过评论1 - #青训营笔记创作活动#
1月21日 打卡day42
今日学习
新年快乐!!!
今天的内容关于Mysql表。
数据的插入,查询时长较长。
后续业务需求的扩展 在表中新增字段 影响较大。
表中的数据并不是所有的都为有效数据 需求只查询时间区间内的。
InnoDB存储引擎最小储存单元是页,一页大小就是16k。
B+树叶子存的是数据,内部节点存的是键值+指针。索引组织表通过非叶子节点的二分查找法以及指针确定数据在哪个页中,进而再去数据页中找到需要的数据。
分区是指将一个表的数据按照条件分布到不同的文件上面,未分区前都是存放在一个文件上面的,但是它还是指向的同一张表,只是把数据分散到了不同文件而已。
与单个磁盘或文件系统分区相比,可以存储更多的数据。
对于那些已经失去保存意义的数据,通常可以通过删除与那些数据有关的分区,很容易地删除那些数据。相反地,在某些情况下,添加新数据的过程又可以通过为那些新数据专门增加一个新的分区,来很方便地实现。
一些查询可以得到极大的优化,这主要是借助于满足一个给定WHERE语句的数据可以只保存在一个或多个分区内,这样在查找时就不用查找其他剩余的分区。因为分区可以在创建了分区表后进行修改,所以在第一次配置分区方案时还不曾这么做时,可以重新组织数据,来提高那些常用查询的效率。
数据表分区:1.数据量较大 2.查询场景只在某个区 3.没有联合查询的场景分区分表是在物理上对数据表所对应的文件进行拆分,对应的表名是不变的,所以不会影响到之前业务逻辑的sql分表后的查询等业务会创建对应的对象,也会造成一定的开销分区数据若要聚合的话 耗费时间也较长;使用范围不适合数据量千万级以上的。
数据表分表:数据量较大,无法区分明显冷热区 且数据可以完整按照区间划分适用于对冷热分区的界限不是很明显的数据,对后续类似的数据可以采用该方式,将大表拆分成小表 提高查询插入等效率若大数据表逐渐增多 那么对应的数据库表越来越多 每个表都需要分表;区间的划分较为固定 若后续单表的数据量大起来 也会对性能造成影响;实现复杂度相对方案三比较复杂 需要测试整个实现过程 在编码层处理 对原有业务有影响。
冷热归档分库:数据量较大;数据冷热分区明显;冷数据使用频率极低;数据迁移的过程对业务的影响较小 开发量也较少减少成本需要确认分表规则。
展开赞过评论1 - #青训营笔记创作活动#
1月20日 打卡day41
今日学习
今天的内容关于Debug。
如果说Debug的位置是网关入口,那么流量会很大,各种类型的请求都会走到这个断点里面,如果不能按照条件进入断点,会非常影响我们的效率。
如果说Debug某个正在运行的系统,默认情况下会挂起所有的后续请求,很多人都以为系统死机了... 其实最后发现是你在调试。
有什么办法,可以在调试的时候不阻塞剩余的请求吗? 1、默认只断点一次; 2、断点的时候不挂起整个系统;
如下,通过断点管理器器,进入进来,或者右键断点,然后点击more可以进入进来 设置挂起选项,只挂起当前正在调试的线程,然后再下面勾选一旦命中移除断点。
系统常用的链路主要就几条,而常调试问题的地方也只有几处,可以把这几处位置的断点管理起来,在遇到问题的时候直接把断点分组管理拿出来就可以了。
在一些复杂的链路中,方法调用很长,手一抖结果代码又运行了几行,这个时候想去重新开始执行这个断点怎么办?
常规操作是再模拟发出个请求,重新进入断点; 但其实Idea已经提供了对应的方式,直接撤回当前的帧即可,断点会重新进入方法开始执行。
Java8之后的labmda表达式里面一般流程会多一点,也不是很好调试,Idea也有对应的工具,可以直接查看Strem流中的数据,在Debug Window下发,如果识别到labmda表达式后会展示出来。
debug代码是一个常用而且很常见的技能,但是不是每个人都能很有效率的debug代码...
有一些idea隐藏的debug方式,虽然一些人不关注,但是有用并且能极大提升效率
文章主要介绍一些一些在实际项目中相对有用的可以提升debug能力的一些经验。最后如果说实在是有问题,但是又没办法进入调试模式,可以考虑arthas的trace和watch。
展开赞过评论1 - #青训营笔记创作活动#
1月19日 打卡day40
今日学习
今天的内容关于Redis。
固定窗口计数是指,假设我们的限流规则是:1min内最多只能访问10次,那么固定窗口就是固定了【 1min-2min】这个窗口内,只能有10次访问 ,相应的我们就要给这个窗口维护一个计数器。 为了节省空间,其实我们不需要维护一个个窗口,只需要维护当前访问时间所在的窗口即可,以及对应的计数器,当新的访问到达了下一个窗口时,则计数器重置即可。
假设我们的限流规则是:1min内最多只能访问10次,那么滑动窗口呢就是会根据你访问进来的时间,以访问时间作为区间末尾,当前时间-1min作为区间头部,相当于窗口一直在往右滑动,这样其实就能在一定程度上解决我们刚才提到的窗口临界问题。
其实窗口临界问题,就是在即将被移出窗口的这段区间内,可能一次性访问量达到了我们的阈值,而由于要移出窗口了,计数又将重置了,所以这些访问量就相当于不会被后续统计到,那么后续再次超过阈值,就变成双倍阈值了。
延时队列其实有很多种实现的方式,比如RocketMQ本身支持发送延迟消息,但RocketMQ支持的延迟等级有限,自定义程度不高,比如我抢到了一个场地之后,要设置场地时间结束后,修改订单状态为【已结束】,这时RocketMQ就没法精准的设置一个定时任务的时间。于是可以用Redis中的ZSet数据结构,以任务的时间作为作为score进行排序,按时间先后进行排序,后台再开启定时任务,定时利用 zrangebysocre 查询符合条件的所有待处理的任务即可。
先删除token,若后续执行业务期间失败了,则直接第二次点击按钮,调用支付接口时,由于没有刷新页面,前端还存储了刚才那份token,由于后台没有这个token,就会一直失败所以此时需要:客户端主动刷新页面,删除掉前端这个token,重新完成提交这个过程,重新调用获取token接口,再走一遍流程先执行业务,再删除token,但此时若没有加锁的话,其他线程调用接口时,由于A线程还没执行完业务,redis里边的token还未删掉,那么B线程调用支付接口时,也会查到还有token,也能够去执行业务,这样就破坏我们的幂等性了。所以如果采用这种策略的话,需要加锁,保证A线程执行完业务,删掉token之后,其他线程才能调用这个支付接口。
展开赞过评论1 - #青训营笔记创作活动#
1月18日 打卡day39
今日学习
今天的内容关于空指针异常。
当对对象的属性进行检查,判断它的值是否为期望的格式,最终却发现查看的并不是一个对象,而是一个空指针,它会立即抛出一个让人厌烦的 NullPointerException 异常。本质上,Optional 类是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
可以使用 of() 和 ofNullable() 方法创建包含值的 Optioanal 实例,区别在于如果将 null 当作参数传进去 of() 会报空指针异常,所以当对象可能存在或者不存在,应该使用 ofNullable()。
map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链式调用的操作成为可能 —— 这里的下一环就是 orElse() 。
flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果
除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。
filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。
同类型的 API 是orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果
orElse() 和 orElseGet() 的不同之处在于当 ofNullable() 传入参数不为空时,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User 对象。
在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。
Optional 是 Java 语言的有益补充 —— 它旨在减少代码中的 NullPointerExceptions。
展开赞过评论1