Redis
Nosql概述
读写分离(垂直拆分)+缓存机制的 MySQL 数据库:
分库分表+ 水平拆分 的 MySQL集群数据库:
其本质为数据库的 ==读== 和 ==写==:
MySQL的引擎复习:
MyISAM:使用 表锁 ,影响工作效率,高并发下会造成锁问题!
Innodb:使用 行锁。
什么是Nosql?
Nosql(Not Only SQL(不仅仅是SQL)):
非关系型数据库。
NoSQL的特点?
※解耦合
1.方便扩展(数据之间没有关系,很好扩展)
2.大数据量高性能(Redis一秒写8w次,读取11w次)
3.数据类型是多样性的(无需事先涉及数据库)
RDBMS(关系型数据库)和NoSQL(非关系型数据库)对比?
==RDBMS==:
1.结构化组织
2.SQL语言
3.数据和关系都存在单独的表中
4.数据定义语言操作
5.基础的事务
==NoSQL==:
1.不仅仅是数据
2.没有固定的查询语言
3.键值对存储,列存储,文档存储,图形数据库等等
4.最终一致性
5.CAP定理 和 BASE (异地多活)
6.高性能,高并发,高可拓(大数据的“3V”)
7.海量Volume,多样Variety,实时Velocity(大数据的“三高”)
统一数据服务层:
缓存的处理原理图:
NoSQL的四大分类
KV键值对:
1.新浪:Redis
2.美团:Redis + Tair
3.百度、阿里:Redis + Memocache
文档型数据库:
1.MongoDB:
1)MongoDB是一个基于分布式文件存储的数据库,由C++编写,主要用来处理大量文档。
2)MongoDB是一个介于关系型数据库和非关系型数据库中中间的产品。MongoDB是非关系型数据库中功能最丰富以及最像关系型数据库的。
2.ConthDB。
列存储数据库:
1.HBase(大数据)
2.分布式文件系统
图关系数据库:
其存储的关系信息。比如:朋友圈社交网络,广告推荐等等。
1.Neo4j。
2.InfoGrid。
Redis入门
Redis是什么?
Redis(Remote Dictionary Server)即远程字典服务。是一个==开源==的使用ANSI C语言编写,支持网络,key-value数据库,并提供多种语言的API。是当下最热门的NoSQL技术之一,也被称为结构化数据库。
Redis的作用?
Redis会周期的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并在其基础上实现了master-slave(主从)同步。
1.内存储存,持久化。持久化很重要(rdb,aof)。
2.效率高,可用于高速缓存。
3.发布订阅系统。
4.地图信息分析。
5.计时器,计数器(例如:浏览量)。
特性?
1.多样的数据类型
2.持久化
3.集群
4.事务
Redis的相关网站?
1.官网: redis.io/
2.中文网: www.redis.cn/
3.下载地址:官网下载即可
Windows安装
1.下载安装包:github.com/dmajkic/red…
2.得到压缩包,解压到环境目录下。
3.开启 redis-server.exe 即可。(默认端口号 6379)
4.使用redis客户端连接redis。保持后台运行 redis-server.exe的同时,开启 redis-cli.exe即可。
redis命令
一些测试命令?
1)ping:测试连接。(返回pang即为连接成功)
2)set yyy xxx:设置键值对 yyy=xxx 。
3)get yyy:通过key值yyy获取value值xxx。
Linux安装
1.下载安装包 redis-6.2.6.tar.gz。
2.解压Redis的安装包。
3.安装 gcc :
Tips:centos7自带的gcc版本是4.8,通过gcc官网安装手册可知,依赖文件太多了,手动升级安装太繁琐。可以通过安装devtoolset的方式间接升级gcc至高版本(4.8.5暂时未遇到问题)
sudo yum install centos-release-scl sudo yum install devtoolset-7-gcc* scl enable devtoolset-7 bash which gcc gcc --version
4.8.5版本:
yum install gcc
4.在redis目录下make操作。
5.redis的默认安装路径:/usr/local/bin。
redis-benchmark:性能测试工具,可在自己本子运行。
redis-check-aof:修复有问题的AOF文件。
redis-check-dump:修复有问题的dump.rdb文件。
redis-sentinel:Redis集群使用
redis-server:Redis服务器启动命令
redis-cli:客户端,操作入口
6.将redis压缩包解压后文件夹中的redis.conf拷贝到自建的 myconfig目录下。防止配置文件恶意修改。之后就用自建的myconfig下的配置文件进行启动。修改配置文件daemonize为yes,保证其可以在后台运行。
7.启动redis的服务:redis-server myconfig/redis.conf。表示使用myconfig/redis.conf配置文件来启动redis服务。
8.使用Redis客户端进行连接测试:
①执行
redis-cli -p 6379。②测试是否连接成功:
ping③set and get。
④
keys *:查看当前所有的键。
9.查看redis进程是否开启:
ps -ef | grep redis:过滤查看关于redis的进程和信息。
10.关闭Redis服务的方式:
shutdown
exit
测试性能
redis-benchmark是一个压力测试工具,属于官方自带的。
Redis的基本知识
数据库
redis有16个数据库。默认使用的是第0个。
vim myconfig/redis.conf
数据库相关操作:
dbsize:查看数据库数据量 select [index]:切换数据库index
set,get,keys *:前文有记载,此处不做介绍。
flushall:清除所有数据库
flushdb:清除当前数据库
Redis是单线程的!
Redis是很快的,其是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽的。
※Redis为什么单线程还这么快?
多线程不一定比单线程效率高!操作系统速度排序:CPU>内存>硬盘。redis是将所有数据放在内存中的,多线程相比单线程存在CPU的上下文切换,较耗时。对内存系统来说,如果没有上下文切换效率就是最高的。
五大数据类型
百度百科:
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作==数据库==、==缓存==和==消息中间件==。 它支持多种类型的 数据结构,如 字符串(strings),散列(hashes),列表(lists),集合(sets),有序集合(sorted sets)与范围查询, bitmaps,hyperloglogs和地理空间(geospatial)索引半径查询。Redis内置了复制(replication),LUA脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence),并通过 Redis哨 兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)。
Redis-Key
基本命令:
exists [key]:判断当前key是否存在
move [key] [index]:将当前key移动到指定数据库index中
expire [key] [time]:设置当前key过期时间time
ttl [key]:查看当前key的剩余时间
type [key]:查看当前key对应的类型
遇到未知的命令可在官网进行搜索查看:
String 类型
基本命令:
append [key] [String]:将string拼接至key所储存的字符串中,如果当前key不存在,那相当于set key string
strlen [key]:获取到key对应值的长度
incr [key]:使key所对应值+1
decr [key]:使key所对应值-1
incrby [key] [length]:使key所对应值+length
decrby [key] [length]:使key所对应值-length
getrange [key] [startIndex] [endIndex]:截取key中第startIndex~endIndex对应的字符串。
setrange [key] [startIndex] [string]:使用string值替换key对应值中从startIndex开始的字符串。
栗子:
set key1 abcdefg OK get key1 "abcdefg" setrange key1 1 xx (integer) 7 get key2 "axxdefg"※setex & setnx
setex (set with expire):设置过期时间
setnx (set if not exist):不存在再设置,在分布式锁中常使用。
setex [key] [time] [string]:设置key所对应的string值,并且加入过期时间time
setnx [key] [string]:如果不存在才会设置 key = value,否则无效。
mset k1 v1 k2 v2 k3 v3:批量设置键值对k1=v1,k2=v2,k3=v3。
mget k1 k2 k3:批量获取多个值。
msetnx k1 v1 k4 v4:同区分是否存在原k1 & k4。其是一个原子性操作,要么一起成功,要么一起失败。
getset [key] [value]:取得jkey对应旧值,替换新值value。
List 类型
基本命令:
LPUSH list one:将一个值one或多个值插入到列表头部(左)。
LPUSH list two:同上
LPUSH list three:同上
LRANGE list 0 -1:获取到list表中所有值:
"three" "two" "one"(先进后出)
LRANGE list 0 1:获取到list表中第一位到第二位的值:
"three" "two"
RPUSH list four:将一个值four或多个值插入到列表尾部(R)。
LRANGE list 0 -1:
"three" "two" "one" "four"
LPOP list:从左边移除第一个元素(上例即为 移除three)
Lindex list [index]:通过下标从坐标获得第index的对应值。
Llen list:获取列表list的长度。
Lrem list n [string]:从list列表中移除n个string值元素。
**ltrim list m n **:仅保留m,n两处的元素,其余部分被裁剪。
rpoplpush:移除列表中最后一个元素,将他移动到新的列表中。
栗子:
rpush mylist "hello0" (integer) 1 rpush mylist "hello1" (integer) 2 rpush mylist "hello2" (integer) 3 rpoplpush mylist myotherlist "hello2" lrange mylist 0 -1 1) "hello0" 2) "hello1" lrange myotherlist 0 -1 1) "hello2"lset list 0 [string]:将列表中指定下标的值替换成另外一个值,更新操作。如果不存在,会报错。
linsert list before [string] [newStr]:在list列表中的string字符串前插入指定字符串newstr。
linsert list after [string] [newStr]:与上同理。
总结:
1.它实际上是一个双向链表。
2.如果key不存在,创建新的链表;如果存在,新增内容,如果移除所有值,空链表代表不存在!在两边插入或改动值,效率最高!中间元素相对效率较低。
Set类型
基本命令:
sadd myset [string]:向myset集合中加入一个string类型元素。
smembers myset:查看myset集合中所有值。
sismember myset [string]:判断myset集合中是否存在 string字符串。
scard myset:获取myset集合中的元素个数。
srem myset [string]:移除myset集合中的string元素。
srandmember myset [integer]:从myset集合中随机获取integer个元素。(抽奖)
spop myset:随机移除一个元素。
smove myset myset2 [string]:将string值从myset移动到myset2集合中。
数学集合类
sdiff set1 set2:求set1集合与set2集合的差集。
sunion set1 set2:求set1集合与set2集合的并集。
sinter set1 set2:求set1集合与set2集合的交集。
Hash(哈希)类型
基本模型:
key-map:key-<key,map>(套娃)
基本命令:
hset myhash [key] [value]:将键值对key=value存入myhash集合。
hget myhash [key]:在hash集合中查找key所对应元素。
hmset myhash key1 value1 key2 value2:将键值对key1=value1,key2=value2插入myhash集合中。(如果存在key1或key2则覆盖value值)
hmget myhash key1 key2:获取myhash集合中key1和key2对应的value值。
hgetall myhash:获取myhash集合中所有的子key值和value值。
hdel myhash key1:删除myhash集合中的key1元素。
hlen myhash:获取myhash集合中的元素个数。
hexists myhash [key1]:判断myhash集合中是否存在key1键。
hkeys myhash:获取当前myhash集合的所有子key。
hvals myhash:获取当前myhash集合所有value。
hincrby myhash [key] n:令myhash集合中key元素对应value值增加n。
总结:哈希更适合存对象。
Zset(有序集合)
基本命令:
zadd myset 1 one添加一个值one,并标号为1,此标号用于排序。
zadd myset 2 two 3 three:添加多个值,并标号用于排序。
zrange myset 0 -1:查看myset集合中所有的值(不包含序号)
三种特殊类型
地理位置Geospatial
基本命令:
#getadd 添加若干个地理位置信息
#规则:南北极无法直接添加
#参数:key value(经度,纬度,名称)
#有效的经度 -180~180
#有效的纬度 -85~85
geoadd china:city 116.40 39.90 beijing
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing
geoadd china:city 114.05 22.52 shenzhen
geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
#getpos 获取若干个对应城市名称的经纬度
geopos china:city beijing
geopos china:city chongqing xian
#geodist 返回两个指定两个位置的距离
geodist china:city beijing shanghai km
以km为单位获取北京到上海之间的距离
#georadius 筛选距离信息符合要求的城市信息
georadius china:city 110 30 500 km
以110 30位置为圆心,以500km为半径范围内的china:city集合中的城市名
georadius china:city 110 30 500 km withdist withcoord count 2
在china:city集合中查询以110 30为圆心,500km为半径的城市经纬度以及名称信息,最多查出2条。
#georadiusbymember 以某个城市名为中心查找附近的城市名
georadiusbymember china:city beijing 1000 km
※ GEO底层的实现原理其实是Zset!我们可以使用Zset命令来操作GEO!
zrange china:city 0 -1
<查看china:city集合中所有城市的名称>
zrem china:city beijing
<移除指定元素>
基数统计Hyperloglog
基本命令:
pfadd mykey a b c d e f g:将abcdefg放入mykey集合中
pfcount mykey:统计放入mykey集合中的元素个数(仅统计不重复的元素数量)
pfmerge mykey1 mykey2 mykey3:合并三个集合1,2,3的不重复元素。新集合默认为mykey1。
使用场景:如果允许少量容错,则可用于网页浏览用户量等等基数计数。
Bitmaps位存储
使用场景:存储用户一周的打卡量:
setbit sign 0 1:用户星期日已打卡
setbit sign 1 0:用户星期一未打卡
setbit sign 2 0:用户星期二未打卡
......
getbit sign [integer]:查询对应某天是否打卡
bitcount sign:统计打卡的天数,可用于判断全勤奖的获得
事务
概述
※Redis单条命令是保存原子性的,但事务不保证原子性!
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,会按照顺序执行:一次性,顺序性,排他性!执行一系列的命令!
Redis的事务:
①开启事务(multi)
②命令入队(...)
③执行事务(exec)
执行事务时,按照入队顺序依次执行。
④放弃事务(discard)
异常的处理:
编译型异常:即代码语法有问题,事务中所有命令都不会被执行。
运行时异常(1/0):其他语句可正常执行,逻辑错误异常语句不执行。(比如对字符串自加)
监控
悲观锁:有操作便加锁
乐观锁:有监控也不一定加锁
多线程案例的乐观锁操作:
客户端1:
set money 100 #设置余额100
set out 0 #设置支出0
watch money #监控money对象
multi #开启事务
decrby money 20
incrby out 20
(在事务执行前进行第二线程的操作⬇)
exec #执行事务失败(原理是执行时比对原money值是否发生变化,若变化则执行失败)
Jedis
使用java来操作Redis
实例:
1.加入依赖
<--jedis的包--!>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<--fastjson--!>
<dependency><groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version></dependency>
2.测试连接
public class TestPing {
public static void main(String[] args){
Jedis jedis = new Jedis("主机号","端口号");
sout(jedis.ping());//pong
jedis.flushDB();//清空数据库
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","zs");
Transaction multi = jedis.multi();//开启事务
String result = jsonObject.toJSONString();
//开启监视
jedis.watch(result);
try{
multi.set("user1",result);
multi.set("user2",result);
multi.exec();//执行事务
}catch(Exception e){
multi.discard();//放弃事务
e.printStackTrace();
}finally{
sout(jedis.get("user1"));
sout(jedis.get("user2"));
jedis.close();//关闭连接
}
}
}
SpringBoot集成Redis
说明:在SpringBoot2.x之后,原来使用的Jedis被替换为了lettuce。
Jedis:采用的直连,多个线程操作不安全。如果要安全,需使用jedis pool连接池。
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况。
使用步骤:
1.创建springboot项目,勾选nosql中的redis。
2.导入依赖
3.书写配置文件规定主机号,端口号等等。
4.测试:
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void test1(){
//redisTemplate
//opsForValue:操作字符串,类似String
//opsForList:操作List
//opsForSet
//opsForHash
//opsForZSet
//opsForGeo
//opsForHyperloglog
//除此之外,常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD
redisTemplate.opsxxx();
//连接的作用
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushdb();
connection.flushAll();
}
}
※存储对象的操作:
工作中常常使用JSON方式来存储对象,以免其输出转移编码。
在企业开发中,所有实体类pojo中的存储都需要 序列化,可以让实体类实现 Serializable接口。SpringBoot集成Redis的配置类中存在序列化信息。
但公司中常常使用自己书写的工具类,以便轻量级开发。
Redis.conf详解
学习过程中,该文件被放在myconfig/Redis.conf下,是一套附件,修改改此文件即可,防止修改错误的发生。
单位?
Redis对字母大小写不敏感。
包含?
类比于关键字import,标签等。
网络?
bind (ip地址) #绑定的ip
protected-mode yes #保护模式
port 6379 #端口号
通用General?
daemonize yes #以守护进程的方式运行
pidfile /var/run/redis_6379.pid #如果以后台的方式运行,我们需要一个pid文件
loglevel notice #日志信息,四个级别
logfile "" #日志文件位置名
database 16 #数据库默认个数16
always-show-logo yes #是否默认显示logo
快照?
做持久化操作的,简而言之:在规定时间执行多少次操作,就会持久化到文件**.rdb.aof**。
redis是内存数据库,如果没有持久化机制,那么数据断电即失。
save 900 1 #900秒内至少1个key被改变,进行一次持久化
save 300 10 #与上同理
save 60 10000
stop-writes-on-bgsave-error yes #如果持久化出错,则停止写操作
rdbcompression yes #是否压缩rdb文件,需要消耗一些cpu资源
rdbchecksum yes #保存rdb文件时,进行错误的校验
dir ./ #rdb文件保存的路径
replication 主从复制,暂不详解
SECURITY安全?
设置密码(默认无密码)
config get requirepass #获取到当前是否设置密码
config set requirepass #设置密码
auth 【密码】 #登录
客户端限制CLIENTS?
maxclients 10000 #设置客户端最大容纳数量10000
maxmemory <bytes> #redis的最大内存容量设置
maxmemory-policy noeviction #内存溢出后处理策略
APPEND ONLY模式 aof配置
appendonly no #默认是不开启aof模式的,默认使用rdb方式进行持久化的,在大部分情况下,rdb够用!
appendfilename "appendonly.aof" #持久化的文件的名字
appendfsync everysec #每秒执行一次sync同步,参数还有always和no。
Redis持久化
RDB(Redis DataBase)
工作原理:
Redis会单独创建一个fork子进程来进行持久化,会先将数据写入一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件,其性能极高。RDB比AOF更加高效,但其缺点是如果最后一次持久化失败则数据丢失。
RDB保存的文件是dump.rdb,都是在配置文件中的快照进行配置的。
dbfilename dump.rdb
save 60 5
触发机制:
1.save的规则满足的情况下,会触发rdb机制
2.执行flushall命令,也会出发rdb规则。
3.退出redis,也会产生rdb文件
备份之后就自动生成一个dump.rdb。
如何恢复rdb文件
1.只需将rdb文件放在我没redis启动目录下即可,redis启动会自动检查dump.rdb回复其中的数据。
2.查看需要存在的位置:
config get dir
1)"dir"
2)"/usr/local/bin"
RDB的优缺点:
优点:
1.适合大规模的数据恢复。
2.如果对数据完整性要求不高,适合使用。
缺点:
1.需要一定时间间隔进程操作。
2.fork进程的适合,会占用一定的cpu空间。
AOF(Append only file)
工作原理:以日志形式记录每个写操作,将Redis执行的所有指令记录下来,只需追加无法改写文件,redis重启时会根据日志文件内容将指令从前到后执行一次以完成数据恢复。
默认不开启,需手动在配置文件中开启。后重启,在redis配置文件的目录下即出现 appendonly.aof文件。
修复aof:
如果aof文件被恶意修改,导致aof译码时出错,可使用redis-check-aof对aof文件进行修复。但错误行数据会被移除。
优缺点:
优点:
1.可以改为每一次修改都同步,文件完整性会更好。
2.默认开启时每秒同步一次,可能会丢失一秒的数据。
缺点:
1.相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢!
2.aof运行效率比rdb慢。aof默认为无限追加,所有其配置文件会越来越大,达到规定值时,Redis还能对AOF文件进行后台重写,使得其文件体积不至于过大。
扩展:
1.在同时开启两种方式时:
在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。 RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,那要不要只使用AOF呢?作者建议不要,因为更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留着作为一个万一的手段。
2.性能建议:
因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留 save 900 1 这条规则。
如果Enable AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite 的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。 如果不Enable AOF,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就是这种架构。
Redis发布订阅
定义:Redis发布订阅 是一种消息通信模式。
命令:
subscribe [name]:订阅一个给定的名为name的频道信息。
publish [info]:发布一条动态【信息】
使用场景:
1.实时消息系统。
2.实时聊天。(频道当作聊天室,使信息回显给所有人)
主从复制
原理以及用处
概念:将一台Redis服务器的数据,复制到其他的Redis服务器上,数据的复制是单向的,只能从master->slave,master处理写的操作,slave处理读的操作。
※作用:
1.数据冗余:实现了数据的热备份,是持久化之外的一种数据冗余方式。
2.故障恢复:当主节点出现问题时,可以有节点提供服务,实现快速的回复数据,是一种服务的冗余。
3.负载均衡:配合读写分离,可以由主节点提供写服务,从节点提供读服务,分担雾浮起负载,尤其是写少读多的情况下,通过多个从节点分担负载,可以大大提高Redis服务器的并发量。
4.高可用基石:其还是 哨兵 和 集群 能够实施的基础,因此说其是Redis高可用的基础。
一般来说,单台Redis服务器最大使用内存不得超过20G。
环境配置
一般来说,只需要配置从机,不用配置主库!
info replication #查看主从服务器信息
从机的配置文件拷贝后一般需要改以下配置:
1.端口号:6380
2.进程号:pidfile:/var/run/redis_6380.pid
3.日志文件名:logfile "6380.log"
4.数据持久化文件:dbfilename dump6380.rdb
默认情况下,所有服务器都是主机
配置从机时只需要 **salveof [主机名] [端口号]**命令即可,端口号是 主机的端口号(认老大原理)
但实际上配置主从关系应在配置文件中指定:
在redis配置文件中,搜索 replication相关信息,修改 repilicaof <masterip> <masterport>改为固定值即可。
拓展:
主机可以写,从机只能读。主机中的所有信息和数据,都会自动保存到从机。
主机断了以后,从机依旧附属于主机,但无法进行写的操作。如果从机断线,重连后依旧能获取到主机信息。
谋朝篡位:如果主机断开了连接,可以使用slaveof no one 将自己变为主机。如果这个时候主机修复了,只能重新配置从机信息了。
哨兵模式
(自动选取主机/老大 的方式)
(自动版谋朝篡位)
概述:哨兵模式是一种特殊模式,首先Redis提供了哨兵的命令,哨兵是一个独立进程,它会独立运行。其原理是 哨兵通过发送命令,等待Redis服务器相应,从而监控多个Redis的运行状态。
为了防止哨兵服务器宕机:(多哨兵模式)
解释:哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
基本用法:
1.配置哨兵配置文件 sentinel.conf
sentinel monitor [name] [ip] [port] 1 # 设置监控IP+port的服务器且名为name的哨兵系统,1代表开启投票机制
2.启动哨兵模式
redis-sentinel [sentinel.conf文件]
优缺点:
优点:
1.哨兵集群,基于主从复制,所有的主从复制优点都有。
2.主从可以切换,故障可以转移,系统可用性更好。
3.哨兵模式是主从模式的升级,手动到自动,更加健壮。
缺点:
1.Redis不好在线扩容,一旦到达上限集群容量,扩容十分麻烦。
2.实现哨兵模式的配置较麻烦。
缓存穿透和雪崩
缓存穿透(查不到)
概念:
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案:
1.布隆过滤器:一种数据结构,对所有可能查询的参数以hash的方式存储,在控制层进行校验,不符合则丢弃,从而避免对底层存储系统的查询压力。
2.缓存空对象:当存储层不命中后,即使返回的空对象也会被缓存起来,再设置一个过期时间。从而保护了后端数据源。
两个问题:
1.如果控制能被缓存,意味着缓存需要更多空间存储更多的键,因为当中可能会有很多空值的键。
2.即使对控制设置了失效时间,还是会存在缓存层和存储层数据有一段时间窗口不一致,对于需要保持一致性的业务有影响。
缓存击穿(量太大,缓存过期)
这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。
解决方案:
1.设置热点数据永不过期。
2.加互斥锁:使用分布式锁,保证对每个key同时只有一个线程去查询后端服务,其他线程无权限,因此只需要等待即可。对分布式锁的考验极大。
缓存雪崩
概念:
缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis 宕机!产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓 存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
使用环境:
双十一当天,会停掉一些服务,比如退款。(服务降级)
解决方案:
1.redis高可用:既然redis有可能挂,那么多增设几台redis服务器,即 集群搭建思路 。
2.限流降级:
缓存失效后,通过加锁或者队列的方式来控制数据库读写缓存的线程数量,比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
3.数据预热:
正式部署前,将可能的数据线预先访问一遍,这样部分可能大量访问的数据会加载到缓存中,再即将发生大规模访问前手动加入加载缓存不同的key,设置不同的过期时间,让其失效时间尽量均衡。
SpringCloud微服务
1.认识微服务
1)微服务架构
微服务的架构图:
微服务技术完整:
2)单体架构与分布式架构
单体架构:
概念:
将业务的所有功能集中再一个项目中开发,打包成一个包进行部署。
优点:
a.架构简单
b.部署成本低
缺点:
c.耦合度高
分布式架构 :
概念:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,成为一个服务。
优点:
a.降低服务耦合
b.有利于服务升级拓展
缺点:
c.架构非常复杂
d.运维,监控,部署难度提高
待解决的问题:
c.服务拆分粒度如何处理?
d.服务集群地址如何维护?
e.服务之间如何实现远程调用?
f.服务健康状态如何感知?
3)微服务概述
概念:
微服务是一种经过良好架构设计的分布式架构方案。
特征:
a.单一职责:微服务拆分粒度更小,每一个服务都对应唯一的的业务能力,做到单一职责,避免重复开发。
b.面向服务:微服务对外暴露业务接口。
c.自治:团队独立,技术独立,数据独立,部署独立。
d.隔离性强:服务调用做好隔离,容错,降级,避免出现级联问题。
4)SpringCloud框架概述
概念:
SpringCloud是目前国内最广泛的服务框架,其官网地址: spring.io/projects/sp…
springcloud与springboot版本兼容:
5)服务拆分和远程调用
服务拆分:
实际案例:
订单信息和用户信息 服务拆分。
a.不同微服务,不要重复开发相同业务。
b.微服务数据独立,不要访问其他微服务的数据库。
c.微服务可以将自己的业务暴露成接口,供其他微服务调用。
拆分实例demo问题解决:
Q:导入项目时遇到 java发行版本错误5?
A:解决方案1:添加以下内容
<properties>
<java.version>1.8</java.version>
</properties>
解决方案2:添加以下内容:
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler>
</properties>
远程调控
实际案例:根据订单id查询订单的同时,吧订单所属用户信息一起返回。
RestTemplate的使用
使用条件:
①基于RestTemplate发起的http请求实现远程调用。
②http请求做远程调用是与语言无关的调用,只有知道对方的ip,端口,接口路径,请求参数即可。
使用示例:
1.在 主启动类(配置类) 下注入RestTemplate对象:
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//将RestTemplate注入容器
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.在orderService中发起http请求:
@Service
public class OrderService{
@Autowired
private OrderMapper orderMapper;
//自动注入restTemplate对象
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId){
//查询订单
Order order = orderMapper.findById(orderId);
//利用RestTemplate发起http请求,查询用户
String url = "http://localhost:8081/user/"+order.getUserId();
//GET方式访问
User user = restTemplate.getForObject(url,User.class);
order.setUser(user);
}
}
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//将RestTemplate注入容器
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
测试问题:
跨服务器的远程调用需要双开服务器,否则会报 Connection refused:connect错误。
6)Eureka注册中心
提出问题:
①服务消费者该如何获取服务提供者的地址信息?
答:服务提供者启动时向eureka注册自己的信息。euraka保存这些信息。消费者根据服务名向eureka拉去提供者信息。
②如果有多个服务提供者,消费者如何选择?
答:负载均衡算法选择其中一个。
③消费者如何得知服务提供者的健康状态?
答:服务提供者每隔30s向EurakaServer发起心跳,报告健康状态。eureka会更新记录服务列表信息,心跳不正常会被剔除。消费者就可以拉渠道最新的信息。
总结:
在Euraka架构中,微服务角色有两类:
1.EurakaServer:服务端,注册中心。其包含功能:
①记录服务信息。
②心跳监控(轮询机制)。
2.EurakaClient:客户端。其包含功能:
①Provider:服务提供者,其需要做两件事:
Ⅰ.注册自己信息到EurakaServer
Ⅱ.每隔30s向EurakaServer发送心跳。
② consumer:服务消费者。其需要做两件事:
Ⅰ.根据服务名称从EurakaServer拉取服务列表。
Ⅱ.基于服务列表做负载均衡,选一个微服务后发起远程调用。
搭建EurakaServer
1.引入euraka-server依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
2.添加@EnableEurakaServer注解
@SpringBootApplication
@EnableEurekaServer
public class EurakaApplication {
public static void main(String[] args) {
SpringApplication.run(EurakaApplication.class,args);
}
}
3.在application.yml中配置euraka地址信息。
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
注册user-server
操作步骤:
1.引入eureka-client依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>
2.在application.yml配置以下内容。
spring:
application:
name: xxxserver
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
多次启动相同服务
采用改端口拷贝方式解决端口冲突问题。
选中 服务栏的要双开的服务 Ctrl+D 复制项目,修改端口(在environment中的VM options输入-Dserver.port=8082)。
服务拉取
概念:基于服务名称获取服务列表,然后再对服务列表做负载均衡。
步骤:
1.修改OrderService的代码,修改访问的url路径,用服务名称代替IP,端口:
String url = "http://userservice/user/"+order.getUserId();
2.再order-service项目的启动类OrderApplication中的RestTemplate添加 负载均衡 注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
※切记勿忘添加注解,第一次测试无法访问原因是未加注解,根据名称无法访问到。
Ribbon负载均衡
流程图:
细则流程图:
IRure中的策略:
默认策略:(ZoneAvoidanceRule):在一个区域Zone的服务进行轮询。
主观修改轮询规则的方法:
1.再服务启动类中定义一个新的IRule:(作用于全局,所有服务均为随机)
@Bean
public IRule randomRule(){
//返回值可以是IRule中的任意一种Rule
return new RandomRule();
//样例写法即为随机选择服务
}
2.在yml文件中配置轮询规则:(作用于局部,仅作用域服务名称对应服务)
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
饥饿加载
概念:默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: #指定饥饿加载的名称
-orderservice
-xxx
※ 通过实例测试,饥饿加载的网页运行效率是之前的十倍有余。
7)Nacos注册中心
Nacos简介:阿里巴巴的产品,是SpringCloud中一个组件,相比eureka功能更加丰富,在国内受欢迎程度更高。
下载与安装
执行命令:
startup.cmd -m standalone
# 单机模式启动
进入弹出网址,登录。(初始用户名和密码均为nacos)
服务注册
步骤:
1.添加依赖
<!--springcloudalibaba的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2.去掉eureka的依赖
3.去掉yml文件中eureka的配置。
4.添加nacos的配置信息:
cloud:
nacos:
server-addr: localhost:8848
nacos服务的分级存储模型
内容:
①一级是服务,例如userservice
②二级是集群,例如杭州or上海集群
③三级是实例,例如杭州机房的某台部署了userservice的服务器
设置步骤:
修改application.yml文件,做以下修改:
spring:
cloud:
nacos:
discovery:
cluster-name: com.alibaba.cloud.nacos.ribbon.NacosRule #集群名
NacosRule和权重负载均衡
NacosRule负载均衡规则:
①优先选择同集群服务实例列表
②本地集群找不到提供者,才回去其他集群寻找,并且会报错警告
③确定了可用实例列表后,再采用随机负载均衡挑选实例
根据权重负载均衡:
内容:Nacos提供权重控制负载均衡规则。
步骤:再nacos界面中设置服务权重(0~1),权重大的服务实例更容易被访问到。比如,权重为0.1与1的服务实例,1的访问概率约为0.1的十倍。权重为0时服务不会被访问到。
Nacos环境隔离
内容:
①namespace来做环境隔离
②每个namespace都要唯一id
③不同namespace下的服务不可见。
使用步骤:
再yml配置文件中做以下配置:
spring:
cloud:
nacos:
discovery:
namespace: xxx #此处填写命名空间的uid
eureka和nacos对比
共同点:
①都支持服务注册和服务拉取
②都支持服务提供者心跳方式做健康检测
不同点:
①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时模式采用主动检测模式。
设置非临时实例的方法,再yml配置文件中修改以下内容:
spring:
cloud:
nacos:
discovery:
ephemeral: false #是否为临时文件
②临时实例心跳不正常会被剔除,非临时实例不会被剔除。
③Nacos支持服务列表变更的推送模式,服务列表更新及时。
④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式,Eureka采用AP模式。
统一配置管理
进入 nacos->配置中心->新建配置->命名配置名为 【服务名-开发模式.yaml】->补充配置内容 步骤即可。
命名模式样例:userservice-dev.yaml -> 用户服务的开发者模式
配置内容样例:
patterns:
dateformat: yyyy-MM-dd HH-mm-ss
由于项目启动时会优先访问 nacos 的共有配置文件,故需提前得知nacos的内存地址,其存储在服务的bootstrap.yaml(该文件优先级远高于服务的配置文件)中。
步骤:
①引入Nacos的配置管理客户端依赖:
<!--nocos的配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
②在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring:
application:
name: userservice #服务名称
profile:
active: dev #开发环境
cloud:
nacos:
server-addr: localhost:8848 #Nacos地址
config:
file-extension: yaml #文件名后缀
样例测试展示:
在nacos中新建一个统一配置管理。
按照以上配置依次配。
最后在userservice服务中进行测试:
@RestController
@RequestMapping("/user")
public class UserController {
//注入nacos的配置属性
//此处@Value可换为nacosValue
//@Value(${key})注解可以获取到 配置文件中的属性值
@NacosValue("${pattern.dateformat}")
private String dateformat;
//编写controller,通过日期格式化器格式化现在时间并返回
@GetMapping("now")
public String now(){
return LocalDate.now().format(
DateTimeFormatterl.ofPattern(dateformat,Locale.China))
}
}
配置热更新:
方法一:在服务的Controller类上加 @RefreshScope 注解实现热刷新。
@RefreshScope 注解需配合 @Value注解一同使用。
方法二:创建一个专门的配置类实现热刷新:(加入@ConfigurationProperties注解)
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
多环境共享配置
优先级排序:服务名-profile.yaml > 服务名.yaml > 本地配置。
Nacos集群的搭建
P29详细讲解
认识集群(结构图):
数据库集群的作用是同步Nacos注册中心的数据。
搭建步骤:
1)搭建数据库集群,初始化数据库表结构
2)配置nacos
集群配置,数据库配置......
3)启动nacos集群(多个nacos节点)
4)使用nginx实现反向代理
8)Http客户端Feign
RestTemplate存在的问题
之前利用RestTemplate发起远程调用的代码:
String url = "http://userservice/user/"+order.getUserId();
User user = restTemplate.getForObject(url,User.class);
分析问题:
1.代码可读性差。
2.参数复杂URL难以维护。
Feigh的概述
概念:Feigh是一个声明式的http客户端。
使用步骤:
①在使用者上引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
②加Feigh的注解:
@EnableFeighClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args){
SpringApplication.run(OrderApplication.class,args);
}
}
③编写Feigh客户端:
首先明确发起请求的五个必要信息:
服务名称:userservice
请求方式:GET
请求路径:/user/{id}
请求参数:Long id
返回值类型:User
使用Feigh仅需要将以上信息交给Feigh即可:
@FeighClient("userservice")
public interface UserClient {
@GetMapping
User findById(@PathVariable("id") Long id);
}
※测试时注意去掉有关于RestTemplate的配置:包括yml中的namespace和
在Order的service层进行测试:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById (Long orderId){
Order order = orderMapper.findById(orderId);
User user = userClient.findById(order.getUserId());
order.setUser(user);
return order;
}
}
自定义Feign的配置
自定义设置Feign的日志:
方式一(配置文件yml):
① 全局生效
feign:
client:
config:
default: #全局配置
loggerLevel: FULL #日志级别
② 局部生效:
feign:
client:
config:
userservice: #局部配置
loggerLevel: FULL
方式二(java代码):
声明一个配置类:
public class FeignClientConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
全局配置:将以下注解放在启动类上:
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
局部配置,将以下注解放在配置类上:
@FeignClient(value = "userservice",configuration = FeignClientConfiguration.class)
Feign的性能优化
Feign的底层客户端实现:
1.URLConnection:默认实现,不支持连接池。
2.Apache HttpClient:支持连接池。
3.OKHttp:支持连接池。
优化方式:
①使用连接池代替默认的URLConnection。
②日志级别,最好使用basic或者none。
优化步骤:
①引入依赖:
<!--引入httpClient依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
②修改配置文件
feign:
httpclient:
enabled: true #开启HttpClient开关
max-connection: 200 #最大连接数
max-connections-per-route: 50 #单个路径的最大连接数
Feign的最佳实践
方式一(继承):
给消费者的FeignClient和 提供者的controller 定义统一的父接口作为标准。
public interface UserAPI {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
但该方案也有一定缺点:
①由于二者的API完全吻合,从而造成了服务紧耦合,不宜维护和修改。
②父接口参数列表中的映射(即@PathVariable)不会被继承。
方式二(抽取):
将FeignClient抽取为独立模块,并且吧接口有关的POJO,默认的Feign配置都放在该模块中,提供所有消费者使用。
实现步骤:
①创建一个module,命名为feign-api,然后引入feign的starter依赖。
②将order-service中编写的UserClient,User,DefaultFeignConfiguration都复制到feign-api项目中。
③在order-service中引入feign-api的依赖。
④修改order-service中的所有与上述三个组件有关的import导包部分,改成导入feign-api中的包。
⑤重启测试即可。
缺点:
该方案客户在引入依赖时,会引入许多多余的方法,造成内存的膨胀。
值得注意的一点:
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用(即无法自动注入,spring容器中没有对应类创建号的对象),采用以下方法解决:
方式一:指定FeignClient所在的包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:指定FeignClient字节码
@EnableFeignClients(clients = {UserClient.class})
9)统一网关Gateway
网关的概述
网关的功能:
①身份认证和权限校验
②服务路由,负载均衡
③请求限流
网关的技术实现:
步骤:
1.创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:
<!--nacos的服务注册发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关gateway的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2.编写路由配置及nacos地址
server:
port: 10010 # 网关端口
spring:
application:
name: gateway #服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: #网关路由配置
- id: user-service #路由id,只要唯一即可
uri: lb://userservice #路由的目标地址,lb是负载均衡,后面是服务名称
predicates: #路由断言,就是判断请求是否符合路由规则的条件
- Path=/user/** # 只要以/user开头就符合要求
路由断言工厂:
读取断言规则,从而对用户发起的路由做判断。
路由过滤器GatewayFilter
作用:可以对网关的请求和微服务返回的响应做处理:
案例:给所有进入userservice的请求添加一个请求头:Truth=NB
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
spring:
cloud:
gateway:
routes: #网关路由配置
- id: user-service
uri: lb://userservice
predicates:
- Path:/user/**
filters: #过滤器
- AddRequestHeader=Truth,NB # 添加请求头
default-filters: #给所有服务增加请求头
- AddRequestHeader=Truth,NB
全局过滤器GlobalFilter
概述:其处理一切进入网关的请求和微服务响应,与GatewayFilter作用一样。区别在于前者通过配置定义,处理逻辑固定,而后者逻辑需要自己写代码实现。定义方式是 实现GlobalFilter接口。
接口源码:
public interface GlobalFilter {
/**
* @param exchange 请求上下文,可获取到Request,Response等信息
* @param chain 用来吧请求委托给下一个过滤器
* @return 返回标识当前业务结束
*/
Mono<void> filter(ServerWebExchange,Gateway);
}
案例:定义全局过滤器,拦截请求,判断参数是否满足:
①参数中是否含有 authorization
②authorization参数值是否为admin
如果同时满足①②,则放行。
自定义过滤器:
@Order(-1) //排序,该组件的优先级(-1较高)
@Component //组件,将过滤器注入spring容器
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
//获取请求参数
MultiValueMap<String,String> params = exchange.getRequest().getQueryParams();
//获取参数
String auth = params.getFirst("authorization");
//校验
if("admin".equals(auth)){
//放行
return chain.filter(exchange);
}
//拦截
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
}
过滤器执行顺序法则:
①order值越小,优先级越高。
②当order值一样时,顺序是defaultFilter优先,然后是局部的路由过滤器,最后是全局过滤器。
跨域问题处理
跨域的概念:
①域名不同:www.taobao.com & www.taobao.org
②域名相同,端口不同。
跨域问题:浏览器 禁止请求发起者和服务端发生跨域 ajax 请求,请求被浏览器拦截的问题。
配置信息展示:
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true #解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: #允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: #允许跨越ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" #允许在请求中携带的头信息
allowCredentials: true #是否允许携带Cookie
maxAge: 360000 #跨域检测的有效期
2.Docker
1)认识docker
为什么要使用docker?
①依赖关系复杂,容易出现兼容性问题。
②开发测试生产环境有差异。
docker如何解决依赖的兼容问题?
①将应用的函数库,依赖,配置与应用一起打包,形成 可移植镜像。
②将每个应用放到一个隔离容器去运行(沙箱机制),避免互相干扰。
Docker如何解决不同系统环境的问题?
①Docker将用户程序与所需要调用的系统(Ubuntu,CentOS)函数库一起打包。
②Docker运行到不同操作系统时,直接基于打包的库函数,借助于操作系统的Linux内核来运行。
Docker与虚拟机
虚拟机(virtual machine)是在操作系统中模拟硬件设备,然后运行另一个操作系统,比如在Windows上运行Ubuntu系统。使用了 Hypervisor 技术
对比:
①性能:Docker更接近原生,而虚拟机性能较差。
②硬盘占用:Docker一般为MB,虚拟机一般为GB。
③启动速度:Docker以秒为单位,虚拟机以分钟级为单位。
④本质:Docker是一个系统进程,虚拟机是在操作系统中的另一套操作系统。
镜像和容器
镜像(Image):
镜像是只读的!!!Docker将应用程序及其所需的依赖,函数库,环境,配置等文件打包在一起。
容器(Container):
镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器做隔离,对外部可见。
DockerHub
DockerHub:是一个Docker镜像的托管平台。这样的平台叫做Docker Registry。
栗子:网易云镜像服务,阿里云镜像服务......
Docker架构
Docker是一个 C/S架构 的程序,由两部分组成:
♦ 服务端(server):Docker守护进程,负责处理Docker指令,管理镜像,容器等
♦ 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可在本地或远程向服务端发送指令。
2)使用Docker
安装Docker
必须是在64位的CentOS7系统下,内核不能低于3.10,CentOS 7 满足最低内核要求。
卸载Docker:执行以下命令
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine \
docker-ce
安装yum工具:
yum install -y yum-utils \
device-mapper-persistent-data \
lvm2 --skip-broken
更新本地镜像源:
# 设置docker镜像源
yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g'
/etc/yum/repos.d/docker-ce.repo
yum makecache fast
安装Docker:(ce为社区版)
yum install -y docker-ce
启动Docker前一定要关闭防火墙!!!
启动Docker前一定要关闭防火墙!!!
启动Docker前一定要关闭防火墙!!!
关闭命令:
# 关闭防火墙
systemctl stop firewalld
# 防止开机自启
启动docker:
sudo systemctl start docker
查看启动是否成功:
docker info
Docker镜像操作
镜像的命名:
由两部分组成:[repository]:[tag]
栗子:mysql:5.7
tag的默认值是latest,代表最新版本。
样例1:从DockerHub上拉取一个nginx镜像并查看。
1.访问hub.docker.com页面,搜索nginx关键字。
2.拉取自己需要的镜像即可,通过命令docker pull nginx)(默认最新版)(sudo?)
3.通过命令 docker images 查看镜像。
样例2:
1.通过命令 将nginx:lastest镜像导出到nginx.tar包中。
docker save -o nginx.tar nginx:lastest
2.删除本地的nginx镜像 命令 。
docker rmi nginx:lastest
3.重新将nginx镜像加载到本地镜像 命令 。
docker load -i nginx.tar
不要死记命令,学习查询帮助文档!!!
docker xxx --help。
Docker容器基本操作
额外命令:
docker exec:进入容器执行命令
docker logs:查看容器运行日志
docker ps:查看所有运行的容器及状态
docker rm:删除指定容器
样例1:创建运行一个nginx容器
1.使用Docker Hub查询Nginx容器的运行命令 。
docker run --name containrName -p 80:80 -d nginx
docker run:创建并运行一个容器
参数解读:
--name:给容器起一个名字,比如叫做mn
-p:将宿主机端口与容器端口映射,冒号左侧是宿主机端口。
-d:后台运行容器。
样例2:进入Nginx容器,修改HTML文件内容,添加“hello world”。
1.进入容器,命令
docker exec -it mn bash
参数解读:
docker exec:进入容器内部,执行一个命令
-it:给当前进入的容器创建一个标准输入输出终端,允许我们与容器交互。
mn:要进入容器的名称。
bash:进入容器后执行的命令,bash是一个linux终端交互命令。
2.进入nginx的HTML所在目录 /usr/share/nginx/html。(可在DockerHub上查看位置)
cd /usr/share/nginx/html
3.修改index.html的内容(无vim命令)
不推荐在容器内修改文件
4.停止容器运行
docker stop mn # 停止容器
docker ps #默认查看运行时容器
docker ps -a #查看所有的容器
5.重启容器
docker start mn # 重新开始容器运行
6.删除容器
# 方案1
docker stop nm # 先暂停容器
docker rm mn # 删除容器
# 方案2
docker rm -f mn # 强制删除mn容器
数据卷
数据卷操作语句
docker volume [COMMAND]:
create:创建一个volume。
inspect:显示一个或多个volume的信息。
ls:列出所有的volume。
prune:删除未使用的volume。
rm:删除一个或多个指定的volume。
数据卷的挂载
案例:
docker run \
--name mn \
-v html:/root/html \ # 把html数据卷挂载在容器内对应目录中。
-p 8080:80
nginx \
样例:创建一个nginx容器,修改容器内的html目录内的index.html内容
在上个案例中,我们进入nginx内部,以及知道nginx的html目录所在位置/usr/share/nginx/html,我们需要把这个目录挂载到html这个数据卷上,方便操作其中的内容。
1.创建容器并挂在数据卷到容器内的HTML目录。
docker run --name mn -p 80:80 -v html:/usr/share/nginx/html -d nginx
2.查看当前容器
docker ps
3.查看数据卷html信息
docker inspect html
4.找到对应路径下的index.html文件直接进入修改即可。
该种方式进入修改无需进入容器,直接数据卷同步修改。
样例2:创建并运行一个MYSQL容器,将宿主机目录直接挂载到容器。
-v [宿主机目录]:[容器内目录]
-v [宿主机文件]:[容器内文件]
实现思路:
1.将mysql.tar文件上传到虚拟机,通过load命令加载为镜像
docker load -i mysql.tar # 加载镜像
docker images # 查看镜像是否导入成功
2.创建目录/tmp/mysql/data
mkdir -p mysql/data
mkdir -p mysql/conf
cd mysql/conf
3.创建目录/tmp/mysql/conf,将课前资料提供的hmy.cnf文件上传到/tmp/mysql/conf
4.去DockerHub查阅资料,创建并运行MYSQL容器,要求:
①挂载/tmp/mysql/data到mysql容器内数据存储目录
②挂载/tmp/mysql/conf/hmy.cnf到mysql容器的配置文件
③设置MYSQL密码
难点※:
docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=123 \
-p 3306:3306\
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/data:/var/lib/mysql \
-d \
mysql:5.7.25
3)自定义镜像
镜像结构
定义复习:镜像是将应用程序及其需要的 系统函数库,环境,配置,依赖打包而成。
镜像是一个分层结构,每一层是一个Layer。
BaseImage层:包含基本的系统函数库,环境变量。文件系统。
Entrypoint:入口,是镜像中应用启动的命令。
其他:再BaseImage基础上添加依赖,安装程序,完成整个应用的安装和配置。
Dockerfile
定义:是一个文本文件,包含一个个的指令,用指令来说明要执行什么操作来构建镜像。每个指令都会形成一层Layer。
※不会多查文档!!!
官方文档:docs.docker.com/engine/refe…
样例1:基于Ubuntu镜像构建一个新镜像,运行一个java项目。
①准备一个空文件夹docker-demo
# 切换目录,新建文件夹
cd /tmp/
mkdir docker-demo
ll
②将事先准备好的docker-demo.jar,jdk8.tar.gz,Dockerfile拷贝到docker-demo中。
③进入docker-demo,运行命令:
# -t == tag 版本
# 空格之后加“.”代表当前目录下
docker build -t javaweb:1.0 .
④检查是否操作成功
docker images # 查看所有镜像
docker run --name web -p 8090:8090 -d javaweb:1.0
# 让名为javaweb 1.0版本的镜像以8090端口在容器8090下并且可以后台运行
Dockerfile的写法案例:
# 指定基础镜像(基于ubuntu)
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local
# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 安装JDK
RUN cd $JAVA_DIR \
&& tar -xf ./jdk8.tar.gz \
&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 暴露端口(如果需要改端口需要在boot中去修改port)
EXPOSE 8090
# 入口 java的启动命令
ENTRYPOINT java -jar /tmp/app.jar
样例2:基于java:8-alpine镜像,将java项目部署到容器
Dockerfile:
#指定基础镜像(基于java:8-alpine)
FROM java:8-alpine
COPY ./docker-demo.jar /tmp/app.jar
# 暴露端口(如果需要改端口需要在boot中去修改port)
EXPOSE 8090
# 入口 java的启动命令
ENTRYPOINT java -jar /tmp/app.jar
DockerCompose
定义:是一个基于Compose文件帮我们快速部署分布式应用,无需手动创建和运行容器!其是一个文本文件,通过定义集群中的每个容器如何运行。
安装:
curl -L http://github.com/docker/compose/releases/download/1.29.1/docker-compose-'uname -s'-'uname-m' > /usr/local/bin/docker-compose
上传到/usr/local/bin/目录也可以。
修改文件权限:
chmod +x /usr/local/bin/docker-compose
自动补全命令:
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
# 如果出错,执行以下命令
echo ""
样例:将之前学习的cloud-demo微服务集群利用docker-compose构建集群。
3.RabbitMQ
1)同步通信和异步通信
同步通信和异步通信:与生活中的视频电话与聊天相似。
同步通信存在的问题:
①耦合度高。每次加入新的请求,都需要修改原代码。
②性能下降。调用者需要等待服务提供者响应,调用链过长响应时间等于每次调用时间之和。
③资源浪费。调用链中每个服务在等待响应的过程中,不能释放请求占用的资源,高并发下会浪费系统资源。
④级联失败。如果服务提供者出现问题,所有调用方法都会出问题。
优点:时效性较强。
异步通信:
特点:事件驱动优势:
①流量削峰:高并发流量发起请求时,Broker缓存请求信息。微服务依次去取请求。
②耦合度低。
③吞吐量提升。
④故障隔离。
缺点:
①依赖于Broker的可靠性,安全性和吞吐能力。
②架构复杂了,业务没有明显的流程线,不好追踪管理。
2)MQ消息队列
定义:英文名MessageQueue,事件驱动架构中的Broker,表面上看就是存放消息的队列。
RabbitMQ安装
概念:RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址为:www.rabbitmq.com/
安装:
环境:在CentOS7的虚拟机下用Docker来安装。
下载镜像:
方式一:在线拉取
# 从镜像服务器中拉取rabbitmq镜像
docker pull rabbitmq:3-management
# 将其导入到系统本地
docker save -o rabbitmq.tar rabbitmq:3-management
方式二:从本地拉取
上传到虚拟机后,使用命令加载镜像即可:
docker load -i mq.tar
安装MQ:
# 创建MQ容器
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mql \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
# 查看是否创建成功
docker ps
RabbitMQ中的几个概念:
①channel:操作MQ的工具(通道)
②exchange:路由消息到队列中
③queue:缓存消息
④virtual host:虚拟主机,是对queue,exchange等的资源逻辑分组。
HelloWorld消息模型
HelloWorld:
概述:官方的HelloWorld是基于最基础的消息队列模型来实现的,其中包括三个角色:
① publisher:消息发布者,将消息发送到队列queue
② queue:消息队列,负责接受并缓存消息
③ consumer:订阅队列,处理队列中的消息
publisher----->queue----->consumer
基本消息队列的消息发送流程:
1.建立connection
2.创建channel
3.利用channel声明队列queue
4.利用channel向队列发送消息
基本消息队列的消息接收流程:
1.建立connection
2.创建channel
3.利用channel声明队列queue
4.定义consumer的消费行为handleDelivery()
5.利用channel将消费者与队列绑定
SpringAMQP
AMQP(Advanced Message Queuing Protocal):用于在应用程序或之间传递业务消息的开放标准,该协议与语言和平台无关,更符合微服务中独立性的要求。
Spring AMQP:基于AMQP协议定义的一套API规范,提供模板来发送和接收消息,包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
案例1: 利用Spring AMQP实现HelloWorld中的基础消息队列功能。
流程如下:
1.在父工程中引入spring-amqp的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.在publisher服务中利用RabbitTemplate发送消息到simple.queue这个队列。
配置mq的连接信息:
spring:
rabbitmq:
host: (主机名)
port: 5672
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 #密码
在publisher服务中新建一个测试类,编写测试方法:
@Runwith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue(){
String queueName = "simple.queue";
String message = "hello,spring amqp!";
rabbitTemplate.convertAndSend(queueName,message);
}
}
3.在consumer服务中编写消费逻辑,绑定simple.queue这个队列。
配置consumer信息,添加mq连接信息:
spring:
rabbitmq:
host: (主机名)
port: 5672
virtual-host: /
username: itcast
password: 123321
在consumer服务中新建一个类,编写消费逻辑:
@Component
public class SpringRabbitListener {
@RabbitListener(queues="simple.queue")
public void ListenSimpleQueueMessage(String msg) throws InterruptedException {
System.out.println("spring消费者接收到消息:"+msg);
}
}
WorkQueue工作队列
定义:一个队列绑定多个消费者。
作用:有效防止了消息队列的阻塞。
样例1:模拟WorkQueue,实现一个队列绑定多个消费者。
基本思路:
1.在publisher中服务定义测试方法,每秒产生50条消息,发送到simple.queue。
2.在consumer服务中定义两个消息监听者,都监听simple.queue队列。
3.消费者1每秒处理50条信息,消费者2每秒处理10条信息。
消费预取机制:
修改application.yml文件,设置preFetch这个值,可以控制预取消息的上限:
spring:
rabbitmq:
host: (主机名)
port: 5672 # 端口
virtual-host: /
username: itcast
password: 123321
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完了才能接取下一个消息
发布订阅模型
定义:与之前案例的区别是允许将同意消息发送给多个消费者。实现方式是加入了exchange(交换机)。常见的exchange包括:Fanout 广播, Direct 路由, Topic 话题。exhcange负责消息路由,路由失效则消息丢失。
FanoutExchange
定义:会接收到消息路由到每一个跟其绑定的queue。
案例1:实现FanoutExchange。
在配置类中声明FanoutExchange,Queue和绑定关系对象Binding,代码如下:
@Configuration
public class FanoutConfig {
//声明交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
//声明队列
//注意Queue的包:org.springframework.amqp.core.Queue;
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//绑定队列1和交换机
@Bean
public Binding bindingQueue1(Queue q,FanoutExchange fe){
return BindingBuilder.bind(q).to(fe);
}
}
使用RabbitMQ的方式:
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
,,,
}
}
DirectExchange
定义:会将接收到的消息规则路由到指定的Queue,因此成为路由模式(routes)。
①每一个Queue都与Exchange设置一个BindingKey。
②发布者发送消息时,指定消息的RoutingKey。
③Exchange将消息路由到BindingKey与消息RoutingKey一致的队列。
案例:利用SpringAMQP演示DirectExchange。
实现思路如下:
1.在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2。
2.利用@RabbitListen声明Exchange,Queue,RoutingKey。
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct" , type = ExchangeTypes.DIRECT),
key = {"red","yellow"}
))
public void listenDirectQueue2(String msg){
System.out.println("消费者2接收到消息:"+msg);
}
TopicExchange
定义:与上者类似,区别在于routingKey必须是多个单词的列表,并且以.分割。
Queue与Exchange指定key时可以使用通配符:
#:代指0或多个单词
*:代指一个单词
key的命名案例:china.news,china.weather,japan.news,japan.weather。
样例:利用SpringAMQP演示TopicExchange使用。
声明思路:
①利用@RabbitListener声明交换机,队列和RoutingKey
②在消费者服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2。
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
key = "china.#"
))
③在publisher中编写测试方法,向itcast.topic发送消息。
消息转换器
定义:Spring的对消息对象处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。推荐使用JSON方式序列化。
实现步骤:
①引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.10</version>
</dependency>
②在publisher服务声明MessageConverter:
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
③然后定义一个消费者,监听object.queue队列并消费消息:
@RabbitListener(queues = "object.queue")
public void ListenObjectQueue(Map<String,Object> msg){
sout("...")
}