Redis基础以及JavaAPI
1.NoSQL
NoSQL,泛指非关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。
2.分类
1.键值(Key-Value)存储数据库
相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
数据模型: 一系列键值对
优势: 快速查询
劣势: 存储的数据缺少结构化
2.列存储数据库
相关产品:Cassandra, HBase, Riak
典型应用:分布式的文件系统
数据模型:以列簇式存储,将同一列数据存在一起
优势:查找速度快,可扩展性强,更容易进行分布式扩展
劣势:功能相对局限
3.文档型数据库
相关产品:CouchDB、MongoDB
典型应用:Web应用(与Key-Value类似,Value是结构化的)
数据模型: 一系列键值对
优势:数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一的查询语法
4.图形(Graph)数据库
相关数据库:Neo4J、InfoGrid、Infinite Graph
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
3.Redis概述
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库。它通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的键值数据类型如
下:
字符串类型(string)
散列类型(hash)
列表类型(list)
集合类型(set)
有序集合类型(sortset)
4.Redis安装
1.Windows安装
在github或者redis.io上下载windows版本的redis
解压即用 确保6379端口不被占用
2.Linux安装
在redis官网上下载redis的安装包
解压
tar -zxvf reids-*.tar.gz
安装gcc(C语言的环境) 由于redis是用C语言编写的所以要安装C语言的环境
yum install gcc
在解压后的Redis根目录 编译安装Redis
make install
启动
redis-server redis.conf
连接
redis-cli -h host -p port
5.jedis(redis Java客户端)
1.Mavne依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2.jedis连接和连接池
/**
* java redis数据的存取
*/
@Test
public void test() {
// 创建jedis的客服端
Jedis jedis = new Jedis("localhost", 6379);
// 设置一个key为name的值为 xiaoyu 的数据
jedis.set("name", "xiaoyu");
// 获取name值
String name = jedis.get("name");
System.out.println(name);
// 关闭连接
jedis.close();
}
/**
* jedis连接池的使用
*/
@Test
public void testPool() {
// 创建jedis连接池配置的对象
JedisPoolConfig config = new JedisPoolConfig();
// 设置最大连接数
config.setMaxTotal(3);
// 设置最大等待时间
config.setMaxWaitMillis(1000);
// 创建连接池
JedisPool pool = new JedisPool(config, "localhost", 6379);
// 从连接池获取连接
Jedis jedis = pool.getResource();
// 获取数据
String name = jedis.get("name");
System.out.println(name);
// 归还连接
pool.returnResourceObject(jedis);
// 销毁连接池
pool.destroy();
}
3.连接池的工具类
package cn.yu.util;
import java.util.HashMap;
import java.util.Map;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class DBUilts {
/**
* 存放连接池的集合
*/
private static Map<String,JedisPool> map = new HashMap<String,JedisPool>();
/**
*单例的连接池工具类
*/
private DBUilts util;
private DBUilts() {
}
public DBUilts getInstance() {
if(util == null) {
util = new DBUilts();
}
return util;
}
/**
* 创建连接池
* @param host
* @param port
* @return
*/
private static JedisPool createPool(String host,int port) {
JedisPool pool = new JedisPool(host,port);
map.put(host+port, pool);
return pool;
}
/**
* 创建连接
* @param host
* @param port
* @return
*/
public static Jedis createJedis(String host,int port) {
return new Jedis(host, port);
}
/**
* 获取连接池
* @param host
* @param port
* @return
*/
public static JedisPool getPool(String host,int port) {
JedisPool pool = map.get(host+port);
if (pool == null) {
pool = createPool(host, port);
}
return pool;
}
}
4.String(字符串类型)
Redis中没有使用C语言的字符串表示,而是自定义一个数据结构叫SDS(simple dynamic string)即简单动态字符串
C语言对字符串的存储是使用字符数组,遇到’\0’字符则认为字符串结束,redis的字符串可以存储任何类型的数据,因为任何类型数据都可以表示成二进制,sds结构中的char buf[]就是存储了二进制数据。
Redis的字符串是二进制安全的,什么是二进制安全?简单理解就是存入什么数据取出的还是什么数据。redis中的sds不像c语言处理字符串那样遇到’\0’字符则认证字符串结束,它不会对存储进去的二进制数据进行处理,存入什么数据取出还是什么数据。
/**
* 对已有值的key追加值
*/
@Test
public void testAppend() {
// 创建redis客户端
Jedis jedis = new Jedis("localhost",6379);
// 设置值
jedis.set("name", "xioayu");
System.out.println(jedis.get("name"));
// 追加值
jedis.append("name", "niubi");
System.out.println(jedis.get("name"));
jedis.close();
}
/**
* 一次设置多个值
*/
@Test
public void testMset() {
Jedis jedis = new Jedis("localhost",6379);
jedis.mset(new String[] {"name","xiayu","age","19"});
jedis.close();
}
/**
* 一次获取多个值
*/
@Test
public void testMget() {
Jedis jedis = new Jedis("localhost",6379);
List<String> lists = jedis.mget(new String[] {"name","age"});
for (String value : lists) {
System.out.println(value);
}
jedis.close();
}
5.hash(hash类型)
hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型
public static void testHsh() {
System.out.println("==Hash==");
Jedis jedis = RedisUtil.getJedis();
try {
Map<String, String> pairs = new HashMap<String, String>();
pairs.put("name", "Akshi");
pairs.put("age", "2");
pairs.put("sex", "Female");
jedis.hmset("kid", pairs);
List<String> name = jedis.hmget("kid", "name");// 结果是个泛型的LIST
System.out.println(name);
jedis.hdel("kid","age"); //删除map中的某个键值
System.out.println(jedis.hmget("kid", "pwd")); // 因为删除了,所以返回的是null
System.out.println(jedis.hlen("kid")); // 返回key为user的键中存放的值的个数
System.out.println(jedis.exists("kid"));// 是否存在key为user的记录
System.out.println(jedis.hkeys("kid"));// 返回map对象中的所有key
System.out.println(jedis.hvals("kid"));// 返回map对象中的所有value
Iterator<String> iter = jedis.hkeys("kid").iterator();
while (iter.hasNext()) {
String key = iter.next();
System.out.println(key + ":" + jedis.hmget("kid", key));
}
List<String> values = jedis.lrange("messages", 0, -1);
values = jedis.hmget("kid", new String[] { "name", "age", "sex" });
System.out.println(values);
Set<String> setValues = jedis.zrange("hackers", 0, -1);
setValues = jedis.hkeys("kid");
System.out.println(setValues);
values = jedis.hvals("kid");
System.out.println(values);
pairs = jedis.hgetAll("kid");
System.out.println(pairs);
// 清空数据
System.out.println(jedis.flushDB());
// 添加数据
jedis.hset("hashs", "entryKey", "entryValue");
jedis.hset("hashs", "entryKey1", "entryValue1");
jedis.hset("hashs", "entryKey2", "entryValue2");
// 判断某个值是否存在
System.out.println(jedis.hexists("hashs", "entryKey"));
// 获取指定的值
System.out.println(jedis.hget("hashs", "entryKey")); // 批量获取指定的值
System.out.println(jedis.hmget("hashs", "entryKey", "entryKey1"));
// 删除指定的值
System.out.println(jedis.hdel("hashs", "entryKey"));
// 为key中的域 field 的值加上增量 increment
System.out.println(jedis.hincrBy("hashs", "entryKey", 123l));
// 获取所有的keys
System.out.println(jedis.hkeys("hashs"));
// 获取所有的values
System.out.println(jedis.hvals("hashs"));
} catch (Exception e) {
e.printStackTrace();
} finally {
RedisUtil.getPool().returnResource(jedis);
}
}
6.list(列表类型)
列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。
列表类型内部是使用双向链表(linkedList)实现的,所以向列表两端添加元素的时间复杂度为,获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的条记录也是极快的
public static void testList() {
System.out.println("==List==");
Jedis jedis = RedisUtil.getJedis();
try {
// 开始前,先移除所有的内容
jedis.del("messages");
jedis.rpush("messages", "Hello how are you?");
jedis.rpush("messages", "Fine thanks. I'm having fun with redis.");
jedis.rpush("messages", "I should look into this NOSQL thing ASAP");
// 再取出所有数据jedis.lrange是按范围取出,
// 第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有
List<String> values = jedis.lrange("messages", 0, -1);
System.out.println(values);
// 清空数据
System.out.println(jedis.flushDB());
// 添加数据
jedis.lpush("lists", "vector");
jedis.lpush("lists", "ArrayList");
jedis.lpush("lists", "LinkedList");
// 数组长度
System.out.println(jedis.llen("lists"));
// 排序
//System.out.println(jedis.sort("lists"));
// 字串
System.out.println(jedis.lrange("lists", 0, 3));
// 修改列表中单个值
jedis.lset("lists", 0, "hello list!");
// 获取列表指定下标的值
System.out.println(jedis.lindex("lists", 1));
// 删除列表指定下标的值
System.out.println(jedis.lrem("lists", 1, "vector"));
// 删除区间以外的数据
System.out.println(jedis.ltrim("lists", 0, 1));
// 列表出栈
System.out.println(jedis.lpop("lists"));
// 整个列表值
System.out.println(jedis.lrange("lists", 0, -1));
} catch (Exception e) {
e.printStackTrace();
} finally {
RedisUtil.getPool().returnResource(jedis);
}
}
7.Set(集合类型)
Redis的Set类型和java中的Set比较类似,每个元素都是不能重复的,并且没有顺序,与List类型的区别如下图所示。
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型的Redis内部是使用值为空的散列表实现,所有这些操作的时间复杂度都为0(1)。
Redis还提供了多个集合之间的交集、并集、差集的运算
@Test
public void testSet() {
Jedis jedis = new Jedis("localhost", 6379);
// 存储set类型数据
jedis.sadd("myset", "a", "b", "c");
jedis.sadd("myset", "a", "b", "c");
jedis.sadd("myset1", "a", "e", "c");
jedis.sadd("myset2", "j", "k", "l");
// 遍历set中所有数据
System.out.println(jedis.smembers("myset"));
// 删除指定元素
System.out.println(jedis.srem("myset", "a"));
// 判断元素是否存在
System.out.println(jedis.sismember("myset", "b"));
// 交集
System.out.println(jedis.sinter("myset1", "myset"));
// 并集
System.out.println(jedis.sunion("myset1", "myset"));
// 差集
System.out.println(jedis.sdiff("myset1", "myset"));
jedis.close();
}
8.SortSet(有序集合)
在集合类型的基础上有序集合类型为集合中的每个元素都关联一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在在集合中,还能够获得分数最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作。
在某些方面有序集合和列表类型有些相似。
-
二者都是有序的。
-
二者都可以获得某一范围的元素。
二者有着很大区别:
-
列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会变慢。
-
有序集合类型使用散列表实现,所有即使读取位于中间部分的数据也很快。
-
列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改分数实现)
-
有序集合要比列表类型更耗内存。
@Test
public void testsortSet() {
Jedis jedis = new Jedis("localhost", 6379);
// 添加数据 指定key score value 会根据score对value进行排序
jedis.zadd("sortset", 100, "Alan Key");
jedis.zadd("sortset", 111, "Richard Stallman");
jedis.zadd("sortset", 112, "Hong kong");
jedis.zadd("sortset", 999, "teday");
// 默认是分数由低到高
Set<String> setValues = jedis.zrange("sortset", 0, -1);
System.out.println(setValues.toString());
// revrange 倒叙分数由高到低排序
Set<String> setValues1 = jedis.zrevrange("sortset", 0, -1);
System.out.println(setValues1.toString());
// 清空数据
System.out.println(jedis.flushDB());
// 添加数据
jedis.zadd("zset", 10.1, "hello");
jedis.zadd("zset", 10.0, ":");
jedis.zadd("zset", 10.3, "zset");
jedis.zadd("zset", 9.5, "hello");
// 获取元素个数
System.out.println(jedis.zcard("zset"));
// 删除元素 如果有相同的值都会被删除
System.out.println(jedis.zrem("zset", "hello"));
// 根据分数区间统计元素的个数
System.out.println(jedis.zcount("zset", 9, 11));
// 获取整个集合值
System.out.println(jedis.zrange("zset", 0, -1));
// 获取集合值和分数
Set<Tuple> tuple = jedis.zrangeWithScores("zset", 0, -1);
System.out.println("分数\t值");
for (Tuple t : tuple) {
System.out.print(t.getScore() + "\t" + t.getElement());
System.out.println();
}
Set<Tuple> tuple1 = jedis.zrangeByScoreWithScores("zset", 9, 11);
System.out.println("分数\t值");
for (Tuple t : tuple1) {
System.out.print(t.getScore() + "\t" + t.getElement());
System.out.println();
}
// 获取排名 索引号 从0开始
System.out.println(jedis.zrank("zset", "zset"));
jedis.close();
}
9.Redis和MySQL数据同步(重点)
Redis有着高性能、低延时的查询,适合作为关系型数据库的缓存来缓解数据库操作的压力,提高数据查询的性能,MySQL表结构如下图所示。
Redis数据库设计
主键 | list(emp:emp_id) | 1 2 3 |
---|---|---|
数据 | hash(emp:1) | emp_id 1 ename zhangsan birthdate 2017-02-01 … |
关联 | list(dept:1emp:) | 1 2 3 |
部门实体类
package cn.yu.model1;
import java.util.Set;
public class Dept {
private Integer deptId;
private String deptname;
private String dloc;
private Set<Emp> emps;
@Override
public String toString() {
return "Dept [deptId=" + deptId + ", deptname=" + deptname + ", dloc=" + dloc + ", emps=" + emps + "]";
}
public Set<Emp> getEmps() {
return emps;
}
public void setEmps(Set<Emp> emps) {
this.emps = emps;
}
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptname() {
return deptname;
}
public void setDeptname(String deptname) {
this.deptname = deptname;
}
public String getDloc() {
return dloc;
}
public void setDloc(String dloc) {
this.dloc = dloc;
}
}
员工
package cn.yu.model1;
import java.util.Date;
public class Emp {
private Integer empId;
private Integer deptId;
private String ename;
private Date birthdate;
private Integer gender;
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public Date getBirthdate() {
return birthdate;
}
public void setBirthdate(Date birthdate) {
this.birthdate = birthdate;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
}
同步数据
@Test
public void testQueryRedis() {
Jedis jedis = new Jedis("localhost", 6379);
// 获取所有球队的id
List<String> ids = jedis.lrange("team:teamid", 0, -1);
// 存储对象的集合
List<Dept> list = new ArrayList<Dept>();
// 根据id获取字段
for (String teamId : ids) {
// 获取字段值
String name = jedis.hget("team:" + teamId, "t_name") ;
String loc = jedis.hget("team:" + teamId, "loc");
// 创建一个dept对象
Dept dept = new Dept();
dept.setTeamId(new Integer(teamId));
dept.setName(name);
dept.setLoc(loc);
list.add(dept);
}
System.out.println(list);
jedis.close();
}
/**
* redis的多对一查询
*/
@Test
public void testQueryRedis1() {
Jedis jedis = new Jedis("localhost", 6379);
// 查询员工
Emp emp = new Emp();
String empId = jedis.hget("emp:1", "emp_id");
String ename = jedis.hget("emp:1", "ename");
String gender = jedis.hget("emp:1", "gender");
Integer teamId = new Integer(jedis.hget("emp:1", "team_id"));
emp.setEmpId(new Integer(empId));
emp.setEname(ename);
emp.setGender(gender);
emp.setTeam_id(teamId);
// 根据teamid查询球队
Dept dept = new Dept();
String tname = jedis.hget("team:" + teamId, "t_name");
String loc = jedis.hget("team:" + teamId, "loc");
dept.setTeamId(teamId);
dept.setName(tname);
dept.setLoc(loc);
emp.setDept(dept);
System.out.println(emp);
jedis.close();
}
/**
* redis的一对多查询
*/
@Test
public void testQueryRedis2() {
Jedis jedis = new Jedis("localhost",6379);
// 查询出部门信息
Dept dept = new Dept();
String teamId = jedis.hget("team:2", "t_id");
String name = jedis.hget("team:2", "t_name");
String loc = jedis.hget("team:2", "loc");
dept.setTeamId(new Integer(teamId));
dept.setLoc(loc);
dept.setName(name);
// 获取部门下的所有的员工id
List<String> list = jedis.lrange("team:" + teamId + ":emp", 0, -1);
Set<Emp> set = new HashSet<Emp>();
for(String empId : list) {
String ename = jedis.hget("emp:" + empId , "ename");
String gender = jedis.hget("emp:" + empId , "gender");
Emp emp = new Emp();
emp.setEmpId(new Integer(empId));
emp.setEname(ename);
emp.setGender(gender);
emp.setTeam_id(new Integer(teamId));
set.add(emp);
}
dept.setEmps(set);
System.out.println(dept);
jedis.close();
}
@Test
public void MysqlForRedis() throws IOException {
// 创建redis的连接实例
Jedis jedis = new Jedis("localhost",6379);
// 读取配置文件
InputStream in = Resources.getResourceAsStream("sqlMapConfig.xml");
// 创建sqlSessionFactory的实例
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 获取seesion
SqlSession session = factory.openSession();
// 获得注册的接口实现类
DeptDao dao = session.getMapper(DeptDao.class);
// 查询部门信息
cn.yu.model1.Dept dept = dao.selectDeptById(1);
// 存入部门信息
jedis.hset("dept:" + dept.getDeptId(), "dept_id",dept.getDeptId().toString());
jedis.hset("dept:" + dept.getDeptId(), "dept_name", dept.getDeptname());
jedis.hset("dept:" + dept.getDeptId(), "dept_loc",dept.getDloc());
// 存入员工信息
Set<cn.yu.model1.Emp> emps = dept.getEmps();
for (cn.yu.model1.Emp emp : emps) {
jedis.hset("emp:" + emp.getEmpId(), "emp_id", emp.getEmpId().toString());
jedis.hset("emp:" + emp.getEmpId(), "ename", emp.getEname());
jedis.hset("emp:" + emp.getEmpId(), "birthdte", new SimpleDateFormat("yyyy-MM-dd").format(emp.getBirthdate()).toString());
jedis.hset("emp:" + emp.getEmpId(), "gender", emp.getGender().toString());
jedis.hset("emp:" + emp.getEmpId(), "dept_id", emp.getDeptId().toString());
jedis.lpush("dept:" + dept.getDeptId() + ":emp", emp.getEmpId().toString());
}
session.close();
jedis.close();
}