redis 乐观锁_数据库乐观锁实现

redis 乐观锁_数据库乐观锁实现文章目录GeospatialHyperloglogBitmapsRedis事务悲观锁和乐观锁JedisSpringboot继承RedisGeospatial存储地理位置的数据结构应用场景朋友的定位,附近的人,打车距离计算Geospatial底层使用的是Zset127.0.0.1:6379> geoadd city 116.23 40.22 beijing 添加一个数据127.0.0.1:6379> geoadd city 121.47 31.23 shanghai 118.77

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

Geospatial

存储地理位置的数据结构
应用场景
朋友的定位,附近的人,打车距离计算
Geospatial底层使用的是Zset

127.0.0.1:6379> geoadd city 116.23 40.22 beijing		添加一个数据
127.0.0.1:6379> geoadd city 121.47 31.23 shanghai 118.77 32.04 nanjing 113.28 23.13 guangzhou 114.09 22.55 shenzhen

127.0.0.1:6379> geodist city beijing tianjin		//计算距离
“157.7274”

127.0.0.1:6379> geopos city beijing tianjin shanghai nanjing guangzhou shenzhen		//返回指定城市的经纬度

127.0.0.1:6379> geohash city beijing tianjin	//返回一个或多个位置元素的 Geohash 表示
“wx4sucu47r0”
"wwgmftx1sz0"

127.0.0.1:6379> georadius city 120 20 1000 km //以120,20为中心,寻找方圆1000米的城市
"shenzhen"
"guangzhou"
127.0.0.1:6379> georadius city 120 20 1000 km withdist 	//返回位置元素的同时,将位置元素和中心之间的距离一并返回
"shenzhen"
"674.9271"
"guangzhou"
"777.2656"
127.0.0.1:6379> georadius city 120 20 1000 km withcoord		//将位置信息和经度纬度一并返回
127.0.0.1:6379> georadius city 120 20 1000 km withcoord count 1  	//	选取前1个匹配的元素		

127.0.0.1:6379> georadiusbymember city beijing 1000 km		//找出位于指定元素周围的其他元素

//GEO 底层的实现原理其实就是 Zset!我们可以使用Zset命令来操作geo!。
127.0.0.1:6379> zrange city 0 -1
127.0.0.1:6379> zrem city tianjin

Hyperloglog

什么是基数
A{1,3,5,7,8,7}
基数(不重复的元素) = 5

Hyperloglog简介
Hyperloglog就是做基数统计的一个算法。
Hyperloglog类型允许接受误差

应用场景
网页的浏览量:(一个人访问一个网站多次,当时还是算作一个人)
传统方法:使用set保存用户id,然后就可以统计set中的元素数量作为判断标准,但是这种方法需要保存大量的用户id,耗费内存。这时候需要用到Hyperloglog数据结构

Hyperloglog的优缺点:
优点:占用的内存是固定的,比如要放2^64不同的元素,则只需要固定的12KB的内存。
缺点:有0.81%的错误率。如果不允许出错,那么则只能使用set

linux命令

127.0.0.1:6379> PFADD mykey a b c d e f g h i j	//创建Hyperloglog
(integer) 1
127.0.0.1:6379> PFCOUNT mykey		//数量
(integer) 10
127.0.0.1:6379> PFADD mykey2 i j z x c v b n m
(integer) 1

127.0.0.1:6379> PFMERGE mykey3 mykey1 mykey2		//合并mykey1 喝  mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3		//合并后 消除重复元素
(integer) 9

Bitmaps

位存储
32位机器上的自然数一共有2的32次方约42亿个,如果用一个bit来存放一个整数,1代表存在,0代表不存在,那么把全部自然数存储在内存只要4294967296 / (8 * 1024 * 1024) = 512MB(8bit = 一个字节),而这些自然数存放在文件中,一行一个数字,1个整数4个字节,48512MB需要16G的容量。可见,bitmap算法节约了非常多的空间。
应用场景:
比如统计用户信息,活跃或不活跃,打开,或者未打卡,登录,或者未登录。只要设计两个状态的都可以使用Bitmaps。

127.0.0.1:6379> SETBIT sign 0 1		//setbit sign offset status(0/1)
(integer) 0		
127.0.0.1:6379> SETBIT sign 2 1
(integer) 0
127.0.0.1:6379> BITCOUNT sign		//统计有多少位是1
(integer) 2
127.0.0.1:6379> 

Redis事务

Redis只是单条命令保证原子性,但是事务并不保证原子性
Redis的事务就是一组命令的集合
redis 是单线程的,也就意味着它总是以串行方式执行,同一时刻内不会有其他事务打断当前事务的执行。redis的事务具有隔离性,也就意味这Redis没有一堆脏读,不可重复读,等一些列问题。
命令
开启事务:multi
命令入队:众多Redis命令
执行事务:exec

127.0.0.1:6379> multi		//开启事务
OK
127.0.0.1:6379> set k1 v1		//命令入队
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec			//执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD		//放弃事务
OK
127.0.0.1:6379> get k1
(nil)

事务不保证原子性 实例
编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3		//命令语法错误
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> EXEC		//不能执行事务,所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.

运行时异常(代码没问题,但是实际运行的时候发现不对),那么执行命令的时候,其他命令是可以正常执行的。

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1		//这条命令运行时错误
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range		//虽然事务中有一条运行时错误的命令,但是第二条命令还是会执行
2) OK
127.0.0.1:6379> get k2
"v2"

悲观锁和乐观锁

悲观锁:认为什么时候都会有问题,无论做什么都会加锁
乐观锁:认为什么时候都不会有问题,无论做什么都不会上锁。但是需要机制去判断一下再次期间是否有人更改了数据
乐观锁version版本
使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据

//更新商品信息1 
    Goods goods1 = this.goodsDao.getGoodsById(goodsId);  //获取了version值
    goods1.setStatus(2);
    int updateResult1 = this.goodsDao.updateGoodsUseCAS(goods1);  //version值并没有被更改
    System.out.println("修改商品信息1"+(updateResult1==1?"成功":"失败"));  
      
    goods1.setStatus(2);
    int updateResult2 = this.goodsDao.updateGoodsUseCAS(goods1);  //version值早已经被更改,所以更新失败
    System.out.println("修改商品信息2"+(updateResult2==1?"成功":"失败"));

Redis使用监控机制来实现乐观锁

127.0.0.1:6379> set mymoney 100
OK
127.0.0.1:6379> set yourmoney 0
OK

# 线程1先开启监控		这时相当于线程1拿到version
127.0.0.1:6379> watch mymoney
OK

# 线程2开始对进行转账操作修改值 这时候相当于线程2更改version
127.0.0.1:6379> INCRBY mymoney -50
(integer) 50
127.0.0.1:6379> INCRBY yourmoney 50
(integer) 50


#	线程1 开始进行转账,发现这时的version和之前拿到的version不相等,执行失败
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY mymoney 10
QUEUED
127.0.0.1:6379> INCRBY yourmoney 10
QUEUED
127.0.0.1:6379> EXEC
(nil)
# 如果修改失败,重新unwatch然后watch拿到最新的值
# 可以先撤销监控 在重新监控 这样拿到的就是最新值
127.0.0.1:6379> UNWATCH
OK
127.0.0.1:6379> WATCH mymoney
OK
.....继续执行命令

Jedis

Jedis是Redis官方推荐的java链接开发工具,使用java操作Redis中间件,那么一定要对jedis十分熟悉
1.windows下 启动jedis服务器

redis-server.exe redis.windows.conf
  1. 导入对应依赖
<dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>
  1. 连接数据库并操作
public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println(jedis.ping());
        System.out.println(jedis.set("key1","value1"));
        System.out.println(jedis.get("key1"));
        System.out.println(jedis.select(2));
        System.out.println(jedis.flushDB());
        jedis.close();
        ...
    }

自定义RedisTemplate

public void test() throws JsonProcessingException { 
   
        User user = new User("user",22);
        String json = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",json);
        System.out.println(redisTemplate.opsForValue().get("user"));//结果{"name":"user","age":22}
    }

查看Linux

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user"		# 乱码

直接传对象需要对象序列化

public void test() throws JsonProcessingException {
        User user = new User("user",22);	//对象需要序列化
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user"		//仍然乱码

自定义RedisTemplate

@Bean
    @ConditionalOnMissingBean(
        name = { 
   "redisTemplate"}
    )	//我们可以自定义RedisTemplate
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { 
   
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

		//默认序列化是jdk序列化 我们需要更改这个才能使得传递的key value显示正常字符串,否则默认的序列化是二进制
	if (this.defaultSerializer == null) { 
   
            this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
        }

自定义RedisTemplate

@Configuration
public class RedisConfig { 
   
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){ 
   
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // Jackson序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 字符串序列化配置
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        
        //redisTemplate配置key value的序列化方实
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

这下再用我们自定义的redisTemplate进行Redis传递数据


    @Qualifier("redisTemplate")
    @Autowired
    RedisTemplate redisTemplate;
	@Test
    public void test() throws JsonProcessingException { 
   
        User user = new User("user",22);
        redisTemplate.opsForValue().set("user",user);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

这下使用我们自定义的RedisTemplate就不会出现二进制了

127.0.0.1:6379> keys *
1) "user"

Redis.conf详解

# Redis单位
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

# units are case insensitive so 1GB 1Gb 1gB are all the same.		# 对大小写不敏感

# include .\path\to\local.conf	//可以包含其他Redis配置文件
# include c:\path\to\other.conf

bind 127.0.0.1	#默认绑定本地ip  可以填 * 让所有网络连接

port 6379		# 端口设置

# GENERAL通用配置
daemonize yes	#默认是No,以守护进程的方实打开。一推出,进程就结束
pidfile /var/run/redis_6379.pid   	# 如果以后台的方实运行,我们就需要指定一个pid文件
# 日志级别
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice

logfile ""		# 日志文件位置

databases 	# 默认数据库数量

save 900 1		# 900s 内如果至少有一个key进行修改,则进行持久化操作
save 300 10	# 300s 内如果至少10个key 进行修改,则进行持久化
save 60 10000

stop-writes-on-bgsave-error yes		# 持久化出错后是否还让redis继续工作

rdbcompression yes 	# 是否压缩rdb文件	需要消耗一些cpu资源

rdbchecksum	yes	#保存rdb文件的时候,进行错误的检查校验

dir ./		# rdb文件保存的目录

# SERCURITY		安全
required password 	# 配置登录redis密码	可以使用Linux命令	config set requiredpass

# Clients 客户端配置
# maxclients 10000		最大的客户端数量

# maxmemory <bttes>	#redis 占用最大内存
# maxmemory-policy noeviction	#内存达到上限之后的处理策略


版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/169025.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 微信强制解绑手机号码方法[通俗易懂]

    微信强制解绑手机号码方法[通俗易懂]现在微信不用手机号已经无法进行注册,但是一个手机号却可以多次注册。这问题就来了,怎么将手机号进行解绑然后继续注册呢?7月27日实测有效。步骤如下:1、记住需要解绑手机的微信号,然后退出微信2、

  • MQTT服务器搭建

    MQTT服务器搭建1、MQTT是一种消息传输协议,和我们常用的RabbitMq比较类似,不过MQTT我们基本都是用于在物联网(比如说连接边缘计算机采集PLC数据)。2、MQTT通讯模式看下边这张图应该就可以明白。发布者和订阅者提前约定一个主题,当发布者在这个主题下发布任何消息,订阅者就自动接收到了。3、windows搭建MQTT服务器,网上大多资料都是说的客户端,刚入坑的朋友可能就分不清,搞得很懵。我在这里说下我的模式,我租一台阿里服务器,在服务器上搭建MQTT服务,我本地跑一个客户端,用来测试订阅其他客户端给我服务器发

  • idea2019.3激活码(注册激活)

    (idea2019.3激活码)本文适用于JetBrains家族所有ide,包括IntelliJidea,phpstorm,webstorm,pycharm,datagrip等。IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/ide…

  • iframe高度自适应_读取跨域iframe

    iframe高度自适应_读取跨域iframe采用JavaScript来控制iframe元素的高度是iframe高度自适应的关键,同时由于JavaScript对不同域名下权限的控制,引发出同域、跨域两种情况。由于客户端js使用浏览器的同源安全策略,跨域情况下,被嵌套页面如果想要获取和修改父页面的DOM属性会出现权限不足的情况,提示错误:Permissiondeniedtoaccessproperty’document’。这是

    2022年10月12日
  • 【Win7】【磁盘管理】删除相似“33fbc1d57e9aaf1ea88e6f08”缓存目录

    【Win7】【磁盘管理】删除相似“33fbc1d57e9aaf1ea88e6f08”缓存目录

    2021年12月31日
  • popd和pushd使用

    转自http://blog.163.com/benben_long/blog/static/199458243201211334556266/让切换目录更方便:pushd,popd,dirs,cd-一,为何要使用这几个命令?   可能大家会有疑问,为何要使用这几个命令,   难道用cd不就可以切换目录了吗?   没错,使用cd就可以切换到需要访问的目录,   但

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号