[片段] 方法参数收集
以前的代码,用于收集当前方法的所有参数,放在map中方便调取
1 | import com.google.common.collect.ImmutableMap; |
附送一段代码,用于将方法中收集的参数转换成Bean
1 | import org.springframework.beans.BeanUtils; |
使用实例:
1 |
|
以前的代码,用于收集当前方法的所有参数,放在map中方便调取
1 | import com.google.common.collect.ImmutableMap; |
附送一段代码,用于将方法中收集的参数转换成Bean
1 | import org.springframework.beans.BeanUtils; |
使用实例:
1 |
|
更小的通常更好,但是要确保没有低估需要存储的值的范围,因为在schema中的多个地方增加数据类型范围是一个十分耗时的操作。
简单的数据类型的操作通常需要更少的cpu时间。
可为NULL的列使得索引、索引比统计和值比较都更为复杂。
当然也有例外,InnoDB使用单独的位存储NULL值, 所以对于稀疏数据有很好的空间效率。
一旦选定一种类型,要确保在所有的关联表中都使用同样的类型。类型之间需要精确匹配,包括像UNSIGNED这样的属性。尽量只用整型定义标识符。
其在临时表和排序时可能导致悲观的按最大长度分配内存
范式是好的,但是反范式有时也是必须的,并且能带来好处。
1 | where last_name = 'a' and first_name like 'J%' and dob = '1877-12-23' |
如果查询中列不是独立的,则mysql不会使用索引
1 | select actor_id from sakila.actor where actor_id + 1 = 5; |
有时候需要索引很长的字符列,通常可以索引开始部分的字符,同时也会降低索引的选择性。
索引的选择性指的是,不重复的索引值和数据表的记录总数的比值。索引的选择性越高表示查询效率越高,因为选择性高的索引可以过滤掉更多的行。
前缀索引是一种能使索引更小,更快的有效方法,但是也有其缺点:前缀索引无法做order by 和 group by,也无法使用前缀索引做覆盖索引。
最容易遇到的困惑是多列索引的顺序,正确的顺序依赖于使用索引的查询,同时需要考虑如何更好的满足排序和分组需要。对于如何选择索引的列顺序有一个经验法则:将选择性最高的列放在索引的最前列。只有不需要考虑排序和分组时,将选择性跟高的列放在最前面通常是最好的,但是考虑问题需要更全面,避免随机I/O和排序更加重要。
如果一个索引包含所需要查询的字段的值,我们就可以称之为“覆盖索引”
覆盖索引的好处:
覆盖索引的陷阱:
1
select * from products where actor = 'SEAN CARREY' and title like '%APOLLO%';
mysql有两种方式可以生成有序的结果:通过排序操作;或者使用索引顺序扫描。mysql可以使用同一个索引既满足排序,有用于查找行。
只有当索引的列顺序和ORDER BY子句的顺序完全一致,并且所有列的排序方向都一样时,mysql才能使用索引来对结果做排序。如果查询需要关联多张表,则只有当ORDER BY子句引用的字段全部为第一个表时,才能用索引做排序。ORDER BY子句和查询的限制是一样的:需要满足索引的最左前缀的要求。
有一种情况下ORDER BY子句可以不满足索引的最左前缀要求:
1 | select rental_id, staff_id from sakila.rental where rental_date = '2005-05-25' |
索引为key(rental_date, inventory_id, customer_id),前导列为常量的时候,如果where子句或者join子句中对这些列指定了常量,就可以弥补ORDER BY的不足。
1 | where rental_date > '2005-12-25' order by inventory_id, customer_id; |
对于索引上是范围查询,mysql无法使用之后的索引列
索引可以让查询锁定更少的行,如果你的查询从不访问那些不需要的行,那么就会锁定更少的行。但这只有当innoDB在存储引擎层能够过滤掉所有不需要的行是才有效。如果索引无法过滤掉无效的行,那么innoDB检索到数据并返回给服务器层后,innoDB已经锁定这些行了(mysql 5.6后没有这个问题)。
下面的查询:
1 | where last_online > date_sub(now(), interval 7 day) and age bwtween 18 and 25 |
这个查询有一个问题:它有两个范围条件,last_online和age列,mysql可以使用last_online的索引或者是age列的索引,但是无法同时使用它们。
如果一个查询匹配结果有上百万行的话会怎样?
1 | select * from profiles where sex = 'm' order by rating limit 10; |
即使有索引,如果用户界面需要翻页,并且翻页到比较靠后的地方也会非常慢,如:
1 | select * from profiles where sex = 'm' order by rating limit 1000000, 10; |
无论如何创建索引,这种查询都是个严重的问题,mysql需要花费大量时间来扫描需要丢弃的数据。其中一个解决的办法是限制能够翻页的数量。
优化这类索引的另一个比较好的策略是使用延迟关联,通过使用覆盖索引返回需要的主建,再根据这些主建回主表获得需要的行,这样可以减少mysql扫描需要丢弃的行数。
1 | select * from profiles innner join |
查询性能低下最基本的原因是访问数据太多。某些查询可能不可避免的需要筛选大量数据,单这并不常见。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效的查询,我们发现通过下面几个步骤来分析总是很有效:
第一种情况可以使用limit和选择需要的列来解决。在确定查询只返回需要的数据之后,接下来应该看看查询为了返回结果是否扫描了过多的数据,对于mysql有三个衡量查询开销的指标:
没有那个指标能完美地衡量查询的开销,但它们大致反映了mysql内部查询时需要访问多少数据,并可以大概推算出查询运行的时间。
在评估查询开销的时候,需要考虑一下从表中找到某一行数据的成本。mysql有好几种访问方式可以查找并返回一行结果。有些访问方式可能需要扫描很多行才能返回一行结果,也有些访问方式可能无需扫描就能返回结果。
在explain语句中的type列反应了访问类型。访问类型有很多种,从全表扫描到索引扫描、范围扫描、唯一索引查询、常数引用等。这里列的这些,速度是从慢到快,扫描行数也是从大到小。如果查询没有办法找到合适是访问类型,那么解决的最好办法通常是增加一个合适的索引。
一般mysql能够使用如下三种方式应用where条件,从好到坏依次为:
如果发现查询需要扫描大量的数据但只返回少数的行,那么可以使用下面的技巧去优化它:
有时候对于一个大查询我们需要分而治之,将大查询分成小查询。删除旧数据是一个很好的例子,如果用一个大的语句一次性完成,则可能一次需要锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞恨到重要的查询。
例如下面这个查询:
1 | select * from tag |
可以分解成下面这个查询来代替:
1 | select * from tag where tag = 'mysql'; |
用分解关联查询的方式重构查询有如下的优势:
因为字符比较是从右往左比较的,所以第一层循环 needle.length + 1
<= i < haystack.length
。
1 | start=needle.length - 1 end=haystack.length - 1 |
第二层循环中i变量表示坏字符的位置、j表示搜索坏字符开始位置
1 | i |
i,j指针向左搜索,如果完全匹配直接返回i即可
1 | for (j = needle.length - 1; needle[j] == haystack[i]; --i, --j) { |
坏字符:从右向左第一个不匹配的字符
好字符:从坏字符下一个字符直到最后的字符
可以认为字符移动有两种策略:坏字符对齐和好字符对齐,然后选择字符移动距离大的策略即可
1 | /** |
用来批量加密用@Decrypted注解的String字段,可能还有一些坑。
1 |
|
这回是标题党, 记录下两个分而治之的排序算法(手写),分而治之的算法很容易改造成并行算法,肯定是未来的潮流, leetcode已通过, 两个算法都使用了原地(inplace)更新。
1 | class Solution { |
Manacher 算法 容易理解,实现起来也没什么大坑,复杂度还是 O(n)的, 花半个小时实现下很有意思
1 | class Solution { |
最简单的解决方法,实现简单。但是在微服务中调用接口次数太多,性能很差。
实现较复杂,但是性能好很多,下面主要介绍这种方法的思路
以分页读取数据为例:
为什么需要所属权限这个字段呢? 因为决定能否看到这行是有你所拥有的所有权限决定的,而决定能否看到哪个列是由这行所拥有的权限决定的。
如何获取该行所拥有的权限呢,我的做法是分不同的权限查询结果通过union 组合起来
记录原始顺序是因为后面分组后会打乱, 为什么要分组?分组后同样的查询才能聚合在一起,可以简化代码
这里我使用的graphql来选择需要查询的字段
可以使用guava Ordering工具类方便生成Compartor
[]: https://blog.yamato.moe/2018/11/06/2018-11-06-biz/ “根据权限查询时避免角色切换的一种思路”
[]: https://blog.yamato.moe/2019/04/04/Mybatis%20ResultSetHandler_2019-04-04%20%E7%BB%AD/ “【片段】 Mybatis ResultSetHandler 实践-续”
[]: https://blog.yamato.moe/2019/01/09/Mybatis%20ResultSetHandler_2019-01-09/ “【片段】 Mybatis ResultSetHandler 实践”
104. Maximum Depth of Binary Tree (Easy)
递归计算二叉树左右两边深度,取最大值。
1 |
|
110. Balanced Binary Tree (Easy)
递归遍历二叉树左右子树深度
1 | /** |
543. Diameter of Binary Tree (Easy)
递归遍历二叉树左右子树深度, 路径就是两边子树深度之和
1 | /** |
226. Invert Binary Tree (Easy)
递归交换左右子树的引用
1 | /** |
617. Merge Two Binary Trees (Easy)
递归时如果其中一个节点是空,可以直接复用该节点。如果新建节点,需要拷贝节点的左右子树引用,递归时会用到。
1 | /** |
Leetcode : 112. Path Sum (Easy)
递归查询子树和是否等于目标和
1 | /** |
双层递归
以root为根节点的路径数量= 以root为起点统计路径和+root左节点为起点统计路径和+root右节点为起点统计路径和
1 | /** |
572. Subtree of Another Tree (Easy)
1 | /** |
1 | /** |
111. Minimum Depth of Binary Tree (Easy)
和最大路径类似
1 | /** |
404. Sum of Left Leaves (Easy)
1 | /** |
337. House Robber III (Medium)
递归查询两种情况
1 | /** |
Second Minimum Node In a Binary Tree (Easy)
第二小节点在子树节点上,如果子树值与根节点相等,继续向下查找
1 | /** |
637. Average of Levels in Binary Tree (Easy)
BFS
1 | /** |
513. Find Bottom Left Tree Value (Easy)
DFS
1 | /** |
入栈条件: 未访问过该节点
出栈条件: 访问过该节点
1 | /** |
入栈条件: 无
出栈条件: 直接出栈
1 | /** |
入栈条件: 未访问过该节点
出栈条件: 访问过该节点
入栈顺序: right -> middle -> left
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
二叉树中序遍历
1 | /** |
链表转数组
1 | /** |
还可以使用双指针找到链表中间节点,缺点是重复遍历节点
1 | /** |
自己写两次遍历搜索二叉树,注意要排除自身节点
1 | /** |
正经思路, 中序遍历转化为排序数组, 使用双指针查找
1 | /** |
使用多个角色查询列表时,会遇到两个维度的不同点:
举一个例子:
假设存在一个登录员工拥有两个角色:
那么,在列表中他能看见:
基本信息 | 薪酬信息 | 长期激励信息 | |
---|---|---|---|
低职级/无长期激励 | √ | √ | x |
低职级/长期激励 | √ | √ | √ |
高职级/无长期激励 | x | x | x |
高职级/长期激励 | √ | x | √ |
基本思路已经在前期概要里介绍,本人已经实践了一段时间,挖了两个深坑正在解决中。
最开始的实现中数据是一条一条读取的,同时薪酬字段属于加密信息,使用了第三方微服务提供解密,读取字段多+解密字段多 导致了在百条分页的情况下接口在超时的边缘不断试探。。。
解决方案:
因为之前为了方便我们解密使用了mybatis的TypeHandler做到字段隐式加解密,目前我们的做法是对于单条数据的加解密,还是保持原来的typeHandler做法,而对批量数据处理,重新写一套数据实体,同时使用mybatis的拦截器对查询的批量数据做批量解密的处理。具体做法可以参见我的另一片文章:【片段】 Mybatis ResultSetHandler 实践-续
批量查询返回的列表中列字段都是一致的,而我们的需求是不同的行能看见不同的列字段,把批量查询出来的列表直接返回是有问题的,这个问题因为疏忽导致了线上的一次故障。
所以目前的思路是先做一次数据批量预取,之后在对列字段做处理,隐藏掉不能看见的字段。
没有想到当时想解决权限查询时避免角色切换这个问题时会遇到这么多困难,想法是正确的,在实际执行时还是困难重重。值得欣慰的在最开始的时候思路和方向都是正确的,同时也把其中遇到的各种问题和心得记录了下来,经过层层积累,才到达现在的高度。
[]: https://blog.yamato.moe/2018/11/06/2018-11-06-biz/ “根据权限查询时避免角色切换的一种思路”
[]: https://blog.yamato.moe/2019/04/04/Mybatis%20ResultSetHandler_2019-04-04%20%E7%BB%AD/ “【片段】 Mybatis ResultSetHandler 实践-续”
[]: https://blog.yamato.moe/2019/01/09/Mybatis%20ResultSetHandler_2019-01-09/ “【片段】 Mybatis ResultSetHandler 实践”
resilience4j 源码还是比较清晰简单的,比较适合阅读。
放一张主要类的结构图:
Retry接口是提供重试功能的入口,主要提供了方法模版,具体校验结构,失败后处理由Context子类实现。
1 | /** |
这里摘抄了一段核心代码,作用是循环直到context.onResult(result)返回true为止,需要留意context.onResult/onRuntimeError/onError可能执行多次, onSuccess只会执行一次,这里每次进入重试都是一个新的context对象。
1 | public boolean onResult(T result) { |
先关注onResult,它负责判断是否需要继续重试,如果通过校验或者重试超过此数,会停止重试。
onRuntimeError/onError, 负责把catch的异常存储在lastRuntimeException中。
1 | public void onSuccess() { |
onSuccess负责统计和发送事件。
总体来说retry比较简单,需要注意的点有一个如果设置了结果校验,如果一直校验不通过,将返回未通过的结果,而不是返回失败。
这次拦截的方法是handleResultSets(Statement stmt),用来批量解密用@Encrypted注解的String字段。
上次的局限是只能批量解密一个对象的所有加密字段,对批量数据来说稍显不足,这个主要改进了这一点。
1 |
|
1 | package app.pooi.common.encrypt; |
纯记录,供自己参考🤣。
1 | private final MybatisProperties properties; |
支持and查询、多选、多字段排序分页,缺少的功能:or 条件
核心类,有一些测试代码,将就一下。另外需要spring-data-redis 2.0版本以上
1 | package app.pooi.redissearch.search; |
辅助类
1 | import lombok.Data; |
做一个轻量级的搜索还是可以的。
一般来说可以使用getGenericSuperclass 获取子类范型信息,但是泛型有嵌套的话想获取完整信息还是有点复杂的。例如:Message<List
guava中有强大的TypeToken帮助你保存复杂泛型信息,可以参考:
1 | ParameterizedTypeReference<Message<T>> responseTypeRef = |
如果需要在spring框架中使用,需要一个适配器:
1 | public class ParameterizedTypeReferenceBuilder { |
关于java的泛型我就不多做吐槽了。
拦截器实现:
1 | package app.pooi.common.entity; |
配置:
1 |
|
目前是使用了spring aop 来拦截方法调用,把方法参数包装成Map形式
1 |
|
1 | public class BinderUtil { |
这次拦截的方法是handleResultSets(Statement stmt),用来批量解密用@Encrypted注解的String字段,可能还有一些坑。
1 |
|
1 | package app.pooi.common.encrypt; |
职业发展的驱动力应该来自自身,工作属于公司,职业生涯属于自己。
大多数人形成的错误的心态: 认为在为公司打工,没有把自己的职业生涯当作生意来看待。铭记在心,开始积极主动的管理自己的职业生涯吧。
自己能提供什么:自己的能力就是创造软件
自己需要做什么:
持续不断地改进和完善自己的产品
传达自己的价值,和千万同行的独特之处
一头扎进工作不可能非同凡响,你应该:
无论因为何种原因你没有为自己的职业生涯设定目标, 现在都是时候设定目标了。 不是明天, 也不是下周, 就是现在。 没有明确的方向, 你走的每一步都是徒劳的。
先在心中树立一个大目标,然后分解成多个小目标
定期核对自己的目标,必要时还要调整。
在构建大型系统时,常常会遇到各种错误。在计划构建一个系统时,定义系统的“健康状态”十分重要。
“健康状态”必须是可度量的,一般做法是使用SLAs来度量系统的“健康状态”。最常见的SLA为
可达性
从时间维度衡量(99.999%可达性,每年下线50分钟)
准确性
对于数据的丢失或失真是否可以接受?可以达到多少百分比?对于支付系统来说,不接受任何数据的丢失和失真
容量
系统支持并发
延迟
响应延迟,一般衡量95%请求的响应时间和99%请求响应时间
确保新系统比被替代系统“更好”,可以使用上面四个SLA指标来衡量,可达性是最重要的需求。
随着新业务的增长,负载也会增加。最常见的伸缩策略是垂直和水平伸缩。
水平伸缩就是增加更多的机器或节点,对于分布式系统来说水平伸缩是最常有的方式。
垂直伸缩基本上就是买更大/好的机器。
可达性对于任何系统都是很重要的,但是分布式系统一般都构建在低可达性的机器上(比如:服务的可达性要求99.999% 机器的可达性为99.9%)。简单的做法是维护一组机器组成集群,这样服务的可达性不依赖单独的机器。
一致性是在高可用系统中最需要关心的。一个一致性系统在所有的节点上看到和返回的数据在同一时间是相同的。如果使用一组机器来组成集群,它们还需要互相发送消息来保持同步,但是发送消息可能失败,这样一些节点就会因为不一致而不可达。
一致性有多个模型,在分布式系统最常用的是强一致性,弱一致性和最终一致性。一般来说,一致性要求越低,系统可以工作的更快,但是返回的数据不一定是最新的。
系统中的数据需要是一致的,但是到底是怎样的一致?对于一些系统,需要强一致性,比如一次支付必须是强一致的存储下来。对于没那么重要的部分,最终一致性是可以考虑的权衡。比如列出最近的交易。
持久性表示一旦数据成功添加到数据存储,它就永远可以访问到。不同的分布式数据库拥有不同级别的数据持久性。一般使用副本来增加数据持久性。
对于支付系统来说,数据不允许丢失。我们构建的分布式数据存储需要支持集群级别的数据持久型。目前Cassandra, MongoDB, HDFS和Dynamodb 都支持多种级别的数据持久性。
分布式系统中的节点执行计算,存储数据,互相发送消息。发送消息的关键是消息的可靠到达。对于关键系统,经常需要消息零丢失。
对于分布式系统,发送消息一般石油分布式消息服务发送,如RabbitMQ,Kafka。这些消息服务支持不同级别的消息投递可靠性。
消息保持表示当节点处理消息失败时,在错误被解决前消息一直被保持着。消息的持久性一般在消息队列层被使用。如果在消息发送的时候队列或节点下线了,那在它们重新上线是还能接收到消息。
在支付系统中我们需要每一条消息投递一次,在构建系统中保证投递一次和投递至少一次在实现上是有区别的。最后我们使用了kafka来保证投递至少一次。
在分布式系统中,很多东西都可能出错,连接会丢包或超时,客户端经常会重试这些请求。一个幂等的系统保证无论多少特定的请求被执行,一个请求实际的操作只会执行一次。比如支付请求,如果客户端请求支付并且请求已经成功,但是客户端超时了,客户端是能够重试相同的请求的。对于一个幂等的系统,一个个人的支付是不能被收取两次的。
对幂等的设计,分布式系统需要某种分布式锁机制。假设我们想要使用乐观锁来实现幂等性,这时系统需要强一致性的锁来执行操作,我们可以使用同一个版本的乐观锁来检查是否有启动了额外的操作。
根据系统的一致性和操作的类型,有很多方式来实现幂等性。在设计分布式系统时,幂等性时最容易忽略的。在支付系统中,幂等操作时最重要的,它避免了双花和双收问题。消息系统已经保证了消息至少消费一次,我们只需要保证所有重复的消息保证幂等性即可。我们选择使用乐观锁,并使用强一致性存储作为乐观锁的数据源。
分布式系统经常需要存储大量的数据,远超一台节点的容量。一般的解决方案时使用分片,数据使用某种hash算法被水平分区。尽管很多分布式数据库屏蔽了分片的实现,但是分片还是挺有意思的,特别是关于重新分片。
许多分布式系统在多个拥有数据和统计信息。为保证对数据操作的一致性,使用基于投票的方式是不行的,只有超过一定数量的节点操作成功,这个操作才是成功的,这个叫做法定人数。
描述分布式系统最普遍的做法是使用Actor模型,还有一种方法是CSP。
Actor模型基于actor互相发送消息并作出回应。每一个actor只能做少量的动作,创建其他actors, 发送消息或者决定如何处理下个消息。通过这些简单的规则,复杂的分布式系统可以被准确描述,可以在actor崩溃后自我修复。
使用akka提供了标准的分布式模型,避免我们重复造轮子。
当构建大型分布式系统时,目标常常是它们的弹性,伸缩性,和扩展性。反应式架构是在这个领域最流行和最通用的方案。
Update your browser to view this website correctly.&npsb;Update my browser now