1、Redis和nosql简介,api调用
14.1/ nosql介绍
NoSQL:一类新出现的数据库(not only sql),它的特点:
1、 不支持SQL语法
2、 存储结构跟传统关系型数据库中的那种关系表完全不同,nosql中存储的数据都是KV形式
3、 NoSQL的世界中没有一种通用的语言,每种nosql数据库都有自己的api和语法,以及擅长的业务场景
4、 NoSQL中的产品种类相当多:
a) Mongodb 文档型nosql数据库,擅长做CMS系统(内容管理系统)
b) Redis 内存数据库,数据结构服务器,号称瑞士军刀(精巧),只要你有足够的想象力,它可以还给你无限惊喜
c) Hbase hadoop生态系统中原生的一种nosql数据库,重量级的分布式nosql数据库,用于海量数据的场景
d) Cassandra hadoop生态系统中原生的一种分布式nosql数据库,后起之秀
NoSQL和SQL数据库的比较:
1、适用场景不同:sql数据库适合用于关系特别复杂的数据查询场景,nosql反之
2、“事务”特性的支持:sql对事务的支持非常完善,而nosql基本不支持事务
3、两者在不断地取长补短,呈现融合趋势
14.2/ redis介绍
14.2.1 简述
Redis是一个高性能的kv对缓存和内存数据库(存的不像mysql那样的表)
Redis的存储结构就是key-value,形式如下:
注: redis中的value内部可以支持各种数据结构类型,比如可以存入一个普通的string,还可以存list,set,hashmap,sortedSet(有序的set)
14.2.2 redis应用场景
A、用来做缓存(ehcache/memcached)——redis的所有数据是放在内存中的(内存数据库)
B、可以在某些特定应用场景下替代传统数据库——比如社交类的应用
C、在一些大型系统中,巧妙地实现一些特定的功能:session共享、购物车
只要你有丰富的想象力,redis可以用在可以给你无限的惊喜…….
14.2.3 redis的特性
1、redis数据访问速度快(数据在内存中)
2、redis有数据持久化机制(持久化机制有两种:1、定期将内存数据dump到磁盘;2、aof(append only file)持久化机制——用记日志的方式记录每一条数据更新操作,一旦出现灾难事件,可以通过日志重放来恢复整个数据库)
3、redis支持集群模式(容量可以线性扩展)
4、redis相比其他缓存工具(ehcach/memcached),有一个鲜明的优势:支持丰富的数据结构
14.3.Redis的api客户端连接
新建一个maven工程,导入jedis的maven依赖坐标
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.2</version> <type>jar</type> <scope>compile</scope> </dependency> |
然后写一个类用来测试服务器跟客户端的连通性:
public class RedisClientConnectionTest { public static void main(String[] args) { // 构造一个redis的客户端对象 Jedis jedis = new Jedis("pinshutang.zicp.net", 6379); String ping = jedis.ping(); System.out.println(ping); } } |
15、Redis数据功能(String类型的数据处理)
15.1 String类型的数据
(常作为缓存使用)
1/插入和读取一条string类型的数据
redis notrue-centos:6379> set sessionid-0001 "zhangsan" OK redis notrue-centos:6379> get sessionid-0001 "zhangsan" |
2/对string类型数据进行增减(前提是这条数据的value可以看成数字)
192.168.106.80:6379> DECR key (integer) -1 192.168.106.80:6379> DECR key (integer) -2 192.168.106.80:6379> DECR key (integer) -3 192.168.106.80:6379> INCR key (integer) -2 192.168.106.80:6379> INCR key (integer) -1
192.168.106.80:6379> DECRBY key 4 (integer) -5 192.168.106.80:6379> DECRBY key -7 (integer) 2 192.168.106.80:6379> INCRBY key 2 (integer) 4 192.168.106.80:6379> INCRBY key -9 (integer) -5 |
3/一次性插入或者获取多条数据
192.168.106.80:6379> MSET key1 1 key2 2 key3 3 OK 192.168.106.80:6379> MGET key1 key2 key3 1) "1" 2) "2" 3) "3" |
4/在插入一条string类型数据的同时为它指定一个存活期限
setex key seconds value
例如: # bancao这条数据就只会存活10秒钟,过期会被redis自动清除 192.168.106.80:6379> setex bancao 10 testvalue OK 192.168.106.80:6379> get bancao "testvalue" 192.168.106.80:6379> get bancao "testvalue" 192.168.106.80:6379> get bancao (nil) 192.168.106.80:6379> get bancao (nil) |
案例:
其中maven项目的pom文件的内容如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.toto.redis</groupId> <artifactId>redistest</artifactId> <version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.2</version> <type>jar</type> <scope>compile</scope> </dependency> -->
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.0</version> </dependency>
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.3.1</version> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.7</version> <scope>test</scope> </dependency> </dependencies> </project> |
代码:
package cn.toto.redis.client;
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream;
import org.junit.Before; import org.junit.Test;
import com.google.gson.Gson;
import cn.toto.redis.bean.ProductInfo;
import redis.clients.jedis.Jedis;
public class StringStructureData {
private Jedis jedis = null;
@Before public void init() { //这里的是hadoop2可以用ip地址替换 jedis = new Jedis("hadoop2",6379);
}
@Test public void testString() { jedis.set("user02:name", "ruhua"); jedis.set("user03:name", "滑板鞋");
String u02 = jedis.get("user02:name"); String u03 = jedis.get("user03:name");
System.out.println(u02); System.out.println(u03); }
@Test public void testObjectCache() throws Exception { ProductInfo p = new ProductInfo();
p.setName("username"); p.setDescription("备注信息"); p.setCatelog("unknow"); p.setPrice(10.8);
//将对象序列化成字节数组 ByteArrayOutputStream ba = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(ba);
//用对象序列化流来将p对象序列化,然后把序列化之后的二进制数据写到ba流中 oos.writeObject(p);
//将ba流转成byte数组 byte[] pBytes = ba.toByteArray();
//将对象序列化之后的byte数组存到redis的string结构数组中 jedis.set("product:01".getBytes(), pBytes);
//根据key从redis中取出对象的byte数据 byte[] pBytesResp = jedis.get("product:01".getBytes());
//将byte数据反序列化列出对象 ByteArrayInputStream bi = new ByteArrayInputStream(pBytesResp); ObjectInputStream oi = new ObjectInputStream(bi);
//从对象读取流中取出对象 ProductInfo pResp = (ProductInfo) oi.readObject(); System.out.println(pResp); }
@Test public void testObjectToJsonCache() { ProductInfo p = new ProductInfo();
p.setName("ABC"); p.setDescription("刘亦菲专用"); p.setCatelog("化妆品"); p.setPrice(10.8);
//利用gson将对象转成json串 Gson gson = new Gson(); String pJson = gson.toJson(p);
//将json串存入redis jedis.set("prodcut:02", pJson);
//从redis中取出对象的json串 String pJsonResp = jedis.get("prodcut:02");
//将返回的json解析成对象 ProductInfo pResponse = gson.fromJson(pJsonResp, ProductInfo.class);
//显示对象的属性 System.out.println(pResponse); } } |
16、List数据结构(及Java调用处理)
16.1 List图示
16.2 List功能演示
#从头部(左边)插入数据 redis>LPUSH key value1 value2 value3 #从尾部(右边)插入数据 redis>RPUSH key value1 value2 value3 #读取list中指定范围的values redis>LRANGE key start end redis> lrange task-queue 0 -1 读取整个list #从头部弹出一个元素 LPOP key #从尾部弹出一个元素 RPOP key #从一个list的尾部弹出一个元素插入到另一个list RPOPLPUSH key1 key2 ## 这是一个原子性操作 例如下面的案例: #从头部(左边)插入数据 192.168.106.81:6379> lpush task task0 task1 task2 (integer) 3 192.168.106.81:6379> lrange task 0 2 1) "task2" 2) "task1" 3) "task0" 192.168.106.81:6379> lrange task 0 -2 1) "task2" 2) "task1" 192.168.106.81:6379> lrange task 0 -1 1) "task2" 2) "task1" 3) "task0" #从尾部(右边)插入数据 192.168.106.81:6379> rpush ids id0 id1 id2 (integer) 3 192.168.106.81:6379> lrange ids 0 -1 1) "id0" 2) "id1" 3) "id2" 192.168.106.81:6379> lpop ids "id0" 192.168.106.81:6379> lpop ids "id1" 192.168.106.81:6379> rpop task "task0" 192.168.106.81:6379> rpoplpush task ids "task1" 192.168.106.81:6379> lrange ids 0 -1 1) "task1" 2) "id2" 192.168.106.81:6379> |
16.3 List的应用案例demo
1 需求描述
任务调度系统:
生产者不断产生任务,放入task-queue排队
消费者不断拿出任务来处理,同时放入一个tmp-queue暂存,如果任务处理成功,则清除tmp-queue,否则,将任务弹回task-queue
2 代码实现
1/生产者
——模拟产生任务
package cn.toto.redis.productAndCustomer;
import java.util.Random; import java.util.UUID;
import redis.clients.jedis.Jedis;
public class TaskProducer { //获取一个redis的客户端连接对象 public static Jedis getRedisConnection(String host,int port) { Jedis jedis = new Jedis(host,port); return jedis; }
public static void main(String[] args) { Jedis jedis = getRedisConnection("hadoop3", 6379);
Random random = new Random(); //生成任务 while(true) { try { //生成任务的速度有一定的随机性,在1-2秒之间 Thread.sleep(random.nextInt(1000) + 1000); //生成一个任务 String taskid = UUID.randomUUID().toString(); //往任务队列"task-queue"中插入,第一次插入时,"task-queue"还不存在 //但是lpush方法会在redis库中创建一条新的list数据 jedis.lpush("task-queue", taskid); System.out.println("向任务队列中插入了一个新的任务:" + taskid); } catch (Exception e) { e.printStackTrace(); } } } } |
2/消费者
——模拟处理任务,并且管理暂存队列
package cn.toto.redis.productAndCustomer;
import java.util.Random;
import redis.clients.jedis.Jedis;
public class TaskConsumer {
public static void main(String[] args) { Jedis jedis = new Jedis("hadoop3",6379); Random random = new Random();
while(true) { try { //从task-queue中取一个任务,同时放入tmp-queue String taskid = jedis.rpoplpush("task-queue", "tmp-queue");
//模拟处理任务 Thread.sleep(1000);
//模拟有成功又有失败的情况 int nextInt = random.nextInt(13); if (nextInt % 7 == 0) {//模拟失败的情况 //在失败的情况下,需要将任务从tmp-queue弹回"task-queue" jedis.rpoplpush("tmp-queue", "task-queue"); System.out.println("----任务处理失败:" + taskid); } else {//模拟成功的情况 //成功的情况下,将任务从"tmp-queue"清除 jedis.rpop("tmp-queue"); System.out.println("任务处理成功:" + taskid); } } catch (Exception e) { e.printStackTrace(); } } } } |
上述机制是一个简化版,真实版的任务调度系统会更加复杂,如下所示:
(增加了一个专门用来管理暂存队列的角色,以便就算消费者程序失败退出,那些处理失败的任务依然可以被弹回task-queue)
17、Hash数据结构
17.1 Hash图示
Redis中的Hashes类型可以看成具有String Key和String Value的map容器
17.2 Hash功能演示
1、往redis库中插入一条hash类型的数据
redis> HSET key field value |
举例:
redis 127.0.0.1:6379> hset user001:zhangsan iphone 6 (integer) 1 redis 127.0.0.1:6379> hset user001:zhangsan xiaomi 7 (integer) 1 redis 127.0.0.1:6379> hset user001:zhangsan meizu 8 (integer) 1 |
在redis库中就形成了这样一条数据:
2、从redis库中获取一条hash类型数据的value
ü 取出一条hash类型数据中所有field-value对
redis 127.0.0.1:6379> hgetall user001:zhangsan 1) "iphone" 2) "6" 3) "xiaomi" 4) "7" 5) "meizu" 6) "8" |
ü 取出hash数据中所有fields
redis 127.0.0.1:6379> HKEYS user001:zhangsan 1) "iphone" 2) "xiaomi" 3) "meizu" |
ü 取出hash数据中所有的value
redis 127.0.0.1:6379> hvals user001:zhangsan 1) "6" 2) "7" 3) "8" |
ü 取出hash数据中一个指定field的值
redis 127.0.0.1:6379> hget user001:zhangsan xiaomi "8" |
ü 为hash数据中指定的一个field的值进行增减
redis 127.0.0.1:6379> HINCRBY user001:zhangsan xiaomi 1 (integer) 8 |
ü 从hash数据中删除一个字段field及其值
redis 127.0.0.1:6379> hgetall user001:zhangsan 1) "iphone" 2) "6" 3) "xiaomi" 4) "7" 5) "meizu" 6) "8" redis 127.0.0.1:6379> HDEL user001:zhangsan iphone (integer) 1 redis 127.0.0.1:6379> hgetall user001:zhangsan 1) "xiaomi" 2) "7" 3) "meizu" 4) "8" |
18、Set数据结构功能
集合的特点:无序、无重复元素
1、 插入一条set数据
redis 127.0.0.1:6379> sadd frieds:zhangsan bingbing baby fengjie furong ruhua tingting (integer) 6 redis 127.0.0.1:6379> scard frieds:zhangsan (integer) 6 redis 127.0.0.1:6379>
|
2、获取一条set数据的所有members
redis 127.0.0.1:6379> smembers frieds:zhangsan 1) "fengjie" 2) "baby" 3) "furong" 4) "bingbing" 5) "tingting" 6) "ruhua" |
3、判断一个成员是否属于某条指定的set数据
redis 127.0.0.1:6379> sismember frieds:zhangsan liuyifei #如果不是,则返回0 (integer) 0 redis 127.0.0.1:6379> sismember frieds:zhangsan baby #如果是,则返回1 (integer) 1 |
4、求两个set数据的差集
#求差集 redis 127.0.0.1:6379> sdiff frieds:zhangsan friends:xiaotao 1) "furong" 2) "fengjie" 3) "ruhua" 4) "feifei" #求差集,并将结果存入到另一个set redis 127.0.0.1:6379> sdiffstore zhangsan-xiaotao frieds:zhangsan friends:xiaotao (integer) 4 #查看差集结果 redis 127.0.0.1:6379> smembers zhangsan-xiaotao 1) "furong" 2) "fengjie" 3) "ruhua" 4) "feifei" |
5、 求交集,求并集
#求交集 redis 127.0.0.1:6379> sinterstore zhangsan:xiaotao frieds:zhangsan friends:xiaotao (integer) 2 redis 127.0.0.1:6379> smembers zhangsan:xiaotao 1) "bingbing" 2) "baby"
#求并集 redis 127.0.0.1:6379> sunion frieds:zhangsan friends:xiaotao 1) "fengjie" 2) "tangwei" 3) "liuyifei" 4) "bingbing" 5) "ruhua" 6) "feifei" 7) "baby" 8) "songhuiqiao" 9) "furong" 10) "yangmi" |
案例代码:
package cn.toto.redis.hashcart;
import java.util.Map; import java.util.Map.Entry; import java.util.Set;
import org.junit.Before; import org.junit.Test;
import redis.clients.jedis.Jedis;
public class BuyCartServiceImpl {
private Jedis jedis = null; private static final String CART_PRIFIX = "cart:";
@Before public void init() { jedis = new Jedis("hadoop3",6379); }
/** * 添加商品到购物车 */ @Test public void testAddItemToCart() { jedis.hset(CART_PRIFIX + "user02", "上衣", "2"); jedis.hset(CART_PRIFIX + "user02", "鞋子", "1"); jedis.hset(CART_PRIFIX + "user02", "毛衣", "3"); jedis.hset(CART_PRIFIX + "user02", "羽绒服", "4");
jedis.close(); }
/** * 查看购物车信息,当本类中的其它单元测试执行完成之后,可以通过执行这个单元测试方法查看内容 * @author toto * @date 2017-1-14 * @note begin modify by 涂作权 2017-1-14 原始创建 */ @Test public void testGetCartInfo() { Map<String, String> cart = jedis.hgetAll(CART_PRIFIX + "user02"); Set<Entry<String, String>> entrySet = cart.entrySet(); for(Entry<String, String> ent : entrySet) { System.out.println(ent.getKey() + ":" + ent.getValue()); } jedis.close(); }
/** * 编辑缓存中的内容 */ @Test public void editCart() { //给蜡烛商品项的数量加1 jedis.hincrBy(CART_PRIFIX + "user02", "羽绒服", 1); jedis.close(); }
/** * 删除内存中上衣这个内容 */ @Test public void delItemFromCart() { jedis.hdel(CART_PRIFIX + "user02", "上衣"); jedis.close(); } } |
生产者消费者:
生产者: package cn.toto.redis.listqueue;
import java.util.Random; import java.util.UUID;
import redis.clients.jedis.Jedis;
public class TaskProducer { private static Jedis jedis = null;
public static void main(String[] args) throws Exception { jedis = new Jedis("hadoop2",6379); Random random = new Random();
while(true) { int nextInt = random.nextInt(1000); Thread.sleep(1000 + nextInt);
//生成一个任务的id String taskid = UUID.randomUUID().toString();
jedis.lpush("task-queue", taskid);
System.out.println("生成了一个任务:" + taskid); } } } |
消费者: package cn.toto.redis.listqueue;
import java.util.Random;
import redis.clients.jedis.Jedis;
/** * @brief TaskProcessor.java 任务处理模块 */ public class TaskProcessor {
private static Jedis jedis = null;
public static void main(String[] args) throws Exception { Random random = new Random();
jedis = new Jedis("hadoop2",6379);
while(true) { Thread.sleep(1500);
//从任务队列中取出一个任务,同时放入到暂存队列中 String taskid = jedis.rpoplpush("task-queue", "tmp-queue");
//处理任务 if (random.nextInt(19) % 9 == 0) { //模拟失败 //失败的情况下,需要将任务从暂存队列弹回任务队列 jedis.rpoplpush("tmp-queue", "task-queue"); System.out.println("该任务处理失败:" + taskid); } else { //模拟成功 //成功的情况下,只需要将任务从暂存队列清除 jedis.rpop("tmp-queue"); System.out.println("任务处理成功:" + taskid); } } } } |
Set的案例:
package cn.toto.redis.set;
import java.util.Set;
import redis.clients.jedis.Jedis;
public class TestSet {
public static void main(String[] args) { Jedis jedis = new Jedis("hadoop2",6379);
jedis.sadd("friends:shuangshuang", "dandan","lulu","lili"); jedis.sadd("friends:laobi", "laowang","laodu","laoli","lili","lulu");
//判断一个成员是否属于指定的集合 Boolean isornot = jedis.sismember("friends:laobi", "shuangshuang"); System.out.println(isornot);
//求两个集合的差并交集 Set<String> ssDiffbb = jedis.sdiff("friends:shuangshuang","friends:laobi"); Set<String> ssUnionbb = jedis.sunion("friends:shuangshuang","friends:laobi"); Set<String> ssInterbb = jedis.sinter("friends:shuangshuang","friends:laobi");
//打印结果 for (String mb : ssUnionbb) { System.out.println(mb); } } } |
19、sortedSet(有序集合)数据结构
19.1 sortedSet图示
sortedset中存储的成员都有一个附带的分数值
而redis就可以根据分数来对成员进行各种排序(正序、倒序)
1、 sortedSet存储内容示意图:
19.2 SortedSet功能演示
1、往redis库中插入一条sortedset数据
redis 127.0.0.1:6379> zadd nansheng:yanzhi:bang 70 liudehua 90 huangbo 100 weixiaobao 250 yangwei 59 xiaotao (integer) 5
|
2、 从sortedset中查询有序结果
#正序结果 redis 127.0.0.1:6379> zrange nansheng:yanzhi:bang 0 4 1) "xiaotao" 2) "liudehua" 3) "huangbo" 4) "weixiaobao" 5) "yangwei" #倒序结果 redis 127.0.0.1:6379> zrevrange nanshen:yanzhi:bang 0 4 1) "yangwei" 2) "weixiaobao" 3) "huangbo" 4) "liudehua" 5) "xiaotao" |
3、 查询某个成员的名次
#在正序榜中的名次 redis 127.0.0.1:6379> zrank nansheng:yanzhi:bang xiaotao (integer) 0
#在倒序榜中的名次 redis 127.0.0.1:6379> zrevrank nansheng:yanzhi:bang xiaotao (integer) 4 |
4、修改成员的分数
redis 127.0.0.1:6379> zincrby nansheng:yanzhi:bang 300 xiaotao "359" redis 127.0.0.1:6379> zrevrank nansheng:yanzhi:bang xiaotao (integer) 0 |
案例:
下面的这个先运行: package cn.toto.redis.sortedset;
import java.util.Random;
import redis.clients.jedis.Jedis;
public class LolBoxPlayer {
public static void main(String[] args) throws Exception { Jedis jedis = new Jedis("hadoop2",6379);
Random random = new Random(); String[] heros = {"易大师","德邦","剑姬","盖伦","阿卡丽","金克斯","提莫","猴子","亚索"}; while(true) { int index = random.nextInt(heros.length); //选择一个英雄 String hero = heros[index];
//开始玩游戏 Thread.sleep(1000);
//给集合中的该英雄的出场次数加1 //第一次添加的时候,集合不存在,zincrby方法会创建 jedis.zincrby("hero:ccl:phb", 1, hero);
System.out.println(hero + "出场了......"); } } } |
下面的这个后运行: package cn.toto.redis.sortedset;
import java.util.Set;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Tuple;
public class LolBoxViewer {
public static void main(String[] args) throws Exception { Jedis jedis = new Jedis("hadoop2",6379);
int i = 1; while(true) { //每隔3秒查看一次榜单 Thread.sleep(3000); System.out.println("第" + i + "次查看榜单-----------");
//从redis中查询榜单的前N名 Set<Tuple> topHeros = jedis.zrevrangeWithScores("hero:ccl:phb", 0, 4);
for (Tuple t : topHeros) { System.out.println(t.getElement() + " " + t.getScore()); }
i++;
System.out.println(""); System.out.println(""); System.out.println(""); } } } |