一、需求缘起
之前做微信钱包的项目,许多功能都需要与腾讯微信的前台(App)或后台交互,微信需要一个access_token凭证用于身份验证。
这是微信公众平台对access_token的一段描述
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
因此第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则会造成access_token覆盖而影响业务。
中控服务器结果如下图
存在什么问题?中控服务器只有一个,单机。如果中控服务器挂了,所有业务都会受到影响。
改进一下架构,采用两个中控服务器(如下图)。
采用两个中控服务器,能够防止单点故障。但是两个定时器会从微信服务器分别获取access_token,会导致access_token刷新次数增加(微信规定一天只能刷新2000次)。极限情况下还可能导致Cache中的access_token失效(中控服务器1先获取access_token1,中控服务器2后获取access_token2,由于分布式原因,有可能access_token1在access_token2之后才存入Cache)。
需要一个技术手段,确保两个中控服务器,只有一个能够从“微信服务器”获取access_token。分布式锁!
二、怎么做
网上有很多分布式锁的做法,通过zookeeper,redis等软件都能实现。(http://surlymo.iteye.com/blog/2082684)
在这里,采用mysql数据库来实现这个功能。
微信access_token是2小时过期,为了保险起见,每隔1个小时就获取(刷新)一次access_token。
建立一张数据库表t_token_lock(id,refreshtime,version),
id:是主键,分布式锁功能里用来定位某条数据记录
refreshtime:刷新access_token的时间,每更新一次增加1小时(每隔1小时刷新一次)
version:记录更新的版本号,默认从0开始,每更新一次就+1
数据库表中最初存放一条记录(1,2017-03-18 21:00:00,0)
两个中控服务器通过定时器(假设) 每过5分钟访问一次数据库表,执行以下操作:
1、查询数据记录 select id,refreshtime,version from t_token_lock where id=1, 将得到的refreshtime记为lastRefreshtime,version记为lastVersion
2、判断当前时间now是否大于refreshtime 如果是,执行第3步;如果否(还没到刷新时间),结束。
3、更新数据记录 update t_token_lock set refreshtime=lastRefreshtime +1h and version=lastVersion+1 where id=1 and version=lastVersion
只有第3步能够成功更新记录的中控服务器,允许访问微信服务器刷新access_token。分布式锁已经实现了!
三、原理解释
怎么理解这3步实现了分布式锁呢?
两个中控服务器每个5分钟读一次数据,大多数时候,refreshtime>now(refreshtime是设置的未来一个小时的时刻,这一个小时之内的时间访问数据,都是这个结果),这种情况下没到刷新时间,不会刷新acceess_token。
我们只需要讨论临界情况,即查询数据记录时需要刷新access_token的情况。
两个中控服务器的计时器一般是不同步的,多数情况下,两个中控服务器会一前一后(一个执行完了,另一个才执行)的执行这3步。这种情况下,后一个执行这3个步骤的中控服务器,看到的refreshtime已经是1个小时之后,它不会去刷新access_token。
因此,只需要讨论两个中控服务器并发同时执行这3步的时候
当两个中控服务器同时执行了第1步、第2步后,它们会执行第3步更新数据库记录。由于mysql数据库锁的存在,不可能同时更新同一条数据记录。后一个更新记录的中控服务器更新记录时,version字段的值已经被前一个中控服务器+1,因此version=lastVersion的条件不满足,不能成功更新记录。后一个中控服务器也就不会去请求(刷新)access_token。
至此,分布式锁被转化成了mysql的行级锁。那mysql的行级锁又是怎么实现的呢(苦海无边,回头是岸)?不管怎样,mysql在单机上实现锁会容易多了。