获得徽章 14
- #青训营笔记创作活动#
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 - #青训营笔记创作活动#
1月17日 打卡day38
今日学习
今天的内容关于高并发系统。
系统构成:页面静态化,CDN加速,负载均衡,防重,缓存,异步,熔断、限流,分布式锁。
秒杀的商品信息页面其实固定的,包含了商品的基本信息,sku、spu等,比如商品名、商品图片、属性等,为了减少不必要的服务端请求,通常情况下,重新设计秒杀商品页面,不使用网站原来的商品详情页面,对活动页面做静态化处理。用户浏览商品等常规操作,并不会请求到服务端。只有到了秒杀时间点,并且用户主动点了秒杀按钮才允许访问服务端。静态化就是指把原本动态生成的html页面变为静态内容保存,用户客户端请求的时候,直接返回静态页面,不用再经过服务渲染,不用访问数据库,可以大大的减小数据库的压力,显著的提高性能。
高并发的系统架构都会采用分布式集群部署,服务上层有着层层负载均衡,并提供各种容灾手段(双活机房、节点容错、服务器灾备等)保证系统的高可用,流量也会根据不同的负载能力和配置策略均衡到不同的服务器上。
OSPF(开放式最短链路优先)是一个内部网关协议(Interior Gateway Protocol,简称IGP)。OSPF通过路由器之间通告网络接口的状态来建立链路状态数据库,生成最短路径树,OSPF会自动计算路由接口上的Cost值,但也可以通过手工指定该接口的Cost值,手工指定的优先于自动计算的值。OSPF计算的Cost,同样是和接口带宽成反比,带宽越高,Cost值越小。到达目标相同Cost值的路径,可以执行负载均衡,最多6条链路同时执行负载均衡。
Nginx是一款非常高性能的http代理/反向代理服务器,服务开发中也经常使用它来做负载均衡。Nginx实现负载均衡的方式主要有三种:轮询、加权轮询、ip hash轮询。
ip_hash 策略能够将某个客户端IP的请求固定到同一台服务器上,例如A用户访问服务器,通过固定算法后,被固定到 192.168.136.136 的web服务器上,那么,用户A下次访问时,依旧会到访问 192.168.136.136 服务器。因此,该策略解决了多台服务器Session不共享的问题。ip_hash 策略类似于url_hash ,一个采用Ip地址进行计算,一个采用URL地址进行计算。
缓存击穿最好使用分布式锁和布隆过滤器解决。
展开评论1 - #青训营笔记创作活动#
1月16日 打卡day37
今日学习
今天的内容关于缓存安全。
大部分服务端使用的抗压型缓存,为了保证缓存执行速度,普遍都是将数据存储在内存中。而受限于硬件与成本约束,内存的容量不太可能像磁盘一样近乎无限的去随意扩容使用。对于实际数据量及其庞大且无法将其全部存储于缓存中的时候,我们需要保证存储在缓存中的有限部分数据要尽可能的命中更多的请求,即要求缓存中存储的都是热点数据。
缓存实现的时候,必须要有一种机制,能够保证内存中的数据不会无限制增加 —— 也即数据淘汰机制。数据淘汰机制,是一个成熟的缓存体系所必备的基础能力。这里有个概念需要厘清,即数据淘汰策略与数据过期是两个不同的概念。
先执行数据库的数据更新操作。
更新成功,再去执行缓存记录删除操作。
缓存如果删除失败,则按照预定的重试策略(比如对于指定错误码进行重试,最多重试3次,每次重试间隔100ms等)进行重试。
如果缓存删除失败,且重试依旧失败,则将此删除事件放入到MQ中。
独立的补偿逻辑,会去消费MQ中的消息事件请求,然后按照补偿策略继续尝试删除。
每个缓存记录设定过期事件,极端情况下,重试删除、补偿删除等策略全部失败时,等到数据记录过期自动从缓存中淘汰,作为兜底策略。
大量无效的ID请求到系统内,因为无法命中缓存而被转到MySQL中查询,而MySQL中其实也无法查询到对应的数据(因为这些ID是恶意生成的、压根不存在)。大量此类请求频繁的传入,就会导致请求一直依赖MySQL进行处理,极易冲垮下游模块。这个便是经典的缓存穿透问题(缓存穿透与缓存击穿非常相似,区别点在于缓存穿透的实际请求数据在数据库中也没有,而缓存击穿是仅仅在缓存中没命中,但是在数据库中其实是存在对应数据的)。
缓存穿透的情况往往出现在一些外部干扰或者攻击情景中,比如外部爬虫、比如黑客攻击等等。为了解决缓存穿透的问题,可以考虑基于一些类似白名单的机制(比如基于布隆过滤器的策略,后面系列文章中会详细探讨),当然,有条件的情况下,也可以构建一些反爬策略,比如添加请求签名校验机制、比如添加IP访问限制策略等等。
展开评论1