情绪管理
不要情绪化
有问题就陈述具体事项和带来的影响,而不是描述自己的感受和可能做出的应对策略
示例一
错误示例:你这么搞我没法干了(描述应对策略)
正确示例:你这么搞导致我做这件事情的成本太高了(陈述问题和带来的影响)
示例二
错误示例:各种流程太多了,让人觉得很烦(描述自身感受)
正确示例:各种流程太多了,导致做事情效率太低(陈述问题和带来的影响)
有问题就陈述具体事项和带来的影响,而不是描述自己的感受和可能做出的应对策略
错误示例:你这么搞我没法干了(描述应对策略)
正确示例:你这么搞导致我做这件事情的成本太高了(陈述问题和带来的影响)
错误示例:各种流程太多了,让人觉得很烦(描述自身感受)
正确示例:各种流程太多了,导致做事情效率太低(陈述问题和带来的影响)
在原来镜像的 Dockerfile 中添加如下定义
RUN pecl install xdebug-3.0.2
RUN docker-php-ext-enable xdebug
RUN { \
echo 'zend_extension=xdebug.so'; \
echo 'xdebug.mode=profile'; \
echo 'xdebug.start_with_request=trigger'; \
echo 'xdebug.output_dir=/tmp/xdebug'; \
} > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini;
重新打包镜像就能得到一个包含了 xdebug 扩展的 php 镜像了,如果不确定是否添加成功,可以在容器运行起来之后,执行docker exec -i container_name php -m | grep xdebug
检查 xdebug 扩展是否安装成功
这里我们采用的是 xdebug3,配置与 xdebug2 不太一样,具体区别可以看这里,网上能搜到的大部分都是 xdebug2 的配置。
为了不影响其他请求的性能,也为了减少日志数量,我们采用 start_with_request=trigger,手动触发 xdebug profile
xdebug.mode=profile
表示使用 xdebug 的 profile 模式xdebug.start_with_request=trigger
表示默认不开启 xdebug profile,需要通过 http 请求中,添加 >XDEBUG_PROFILE=1
参数来开启,GET/POST 均可xdebug.output_dir
指定 xdebug 日志的存放位置,如果指定的文件夹不存在,是无法生成日志的
由于我们是在 docker 中使用 xdebug,xdebug.output_dir 对应的目录最好从宿主机挂载进去,方便查看
构造一个请求,例如:http://127.0.0.1:8080/xdebug?XDEBUG_PROFILE=1。
之后可以在上述配置的文件夹中找到文件名类似cachegrind.out.20
的文件,就是我们要的 profile 文件了。
Mac 用户可以下载 qcachegrind 来查看 profile 文件 brew install qcachegrind
下载完之后打开文件就能看到图形化的分析结果了。
生产环境有一个场景是,a.com 会加载 b.com 下的一个静态资源 b.com/b.js,并且需要验证登陆才能拿到。通过如下代码获取
addScript('b.com/b.js')
function addScript(src, callback) {
var script = document.createElement('script')
script.src = src
script.onload = script.onerror = function() {
if (script.parentNode) {
script.parentNode.removeChild(script)
}
if (callback) {
callback()
callback = null
}
}
document.head.appendChild(script)
}
也就是我们常用的 jsonp 的形式
我们通常会手动创建 script 标签并设置 src 属性,来实现 jsonp 跨域请求
之前一直没问题,突然有一天有同事发现这个资源加载不到了,并且只是个别人会有这个问题。
查看之后发现,在出问题的浏览器中addScript('b.com/b.js')
发出请求里没带 cookie,所以获取静态资源的请求直接 302 到了登陆页。经过一番查找,发现问题出在 cookie 的 SameSite 属性上。
在没有这个问题的浏览器中,通过手动设置 Chrome 的 chrome://flags/#same-site-by-default-cookies
为 Enabled 也能重现这个问题,至此问题确认。
下面对 SameSite 做简单介绍。
Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite
属性,用来防止 CSRF 攻击和用户追踪。
Cookie 的SameSite
属性用来限制第三方 Cookie,从而减少安全风险。
它可以设置三个值。
Strict
最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
Lax
规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
请求类型 | 示例 | 正常情况 | Lax |
---|---|---|---|
链接 | <a href="..."></a> | 发送 Cookie | 发送 Cookie |
预加载 | <link rel="prerender" href="..."/> | 发送 Cookie | 发送 Cookie |
GET 表单 | <form method="GET" action="..."> | 发送 Cookie | 发送 Cookie |
POST 表单 | <form method="POST" action="..."> | 发送 Cookie | 不发送 |
iframe | <iframe src="..."></iframe> | 发送 Cookie | 不发送 |
AJAX | $.get("...") | 发送 Cookie | 不发送 |
Image | <img src="..."> | 发送 Cookie | 不发送 |
设置了Strict
或Lax
以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。
Chrome 计划将Lax
变为默认设置。这时,网站可以选择显式关闭SameSite
属性,将其设为None
。不过,前提是必须同时设置Secure
属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
下面的设置无效。
Set-Cookie: widget_session=abc123; SameSite=None
下面的设置有效。
Set-Cookie: widget_session=abc123; SameSite=None; Secure
解决方案就是显式设置 cookie 的 SameSite 属性为 None。一定是显式设置,response 中的 Set-Cookie 头里一定要包含 SameSite: None 字样。
备注:文中提到 jsonp 以及 scrip 标签的 src 属性,主要是为了能让搜这几个关键字的人有可能搜索到这篇文章
关键词:groovyshell evaluate 内存泄露
grovvy 版本 2.4.10
代码在线上运行一段时间后,容器会自动重启,查看容器监控发现是内存溢出导致了自动重启。
查看 jvm 堆情况。堆内存的使用实际不大,但是 docker 实例的内存却又占满了,又因为 docker 实例并没有安装 agent 进程,说明很大可能是 JVM 堆外内存导致。
使用 jstat -gc pid 查看,发现 Metaspace 占用很大
回想该应用出现状况前的改动,主要是新增了 groovy 脚本执行任务。查看代码发现每次执行都会创建对象
public Object evaluate(String name, String script, Binding binding) {
binding.setVariable("logger", LoggerFactory.getLogger("script-" + name));
binding.setVariable("builder", new Builder());
binding.setVariable("selector", this.selector);
GroovyShell shell = new GroovyShell(binding);
return shell.evaluate(script);
}
看监控类加载总量很大,猜测是因为生成很多临时 class 对象,无法 gc
new GroovyShell() 未指定 patentClassloader
所以 GroovyShell 初始化的时候会自动 new 一个 classLoader 并设置 GroovyShell.class.getClassLoader() 为父 classloader
所以最终加载脚本文件的时候是交给 GroovyShell.class.getClassLoader() 处理的
执行 evaluate 方法,只传入了脚本字符串,直接 evaluate 字符串不会缓存脚本,所以每次执行都会重新加载一次,生成一个全新的 class 对象
但是 JVM 中的 Class 只有满足以下三个条件,才能被回收(也就是该 Class 被卸载 unload)
由于实际负责加载类的 classloader 不会回收,导致 class 对象一直存在于 Metaspace 并且随着时间推移越来越多,并且启动时没有设置 Metaspace 最大值,导致 Metaspace 空间无限增长,最终容器自身 oom,自动重启
-XX:MetaspaceSize 表示触发 full gc 的容量。对于64位JVM来说,MetaspaceSize 默认为 20.75MB,-XX:MaxMetaspaceSize 默认值是无限大
MySQL5.0之前,单表一次查询只能使用一个索引,无法同时使用多个索引分别进行条件扫描。但是从5.1开始,引入了 index merge 优化技术,对同一个表可以使用多个索引分别进行条件扫描。相关文档:http://dev.mysql.com/doc/refman/5.6/en/index-merge-optimization.html
index(a) index(b)
select * from table where a=1 and b=1
可能会用到索引 a 和 b(index_merge),也可能只用到 a 或者 b
mysql 会选择它觉得快的索引策略,但是 index_merge 不一定能比单索引查询快,这个要根据表里的数据具体分析。
如果没有单独按照 b 查询的场景,这里可以设置为联合索引 index(a, b),查询条件为 a=1 and b=2
时会比上述索引快一些
默认是:可重复读(Repeated Read)
在事务隔离级别为「可重复读」的情况下,会出现「幻读」,这是网上以及各种面试经常看到的问题。
但是「幻读」如何定义,哪些现象被称为「幻读」,却很少有明确的说法。
网上能看到最多的关于幻读的描述是:一个事务相同的 sql 语句读取 2 次,得到的记录条数不一致(由于其他事务在第二次读取之前插入了符合查询条件的数据)。但是 mysql 中隔离级别设置为「可重复读」时,并不存在这个现象,实验环境为10.1.22-MariaDB
,大家可以自己实验下。
关于「幻读」的定义还有另一个说法,我们模拟一个业务中比较常见的场景来说明。例如:
给用户给帖子点赞,用 uid 表示用户 id,tid 表示帖子 id,uid + tid 设置为唯一索引。
假如说这时候用户 1 要为帖子 2 点赞,代码做的事情就是:查询用户 1 对帖子 2 有没有点赞记录,如果有就什么都不做,没有则新增一条记录。假如这个操作并发了,A 事务查询点赞记录时没有查到,同时 B 事务查询点赞记录也没有查到,之后 A 事务插入了点赞数据,并且提交事务,然后 B 事务再插入这条数据的时候就会报唯一键冲突,引发异常。这个场景下 B 事务遇到的情况就属于「幻读」
因为前一个说法在 mysql 默认的隔离级别下不存在,据说是“由于 InnoDB 引擎的「可重复读」级别还使用了 MVCC,避免了这个问题”,所以我个人理解后一种才算是「幻读」
关于 mvcc 这里涉及数据库的「并发控制」,可以参考浅谈数据库并发控制 - 锁和 MVCC
这里我还想喷一下部分公司的面试官,上来就问如果出现了「幻读」怎么解决,而不提具体场景,事实上可能大家理解的「幻读」就不是一回事,并且「幻读」是一个抽象概念,不如直接说场景,不知道幻读的概念,不见得不能解决问题
网上关于幻读的解释
- 第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。来源
表示除非显式地开始一个事务,否则每个查询都被当做一个单独的事务自动执行
InnoDB 通过索引来实现行锁,而不是通过锁住记录。因此,当操作的两条不同记录拥有相同的索引时,也可能会因为行锁而发生等待。
参考文献: