NoSQL(NoSQL=NotOnlySQL),意即“不仅仅是SQL”,泛指非关系型的数据库.
它们都有些共同的特征:不需要预定义模式:不需要事先定义数据模式,预定义表结构。数据中的每条记录都可能有不同的属性和格式。当插入数据时,并不需要预先定义它们的模式。
弹性可扩展:可以在系统运行的时候,动态增加或者删除结点。不需要停机维护,数据可以自动迁移。
NoSQL代表MongDB、Redis、Memcache.
2.什么是Redis?Redis即远程字典服务,是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、“Key-Value”数据库,并提供多种语言的API;redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。
Redis是现在最受欢迎的NoSQL数据库之一.
3.Redis的优势当前Redis已经成为了主要的NoSQL工具,其原因如下。
1)响应快速Redis响应非常快,每秒可以执行大约110000个写入操作,或者81000个读操作,其速度远超数据库。如果存入一些常用的数据,就能有效提高系统的性能。
2)支持8种数据类型它们是字符串、哈希结构、列表、集合、可排序集合,Geo类型,基数和位图。前5种比较常用。
比如对于字符串可以存入一些Java基础数据类型,哈希可以存储对象,列表可以存储List对象等。这使得在应用中很容易根据自己的需要选择存储的数据类型,方便开发。
对于Redis而言,虽然只有6种数据类型,但是有两大好处:一方面可以满足存储各种数据结构体的需要;另外一方面数据类型少,使得规则就少,需要的判断和逻辑就少,这样读/写的速度就更快。
3)操作都是原子的所有Redis的操作都是原子的,从而确保当两个客户同时访问Redis服务器时,得到的是更新后的值(最新值)。在需要高并发的场合可以考虑使用Redis的事务,处理一些需要锁的业务。
4)MultiUtility工具正是因为Redis具备这些优点,使得它成为了目前主流的NoSQL技术,在Java互联网中得到了广泛使用。
4.Redis应用场景在分布式系统中,服务器有多台,客户的请求可能会发送到不同的服务器,我们可以用redis解决多台服务器间的session共享。
在高并发的情况下,所有的请求直接访问数据库,数据库容易出现异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。
具体场景:
1、会话缓存(最常用)
2、消息队列(支付)
3、活动排行榜或计数
4、发布,订阅消息(消息通知)
6、秒杀活动,抢红包
二、Redis的安装和配置1.安装redisversion:'3.0'services:redis:restart:alwaysimage:/library/redis:5.0.9container_name:redisenvironment:-TZ=Asia/Shanghaiports:-6379:63792.使用Redis-cli连接Redis服务器
先进入redis容器内部:
dockerexec-it容器idbash
打开redis客户端
redis-cli
输入命令测试
3.使用Redis可视化管理工具连接Redis服务器RedisDesktopManager是redis可视化管理工具,redi可视化客户端,redis集群管理工具。
3.1下载和安装下载以后安装,直接下一步下一步就可以。
3.2创建连接连接成功后:
三Redis常用的5种数据类型Redis支持的8种数据类型中前5种比较常用:string(字符串),hash(哈希),list(列表),set(集合)及zset(sortedset:有序集合)。
Redis是一个key-value形式的存储系统,key是一个“字符串”,而value对应的则是前面提到的5种数据类型。
字符串(string)
这是最常见和最容易理解的一种数据类型,它表示存储在redis中的值是一个“字符串”类型的数据。但实际上,它还能存储整型数据,后面我们将通过INCR命令,对值进行自增操作。
哈希(hash)
又称“散列”,这种数据类型类似于Java中的Map类型。初学者可能会疑惑,前面的“字符串”类型,一个key一个value不就是Map类型么。
实际上,在本文开头提到,redis是一种key-value形式的存储系统,我们所说的redis数据类型指的是value的数据类型。所以哈希(hash)也就是value是类似Map的一种数据类型。在后面的章节中我们会更直观的感受到。
列表(list)
列表(list)也可以理解为数组,和在Java中的List类型类似。略微不同的是,Java中的列表可以是泛型类型,也就是说Java中的List数据结构可以是字符串、整型等。而在redis中列表中的数据类型则只有字符串类型。
集合(set)
set类型在redis中被称为集合,同样它和Java的Set集合相同。和redis的列表(list)类似,不同地是,列表(list)的数据是可以重复的且是插入有序,而集合(set)中的数据是不可重复的且是无序。
有序集合(zset)
有序集合(zset)尽管看起来是集合(set)类型多了“有序”的特性。但实际上,可以说它和哈希(hash)更相似。因为它和哈希(hash)一样也是Map类型,不同地是它的key是实际上的成员,而value则是用于排序的“分值”。这个特性能帮助我们快速的实现“点赞数最高倒序排列”等功能。
四Redis常用命令1.Redis字符串(String)常用命令1.set
setunamedaimenglaoshisetkeyvalue
get
getunamegetkey
mset
msetsexmage23msetkey1value1key1value1
4.mget
mgetsexagemgetkey1key2
5.自增
incrageincrkeyincrbyage3incrbykeyincrement
自减、
decragedecrkeydecrbyage3decrbykeyincrement
Setnx
existscity如果不存在city,则可以赋值,如果存在,则赋值失败设置message=hello保存时间60秒存数据取数据批量存储数据批量取数据增量正数为+负数为-判断字段是否存在给不存在的字段赋值如果字段已存在赋值失败返回某个对象所有的字段名返回某个对象所有的字段值列表左边添加一个值也可以添加多个值rpushlist1cc移除并返回列表左边第一个元素rpoplist1list1存在则左边添加一个bbrpushxlist1dd获取列表中所有元素0代表第一个元素-1代表最后一个元素返回下标为0的元素设置集合list1的第一个元素为aa返回列表的长度在bb前面添加一个ee只保留列表中第2到最后的元素将list1的最后一个元素弹出并添加到list2的左边
Brpoplpush
brpoplpushlist1list22Brpoplpush命令从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它;如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。4.Redis集合(Set)命令
sadd添加数据
saddmysetuser1,user2,user3saddsetvalue1value2。。
返回元素
smembersmyset随机返回一个元素,此元素还在集合中srandmembermyset随机移除一个元素spopmyset2移除集合中的一个或多个成员元素,不存在的成员元素会被忽略
scard返回集合的数量
scardmyset1返回集合的数量
smove移动数据
smovemyset1myset2user1返回myset1和myset2的交集
sunion并集
sunionmyset1myset2返回myset1和myset2的差集5.Redis有序集合(sortedset)命令
添加数据
。。
取数据
zrangeaccessset13withscores返回整个集合按分数递增zrevrangeaccessset0-1withscores返回分数在1到100之间的数据zrangebyscoreaccessset1100withscores返回分数在1到100之间的数据和分数的前1个zrangebyscoreaccessset-inf+infwithscores降序返回分数在1到100之间的数据
移除数据
将的分数增加2
返回指定元素的分数
返回的排名0代表第一查看所有的keykeysu*修改key的名称id为uid在集群模式下,key和newkey需要在同一个hashslot。key和newkey有相同的hashtag才能重命名
设置key的过期时间
expireuname20expirekey_nameseconds返回uname的过期时间返回指定key的过期时间时间单位为秒返回指定key的过期时间时间单位为毫秒
删除指定key的过期时间
persistuname将数据库0中的uname移动到数据库1select1查看数据库1是否有uname
返回key中值得类型
typeuname给频道my_channel发布消息用于将消息发送到指定的频道返回的接收到消息的订阅者数量
订阅消息
subscribemy_channelsubscribechannel_name返回结果包含返回值的类型(订阅成功)、订阅的频道名称、目前已订阅的频道数量
取消订阅
unsubscribemy_channelunsubscribechannel_name订阅所有以channel开头的频道
按模式取消订阅
punsubscribechannel:*查询至少有一个订阅者的频道
查看频道订阅数
pubsubnumsubmy_channelRedis服务器连接端口=6379=50=50=2=1000
如果使用的是yml格式则为:
jedis:pool:host:42.192.12.30port:6379config:maxTotal:50maxIdle:50minIdle:2maxWaitMillis:1000
注意:yml基本格式要求:1.大小写敏感;2.使用缩进代表层级关系;3.缩进只能使用空格,不能使用tab键,不要求空格个数,只需要相同层级左对齐(一般2或4个空格)。配置项格式为key:value,冒号后要有一个空格:
创建RedisConfiguration类,将配置文件信息注入进来
;;;;;;;;@ConfigurationpublicclassRedisConfiguration{@Autowired@Qualifier("")privateJedisPoolConfigjedisPoolConfig;@Bean(name="")publicJedisPooljedisPool(@Value("${}")Stringhost,@Value("${}")intport){returnnewJedisPool(jedisPoolConfig,host,port);}@Bean(name="")publicJedisPoolConfigjedisPoolConfig(@Value("${}")intmaxTotal,@Value("${}")intmaxIdle,@Value("${}")intminIdle,@Value("${}")intmaxWaitMillis){JedisPoolConfigconfig=newJedisPoolConfig();(maxTotal);(maxIdle);(maxWaitMillis);returnconfig;}}创建代码测试:
@RestControllerpublicclassTestController{@AutowiredprivateJedisPooljedisPool;@RequestMapping("testRedisPool")publicStringtestRedisPool(){Jedisjedis=();("name","aa");Stringval=("name");returnval;}}六、设置Redis访问权限1.设置配置文件路径ersion:'3.0'services:redis:restart:alwaysimage:/library/redis:5.0.9container_name:redisenvironment:-TZ=Asia/Shanghaiports:-6379:6379volumes:-./redis/:/usr/local/etc/redis/:["redis-server","/usr/local/etc/redis/"]2.配置文件中设置密码
在redis目录下创建文件
文件中添加:
requirepassdaimenglaoshi添加lua文件对应的数据卷command:["redis-server","/usr/local/etc/redis/"]
重新执行docker-compose
docker-composedowndocker-composeup-d3.2.在lua数据卷目录下创建lua文件
1.lua文件内容如下:
localkey=KEYS[1]localvalue=ARGV[1]localkey2=KEYS[2]localvalue2=ARGV[2]("set",key,value);("set",key2,value2);3.3.执行lua文件进入到redis容器
dockerexec-it容器idbash
执行lua脚本文件
redis-cli--eval/usr/local/etc/redis/lua/1.luanameage,aa18redis-cli-a密码--evallua文件路径key1key2,value1value2添加lua文件对应的数据卷-./redis/data/:/datadaemonize是用来指定redis是否要用守护线程的方式启动,yes代表后台运行redisdaemonizeyeslocalhost可以改为ipsentinelmonitormasterlocalhost63792这里的master是之前给主节点取的名字,可以改用ip3.3运行docker-compose文件3.4启动哨兵
分别进入三个容器内部,启动哨兵,可以开三个会话,方便测试
3.5连接redis服务器,查看节点状态从节点都启动以后,主节点可以看到有两个从节点连接上了,以及对应的ip,从节点可以看到主节点的ip
3.6测试主节点关闭,重新选举master关闭主节点:
从1被选举为新的主节点:
从2还是从节点
扩展:哨兵也可以单独创建
version:'3.0'services:sentinel1:restart:alwaysimage:/library/redis:5.0.9container_name:sentinel1environment:-TZ=Asia/Shanghaiports:-26380:26379volumes:-./sentinel/:/usr/local/etc/redis/:["redis-sentinel","/usr/local/etc/redis/"]sentinel2:restart:alwaysimage:/library/redis:5.0.9container_name:sentinel2environment:-TZ=Asia/Shanghaiports:-26381:26379volumes:-./sentinel/:/usr/local/etc/redis/:["redis-sentinel","/usr/local/etc/redis/"]sentinel3:restart:alwaysimage:/library/redis:5.0.9container_name:sentinel3environment:-TZ=Asia/Shanghaiports:-26382:26379volumes:-./sentinel/:/usr/local/etc/redis/:["redis-sentinel","/usr/local/etc/redis/"]十一Redis集群1.为什么要搭建Redis集群?
我们前面设置了主从,可以将读写分离,应对大的访问量,但是当访问量继续增大的情况下,一个主从还是不够,而且由于Redis主从架构每个数据库都要保存整个集群中的所有数据,容易形成木桶效应,所以之后的版本添加了集群(Cluster)架构,也就是可以将数据分布在集群中不同的redis节点,每个redis节点可以再搭建自己的主从。
2.Redis集群方案Redis集群方案基于分而治之的思想。Redis中数据都是以Key-Value形式存储的,而不同Key的数据之间是相互独立的。因此可以将Key按照某种规则划分成多个分区,将不同分区的数据存放在不同的节点上。这个方案类似数据结构中哈希表的结构。在Redis集群的实现中,使用哈希算法(公式是CRC16(Key)mod16383)将Key映射到0~16383范围的整数。这样每个整数对应存储了若干个Key-Value数据,这样一个整数对应的抽象存储称为一个槽(slot)。每个RedisCluster的节点——准确讲是master节点——负责一定范围的槽,所有节点组成的集群覆盖了0~16383整个范围的槽。
RedisCluster的槽位空间是自定义分配的,类似于Windows盘分区的概念。这种分区是可以自定义大小,自定义位置的。RedisCluster包含了16384个哈希槽,每个Key通过计算后都会落在具体一个槽位上,而这个槽位是属于哪个存储节点的,则由用户自己定义分配。例如机器硬盘小的,可以分配少一点槽位,硬盘大的可以分配多一点。如果节点硬盘都差不多则可以平均分配。
3.Redis集群实现原理(1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.
(2)节点的fail是通过集群中超过半数的master节点检测失效时才生效,所以集群中的节点数应该是2n+1.
(3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
(4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node-slot-key
我们以搭建三主三备集群为例。
创建redis_cluster文件夹,在文件夹中创建文件
4.1创建文件version:'3.0'services:redis_1:restart:alwaysimage:/library/redis:5.0.9container_name:redis_1environment:-TZ=Asia/Shanghaiports:-7001:7001-17001:17001volumes:-./redis/7001/:/usr/local/etc/redis/:["redis-server","/usr/local/etc/redis/"]redis_2:restart:alwaysimage:/library/redis:5.0.9container_name:redis_2environment:-TZ=Asia/Shanghaiports:-7002:7002-17002:17002volumes:-./redis/7002/:/usr/local/etc/redis/:["redis-server","/usr/local/etc/redis/"]redis_3:restart:alwaysimage:/library/redis:5.0.9container_name:redis_3environment:-TZ=Asia/Shanghaiports:-7003:7003-17003:17003volumes:-./redis/7003/:/usr/local/etc/redis/:["redis-server","/usr/local/etc/redis/"]redis_4:restart:alwaysimage:/library/redis:5.0.9container_name:redis_4environment:-TZ=Asia/Shanghaiports:-7004:7004-17004:17004volumes:-./redis/7004/:/usr/local/etc/redis/:["redis-server","/usr/local/etc/redis/"]redis_5:restart:alwaysimage:/library/redis:5.0.9container_name:redis_5environment:-TZ=Asia/Shanghaiports:-7005:7005-17005:17005volumes:-./redis/7005/:/usr/local/etc/redis/:["redis-server","/usr/local/etc/redis/"]redis_6:restart:alwaysimage:/library/redis:5.0.9container_name:redis_6environment:-TZ=Asia/Shanghaiports:-7006:7006-17006:17006volumes:-./redis/7006/:/usr/local/etc/redis/:["redis-server","/usr/local/etc/redis/"]4.2创建redis配置文件
在redis_cluster文件夹中,创建7001,7002,7003,7004,7005,7006文件夹,在文件夹中创建文件
开启redis集群功能cluster-enabledyes集群对外端口号cluster-announce-port7001Redis集群中节点允许不可用的最长时间,而不会将其视为失败。如果主节点超过指定的时间不可达,它将由其从属设备进行故障切换cluster-node-timeout5000
另外5个配置文件相同,只需要将端口号修改一下。
4.3执行docker-compose创建6个redis服务器docker-composeup-d4.4将6个redis节点搭建成集群
进入任何一个redis节点:
dockerexec-it容器idbash
输入命令:
:700142.192.12.30:700242.192.12.30:700342.192.12.30:700442.192.12.30:700542.192.12.30:7006--添加redis集群配置信息[外链图片转存中(img-Csxs0vCO-1669557586934)]6.3创建测试类测试[外链图片转存中(img-VfUi7Cyj-1669557586935)][外链图片转存中(img-UknG1VAF-1669557586935)]#3.什么是缓存击穿?缓存击穿是指一个Key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,导致数据库宕机。解决方案:1.定时任务主动刷新缓存启动定时任务,在redis缓存失效之前,主动查数据库更新缓存。2.使用互斥锁在根据key获得的value值为空时,先锁上,再从数据库加载,加载完毕,释放锁。若其他线程发现获取锁失败,则睡眠50ms后重试。至于锁的类型,单机环境用并发包的Lock类型就行,集群环境则使用分布式锁(比如redis的setnx)3.jvm缓存+redis缓存的多级缓存服务器只会在jvm缓存失效,且redis缓存也失效的情况下才会查询数据库,而多个服务器的jvm缓存失效时间是随机值,所以很大程度上避免的同时失效去查库的情况,由于所有服务器jvm缓存同时失效redis缓存也失效的可能性极低,所以数据库上重复的查询会很少