5中基本数据类型 String,hash,list,set,zset
String
String 类型是包含很多种类的的特殊类型(字节串,整数,浮点),并且是二进制安全的,比如对序列化对象进行存储,图片,进行二进制存储,比如一个简单字符串,数值等。
常规操作
l get和set方法
l setnx( not exist)
l setex(expired) 设置有效期(psetex)
l setrange 替换字符串
l mset和mget 一次性获取或设置多个值
l getset 返回旧值设置新值
l append[name] 字符串追加方法
l strlen[name] 获取字符串长度
计数相关操作
l incr和decr 对某一值进行递增递减(可以用来计数)
l incrby和decrby对某一值进行制定长度递增递减
l incrbyfloat 对浮点数操作
HASH(map)
Hash 类型是String类型的field和value的映射表,它特别适合存储对象,相比较而言,将一个对象存在hash类型里比存String类型里占用内存空间更少,并方便存储整个对象,每个hash可以存4294967295个键值对。
l hset和hget
l hsetnx
l hincrby和hdecrby 递增和递减
l hexists 存在返回,不存在返回0
l hlen 返回集合所有键数值
l hdel 删除制定field
l hkeys 返回所有字段
l hvals 返回所有value值
l hgetall 返回所有key和value
list
List 类型是链表结构的集合,常用于存储任务信息,最近浏览过的文章,常用联系人,主要有pop和push功能,list是一个双端链表,即可以作为栈 ,也可以作为队列,
一个队列最多可以包含4294967295个元素,就是每个列表超过40亿个元素。
l lpush 从头部加元素(栈)
l rpush 从尾部加元素(队列)
l linsert 插入元素
l lset 将指定下标的元素替换掉
l lrem 删除元素 返回删除个数
l ltrim 保留制定key 的值范围内的数据
l lpop 从list头部删除元素,并返回删除元素
l rpop 从list尾部删除元素,并返回删除元素
l rpoplpush 从尾部删除元素 在从头部加入元素(内部循环)
l lindex 返回key的list中index位置元素
l llen返回元素个数
阻塞命令
set
set集合是String 类型的无序集合不可以有重复元素,set是通过hashtable实现的,对集合我们可以去交集,并集,差集。(也是可以储存40多亿个成员)
l sadd 添加
l srem 删除
l spop 随机返回删除的key(弹出一个元素)
l sdiff 返回俩集合不同元素
l sdiffstore 返回不同元素存在另一个集合
l smembers 查看集合元素
l smove 从一个set集合移到另一个set集合中(相当于剪切复制)
l scard 查看集合里元素个数
l srandmember 随机返回一个元素
l sismember 判断某元素是否为集合中元素(
返回1代表是集合中元素,返回0则不是)
ZSet
ZSet 有序集合 (有序且唯一)内部是hash表和跳跃表实现的
l zadd 添加
l zrem 删除
l zincrby 以制定值去自动减少或增加
l zrangebyscore找到制定区间范围的数据
l zremrangebyrank删除1到1
l zremrangebyscore删除制定需要
l zcard 返回集合里所有元素个数
l zcount 返回集合中score在给定区间的数量
应用场景
排行榜
高级特性
l 返回满足的所有键keys*(可以模糊匹配)
l exists 是否存在制定key
l 设置摸个key的过期时间,使用ttl查看剩余时间
l persist取消过期时间
l select 选着数据库 数据库为0到15(一个16个库)默认0
l move【key】 [数据库下标] 将当前数据中的key转移到其他数据库中
l randomkey 随机返回数据库里的一个key
l rename 重命名key
l echo 打印命令
l dbsize 查看数据库key数量
l info 获取数据库信息
l config get 返回相关配置信息
l config get* 返回所有配置
l flushdb 清空当前数据库
l flushall 清空所有数据库
发布订阅
redis 提供简单发布订阅功能
l 使用subscribe【频道】 进行订阅监听。
l 使用publish[频道][发布内容]进行发布消息的广播
模式其实就是一个分组
应用场景
消息队列
场景:日志做异步处理,
数据持久化
redis是一个支持持久化的内存数据库,也就是redis经常将内存中的数据同步到硬盘来保证持久化
l snapshotting (快照) 默认方式 将内存中数据以快照方式写入二进制文件中,默认dump.rdb
l append-only file(缩写 aof)的方式---推荐,由于快照方式在一定时间间隔做一次,有可能发生数据丢失情况,aof方式比快照有更好的持久化性,
事物
redis的事物不能保证同时成功或失败进行提交回滚。
redis通过pipeline提升吞吐量
排序
redis命令的sort可以对列表键,集合键或有序集合键的值进行排序
sort<key> 命令的实现
这个命令可以对一个包含数字值的键key进行排序
sort<key> ALPHA
对一个包含三个字符串值的集合键进行排序(按字典序)
ASC和DESC选项的实现
默认sort命令执行排序,按值从小到大大排序
相反的desc可以执行降序排序,按值从大到小排序
BY选项的实现
默认,sort使用被排序键包含的所有元素作为排序的权重,元素本身决定元素在排序之后所处位置
使用by选项,可以指定某些字符串,或某hash键所包含的域来作为元素的权重,对一个键进行排序
例 我们按水果价格对水果排序
带有ALPHA选项的by选项的实现
by选项默认假设权重键保存的值为数字值,如果保存的是字符串,那么就需要ALPHA和by一起使用。
limit选型实现
默认sort会返回所有排序的元素给客户端
limit <offset> <count>
offset:跳过已排序数量
count:跳过后,返回已排序数量
get选项实现
默认sort会返回所有排序键本身所包含元素给客户端
但get 可以查找并返回某些键值
store 选项实现
默认不保存返回结果
慢查询日志
用于记录执行时间超过给定时间的命令请求,可以用来监视和优化查询速度
hyperloglog
应用:搜索日志
GEO地理信息存储
找方原多少里内的所有用户
以用户为中心点
lua脚本与redis
redis原子性操作 针对一个jvm下 多线程也可以实现原子性,如果,程序是分布式部署,在多jvm下同样的redis操作无法保证原子性操作,当然我们可以使用分布式锁,来保证原子性操作,
lua脚本与redis结合使用可以实现原子性操作(主要实现了redis复合性操作是原子性的,lua脚本同一时间点肯定只有一个单线程能访问我们的lua脚本),避免多线程并发时出现数据的不一致的问题,例如我们简单抢票,抢红包等场景
lua 是一个小巧的脚本语言。是由c编写的 代码简洁优美,几乎所有操作系统平台都可以编译,运行,一个完整的lua解释器不过200k,在目前所有脚本引擎中,lua的速度最快,这一切都决定了lua是作为嵌入式脚本的最佳选择。
带参数执行
Cjson 是lua实现解析json数据类型的工具类
分布式锁
为什么用redis实现分布式锁,分布式锁实现方案有很多,大多底层原理都是相同的,但是当我们系统已经非常复杂了,引入过多的中间件,会让我们系统更加复杂,所以我们就好是在已有的中间件上完成此事,(需要缓存的项目,80%都会用到redis),所以我们用redis实现分布式锁。
在释放锁时有问题:
当两个用户val相同,或同一用户释放了两次锁,那么第一次删除了,第二次就会报null,
所以我们用lua规避这个问题(就不会出现时间窗口问题)
当然用redission 实现分布式锁 使用第三方实现好的功能就是会比自己实现的在效率和安全性上号很多。
java如何使用lua脚本
我们可以使用redis加lua的方式,实现一个完整的事物,保证事物的原子性
我们使用jedis下的scriptload方法(脚本加载)把我们写好的脚本加载到redis内存中(注意是内存每次重启则失败)。
scriptload方法会返回一个索引key,我们只需要通过索引key就可以找到我们之前放到redis中的脚本。
调用evalsha方法,传入key以及操作键,参数值,进行返回
实战
1.需求分析
抢红包的场景有点像秒杀,但是要比秒杀简单点。
因为秒杀通常要和库存相关。而抢红包则可以允许有些红包没有被抢到,因为发红包的人不会有损失,没抢完的钱再退回给发红包的人即可。
另外像小米这样的抢购也要比淘宝的要简单,也是因为像小米这样是一个公司的,如果有少量没有抢到,则下次再抢,人工修复下数据是很简单的事。而像淘宝这么多商品,要是每一个都存在着修复数据的风险,那如果出故障了则很麻烦。
redis 实现秒杀系统
当插入的秒杀请求数达到上限时,停止所有后续插入。 后台启动多个工作线程,使用 LPOP key 读取秒杀成功者的用户id,进行后续处理。 或者使用LRANGE key start end命令读取秒杀成功者的用户id,进行后续处理。 每完成一条秒杀记录的处理,就执行INCR key_num。一旦所有库存处理完毕,就结束该商品的本次秒杀,关闭工作线程,也不再接收秒杀请求。
附近的人
思路
Ø 用户点击附近的人,移动端首先获取用户当前位置(根据gps) geoadd(精度,维度,名字)
以当前用户为中心 找方圆5公里以内的所有用户,所以有附近人列表(按距离排序)
Ø 根据georadiusbymember
根据当前用户坐标查找方圆5公里内的所有用户
根据用户的坐标信息呈现用户之间的距离
SSDB与redis
目前对于互联网公司不使用Redis的很少,Redis不仅仅可以作为key-value缓存,而且提供了丰富的数据结果如set、list、map等,可以实现很多复杂的功能;但是Redis本身主要用作内存缓存,不适合做持久化存储,因此目前有如SSDB、ARDB等,还有如京东的JIMDB,它们都支持Redis协议,可以支持Redis客户端直接访问;而这些持久化存储大多数使用了如LevelDB、RocksDB、LMDB持久化引擎来实现数据的持久化存储;京东的JIMDB主要分为两个版本:LevelDB和LMDB,而我们看到的京东商品详情页就是使用LMDB引擎作为存储的,可以实现海量KV存储;当然SSDB在京东内部也有些部门在使用;另外调研过得如豆瓣的beansDB也是很不错的。
LevelDb 的特点
1. 首先,LevelDb是一个持久化存储的KV系统,和Redis这种内存型的KV系统不同,LevelDb不会像Redis一样狂吃内存,而是将大部分数据存储到磁盘上。
2. 其次,LevelDb在存储数据时,是根据记录的key值有序存储的,就是说相邻的key值在存储文件中是依次顺序存储的,而应用可以自定义key大小比较函数,LevelDb会按照用户定义的比较函数依序存储这些记录。
3. 再次,像大多数KV系统一样,LevelDb的操作接口很简单,基本操作包括写记录,读记录以及删除记录。也支持针对多条操作的原子批量操作。
4. 另外,LevelDb支持数据快照(snapshot)功能,使得读取操作不受写操作影响,可以在读操作过程中始终看到一致的数据。
5. 除此外,LevelDb还支持数据压缩等操作,这对于减小存储空间以及增快IO效率都有直接的帮助。
6. LevelDb性能非常突出,官方网站报道其随机写性能达到40万条记录每秒,而随机读性能达到6万条记录每秒。总体来说,LevelDb的写操作要大大快于读操作,而顺序读写操作则大大快于随机读写操作。
Memtable:
对应Leveldb中的内存数据,LevelDB的写入操作会直接将数据写入到Memtable后返回。读取操作又会首先尝试从Memtable中进行查询,允许写入和读取。当Memtable写入的数据占用内存到达指定数量,则自动转换为Immutable Memtable,等待Dump到磁盘中,系统会自动生成新的Memtable供写操作写入新数据。
Log文件
当应用写入一条Key:Value记录的时候,LevelDb会先往log文件里写入,成功后将记录插进Memtable中,这样基本就算完成了写入操作,Log文件在系统中的作用主要是用于系统崩溃恢复而不丢失数据,假如没有Log文件,因为写入的记录刚开始是保存在内存中的,此时如果系统崩溃,内存中的数据还没有来得及Dump到磁盘,所以会丢失数据(Redis就存在这个问题)。
因为一次写入操作只涉及一次磁盘顺序写和一次内存写入,所以这是为何说LevelDb写入速度极快的主要原因。
Immutable Memtable
当Memtable插入的数据占用内存到了一个界限后,需要将内存的记录导出到外存文件中,LevleDb会生成新的Log文件和Memtable,Memtable会变为Immutable,为之后向SST文件的归并做准备。顾名思义,Immutable Mumtable不再接受用户写入,只能读不能写入或者删除,同时会有新的Log文件和Memtable生成,LevelDb后台调度会将Immutable Memtable的数据导出到磁盘,形成一个新的SSTable文件。
SST文件
SSTable就是由内存中的数据不断导出并进行Compaction操作(压缩操作,下文会讲到)后形成的,而且SSTable的所有文件是一种层级结构,第一层为Level 0,第二层为Level 1,依次类推,层级逐渐增高,这也是为何称之为LevelDb的原因。
redis集群模式
u 第一种:使用主从复制的方式
u 第二种:使用高可用哨兵、Keepalived的方式
u 第三种:集群方式或第三方支持如codis集群等技术
redis数据库设计
我们需要把关系型数据库变成键值对存储
1.球队的存储:
Key的设计:在key中我们可以使用list来存储所有的球队的id
List中的key
结构类型 | Key | value |
list | team:team_id | 1 2 3 4… |
team:team_id : [1, 2, 3, 4….]
每一支球队的存储使用hash
结果类型 | Key | Key_attr | Value |
hash | team:[team_id值] | name | 公牛 |
hash | team:[team_id值] | addr | 芝加哥 |
hash | team:[team_id 值] | team_id | 101 |
team:101 : [{team_id:101},{name : 公牛},{addr:芝加哥}]
team:102 : [{team_id:102},{name : 骑士},{addr:克利夫兰}]
每一个雇员使用hash
结果类型 | Key | Key_attr | Value |
hash | emp:[emp_id值] | emp_id | 1 |
hash | emp:[emp_id值] | name | 张三 |
hash | emp:[emp_id值] | birthday | 2016-12-12 |
hash | emp:[emp_id值] | gender | 101 |
hash | emp:[emp_id值] | team_id | 101 |
emp:001 : {emp_id:001, name:张三,birthday:2016-12-12,gender:1, team_id:101}
emp:002 : {emp_id:002, name:李四,birthday:2016-12-13,gender:1, team_id:101}
从球队方向关注:是一对多的关系
一对多存储类型 | Key | Value |
list | team:[team_id值]:emp | [001, 002,…..] |
team:101:emp : [001, 002]
多对一关系可以直接在员工的hash结构中来获得一的一端。
从关系型数据库同步到Redis中:
@Test
publicvoid queryTeam() {
SessionFactory factory = HiberUtils.getSessionFactory();
Session session = factory.openSession();
Jedis jedis = RedisUtils.getJedis();
try {
String hql = "from Team";
Query query = session.createQuery(hql);
List<Team> tList = query.list();
for(Team team : tList){
//加入把team信息加入Redis中
jedis.hset("team:"+team.getTeamId(), "name", team.gettName());
jedis.hset("team:"+team.getTeamId(), "loc", team.getLoc());
jedis.hset("team:"+team.getTeamId(), "team_id", team.getTeamId()+"");
//把每一个team_id存储在key为team:team_id中
jedis.lpush("team:team_id", team.getTeamId()+"");
//获得emp集合
Set<Emp> eList = team.getEmpSet();
for(Emp emp : eList){
//把emp信息加入Redis中
jedis.hset("emp:"+emp.getEmpId(), "emp_id", emp.getEmpId()+"");
jedis.hset("emp:"+emp.getEmpId(), "name", emp.getEname()+"");
jedis.hset("emp:"+emp.getEmpId(), "birthday", emp.getBirthday()+"");
jedis.hset("emp:"+emp.getEmpId(), "gender", emp.getGender()+"");
jedis.hset("emp:"+emp.getEmpId(), "team_id", team.getTeamId()+"");
//存储team和temp的一对多的关系
jedis.lpush("team:"+team.getTeamId()+":emp", emp.getEmpId()+"");
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
session.close();
RedisUtils.returnJedis(jedis);
}
查询
@Test
publicvoid queryTeam5() {
Jedis jedis = RedisUtils.getJedis();
List<Team> tList = new ArrayList<Team>();
try {
List<String> teamIds = jedis.lrange("team:team_id", 0, -1);
for(String teamId : teamIds){
//获得球队信息
String tName = jedis.hget("team:"+teamId, "name");
String loc = jedis.hget("team:"+teamId, "loc");
String teamIdStr = jedis.hget("team:"+teamId, "team_id");
Team team = new Team();
team.setLoc(loc);
team.settName(tName);
team.setTeamId(new Integer(teamId));
//获得员工的信息
List<String> empList = jedis.lrange("team:"+teamId+":emp", 0, -1);
Set<Emp> eSet = new HashSet<Emp>();
for(String empId : empList){
//获得每一个员工信息
//String empId = jedis.hget("emp:"+empId, "emp_id");
String name = jedis.hget("emp:"+empId, "name");
String birthday = jedis.hget("emp:"+empId, "birthday");
String gender = jedis.hget("emp:"+empId, "gender");
//String teamId = jedis.hget("emp:"+empId, "team_id");
Emp emp = new Emp();
emp.setEmpId(new Integer(empId));
emp.setEname(name);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
emp.setBirthday(sdf.parse(birthday));
emp.setGender(new Integer(gender));
emp.setTeam(team);
eSet.add(emp);
}
team.setEmpSet(eSet);
tList.add(team);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
RedisUtils.returnJedis(jedis);
}
}