Redis实现高并发分布式序列号
使用Redis实现高并发分布式序列号生成服务
序列号的构成
为建立良好的数据治理方案,作数据掌握、分析、统计、商业智能等用途,业务数据的编码制定通常都会遵循一定的规则,一般来讲,都会有自己的编码规则和自增序列构成。比如我们常见的身份证号、银行卡号、社保电脑号等等。
以某公司产品标识码(代表该产品的唯一编码)的构成为例:
规则定义:商品款号(8位)+颜色号(3位)+号型码(3位) (共14位)
其标识码为:62X19001 001 46A
业务含义为: 2009年男装秋冬季仿毛套西黑色170A版
简单来讲,业务编码是由规则和序列构成,规则是允许定义和编辑的,序列通常要求并发安全。整个序列号生成规则要求读写并发安全。
序列号生成方案
由于Redis的高性能,高并发和数据一致性的保证,以及断电数据不丢失,分布式扩展能力等优势。我们采用Redis存储并持久化序列和业务规则来配置和管理整个序列号的产生。
规则定义举例:前缀+时间(YYYYMMDD)+所使用的序列(指定长度),那么产生的序列号类似于SO20150520000124
具体规则可根据实际业务需求来设计。
实现要求:整个生成过程使用Jedis完成,保证原子事务。并通过压力测试。
尽管规则的配置更适合使用表结构来存储,但为了构建分布式的数据库集群,通常都会采用分库和分表(分片)的方式,在这种要求下,我们将规则的配置和序列都放在Redis,以便于提供独立的全局序列生成服务.而不用担心数据库伸缩带来的影响.

序列号生成服务部署图
序列号实现方案
1. 规则配置管理
在Redis的设计中,要想实现比如
| 
 select * from users where user.location="shanghai"  | 
这样的查询,是没办法通过value进行比较得出结果的。但是可以通过不同的数据结构类型组合来做到这一点。比如如下的数据定义
| 
 users:1 {name:Jack,age:28,location:shanghai} users:2 {name:Frank,age:30,location:beijing} users:location:shanghai [1]  | 
其中users:1 users:2 分别定义了两个用户信息,通过Redis中的hash数据结构,而users:location:shanghai 记录了所有上海的用户id,通过集合数据结构实现。
这样通过两次简单的Redis命令调用就可以实现我们上面的查询。
| 
 Jedis jedis = jedisPool.getResource(); Set<String> shanghaiIDs = jedis.smembers("users:location:shanghai"); //遍历该set //... //通过hgetall获取对应的user信息 jedis.hgetAll("users:" + shanghaiIDs[0]);  | 
通过诸如以上的设计,可以实现简单的条件查询。但是这样的问题也很多,首先需要多维护一个ID索引的集合,其次对于一些复杂查询无能为力(当然也不能期望Redis实现像关系数据库那样的查询,Redis不是干这的)。针对本序列号生成方案,这种方式完全是够用的,可以直接参考本节的代码示例。
如果想更进一步,Redis2.6集成了Lua(Redis是用ANSI C写的,可以想象支持Lua是一件很自然的事),可以通过eval命令,直接在RedisServer环境中执行Lua脚本,就是说可以让你用Lua脚本,对Redis中存储的key value进行操作,这个意义就大了,甚至可以将系统所需的各种业务写成一个个lua脚本,提前加载进入Redis,然后对于请求的响应,只需要调用一个个lua脚本就行。(当然这些操作也完全可以使用Jedis来完成,但显然lua效率更高)
比如,现在我们要实现一个‘所有年龄(age)大于28岁的用户(user)’这样一个查询,那么通过以下的Lua脚本就可以实现
| 
 public static final String SCRIPT = "local resultKeys={};" + "for k,v in ipairs(KEYS) do " + " local tmp = redis.call('hget', v, 'age');" + " if tmp > ARGV[1] then " + " table.insert(resultKeys,v);" + " end;" + " end;" + "return resultKeys;";  | 
执行脚本代码
| 
 Jedis jedis = jedisPool.getResource(); jedis.auth(auth); List<String> keys = Arrays.asList(allUserKeys); List<String> args = new ArrayList<>(); args.add("28"); List<String> resultKeys = (List<String>)jedis.evalsha(funcKey, keys, args); return resultKeys;  | 
注意,以上的代码中使用的是evalsha命令,该命令参数的不是直接用Lua脚本字符串,而是提前已经加载到Redis中的函数的一个SHA索引,通过以下的代码将系统中所有需要执行的函数提前加载到Redis中,通常在自己的系统中维护一个函数哈希表funcTable,后续需要实现什么功能,就从函数表中获取对应功能的SHA索引,通过evalsha调用就行。
| 
 String shaFuncKey = jedis.scriptLoad(SCRIPT);//加载脚本到Redis中,获取sha索引 funcTable.put(funcName_age, shaFuncKey);//添加到系统维护的函数表中  | 
通过以上的方法,便可以使较为复杂的查询放到Redis中去执行,提高效率。
可见,想要将全部业务代码都使用lua脚本来实现的业务系统是可能的,lua脚本等同于关系型数据库中的存储过程或者函数。当然,全部使用lua的开发成本未必不大,毕竟不是关系型数据库,存储思维不同。
代码示例:
| 
 //配置生成规则(CRUD): //假设销售单号生成规则:prefix+time+seq //生成之后的结果类似于:SO20150520023014 //------模拟常规数据库操作------ //添加数据 shardedJedis.hset("rules", "somaster", "name:销售单号,prefix:SO,time:YYYYMMDD,seq:seq_so,seq_len:6"); shardedJedis.hset("rules", "pomaster", "name:采购单号,prefix:PO,time:YYYYMMDD,seq:seq_po,seq_len:6"); shardedJedis.hset("rules", "test", "name:test,prefix:PO,time:YYYYMMDD,seq:seq_po,seq_len:6"); //判断某个值是否存在 System.out.println(shardedJedis.hexists("rules", "test")); // 删除指定的值 System.out.println(shardedJedis.hdel("rules", "test")); // 获取指定的值 System.out.println(shardedJedis.hget("rules", "somaster")); // 获取所有的keys System.out.println(shardedJedis.hkeys("rules")); // 获取所有的values System.out.println(shardedJedis.hvals("rules")); //更新 = 插入同名的key System.out.println("update before:"+shardedJedis.hvals("rules")); System.out.println(shardedJedis.hset("rules", "somaster", "new test somaster")); System.out.println("update after:"+shardedJedis.hvals("rules"));  | 
我示例代码中使用的是hash而不是直接使用key-value来存储,是更优的方案。至此CRUD都能直接满足了,最后,你获取到所有values,需要自己处理分页。
也可以使用list和set组合的方式存储。这种方式是将list index和set key对应起来,根据序号进行分页是容易的,但在每次新增和删除时,都需要修改序号和key的对应关系。
两者相比,使用hash的成本显然更低,也不易出错。
2. 序列号的使用
Redis中对序列的生成早已考虑周到,使用单线程操作序列的方式以保证并发安全,同时,使用也极其简单。更多操作详见官网API
代码示例
| 
 //sequence System.out.println("seq:"+shardedJedis.incr("seq")); System.out.println("seq:"+shardedJedis.incr("seq")); System.out.println("another_seq:"+shardedJedis.incr("another_seq"));  | 
最后,生成序列服务只需要通过对应的规则名,获取规则表达式,解析之后结合序列号,最终生成即可。
并发测试
这里我们使用CyclicBarrier做并发测试,CyclicBarrier会开启指定数量的线程,等待这些线程就绪之后,同时执行测试内容,以达到真实并发的测试目的。
Loadrunner等压力测试工具也能完成测试任务。
| 
 1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
31 
32 
33 
34 
35 
36 
37 
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 
48 
49 
50 
51 
52 
53 
54 
55 
56 
57 
58 
59 
60 
61 
62 
 | 
public class CyclicBarrierTest {    //初始化    JedisPoolTest test = new JedisPoolTest();    public static void main(String[] args) {         int count = 1000;         CyclicBarrier cyclicBarrier = new CyclicBarrier(count);         ExecutorService executorService = Executors.newFixedThreadPool(count);         for (int i = 0; i < count; i++)              executorService.execute(new CyclicBarrierTest().new Task(cyclicBarrier));         executorService.shutdown();         while (!executorService.isTerminated()) {              try {                   Thread.sleep(10);              } catch (InterruptedException e) {                   e.printStackTrace();              }         }    }    public class Task implements Runnable {         private CyclicBarrier cyclicBarrier;         public Task(CyclicBarrier cyclicBarrier) {              this.cyclicBarrier = cyclicBarrier;         }         @Override         public void run() {              try {                   // 等待所有任务准备就绪                   cyclicBarrier.await();                   // 测试内容                // 待测试的url                    String para = "sysTemNo=ERP&seqName=WH-ZONE-ID&iVar=00";                    System.out.println(host + para);                    URL url = new URL(host);                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();                    // connection.setRequestMethod("POST");                    // connection.setRequestProperty("Proxy-Connection", "Keep-Alive");                    connection.setDoOutput(true);                    connection.setDoInput(true);                    PrintWriter out = new PrintWriter(connection.getOutputStream());                    out.print(para);                    out.flush();                    out.close();                    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));                    String line = "";                    String result = "";                    while ((line = in.readLine()) != null) {                        result += line;                    }                    System.out.println(result);//                   System.out.println(test.getJedis().incr("seq"));//                System.out.println(test.getShardedJedis().incr("seq"));              } catch (Exception e) {                   e.printStackTrace();              }         }    }} | 
测试结果:
单台Redis未经任何设置,500并发100% pass,到1000并发时只有67%pass率,此时存在连接超时和被拒的情形。但不存在任何重复号码或丢失号码。500并发数其实已经完全满足我当前系统的要求。考虑到Redis本身可以集群扩展,完全能够应对将来更高的并发需求。
Redis实现高并发分布式序列号的更多相关文章
- 使用Redis实现高并发分布式序列号生成服务
		
序列号的构成 为建立良好的数据治理方案,作数据掌握.分析.统计.商业智能等用途,业务数据的编码制定通常都会遵循一定的规则,一般来讲,都会有自己的编码规则和自增序列构成.比如我们常见的身份证号.银行卡号 ...
 - Redis实现高并发分布式锁
		
分布式锁场景在分布式环境下多个操作需要以原子的方式执行首先启一个springboot项目,再引入redis依赖包: <!-- https://mvnrepository.com/artifa . ...
 - 《Netty Redis Zookeeper 高并发实战》声明
		
疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列 [博客园总入口 ] 这里, 对疯狂创客圈 <Netty Redis Zookeeper 高并发实战> 一书,进行一些必要说明. ...
 - 关于redis的几件小事(五)redis保证高并发以及高可用
		
如果你用redis缓存技术的话,肯定要考虑如何用redis来加多台机器,保证redis是高并发的,还有就是如何让Redis保证自己不是挂掉以后就直接死掉了,redis高可用 redis高并发:主从架构 ...
 - Redis的高并发、持久化、高可用架构设计
		
就是如果你用redis缓存技术的话,肯定要考虑如何用redis来加多台机器,保证redis是高并发的,还有就是如何让Redis保证自己不是挂掉以后就直接死掉了,redis高可用 我这里会选用我之前讲解 ...
 - Nginx与Redis解决高并发问题
		
原文链接:http://bbs.phpchina.com/forum.php?mod=viewthread&tid=229629 第一版产品采用的是Jquery,Nginx,PHP(CI框架) ...
 - nginx+lua+redis构建高并发应用(转)
		
nginx+lua+redis构建高并发应用 ngx_lua将lua嵌入到nginx,让nginx执行lua脚本,高并发,非阻塞的处理各种请求. url请求nginx服务器,然后lua查询redis, ...
 - Java互联网架构-直播互动平台高并发分布式架构应用设计
		
概述 网页HTML 静态化: 其实大家都知道网页静态化,效率最高,消耗最小的就是纯静态化的 html 页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法, ...
 - 不懂这些高并发分布式架构、分布式系统的数据一致性解决方案,你如何能找到高新互联网工作呢?强势解析eBay BASE模式、去哪儿及蘑菇街分布式架构
		
互联网行业是大势所趋,从招聘工资水平即可看出,那么如何提升自我技能,满足互联网行业技能要求?需要以目标为导向,进行技能提升,本文主要针对高并发分布式系统设计.架构(数据一致性)做了分析,祝各位早日走上 ...
 
随机推荐
- zerglurker的c语言教程006——第一功能
			
行,以往的经验教训后,.成员main性能.变数.命名等基本概念应该有一个初步的了解 下面,我们就可以开始我们自己的第一个定义的函数. 仿照头等舱.操作的第二个教训.添加一个新的项目的解决方案Lessi ...
 - LeanCloud获取最近会话列表和获取最后一条聊天记录
			
最近公司有项目需要集成IM聊天功能,领导要求用LeanCloud集成,搞不出来就要背包滚蛋啊,没办法只能硬着头皮搞了. 刚拿到官方提供的demo感觉:嗯,不错.图片语音啥的都有了,但尼玛这还不够啊,还 ...
 - 返璞归真 asp.net mvc (7) - asp.net mvc 3.0 新特性之 Controller
			
原文:返璞归真 asp.net mvc (7) - asp.net mvc 3.0 新特性之 Controller [索引页][源码下载] 返璞归真 asp.net mvc (7) - asp.net ...
 - javascript有用小技巧—实现分栏显示
			
记得给师哥师姐測试考试系统的时候,看到他们的考试页面能够实现隐藏左边的考生信息部分,当时认为好高大上.好人性化. 如今学了javascript,我也能实现这个功能了,以下来显摆一下. 1.页面设计: ...
 - PHPthinking官方论坛
			
PHPthinking官方论坛正式上线一个月!眼下.我们已经有数百个固定用户的.论坛发展迅速,所有份额一些技术贴,我们希望,其他许多用户增加来,创建学习.交流更方便.丰富的内容PHP座谈会! PHPt ...
 - 在linux上创建nfs遇到的问题。
			
我们部署程序时,图片server是单独的一台server,有自己独立的域名.而应用部署在还有一台server上,我们使用一些附件上传工具.比方ajaxfileupload上传附件时是无法跨域訪问的. ...
 - mysql语句在node.js中的写法
			
总结一下mysql语句在node.js中的各种写法,参考了npm网站mysql模块给的实例. 查询 select //1 db.query('select * from tuanshang_users ...
 - Mysql入门到精通数据表的操作
			
变更表 ALTER TABLE tb_name; 1.加入场 ALTER TABLE tb_name ADD 字段名字 字段类型 约束条件 [FIRST/AFTER 字段名称] 1>加入user ...
 - JDBC公共动作类
			
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sq ...
 - hdu 4912 Paths on the tree(lca+馋)
			
意甲冠军:它使树m路径,当被问及选择尽可能多的路径,而这些路径不相交. 思考:贪心,比較忧伤.首先求一下每对路径的lca.依照lca的层数排序.在深一层的优先级高.那么就能够贪心了,每次选择层数最深的 ...