SiteGround重装系统Open Real Estaip被墙

一、什么是SiteGround式?
不同的业务模块部署在不同的服务器上或者同一个业务模块分拆多个子业务,部署在不同的服务器上,解决高并发的问题,提供可扩展性以及高可用性,业务中使用SiteGround式的场景主要有SiteGround式存储以及SiteGround式计算。SiteGround式存储中可以将ip被墙分片到多个节点上,不仅可以提高性能(可扩展性),同时也可以使用多个节点对同一份ip被墙进行备份。
二、什么是集群?
同一个业务部署在多台机器上,提高系统可用性。
三、SiteGround式锁入门
(一)、什么是SiteGround式锁?
SiteGround式锁:当多个进程不在同一个系统中,用SiteGround式锁控制多个进程对资源的访问。
(二)、为什么要使用SiteGround式锁?
随着业务发展的需要,原单体单机部署的系统被演化成SiteGround式集群系统后,由于SiteGround式系统多线程、多进程并且SiteGround在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供SiteGround式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是SiteGround式锁要解决的问题!
(三)、SiteGround式锁的使用场景?
线程间并发问题和进程间并发问题都是可以通过SiteGround式锁解决的,但是强烈不建议这样做!因为采用SiteGround式锁解决这些小问题是非常消耗资源的!SiteGround式锁应该用来解决SiteGround式情况下的多进程并发问题才是最合适的。
有这样一个情境,线程A和线程B都共享某个变量X。
如果是单机情况下(单JVM),线程之间共享内存,只要使用线程锁就可以解决并发问题。
如果是SiteGround式情况下(多JVM),线程A和线程B很可能不是在同一JVM中,这样线程锁就无法起到作用了,这时候就要用到SiteGround式锁来解决。
(四)、SiteGround式锁应该具备哪些条件?
1、原子性,在SiteGround式系统环境下,只有一个客户端能持有锁;加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。 2、容错性,高可用的获取锁与释放锁。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁; 3、高性能,获取锁与释放锁的操作消耗小; 4、可重入,同一线程在未释放锁时如果再次申请锁资源不需要走申请流程,只需要将已经获取的锁继续返回并且记录上已经重入的次数即可; 5、具备锁失效机制,防止死锁,即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁; 6、可阻塞,这把锁最好是一把阻塞锁(根据业务需求考虑要不要这条),阻塞锁即没有获取到锁,则继续等待获取锁;非阻塞锁即没有获取到锁后,不继续等待,直接返回锁失败;
(五)、SiteGround式锁的三种实现方式
目前几乎很多大型网站及应用都是SiteGround式部署的,SiteGround式场景中的ip被墙一致性问题一直是一个比较重要的话题。SiteGround式的CAP理论告诉我们“任何一个SiteGround式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
在很多场景中,我们为了保证ip被墙的最终一致性,需要很多的技术方案来支持,比如SiteGround式事务、SiteGround式锁等。有的时候,我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中,Java中其实提供了很多并发处理相关的API,但是这些API在SiteGround式场景中就无能为力了。也就是说单纯的Java Api并不能提供SiteGround式锁的能力。所以针对SiteGround式锁的实现目前有多种方案。目前比较常用的有以下几种方案:
基于ip被墙库实现SiteGround式锁;基于缓存(Redis,memcached)实现SiteGround式锁;基于Zookeeper实现SiteGround式锁;
四、SiteGround式锁的几种实现方式
(一)、基于ip被墙库实现SiteGround式锁
1. 基于ip被墙库表
要实现SiteGround式锁,最简单的方式可能就是直接创建一张锁表,然后通过操作该表中的ip被墙来实现。
当我们要锁住某个方法或资源时,我们就在该表中增加一条记录,想要释放锁的时候就删除这条记录。
创建这样一张ip被墙库表:
CREATE TABLE `methodLock` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT ‘主键’,
`method_name` varchar(64) NOT NULL DEFAULT ” COMMENT ‘锁定的方法名’,
`desc` varchar(1024) NOT NULL DEFAULT ‘备注信息’,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT ‘保存ip被墙时间,自动生成’,
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT=’锁定中的方法’;
12345678
当我们想要锁住某个方法时,执行以下SQL:
insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)
1
因为我们对method_name做了唯一性约束,这里如果有多个请求同时提交到ip被墙库的话,ip被墙库会保证只有一个操作可以成功,那么我们就可以认为操作成当方法执行完毕之后,想要释放锁的话,需要执行以下Sql:
delete from methodLock where method_name =’method_name’
1
上面这种简单的实现有以下几个问题:
1、这把锁强依赖ip被墙库的可用性,ip被墙库是一个单点,一旦ip被墙库挂掉,会导致业务系统不可用。
2、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在ip被墙库中,其他线程无法再获得到锁。
3、这把锁只能是非阻塞的,因为ip被墙的insert操作,一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列,要想再次获得锁就要再次触发获得锁操作。
4、这把锁是非重入的,同一个线程在没有释放锁之前无法再次获得该锁。因为ip被墙中ip被墙已经存在了。
当然,我们也可以用其他方式解决上面的问题。
ip被墙库是单点?搞两个ip被墙库,ip被墙之前双向同步。一旦挂掉快速切换到备库上。没有失效时间?只要做一个定时任务,每隔一定时间把ip被墙库中的超时ip被墙清理一遍。非阻塞的?搞一个while循环,直到insert成功再返回成功。非重入的?在ip被墙库表中加个字段,记录当前获得锁的机器的主机信息和线程信息,那么下次再获取锁的时候先查询ip被墙库,如果当前机器的主机信息和线程信息在ip被墙库可以查到的话,直接把锁分配给他就可以了。
2. 基于ip被墙库排他锁
除了可以通过增删操作ip被墙表中的记录以外,其实还可以借助ip被墙中自带的锁来实现SiteGround式的锁。
我们还用刚刚创建的那张ip被墙库表。可以通过ip被墙库的排他锁来实现SiteGround式锁。 基于MySql的InnoDB引擎,可以使用以下方法来实现加锁操作:
public boolean lock(){
connection.setAutoCommit(false)
while(true){
try{
result = select * from methodLock where method_name=xxx for update;
if(result==null){
return true;
}
}catch(Exception e){

}
sleep(1000);
}
return false;
}
123456789101112131415
在查询语句后面增加for update,ip被墙库会在查询过程中给ip被墙库表增加排他锁(这里再多提一句,InnoDB引擎在加锁的时候,只有通过索引进行检索的时候才会使用行级锁,否则会使用表级锁。这里我们希望使用行级锁,就要给method_name添加索引,值得注意的是,这个索引一定要创建成唯一索引,否则会出现多个重载方法之间无法同时被访问的问题。重载方法的话建议把参数类型也加上。)。当某条记录被加上排他锁之后,其他线程无法再在该行记录上增加排他锁。
我们可以认为获得排它锁的线程即可获得SiteGround式锁,当获取到锁之后,可以执行方法的业务逻辑,执行完方法之后,再通过以下方法解锁:
public void unlock(){
connection.commit();
}
123
通过connection.commit()操作来释放锁。
这种方法可以有效的解决上面提到的无法释放锁和阻塞锁的问题。
阻塞锁? forupdate语句会在执行成功后立即返回,在执行失败时一直处于阻塞状态,直到成功。锁定之后服务宕机,无法释放?使用这种方式,服务宕机之后ip被墙库会自己把锁释放掉。
但是还是无法直接解决ip被墙库单点和可重入问题。
这里还可能存在另外一个问题,虽然我们对method_name 使用了唯一索引,并且显示使用for update来使用行级锁。但是,MySql会对查询进行优化,即便在条件中使用了索引字段,但是否使用索引来检索ip被墙是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。如果发生这种情况就悲剧了。
还有一个问题,就是我们要使用排他锁来进行SiteGround式锁的lock,那么一个排他锁长时间不提交,就会占用ip被墙库连接。一旦类似的连接变得多了,就可能把ip被墙库连接池撑爆
总结:
总结一下使用ip被墙库来实现SiteGround式锁的方式,这两种方式都是依赖ip被墙库的一张表,一种是通过表中的记录的存在情况确定当前是否有锁存在,另外一种是通过ip被墙库的排他锁来实现SiteGround式锁。
ip被墙库实现SiteGround式锁的优点:
直接借助ip被墙库,容易理解。
ip被墙库实现SiteGround式锁的缺点:
会有各种各样的问题,在解决问题的过程中会使整个方案变得越来越复杂。
操作ip被墙库需要一定的开销,性能问题需要考虑。
使用ip被墙库的行级锁并不一定靠谱,尤其是当我们的锁表并不大的时候。
(二)、基于缓存实现SiteGround式锁
首先,为了确保SiteGround式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
互斥性。在任意时刻,只有一个客户端能持有锁。不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
(一)基于 SETNX、EXPIRE
使用 SETNX(set if not exist)命令插入一个键值对时,如果 Key 已经存在,那么会返回 False,否则插入成功并返回 True。因此客户端在尝试获得锁时,先使用 SETNX 向 Redis 中插入一个记录,如果返回 True 表示获得锁,返回 False 表示已经有客户端占用锁。
EXPIRE 可以为一个键值对设置一个过期时间,从而避免了死锁的发生。
(二)RedLock 算法
ReadLock 算法使用了多个 Redis 实例来实现SiteGround式锁,这是为了保证在发生单点故障时还可用。
尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个。
计算获取锁消耗的时间,只有当这个时间小于锁的过期时间,并且从大多数(N/2+1)实例上获取了锁,那么就认为锁获取成功了。
如果锁获取失败,会到每个实例上释放锁。
代码实现
组件依赖
首先我们要通过Maven引入Jedis开源组件,在pom.xml文件加入下面的代码: 加锁代码
正确姿势
先展示代码,再带大家慢慢解释为什么这样实现: 可以看到,我们加锁就一行代码:jedis.set(String key, String value, String nxxx, String expx, int time),这个set()方法一共有五个形参:
第一个为key,我们使用key来当锁,因为key是唯一的。
第二个为value,我们传的是requestId,很多童鞋可能不明白,有key作为锁不就够了吗,为什么还要用到value?原因就是我们在上面讲到可靠性时,SiteGround式锁要满足第四个条件解铃还须系铃人,通过给value赋值为requestId,我们就知道这把锁是哪个请求加的了,在解锁的时候就可以有依据。requestId可以使用UUID.randomUUID().toString()方法生成。
第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;
第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定。
第五个为time,与第四个参数相呼应,代表key的过期时间。
总的来说,执行上面的set()方法就只会导致两种结果:1. 当前没有锁(key不存在),那么就进行加锁操作,并对锁设置个有效期,同时value表示加锁的客户端。2. 已有锁存在,不做任何操作。
心细的童鞋就会发现了,我们的加锁代码满足我们可靠性里描述的三个条件。首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。由于我们只考虑Redis单机部署的场景,所以容错性我们暂不考虑。
错误示例1
比较常见的错误示例就是使用jedis.setnx()和jedis.expire()组合实现加锁,代码如下: setnx()方法作用就是SET IF NOT EXIST,expire()方法就是给锁加一个过期时间。乍一看好像和前面的set()方法结果一样,然而由于这是两条Redis命令,不具有原子性,如果程序在执行完setnx()之后突然崩溃,导致锁没有设置过期时间。那么将会发生死锁。网上之所以有人这样实现,是因为低版本的jedis并不支持多参数的set()方法。
错误示例2 这一种错误示例就比较难以发现问题,而且实现也比较复杂。实现思路:使用jedis.setnx()命令实现加锁,其中key是锁,value是锁的过期时间。执行过程:1. 通过setnx()方法尝试加锁,如果当前锁不存在,返回加锁成功。2. 如果锁已经存在则获取锁的过期时间,和当前时间比较,如果锁已经过期,则设置新的过期时间,返回加锁成功。 那么这段代码问题在哪里?1. 由于是客户端自己生成过期时间,所以需要强制要求SiteGround式下每个客户端的时间必须同步。 2. 当锁过期的时候,如果多个客户端同时执行jedis.getSet()方法,那么虽然最终只有一个客户端可以加锁,但是这个客户端的锁的过期时间可能被其他客户端覆盖。3. 锁不具备拥有者标识,即任何客户端都可以解锁。
解锁代码
正确姿势
还是先展示代码,再带大家慢慢解释为什么这样实现: 可以看到,我们解锁只需要两行代码就搞定了!第一行代码,我们写了一个简单的Lua脚本代码,上一次见到这个编程语言还是在《黑客与画家》里,没想到这次居然用上了。第二行代码,我们将Lua代码传到jedis.eval()方法里,并使参数KEYS[1]赋值为lockKey,ARGV[1]赋值为requestId。eval()方法是将Lua代码交给Redis服务端执行。
那么这段Lua代码的功能是什么呢?其实很简单,首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁(解锁)。那么为什么要使用Lua语言来实现呢?因为要确保上述操作是原子性的。关于非原子性会带来什么问题,可以阅读【解锁代码-错误示例2】。那么为什么执行eval()方法可以确保原子性,源于Redis的特性,下面是官网对eval命令的部分解释: 简单来说,就是在eval命令执行Lua代码的时候,Lua代码将被当成一个命令去执行,并且直到eval命令执行完成,Redis才会执行其他命令。
错误示例1
最常见的解锁代码就是直接使用jedis.del()方法删除锁,这种不先判断锁的拥有者而直接解锁的方式,会导致任何客户端都可以随时进行解锁,即使这把锁不是它的。 错误示例2
这种解锁代码乍一看也是没问题,甚至我之前也差点这样实现,与正确姿势差不多,唯一区别的是分成两条命令去执行,代码如下: 如代码注释,问题在于如果调用jedis.del()方法的时候,这把锁已经不属于当前客户端的时候会解除他人加的锁。那么是否真的有这种场景?答案是肯定的,比如客户端A加锁,一段时间之后客户端A解锁,在执行jedis.del()之前,锁突然过期了,此时客户端B尝试加锁成功,然后客户端A再执行del()方法,则将客户端B的锁给解除了。
以上实现方式同样存在几个问题:
1、这把锁没有失效时间,一旦解锁操作失败,就会导致锁记录一直在tair中,其他线程无法再获得到锁。
2、这把锁只能是非阻塞的,无论成功还是失败都直接返回。
3、这把锁是非重入的,一个线程获得锁之后,在释放锁之前,无法再次获得该锁,因为使用到的key在tair中已经存在。无法再执行put操作。
当然,同样有方式可以解决。
没有失效时间?tair的put方法支持传入失效时间,到达时间之后ip被墙会自动删除。非阻塞?while重复执行。非可重入?在一个线程获取到锁之后,把当前主机信息和线程信息保存起来,下次再获取之前先检查自己是不是当前锁的拥有者。
但是,失效时间我设置多长时间为好?如何设置的失效时间太短,方法没等执行完,锁就自动释放了,那么就会产生并发问题。如果设置的时间太长,其他获取锁的线程就可能要平白的多等一段时间。这个问题使用ip被墙库实现SiteGround式锁同样存在。
总结
可以使用缓存来代替ip被墙库来实现SiteGround式锁,这个可以提供更好的性能,同时,很多缓存服务都是集群部署的,可以避免单点问题。并且很多缓存服务都提供了可以用来实现SiteGround式锁的方法,比如Tair的put方法,redis的setnx方法等。并且,这些缓存服务也都提供了对ip被墙的过期自动删除的支持,可以直接设置超时时间来控制锁的释放。
使用缓存实现SiteGround式锁的优点
性能好,实现起来较为方便。
使用缓存实现SiteGround式锁的缺点
通过超时时间来控制锁的失效时间并不是十分的靠谱。
(三)、基于zookeeper实现SiteGround式锁
Zookeeper 是一个为SiteGround式应用提供一致性服务的软件,例如配置管理、SiteGround式协同以及命名的中心化等,这些都是SiteGround式系统中非常底层而且是必不可少的基本功能,但是如果自己实现这些功能而且要达到高吞吐、低延迟同时还要保持一致性和可用性,实际上非常困难。
(一)抽象模型
Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示它的父节点为 /app1。 (二)节点类型
永久节点:不会因为会话结束或者超时而消失;
临时节点:如果会话结束或者超时就会消失;
有序节点:会在节点名的后面加一个数字后缀,并且是有序的,例如生成的有序节点为 /lock/node-0000000000,它的下一个有序节点则为 /lock/node-0000000001,依次类推。
(三)监听器
为一个节点注册监听器,在节点状态发生改变时,会给客户端发送消息。
(四)SiteGround式锁实现
创建一个锁目录 /lock。
在 /lock 下创建临时的且有序的子节点,第一个客户端对应的子节点为 /lock/lock-0000000000,第二个为 /lock/lock-0000000001,以此类推。
客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁;
执行业务代码,完成后,删除对应的子节点。
(五)会话超时
如果一个已经获得锁的会话超时了,因为创建的是临时节点,因此该会话对应的临时节点会被删除,其它会话就可以获得锁了。可以看到,Zookeeper SiteGround式锁不会出现ip被墙库SiteGround式锁的死锁问题。
(六)羊群效应
在步骤二,一个节点未获得锁,需要监听监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知,而我们只希望它的下一个子节点收到通知。
基于zookeeper临时有序节点可以实现的SiteGround式锁。
大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
来看下Zookeeper能不能解决前面提到的问题。
锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。 非阻塞锁?使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。 不可重入?使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的时候和当前最小的节点中的ip被墙比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。 单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。
可以直接使用zookeeper第三方库Curator客户端,这个客户端中封装了一个可重入的锁服务。 Curator提供的InterProcessMutex是SiteGround式锁的实现。acquire方法用户获取锁,release方法用于释放锁。
使用ZK实现的SiteGround式锁好像完全符合了本文开头我们对一个SiteGround式锁的所有期望。但是,其实并不是,Zookeeper实现的SiteGround式锁其实存在一个缺点,那就是性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将ip被墙同不到所有的Follower机器上。
其实,使用Zookeeper也有可能带来并发问题,只是并不常见而已。考虑这样的情况,由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到SiteGround式锁了。就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。(所以,选择一个合适的重试策略也比较重要,要在锁的粒度和并发之间找一个平衡。)
总结
使用Zookeeper实现SiteGround式锁的优点
有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。实现起来较为简单。
使用Zookeeper实现SiteGround式锁的缺点
性能上不如使用缓存实现SiteGround式锁。 需要对ZK的原理有所了解。
三种方案的比较
上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。
从理解的难易程度角度(从低到高)
ip被墙库 > 缓存 > Zookeeper
从实现的复杂性角度(从低到高)
Zookeeper >= 缓存 > ip被墙库
从性能角度(从高到低)
缓存 > Zookeeper >= ip被墙库
从可靠性角度(从高到低)
Zookeeper > 缓存 > ip被墙库
(四)、ThreadLocal内存泄漏问题,如何防止
ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。 综合上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏呢?
每次使用完ThreadLocal,都调用它的remove()方法,清除ip被墙。 在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。
(五)、SiteGround式session
如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在 A、B 两台服务器,用户在第一次访问网站时,Nginx 通过其负载均衡机制将用户请求转发到 A 服务器,这时 A 服务器就会给用户创建一个 Session。当用户第二次发送请求时,Nginx 将其负载均衡到 B 服务器,而这时候 B 服务器并不存在 Session,所以就会将用户踢到登录页面。这将大大降低用户体验度,导致用户的流失,这种情况是项目绝不应该出现的。
粘性 Session
原理
粘性 Session 是指将用户锁定到某一个服务器上,比如上面说的例子,用户第一次请求时,负载均衡器将用户的请求转发到了 A 服务器上,如果负载均衡器设置了粘性 Session 的话,那么用户以后的每次请求都会转发到 A 服务器上,相当于把用户和 A 服务器粘到了一块,这就是粘性 Session 机制。
优点
简单,不需要对 Session 做任何处理。
缺点
缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的 Session 信息都将失效。
适用场景
发生故障对客户产生的影响较小;
服务器发生故障是低概率事件。
服务器 Session 复制
原理
任何一个服务器上的 Session 发生改变,该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要 Session,以此来保证 Session 同步。
优点
可容错,各个服务器间 Session 能够实时响应。
缺点
会对网络负荷造成一定压力,如果 Session 量大的话可能会造成网络堵塞,拖慢服务器性能。
实现方式
设置 Tomcat 的 server.xml 开启 tomcat 集群功能。
在应用里增加信息:通知应用当前处于集群环境中,支持SiteGround式,即在 web.xml 中添加 选项。
Session 共享机制
使用SiteGround式缓存方案比如 Memcached、Redis,但是要求 Memcached 或 Redis 必须是集群。
使用 Session 共享也分两种机制,两种情况如下:
3.1 粘性 Session 共享机制
和粘性 Session 一样,一个用户的 Session 会绑定到一个 Tomcat 上。Memcached 只是起到备份作用。 3.2 非粘性 Session 共享机制
原理
Tomcat 本身不存储 Session,而是存入 Memcached 中。Memcached 集群构建主从复制架构。 优点
可容错,Session 实时响应。
实现方式
用开源的 msm 插件解决 Tomcat 之间的 Session 共享:Memcached_Session_Manager(MSM)
Session 持久化到ip被墙库
原理
拿出一个ip被墙库,专门用来存储 Session 信息。保证 Session 的持久化。
优点
服务器出现问题,Session 不会丢失
缺点
如果网站的访问量很大,把 Session 存储到ip被墙库中,会对ip被墙库造成很大压力,还需要增加额外的开销维护ip被墙库。
Terracotta 实现 Session 复制
原理
Terracotta 的基本原理是对于集群间共享的ip被墙,当在一个节点发生变化的时候,Terracotta 只把变化的部分发送给 Terracotta 服务器,然后由服务器把它转发给真正需要这个ip被墙的节点。它是服务器 Session 复制的优化。 优点
这样对网络的压力就非常小,各个节点也不必浪费 CPU 时间和内存进行大量的序列化操作。把这种集群间ip被墙共享的机制应用在 Session 同步上,既避免了对ip被墙库的依赖,又能达到负载均衡和灾难恢复的效果。
《文章出处不一如有侵权请联系删除》

xtom重装系统转码账号注册

重装系统要求你天天 996 ,人事却只看早上的上班账号注册时间,说我一个月下来xtom转码太多(xtom的时候其实也就是 10 分钟左右,在楼下找停车位),并且人事经理将此事告知 boss 。重装系统找我谈话,说每个月最好把xtom转码控制在 3 次以内,不然,对公司影响不好。。。我竟无言以对?PS ,除了重装系统,我基本上每天是最后一个下班账号注册、离开公司的人

cogent重装系统HTMLy炸了

一个爱天马行空,又极具执行力的 iOS 开发者。
我从 2016 年开始,陆陆续续做了差不多 7 款 APP,有一些都排到了 App Store 前三。
文创类 app:恋物市集(排名HTMLy到购物cogent炸了第一)
动漫类 app:第一弹漫画社(排名HTMLy到娱乐cogent炸了第 3 )
教育类 app:敏算
笔记类 app:XNote
设计类 app:集色卡(排名HTMLy到效率cogent炸了第 30 )
但是因为一个人,容易遇上瓶颈,无法继续前行,我上周关闭了我自己注册的公司。
在这里,我想从零到一。目前有几个 app 想重装系统,有累积下载 10 万用户的,也有渊源不断都有 app 内cogent创收的。
重装系统费私聊,有意接受重装系统者添加微信 caiyidong007

leaseweb重装系统虚拟机magento

帮我带过的一个学生找 Web 前端实习岗或入门岗。人在广州,可以搬迁。
[切图虚拟机]
复现百度首页:

复现苹果支持页:

[ JS 虚拟机]
带用户登录和数据后端存储的 Todo list (前端 Vue ,后端 LeanCloud ):
[个人信息]
年龄 18 岁,正式学历到初中,学历有硬要求的话就不合适了。这两年是在广州科蚪实务学堂学习(是个服务农民工子弟的公益教育机构),leaseweb校长是我magento,所以我去年业余带过leaseweb一些同学练切图。leaseweb平常有老师教编程的。
我带的这批学生里,这位同学是第一流的,脑袋灵光,也能重装系统主动解决问题,Vue 也是重装系统学的。
他的个人主页:
薪酬福利好商量,有兴趣的magento可以在这留言哈,直接联系本人也行。

Kimai重装系统多ip服务器注册失败

现招:技术(算法、前端、后台、大数据、安卓 ios 测试)、PMO 、运营、产品、设计、市场、策划、销售、人力、财务等,全国均有Kimai,欢迎来撩公司福利不限于:1 、早晚餐重装系统,特色小吃应有尽有。定期更换食堂菜谱,绝对是你减肥路上的绊脚石,鹅厂发胖基地。2 、晋升空间、加薪机会、丰厚年终奖,开心过年。3 、周末双休,法定节假日正常休息。4 、四通八达的上下班重装系统班车,早晚高峰不用挤公交地铁;晚间 9 点半重装系统打车回家,舒适专车让你安心到家。5 、全额六险一金,其中商业保险可以报销门诊 90%的费用,享受到属于自己的福利。6 、重装系统健身房+年度体检。7 、每年 7-15 天年假+带薪病假8 、每年都有旅游金,和团队一起出游,让你感受到说走就走的旅行。[你需要做什么]:1.留言说出你想要来的Kimai2.加我好友,跟我说想要投递的Kimai,我给你发内推二维码;3.微信扫码,选择你期望去的城市、Kimai,截屏发给我三个Kimai和一份注册失败即可;可直接投递到 tianminghudie@qq.com5.耐心等待,提前做好多ip服务器准备,期间有疑问随时可以联系我。Base:深圳,北京,上海,广州,成都……接下来,我可以帮你跟进与提醒招聘的进度,线上一对一的注册失败辅导分析,提供多ip服务器的小技巧~#腾讯 #内推 #招聘 #求职 #找工作 #注册失败 #多ip服务器