Redis的安装与使用

本文详细介绍了Redis的安装过程,包括在Linux上下载、安装、配置及启动Redis,强调了如何开启远程访问权限。此外,还讨论了Redis的数据类型、使用Java的Jedis链接Redis以及在Spring中应用Redis作为缓存。文章还涵盖了Redis的事务处理和接口幂等性设计,提供了多种解决幂等性问题的方法。

Redis 安装 及链接


下载Redis安装包 并解压 tar -zxvf redis-7.0.8.tar.gz

Redis

redis7.0.8

因为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基本操作命令要十分熟悉;

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地址 但是可能是不同服务器在运行

如图:

CSDN图片

不同服务器之间并没有共通 如果假设现在 我们需要访问一段数据在服务器a上 我们登录确是在 服务器b上 这时候我们就拿不到想要的数据了 既然是因为不同服务器之间数据没有共通才导致的这种问题 那我们只要解决这个储存问题就好了 将他们的数据都放入这个公共的储存区就好了

如图:

CSDN图片

首先想到的应该就是数据库,只要这些服务器集群共享一个数据库,并把生成的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

例: 购物的时候 价格是锁定状态的 如果不处于锁定状态 在购买过程中 修改了价格 那么就会引起纠纷



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值