Redis分布式内存锁:余量扣除示例

来源:互联网 发布:淘宝店铺修改类目 编辑:程序博客网 时间:2024/04/29 11:13

转自21cto


余量扣除,即在高并发,大用户下,每个用户的余量数据频繁发生变化。例如:12306的某车次票的余量,商品库存,短信余量账本等。

针对,此类频繁发生修改的原子类余量对象,采用mysql,oracle等数据,一定会存在操作瓶颈。本文拟采用内存的办法实现,使用redis+Redisson客户端完成。当然,或许可以采用mangodb这类no-sql数据库。

Redisson客户端

https://github.com/mrniko/redisson/wiki

实现redis分布锁的客户端开源项目,redission支持4中连接redis方式,分别为单机,主从, Sentinel , Cluster 集群,并提供以下类库

1.AtomicLong原子操作

2.分布式List

3.分布式Set

4.分布式Map

5.分布式Queue,

6.分布式SortedSet,

7.分布式ConcureentMap

8.分布式Lock

9.分布式CountDownLatch

10. 分布式Publish / Subscribe, HyperLogLog等



余量扣除代码片段

resources/redis.properties

Css代码

  1. #redis部署模式 SingleHost1 MasterSlave 2 Sentinel 3 Cluster 4

  2. redis.deploymentModel=2

  3. redis.hosts=192.168.161.73:6379,192.168.161.129:6379

  4. redis.masterName=mymaster

  5. redis.masteAddress=192.168.161.73:6379



RedissonClient 

Java代码

  1. import java.io.FileNotFoundException;

  2. import java.io.IOException;

  3. import java.util.Properties;

  4. import java.util.concurrent.Future;

  5. import org.redisson.Config;

  6. import org.redisson.Redisson;

  7. import org.redisson.connection.RandomLoadBalancer;

  8. import org.redisson.core.RAtomicLong;

  9. import org.redisson.core.RMap;

  10. /**

  11. *分布式锁客户端

  12. * @author

  13. *

  14. */

  15. publicclass RedissonClient {

  16. privatestatic RedissonClient instance;

  17. private RedissonClient(String filename) throws FileNotFoundException, IOException{

  18. init(filename);

  19. }

  20. publicstatic synchronized RedissonClient getInstance(String filename){

  21. if(instance==null){

  22. try {

  23. instance=new RedissonClient(filename);

  24. }catch (FileNotFoundException e) {

  25. // TODO Auto-generated catch block

  26. e.printStackTrace();

  27. }catch (IOException e) {

  28. // TODO Auto-generated catch block

  29. e.printStackTrace();

  30. }

  31. }

  32. return instance;

  33. }

  34. publicstatic void main(String[] args){

  35. RedissonClient client= RedissonClient.getInstance("resources/redis.properties");

  36. Redisson redisson=client.getSingleClient("ip:6379");

  37. /*

  38. RMap<String, String> map = redisson.getMap("anyMap");

  39. String prevObject = map.put("123", new String());

  40. String currentObject = map.putIfAbsent("323", new String());

  41. String obj = map.remove("123");

  42. map.fastPut("321", new String());

  43. map.fastRemove("321");

  44. Future<String> putAsyncFuture = map.putAsync("321");

  45. Future<Void> fastPutAsyncFuture = map.fastPutAsync("321");

  46. map.fastPutAsync("321", new String());

  47. map.fastRemoveAsync("321");

  48. redisson.shutdown();

  49. */

  50. /**

  51. * Distributed Object storage example

  52. * Redisson redisson = Redisson.create();

  53. RBucket<AnyObject> bucket = redisson.getBucket("anyObject");

  54. bucket.set(new AnyObject());

  55. bucket.setAsync(new AnyObject());

  56. AnyObject obj = bucket.get();

  57. redisson.shutdown();

  58. */

  59. /**Distributed Set example

  60. Redisson redisson = Redisson.create();

  61. RSet<SomeObject> set = redisson.getSet("anySet");

  62. set.add(new SomeObject());

  63. set.remove(new SomeObject());

  64. set.addAsync(new SomeObject());

  65. redisson.shutdown();

  66. **/

  67. final RAtomicLong atomicLong = redisson.getAtomicLong("anyAtomicLong");

  68. //atomicLong.set(1000);//初始化余量为1000,可以通过redis linux客户端设置1000

  69. for(int i=0;i<2000;i++){//开始扣减余量,每次扣1.共计扣2000次。

  70. new Runnable(){

  71. @Override

  72. publicvoid run() {

  73. try {

  74. Thread.currentThread().sleep(5);

  75. }catch (InterruptedException e) {

  76. e.printStackTrace();

  77. }

  78. if(atomicLong.get()==0) {//当余量为0时,系统提示余量不够并退出

  79. System.out.println(" error less than 0");

  80. return ;

  81. }

  82. long r=atomicLong.decrementAndGet();

  83. System.out.println("get "+r);

  84. }

  85. }.run();;

  86. }

  87. }

  88. privatestatic Redisson redisson=null;

  89. public Redisson getClient(){

  90. return redisson;

  91. }

  92. publicvoid init(String filename) throws FileNotFoundException, IOException

  93. {

  94. //RedissonClient client=new RedissonClient();

  95. PropertyReader propReader=PropertyReader.getInstance(filename);

  96. Properties props=propReader.getProperties(filename);

  97. String masterName=props.getProperty("redis.masterName");

  98. String masteAddress=props.getProperty("redis.masteAddress");

  99. int deployment_model=Integer.valueOf(props.getProperty("redis.deploymentModel")).intValue();

  100. String hosts=props.getProperty("redis.hosts");

  101. //redis部署模式 SingleHost 1 MasterSlave 2 Sentinel 3 Cluster 4

  102. switch(deployment_model){

  103. case1:

  104. redisson=getSingleClient(hosts);//单机

  105. break;

  106. case2:

  107. redisson=getMasterSlaveClient(masteAddress,hosts);

  108. break;

  109. case3:

  110. redisson=getSentinelClient(masterName,hosts);

  111. break;

  112. case4:

  113. redisson=getClusterClient(hosts);

  114. break;

  115. }

  116. }

  117. public Redisson getSingleClient(String host){

  118. //Single server connection:

  119. // connects to default Redis server 127.0.0.1:6379

  120. //edisson redisson = Redisson.create();

  121. // connects to single Redis server via Config

  122. Config config =new Config();

  123. config.useSingleServer()

  124. .setAddress(host)

  125. .setConnectionPoolSize(1000)

  126. ;

  127. Redisson redisson = Redisson.create(config);

  128. return redisson;

  129. }

  130. //Master/Slave servers connection:

  131. public Redisson getMasterSlaveClient(String add,String hosts){

  132. Config config =new Config();

  133. String[] hostarr=hosts.split(",");

  134. config.useMasterSlaveConnection()

  135. .setMasterAddress(add)

  136. .setLoadBalancer(new RandomLoadBalancer()) // RoundRobinLoadBalancer used by default

  137. .addSlaveAddress(hostarr)

  138. .setMasterConnectionPoolSize(10000)

  139. .setSlaveConnectionPoolSize(10000);

  140. Redisson redisson = Redisson.create(config);

  141. return redisson;

  142. }

  143. //Sentinel servers connection:

  144. public Redisson getSentinelClient(String masterName,String hosts){

  145. String[] hostarr=hosts.split(",");

  146. Config config =new Config();

  147. config.useSentinelConnection()

  148. .setMasterName(masterName)

  149. .addSentinelAddress(hostarr)

  150. .setMasterConnectionPoolSize(10000)

  151. .setSlaveConnectionPoolSize(10000);

  152. Redisson redisson = Redisson.create(config);

  153. return redisson;

  154. }

  155. //Cluster nodes connections:

  156. public Redisson getClusterClient(String hosts){

  157. Config config =new Config();

  158. config.useClusterServers()

  159. .setScanInterval(2000)// sets cluster state scan interval

  160. .addNodeAddress("127.0.0.1:7000","127.0.0.1:7001")

  161. .setMasterConnectionPoolSize(10000)

  162. .setSlaveConnectionPoolSize(10000);

  163. Redisson redisson = Redisson.create(config);

  164. return redisson;

  165. }

  166. }

余量扣除代码片段2,扣除任意数值。

Java代码

  1. /**

  2. * 扣取现金账本

  3. * @param actid 账户id

  4. * @return

  5. * @throws FileNotFoundException

  6. * @throws IOException

  7. * @throws InterruptedException

  8. */

  9. privateboolean deductCashAccount(String actid,int amount)throws FileNotFoundException, IOException, InterruptedException{

  10. /**扣余额直接操作redis缓存数据库,key由账户ID,-字符,字符串balance组成*/

  11. long start=System.currentTimeMillis();

  12. if(redisson==null) {

  13. logger.error("Redisson is NULL");

  14. returnfalse;

  15. }

  16. String key="CASH_"+actid;

  17. String lock_point="LOCK_CASH_"+actid;

  18. RLock lock=redisson.getLock(lock_point);//获取账户锁对象

  19. logger.info("get lock "+lock_point);

  20. boolean locked=lock.tryLock(10,60, TimeUnit.SECONDS);//尝试锁住账户对象,waitTime第一个参数获取锁超时时间30毫秒,leaseTime第二参数,锁自动释放时间

  21. if(!locked) {

  22. logger.info("cann't get lock ,id="+actid);

  23. returnfalse;

  24. }

  25. //lock.lock();

  26. logger.info("get lock "+lock_point+" ok");

  27. RBucket<Integer> atomicbalance = redisson.getBucket(key);//获取原子余量

  28. boolean result_flag=true;

  29. if(atomicbalance.get()==0) {

  30. logger.error(" error ,balance less than or equal to 0");

  31. result_flag=false;

  32. }

  33. else{

  34. atomicbalance.set(atomicbalance.get().intValue()-amount);//扣除余量

  35. logger.info("balance is "+atomicbalance.get());

  36. result_flag=true;

  37. }

  38. lock.unlock();//解锁

  39. logger.info("debut cash , cost time:"+(System.currentTimeMillis()-start));

  40. return result_flag;

  41. }



实验结论:

本人在一项目中,使用以上两个分布式锁对象,进行了ab压力测试。设置初始余量,将以前代码片,发布为http api服务,ab多线程压力测试扣除余量操作,最终扣除的余量正确。

0 0