Redis 安装 及链接
下载Redis安装包 并解压 tar -zxvf redis-7.0.8.tar.gz
因为redis是使用c语言写的 所以还需要下载一个 gcc解析器
yum install gcc
进入目录 cd redis-7.0.8
然后
make distclean && make
完成后
make install
启动
src/redis-server
能看到 千层饼 证明启动完成 但这样有一个问题 那就是会占用掉控制台 所以我们需要配置 redis.conf文件 (因为以后也会多次配置该文件 防止 该文件被不小心弄乱 可以先cp一份保存 cp redis.conf redis.conf.bak)
在该配置文件内 找到 daemonize no (通过 查看模式 / 搜索 damonize 按n 下一个)
将它改成 daemonize yes 表示按照配置文件启动 如果不加后面的 则会以默认的方式启动 刚才配置的就不会生效
通过 ps -ef|grep redis 可以查看是否启动成功
输入 src/redis-cli 可以进入 redis的客户端 输入ping 返回 PONG 说明链接成功
关闭 SHUTDOWN
退出 exit
以上内容 可以保证 redis在linus上正常运行 但是 如果我们使用一些工具(如RedisDesktopManager等图形化页面工具)进行操作 则无法链接上 需要开启远程链接权限
在 redis.conf 配置文件中找到 bind 127.0.0.1 -::1 这个就表示 只能在本地 链接到 redis 可以将它注释掉 这样在任意位置都可以进行链接了 或者自己手动添加允许访问的地址
同时在找到 protected-mode yes 将远程链接的保护措施设置为 no 但这样做不安全 所以建议选择设置密码
找到 #requirepass foobared 将注释关闭 将foobared 改成你要设置的密码 例 requirepass 123
设置完成后 重启 redis即可 1.src/redis-cli 2.SHUTDOWN 3.exit 4.src/redis-server redis.conf
重启2: 先找到redis 的进程 ps -ef|grep redis 然后在kill -9 进程号 最后再重新启动 src/redis-server redis.conf
因为设置了密码 进入进程 src/redis-cli 后需要输入密码 auth-123 或者在进入进程时就加上密码 src/redis-cli -a 123但这样不安全 不建议使用
数据类型
以下类型指的是 value的类型 而不是 k的类型
keys * 查询所有
flushAll 清除所有
Spring
Bitmap
这个命令操作的是 储存数据的底层二进制
以 "a"为例 他的ascll码 为 97 二进制为 0110 0001
setbit命令可操作的就是 这个二进制数
setbit k(k为a的k值) 7(操作的就是第七位数 从0开始) 0(将第七位即1 改为0)
bitcount计算这个 k 中有多少位 是1 经典运用场景: 打卡签到
Hash
这个的数据类型 k的值还是字符串 value的值又是由 k-v组成
例: 张三 [年龄 18 名字 zhangsan 地址 广州]
使用java代码链接Redis(重点)
使用Jedis链接Redis
jedis 是 Redis 官方推荐的Java连接开发工具;使用 Java 操作 redis 中间件;在企业中用的最多的就是Jedis。Jedis提供了完整Redis命令,而Redisson有更多分布式的容器实现。 如果使用jedis操作redis,那么需要对redis基本操作命令要十分熟悉;
1.创建一个maven工程 导入依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
2.连接redis
public static void main(String[] args) {
//直接 new 一个Jedis对象 传递 链接地址 和端口号 默认端口号就是 6379
Jedis jedis = new Jedis("192.168.217.129", 6379);
//直接调用 auth 方法 输入密码 (auth就是 redis输入密码的命令)
jedis.auth("123");
//flushAll() 清除数据库中原有的数据
jedis.flushAll();
//set 添加/修改 k1的数据
jedis.set("k1","2222");
System.out.println(jedis.get("k1"));
jedis.set("k1","123");
System.out.println(jedis.get("k1"));
//关闭 jedis 释放资源
jedis.close();
}
这种方法 有一个很大缺点就是每次我们操作redis 都需要进行一次链接(如果一直不关当然可以但是这样会造成大量的资源浪费)
基于这种缺点我们可以建立一个连接池 这样我们需要使用redis的时候就可以自动到连接池中去获取一个进程 不需要的时候放回池里面
1. 建立一个连接池
public class Pool {
public Jedis pool() {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100); // 连接池中最大的活跃数
poolConfig.setMaxIdle(10); // 最大空闲数
poolConfig.setMinIdle(5); // 最小空闲数
poolConfig.setMaxWaitMillis(3000); // 当连接池空了之后,多久没获取到Jedis对象,就超时
//建立连接池
JedisPool pool = new JedisPool(poolConfig, "192.168.217.129", 6379);
//通过getResource() 方法 在连接池中获取 jedis对象
Jedis jedis = pool.getResource();
//输入密码
jedis.auth("123");
return jedis;
}
}
2.使用 需要调用Jedis的时候 直接new一个 Pool对象 调用pool 方法就可以了 如:
public static void main(String[] args) {
//获取连接池对象 并调用 方法 获取 jedis对象
Jedis jedis=new Pool().pool();
jedis.set("k5","55");
System.out.println(jedis.get("k5"));
jedis.close();
jedis.set("k5","666");
System.out.println(jedis.get("k5"));
jedis.close();
}
二.
public static void main(String[] args) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100); // 连接池中最大的活跃数
poolConfig.setMaxIdle(10); // 最大空闲数
poolConfig.setMinIdle(5); // 最小空闲数
poolConfig.setMaxWaitMillis(3000); // 当连接池空了之后,多久没获取到Jedis对象,就超时
JedisPool jedisPool = new JedisPool(poolConfig, "192.168.217.129", 6379, null, "123");
//获取一个链接
redis.clients.jedis.Jedis jedis = jedisPool.getResource();
jedis.set("11","22");
System.out.println(jedis.get("11"));
}
使用连接池操作Redis,避免频繁创建和销毁链接对象消耗资源
Redis在 springBoot项目中: 首选Spring Data Redis 框架
SpringDataRedis
1.建立一个 springboot项目 选择 spring web 和 Spring Data Redis(Access+Driver)
pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
//test测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.配置一下 redis的链接信息
spring.data.redis.host=192.168.217.129
spring.data.redis.password=123
spring.data.redis.port=6379 #默认就为 6379 如果没有修改的话 可以不用配置
3. //直接注入 RedisTemplate就可以了
@Autowired
StringRedisTemplate stringRedisTemplate;
@Test
void contextLoads() {
//opsForXXX 对应的就是 redis中的数据类型 下面以value举例
//调用opsForValue() 方法 获得一个redis链接对象 赋给 ops 然后直接使用就可以了
ValueOperations ops = stringRedisTemplate.opsForValue();
ops.set("k","123666");
System.out.println(ops.get("k"));;
}
3.2. //如果使用的是 RedisTemplate 它的存储规则是 key和value默认都是对象,即使存入的是字符串,也是当对象来对待的. 而对象则必须序列化之后才能存到 Redis中 所有这里kkk最终存储完成后 就不仅仅是kkk了 它在数据库中显示的是 \xAC\xED\x00\x05t\x00\x03kkk 这是个对象
@Test
void contextLoads() {
ValueOperations ops = redisTemplate.opsForValue();
ops.set("kkk","1233");
System.out.println(ops.get("kkk"));
}
注意: 如何存储的是一个对象(上面的存的是一个字符串 只是它是以对象的形式存的) 那么存储的对象需要实现序列化 存储的对象实现序列化接口即可 如:
public class Book implements Serializable {
private Integer id;
private String name;
private String author;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
}
//存储到 Redis数据库中 k的值为对象形式 v的值也是对象形式 \xAC\xED\x00\x05sr\x00\x17com.qf.jedis_dome2.BookT&S&\xDC\xCA\xE8L\x02\x00\x03L\x00\x06authort\x00\x12Ljava/lang/String;L\x00\x02idt\x00\x13Ljava/lang/Integer;L\x00\x04nameq\x00~\x00\x01xpt\x00\x02zssr\x00\x11java.lang.Integer\x12\xE2\xA0\xA4\xF7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xAC\x95\x1D\x0B\x94\xE0\x8B\x02\x00\x00xp\x00\x00\x00\x0Ct\x00\x06\xE4\xB8\x89\xE5\x9B\xBD
@Test
void test1() {
//对象
ValueOperations ops = redisTemplate.opsForValue();
Book book = new Book();
book.setId(12);
book.setAuthor("zs");
book.setName("三国");
ops.set("book",book);
System.out.println(ops.get("book"));
}
#Redis使用 spring cache框架
1.创建一个 springboot项目 选择 spring web 和 Spring Data Redis(Access+Driver) 以及Spring Cache abstraction
pom:
//spring cache
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
//测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2. 配置redis链接
spring.data.redis.host=192.168.217.129
spring.data.redis.password=123
spring.data.redis.port=6379
3.
自定义Redis客户端
session共享
现如今有很多项目都是部署在多个不同服务器,虽然我们访问的同一个ip地址 但是可能是不同服务器在运行
如图:
不同服务器之间并没有共通 如果假设现在 我们需要访问一段数据在服务器a上 我们登录确是在 服务器b上 这时候我们就拿不到想要的数据了 既然是因为不同服务器之间数据没有共通才导致的这种问题 那我们只要解决这个储存问题就好了 将他们的数据都放入这个公共的储存区就好了
如图:
首先想到的应该就是数据库,只要这些服务器集群共享一个数据库,并把生成的session信息存放到数据库中不就可以了,这样大家都可以访问;数据库有关系型和非关系型(NoSql):
关系型数据库:Mysql等
非关系型数据库:Redis(K/V数据库)等
这里其实选择非关系型数据库最好,因为Redis基于内存,读写性能高,很适合这种用户信息频繁读取的情况;
还可以通过文件服务器实现,这里就不介绍了;
还有一种方法,可以通过nginx的iphash实现,该方法非常简单,但是思路和上面两种不同,原理就是同一个ip的所有请求都会被nginx进行iphash进行计算,将结果绑定到指定服务器,之后这个请求都会访问到该服务器中。 但是这样就有一些问题,首先就算负载均衡就没有太大意义了,如果绑定的服务器挂了,那么iphash也就失效了;又或者你的请求被其他服务分发而未走nginx服务,那么iphash同样不生效;所以谨慎使用;
1.创建一个springboot项目 添加springweb, Spring Data Redis(Access+Driver) 以及spring session
pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2.配置 Redis配置信息
spring.data.redis.port=6379
spring.data.redis.host=8.134.71.10
spring.data.redis.password=123
#默认的就是8080 但是因为要后面需要调用注入 所以必须配置 不然无法找到这个端口号
server.port=8080
3.写两个简单的接口 get set
@RestController
public class HelloController {
@Value("${server.port}")
Integer port;
@GetMapping("/get")
public String get(HttpSession httpSession) {
Object name = httpSession.getAttribute("name");
//返回 port看出是哪个 端口链接的就好
return name + ":" + port;
}
@GetMapping("/set")
public String set(HttpSession httpSession) {
httpSession.setAttribute("name", "shang");
//返回 port看出是哪个 端口链接的就好
return String.valueOf(port);
}
}
先到本地测试一下 确保部署到linux没问题
打成jar包 分别用 8080和8081接口 运行两个jar
java -jar redis_session-0.0.1-SNAPSHOT.jar
java -jar redis_session-0.0.1-SNAPSHOT.jar --server.port=8081
然后在访问 get/set接口
1.http://localhost:8080/get -- null:8080
2.http://localhost:8081/set -- 8081
3.http://localhost:8080/get --shang:8080
测试没问题 将数据库数据清除
将jar打包到linux上 再次启动 并再次测试 与上相同 只要将localhost 改成你的虚拟机地址
测试无误就可以进行下一步了
使用docker 装一个 nginx 我们利用nginx来做上面两个的反向代理服务器 (我们这时候访问接口 还是通过两个不同的ip即8080/8081 这在实际应用中显然并不合理 用户怎么会记多个地址呢) 完成后 我们就可以在一个 ip上(接口还是要变的)访问在不同位置的数据了
docker run --name nginx-redis_session -p 88:80 -d nginx
( 名字为nginx-redis_session 端口号为 88 后台启动 )
进入容器 docker exec -it nginx-redis_session /bin/bash
在cd etc/nginx/conf.d
查看文件 cat default.conf
复制一份 然后退出容器 exit
修改nginx的 default.conf 文件配置 vi default.conf
upstream myserver.com{
server 8.134.71.10:8080 weight=1;
#weight 表示权重 与下面都为1 就表示 发送的请求有一半在 8080这上面 一半在8081那边 这个根据实际服
#务器的性能去分配权重
server 8.134.71.10:8081 weight=1;
}
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
#将所有指定拦截请求给注释掉
#请求转发 到myserver.com 即上面配置的
# root /usr/share/nginx/html;
# index index.html index.htm;
proxy_pass http://myserver.com;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
复制到docker的nginx中 docker cp ./default.conf nginx-redis_session:/etc/nginx/conf.d/
配置完成后 重启 nginx docker restart nginx-redis_session
然后访问 http://8.134.71.10:88/get
http://8.134.71.10:88/set
访问http://8.134.71.10:88/get 时有一半是出现shang:8081 还有一半是shang:8080(这是之前配置权重的原因)
测试无误......
接口幂等性
幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口,多次发出同一个请求,必须保证操作只执行一次。 调用接口发生异常并且重复尝试时(如网络延迟等问题),总是会造成系统所无法承受的损失,所以必须阻止这种现象的发生。 比如下面这些情况,如果没有实现接口幂等性会有很严重的后果: 支付接口,重复支付会导致多次扣钱 订单接口,同一个订单可能会多次创建
在SQL的[增删改查]中 :
select: 查询操作
查询数据 不会对数据参数造成影响 查询一次或多次在数据不变的情况下 查询结果都是不会变的 所 以 select操作天然具有幂等操作
delete: 删除操作
删除操作可以分为两种:绝对删除和相对删除。其中,绝对删除不会对数据产生副作用,具有幂等性 (绝对删除 删除第一次的时候数据就没了 第二次删除 数据本身就没有了 再删一次产生的结果都是相同 的);相对删除会对数据产生副作用,不具有幂等性。
insert: 新增操作
新增操作在重复提交的场景下会出现幂等性问题,比如以上的支付场景。
update:更新操作
更新操作可以分为两种:绝对更新和相对更新。其中,绝对更新不会对数据产生副作用,具有幂等性;相对更新会对数据产生副作用,不具有幂等性。
幂等性解决方案
1.唯一索引:
使用唯一索引可以避免脏数据的 insert,当插入重复数据时数据库会抛出异常,保证了数据的唯一性。
2.乐观锁:
这里的乐观锁指的是用乐观锁的原理去实现,为数据字段增加一个version字段,当数据需要更新时,先去数据库里获取此时的version版本号
select version from tablename where xxx
更新数据时首先和版本号作对比,如果不相等说明已经有其他的请求去更新数据了,提示更新失败。
update tablename set count=count+1,version=version+1 where version=#{version}
3.悲观锁:
乐观锁可以实现的往往用悲观锁也能实现,在获取数据时进行加锁,当同时有多个重复请求时其他请求都无法进行操作
4.分布式锁:
幂等的本质是分布式锁的问题,分布式锁正常可以通过redis或zookeeper实现;在分布式环境下,锁定全局唯一资源,使请求串行化,实际表现为互斥锁,防止重复,解决幂等。
5.token机制
token机制的核心思想是为每一次操作生成一个唯一性的凭证,也就是token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。token机制的应用十分广泛。
代码: f/qf/2207/myCode/idempotence
1.创建一个spring boot项目 添加spring web和spring data redis(access+driver)依赖
pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.配置redis的链接信息
spring.data.redis.port=6379
spring.data.redis.host=8.134.71.10
spring.data.redis.password=123
3.自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//方法上使用
public @interface Idempotence {
4.定义测试接口
@RestController
public class HelloController {
@Autowired
TokenServer tokenServer;
@PostMapping("/hello")
@Idempotence
public String helloSet() {
return "hello set";
}
@GetMapping("/hello")
public String helloGet() {
return "hello get";
}
@GetMapping("/generate")
public String generateToken() {
return tokenServer.generateToken();
}
}
5.定义拦截器 (这里我们的@Idempotence 自定义注解是加在controller上的 所以我们需要配置拦截器 在拦截器中解析 这个注解)
...
//使用AOP来处理幂等性问题 还是用之前的代码 将拦截器的部分注释掉
redis的事务
MULTI 开启事务
exec 执行事务
discard 放弃执行
在redis中如果开启了事务(MULTI) 执行一些指令时 如 指令1 指令2 指令3 指令4 四个指令 这些指令会全部放在一个队列中 如果执行事务则指令全部执行(exec) 或 全部放弃执行(discard)
当事务中 指令2 是一个错误语句时 事务全部执行 指令134会执行成功而 指令2会执行失败
有些指令在 没开始执行事务时就会提醒指令错误 但只有选择执行指令时才会失败 而在提交事务后事务全部都会报错
(error) EXECABORT Transaction discarded because of previous errors.
Redis的事务想发挥功能,需要配置watch监听机制
在开启事务之前,先通过watch命令去监听一个或多个key,在开启事务之后,如果有其他客户端修改了我监听的key,事务会自动取消。
如果执行了事务,或者取消了事务,watch监听自动消除,一般不需要手动执行unwatch
例: 购物的时候 价格是锁定状态的 如果不处于锁定状态 在购买过程中 修改了价格 那么就会引起纠纷
本文详细介绍了Redis的安装过程,包括在Linux上下载、安装、配置及启动Redis,强调了如何开启远程访问权限。此外,还讨论了Redis的数据类型、使用Java的Jedis链接Redis以及在Spring中应用Redis作为缓存。文章还涵盖了Redis的事务处理和接口幂等性设计,提供了多种解决幂等性问题的方法。
548

被折叠的 条评论
为什么被折叠?



