精华内容
下载资源
问答
  • 在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,一个登录用户,一会是登录状态,一会又不是登录状态。...

    https://blog.csdn.net/myplay2010/article/details/50716663

    一,什么负载均衡

    一个新网站是不要做负载均衡的,因为访问量不大,流量也不大,所以没有必要搞这些东西。但是随着网站访问量和流量的快速增长,单台服务器受自身硬件条件的限制,很难承受这么大的访问量。在这种情况下,有二种方案可以选择: 
    1,对单台服务器的硬件进行更新,由双核的变成四核的,内存加大等。 
    2,增加服务器的台数,来分担服务器的负担。以实现增加网络带宽,增加服务器的处理能力的目的。

     

    第一种方法可以理解为纵向发展,这种方法总是有限。 
    第二种方法才是解决问题的正确选择 
    实现负载均衡的方法,大至分为二个方向,一种是用软件来实现负载均衡,另一种是硬件实现负载均衡(包括结合硬件和软件)用软件来实现负载均衡,实现负载均衡的过程,自身也要消耗一些系统资源,响应时间增加。例如:LVS,nginx,haproxy,apache等这些基于应用层的负载均衡软件,适合那些访问量不是特别大的网站。如果像sina,163这样大访量的网站,用硬件来实现负载均衡是最明志的选择。

    负载均衡的算法很多,有根据请求数来进行负载均衡的,有根IP来负载均衡的,有根据流量的等等。我经常会用的二种算法。

    一个是根据请求数 
    a,可以实现各台服务器都能比较平均分担客户的请求,其中一台服务器down掉的话也不会造成不好的影响。 
    b,服务器间的状态要同步,如session,需要其他手段来同步这些状态。

    一个是根据IP 
    a,ip_hash算法可以把一个ip映射到一台服务器上,这样可以解决session同步的问题 
    b,ip_hash也有不好的地方就是,假如其中的一台服务器down掉的话,映射到这台的服务器的用户就郁闷了。 
    c,ip_hash容易导致负载不均衡的情况,现在河蟹政府对google的搜索关键词进行过滤,你会经常发现google打不开,但是过一会就好了。这让那些google的爱好者们郁闷不已,很多用户都到国外找代理去了,狗急跳墙,人急帆樯。如果这样的话,这些代理会被分到同一个服务器,会导致负载不均衡 ,甚至失效。

    二,什么是会话保持,有什么作用

    会话保持是指在负载均衡器上有一种机制,在作负载均衡的同时,还保证同一用户相关连的访问请求会被分配到同一台服务器上。

    会话保持有什么作用呢,举例说明一下 
    如果有一个用户访问请求被分配到服务器A,并且在服务器A登录了,并且在很短的时间,这个用户又发出了一个请求,如果没有会话保持功能的话,这个用户的请求很有可能会被分配到服务器B去,这个时候在服务器B上是没有登录的,所以你要重新登录,但是用户并不知道自己的请求被分配到了哪里,用户的感觉就是登录了,怎么又要登录,用户体验很不好。 
    还有你在淘宝上面买东西,从登录=》拍得东西=》添加地址=》付款,这是一个一系列的过程,也可以理解成一次操作过程,所有这一系列的操作过程都应当由一台服务器完成,而不能被负载均衡器分配到不同的服务器上。

    会话保持都会有时间的限制(映射到固定某一台的服务器除外,如:ip_hash),各种负载均衡工具都会提供这种会话保持时间的设置,LVS,apache等。连php语言都提供了会话保持时间的设定session.gc_maxlifetime会话保持时间的设定要大于session生存时间的设定,这样可以减少需要同步session的情况,但是不能杜绝。所以同步session还是要做的。

    三,session同步

    为什么要进行session同步,说会话保持的时候已经提到了。具体方法请参考web集群时session同步的3种方法

    web集群时session同步的3种方法

    在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上,如果session不同步的话,一个登录用户,一会是登录状态,一会又不是登录状态。所以本文就根据这种情况给出三种不同的方法来解决这个问题: 
    1、利用数据库同步session 
    在做多服务器session同步时我没有用这种方法,如果非要用这种方法的话,我想过二种方法: 
    a,用一个低端电脑建个数据库专门存放web服务器的session,或者,把这个专门的数据库建在文件服务器上,用户访问web服务器时,会去这个专门的数据库check一下session的情况,以达到session同步的目的。 
    b,这种方法是把存放session的表和其他数据库表放在一起,如果mysql也做了集群了话,每个mysql节点都要有这张表,并且这张session表的数据表要实时同步。 
    说明:用数据库来同步session,会加大数据库的负担,数据库本来就是容易产生瓶颈的地方,如果把session还放到数据库里面,无疑是雪上加霜。上面的二种方法,第一点方法较好,把放session的表独立开来,减轻了真正数据库的负担

    2、利用cookie同步session 
    session是文件的形势存放在服务器端的,cookie是文件的形势存在客户端的,怎么实现同步呢?方法很简单,就是把用户访问页面产生的session放到cookie里面,就是以cookie为中转站。你访问web服务器A,产生了session把它放到cookie里面了,你访问被分配到web服务器B,这个时候,web服务器B先判断服务器有没有这个session,如果没有,在去看看客户端的cookie里面有没有这个session,如果也没有,说明session真的不存,如果cookie里面有,就把cookie里面的sessoin同步到web服务器B,这样就可以实现session的同步了。

    说明:这种方法实现起来简单,方便,也不会加大数据库的负担,但是如果客户端把cookie禁掉了的话,那么session就无从同步了,这样会给网站带来损失;cookie的安全性不高,虽然它已经加了密,但是还是可以伪造的。

    3、利用memcache同步session 
    memcache可以做分布式,如果没有这功能,他也不能用来做session同步。他可以把web服务器中的内存组合起来,成为一个"内存池",不管是哪个服务器产生的sessoin都可以放到这个"内存池"中,其他的都可以使用。

    优点:以这种方式来同步session,不会加大数据库的负担,并且安全性比用cookie大大的提高,把session放到内存里面,比从文件中读取要快很多。 
    缺点:memcache把内存分成很多种规格的存储块,有块就有大小,这种方式也就决定了,memcache不能完全利用内存,会产生内存碎片,如果存储块不足,还会产生内存溢出。

    四,总结

    上面三种方法都是可行的 
    第一种方法,最影响系统速度的那种,不推荐使用; 
    第二种方法,效果不错,不过安全隐患一样的存在; 
    第三种方法,个人觉得第三种方法是最好的,推荐大家使用;

    展开全文
  • Zookeeper同步锁

    千次阅读 2014-07-21 11:59:25
    用到的是Zookeeper还只是初步用到了一下同步锁没做进一步的应用 想了解Zookeeper 大家都推荐:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/这个文章 是不错 直接贴代码: ...

    最近在弄分布式

    用到的是Zookeeper还只是初步用到了一下同步锁没做进一步的应用

    想了解Zookeeper 大家都推荐:http://www.ibm.com/developerworks/cn/opensource/os-cn-zookeeper/这个文章 是不错


    直接贴代码:

    package com.zookeeper.test;
    
    import java.io.IOException;
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.TimeUnit;
    
    import org.apache.zookeeper.CreateMode;
    import org.apache.zookeeper.KeeperException;
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooDefs;
    import org.apache.zookeeper.ZooKeeper;
    import org.apache.zookeeper.data.Stat;
    
    public class ZkDistributedLock implements Watcher {
    	private ZooKeeper zk;
    	private String config = "192.168.1.213:2181";
    	private String root = "/locks"; // 锁的根节点
    	private String lockName; // 竞争节点前缀
    	private String waitNode; // 被监听的前一个节点
    	private String newNode; // 当前节点
    	private CountDownLatch latch; // 计数器
    	private int sessionTimeout = 60 * 1000; // zookeeper会话超时时间
    
    	/**
    	 * 创建Zookeeper连接并建立跟目录
    	 */
    	public ZkDistributedLock(String lockName) {
    		this.lockName = lockName;
    		try {
    			zk = new ZooKeeper(config, sessionTimeout, this);
    			Stat stat = zk.exists(root, false);
    			if (stat == null) {
    				// 创建根节点
    				zk.create(root, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,
    						CreateMode.PERSISTENT);
    			}
    		} catch (IOException e) {
    			System.out.println("ZkDistributedLock对象初始化失败,IOException:" + e);
    		} catch (KeeperException e) {
    			System.out.println("ZkDistributedLock对象初始化失败,KeeperException:" + e);
    		} catch (InterruptedException e) {
    			System.out.println("ZkDistributedLock对象初始化失败,InterruptedException:"
    					+ e);
    		}
    	}
    
    	/**
    	 * 监听器动作
    	 */
    	public void process(WatchedEvent event) {
    		if (this.latch != null) {
    			System.out.println("process监听被触发:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+Thread.currentThread().getId());
    			this.latch.countDown();
    		}
    	}
    
    	/**
    	 * 上锁操作
    	 * 
    	 * @return
    	 */
    	public boolean lock() {
    		if (tryLock()) {
    			return true;
    		} else {
    			return waitForLock(waitNode, sessionTimeout);
    		}
    	}
    
    	/**
    	 * 尝试获取锁操作
    	 * 
    	 * @return
    	 */
    	private boolean tryLock() {
    		String splitStr = "_lock_";
    		try {
    			// 创建临时子节点
    			newNode = zk.create(root + "/" + lockName + splitStr, new byte[0],
    					ZooDefs.Ids.OPEN_ACL_UNSAFE,
    					CreateMode.EPHEMERAL_SEQUENTIAL);
    			System.out.println("子节点:" + newNode + "成功建立!");
    			// 取出所有子节点
    			List<String> subNodes = zk.getChildren(root, false);
    			// 取出所有lockName的锁
    			List<String> lockObjNodes = new ArrayList<String>();
    			for (String node : subNodes) {
    				String _node = node.split(splitStr)[0];
    				if (_node.equals(lockName)) {
    					lockObjNodes.add(node);
    				}
    			}
    			Collections.sort(lockObjNodes);
    			if (newNode.equals(root + "/" + lockObjNodes.get(0))) {
    				// 如果是最小的节点,则表示取得锁
    				System.out.println("Thread" + Thread.currentThread().getId()
    						+ newNode + " get lock !");
    				return true;
    			}
    			// 如果不是最小的节点,找到比自己小1的节点
    			String subNewNode = newNode.substring(newNode.lastIndexOf("/") + 1);
    			waitNode = lockObjNodes.get(Collections.binarySearch(lockObjNodes,
    					subNewNode) - 1);
    		} catch (KeeperException e) {
    			System.out.println("尝试获取锁操作失败,KeeperException:" + e);
    		} catch (InterruptedException e) {
    			System.out.println("尝试获取锁操作失败,InterruptedException:" + e);
    		}
    		return false;
    	}
    
    	/**
    	 * 等待前一个节点开锁
    	 * 
    	 * @param lower
    	 * @param waitTime
    	 * @return
    	 */
    	private boolean waitForLock(String lower, long waitTime) {
    		Stat stat;
    		try {
    			stat = zk.exists(root + "/" + lower, true);
    			// 判断比自己小一个数的节点是否存在,如果不存在则无需等待锁,同时注册监听
    			if (stat != null) {
    				System.out.println("Thread " + Thread.currentThread().getId()
    						+ " waiting for " + root + "/" + lower);
    				this.latch = new CountDownLatch(1);
    				this.latch.await(waitTime, TimeUnit.MILLISECONDS);
    				System.out.println(newNode+"节点获得锁!");
    				this.latch = null;
    			}
    			return true;
    		} catch (KeeperException e) {
    			System.out.println("等待前一个节点开锁失败,KeeperException:" + e);
    		} catch (InterruptedException e) {
    			System.out.println("等待前一个节点开锁失败,InterruptedException:" + e);
    		}
    		return false;
    	}
    
    	/**
    	 * 解锁
    	 */
    	public void unlock() {
    		try {
    			System.out.println("子节点:" + newNode + "已解锁!");
    			zk.delete(newNode, -1);
    			newNode = null;
    			zk.close();
    		} catch (InterruptedException e) {
    			System.out.println("解锁异常,InterruptedException:" + e);
    		} catch (KeeperException e) {
    			System.out.println("解锁异常,KeeperException:" + e);
    		}
    	}
    }
    

    还只是初步运用 有什么问题 欢迎指正

    感觉要注意的是 会话超时 下一个锁就会自动启动 因为这情况 我在测试的时候还以为代码bug

    然后还一个创建zk连接的时候

    就算服务器关闭也不会报错

    所以我推测创建zk连接其实只是保存了连接信息没实际访问服务器

    要到访问文件目录的时候才实际连服务器

    我没时间看源码

    不知道这个推测是不是正确

    展开全文
  • 来源:https://log.zvz.im/2016/02/27/PHP-session/   ...PHP session机制也许不是每个人都很清楚,如果你不注意,就会造成程序运行慢的问题。 如果你能了解其背后的机制,且能预判这

    来源:https://log.zvz.im/2016/02/27/PHP-session/ 

     https://ma.ttias.be/php-session-locking-prevent-sessions-blocking-in-requests/

    PHP session 的锁机制也许不是每个人都很清楚,如果你不注意,就会造成程序运行慢的问题。 如果你能了解其背后的机制,且能预判这种机制给你的PHP程序所带来的影响,并且避免它。那么 session 阻塞问题就根本算不上什么问题。

    当你调用 session_start() 时,都发生了什么

    我们使用一个基本的 PHP 配置为例:当你开始一次 PHP 会话时,PHP会在 session.save_path 路径下创建一个普通的文件,默认路径为 /var/lib/php/session 或者 /tmp。所有的 session 数据都保存在这个地方。

    如果你的用户还没有一个 session cookie ,那么 PHP 将产生一个新的 ID,并设置到用户机器的 cookie 中。如果是一个已访问过的用户,那么他会将 cookie 发送给你的 web 服务器,PHP 则会解析它,并且从 session.save_path 路径下加载到相应的 session 数据。
    简而言之,这就是 session_start() 的所做的工作。

    会话锁与并发

    接下来我们举一个稍微完整一点的例子,来我们说明PHP初始化session后,各个场景下所发生的事情。

    Timing PHP Code Linux/Server
    0ms session_start(); 创建文件锁:/var/lib/php/session/sess_$identifier
    15ms SQL查询,for循环,第三方API调用 持有session文件锁
    350ms PHP脚本执行结束 session文件锁被移除

    当你调用session_start()(或者PHP的session.auto_start被设置为true时,该方法会被自动调用),操作系统会锁住session文件。大多数文件锁的实现都是flock,在Linux上,它也用于防止定时任务的重复执行或者其它文件锁定工作。
    在Linux机器上,一个session文件锁看起来就像这样子。

         
    1
    2
         
    $ fuser /var/lib/php/session/sess_cdmtgg3noi8fb6j2vqkaai9ff5
    /var/lib/php/session/sess_cdmtgg3noi8fb6j2vqkaai9ff5: 2768 2769 2770

    fuser报告了3个进程的PID,这些进程要么正持有此文件锁,或者正在等待此文件锁的释放。

         
    1
    2
    3
         
    $ lsof /var/lib/php/session/sess_cdmtgg3noi8fb6j2vqkaai9ff5
    COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
    php-fpm 2769 http_demo 5uW REG 253,1 0 655415 sess_cdmtgg3noi8fb6j2vqkaai9ff5

    lsof可以告知你当前持有文件锁的PID以及指令。
    该session的文件锁会保持到脚本执行结束或者被主动移除(后面会讲到)。这是一个读写锁:任何对session读取都必须等到锁被释放之后。
    锁本身并不是问题。它保护session文件中的数据,防止多个同时写入损毁数据或者覆盖之前的数据。
    但是当第二个并发的PHP执行想要获取同一个PHP会话的时候,就会造成问题了。

    Timing script 1 Linux/Server script 2
    0ms session_start(); script1锁定(flock)文件/var/lib/php/session/sess_$identifier session_start();被调用,但是被锁阻塞。PHP等待锁被移除。
    15ms SQL查询,for循环,第三方API调用 文件锁保持不变。 脚本仍然在等待,啥都不做。
    350ms script1执行结束。 script1持有的文件锁被移除。 script2仍然在等待。
    360ms   script2得到新的文件锁。 script2现在可以执行它的SQL查询,for循环…
    700ms   script2持有的文件锁被移除。 script2执行结束。

    解释一下上面的表格:

    • 当2个PHP文件同时想要开始一个会话时,只有一个能赢且获得锁。另一个则需要等待。
    • 当它等待的时候,不会做任何事情:session_start()阻塞了之后动作的执行。
    • 一旦第一个脚本的锁被移除,第二个脚本在获得锁的同时就可以向后继续执行了。

    在绝大多数场景下,这都使得PHP对于同一个用户来说,表现得像是一系列同步脚本:一个执行完成后执行下一个,没有平行的请求。即使你使用AJAX调用这些PHP脚本也无济于事。
    所以,刚才两个脚本没能同时在350ms左右的时间执行完毕,第一个脚本350ms执行完毕,而第一个脚本则消耗两倍的时长执行了700ms,因为它得等第一个脚本先执行完。

    可选的session处理器:redis,memcache,mysql

    如果你在寻求一个快速的解决方案,觉得“我只需要把session保存在memcached里”,那么你会失望的。默认的memcached配置使用了与之前描述相同的、安全的逻辑:只要有一个PHP使用了sessions那它们就会阻塞。
    如果你正在使用PHP的memcached扩展,你可以将memcached.sess_locking设置为“off”,来避免session锁。该配置项的默认值是“on”,与普通的session处理器一样会阻塞。
    如果你在使用redis,那么你是幸运的,因为redis的session处理器还没有支持锁功能。用redis作为session存储后端,是没有锁的。
    如果你在使用MySQL作为session后端存储(比如Drupal),你会有一个自己的实现:没有一个PHP扩展实现了使用MySQL作为session存储的功能。在你的PHP代码中会有一个函数session_set_save_handler()申明了负责session数据读取和写入的类或者方法。也就是说你的代码实现决定了session是否会产生阻塞。

    PHP session锁:想要解决的问题

    我对于session锁行为的看法看起过于负面了,但实际上我只是提醒你注意它的行为方式。其实锁的存在也它好的一面。
    想象以下没有“session锁”的场景,当两个脚本同时处理同一个session数据时,可能引发错误:

    Timing script 1 script 2
    0ms session_start();session数据被读入到$_SESSION变量中 session_start();session数据被读入到$_SESSION变量中
    15ms 脚本1写入session数据:$_SESSION['payment_id'] = 1; 脚本2写入session数据:$_SESSION['payment_id'] = 5;
    350ms sleep(1); 脚本结束,保存session数据
    450ms 脚本结束,保存session数据

    session中的数据值应该是多少?
    应当是脚本1的所保存的值。因为脚本2所保存的值被脚本1最后所保存的值覆盖了。

    这是一个非常尴尬,而且又很难排查的并发问题。session锁可以防止这种情况发生。
    绝大多数情况下,这是写session数据时才会碰到的问题。如果你有一个PHP脚本只是读取session数据(大多数ajax请求都是),你可以安全地对数据进行多次读取。
    另一方面,如果你有一个长时间运行的脚本,它读取了session数据并且还会修改session数据,而另一个脚本开始执行并且读取到了旧的过时数据 — 这也可能使你的应用出错。

    关闭PHP的会话锁:PHP 5.x 和 PHP 7

    PHP中有一个方法叫做session_write_close()。它的功能如其名:写入session数据,关闭session文件,从而解除了session锁。你在PHP代码中,可以这样使用。

         
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
         
    <?php
    // This works in PHP 5.x and PHP 7
    session_start();
    $_SESSION[ 'something'] = 'foo';
    $_SESSION[ 'yolo'] = 'swag';
    session_write_close();
    // Do the rest of your PHP execution below

    上面的示例代码先开启了session(将session数据读到$_SESSION中),然后写入数据再解除锁。接下来,它就再也不能写入这个session文件了。如果接下来该脚本还在继续操作$_SESSION变量,那么这些变化都不会被保存下来。
    从PHP 7开始,在调用session_start()的时候你可设置额外的选项。

         
    1
    2
    3
    4
    5
         
    <?php
    session_start([
    'read_and_close' => true
    ]);
    ?>

    以上语法等同于:

         
    1
    2
    3
    4
    5
         
    <?php
    session_start();
    session_write_close();
    ?>

    它先读取了session数据,然后立刻释放了锁,这样就不会阻塞其它脚本了。

    展开全文
  • java同步锁的几种实现方式

    千次阅读 2019-10-12 17:07:23
    单机环境下对共享资源的同步访问可以用java提供的api去实现。 一、synchronized关键字 这应该是java开发人员很熟悉的一个关键字了,可以用在代码块和方法上,保证代码或方法的同步运行。 看如下一个简单的代码...

    单机环境下对共享资源的同步访问可以用java提供的api去实现。

    一、synchronized关键字

    这应该是java开发人员很熟悉的一个关键字了,可以用在代码块和方法上,保证代码或方法的同步运行。

    看如下一个简单的代码示例,使用了十个线程,每个线程修改十次资源:

    public class LockTest {
    
    	public static void main(String[] args) {
    		Haha hh = new Haha();
    		CountDownLatch cdl = new CountDownLatch(10);
    		for(int i=0 ;i<10;i++) {
    			new Thread(new Runnable() {
    				@Override
    				public void run() {
    					for(int j=0;j<10;j++) {
    						try {
    							TimeUnit.MILLISECONDS.sleep(100);
    						} catch (InterruptedException e) {
    							e.printStackTrace();
    						}
    						hh.addIndex();
    					}
    					cdl.countDown();
    				}
    			}).start();
    		}
    		try {
    			cdl.await();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("final index=" + hh.getIndex());
    	}
    }
    class Haha{
    	
    	private static int index=0;
    	
    	public void addIndex() {
    		System.out.println(++index);
    	}
    
    	public int getIndex() {
    		return index;
    	}
    }

    以上代码逻辑,我们十个线程总共运行100次addIndex(),期望值最后index=100,实际上最后的打印final index= 经常会小于100,这就是多线程下共享资源使用不同步的结果,利用synchronized将代码同步,修改为:

    public synchronized void addIndex() {
    		System.out.println(++index);
    	}

    或者:

    public void addIndex() {
    		synchronized (this) {
    			System.out.println(++index);
    		}
    	}

    再运行后,final index= 的打印结果就能准确的为100

    以上代码,我们注意,index的定义是个static,也就是说所有的Haha类共享同一个变量,那如果我们多个线程执行多个Haha实例,会不会有问题,我们将上面的代码改造下,将一开始的实例化,放到线程中去实例,也就是说每个线程都有一个Haha实例:

    public class LockTest {
    
    	public static void main(String[] args) {
    		CountDownLatch cdl = new CountDownLatch(10);
    		for(int i=0 ;i<10;i++) {
    			new Thread(new Runnable() {
    				@Override
    				public void run() {
    					Haha hh = new Haha();
    					for(int j=0;j<10;j++) {
    						try {
    							TimeUnit.MILLISECONDS.sleep(100);
    						} catch (InterruptedException e) {
    							e.printStackTrace();
    						}
    						hh.addIndex();
    					}
    					cdl.countDown();
    				}
    			}).start();
    		}
    		try {
    			cdl.await();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		Haha hh = new Haha();
    		System.out.println("final index=" + hh.getIndex());
    	}
    }
    class Haha{
    	
    	private static int index=0;
    	
    	public void addIndex() {
    		synchronized (this) {
    			System.out.println(++index);
    		}
    	}
    
    	public int getIndex() {
    		return index;
    	}
    }

    虽然我们的addIndex()方法有synchronized关键字,但是打印结果仍然不理想,最后的index仍然不为100,这是为什么呢?我们注意到synchronized后面的(this),这个this表示当前实例对象,每个线程的实例中锁不一样,自然不会互相影响,所以这里我们应该改为:

    public void addIndex() {
    		synchronized (Haha.class) {
    			System.out.println(++index);
    		}
    	}

    所以使用synchronized关键字的时候要注意:

    a.对于普通方法,synchronized使用的默认是当前对象实例锁;

    b.对于静态方法,synchronized使用的默认是当前类对象锁;

    c.对于代码块,synchronized使用的锁就是括号中指定的锁,这里要特别注意,如果共享资源是对象实例级别,可以用this,但是如果资源是类的所有对象级别,那么要用全局唯一锁,如Haha.class

    二、ReentrantLock

    ReentrantLock是java.util.concurrent包下提供的一套互斥锁,通过lock和unlock方法手动获取锁和释放锁,代码使用很简单:

    private static Lock l = new ReentrantLock();
    public void addIndex() {
    	l.lock();
    	try {
    		System.out.println(++index);
    	} finally {
    		l.unlock();
    	}
    }

    ReentrantLock的创建根据实际需求判断是对象实例级别还是类级别,合理使用static定义,为了避免因代码异常导致的死锁,我们需要在finally代码块里释放锁。

    相比于synchronized关键字是属于非公平锁,ReentrantLock默认也是非公平锁,但是可以使用公平锁(所有线程按请求锁的顺序获取锁)

    使用公平锁的方式:

    Lock l = new ReentrantLock(false);

    三、ReentrantReadWriteLock

    ReentrantReadWriteLock也是java.util.cncurrent包下提供的一种锁实现,上面提到的synchronized和ReentrantLock都是属于排他锁,任何线程对资源的访问都是同步发生,而ReentrantReadWriteLock读写分开,运行多个线程同时读,但是不允许读写、写写的线程同时访问,大大提高了并发性,由于我也没怎么用过,此处不多说明,希望后续能够详细了解后再整理出来。

    以上同步锁很好的解决单机环境下并发使用资源共享的问题,在分布式系统中,这些锁就无能为力了,此时,我们就需要一个能够实现整个项目中全局占用的锁,也就是我们通常说的分布式锁,我们可以通过以下几个基本原则,来实现非公平锁:

    1.同一时间只能有一个客户端能获取到锁;

    2.要避免死锁;

    3.a线程占用的锁,不能被b线程给解掉;

    4.锁的性能问题以及可用性;

    基于以上的原则,我们可以自己去实现分布式锁,以下是几种锁的实现思路:

    四、利用数据库唯一主键

    可以定义一个只包含唯一主键的表,使用共享资源前,往库里插入一条指定的lock记录,如果插入成功表示获取到锁,插入失败,意味着主键冲突,说明其他线程已经获取锁,释放锁就是删除这条记录。

    先创建一个锁表db_lock,其中lock_name就是锁的名字,具有唯一性,lock_time表示占用锁的时间,用来判断锁是否超时:

    create table db_lock(
        lock_name varchar(100) not null primary key,
        lock_time datetime
    )engine=innodb default charset=utf8;

    以下用mybatis简单实现锁,提供部分代码:

    <mapper namespace="lin.db.test.mybatis.DbLockDao">
      <select id="addLock" parameterType="string">
      	insert into db_lock (lock_name,lock_time) values (#{lock},current_timestamp)
      </select>
      <delete id="delLock" parameterType="string">
      	delete from db_lock where lock_name = #{lock}
      </delete>
    </mapper>
    public class DbLock {
    	
    	private static SqlSessionFactory sqlSessionFactory = null;
    	
    	//引入threadLocal记录当前线程占用锁情况,可以避免当前线程解非自己占用的锁
    	private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    	
    	static {
    		try {
    			InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    			sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	/** 获取锁 */
    	public static boolean lock(String lock) {
    		Objects.requireNonNull(lock);
    		SqlSession sqlSession = null;
    		try {
    			//向数据库插入一条数据,成功表示获取到锁
    			sqlSession = sqlSessionFactory.openSession();
    			DbLockDao dbLockDao = sqlSession.getMapper(DbLockDao.class);
    			dbLockDao.addLock(lock);
    			sqlSession.commit();
    			//获取到锁,把锁记录到threadLocal中
    			threadLocal.set(lock);
    			return true;
    		} catch (Exception e) {
    			//失败表示锁被其他线程获取
    			return false;
    		} finally {
    			if(sqlSession != null) {
    				sqlSession.close();
    			}
    			
    		}
    	}
    	
    	/** 释放锁 */
    	public static void unlock(String lock) {
    		Objects.requireNonNull(lock);
    		//解锁时判断是不是当前线程占用了这个锁
    		if(!lock.equals(threadLocal.get())) {
    			System.out.println(Thread.currentThread().getName() + " 非当前线程占用锁,无法解锁");
    			return;
    		}
    		SqlSession sqlSession = null;
    		try {
    			sqlSession = sqlSessionFactory.openSession();
    			DbLockDao dbLockDao = sqlSession.getMapper(DbLockDao.class);
    			dbLockDao.delLock(lock);
    			sqlSession.commit();
    			//解锁完成把占用锁标识删除
    			threadLocal.remove();
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			if(sqlSession != null) {
    				sqlSession.close();
    			}
    			
    		}
    	}
    
    	public static void main(String[] args) {
    		String lockName = "testLock";
    		for(int i = 0 ;i<3; i++) {
    			new Thread(new Runnable() {
    				
    				@Override
    				public void run() {
    					while(true) {
    						if(lock(lockName)) {
    							System.out.println(Thread.currentThread().getName() + " 获取到锁");
    							try {
    								//获取到锁后开始执行业务逻辑
    								try {
    									TimeUnit.SECONDS.sleep(1);
    								} catch (InterruptedException e) {
    									e.printStackTrace();
    								}
    							}finally {
    								//释放锁,记得要在try-finaly里释放,避免死锁
    								System.out.println(Thread.currentThread().getName() + " 释放了锁");
    								unlock(lockName);
    							}
    							break;
    						}
    					}
    					
    				}
    			}).start();
    		}
    	}
    }

    结合之前我们提到的几点原则,对比代码:

    1.利用了数据库主键冲突来保证同一时间只有一个客户端能占用锁;

    2.try-finally代码块中释放锁,避免死锁,不过当线程中途突然停止后,仍然会有死锁,所以库里记录了时间字段,我们可以在数据库部署一个监控脚本,去定时释放锁,或者其他大家能想到的办法,只要保证锁不会一直被占用;

    3.代码中ThreadLocal相关的代码片段就是用来保证当前客户端不能解锁非当前客户端占用的锁;

    4.至于可用性,就靠数据库本身的可用性了。

    一般来说,不会用关系型数据库来实现锁的,频繁访问数据库性能上会比较差,但是我们可以学习这种锁设计的思想。

    五、利用redis实现分布式锁

    redis相比关系型数据库,性能上很快,用它来实现锁,更方便简单,redis的集群也能保证很好的可用性。

    以下上相关代码片段供参考:

    public class LockUtil {
    	
    	private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    	
    	private static final int DEFAULT_TIMEOUT_SECONDS = 60; //锁失效时间60秒
    	
    	private static final String SET_IF_NOT_EXIST = "NX";
    	private static final String SET_EXPIRE_SECOND = "EX";
    	private static final String SET_RESULT_OK = "OK";
    
    	/**
    	 * 获取锁
    	 * @param lock
    	 * @return
    	 */
    	public static boolean lock(String lock) {
    		return lock(lock, DEFAULT_TIMEOUT_SECONDS);
    	}
    	
    	/**
    	 * 获取锁
    	 * @param lock
    	 * @param lockTimeOutSeconds 锁超时时间,单位秒
    	 * @return
    	 */
    	public static boolean lock(String lock,int timeOutSeconds) {
    		Objects.requireNonNull(lock);
    		String owner = UUID.randomUUID().toString();
    		String r = RedisClusterClient.getClient().set(lock, owner, SET_IF_NOT_EXIST, SET_EXPIRE_SECOND, timeOutSeconds);
    		if( SET_RESULT_OK.equals(r) ) {
    			threadLocal.set(owner);
    			return true;
    		}
    		return false;
    	}
    	
    	/**
    	 * 释放锁
    	 * @param lock
    	 */
    	public static void unLock(String lock) {
    		Objects.requireNonNull(lock);
    		String owner = threadLocal.get();
    		//利用lua脚本实现 判断+删除 的原子操作
    		String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisClusterClient.getClient().eval(script, Collections.singletonList(lock), Collections.singletonList(owner));
    	}
    }


    不管什么工具或API或第三方接口,只要能实现整个分布式系统的全局唯一,理论上我们都可以用来设计分布式锁,思想都是差不多的,比如,我们也可以用zookeeper去实现分布式锁,也可以用memcache去实现,以上代码案例都是简单的非公平锁实现,可以在此基础上实现更复杂的锁机制,当然前提条件是你对其中的原理很熟悉。

     

    展开全文
  • 一个同步 的案例

    2013-01-01 16:18:37
    synchronized (lock) { //旗标 list.add(session); } } public void sessionDestroyed(HttpSessionEvent se) { System.out.println(se.getSession() + "被销毁了"); } public void ...
  • 单块系统:利用Session机制实现。 分布式系统:利用redis缓存机制实现。 单块系统 实现思路: 表单加载后,通过ajax获取token,设置到session,并填写到表单hidden token中 表单提交时,携带token参数 利用aop拦截器校验...
  • 一、会话状态Session  Session用于服务器端状态管理,使用Session之后,每个客户端都可以将实际的数据保存在服务器上,对于每个客户端的数据,将会生成一个对应的唯一的key(保存在客户端)。客户端与服务器端就是...
  • 随着web开发的应用程序越来越来,我么可能会将原有的一...cookie共享等等,但是结余我们很多应用是很久之前开发的,使用session来管理,未考虑扩展,新的应用想集成进来时不免遇到困难,这里提供一种解决方案 同一tomc
  • 缓存和Session:注解redis缓存数据,Spring-session和redis实现分布式session同步,重启服务会话不丢失。 数据同步:基于redis的分布式。 Web安全:实现XSS过滤和CSR过滤。 多系统交互:Dubbo,ActiveMQ多系统交互...
  • session共享

    千次阅读 2011-04-25 10:10:00
    伴随网站业务规模和访问量的逐步发展,...   我们把网站程序分布部署到多台服务器上,而且独立为几个二级域名,由于Session受实现原理的局限(PHP中Session默认以文件的形式保存在本地服务器的硬盘
  • (1)什么是分布式? (2)为什么需要分布式? (3)mysql如何实现分布式? (4)mysql分布式的优点和缺点? 简介 随着并发量的不断增加,单机的服务迟早要向多节点或者微服务进化,这时候原来单机模式下...
  • session创建时间和保存位置;session如何在一个会话中的多次请求保持不变,即如何识别是一次会话的多个请求;session在分布式环境中如何保持和共享;session分布式环境中使用遇到的问题解决。
  • 分布式Session共享解决方案

    万次阅读 2018-08-26 20:10:54
    Session是服务器用来保存用户操作的一系列会话信息,由Web容器进行管理。单机情况下,不存在Session共享的情况,分布式情况下,如果不进行Session共享会出现请求落到不同机器要重复登录的情况,一般来说解决Session...
  • 【Consul】Consul架构-Session会话

    千次阅读 2016-09-25 15:02:55
    Consul提供session会话机制——可以用于构建分布式session可以绑定到节点、健康检查、KV数据
  • 判断session是否过期

    千次阅读 2015-01-25 15:08:25
    判断session是否过期 ,做项目时发现 session.getLastAccessedTime() 抛出异常,session已过期但还是调用了该方法
  • session简介.doc

    2007-06-18 17:03:57
    session简介.doc
  • Session_flush

    千次阅读 2009-09-13 12:42:00
    *SessionFlush:*session flush方法主要做了两件事: 清理临时记录 执行sql*session在什么情况下执行flush 默认在事务提交时(commit) 显示的调用flush 在执行查询前,如:iteratehibernate按照save(insert),...
  • 会话状态Session

    千次阅读 2016-04-13 10:09:01
    会话状态Session 一、会话状态Session  Session用于服务器端状态管理,使用Session之后,每个客户端都可以将实际的数据保存在服务器上,对于每个客户端的数据,将会生成一个对应的唯一的key(保存在客户端)。...
  • SpringBoot 实现Spring session共享

    千次阅读 2020-02-16 17:40:14
    session共享是什么? 1)HttpSession是通过Servlet容器进行创建和管理的。在单服务环境中,通过Http请求创建的Session信息是存储在Web服务器内存中的,如Tomcat、Jetty等。 2)现在很多的服务器都采用分布式集群的...
  • 在默认情况下,session对象在关闭浏览器后并不是立刻被销毁,因此,为了考虑系统的安全性,在用户退出时,需要即刻清除session对象,防止他人盗用session对象中的信息。清除session信息主要有两种方式,一种是遍历的...
  • Hibernate Session方法集

    千次阅读 2011-11-17 14:19:59
    Hibernate session的方法全集 方法摘要 Transaction beginTransaction () 开始一个工作单位,并返回值相关的事务对象。 Session.LockRequest buildLockRequest ( ...
  • Spring事务传播机制和数据库隔离级别 在标准SQL规范中定义了4个事务隔离级别,不同隔离级别对事务处理不同 。...该隔离级别可以通过 “排他写”实现。事务隔离的最低级别,仅可保证不读取物理损坏的数据。与READ
  •  * 从Dao得到的ID相同的两个Bean,就会发生Hibernate的Session同步,保持Session中仅仅有一个Bean;  * 因此,如果改动一个引用的ID值,Hibernate就会报错,因为这两个Bean都在Session中却ID不同;
  • Tomcat8源码分析系列-Session源码解析

    千次阅读 2018-03-29 00:29:35
    在 web 开发中,我们经常会用到 Session 来保存会话信息,包括用户信息、权限信息,等等。在这篇文章中,我们将分析 tomcat 容器是如何创建 session、销毁 session,又是如何对 HttpSessionListener 进行事件通知 ...
  • session共享原理及实现共享

    千次阅读 2018-05-28 15:14:15
    比如用户登录邮箱后,接下来要收邮件、写邮件,总不能每次操作都让用户输入用户名和密码吧,为了解决这个问题,session的方案就被提了出来,事实上它并不是什么新技术,而且也不能脱离http协议以及任何现有的web技术...
  • ZooKeeper session管理

    千次阅读 2017-11-17 14:15:35
    ZooKeeper的每个session都会被分配一个64-bit数字来标识,称为session id,被分配到客户端,如果客户端重连到一个不同的ZooKeeper服务端,该session id将被发送到该服务端。为了保障安全,服务端会为该session id...
  • 关于php开发中session的优化

    千次阅读 2014-05-05 11:09:03
    问题:如果要大量的session,如何提高
  • 原系统是在PHPCMS基础上进行开发的,...为了实现Session共用,需同步两套框架Session体系,即:同样的Session存储介质,同样的Session_id,同样的Session.cookie_domain,功能同样的open ,read ,write ,close ,desto
  • IIS6,SESSION失效的解决

    千次阅读 2011-06-08 11:52:00
    IIS6,SESSION超时时间过短的解决ASP(Active Server Pages)技术的Session对象用于存储用户在对话期间的私有信息。当前用户的Session对象中定义的变量和对象能在页面之间共享,但是不能为应用中其他用户所访问,因此...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 52,535
精华内容 21,014
关键字:

session加同步锁