37-缓存预热实现

上一篇文章的时候,我们大概的讲了一下缓存预热的步骤和思路。大概分为六个步骤。这里开始我们说一下大概的实现。

1、将访问流量上报到消息队列中

如果一个电商网站,当顾客去访问商品的时候,我们便可以将这个请求的商品信息上传到mq,其实这一步比较简单,根据自己的代码逻辑,加上上传mq的代码即可。

因为之前我们有个项目的商品详情的请求是通过nginx + lua实现的,并且用来kafka,所以这里就做个简单的。

在nginx这一层,接收到访问请求的时候,就把请求的流量上报发送给kafka

这样的话,storm才能去消费kafka中的实时的访问日志,然后去进行缓存热数据的统计

用得技术方案非常简单,从lua脚本直接创建一个kafka producer,发送数据到kafka

# 下载lua和kafka需要的包,解压之后复制到lua脚本的lib包中
wget https://github.com/doujiang24/lua-resty-kafka/archive/master.zip

yum install -y unzip

unzip lua-resty-kafka-master.zip

cp -rf /usr/local/lua-resty-kafka-master/lib/resty /usr/hello/lualib

# 重启 nginx
nginx -s reload

# 修改lua脚本
local cjson = require("cjson")  
local producer = require("resty.kafka.producer")  

local broker_list = {  
    { host = "192.168.31.187", port = 9092 },  
    { host = "192.168.31.19", port = 9092 },  
    { host = "192.168.31.227", port = 9092 }
}

local log_json = {}  
log_json["headers"] = ngx.req.get_headers()  
log_json["uri_args"] = ngx.req.get_uri_args()  
log_json["body"] = ngx.req.read_body()  
log_json["http_version"] = ngx.req.http_version()  
log_json["method"] =ngx.req.get_method() 
log_json["raw_reader"] = ngx.req.raw_header()  
log_json["body_data"] = ngx.req.get_body_data()  

local message = cjson.encode(log_json);  

local productId = ngx.req.get_uri_args()["productId"]

local async_producer = producer:new(broker_list, { producer_type = "async" })   
local ok, err = async_producer:send("access-log", productId, message)  

if not ok then  
    ngx.log(ngx.ERR, "kafka send err:", err)  
    return  
end

两台机器上都这样做,才能统一上报流量到kafka


#  在kafka中构建topic 和 consumer消费者 
bin/kafka-topics.sh --zookeeper 192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181 --topic access-log --replication-factor 1 --partitions 1 --create

bin/kafka-console-consumer.sh --zookeeper 192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181 --topic access-log --from-beginning

(1)kafka在187上的节点死掉了,可能是虚拟机的问题,杀掉进程,重新启动一下

nohup bin/kafka-server-start.sh config/server.properties &

(2)需要在nginx.conf中,http部分,加入resolver 8.8.8.8;

(3)需要在kafka中加入advertised.host.name = 192.168.31.187,重启三个kafka进程

(4)需要启动eshop-cache缓存服务,因为nginx中的本地缓存可能不在了

2、storm从消息队列中消费数据,实时统计出每个商品的访问次数

当我们确定了数据源之后,便要用到storm,实时计算哪些数据是热门访问的数据,

并且对这些数据进行一个排序,形成一个热门榜。

1.通过storm的在spout的初始化方法中连接数据源获取数据,传递给bolt,

2.在经过一些数据分析后获取到自己需要的数据,并且进行排序形成热榜。

这里我们使用kafka作为拓扑的数据源。

创建一个Storm项目,跟前面文章中一样,引入storm依赖。

  • 1: spout实现,kafka消费数据的spout,spout的数据源配置的是kafka,根据自己的业务来设计数据源。spout的任务主要就算从kafka获取数据,发送给bolt。spout这里设置了一个全局的ArrayBlockingQueue,ArrayBlockingQueue是个阻塞式的队列,有一些阻塞的方法,从kafka不断的获取数据然后存入到队列中,这里用队列的task获取数据,没有获取不到会一直阻塞,知道取到数据。
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;

import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;

import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;
import org.apache.storm.utils.Utils;

/**
 * kafka消费数据的spout
 */
public class AccessLogKafkaSpout extends BaseRichSpout {

	private static final long serialVersionUID = 8698470299234327074L;

	private ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(1000);
	
	private SpoutOutputCollector collector;
	
	@SuppressWarnings("rawtypes")
	public void open(Map conf, TopologyContext context,
			SpoutOutputCollector collector) {
		this.collector = collector;
		startKafkaConsumer();
	}



	@SuppressWarnings("rawtypes")
	private void startKafkaConsumer() {
		Properties props = new Properties();
        props.put("zookeeper.connect", "192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181");
        props.put("group.id", "eshop-cache-group");
        props.put("zookeeper.session.timeout.ms", "40000");
        props.put("zookeeper.sync.time.ms", "200");
        props.put("auto.commit.interval.ms", "1000");
        ConsumerConfig consumerConfig = new ConsumerConfig(props);
		
		ConsumerConnector consumerConnector = Consumer.
				createJavaConsumerConnector(consumerConfig);
		String topic = "access-log";
		
		Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(topic, 1);
        
        Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = 
        		consumerConnector.createMessageStreams(topicCountMap);
        List<KafkaStream<byte[], byte[]>> streams = consumerMap.get(topic);
        
        for (KafkaStream stream : streams) {
            new Thread(new KafkaMessageProcessor(stream)).start();
        }
	}
	
	private class KafkaMessageProcessor implements Runnable {

		@SuppressWarnings("rawtypes")
		private KafkaStream kafkaStream;
		
		@SuppressWarnings("rawtypes")
		public KafkaMessageProcessor(KafkaStream kafkaStream) {
			this.kafkaStream = kafkaStream;
		}
		
		@SuppressWarnings("unchecked")
		public void run() {
			ConsumerIterator<byte[], byte[]> it = kafkaStream.iterator();
	        while (it.hasNext()) {
	        	String message = new String(it.next().message());
	        	try {
					queue.put(message);
				} catch (InterruptedException e) {
					e.printStackTrace();
				} 
	        }
		}
		
	}
	
	public void nextTuple() {
		if(queue.size() > 0) {
			try {
				String message = queue.take();
				collector.emit(new Values(message));  
			} catch (Exception e) {
				e.printStackTrace();
			}
		} else {
			Utils.sleep(100);  
		}
	}
	 
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		declarer.declare(new Fields("message"));  
	}
	
}
  • 2: 从spout中发射的数据到达bolt,第一个bolt这里主要是从kafka的消息体中解析出我们要的数据,然后发送到下一个bolt。
import java.util.Map;

import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;

import com.alibaba.fastjson.JSONObject;

/**
 * 日志解析的bolt
 * @author Administrator
 *
 */
public class LogParseBolt extends BaseRichBolt {

	private static final long serialVersionUID = -8017609899644290359L;

	private OutputCollector collector;
	
	@SuppressWarnings("rawtypes")
	public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
		this.collector = collector;
	}
	
	public void execute(Tuple tuple) {
		String message = tuple.getStringByField("message");  
		JSONObject messageJSON = JSONObject.parseObject(message);
		JSONObject uriArgsJSON = messageJSON.getJSONObject("uri_args"); 
		Long productId = uriArgsJSON.getLong("productId"); 
		
		if(productId != null) {
			collector.emit(new Values(productId));  
		}
	}
	
	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		declarer.declare(new Fields("productId"));   
	}

}
  • 3: 第二个bolt用来记录不同的数据和次数,这里用LRUMap实现。并且在bolt的初始化方法中,使用了while循环,一直在对Map中的信息进行排序。

LRUMap,可以用来做缓存,它采用了LRU(least recently used)算法实现,简单的解释是,当Map达到最大容量后,会优先删除掉最不经常被用到的项。

import java.util.Map;

import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.trident.util.LRUMap;
import org.apache.storm.tuple.Tuple;

/**
 * 商品访问次数统计bolt
 * @author Administrator
 *
 */
public class ProductCountBolt extends BaseRichBolt {

	private static final long serialVersionUID = -8761807561458126413L;

	private LRUMap<Long, Long> productCountMap = new LRUMap<Long, Long>(1000);
	
	@SuppressWarnings("rawtypes")
	public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
		new Thread(new ProductCountThread()).start();
	}
	
	private class ProductCountThread implements Runnable {
		
		public void run() {
			List<Map.Entry<Long, Long>> topnProductList = new ArrayList<Map.Entry<Long, Long>>();   
			
			while(true) {
				topnProductList.clear();
				
				int topn = 3;
				
				for(Map.Entry<Long, Long> productCountEntry : productCountMap.entrySet()) {
					if(topnProductList.size() == 0) {
						topnProductList.add(productCountEntry);
					} else {
						// 比较大小,生成最热topn的算法有很多种
						// 但是我这里为了简化起见,不想引入过多的数据结构和算法的的东西
						// 很有可能还是会有漏洞,但是我已经反复推演了一下了,而且也画图分析过这个算法的运行流程了
						boolean bigger = false;
						
						for(int i = 0; i < topnProductList.size(); i++){
							Map.Entry<Long, Long> topnProductCountEntry = topnProductList.get(i);
							
							if(productCountEntry.getValue() > topnProductCountEntry.getValue()) {
								int lastIndex = topnProductList.size() < topn ? topnProductList.size() - 1 : topn - 2;
								for(int j = lastIndex; j >= i; j--) {
									topnProductList.set(j + 1, topnProductList.get(j));  
								}
								topnProductList.set(i, productCountEntry);
								bigger = true;
								break;
							}
						}
						
						if(!bigger) {
							if(topnProductList.size() < topn) {
								topnProductList.add(productCountEntry);
							}
						}
					}
				}
				
				Utils.sleep(60000); 
			}
		}
		
	}
	
	public void execute(Tuple tuple) {
		Long productId = tuple.getLongByField("productId"); 
		
		Long count = productCountMap.get(productId);
		if(count == null) {
			count = 0L;
		}
		count++;
		
		productCountMap.put(productId, count);
	}

	public void declareOutputFields(OutputFieldsDeclarer declarer) {
		
	}

}

 

3、基于storm+zookeeper完成热门商品列表的分段存储

在storm中,我们计算出了我们的热榜数据列表。接下来是将自己的数据存到zookeeper中。

但是要注意,每次都要将自己的热门列表,写入到自己的taskid的zookeeper中。因为storm同时有多个supervisor节点,每个supervisor有多个worker进程,然后worker下生成executor执行task线程任务。

// 1、将自己的taskid写入一个zookeeper node中,形成taskid的列表
// 2、然后每次都将自己的热门商品列表,写入自己的taskid对应的zookeeper节点
// 3、然后这样的话,并行的预热程序才能从第一步中知道,有哪些taskid
// 4、然后并行预热程序根据每个taskid去获取一个锁,然后再从对应的znode中拿到热门商品列表

接下来看下代码实现,代码实现跟上面我们计算排行榜一样,放在bolt的初始化方法的后面,因为每次计算完之后,我们就需要把排行榜放到zookeeper中。

1:ProductCountBolt.java

@SuppressWarnings("rawtypes")
	public void prepare(Map conf, TopologyContext context, OutputCollector collector) {	
		new Thread(new ProductCountThread()).start();
		
		// 1、将自己的taskid写入一个zookeeper node中,形成taskid的列表
		// 2、然后每次都将自己的热门商品列表,写入自己的taskid对应的zookeeper节点
		// 3、然后这样的话,并行的预热程序才能从第一步中知道,有哪些taskid
		// 4、然后并行预热程序根据每个taskid去获取一个锁,然后再从对应的znode中拿到热门商品列表
        
        this.zkSession = ZooKeeperSession.getInstance();
		this.taskid = context.getThisTaskId();
		initTaskId(context.getThisTaskId());
	}

prepare:对于bolt来说,第一个方法,就是prepare方法,初始化的方法。如果还需要往下一个bolt传数据,那么还要传入OutputCollector,这个也是Bolt的这个tuple的发射器。

2:可以看到我们再bolt的初始化方法中调用了 ZooKeeperSession ,和  initTaskId(context.getThisTaskId()) 方法

  •  initTaskId 方法是用来 将数据存放到zookeeper中
private void initTaskId(int taskid) {
		// ProductCountBolt所有的task启动的时候, 都会将自己的taskid写到同一个node的值中
		// 格式就是逗号分隔,拼接成一个列表
		// 111,211,355
		
		zkSession.acquireDistributedLock();
		
		String taskidList = zkSession.getNodeData();
		if(!"".equals(taskidList)) {
			taskidList += "," + taskid;
		} else {
			taskidList += taskid;
		}
		
		zkSession.setNodeData("/taskid-list", taskidList);  
		
		zkSession.releaseDistributedLock();
}
  • ZooKeeperSession 是分布式锁的具体实现
package com.roncoo.eshop.storm.zk;

import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

/**
 * ZooKeeperSession
 * @author Administrator
 *
 */
public class ZooKeeperSession {
	
	private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
	
	private ZooKeeper zookeeper;

	public ZooKeeperSession() {
		// 去连接zookeeper server,创建会话的时候,是异步去进行的
		// 所以要给一个监听器,说告诉我们什么时候才是真正完成了跟zk server的连接
		try {
			this.zookeeper = new ZooKeeper(
					"192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181", 
					50000, 
					new ZooKeeperWatcher());
			// 给一个状态CONNECTING,连接中
			System.out.println(zookeeper.getState());
			
			try {
				// CountDownLatch
				// java多线程并发同步的一个工具类
				// 会传递进去一些数字,比如说1,2 ,3 都可以
				// 然后await(),如果数字不是0,那么久卡住,等待
				
				// 其他的线程可以调用coutnDown(),减1
				// 如果数字减到0,那么之前所有在await的线程,都会逃出阻塞的状态
				// 继续向下运行
				
				connectedSemaphore.await();
			} catch(InterruptedException e) {
				e.printStackTrace();
			}

			System.out.println("ZooKeeper session established......");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取分布式锁
	 * @param productId
	 */
	public void acquireDistributedLock() {
		String path = "/taskid-list-lock";
	
		try {
			zookeeper.create(path, "".getBytes(), 
					Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
			System.out.println("success to acquire lock for taskid-list-lock");  
		} catch (Exception e) {
			// 如果那个商品对应的锁的node,已经存在了,就是已经被别人加锁了,那么就这里就会报错
			// NodeExistsException
			int count = 0;
			while(true) {
				try {
					Thread.sleep(1000); 
					zookeeper.create(path, "".getBytes(), 
							Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
				} catch (Exception e2) {
					count++;
					System.out.println("the " + count + " times try to acquire lock for taskid-list-lock......");
					continue;
				}
				System.out.println("success to acquire lock for taskid-list-lock after " + count + " times try......");
				break;
			}
		}
	}
	
	/**
	 * 释放掉一个分布式锁
	 * @param productId
	 */
	public void releaseDistributedLock() {
		String path = "/taskid-list-lock";
		try {
			zookeeper.delete(path, -1); 
			System.out.println("release the lock for taskid-list-lock......");  
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public String getNodeData() {
		try {
			return new String(zookeeper.getData("/taskid-list", false, new Stat()));  
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}
	
	public void setNodeData(String path, String data) {
		try {
			zookeeper.setData(path, data.getBytes(), -1);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 建立zk session的watcher
	 * @author Administrator
	 *
	 */
	private class ZooKeeperWatcher implements Watcher {

		public void process(WatchedEvent event) {
			System.out.println("Receive watched event: " + event.getState());
			if(KeeperState.SyncConnected == event.getState()) {
				connectedSemaphore.countDown();
			} 
		}
		
	}
	
	/**
	 * 封装单例的静态内部类
	 * @author Administrator
	 *
	 */
	private static class Singleton {
		
		private static ZooKeeperSession instance;
		
		static {
			instance = new ZooKeeperSession();
		}
		
		public static ZooKeeperSession getInstance() {
			return instance;
		}
		
	}
	
	/**
	 * 获取单例
	 * @return
	 */
	public static ZooKeeperSession getInstance() {
		return Singleton.getInstance();
	}
	
	/**
	 * 初始化单例的便捷方法
	 */
	public static void init() {
		getInstance();
	}
	
}

 

创建节点的方法: 

  • String create(final String path, byte data[], List<ACL> acl, CreateMode createMode)
  • void create(final String path, byte data[], List<ACL> acl, CreateMode createMode, StringCallback cb, Object ctx)
path需要创建的数据节点的节点路径,例如,/zk-book/foo
data[]一个字节数组,是节点创建后的初始内容
acl节点的ACL策略
createMode节点类型,是一个枚举类型,通常有4种可选的节点类型
  • 持久(PERSISTENT)
  • 持久顺序(PERSISTENT_SEQUENTIAL)
  • 临时(EPHEMERAL)
  • 临时顺序(EPHEMERAL_SEQUENTIAL)
cb注册一个异步回调函数。开发人员需要实现StringCallback接口,主要是对下面这个方法的重写:
void processResult(int rc, String path, Object ctx, String name);
当服务端节点创建完毕后,ZooKeeper客户端就会自动调用这个方法,这样就可以处理相关的业务逻辑了
ctx用于传递一个对象,可以在回调方法执行的时候使用,通常是放一个上下文(Context)信息

需要注意几点,无论是同步还是异步接口,ZooKeeper都不支持递归创建,即无法在父节点不存在的情况下创建一个子节点。另外,如果一个节点已经存在了,那么创建同名节点的时候,会抛出NodeExistsException异常。

目前,ZooKeeper的节点内容只支持字节数组(byte[])类型,也就是说,ZooKeeper不负责为节点内容进行序列化,开发人员需要自己使用序列化工具将节点内容进行序列化和反序列化。对于字符串,可以简单地使用“string”.getBytes()生成一个字节数组;对于其他复杂对象,可以使用Hessian或是Kryo等专门的序列化工具来进行序列化。

关于ACL权限控制,如果你的应用场景没有太高的权限要求,那么可以不关注这个参数,只需要在acl参数中传入参数Ids.OPEN_ACL_UNSAFE,这就表明之后对这个节点的任何操作都不受权限控制。

 

获取节点的方法:

  • void getData(String path, boolean watch, DataCallback cb, Object ctx)
  • byte[]  getData(String path, boolean watch, Stat stat)
  • void    getData(String path, Watcher watcher, DataCallback cb, Object ctx)
  • byte[]  getData(String path, Watcher watcher, Stat stat)

path:指定数据节点的节点路径, 
watcher:注册的watcher,一旦注册节点内容有变更,就会向客户端发送通知,该参数允许传入null 
stat:指定数据节点的节点状态信息,用法是在接口中传入一个旧的stat变量,该stat变量会在方法执行过程中,被来自服务端响应的新stat对象替换 
Watch:表明是否需要注册一个watcher 
Cb:注册一个异步回调函数 
Ctx:用于传递上下文信息的对象

 

4、缓存预热代码实现

从zookeeper获取到热榜数据,然后放到缓存

代码实现:

/**
 * 缓存预热线程
 * @author Administrator
 *
 */
public class CachePrewarmThread extends Thread {
	
	@Override
	public void run() {
		CacheService cacheService = (CacheService) SpringContext.
				getApplicationContext().getBean("cacheService"); 
		ZooKeeperSession zkSession = ZooKeeperSession.getInstance();
		
		// 获取storm taskid列表
		String taskidList = zkSession.getNodeData("/taskid-list"); 
		
		if(taskidList != null && !"".equals(taskidList)) {
			String[] taskidListSplited = taskidList.split(",");  
			for(String taskid : taskidListSplited) {
				String taskidLockPath = "/taskid-lock-" + taskid;
				
				boolean result = zkSession.acquireFastFailedDistributedLock(taskidLockPath);
				if(!result) {
					continue;
				}
				
				String taskidStatusLockPath = "/taskid-status-lock-" + taskid;
				zkSession.acquireDistributedLock(taskidStatusLockPath);  
				
				String taskidStatus = zkSession.getNodeData("/taskid-status-" + taskid);
				
				if("".equals(taskidStatus)) {
					String productidList = zkSession.getNodeData("/task-hot-product-list-" + taskid);
					JSONArray productidJSONArray = JSONArray.parseArray(productidList);
					
					for(int i = 0; i < productidJSONArray.size(); i++) {
						Long productId = productidJSONArray.getLong(i);
						String productInfoJSON = "{\"id\": " + productId + ", \"name\": \"iphone7手机\", \"price\": 5599, \"pictureList\":\"a.jpg,b.jpg\", \"specification\": \"iphone7的规格\", \"service\": \"iphone7的售后服务\", \"color\": \"红色,白色,黑色\", \"size\": \"5.5\", \"shopId\": 1, \"modifiedTime\": \"2017-01-01 12:00:00\"}";
						ProductInfo productInfo = JSONObject.parseObject(productInfoJSON, ProductInfo.class);
						cacheService.saveProductInfo2LocalCache(productInfo);
						cacheService.saveProductInfo2ReidsCache(productInfo);  
					}
					
					zkSession.setNodeData(taskidStatusLockPath, "success");   
				}
				
				zkSession.releaseDistributedLock(taskidStatusLockPath);
				
				zkSession.releaseDistributedLock(taskidLockPath);
			}
		}
	}
	
}
package com.roncoo.eshop.cache.zk;

import java.util.concurrent.CountDownLatch;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;

/**
 * ZooKeeperSession
 * @author Administrator
 *
 */
public class ZooKeeperSession {
	
	private static CountDownLatch connectedSemaphore = new CountDownLatch(1);
	
	private ZooKeeper zookeeper;

	public ZooKeeperSession() {
		// 去连接zookeeper server,创建会话的时候,是异步去进行的
		// 所以要给一个监听器,说告诉我们什么时候才是真正完成了跟zk server的连接
		try {
			this.zookeeper = new ZooKeeper(
					"192.168.31.187:2181,192.168.31.19:2181,192.168.31.227:2181", 
					50000, 
					new ZooKeeperWatcher());
			// 给一个状态CONNECTING,连接中
			System.out.println(zookeeper.getState());
			
			try {
				// CountDownLatch
				// java多线程并发同步的一个工具类
				// 会传递进去一些数字,比如说1,2 ,3 都可以
				// 然后await(),如果数字不是0,那么久卡住,等待
				
				// 其他的线程可以调用coutnDown(),减1
				// 如果数字减到0,那么之前所有在await的线程,都会逃出阻塞的状态
				// 继续向下运行
				
				connectedSemaphore.await();
			} catch(InterruptedException e) {
				e.printStackTrace();
			}

			System.out.println("ZooKeeper session established......");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取分布式锁
	 * @param productId
	 */
	public void acquireDistributedLock(Long productId) {
		String path = "/product-lock-" + productId;
	
		try {
			zookeeper.create(path, "".getBytes(), 
					Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
			System.out.println("success to acquire lock for product[id=" + productId + "]");  
		} catch (Exception e) {
			// 如果那个商品对应的锁的node,已经存在了,就是已经被别人加锁了,那么就这里就会报错
			// NodeExistsException
			int count = 0;
			while(true) {
				try {
					Thread.sleep(1000); 
					zookeeper.create(path, "".getBytes(), 
							Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
				} catch (Exception e2) {
					count++;
					System.out.println("the " + count + " times try to acquire lock for product[id=" + productId + "]......");
					continue;
				}
				System.out.println("success to acquire lock for product[id=" + productId + "] after " + count + " times try......");
				break;
			}
		}
	}
	
	/**
	 * 获取分布式锁
	 * @param productId
	 */
	public void acquireDistributedLock(String path) {
		try {
			zookeeper.create(path, "".getBytes(), 
					Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
			System.out.println("success to acquire lock for " + path);  
		} catch (Exception e) {
			// 如果那个商品对应的锁的node,已经存在了,就是已经被别人加锁了,那么就这里就会报错
			// NodeExistsException
			int count = 0;
			while(true) {
				try {
					Thread.sleep(1000); 
					zookeeper.create(path, "".getBytes(), 
							Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
				} catch (Exception e2) {
					count++;
					System.out.println("the " + count + " times try to acquire lock for " + path + "......");
					continue;
				}
				System.out.println("success to acquire lock for " + path + " after " + count + " times try......");
				break;
			}
		}
	}
	
	/**
	 * 获取分布式锁
	 * @param productId
	 */
	public boolean acquireFastFailedDistributedLock(String path) {
		try {
			zookeeper.create(path, "".getBytes(), 
					Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
			System.out.println("success to acquire lock for " + path);  
			return true;
		} catch (Exception e) {
			System.out.println("fail to acquire lock for " + path);  
		}
		return false;
	}
	
	/**
	 * 释放掉一个分布式锁
	 * @param productId
	 */
	public void releaseDistributedLock(Long productId) {
		String path = "/product-lock-" + productId;
		try {
			zookeeper.delete(path, -1); 
			System.out.println("release the lock for product[id=" + productId + "]......");  
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 释放掉一个分布式锁
	 * @param productId
	 */
	public void releaseDistributedLock(String path) {
		try {
			zookeeper.delete(path, -1); 
			System.out.println("release the lock for " + path + "......");  
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public String getNodeData(String path) {
		try {
			return new String(zookeeper.getData(path, false, new Stat())); 
		} catch (Exception e) {
			e.printStackTrace();
		}
		return "";
	}
	
	public void setNodeData(String path, String data) {
		try {
			zookeeper.setData(path, data.getBytes(), -1);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 建立zk session的watcher
	 * @author Administrator
	 *
	 */
	private class ZooKeeperWatcher implements Watcher {

		public void process(WatchedEvent event) {
			System.out.println("Receive watched event: " + event.getState());
			if(KeeperState.SyncConnected == event.getState()) {
				connectedSemaphore.countDown();
			} 
		}
		
	}
	
	/**
	 * 封装单例的静态内部类
	 * @author Administrator
	 *
	 */
	private static class Singleton {
		
		private static ZooKeeperSession instance;
		
		static {
			instance = new ZooKeeperSession();
		}
		
		public static ZooKeeperSession getInstance() {
			return instance;
		}
		
	}
	
	/**
	 * 获取单例
	 * @return
	 */
	public static ZooKeeperSession getInstance() {
		return Singleton.getInstance();
	}
	
	/**
	 * 初始化单例的便捷方法
	 */
	public static void init() {
		getInstance();
	}
	
}
	@RequestMapping("/prewarmCache")
	@ResponseBody
	public void prewarmCache() {
		new CachePrewarmThread().start();
	}

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值