目录
一、nosql的特点
是nosql数据库的一种,是最像关系型数据库的非关系型数据库,适合在修改频度很高的环境下使用,且支持结构化的数据存储。
1、适用的场景
- 对数据高并发的读写
- 海量数据的读写
- 对数据可扩展性
2、不适用的场景
- 需要事务主持
- 基于sql的结构化查询存储,处理复杂的关系,需要即席查询
- 用不着sql的和用了sql也不行的情况下,请考虑用NoSql
3、常用的nosql数据库
- memcache
- redis
- mongdb
二、Redis的概述和安装
1、redis概述
- redis中有16个数据库,默认用0号库,用select 数据库号,如select 2,来选择数据库
- 是单线程和多路io复用
- 技术redis是一个开源的key-value存储系统
- 和们擦车的类似,它支持存储的value的类型相对更多,包括string、list、set、zset(有序集合)和hash
- 执行数据类型都支持push/pop、add/remove即取交集并集和差集及更丰富的操作,而且这些操作都是原子性的
- 在此基础上,Redis支持各种不同的方式的排序
- 与memcached一样,为了保证效率,数据都是缓存在内存中的
- 区别的是Redis会周期性的把更新的数据写入磁盘或把修改操作写入追加的记录文件(持久化)
- 并且在此基础上实现了Master-slave(主从)同步
2、在linux系统上安装redis
1、在官网下载redis(redis.io)
2、将压缩包放到linux下
我是用的虚拟机的共享文件夹
3、将压缩包复制到usr/local/src/redis下并且解压
tar redis-6.2.6.tar.gz
4、进入到解压后的redis-6.2.6文件夹下,执行make
cd /usr/local/src/redis/redis-6.2.6
5、如果linux系统没安gcc,则要安装gcc,因为make要用到gcc
- sudo apt install gcc(ubuntu)
- sudo yum install gcc (Centos7)
- 用 gcc --version 查询是否安装
6、编译完成后 继续执行 make install
7、默认安装在/usr/local/bin目录下
进入到 /usr/local/bin目录下执行redis-server,就可前台启动(不推荐)。
8、后台启动
- 备份redis的配置文件
进入到redis-6.2.6目录中
复制到其他目录下自定义
cp redis.conf /etc/redis.conf
- 修改redis的配置文件
将文件中第125行的 daemonize no 属性改为 yes,让服务可以在后台启动
vi redis.conf
按i进入到写模式
修改完之后按esc键在输入:wq后退出
- redis的启动
进入到/src/local/bin目录下,执行
redis-server /etc/redis.conf (刚刚复制的配置文件)
用ps -ef | grep redis查开是否启动
9、连接 redis
用redis-cli命令连接,用ping命令查看是否连接成功
10、关闭redis
- 在redis-cli下执行shutdown来关闭
- kill -9 进程号
三、Redis的数据类型
1、key键的一些操作
- keys * 查看当前库中的所有key
- exists keyName 查看key是否存在
- type keyName 查看key的类型
- del keyName 删除key
- unlike keyName 删除key,异步操作
- expire keyName 10 为key设置过期时间
- ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期
- dbsize 查看数据库key的数量
- flushdb 清空当前数据库
- flushall 清空所有库
2、String数据类型
即一个key对应一个value,String类型是二进制安全的,这意味着string可以包含任何数据,比如jpg图片或者序列化的对象,value最大为512MB,数据结构类似于ArrayList
- set key value 添加键值对
- get key 查询对应的键值
- append key value 将value追加到原值的末尾
- strlen key 获取key的value的长度
- setnx key value 不能覆盖,只能添加
- incr key 将key中存储的数字值加1
- decr key 将key中存储的数字值减1
- incrby/decrby key 步长 自定义数字值增减的步长
- mset key1 value1 [key value ....] 同时设置多个键值
- mget key1 [key ....] 同时获取多个键值
- msetnx key1 value1 [key value ....] 同时设置多个键值,不能覆盖,只能添加
- getrange key <起始位置> <结束位置> 获取值的范围的值
- setex key <过期时间> value 设置key的过期时间
String类型的原子性
所谓原子性就是指不会被线程机制打断的操作,这种操作一旦开始,就会运行到结束。Redis的单命令的原子性主要得益于Redis的单线程和多路io复用技术的应用
3、list列表
一键多值,按照插入的顺序进行排序,底层是一个双向链表,对两端的操作性能很高,通过下标操作中间的结点性能会较差,数据较多时为quicklist,是用多个ziplist组成的,ziplist列表是连续的地址。
- lpush/rpush key value1 value2 <...> 从左边/右边插入一个或多个值
- lpop/rpop key 从左边或右边吐出一个值,当key中的值没有时删除key
- rpoplpush key1 key2 从key1列表右边吐出一个值,插到key2的左边
- lrange key start stop 从key中获取下标start到stop的值,0 -1获取全部
- index key index 按照索引下标获取元素(从左到右)
- llen key 获取列表的长度
- linsert before value newvalue 在value的前面插入newvalue的值
- lrem key n value 从左边删除n个value
- lset key index value 将列表key下标为index的值替换为value
4、set集合
是一个无序集合,它的底层是一个哈希表,复杂度为O(1)
- sadd key value1 value2 ... 将一个或多个menber元素加入到key中
- smembers key 取出该集合的所有值
- sismember key value 判断集合是否含有这个value值
- scard key 返回集合的元素个数
- srem key value1 value2 删除集合中的某个元素
- spop key 随机吐出一个值(会删除)
- srandmember key n 随机取出n个值(不删除)
- smove sourceKey destinationKey value 把 sourceKey中的value值移到destinationKey中
- sinter/sunion/sdiff key1 key2 取两个集合的交集,并集和差集
5、hash集合
是一个键值对集合,底层是ziplist(少量数据)和hashtable(大量数据)
- hset key field value ... 给key集合中的filed键添加value
- hget key field 从key集合中取出value
- hmset key field1 value1 ... 批量给key集合中的filed键添加value
- hexists key fileld 查看哈希表key中,给定域field是否存在
- hkeys key 列出该hash集合的所有field
- hvals key 列出该hash集合的所有value
6、z-set集合
是一个有序集合,根据score进行排序
- zadd key score1 value1 score2 value2 ... 将多个member元素及其score值加入到有序集合key中
- zrange key start stop [withscores] 返回有序集key中,下标在start到stop之间的元素带withscores会返回score的值
- zrangebyscore key min max 返回key中,所有score值在min和max之间的成员
- zincrby key increment value 为元素的score加上增量
- zrem key value 删除该集合下的指定值的元素
- zcount key min max 统计该集合,分数在区间内的元素
- zrank key value 返回该值在集合中的排名,从0开始
四、新增的数据类型
1、Bitmaps
就相当于字符串数组,其中8位为一个元素
- setbit key offset value 设置Bitmaps中的某个偏移量的值,value只能为1或0,offset从0开始
- getbit key offset 获取bitmaps中某个偏移量的值
- bitcount key [start,end] 统计Bitmaps中的1的个数,start和end是获取下标中的1的个数,
- bitop and(or/not/xor)destKey key1 ... 可以做交集,并集,非,异或操作,结果保存在destkey中
2、HyperLogLog
主要用于解决基数问题
- pfadd key element ... 添加指定元素到HyperLogLog中
- pfcount key 统计key中的基数
- pfmerge destkey sourcekey1 sourcekey2 ... 将多个或多个Hll合并后的结果存入到destkey中
3、Geospatial
地理信息的缩写,存的是一个二维坐标,即纬度和经度
- geoadd key longitude latitude member ... 添加地理位置(经度,纬度,地名)
- geopos key member 获取指定地区的坐标值
- geodist key member member [m,km,ft,mi] 求出两个位置的直线距离
- georadius key longitude latitude radius 以给定的经纬度为中心,找出radius半径内的元素
五、配置文件的操作
1、NETWORD
表示只能在本地访问redis
关闭
是否开启安全模式,开启只能在本地访问redis
设置连接超时time,0为永不超时
2、发布和订阅
订阅者要订阅一个频道,发布者可以往这个频道发布消息,订阅者就能看到这个消息,示列
订阅频道(可订阅多个)
往频道发布消息
接收到的消息
六、用jedis操作
这些方法就是在redis-cli中的命令。
- 对key进行操作,获取所有的key
Set<String> keys = jedis.keys("*");
- 添加键值对
jedis.set("name","123");
- 获取键值
String name = jedis.get("name");
- 添加多个键值对
jedis.mset("name2","1","name3","4");
- 获取多个键值
List<String> mget = jedis.mget("name1", "name2");
七、 事务和锁机制
redis事务是一个单独的隔离操作,即事务中的使用命令都会序列化、按顺序的执行。事务在执行的过程中,不会被其他的客户端发送来的命令请求打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队
redis事务的三个特性:单独的隔离操作,没有隔离级别,不保证原子性
1、基本操作(Multi,Exec,discard)
从输入Multi命令开始,输入的命令都会依次进入到命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令,队列的命令依次执行。
组队的过程中可以通过的是card来放弃组队。
- 如果在组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消
- 如果在组队中没有命令出现报告错误,执行时谁有错误就 会被取消
2、事务冲突
- 悲观锁
就会悲观的认为每一次的操作数据都会被被其他的事务所抢占,所以每次在用数据时都会上锁,不让其他事务拿到。
- 乐观锁
认为每次拿数据,都认为没有其他事务去修改,所以jiub会上锁,但是在更新的时候回判断一下在此期间别有没有去操作这个数据,可以用版本号的机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量。redis就是用的乐观锁
3、秒杀案例
用jedispool解决超时问题
用乐观锁解决超卖问题
public boolean miaoSa(String pid, String uid) {
String kcKey = "sk:"+pid+":qt";
String userKey = "sk:"+pid+":user";
if(pid==null || uid == null){
return false;
}
//连接redis
Jedis jedis = new Jedis("192.168.6.148",6379);
//监视库存
jedis.watch(kcKey);
//查看库存是否创建
if(!jedis.exists(kcKey)){
System.out.println("秒杀未开始");
jedis.close();
return false;
}
//判断用户是否重复抢购
if(jedis.sismember(userKey,uid)){
System.out.println("你已秒杀成功,不可重复秒杀");
jedis.close();
return false;
}
//判断库存的数量
int kc = Integer.parseInt(jedis.get(kcKey));
if(kc<1){
System.out.println("已秒光,请下次再来");
jedis.close();
return false;
}
//创建一个事务
Transaction multi = jedis.multi();
//创建命令队列
multi.decr(kcKey);
multi.sadd(userKey,uid);
//执行事务
List<Object> exec = multi.exec();
//判断事务是否执行成功
if (exec == null || exec.size()==0){
System.out.println("秒杀失败");
jedis.close();
return false;
}
// jedis.decr(kcKey);
//
// jedis.sadd(userKey,uid);
System.out.println("秒杀成功");
return true;
}
会出现库存遗留问题 ,用lua脚本解决,类似悲观锁。
七、redis的持久化操作
1、RDB
即在指定的时间间隔内将内存中的数据集快照写入磁盘
- 优势
适合大规模的数据恢复
对数据完整性和一致性要求不高的更适合使用
节省磁盘空间
恢复速度快
- 劣势
- rdb的备份及恢复
将原先的dump.rdb,cp 一份,如果dump.rdb丢失,将cp的那一份的文件名改为dump.rdb即可
2、AOF
以日志的形式来记录每个写操作(增量操作),将Redis执行过程中的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复操作。
如果AOF与RDB同时开启,那么只有AOF有效
备份和恢复与rdb的操作是一样的
AOF默认是关闭的,需要在配置文件中修改appendonly no 改为yes
- 异常恢复
- AOF同步频率设置
八、主从复制
1、介绍
主机数据更新后根据配置和策略,自动同步到备机的master/slaver(主/从)机制,Master以写文主,Slave以读为主。
好处有读写分离,性能扩展,容灾快速回复
- 复制的原理
2、搭建一主多从
- 创建一个文件夹用来存主机和从机的配置文件,如myredis,再将一份配置文件复制到目录中。
- 创建主机和从机的配置文件,引入主要的配置,文件名用redis+端口号命名,如redis6379
- 开启各个服务器 redis-server /usr/local/mayredis/redis6379.conf
- 连接上个服务器 redis-cli -p 6379
通过客户机查看当前库的相关信息
info replication
- 在从机上配置它的主机
slaveof 主机ip 主机端口
如slaveof 127.0.0.1 6379
3、主从复制的一些机制(一主二仆)
- 当从机挂断以后,重新启动后,不会主动变为从机。
- 如要重新变为从机,就必须执行slaveof 127.0.0.1 6379操作
- 如在从机挂掉期间,主机做了写操作,那么从机连接后是可以得到这些数据的
- 当主机挂掉后,从机不会变成主机,从机在主机恢复后继续当从机
4、主从复制的一些机制(薪火相传,反客为主)
- 从机的下面还可以接从机,从机的下一级从机由从机管,不属于主机管
- 薪火相传的风险是一旦某个从机宕机,后面的从机就无法备份,且如果主机挂了就无法为下一级从机写数据了
- 用salveof no one 可以将从机变为主机
5、哨兵模式
是反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
操作步骤
- 创建哨兵的配置文件,sentinel.conf,注意名字不能错
其中mymaster 是为监控对象起的服务器的名称,127.0.0.1和6379是监控主机的IP和port,1为至少有多少个哨兵同意迁移的数量
- 启动哨兵
redis-sentinel sentinel.conf
- 一些机制
如果主机宕机,则会根据优先级别的的大小:slave-priorty默认为100来决定谁做主机
如果原主机重启后会变成从机
runid是redis实例启动后会随机生成的40位runid
九、redis的集群
redis采用了无中心化集群配置
优势:实现扩容,分摊压力,无中心化配置相对简单
劣势:
1、搭建集群
- 搭建集群节点服务器的配置文件,有几个服务器就配几个
使用这个可以批量修改数据
- 打开redis服务器
- 将节点合成合成一个集群
组合之前,要确定所有的redis实例都启动,且nodes-*.conf文件生成正常
要进入到rdis的src目录下
执行
replicas 1 即代表最简单的创建方式,即一个主机对一个从机
通过cluster nodes命令查看集群信息
- 说明
一个集群至少有3个主节点
分配原则尽量保证每个主数据库一些在不同的IP地址中,每个从库和主库不在一个IP地址 上。
- 什么是slots (插槽)
一个redis集群包含16384个插槽,每个数据库中的每个键都属于这16384个插槽的其中一个
集群使用公式CRC16(key)%16384来算key属于哪个槽
2、对集群的操作
- 连接
用redis-cli -p 6379 -c命令连接,-c 表示以集群的方式连接。
- set操作
在集群中不支持mset的操作因为多个键计算不了插槽的位置,如一定要使用,可用以下方法
mset k1{k} v1 k2{k} v2 用分组的方式去实现
- 查询集群中每个槽的键(只能看自己节点的插槽)
cluster getkeysinslot slot count 返回count个slot槽中的键
3、故障修复
- 如果主机宕机,则从机上位,若主机恢复,则变为从机
- 若某个节点的主机和从机都宕机后,可通过配置文件中的cluster-require-full-coverage的值来确定整个集群是否要停止,为yes就整个集群都挂掉,为no就继续运行,只是该节点的插槽无法使用
4、jedis连接集群
//创建配置
HostAndPort hostAndPort = new HostAndPort("192.168.6.148", 6379);
//通过配置对象连接集群
JedisCluster jedisCluster = new JedisCluster(hostAndPort);
jedisCluster.set("k3","v1");
String value = jedisCluster.get("k1");
System.out.println(value);
十、缓存的问题
1、缓存穿透
key所对应的数据在数据源中并不存在,每次针对key的请求从缓存获取不到,请求都会压到数据源上,从而可能压垮数据源,比如用一个不存在的用户id获取用户的信息,不论是缓存还是数据源都没有,若黑客利用此漏洞进行攻击可能压垮数据库
解决方法
2、缓存击穿
key对应的数据存在,但在redis中过期,此时若有大量并发请求发过来,这些请求发行缓存过期一般都会从后端数据库中加载数据并回设到缓存中,这个时候,大并发请求可能瞬间把后端数据库压垮
解决方法
3、缓存雪崩
在极少的时间段内,大量key集中过期,而服务器此时又在并发的服务这些key,
十一、分布式锁
1、概念
在上锁的同时设置过期时间
set key value nx ex 12 其中nx表示上锁,ex表示过期时间,这表示将这个key设成一把锁,在这个key还没被删除前,这个key就不能被再次上锁,实现分布式锁的方式就是不断的对这个key进行加锁,如果该锁没被释放,是加不了锁的,而如果某个事务对这个key加上了锁,就证明或取到这个锁了,才可继续执行事务
用代码实现
//通过UUID获取到一个随机的id
String uuid = UUID.randomUUID().toString();
//获取到这个锁
Boolean lock = redisTemplate.opsForValue().setIfAbsent("LOCK", uuid, 3, TimeUnit.MINUTES);
//判断有没有获取到锁
if(lock){
//判断num值是否为空
String num = (String)redisTemplate.opsForValue().get("num");
if (num.isEmpty()){
return;
}
//将num的值加一
int value = Integer.parseInt(num);
redisTemplate.opsForValue().set("num",++value);
//获取此时正在执行事务的锁的id
String uuid1 = redisTemplate.opsForValue().get("LOCK").toString();
//如果正在执行的事务的锁的id与当前的事务相同则释放
//以防止误删
if(uuid1.equals(uuid)){
redisTemplate.delete("LOCK");
}
}else {
//如果没获取到锁则在次获取
try {
Thread.sleep(100);
testLocal();
}catch (InterruptedException e) {
e.printStackTrace();
}
}