使用es实现轻量级分布式锁

1.前言

一般来说,实现分布式锁的方式有哪几种?

一:Redisson实现

二:ZK实现

这两种实现网上的实现是千篇一律,在本文就不做过多的讲解了

其它方式好像没有了,真的是这样么?

答案是否定的,今天我就给大家分享一个新的思路,使用es实现一个分布式锁,分布式锁其本质就是在一个分布式环境下的一个共享变量的(标志位)的获取,哪个线程先获取到了这个标志位就获得了锁,反之,就只能重试轮询等待,类似于java中的CAS,只不过是在分布式环境下,因为在分布式环境下是多微服务和多节点的,所以一个服务会部署多个节点,此时在高并发环境下就需要借助分布式锁来保证分布式环境下高并发线程操作的安全性,所以才会有分布式 锁的这个玩意。


2.实现

废话不多说,直接上代码,关于es怎么整合这里就不做过多的讲解,之前的文章中也有分享,所以这个步骤忽略

LockUtils

package xxxx.utils;
import lombok.extern.slf4j.Slf4j;import org.elasticsearch.action.delete.DeleteRequest;import org.elasticsearch.action.delete.DeleteResponse;import org.elasticsearch.action.index.IndexRequest;import org.elasticsearch.action.index.IndexResponse;import org.elasticsearch.action.search.SearchRequest;import org.elasticsearch.action.search.SearchResponse;import org.elasticsearch.client.RequestOptions;import org.elasticsearch.client.RestHighLevelClient;import org.elasticsearch.client.indices.CreateIndexRequest;import org.elasticsearch.client.indices.CreateIndexResponse;import org.elasticsearch.client.indices.GetIndexRequest;import org.elasticsearch.common.xcontent.XContentType;import org.elasticsearch.index.query.QueryBuilders;import org.elasticsearch.rest.RestStatus;import org.elasticsearch.search.builder.SearchSourceBuilder;
import java.io.IOException;
/** * 基于es写的轻量级分布式锁,可避免引入redis/zk等其它依赖 **/@Slf4jpublic class LockUtils { /** * id字段名 */ private final static String ID_FIELD = "_id"; /** * 重试等待时间 */ private final static Integer WAIT_SECONDS = 1; /** * 锁的index的名称 */ private final static String LOCK_INDEX = "ee-distribute-lock";
private final static Integer ZERO = 0;
private final static Integer ONE = 1;
private final static String DISTRIBUTED_LOCK_TIP_JSON = "{\"tip\":\"Do not delete unless deadlock occurs\"}";
/** * 尝试获取es分布式锁 * * @param client RestHighLevelClient * @param idValue 相当于key * @param maxRetry 最大重试次数 * @return 是否获取成功 */ public static synchronized boolean tryLock(RestHighLevelClient client, String idValue, Integer maxRetry) { boolean existsIndex = existsIndex(client, LOCK_INDEX); if (!existsIndex) { createEmptyIndex(client, LOCK_INDEX); }
if (maxRetry <= ZERO) { return Boolean.FALSE; }
if (getCount(client, idValue) > ZERO) { try { Thread.sleep(WAIT_SECONDS / maxRetry); } catch (InterruptedException e) { e.printStackTrace(); } return tryLock(client, idValue, --maxRetry); } else { return createLock(client, idValue); } }
/** * 创建锁 * * @param client RestHighLevelClient * @param idValue 相当于key * @return 是否创建成功 */ private static boolean createLock(RestHighLevelClient client, String idValue) { IndexRequest indexRequest = new IndexRequest(LOCK_INDEX); indexRequest.id(idValue); indexRequest.source(DISTRIBUTED_LOCK_TIP_JSON, XContentType.JSON); IndexResponse response; try { response = client.index(indexRequest, RequestOptions.DEFAULT); } catch (IOException e) { e.printStackTrace(); return Boolean.FALSE; } return response.status().equals(RestStatus.CREATED); }
/** * 释放锁 * * @param client RestHighLevelClient * @param idValue 相当于key * @param maxRetry 最大重试次数 * @return 是否释放成功 */ public synchronized static boolean release(RestHighLevelClient client, String idValue, Integer maxRetry) { DeleteRequest deleteRequest = new DeleteRequest(LOCK_INDEX); deleteRequest.id(idValue); if (maxRetry <= ZERO) { return Boolean.FALSE; }
DeleteResponse response; try { response = client.delete(deleteRequest, RequestOptions.DEFAULT); } catch (IOException e) { return retryRelease(client, idValue, --maxRetry); } if (RestStatus.OK.equals(response.status())) { return Boolean.TRUE; } else { return retryRelease(client, idValue, maxRetry); } }
/** * 重试释放 * * @param client RestHighLevelClient * @param idValue id字段值实际未entityClass名,一个entity对应一把锁 * @param maxRetry 最大重试次数 * @return 是否重试成功 */ private static boolean retryRelease(RestHighLevelClient client, String idValue, Integer maxRetry) { try { Thread.sleep(WAIT_SECONDS / maxRetry); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); } return release(client, idValue, --maxRetry); }
/** * 获取个数 * * @param client RestHighLevelClient * @param idValue 相当于key * @return 该id对应的锁的个数, 如果>0 说明已有锁,需重试获取,否则认为无锁 */ private static Integer getCount(RestHighLevelClient client, String idValue) { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices(LOCK_INDEX); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.termQuery(ID_FIELD, idValue)); searchRequest.source(searchSourceBuilder); SearchResponse response; try { response = client.search(searchRequest, RequestOptions.DEFAULT); } catch (IOException e) { e.printStackTrace(); return ONE; } return (int) response.getHits().getTotalHits().value; }
/** * 创建空索引,不含字段 * * @param client RestHighLevelClient * @param indexName 索引名 * @return 是否创建成功 */ public static boolean createEmptyIndex(RestHighLevelClient client, String indexName) { CreateIndexRequest request = new CreateIndexRequest(indexName); CreateIndexResponse createIndexResponse; try { createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT); } catch (IOException e) { log.info("===> distribute lock index has created"); return Boolean.TRUE; } return createIndexResponse.isAcknowledged(); }
/** * 是否存在索引 * * @param client RestHighLevelClient * @param indexName 索引名 * @return 是否存在 */ public static boolean existsIndex(RestHighLevelClient client, String indexName) { GetIndexRequest request = new GetIndexRequest(indexName); try { return client.indices().exists(request, RequestOptions.DEFAULT); } catch (IOException e) { throw new RuntimeException("existsIndex exception indexName:" + indexName + "ex:" + e.getMessage()); } }
}


这个实现是看了easyEs2.x的core的核心包里面的一个工具类的源码扣出来改造了下,是直接可以使用的,easyEs1.x的算是比较稳定的,easyEs2.x还是beta版本,所以不建议使用,最近遇到一个小伙伴使用easyEs2.x在项目中使用了,然后,测试环境可以连接上es服务端,但是后面他们去华为云采购了华为的es,华为的es版本好像最高是7.10的,而easyEs里面依赖的es的版本是7.14,后面部署项目就一直连接不上华为云的es,报错连接被拒绝,由于他们项目赶着上线,任务重,所以只能自己在华为云上搭建了一个es集群,后续在处理这个问题,自己搭建的es可以连上,后面他说又有一个问题翻车了,就是索引的自动托管会有问题,所以还是不建议使用这个开源的项目,最好还是使用官方提供的那个restful的高级客户端,轻量级,也没有过度封装,不像spring-data-elasticsearch这个springBoot提供的集成包,这个也是不建议使用的,过度封装,会有意想不到的bug,如果你要使用easyEs2.x的话官方的建议是你需要做好修改源码的准备,出了问题只能自己搞定了,所以还是自己集成官方restful的高级客户端,关键还是要对es的DSL语法要熟悉和es的原理掌握要666的。对于这种开源项目适合于学习使用,源码写的还是挺好的,别有洞天,代码清秀工整美观,看着就赏心悦目,这不就是我们所追求的梦中情码么,哈哈哈

easyEs官网

https://www.easy-es.cn/pages/7ead0d/#%E7%AE%80%E4%BB%8B

可以使用我之前写的es的启动器,下面的文章里面有分享,看一参看

ES启动器实现及应用间fegin调用fastJson数据解析时间类型转换bug修复

https://mp.weixin.qq.com/s/E7ZckUVvC-v2nUV7AXwEaQ


3.总结

本次分享到此结束,希望我的分享对你有所帮助,请一键三连,么么么哒!


请使用浏览器的分享功能分享到微信等