第六章 redis
6.1 redis 下载安装
6.1.1 下载
下载: wget http://download.redis.io/releases/redis-5.0.5.tar.gz
解压:tar -zxvf redis-5.0.5.tar.gz -C /opt/install
6.1.2 安装
Redis 是 C 语言编写的,先安装 gcc
第一步:安装gcc :
yum install gcc (提示:ccc主要用于把代码编译成二进制文件)
第二步:编译并安装
cd redis-5.0.5
make MALLOC=libc
第三步: 安装
将redis-5.0.5/src目录下二进制文件安装到指定的目录中
cd src
make install PREFIX=/usr/local/redis
第四步: 配置环境变量
vi /etc/profile: 编辑配置文件
export REDIS_HOME=/usr/local/redis
export PATH=$PATH:$REDIS_HOME/bin
source /etc/profile 刷新使配置文件生效
第五步:修改配置文件
默认的配置文件是/opt/install/redis-5.0.5/redis.conf
3.1 设置后台启动
将 daemonize no 改成 daemonize yes
3.2 下面一行必须改成 bind 0.0.0.0 或注释,否则只能在本机访问
bind 127.0.0.1
3.3 把protected yes 该为:protected no 关闭本机保护,不然外机无法访问
3.4 并拷贝到/usr/local/redis/bin目录中一份
6.2 服务端
指定配置文件启动Redis
redis-server /usr/local/redis/bin/redis.conf
提示:redis启动之后可以通过ps -ef | grep redis查看服务是否启动
如果没有netstat命令:需要通过 yum install net-tools 安装网络工具包,然后使用该命令就有了
6.3 客户端
6.3.1 命令行客户端
启动客户端: redis-cli -h IP地址 -p 端口号
提示:1.不指定 -h -p的情况下,默认连接客户端所在机子的redis服务器
2.参数作用:
-h: 指定访问的redis服务器的ip地址
-p: 指定访问的redis服务器的port端口
-a: 指定登录密码
--raw: 解决中文乱码问题
3.redis默认使用的端口号为: 6379
关闭客户端:
quit|exit:都是退出客户端
shutdown: 在客户端关闭服务器端
6.3.2 图形化客户端
注意:如果图形化界面连接不上 redis 服务器端,则很可能是防火墙拦截了 6379 端口号
firewall-cmd --list-all 查看目前已开放的端口号
firewall-cmd --add-port=6379/tcp --permanent 开放端口
firewall-cmd --reload 重新加载使配置生效
也可以设置redis开机自启动:
1.vi /etc/rc.d/rc.local
2.配置 新增下面2个配置
source /etc/profile #让java环境变量在此执行文件执行之前生效。
/usr/local/redis/bin/redis-server /usr/local/redis/bin/redis.conf
3.给该配置文件授权
chmod +x /etc/rc.d/rc.local
redis-server --version --查看redis版本号
6.4 Redis 数据类型及基本命令使用
6.4.1 key
1> key的说明
关于 key 的定义需要大家注意以下几点:
1、key 不要太长,最好不要超过 1024 个字节,太长消耗内存降低查询效率,最大的 key 允许 512MB
2、key 不要太短,太短降低 key 的可读性
3、在项目中 key 最好有一个统一的命名规范,多个单词之间用:分隔,如:user:100:id
4、redis 中的命令语句中,命令是忽略大小写的,而 key
2> key的操作
EXPIRE key seconds 设置key的生存时间(单位:秒)key在多少秒后会自动删除
TTL key 查看key剩余的生存时间 默认是永久
PERSIST key 清除生存时间,永久有效
PEXPIRE key milliseconds 生存时间设置单位为:毫秒
注意:ttl 如果返回值为正数,表示 key 剩余的生存时间为该时间, 如果为-1 表示永久不过期 如果为-2 表示是不存在的 key!
keys * 查看当前库所有key
exists key 判断某个key是否存在
type key 查看你的key是什么类型
del key 删除指定的key数据
补充:库的切换与操作
select 库号 命令切换数据库
dbsize 查看当前数据库的key的数量
flushdb 清空当前库
flushall 通杀全部库
move newkey 库号 将一个库的某个key值移到别的库 如同剪切
6.4.2 string类型
1> 设置获取值
语法:SET key value
作用: 给某个 key 设置值
语法:GET key
作用:获取 key 对应的 value。
如果与该 key 关联的value 不是String 类型,redis 将返回错误信息,该命令只能用于获取String value。
如果该 key 不存在,返回(nil)。
2> 设置/获取多个键值
语法:MSET key value ... eg mset k1 v1 k2 v2 k3 v3
作用:给某个(些)key 设置值
MGET key [key … eg mget k1 k2
3> 取值并赋值
语法: GETSET key value
作用: 修改 key 对应的值
4> 删除
语法:DEL key
作用:将该 key 对应的值减一。
5> 数值增减
语法:INCR key decr key
作用:让当前键值递增,并返回递增后的值
如果该 key 不存在,其初始值为 0,在执行完 incr 之后其值为 1,
如果 value 的值不能转换成整形,如 hello,该操作将执行失败并返回相应的错误信息。
6> 数值增减幅度
语法:incrby key increment DECRBY key decrement
作用:给某个 key 对应的值增加指定的幅度。
eg incrby key1 5
7> 拼接
语法:APPEND key value
作用:类似与 java 中的+,可以拼凑字符串,并返回字符串长度
如果该 key 存在,则在原有的 value 后追加该值;
如果该 key 不存在,则重新创建一个 key/value
6.4.3 hash类型
1> 设置获取值
语法:HSET key field value eg: hset user username zhangsan
作用:设置给某个 key 的某个 field 一个字段值
语法:HGET key field eg:hget user username
作用:一次只能获取一个字段
2> 多个设置获取
语法:HMSET key field value [field value ...] eg:hmset user age 20
作用:一次可以设置多个字段值
语法:HMGET key field [field ...] eg: hmget user age username
作用:一次可以获取多个字段值
3> 不存在时设置
语法:HSETNX key field value eg: hsetnx user age 30 如果user中没有age字段则设置age值为30,否则不做任何操作
作用:当字段不存在时赋值,类似 HSET,区别在于如果字段存在,该命令不执行任何操作
4> 获取所有的filed value
语法:HGETALL key hgetall user
作用:获取所有字段和值
语法:HKEYS key hkeys user
作用:获得所有的字段
语法:HVALS key hvals user
作用:获得所有的 value
5>删除
语法:HDEL key field [field ...]
作用:删除一个或多个字段,返回值是被删除的字段个数
语法:DEL key
作用:删除整个 HASH
6>增加
语法:hincrby key field increment
作用:给某个 key 的某个 field 对应的值增加指定的数值 减去的话值为负数
7> 判断字段是否存在
语法:hexists key field
作用:判断指定的 key 中的 filed 是否存在,存在返回 1,不存在返回
8> 获取key包含的field的数量
语法:hlen key
作用:获取 key 所包含的 field 的数量
6.4.4 List类型
List 中可以包含的最大元 素数量是 4294967295(2^32 -1)
1>向列表两端增加元素
语法:LPUSH key value [value ...] eg: lpush list1 1 2 3
作用:向列表左边(头部)增加元素
语法:RPUSH key value [value ...]
作用:向列表右边(尾部)增加元素
2>查看列表
语法:LRANGE key start stop eg:LRANGE list1 0 -1
作用:获取列表中的某一片段,将返回 start、stop 之间的所有元素(包含两端的元素), 索引从 0 开始。索引可以是负数,如:“-1”代表最后边的一个元素,“-2”代表倒数第二个。
3>从列表两端弹出元素
语法:
LPOP key eg:lpop list1
RPOP key
作用:L(R)POP 命令从列表左|右边弹出一个元素,会分两步完成
第一步是将列表左边的元素从列表中移除
第二步是返回被移除的元素值
4>获取列表中元素的个数
语法:LLEN key
6.4.5 Set类型
Set 集合中不允许出现重复的元素
1>增加/删除元素
语法:SADD key member [member ...] eg:sadd set1 a b c
作用:向指定的集合中添加数据
语法:SREM key member [member ...] srem set1 c
作用: 删除集合 SET 中的元素
2>获得集合中的所有元素
语法:SMEMBERS key eg smembers set1
3>判断元素是否在集合中
语法:SISMEMBER key member 存在返回1 不存在返回0
4>集合差集运算
语法:SDIFF key1 key2 key1-key2
5>集合交集运算
语法:SINTER key1 key2 ..... key1与key2共有的
6>集合并集运算
语法:SUNION key1 key2 ..... key1与key2构成的集合
7>获得 set 成员数量
语法:SCARD key
8>随机返回 set 中的一个成员
语法:SRANDMEMBER key
6.4.6 ZSet类型
它们之 间的主要差别是 Sorted-Sets 中的每一个成员都会有一个分数(score)与之关联,Redis 正是通过分数来为集 合中的成员进行从小到大的排序。
1>增加元素
向有序集合中加入一个元素和该元素的数值,如果该元素已经存在则会用新的数值替换原有的数值。返回
值是新加入到集合中的元素个数,不包含之前已经存在的元素。
语法:ZADD key score member [score member ...]
eg:zadd scoreboard 80 zhangsan
2>获取元素的数值
语法:ZSCORE key member
3>、删除元素
语法:ZREM key member [member ...]
作用:移除有序集 key 中的一个或多个成员,不存在的成员将被忽略
4>获取集合中元素个数
语法:zset key
5>按顺序排列获取元素列表
语法:ZRANGE key start stop [WITHSCORES]
作用:按照元素数值从小到大的顺序返回索引从 start 到 stop 之间的所有元素(包含两端的元素)
(说明:0 -1 返回全部, 注意:0,表示第一个元素,-1 表示最后一个元素)
语法:ZREVRANGE key start stop [WITHSCORES
作用:按照元素数值从大到小的顺序返回索引从 start 到 stop 之间的所有元素(包含两端的元素)
如果需要获得元素的数值的可以在命令尾部加上 WITHSCORES 参数
eg:zrange scoreboard 0 1 WITHSCORES
6>获取集合中分数值在一定范围之间的元素
zrangebyscore guigu:sellsort 9 10
7>给集合中的某个元素增加分值
zincrby guigu:sellsort 20 1001 #给元素1001增加20分
6.5 jedis的使用
6.5.1 添加依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
6.5.2 连接测试
@Test
public void testJedis() {
//创建一个Jedis的连接
Jedis jedis = new Jedis("192.168.43.129", 6379);
//密码认证 如果设置了密码,就需要进行认证
jedis.auth("guigu");
//执行redis命令
jedis.set("mytest", "hello world, this is jedis client!");
//从redis中取值
String result = jedis.get("mytest");
//打印结果
System.out.println(result);
//关闭连接
jedis.close();
}
6.5.3 boot上的使用
public class CommonRedisConfiguration2 {
@Bean(name = "sysJedisPoolConfig")
public JedisPoolConfig a() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(1000);
jedisPoolConfig.setMaxIdle(10);
jedisPoolConfig.setNumTestsPerEvictionRun(1024);
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(30000);
jedisPoolConfig.setMinEvictableIdleTimeMillis(1800000);
jedisPoolConfig.setSoftMinEvictableIdleTimeMillis(10000);
jedisPoolConfig.setMaxWaitMillis(2000);
jedisPoolConfig.setTestOnBorrow(true);
jedisPoolConfig.setTestWhileIdle(true);
jedisPoolConfig.setBlockWhenExhausted(false);
return jedisPoolConfig;
}
@Bean(name = "sysRedisConnectionFactory")
public RedisConnectionFactory b(@Qualifier("sysJedisPoolConfig") JedisPoolConfig jedisPoolConfig) {
RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
redisConfig.setHostName("localhost");
redisConfig.setPort(6379);
redisConfig.setPassword(RedisPassword.of(""));
redisConfig.setDatabase(1);
JedisClientConfiguration.DefaultJedisClientConfigurationBuilder configurationBuilder = (JedisClientConfiguration.DefaultJedisClientConfigurationBuilder) JedisClientConfiguration.builder();
configurationBuilder.usePooling();
configurationBuilder.poolConfig(jedisPoolConfig);
configurationBuilder.readTimeout(Duration.ofMillis(1000));
// configurationBuilder.connectTimeout(Duration.ofMillis(10000));
JedisClientConfiguration clientConfiguration = configurationBuilder.build();
RedisConnectionFactory redisConnectionFacotry = new JedisConnectionFactory(redisConfig, clientConfiguration);
return redisConnectionFacotry;
}
@Bean(name = "sysRedisTemplate")
public RedisTemplate<String, Object> redisTemplate(@Qualifier("sysRedisConnectionFactory") RedisConnectionFactory redisConnectionFacotry) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFacotry);
// 使用fastjson序列化
FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
// value值的序列化采用fastJsonRedisSerializer
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// template.afterPropertiesSet();
return template;
}
}
//测试
@RestController
public class TestController {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@RequestMapping("/test")
public String test() {
try {
String a = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(a,
"this is a good day}");
} catch (Exception e) {
System.out.println(e.getMessage());
}
return "a";
}
}
6.5.4 常用String操作
判断是否有key所对应的值 返回结果:有则返回true,没有则返回false
redisTemplate.hasKey(key)
取值 返回结果:有则取出key值所对应的值
redisTemplate.opsForValue().get(key)
删除单个key值
redisTemplate.delete(key)
批量删除key
redisTemplate.delete(keys)
设置过期时间 Boolean
redisTemplate.expire(key, timeout, unit);
return redisTemplate.expireAt(key, date);
查找匹配的key值
public Set<String> getPatternKey(String pattern) {
return redisTemplate.keys(pattern);
}
修改key
public void renameKey(String oldKey, String newKey) {
redisTemplate.rename(oldKey, newKey);
}
返回传入key所存储的值的类型
public DataType getKeyType(String key) {
return redisTemplate.type(key);
}
如果旧值存在时,将旧值改为新值
public Boolean renameOldKeyIfAbsent(String oldKey, String newKey) {
return redisTemplate.renameIfAbsent(oldKey, newKey);
}
从redis中随机取出一个key
redisTemplate.randomKey()
返回当前key所对应的剩余过期时间
public Long getExpire(String key) {
return redisTemplate.getExpire(key);
}
返回剩余过期时间并且指定时间单位
public Long getExpire(String key, TimeUnit unit) {
return redisTemplate.getExpire(key, unit);
}
将key持久化保存
public Boolean persistKey(String key) {
return redisTemplate.persist(key);
}
将当前数据库的key移动到指定redis中数据库当中
public Boolean moveToDbIndex(String key, int dbIndex) {
return redisTemplate.move(key, dbIndex);
}
设置当前的key以及value值
redisTemplate.opsForValue().set(key, value)
设置当前的key以及value值并且设置过期时间
redisTemplate.opsForValue().set(key, value, timeout, unit)
返回key中字符串的子字符
public String getCharacterRange(String key, long start, long end) {
return redisTemplate.opsForValue().get(key, start, end);
}
将旧的key设置为value,并且返回旧的key
public String setKeyAsValue(String key, String value) {
return redisTemplate.opsForValue().getAndSet(key, value);
}
批量获取值
public List<String> multiGet(Collection<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
在原有的值基础上新增字符串到末尾
redisTemplate.opsForValue().append(key, value)
以增量的方式将double值存储在变量中
public Double incrByDouble(String key, double increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
通过increment(K key, long delta)方法以增量方式存储long值(正值则自增,负值则自减)
public Long incrBy(String key, long increment) {
return redisTemplate.opsForValue().increment(key, increment);
}
如果对应的map集合名称不存在,则添加否则不做修改
Map valueMap = new HashMap();
valueMap.put("valueMap1","map1");
valueMap.put("valueMap2","map2");
valueMap.put("valueMap3","map3");
redisTemplate.opsForValue().multiSetIfAbsent(valueMap);
设置map集合到redis
Map valueMap = new HashMap();
valueMap.put("valueMap1","map1");
valueMap.put("valueMap2","map2");
valueMap.put("valueMap3","map3");
redisTemplate.opsForValue().multiSet(valueMap);
获取字符串的长度
redisTemplate.opsForValue().size(key)
用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始
redisTemplate.opsForValue().set(key, value, offset)
重新设置key对应的值,如果存在返回false,否则返回true
redisTemplate.opsForValue().setIfAbsent(key, value)
将值 value 关联到 key,并将 key 的过期时间设为 timeout
redisTemplate.opsForValue().set(key, value, timeout, unit)
将二进制第offset位值变为value
redisTemplate.opsForValue().setBit(key, offset, value)
对key所储存的字符串值,获取指定偏移量上的位(bit)
redisTemplate.opsForValue().getBit(key, offset)
6.6 配置文件的说明
6.7 内存驱逐策略
6.8 事务
redis的事务分为组队阶段和执行阶段
6.8.1 组队阶段
组队阶段的错误,会导致全部回滚
eg:
multi
set key1 value1
sett key2 value2 (这里是错的,sett没有这个命令,直接报错)
exec (全部失败)
6.8.2 执行阶段
执行阶段的错误,只会导致执行错误的失败
eg:
set key1 value1
multi
set key2 value2
incr key1 (这里是无法String加减的)
exec (key1会失败,key2是成功执行的)
6.8.3 redis事务三大特性
1>单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。
事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
2>没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
3>不保证原子性[不要么同时成功,要么同时失败]
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
6.9 锁
6.9.1 悲观锁
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在
拿数据的时候都会上锁,这样别人想拿这个数据就会 block 直到它拿到锁。传统的关系型数据库里边就用
到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
6.9.2 乐观锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上
锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁
适用于多读的应用类型,这样可以提高吞吐量。Redis 就是利用这种 check-and-set 机制实现事务的。
乐观锁的具体体现是使用 watch指令
6.9.3 watch
在执行 multi 之前,先执行 watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这 些) key 被其他命令所改动,那么事务将被打断。
eg: watch key1
multi
incr key1
exec
这时还未提交,另一个请求对key1进行修改提交,则上面的操作失败
6.10 redis 持久化机制
6.10.1 RDB持久化
RDB 全称 Redis DataBase:Redis 的数据库,在指定的时间间隔内将内存中的数据集生成快照保存到磁盘中,
也就是行话讲的 Snapshot 快照,它恢复时是将快照文件直接读到内存里。触发 RDB 持久化过程分为手动
触发和自动触发
1>dump.rdb 文件
redis.conf 中配置文件名称,默认为 dump.rdb
2>存放位置
rdb 文件的保存路径,也可以修改。默认为 Redis 启动时命令行所在的目录下,建议修改到指定位置
3>手动触发
手动触发分别对应 save 和 bgsave、shutdown 命令
save 命令:阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存 比较大的实例会造成长时间阻塞,
线上环境不建议使用。
bgsave 命令:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子 进程负责,完成后自动结束。
阻塞只发生在 fork 阶段,一般时间很短。具体操作是 Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。基本上 Redis 内部所有的 RDB 操作都是采用 bgsave 命令。Redis 会在后台异步进行快照操作, 快照同时还可以响应客户端请求。
shudown 命令:当然,当我们执行 shutdown 命令的时候,也会将内存中的数据持久化到磁盘上,也是会生成 rdb 文件的
4>自动触发
自动触发是由我们的配置文件来完成的。在 redis.conf 配置文件中,里面有如下配置,我们可以去设置
save m n: 表示m秒内数据集存在n次修改时,自动触发bgsave
案例:
save 900 1: 表示900 秒内如果至少有 1 个 key 的值变化,则保存
save 300 10: 表示300 秒内如果至少有 10 个 key 的值变化,则保存
save 60 10000: 表示60 秒内如果至少有 10000 个 key 的值变化,则保存
5>几个常用的 rdb 配置项
1 stop-writes-on-bgsave-error:当 Redis 无法写入磁盘的话,直接关掉 Redis 的写操作。推荐 yes.
2 rdbcompression:对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis 会采用 LZF 算法进行压缩。如果你不想消耗 CPU 来进行压缩的话,可以设置为关闭此功能。推荐 yes.
3 rdbchecksum:默认值是 yes。在存储快照后,我们还可以让 redis 使用 CRC64 算法来进行数据校验,但是这样做会增加大约 10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
6>rdb 备份及数据恢复
1备份: 将*.rdb的文件拷贝到别的地方【可以写定时脚本】
2.rdb的数据恢复
关闭Redis
先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb
启动Redis,
7>禁用
动态停止 RDB:redis-cli config set save “” #save 后给空值,表示禁用保存策略
6.10.2 AOF 持久化
以日志的形式来记录每个写操作(增量保存),将 Redis 执行过的所有写指令记录下来(读操作不记录), 只 许追加文件但不可以改写文件,redis 启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根 据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
1>文件及存放位置
2>原理
每当有一个写命令过来时,就直接保存在我们的 AOF 文件中。
3>文件重写原理
AOF 的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩 aof 的持久化文件。redis
提供了 bgrewriteaof 命令。将内存中的数据以命令的方式保存到临时文件中,同时会 fork 出一条新进
程来将文件重写,重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的方式重写
了一个新的 aof 文件,这点和快照有点类似。
4>几个重写相关的配置
no-appendfsync-on-rewrite:
重写时是否可以运用 appendfsync,用默认 no 即可,保证数据安全性。
例如:将复杂多个步骤操作的结果,采用简单一个步骤进行操作,减少 aop 文件大小
触发机制,何时重写
Redis 会记录上次重写时的 AOF 大小,默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大
于 64M 时触发重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定 Redis 要满足一定条件才会进行重写。
auto-aof-rewrite-percentage:100
设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
auto-aof-rewrite-min-size:64mb
设置重写的基准值,最小文件64MB。达到这个值开始重写。
5>触发时机
1.appendfsync always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性较好
2.appendfsync everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失
3.appendfsync no: 从不同步
6>备份及数据恢复
AOF 的备份机制和性能虽然和 RDB 不同, 但是备份和恢复的操作同 RDB 一样,都是拷贝备份文件,需要
恢复时再拷贝到 Redis 工作目录下,启动系统即加载。
正常恢复
修改默认的 appendonly no,改为 yes
将有数据的 aof 文件复制一份保存到对应目录(查看目录:config get dir)
恢复:重启 redis 然后重新加载
异常恢复
修改默认的 appendonly no,改为 yes 如遇到 AOF 文件损坏,通过/usr/local/bin/redis-check-aof --fix appendonly.aof 进行修复
恢复:重启
7>禁用
保持默认值就是禁用状态,即: appendonly no
6.10.3 RDB 与 AOF 对比
优先级:RDB 文件和 AOF 文件同时存在的情况下,AOF 优先级要高一些
备份:RDB 快照是一次全量备份,AOF 增量备份,并且有重写机制
实际使用:
1.选择的话,两者加一起才更好,这也是官方推荐的使用方式。
2.因为两个持久化机制特点和自己的需求【需求不同选择的也不一定】,但通常都是结合使用
2.1 如果对数据不敏感,可以选单独用RDB。
2.2 不建议单独用 AOF,因为可能会出现Bug。
2.3 如果只是做纯内存缓存,可以都不用。
3.因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保
留save 900 1 这条规则。
6.11 主从复制
主从复制有两种形式,一种是一主二仆,一种是薪火相传。
6.11.1 如何配置
一般情况下,我们在几台 redis 的配置文件中配置以下几项即可。
注意:蓝色部分在 redis 各个配置文件中配置好即可,而绿色部分在 redis 的公共配置文件中配置。
1>redis6379.conf
daemonize yes
pidfile /var/run/redis6379.pid
port 6379
dbfilename dump6379.conf
appendonly no
logfile ""
protected-mode no
2>redis6380.conf
include /usr/local/redis/bin/redis6379.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
3>redis6381.conf
include /usr/local/redis/bin/redis6379.conf
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb
4>启动这几台 redis 服务并查看
redis-server /usr/local/redis/bin/redis6379.conf
redis-server /usr/local/redis/bin/redis6380.conf
redis-server /usr/local/redis/bin/redis6381.conf
ps -ef | grep redis
5>登录客户端查看运行情况
注意:info replication 可以查看主从复制的相关信息
6.11.2 主从复制之一主二仆
注意:要配置主从复制,记住:配从不配主,即在从服务器配置 slaveof 所以在端口号为 6380 和 6381 的 redis 上配置:
slaveof 192.168.43.129 6379
当然,在新版本的redis中,我们也可以使用replicaof <masterip> <masterport>
永久主从复制:
可以将 slaveof 127.0.0.1 6379 配置增加到文件中。永久生效。
主从复制原理:
每次从机联通后,都会给主机发送 sync(同步)指令
主机立刻进行存盘操作,发送 RDB 文件给从机[ redis.conf 文件中规定了发送 RDB 文件时间:repl-timeout 60]
从机收到 RDB 文件后覆盖自己的 RDB 文件,进行全盘加载[全量复制]
之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令[增量复制]
总而言之,redis 的主从复制是:全量复制和增量复制相结合的方式实现主从复制的。
6.11.3 主从复制之薪火相传
上一个 Slave 可以是下一个 slave 的 Master,Slave 同样可以接收其他 slaves 的连接和同步请求,那么该 slave 作为了链条中下一个的 master, 可以有效减轻 master 的写压力,去中心化降低风险。
用 slaveof 中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦中间的某个 slave 宕机,后面的 slave 都没法备份
6.11.4 代码演示
public static void main(String[] args) throws InterruptedException {
Jedis jedis_M = new Jedis("192.168.43.129",6379);
Jedis jedis_S = new Jedis("192.168.43.129",6380);
jedis_S.slaveof("192.168.43.129",6379);
jedis_M.set("k7","v7");
Thread.sleep(2000);
System.out.println(jedis_S.get("k7"));
}
记得在防火墙汇中放开 6380 端口号,否则报连接异常
firewall-cmd --list-all
firewall-cmd --add-port=6380/tcp --permanent
firewall-cmd --reload
6.12 哨兵模式
反客为主的自动版,实现主从自动切换,能够后台监控主机是否故障,如果故障了根据投票数自动将从库 转换为主库
6.12.1 具体操作
1>调整为一主二仆模式,6379 带着 6380、6381
2>在 redis 的 bin 目录下新建 sentinel.conf 文件
提示:该监控文件的名字不能错哦。
内容为:
sentinel monitor mymaster 127.0.0.1 6379 1
其中 mymaster 为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。
3>启动哨兵模式
redis-sentinel /usr/local/redis/bin/sentinel.conf
4>当主机挂掉,从机选举中产生新的主机
(大概 10 秒左右可以看到哨兵窗口日志,切换了新的主机)
哪个从机会被选举为主机呢?根据优先级别:slave-priority
原主机重启后会变为从机
优先级在redis.conf中默认:slave-priority 100,值越小优先级越高
偏移量是指获得原主机数据最全的
每个redis实例启动后都会随机生成一个40位的runid
6.13 集群
6.13.1 具体操作
1>准备 6 台 redis 服务运行的目录
cd /usr/local/redis/
mkdir redis-cluster
cd redis-cluster
mkdir 7291 7292 7293 7294 7295 7296
2>复制 redis 配置文件到 7291 目录
cp /usr/local/redis/bin/redis.conf /usr/local/redis/redis-cluster/7291
3>修改 7291 的 redis.conf 配置文件,内容:
cd /usr/local/redis/redis-cluster/7291
>redis.conf
vim redis.conf
写入以下配置,完成后把注释删掉
port 7291
daemonize yes
protected-mode no
dir /usr/local/redis/redis-cluster/7291/
cluster-enabled yes #打开集群模式
cluster-config-file nodes-7291.conf #设定集群节点配置文件名(集群信息会写到该文件中)
cluster-node-timeout 5000 #设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
appendonly yes
pidfile /var/run/redis_7291.pid
4>把 7291 下的 redis.conf 复制到其他 5 个目录
cd /usr/local/redis/redis-cluster/7291/
cp redis.conf ../7292
cp redis.conf ../7293
cp redis.conf ../7294
cp redis.conf ../7295
cp redis.conf ../7296
5>批量替换内容
cd /usr/local/redis/redis-cluster/
sed -i 's/7291/7292/g' 7292/redis.conf
sed -i 's/7291/7293/g' 7293/redis.conf
sed -i 's/7291/7294/g' 7294/redis.conf
sed -i 's/7291/7295/g' 7295/redis.conf
sed -i 's/7291/7296/g' 7296/redis.conf
6>启动 6 个 Redis 节点
cd /usr/local/redis/
./bin/redis-server redis-cluster/7291/redis.conf
./bin/redis-server redis-cluster/7292/redis.conf
./bin/redis-server redis-cluster/7293/redis.conf
./bin/redis-server redis-cluster/7294/redis.conf
./bin/redis-server redis-cluster/7295/redis.conf
./bin/redis-server redis-cluster/7296/redis.conf
7>是否启动
ps -ef|grep redis
8>将启动的 6 个 redis 服务组成集群
旧版本中的 redis-trib.rb 已经废弃了,直接用–cluster 命令
注意用绝对 IP,不要用 127.0.0.1
redis-cli --cluster create 192.168.43.129:7291 192.168.43.129:7292 192.168.43.129:7293
192.168.43.129:7294 192.168.43.129:7295 192.168.43.129:7296 --cluster-replicas 1
9>连接到客户端
以单机版方式进入客户端
redis-cli -p 7291
redis-cli -p 7292
redis-cli -p 7293
以集群方式进入客户端
redis-cli -c -p 端口号 [-c 就是 cluster 集群的意思]:进入一个 redis 客户端就行
10>批量写入值
cd /usr/local/redis/redis-cluster
vim redisscript.sh
#!/bin/bash
for ((i=0;i<20000;i++))
do
echo -en "helloredis" | redis-cli -h 192.168.43.129 -p 7291 -c -x set name$i >> redis.log
done
Tip:
-c:是以集群(cluster)的方式往集群中写数据,而不是往单台机子里面写数据
-x:将标准输入(本来输出到控制台上的helloredis) 读入进来作为set name$i的值”
>>:将set 命令的操作结果写入到redis.log文件中
chmod +x redisscript.sh
./redisscript.sh
6.13.2 故障恢复
1. 如果主节点下线?从节点能否自动升为主节点
redis-cli -p 7293 shutdown:关闭某个指定的redis服务
redis-cli -c -p 7291
cluster nodes: 此时会发现端口号为7293的redis对应的从服务器已经自动转为master主服务器了。
2. 主节点恢复后,主从关系会如何? 变为从节点
redis-server /usr/local/redis/redis-cluster/7293/redis.conf
redis-cli -c -p 7293
cluster nodes
3. 如果所有某一段插槽的主从节点都当掉,redis服务是否还能继续?不能继续了。
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为yes ,那么,整个集群都挂掉
如果某一段插槽的主从都挂掉,而cluster-require-full-coverage 为no ,那么,该插槽数据全都不能使用,也无法存储。
redis.conf中的参数 cluster-require-full-coverage , 16384个槽都正常的时候才能对外提供服务。
1. 一般情况下,一个集群最少有3个master节点
2. Redis集群的slot插槽有16384个
3. 在redis-cli进行查询、录入数据时,要使用集群的方式登录(-c),否则查询的数据如果不在对应服
务器的插槽时,会有redis报错问题。
4. 不在一个slot 下的键值,是不能使用mget、mset等多键操作的
5. 可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去,一个slot槽中
的数据一定是在一个组中的。
Eg:
127.0.0.1:7291> set k1{book} v1
OK
127.0.0.1:7291> set k2{book} v2
OK
127.0.0.1:7291> set k3{book} v3
OK
6.13.3 使用 JedisCluster 操作集群
放开端口号:
firewall-cmd --list-all
firewall-cmd --add-port=7291/tcp --permanent #千万要注意,要放开集群的所有的端口号
firewall-cmd --add-port=7292/tcp --permanent #因为数据可能从集群中任意一台机子上返回信息
firewall-cmd --add-port=7293/tcp --permanent
firewall-cmd --add-port=7294/tcp --permanent
firewall-cmd --add-port=7295/tcp --permanent
firewall-cmd --add-port=7296/tcp --permanent
firewall-cmd --reload
简单示例:
public static void main(String[] args) throws IOException {
Set<HostAndPort> nodes = new HashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.43.129",7291));
redis.clients.jedis.JedisCluster jedisCluster = new redis.clients.jedis.JedisCluster(nodes);
jedisCluster.set("username","admin");
String username = jedisCluster.get("username");
System.out.println(username);
jedisCluster.close();
}
公司java用法:
1.配置属性:
//redis.host=29.126.xx.xx,23.xx.xx.xx
//redis.port=31001,31002
2.加载属性
@Component
public class ServerConfig implements AutoCloseable {
@Value("${redis.host}")
private String[] redis_hosts;
private int[] redis_ports;
private String redis_password;
private int redis_connect_timeout;
private int redis_so_timeout;
private int redis_max_attempts;
@Override
public void close() throws Exception {
throw new RuntimeException("Exception in close");
}
//get/set省略
}
3.连接配置
3.1 定义接口
public interface RedisConnect {
JedisCluster getJedisCluster();
}
3.2 连接配置
public class SingleRedisConnect implements RedisConnect {
private final static Logger logger = LoggerFactory.getLogger(SingleRedisConnect.class);
private static JedisCluster jedisCluster;
private static SingleRedisConnect instance;
public static synchronized SingleRedisConnect getInstance(ServerConfig serverConfig){
if (instance == null){
String[] redis_hosts = serverConfig.getRedis_hosts();
int[] redis_ports = serverConfig.getRedis_ports();
String redis_password = serverConfig.getRedis_password();
int redis_connect_timeout = serverConfig.getRedis_connect_timeout();
int redis_max_attempts = serverConfig.getRedis_max_attempts();
int redis_so_timeout = serverConfig.getRedis_so_timeout();
HashSet<HostAndPort> nodes = new HashSet<>();
for (int i = 0; i < redis_hosts.length; i++) {
for (int j = 0; j < redis_ports.length; j++) {
nodes.add(new HostAndPort(redis_hosts[i],redis_ports[j]));
}
}
jedisCluster = new JedisCluster(nodes,redis_connect_timeout,redis_so_timeout,redis_max_attempts,redis_password,new GenericObjectPoolConfig());
instance = new SingleRedisConnect();
}
return instance;
}
@Override
public JedisCluster getJedisCluster() {
try {
if (jedisCluster != null){
return jedisCluster;
}else {
return null;
}
} catch (Exception e) {
logger.error("捕捉jedisCluster异常");
return null;
}
}
4.分装工具类
@Component
public class RedisUtil {
@Resource
private ServerConfig serverConfig;
public void setOp(String key, String value) {
SingleRedisConnect redisConnect = SingleRedisConnect.getInstance(serverConfig);
JedisCluster jedisCluster = redisConnect.getJedisCluster();
jedisCluster.set("aaa" + "bbb" + key, value);
}
}
网上boot用法
1.yml
spring:
redis:
database: 0
timeout: 3000
password: zhuge
cluster:
nodes: 192.168.0.61:8001,192.168.0.62:8002,192.168.0.63:8003,192.168.0.61:8004,192.168.0.62:8005,192.168.0.63:8006
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
2.代码:
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/test_cluster")
public void testCluster() throws InterruptedException {
stringRedisTemplate.opsForValue().set("edsion", "666");
System.out.println(stringRedisTemplate.opsForValue().get("edsion"));
}
RedisCluster 实际上本身就相当于集成了:replication 副本机制(主从复制)+sentinel(哨兵监控)。RedisCluster 是一种去中心化方案,我们连接哪一台都是可以的。
第七章 dubbo
7.1 dubbo架构图
调用关系说明:
1. 服务容器负责启动,加载,运行服务提供者。
2. 服务提供者在启动时,向注册中心注册自己提供的服务。
3. 服务消费者在启动时,向注册中心订阅自己所需的服务。
4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
5. 服务消费者,从提供者地址列表中,基于负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
7.2 zookeeper
使用:双击zkServer.cm会出现闪退
1.在conf目录下,把zoo_sample.cfg复制一份,改为zoo.cfg
2.修改里面的配置#dataDir=/tmp/zookeeper dataDir=../data
3.在外面新建data文件夹
4.重新双击启动
7.3 dubbo入门
7.3.1 服务提供方
1>先创建一个dubbodemo的文件夹。在文件夹下创建提供者和消费者项目。
2>创建maven工程(打包方式为war)dubbodemo_provider,在pom.xml文件中导入如下坐标
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.0.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.7</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<!-- 报红加上,然后可以在去掉 -->
<!--<version>2.2</version>-->
<configuration>
<!-- 指定端口 -->
<port>8081</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
3>配置web.xml文件,在main下创建 webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
4>创建服务接口
这一块后面消费者也用,做抽取的
package com.atguigu.service; //这个包和下面的指定的一致
public interface HelloService {
public String sayHello(String name);
}
5>创建服务实现类
package com.atguigu.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.atguigu.service.HelloService;
@Service //注意:服务实现类上使用的 Service 注解是Dubbo提供的,用于对外发布服务
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello " + name;
}
}
6>在src/main/resources下创建 applicationContext-service.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样 -->
<dubbo:application name="dubbodemo_provider" />
<!-- 连接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 注册 协议和port 端口默认是20880 -->
<dubbo:protocol name="dubbo" port="20881"></dubbo:protocol>
<!-- 扫描指定包,加入@Service注解的类会被发布为服务 一般指定com.atguigu.service,会扫子包 -->
<dubbo:annotation package="com.atguigu.service.impl" />
</beans>
7>启动服务
maven—> tomcat插件启动
8>zkcki.cmd 可以查看 ls /
7.3.2 服务消费方
1>创建 maven 工程(打包方式为war)dubbodemo_consumer,pom.xml 配置和上面服务提供者相同,只需要将 Tomcat 插件的端口号改为8082即可
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.0.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- dubbo相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.7</version>
</dependency>
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>8082</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
2>配置webapp/WEB-INF/web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext-web.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3>将服务提供者工程中的HelloService接口复制到当前工程
package com.atguigu.service;
public interface HelloService {
public String sayHello(String name);
}
4>编写Controller
package com.atguigu.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.atguigu.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("/demo")
public class HelloController {
@Reference //注意:Controller中注入HelloService使用的是Dubbo提供的@Reference注解
private HelloService helloService;
@RequestMapping("/hello")
@ResponseBody
public String getName(String name){
//远程调用
String result = helloService.sayHello(name);
System.out.println(result);
return result;
}
}
5>在src/main/resources下创建applicationContext-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 当前应用名称,用于注册中心计算应用间依赖关系,注意:消费者和提供者应用名不要一样 -->
<dubbo:application name="dubbodemo_consumer" />
<!-- 连接服务注册中心zookeeper ip为zookeeper所在服务器的ip地址-->
<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
<!-- 扫描的方式暴露接口 -->
<dubbo:annotation package="com.atguigu.controller" />
<!-- 运行dubbo不检查提供者是否提前开启 -->
<!-- <dubbo:consumer check="false"></dubbo:consumer> -->
</beans>
6>运行测试
在浏览器输入http://localhost:8082/demo/hello?name=Jack,查看浏览器输出结果
7.3.3 代码抽取
1 创建项目:dubbodemo_interface
2 把 项目dubbodemo_consumer 和 项目dubbodemo_provider当中的 接口 HelloService 拷贝到dubbodemo_interface工程里面
3 删除工程dubbodemo_consumer 和 工程dubbodemo_provider当中的 接口 HelloService
dubbodemo_consumer 工程和dubbodemo_provider添加pom文件的依赖
4 运行程序:http://localhost:8082/demo/hello?name=haha
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>dubbodemo_interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
7.3.4 加入日志
在 resources 文件夹下面引入log4j.properties日志文件
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:\\mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout
7.3.5 控制台
1 安装
1 将资料中的dubbo-admin-2.6.0.war文件复制到tomcat的webapps目录下
2 启动tomcat,此war文件会自动解压
3 修改WEB-INF下的dubbo.properties文件,注意dubbo.registry.address对应的值需要对应当前使用的 Zookeeper的ip地址和端口号
dubbo.registry.address=zookeeper://192.168.134.129:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
4 重启tomcat
5 访问http://localhost:8080/dubbo-admin-2.6.0/ ,输入用户名(root)和密码(root),切换简体中文
7.3.6 Dubbo无法发布被事务代理的Service问题
在入门案例的服务提供者dubbodemo_provider工程基础上进行展示
操作步骤:
(1)在pom.xml文件中增加maven坐标
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
(2)在applicationContext-service.xml配置文件中加入数据源、事务管理器、开启事务注解的相关配置
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
</bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务控制的注解支持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
(3)在HelloServiceImpl类上加入@Transactional注解
package com.atguigu.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.atguigu.service.HelloService;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "8086 hello " + name;
}
}
4)启动服务提供者和服务消费者,并访问
没有可用的服务提供者
解决方案:
通过上面的断点调试可以看到,在HelloServiceImpl类上加入事务注解后,Spring会为此类基于JDK动态代理技术创建代理对象,创建的代理对象完整类名为com.sun.proxy.$Proxy35,导致Dubbo在进行包匹配时没有成功(因为我们在发布服务时扫描的包为com.atguigu.service),所以后面真正发布服务的代码没有执行
(1)修改applicationContext-service.xml配置文件,开启事务控制注解支持时指定proxy-target-class属性,值为true。其作用是使用cglib代理方式为Service类创建代理对象,添加如下配置:
<!--开启事务控制的注解支持-->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
(2)修改HelloServiceImpl类,在Service注解中加入interfaceClass属性,值为HelloService.class,作用是指定服务的接口类型
package com.atguigu.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.atguigu.service.HelloService;
import org.springframework.transaction.annotation.Transactional;
@Service(interfaceClass = HelloService.class)
@Transactional
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "8086 hello " + name;
}
}
7.3.7 负载均衡
随机算法 RandomLoadBalance(默认)
轮询算法 RoundRobinLoadBalance
最小活跃数算法 LeastActiveLoadBalance
一致性hash算法 ConsistentHashLoadBalance
配置负载均衡策略,既可以在服务提供者一方配置,也可以在服务消费者一方配置,如下:
@Controller
@RequestMapping("/demo")
public class HelloController {
//在服务消费者一方配置负载均衡策略
@Reference(check = false,loadbalance = "random")
private HelloService helloService;
@RequestMapping("/hello")
@ResponseBody
public String getName(String name){
//远程调用
String result = helloService.sayHello(name);
System.out.println(result);
return result;
}
//在服务提供者一方配置负载均衡
@Service(loadbalance = "random")
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "hello " + name;
}
}
第八章 Quartz
8.1 测试流程
1:创建maven工程quartz_demo,打包方式为war,导入jar包
2:自定义一个Job
3:提供Spring配置文件application-jobs.xml,配置自定义Job、任务描述、触发器、调度工厂等
4:web.xml中定义
5:启动tomcat完成测试
(1)创建maven工程quartz_demo,打包方式为war,导入Quartz和spring相关坐标,pom.xml文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>quartz_demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<configuration>
<!-- 指定端口 -->
<port>8080</port>
<!-- 请求路径 -->
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
(2)自定义一个Job
// 任务调度类
public class JobDemo {
// 提供方法(备份数据库,清理日志,清理图片)
public void run(){
// 完成业务
System.out.println(new Date());
}
}
(3)提供Spring配置文件application-jobs.xml,配置自定义Job、任务描述、触发器、调度工厂等
1:创建JobDetail对象,作用是负责通过反射调用指定的Job,注入目标对象,注入目标方法
2:注册一个触发器,指定任务触发的时间
3:注册一个统一的调度工厂,通过这个调度工厂调度任务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册自定义Job -->
<bean id="jobDemo" class="com.atguigu.JobDemo"></bean>
<!-- 1:创建JobDetail对象,作用是负责通过反射调用指定的Job,注入目标对象,注入目标方法 -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<!-- 注入目标对象 -->
<property name="targetObject" ref="jobDemo"/>
<!-- 注入目标方法 -->
<property name="targetMethod" value="run"/>
</bean>
<!-- 2:注册一个触发器,指定任务触发的时间 -->
<bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<!-- 注入JobDetail -->
<property name="jobDetail" ref="jobDetail"/>
<!-- 指定触发的时间,基于Cron表达式(0/10表示从0秒开始,每10秒执行一次) -->
<property name="cronExpression">
<value>0/10 * * * * ?</value>
</property>
</bean>
<!-- 3:注册一个统一的调度工厂,通过这个调度工厂调度任务 -->
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<!-- 注入多个触发器 -->
<property name="triggers">
<list>
<ref bean="myTrigger"/>
</list>
</property>
</bean>
</beans>
(4)web.xml中定义
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application-jobs.xml</param-value>
</context-param>
</web-app>
执行Tomcat观察控制台,可以发现每隔10秒会输出一次,说明每隔10秒自定义Job被调用一次。
8.2 cron例子
(1)0 0 2 1 * ? * 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(4)0 0 12 ? * WED 表示每个星期三中午12点
(5)0 0 12 * * ? 每天中午12点触发
(6)0 15 10 ? * * 每天上午10:15触发
(7)0 15 10 * * ? 每天上午10:15触发
(8)0 15 10 * * ? * 每天上午10:15触发
(9)0 15 10 * * ? 2005 2005年的每天上午10:15触发
(10)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(11)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(12)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(13)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(14)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(15)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(16)0 15 10 15 * ? 每月15日上午10:15触发
(17)0 15 10 L * ? 每月最后一日的上午10:15触发
(18)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(19)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
(20)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
(21)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(22)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
8.3cron表达式在线生成器
http://cron.qqe2.com/
第九章 poi
9.1 简介
官方主页: http://poi.apache.org/index.html
API文档: http://poi.apache.org/apidocs/index.html
maven
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.14</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.14</version>
</dependency>
HSSF - 提供读写Microsoft Excel XLS格式档案的功能
XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能(我们使用)
HWPF - 提供读写Microsoft Word DOC格式档案的功能
HSLF - 提供读写Microsoft PowerPoint格式档案的功能
HDGF - 提供读Microsoft Visio格式档案的功能
HPBF - 提供读Microsoft Publisher格式档案的功能
HSMF - 提供读Microsoft Outlook格式档案的功能
9.2 从Excel文件读取数据
@Test
public void test1() throws Exception{
// 1:创建工作簿对象
XSSFWorkbook xssfWorkbook = new XSSFWorkbook("D:\\aaa.xlsx");
// 2:获得工作表对象
XSSFSheet sheet = xssfWorkbook.getSheetAt(0);
// 3:遍历工作表对象 获得行对象
//第一种
int firstRowNum = sheet.getFirstRowNum();
int lastRowNum = sheet.getLastRowNum();
for (int i = firstRowNum; i <= lastRowNum; i++) {
XSSFRow row = sheet.getRow(i);
int firstCellNum = row.getFirstCellNum();
int lastCellNum = row.getLastCellNum(); //2 似乎是原来的加一,下面不能写<=
for (int j = firstCellNum; j < lastCellNum; j++) {
String stringCellValue = row.getCell(j).getStringCellValue();
System.out.println(stringCellValue);
}
}
//第二种
for (Row row : sheet) {
// 4:遍历行对象 获得单元格(列)对象
for (Cell cell : row) {
// 5:获得数据
String stringCellValue = cell.getStringCellValue();
System.out.println(stringCellValue);
}
}
// 6:关闭
xssfWorkbook.close();
}
9.3 向Excel文件写入数据
@Test
public void test2() throws Exception{
// 1:创建工作簿对象
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
// 2.创建工作表对象
XSSFSheet sheet = xssfWorkbook.createSheet("sheet名字");
// 3.创建行对象
XSSFRow row = sheet.createRow(0);
// 4.创建列(单元格)对象, 设置内容 这是一行数据
row.createCell(0).setCellValue("zhangsan1");
row.createCell(1).setCellValue("12");
XSSFRow row1 = sheet.createRow(1);
row1.createCell(0).setCellValue("lisi");
row1.createCell(1).setCellValue("15");
// 5.通过输出流将workbook对象下载到磁盘
FileOutputStream out = new FileOutputStream("d:\\test.xlsx");
xssfWorkbook.write(out);
out.flush();
out.close();
xssfWorkbook.close();
}
ft PowerPoint格式档案的功能
HDGF - 提供读Microsoft Visio格式档案的功能
HPBF - 提供读Microsoft Publisher格式档案的功能
HSMF - 提供读Microsoft Outlook格式档案的功能
##### 9.2 从Excel文件读取数据
```java
@Test
public void test1() throws Exception{
// 1:创建工作簿对象
XSSFWorkbook xssfWorkbook = new XSSFWorkbook("D:\\aaa.xlsx");
// 2:获得工作表对象
XSSFSheet sheet = xssfWorkbook.getSheetAt(0);
// 3:遍历工作表对象 获得行对象
//第一种
int firstRowNum = sheet.getFirstRowNum();
int lastRowNum = sheet.getLastRowNum();
for (int i = firstRowNum; i <= lastRowNum; i++) {
XSSFRow row = sheet.getRow(i);
int firstCellNum = row.getFirstCellNum();
int lastCellNum = row.getLastCellNum(); //2 似乎是原来的加一,下面不能写<=
for (int j = firstCellNum; j < lastCellNum; j++) {
String stringCellValue = row.getCell(j).getStringCellValue();
System.out.println(stringCellValue);
}
}
//第二种
for (Row row : sheet) {
// 4:遍历行对象 获得单元格(列)对象
for (Cell cell : row) {
// 5:获得数据
String stringCellValue = cell.getStringCellValue();
System.out.println(stringCellValue);
}
}
// 6:关闭
xssfWorkbook.close();
}
9.3 向Excel文件写入数据
@Test
public void test2() throws Exception{
// 1:创建工作簿对象
XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
// 2.创建工作表对象
XSSFSheet sheet = xssfWorkbook.createSheet("sheet名字");
// 3.创建行对象
XSSFRow row = sheet.createRow(0);
// 4.创建列(单元格)对象, 设置内容 这是一行数据
row.createCell(0).setCellValue("zhangsan1");
row.createCell(1).setCellValue("12");
XSSFRow row1 = sheet.createRow(1);
row1.createCell(0).setCellValue("lisi");
row1.createCell(1).setCellValue("15");
// 5.通过输出流将workbook对象下载到磁盘
FileOutputStream out = new FileOutputStream("d:\\test.xlsx");
xssfWorkbook.write(out);
out.flush();
out.close();
xssfWorkbook.close();
}