虫虫的技术博客 技术 生活

Friday, April 19, 2019

死锁产生条件和如何处理死锁

死锁是在计算机系统中,两个或者两个以上的进程(或者线程),在执行过程中由于某些存在竞争的资源没有合理的管理和释放而导致线程阻塞,无法继续执行下去的状态。

图片来自百度

死锁产生的条件
1,资源互斥,资源同时只能被有限的线程占有
2,阻塞不释放,线程申请资源阻塞后,不释放已经持有的资源
3,不可剥夺,线程持有的资源,是不能被其它线程剥夺的,只能由自己使用完了释放
4,循环等待,存在一个资源等待环,

死锁会导致资源无法释放,系统hang住,这毕竟是我们不想看到的现象
在死锁产生之前,我们需预防和避免,主要思想就是破坏产生的条件,下面针对四个条件分别进行破坏~
1,资源互斥,如果资源是可以被无限共享和使用的,那也就不存在死锁的问题了
2,线程申请资源阻塞后,释放已经持有的资源,线程返回失败
3,线程按优先级排序,优先级高的线程可以剥夺优先级低的线程持有的资源
4,申请资源的时候检测,如果此次申请如果能产生环路,则取消此次申请
其它方案
5,一次性请求所有资源
6,资源编号,按顺序申请使用
7,超时释放重试
实际方案中,应该是没有针对剥夺与资源互斥上面下手的

在死锁产生之后,我们需要处理后续的问题
1,检测死锁,终止死锁等待环中的节点
2,重启进程

Java并发编程实战》中谈到,数据库系统在设计的时候考虑到了死锁的监测以及从死锁中恢复,当检测到死锁时,将选择一个事务作为牺牲者,让其它事务继续执行,应用程序可以重新发起被强行中止的事务

对于java程序,无法从死锁中恢复,所以都会在开发阶段避免死锁,在实际应用中,对于已经发生死锁的情况,通常都需要根据进程信息等,先定位到死锁的位置,然后重启来解决当时的问题,再优化死锁产生的位置,达到根治的目的 

Wednesday, April 10, 2019

分布式事务梳理

分布式事务目前流行的方案也有很多种,网上能找到大把的技术文章,但看人家的文章总是有各种问题,要么某些地方不好理解,要么某些地方认为不正确,还是按自己的视角来梳理一下。
目前看到的方案,2PC,3PC,TCC,事务消息,本地消息表,等等。
一,2PC
多个数据库在同一个公司,同一个网段可访问到,以及数据库支持2PC,先对多个要提交的事务先发起预提交,多个库确认可以commit了之后,再发起确认commit,当然这个方案存在一些问题,前面说的前提是一些场景上的限制,场景适用后,在发起commit阶段仍然会有可能有不一致的可能性存在,后面会有3PC,同样也是解决了部分问题,无法最终解决一致性问题。
二,3PC
3PC是在2PC的基础之上,在commit又分成了2步,进一步减少数据不一致的可能性。
三,TCC
其实TCC和2PC原理基本一样,只是TCC是不同的资源可能在不同公司,采用不同的服务的方式,多个服务需要支持一个完整的事务,这时候2PC就不适用了,TCC相当于把每个需要事务控制的服务提供确认和取消的操作,这样,在发起事务前先预先锁住资源,待所有资源都确认可以获得,再发起确认操作,中间如有某一个资源预先获取失败,则会取消其它的资源的持有。
在一篇文章又看到解释TCC又分几种类型,通用型,补偿型,异步确保型。通用型也就是刚刚说到的这种,补偿型稍微有点不一样,只提供提交和撤回操作,异步确保型则引入MQ。
对于TCC方案,后面的确认环节同样存在一定的问题,会有不一致的可能性存在,这时候就需要一些超时机制,check机制来尽可能的自动确保事务的一致性,这些机制几乎能解决因网络抖动,服务暂时不可用或者down机后快速恢复等情况引发的问题,对于一些更严重的故障,甚至比如程序出了bug,无法恢复的情况,最终也只能引入人工来修复。
四,事务消息
刚才有提到引入MQ,对于发MQ和本地的操作如何能够保证一致性呢,这个就引出了事务消息的概念,普通的MQ无法保证消息发成功和本地的业务的完全一致性,为了这个目标,在MQ系统中引用2PC的方案,先提交一次消息,然后执行业务,完成之后再次提交一次确认消息,对于没有收到确认的消息,定时轮询回查业务方,或者定期删除。
五,事务消息-本地消息表
有文章提到ebay的一个实现方案,本地消息表来保证本地事务,然后再需要一个timer来轮询本地消息表,检查没有投递成功的消息重复调用发送。如果没有RocketMQ这种支持事务的MQ,可以选用此方案,对业务的入侵较大,不易复用。
基于消息的最终一致方案,目的是保证本地操作和消息发送成功作为一个事务保证一致性。如果消费者端的业务执行成功与否会影响到消息生产者端,则还需要一个回调来报告消费端的成功与失败。
六,Paxos
Zookeeper使用的一致性算法,据说真正的解决了一致性问题,稍后再看。
七,
没有名字,但各大厂都提供的支付接口,与调用方本地的事务需要保证的一致性,我认为这也是解决分布式事务的一个很好的方案。
调用方先创建本地订单,然后调用支付接口,拿到结果后更新本地订单。
针对本地订单已经创建,但是没有拿到支付结果的订单,重复调用支付,支付接口根据调用方的orderid唯一来做幂等。
支付服务方还会定时调用回调接口报告支付成功与失败,来保证事务的一致性和完整性,定时调用本地接口。
如果本地重试和支付宝重试均失败,那就需要人工介入。

对于遇到的一些场景进行分类的话,可以有几个维度的划分
需要回滚,比如订中转的机票,适合用TCC的方案
不需要回滚,比如单纯的支付场景,比如随主业务一起发通知的场景,只需要保证最终能够成功或者到达即可,更适合采用事务消息的方案
这里认为退票和退款这两种情况不属于回滚,属于另起的一个单独的事务

实时一致性,必须强一致,实时看到效果,在线等的场景
最终一致性,允许延迟,只要求最终一致即可,上面的方案七
允许少量不一致的业务,偶尔也会来凑热闹,这个就不属于分布式事务了

基于跨数据库的分布式事务,同一服务内的跨库场景,适用2PC或者3PC
基于跨服务的分布式事务,上面说的场景大多数属于这一类,仍需具体看场景来使用TCC或者事务消息等
分布式消息,本地事务与消息的一致,通常针对不需要回滚的,允许非实时一致的,可选用此方案

再回顾一下对这几个方案的理解,本质的原理几乎是一样的,都需要双方确认,以及提供do和undo操作,以及最终的人工处理。

每次我在遇到不好理解,或者不好解决的问题的时候,都会把这个场景拿到现实生活中找类似的场景,打个比方说吧,就说刚才的订中转机票的场景,现在放到现实中找例子,比如有一个客户需要从订票代理A手里购买航空公司B和航空公司C的票,代理A打电话给B,问票还有么,有的话给自己留一会,如果没有,此次订票失败,代理A再打电话给C问票还有么,有的话给自己留一会,没有的话再回过来告诉A刚才那个票不要了,如果两方都有,就打电话告诉B,票我确定要了,然后再打电话告诉C,票我要了,正常事务就完事了,可是A再最后给C打电话的时候,电话打不通了,联系不上C,那会怎么办,A会一直重试再打几次电话,直到打通为止,正常情况下,不会一直打不通,真遇到了极端情况,C公司系统坏了,票买不了了,航班取消了等等,那也只能有一方赔付事务中断导致的损失,这些永远也无法用一个事务完成的了的,对于前面的正常情况,都是可以用事务来处理。

相关概念

事务,分布式事务 

Monday, March 25, 2019

XXE漏洞解决记录

        内网漏洞平台报了一个XXE漏洞,是我们一个服务域名的根路径,刚一看还以为是弄错了,查了一下代码,果然存在这样的跟路径Controller。看了下逻辑,该服务是用于接收微信的事件通知的,微信是以xml格式提交过来的请求,代码中对XML的解析没有做特殊处理,于是乎。。。
        接到工单后,搜了一下xxe漏洞,简单作个了解,原因XML协议中允许引入外部实体声明。这个外部实体的引用就给了不法分子很大的想象空间。外部的实体引用是个url,写成内部文件路径,可以扫描系统任意路径的文件,写成黑各自己服务的路径,可以收集服务器的信息,写成内网服务地址,可以攻击内网服务,再随意更换ip和端口,可以扫描端口等等。小小一个漏洞,可以利用的地方居然这么多,赶紧修复。


先在本地用postman构造注入的xml内容,模拟注入请求,服务B按预期收到了服务A的请求。

SAXReader reader = new SAXReader();
reader.setFeature("http://xml.org/sax/features/external-general-entities", false); 
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 
Document document = reader.read(xml);

加上以上红色的代码后,再次发送注入请求,服务B不再收到服务A的请求。
自测没问题后,开始上线流程,上完线后,自己用微信关注,取关公众号,服务均能正常接收微信上报过来的事件报文,回归完毕。


参考:
https://blog.csdn.net/zhengpeitao/article/details/82142869
https://www.cnblogs.com/r00tuser/p/7255939.html
https://www.jianshu.com/p/77f2181587a4

Wednesday, March 20, 2019

关于redis分布式锁的几个疑问点

我们在分布式场景下,需要用到分布式锁,分布式锁的实现方案可以有redis实现,zk实现等,这里只讨论redis的方案
先稍微铺垫一下两个redis的不成熟方案,

一,
Get,判断是否存在,如存在直接返回失败,不存在继续
Set,设置锁,key对应的value为1,并设置过期时间
Del,业务执行完毕释放锁
code如下:
    private static long expireTime = 20000;

    public static boolean getLock(String key) {
        String lock = RedisUtil.get(key);
        if (!"1".equals(lock)) {
            RedisUtil.set(key, "1");
            RedisUtil.expire(key, expireTime);
            return true;
        } else {
            return false;
        }
    }

    public static void releaseLock(String key) {
        String check = RedisUtil.get(key);
        if (check != null) {
            RedisUtil.del(key);
        }
    }
此方案明显存在的问题就是,多个线程同时请求get,不存在,同时set,所以会有多个线程进入的可能

二,
Setnx,key对应的value为1,判断返回结果,0为设置失败,已经存在key,直接返回失败,1设置成功,继续
Expire,设置过期时间
Del,业务执行完毕释放锁
code如下:
    private static long expireTime = 20000;

    public static boolean getLock(String key) {
        long lock = RedisUtil.setNX(key, "1");
        if (lock == 1) {
            RedisUtil.expire(key, expireTime);
            return true;
        } else {
            return false;
        }
    }

    public static void releaseLock(String key) {
        String check = RedisUtil.get(key);
        if (check != null) {
            RedisUtil.del(key);
        }
    }
此方法,如果在setnx后expire前宕机,那key就不会被删除也没有过期,别的线程永远也进不来了

然后说下目前同事用到的,以及网上找到的比较被认可的解决方案
Setnx,key对应的value为过期时间戳,判断设置结果,1设置成功,返回成功,0设置失败,key存在内容,继续
Get,取出key的值,判断过期,如没过期,返回失败,如已过期,或者key已经不存在了,继续
Getset,设置锁,同时拿到返回值为设置之前的值,判断是否与刚才取出的值相等,如相等,证明是自己设置的,返回成功,如不相等,证明不是自己设置的,返回失败
Del,业务执行完毕释放锁
code如下:
    private static long expireTime = 20000;

    public static boolean getLock(String key) {
        long lock = RedisUtil.setNX(key, "" + System.currentTimeMillis() + expireTime);
        if (lock == 1) {
            return true;
        } else {
            String check = RedisUtil.get(key);
            if (check != null && System.currentTimeMillis() < Long.valueOf(check)) {
                //key还没有过期
                return false;
            } else {
                //key过期或者已经不存在了
                String old = RedisUtil.getSET(key, "" + System.currentTimeMillis() + expireTime);
                if (old == null) {
                    //说明此时已经key已经不存在,获取锁成功
                    return true;
                } else {
                    // TODO 疑问点1,这里同事用的是否过期的判断,会不会存在风险?
                    if (check.equals(old)) {
                        //获取锁成功
                        return true;
                    } else {
                        //在getSET之  前已经被别的线程获取了锁,获取锁失败
                        return false;
                    }
                }
            }
        }
    }

    public static void releaseLock(String key) {
        String check = RedisUtil.get(key);
        if (check != null) {
            if (System.currentTimeMillis() < Long.valueOf(check)) {
                // TODO 疑问点2,有没有可能删除别的线程设置的key
                RedisUtil.del(key);
            } else {
                // TODO 疑问点3,过期了是否需要删除
            }
        } else {
        }
    }

    // TODO 疑问点4,expire正常来说是需要设置成一个业务一定在此时间内能执行完成的时间,那么,业务如果万一没有执行完成的时候,redis锁还安全么?
    // TODO 疑问点5,redis锁真的安全吗,多个线程错综复杂的执行顺序,如何能比较好的理解呢?
    // TODO 疑问点6,这里还有个疑问,是不是理论上每两行代码之间都可能会中断,比如宕机,比如卡死过了很长时间之后继续执行?这个疑问明显的体现出了基础的薄弱

由于没有找到合适的人讨论,所以这里暂时把自己的疑问记录下来,待以后有机会再理解透彻 


Thursday, March 14, 2019

ThreadLocal理解

        关于ThreadLocal,网上已经有很多文章讲的也是很清楚了,至于应用场景,大多也就是使用demo的方式进行了讲解,达不到让人印象深刻的目的。虽然以前也多次遇到过别人讨论这个东西,大多是面试场景吧,只是直至目前,我也几乎没有在工作中用到过(做开发这么久了,没用过这个,实属汗颜啊),根据理解,用我自己的话把ThreadLocal来描述一遍,以此记录。

        ThreadLocal,是线程本地变量,ThreadLocal为多线程编程提供了一个另一个思路,不用锁,不用共享变量等特点。它的实现思路是在每个线程中存放一个变量的map,这个map用于存放属于线程的变量,这个map的使用就是由ThreadLocal来管理。ThreadLocal有三个方法,get,set,remove,分别是获取变量,设置变量,移除变量。还有一个初始化方法,是给继承类用的。

        对于它的应用场景,按上面的思路来说,就是某个变量是希望在多线程下使用,但又是可以允许不同线程之间互不影响的情况(按线程多例,每个线程一个实例)。比如在spring的web工程体系中,从接收到用户的一个请求到service到dao层都是同一个线程在为其服务,每个线程持有一个数据库连接的实例,这样对于每一个实例来说,都不存在线程安全的问题了

        若以后有机会实际用到的时候,再来补充场景。

Tuesday, March 12, 2019

关于cookie是否能跨域的解读

        这篇文章目的是对CORS跨域携带cookie的理解作一个解释,之前一直对这里有一个误解,以为跨域是可以携带不同域名下的cookie,事实上,cookie的传递是要遵循同源策略的
        问题引出
        同事遇到了一个问题,希望在跨域ajax请求的时候,带上原域的cookie,希望被访问的目标域服务端能访问到原域的cookie,问到了我,我还比较自信的表示跨域这点小事肯定能搞定,因为之前也多次处理过跨域的问题,经过多次尝试,均以失败告终,很是汗颜,最后不断的找资料以及问大牛,得知,这种方式无法实现。

        究其根本&原理说明
        本次的两个域名涉及到跨域。
说到跨域就要说到同源策略,而同源策略,同源策略限制cookie的访问以及ajax请求。
本次的跨域AJAX请求是用的CORS实现方式,CORS是w3c的标准。
CORS方式,默认不允许携带cookie,如果允许携带,是需要客户端和服务端同时设置。CORS方式,携带的cookie,也是要遵循同源策略的,cookie只能携带被访问域名以及其父域下的,其它域的cookie是不被允许访问的
        白话总结
        两个域名下的cookie,是不能相互访问和传递的

对于有相同根域名(二级及以上),可以把cookie设置到根域上才能实现跨子域名的cookie访问(实际上,cookie并没有跨域)

相关问题,跨域的几种方式,每种方式的具体实现,适用场景,针对不同的语言的实现细节,这里就暂不讨论了

CORS,同源策略等名词,直接百科即可

对于在尝试解决问题的过程中,做的一些测试代码,附录如下


Node,原生XHR,document写cookie
zcmtestop.58corp.com进行xhr请求zcmtestop1.58corp.com

var xmlHttp=new XMLHttpRequest();
xmlHttp.open("GET","http://zcmtestop1.58corp.com/",false);
xmlHttp.withCredentials = true;
xmlHttp.send();
result = xmlHttp.responseText;
alert(result);

写cookie
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";

服务端设置header
response().setHeader("Access-Control-Allow-Credentials", "true");
response().setHeader("Access-Control-Allow-Origin", "http://zcmop.58corp.com");

Xhr请求,后端无法拿到zcmtestop.58corp.com域下的cookie

Java,jquery,jquery写cookie
http://zcmop.58corp.com/
$.ajax({type: 'GET',url:'http://zcmop1.58corp.com/scftest',data: {},success:function(data){alert(data);},dataType:"json",xhrFields: {withCredentials: true},crossDomain:true});
$.cookie('the_cookie','the_value',{expires:7,path:'/',domain:'zcmop.58corp.com'});

服务端设置header
    response.writeHead(200,{
        'Content-Type': 'text/plain',
        'Access-Control-Allow-Origin': 'http://zcmtestop.58corp.com',
        'Access-Control-Allow-Credentials': true
        });

Xhr请求,后端无法拿到zcmop.58corp.com域下的cookie
以上两次尝试均失败

同源理解参考

Tuesday, January 22, 2019

事务处理方案思考

对于一些较复杂的业务场景,可能的最长操作有10多个update或者insert操作,还有多个redis操作
对于redis操作,整个事务失败后需要手动处理回滚,多个操作写回滚的代码可能会比较烦索
对于DB操作,如果全部放到一个大事务里面,缺点是相互影响较大,多个更新操作,失败风险较大,子业务会影响主业务
(某些场景不用数据的事务,会用手动处理事务的方式,处理起来较烦索,尤其是操作多的情况,比较来看数据库事务代码就简单得多了)
需要拆分成一个主业务线,和多个副业务线,对于重要并且访问量大的场景,或者不重要的子业务需要较长的响应时间,需要拆分成异步处理的方式
(异步的方式,可以自己写消息队列,也可以用成熟的框架)
目前我的方式是,在拆分成主业务和副业务之后,每个子业务用一个数据库的事务控制,redis手动处理回滚,

然后处理成主业务是多个子业务并行的模式,如果有某个子业务失败了,报警,并且预留重复请求接口,暂时手动人工处理异常情况,量大再议 

Monday, December 24, 2018

排期评估方式

排期评估方式
目前我在58的情况大概如下
对于简单工程来讲,接口数*1天,数据结构表数*1天,定时任务数*1天,(更简单的可以减半)
中间涉及到的其它工作
初始化工程,WEB工程,RPC工程,定时任务工程,各1天
首次调试第三方接口数量,1个1天
对于复杂一点的工程,需要进行模块拆分,针对模块应用以上评估方式


buffer视情况而定,可以是整个工程的10%-30% 

Monday, December 10, 2018

我的几年不换水的生态鱼缸

一个可持续的生态鱼缸,可以做到几年不换水,当然达到这一目标可能有很多种途径,也可以达到一些人的真正认为的不换水的目标,当然也有很多人质疑不可能,毕竟简单的一句话每个人的理解和标准都不是很一样的,所以我的鱼缸也仅仅是比较适合我自己的习惯和方式。

我日常的工作就是,偶尔喂喂鱼,频率大于2天一次,小于一天一次,偶尔加点水,频率约一周一次,洗过滤棉,约一周一次。
当然,达到这个可持续的目标,需要不断的持续的关注鱼缸的健康状况,比如隔一两个月可能会除一下水草。藻类滋生多了的时候,需要加一些苹果螺,另一种丝藻太多的时候,之前需要手动清除一下,后来尝试用虾来除掉,效果还不错。苹果螺多了的话,要么用手往出抓,要么可以放一两条杀手螺,但当杀手螺繁殖起来之后,苹果螺可能就没了,这时候最好就是控制一下杀手螺的数量
虾能继续繁殖目前还没有摸索出合理的方案,按卖虾人所说,有底沙的鱼缸不能养虾,但我的鱼缸,虾也活了有一两年之久了,只是繁殖的比较慢(我希望虾也是可以爆缸的)。
曾经加过除藻剂,加过水草肥,但觉得都没有什么意义,光照,活水,应该就够了

有一个重要的点就是一定要有底沙,或者水草泥,因为我个人比较喜欢沙,所以是一直用的沙,底沙或者水草泥的作用我觉得太强大了,我认为应该是建立起一个生态鱼缸的必备条件吧。
水草,之前也种了一些,目前生命力最强的就是我现在用的,买的时候貌似叫矮珍珠,但现在感觉完全不是矮珍珠(搜索了一下,应该是叫做日本珍珠草),也是繁殖了几年以上。


整体来说,确实几年没有换水,只是在搬家的时候,会抽出70%左右的水,固定了之后再慢慢加回来的。另外也觉得维护鱼缸的健康状态,比日常换水的工作量要小很多,也更有意思一些。

Monday, December 3, 2018

带娃感悟-陪伴

虽然陪伴孩子的时间不算少了,但仍能感觉得孩子还是缺少父母的陪伴,尤其在参加幼儿园活动的时候,更是能感觉到,大部分孩子都是缺少父母的陪伴,
而且大部分的情况下,父母的陪伴都是带有目的性的,也比较少有能全身心的投入到与孩子的玩耍中

陪孩子做游戏,画画,玩玩具等,大人总是按照大人的思路进行,比如不能脏了,不能趴地上,手不能放嘴里,这样摆积木不对,这样画不圆,玩一会要喝水了,再玩一会到时间了,再不抽空玩玩手机,等等等等,孩子当然能够感受到不自由,感觉到大人的陪伴是减分的,大人以为的有目的的培养,有些时候是有必要的,但也有很多时候方法不当,起不到合适的效果, 

做事情需要放大自己的视角

尽力放大自己的视角(就好像玩游戏的时候不断的看小地图,目的相同),而实际做的过程中,需要尽量避免两点,一是放大视角后每件事情都不断的想细节,结果会导致精力不足而影响自己的全局观,二是放大视角后,完全不了解细节而导致与实现脱节,而应该了解清楚框架,针对自己当前正在做的事情上面,要仔细周全,在具体的实现细节上面,要有精准的把握,前提下可以不关心细节,要做到胆大心细
无论在做任何的事情上面,尽可能的从较为全局的视角看待当前所做的事情
胆大心细,说起来简单,做起来难,胆大是指判断准确后果断去做,心细是指在做的过程中在小范围内的想周全,做到稳和准

Sunday, November 18, 2018

书单

最近的书单(测试一下amazon联盟)


Saturday, October 13, 2018

四渡岩场

四渡岩场,过了5.8和5.9加两条5.10b,尝试没过的一条5.10c
总结 
在下面看着容易,感觉简单时容易高估自己的实力
自己上去攀很难,遇到困难时容易低估自己的实力
技巧不足,导致消耗过多体能,多数情况下,能用脚的地方就不用手,有意识的保存最重要部位的能量以备关键时刻使用
坚持还是很重要的,但在坚持的过程中要让体能的恢复速度大于坚持所需要的能量,所以坚持要选择一个相对容易的地方,否则,不讲方法的坚持很可能是把体能耗尽而失败
遗憾
攀岩过程没有受伤,回来坐船手被绳索夹了一下,掉块肉,是个小遗憾。

关于高估和低估自己实力这一点体会,发现人在做其它事情上也会有类似的情况。在定目标,制定计划,立flag,总会高估自己的能力。当实际生活中遇到困难的时候,往往会低估自己的能力,会选择放弃。


多了一项运动爱好,挺好。

Search This Blog

Categories

Popular Posts

Translate

Copyright © 虫虫的成长历程 | Powered by Blogger Design by PWT | Blogger Theme by NewBloggerThemes.com