非关系型数据库NoSQL:
NoSQL特点:易扩展、灵活的数据模型、大数据量,高性能、高可用
为什么使用:
高并发读写High performance 实时统计,在线人数
海量数据高效存储和访问 Huge Storage
高可扩展和高可用性 High Scalability High
NoSQL数据库四大分类:键值存储、列存储、文档数据库、图形数据库
Redis
REmote DIctionary Server(远程字典服务器)
C语言开发的一个开源高性能键值对分布式内存数据库,
支持数据持久化,支持异步将内存中数据保存在磁盘中,同时不影响服务,重启时可再次加载使用
支持的多种键值数据类型
字符串类型、列表类型、有序集合类型、散列类型、集合类型
(官方测试性能:五十个并发程序执行十万次请求,读速度11万次/秒 写8万次/秒)
应用场景:
- 缓存:数据查询、新闻商品内容,聊天室在线好友列表
- 任务队列,秒杀、抢购
- 网站访问统计
- 数据过期处理精确到毫秒,
- 应用排行榜,
- 分布式集群架构中的session分离
安装:
Windows下:下载安装包解压即可
Linux下:
启动方式:
前端启动:window下运行根目录redis-server.exe或者cmd进入目录运行 redis-server.exe
Linux下进入bin路径输入:redis-server
后端启动:修改conf配置文件,将daemonize no改为daemonize yes,然后进入bin目录运行redis-server ./redis.conf
执行ps -ef | grep -i redis 查看是否运行
进入bin目录执行redis-cli shutdown 停止redis运行
给redis发送命令操作数据库
启动redis服务后,进入bin目录执行redis-cli
存入数据:set name zhangsan
获取数据:get name
查看key:keys *
删除键值对:del name
传统的数据库的ACID
A 原子性atomicity 事务的所有操作要么全做完,要么都不做(回滚)
C 一致性Consistency 数据库要一直处于一致性的状态
I 独立性 Isolation 并发的事务间不会互相影响
D 持久性 Durability 事务提交后数据会保存在数据库,宕机也不会丢失
NoSQL中的CAP (一个分布式系统不能三个都满足,最多只能满足两个)
C 强一致性 Consistency
A 可用性 Availability
P 分布式容错性Partition tolerance
因此
CA 传统Oracle数据库满足
AP +弱一致性,大多数网站架构选择
CP Redis、MongoDB
BASE 解决强一致性引起的可用性问题而提出的解决方案
BASE 即基本可用,软状态,最终一致
分布式:不同的多台服务器上部署不同的服务模块,他们之间通过Rpc/Rmi之间通信和调用,对外提供服务和组内协作
集群:不同的多台服务器上部署相同服务模块,通过分布式调度软件进行统一调度,对外提供服务和访问
安装和测试
下载:wget http://download.redis.io/releases/redis-4.0.11.tar.gz
解压: tar -zxvf redis-4.0.11.tar.gz
没安装gcc的安装gcc到系统:yum install gcc
编译:make
将命令文件加到bin目录下:make install
报错可能是bin目录下没有执行文件,从而不能在任意目录下执行
可将执行文件拷贝到目录 cp -r /usr/local/bin/. /usr/bin
查看是否运行 ps -ef|grep redis
启动redis命令:
进入到bin目录,执行指令文件:
[root@izvluy1jk36n8jz admin]# cd /usr/local/bin
[root@izvluy1jk36n8jz bin]# ls -l
total 21872
-rwxr-xr-x 1 root root 2451208 Oct 2 22:34 redis-benchmark
-rwxr-xr-x 1 root root 5774352 Oct 2 22:34 redis-check-aof
-rwxr-xr-x 1 root root 5774352 Oct 2 22:34 redis-check-rdb
-rwxr-xr-x 1 root root 2617232 Oct 2 22:34 redis-cli
lrwxrwxrwx 1 root root 12 Oct 2 22:34 redis-sentinel -> redis-server
-rwxr-xr-x 1 root root 5774352 Oct 2 22:34 redis-server
[root@izvluy1jk36n8jz bin]# redis-server /redis/redis.conf
7580:C 03 Oct 11:33:33.718 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
7580:C 03 Oct 11:33:33.718 # Redis version=4.0.11, bits=64, commit=00000000, modified=0,
pid=7580, just started
7580:C 03 Oct 11:33:33.718 # Configuration loaded
测试性能
[admin@izvluy1jk36n8jz bin]$ redis-benchmark
启动客户端
[root@izvluy1jk36n8jz bin]# redis-cli -p 6379
输入ping测试是否成功连接
127.0.0.1:6379> ping
PONG
连接成功,存键值对
127.0.0.1:6379> set name zhangsan
OK
读取键值对
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> shutdown
not connected> exit
Redis基础知识
- redis是单进程模型处理客户端请求
- 默认16个数据库
选择切换第2个库:输入: select 1
- Dbsize 查看当前库的key数量
- keys * 列出所有key
Keys k? 就像正则表达式,列出k开头的key
- FLUSHDB 清空当前数据库所有key
FLUSHALL 清空所有数据库的key
- Redis索引都是从零开始,默认端口6379
- 数据类型
String类型:二进制安全的,可以是任何数据,包括图片或序列化的对象,理论最大支持512M
DEL name 删除
append name 111 由张三变成zhangsan111
STRLEN name 则显示name的值zhangsan111的strlen是11
值是数字,则可加减 ,必须是数字
Incr 、decr、incrby、decrby
127.0.0.1:6379> set k2 10
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> incr k2
(integer) 11
127.0.0.1:6379> incr k2
(integer) 12
127.0.0.1:6379> decr k2
(integer) 11
127.0.0.1:6379> decr k2
(integer) 10
127.0.0.1:6379> incrby k2 3
(integer) 13
127.0.0.1:6379> incrby k2 5
(integer) 18
127.0.0.1:6379> decrby k2 4
(integer) 14
127.0.0.1:6379> decrby k2 1
(integer) 13
Getrange和setrange
127.0.0.1:6379> set k3 abc123abc
OK
127.0.0.1:6379> get k3
"abc123abc"
127.0.0.1:6379> getrange k3 2 6
"c123a"
127.0.0.1:6379> setrange k3 2 666
(integer) 9
127.0.0.1:6379> get k3
"ab6663abc"
setex (即是set with expire的意思) 键秒 ,setnx(set if not exit)
127.0.0.1:6379> setex k4 10 vaaa123
OK
mset 、mget、 msetnx
127.0.0.1:6379> mset k3 v3 k4 v4
OK
127.0.0.1:6379> mget k3 k4
1) "v3"
2) "v4"
127.0.0.1:6379> msetnx k3 v33 k4 v44 k5 v55
(integer) 0
127.0.0.1:6379> mget k3 k4 k5
1) "v3"
2) "v4"
3) (nil)
127.0.0.1:6379> msetnx k4 v44 l5 v55
(integer) 0
127.0.0.1:6379> msetnx k5 v55 k6 v66
(integer) 1
127.0.0.1:6379> mget k5 k6
1) "v55"
2) "v66"
127.0.0.1:6379>
Hash类型:类似java的map<String,Object>,是一个键值对集合,hash是一个String类型的field和value的映射表,适合存储对象
List类型
Set集合类型 String类型的无序集合,通过HashTable实现
Zset类型(sorted set :有序不可重复集合,带有可重复的double类型的分数)
常见数据类型操作命令:http://redisdoc.com
- NoSQL的数据操作指令
Key关键字
Keys *
move name 2 将name键值对剪切到2数据库
Select 2
get name
clear 回到原来的指令状态
ttl name 查询name多久过期,-1则永不过期,-2已过期
EXPIRE name 10 设置name将10s过期
type name 查看name是什么类型
- Redis的发布订阅
发布订阅是进程间的一种消息通信模式:发送者发送消息,订阅者接收消息,实际上很少用redis做发布订阅
不过redis的主要应用是分布式数据缓存
订阅多个(c1、c2、c3): subscribe c1、c2、c3
消息发布:publish c2 hello 向 c2发送消息hello
订阅多个,通配符*,psubscribe new*
收取消息,publish new1 messagenew1
- Redis的复制机制
也就是主从(master 、slave)复制,主机数据更新后根据配置和策略自动同步到备份的master/slaver机制,Master以写为主,slaver以读为主
从而能实现读写分离、容灾恢复
使用:
- 配从不陪主
- 从库配置:slaveof主库IP主库端口
- 修改配置文件细节操作
拷贝多个redis.conf文件
开启daemonize yes
Pid文件名字,修改端口
指定duank
Log文件名字
Dump.rdp名字
- 常用三招
一主二仆
薪火相传
反客为主
哨兵模式
Info replication 该命令查看主从角色信息
一主二从:(中心化,主机负担重)在从机上 SLAVEOF 127.0.0.1 6380 设置127.0.0.1 6380为它的主机,主机的数据备份到从机,从机不可对数据进行更新。主机出现故障,从机的角色不变,还是从机。
主机恢复后,恢复正常。从机故障,故障期间更新的数据,在故障恢复后,故障恢复后的从机数据并没有更新备份,因此,每次从机与master主机断开后都要重新连接,即重新执行SLAVEOF 127.0.0.1 6380建立连接
薪火相传:(去中心化,减轻主机负担,但有复制延时),上一个slave是下一个slave的master,从机的角色还是slave,只不过不过会有连接的从机
反客为主: 原来的主机故障,从机中会有新主机。SLAVEOF no one(该指令使当前数据库成为主数据库) ,其余从机,与新主机建立连接。原来的主机恢复后,故障期间新主机产生的更新数据原主机不会有
哨兵模式:sentinel ,(复制延迟)可以说是自动版的反客为主 ,slave启动成功后会发送一个sync命令,master接到后会在后台进程执行完毕后将数据文件传送到slave,完成同步。全量复制(一般首次是全量复制)和增量复制(主机数据更新后从机跟着复制更新的部分),但如果是重连master,都会执行全量复制。主机出现故障后会在从机选出新主机,当主机恢复正常后就变成新主机的从机
在conf文件目录下新建sentinel.conf文件 touch sentinel.conf
配置sentinel.conf:如 sentinel monitor host6379 127.0.0.1 6379 1 (1表示自动投票选出主机)
保存文件,启动哨兵:如:redis-sentinel /redis/sentinel.conf
- Java使用Redis
public static void main(String[] args) {
//连接本地的 Redis 服务
Jedis jedis = new Jedis("localhost");
System.out.println("连接成功");
//设置 redis 字符串数据
jedis.set("name", "zhangsan");
// 获取存储的数据并输出
System.out.println("redis 存储的字符串为: "+ jedis.get("name"));
//存储数据到列表中
jedis.lpush("site-list", "Runoob");
jedis.lpush("site-list", "Google");
jedis.lpush("site-list", "Taobao");
// 获取存储的数据并输出
List<String> list = jedis.lrange("site-list", 0 ,2);
for(int i=0; i<list.size(); i++) {
System.out.println("列表项为: "+list.get(i));
}
// 获取数据并输出
Set<String> keys = jedis.keys("*");
Iterator<String> it=keys.iterator() ;
while(it.hasNext()){
String key = it.next();
System.out.println(key);
}
}
- 事务
public static void main(String[]args){
/* Jedis jedis=new Jedis("localhost", 6379);
System.out.println(jedis.ping());
System.out.println("连接成功");
Transaction transaction=jedis.multi();
transaction.set("k1","v1");
transaction.set("k2","v2");*/
/*transaction.exec();*///事务提交
//transaction.discard(); 取消事务
transMethod();
}
public static boolean transMethod(){
Jedis jedis=new Jedis("localhost",6379);
int balance;
int debt;
int amtToSubtract=10;
balance=Integer.parseInt(jedis.get("balance"));
jedis.watch("balance");
if(balance<amtToSubtract){
jedis.unwatch();
System.out.println("额度不足");
return false;
}else {
System.out.println("执行事务……");
Transaction transaction=jedis.multi();
transaction.decrBy("balance",amtToSubtract);
transaction.incrBy("debt",amtToSubtract);
transaction.exec();
System.out.println("事务提交……");
System.out.println("剩余额度:"+jedis.get("balance"));
System.out.println("下月应还:"+jedis.get("debt"));
return true;
}
}
- 主从复制
public static void main(String[]args){
Jedis jedis_M=new Jedis("localhost",6379);
Jedis jedis_S=new Jedis("localhost",6380);
jedis_S.slaveof("localhost",6379);
jedis_M.set("testMS","vvv");
System.out.println(jedis_S.get("testMS"));
}
- JedisPool,redis数据连接池
public final class JedisUtil {
private JedisUtil() {}
private static JedisPool jedisPool;
private static int maxtotal;
private static int maxwaitmillis;
private static String host;
private static int port;
/*读取jedis.properties配置文件*/
static{
ResourceBundle rb = ResourceBundle.getBundle("jedis");
maxtotal = Integer.parseInt(rb.getString("maxtotal"));
maxwaitmillis = Integer.parseInt(rb.getString("maxwaitmillis"));
host = rb.getString("host");
port = Integer.parseInt(rb.getString("port"));
}
/*创建连接池*/
static{
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(maxtotal);
jedisPoolConfig.setMaxWaitMillis(maxwaitmillis);
jedisPool = new JedisPool(jedisPoolConfig,host,port);
}
/*获取jedis*/
public static Jedis getJedis(){
return jedisPool.getResource();
}
/*关闭Jedis*/
public static void close(Jedis jedis){
if(jedis!=null){
jedis.close();
}
}
}
public class TestTransaction {
public static void main(String[]args){
/* Jedis jedis=new Jedis("localhost", 6379);
System.out.println(jedis.ping());
System.out.println("连接成功");
Transaction transaction=jedis.multi();
transaction.set("k1","v1");
transaction.set("k2","v2");*/
/*transaction.exec();*///事务提交
//transaction.discard(); 取消事务
transMethod();
}
public static boolean transMethod(){
Jedis jedis=new Jedis("localhost",6379);
int balance;
int debt;
int amtToSubtract=10;
balance=Integer.parseInt(jedis.get("balance"));
jedis.watch("balance");
if(balance<amtToSubtract){
jedis.unwatch();
System.out.println("额度不足");
return false;
}else {
System.out.println("执行事务……");
Transaction transaction=jedis.multi();
transaction.decrBy("balance",amtToSubtract);
transaction.incrBy("debt",amtToSubtract);
transaction.exec();
System.out.println("事务提交……");
System.out.println("剩余额度:"+jedis.get("balance"));
System.out.println("下月应还:"+jedis.get("debt"));
return true;
}
}
public class Test3 {
public static void main(String[]args){
JedisPool jedisPool=JedisPoolUtil.getJedisPoolInstance();
Jedis jedis=null;
try {
jedis=jedisPool.getResource();
//...进行数据更新的代码
}catch (Exception e){
e.printStackTrace();
}finally {
JedisPoolUtil.release(jedisPool,jedis);
}
}
}