redis 乐观锁_什么时候用乐观锁

redis 乐观锁_什么时候用乐观锁文章目录 Geospatial Hyperloglog Bitmaps Redis 事务 悲观锁和乐观锁 Jedis 自定义 RedisTemplat Redis conf 详解 Geospatial 存储地理位置的数据结构 应用场景 朋友的定位 附近的人 打车距离计算 Geospatial 底层使用的是 Zset 127 0 0 1 6379 geoadd city 116 23 40

文章目录

Geospatial

Hyperloglog

Bitmaps

Redis事务

悲观锁和乐观锁

Jedis

自定义RedisTemplate

Redis.conf详解

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

导入对应依赖



redis.clients
jedis
3.2.0


com.alibaba
fastjson
1.2.62

/code>

code class='prism'>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();


...


}

/code>

code class='prism'>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}


}

/code>

code class='prism'>127.0.0.1:6379>keys *


1) "\xac\xed\x00\x05t\x00\x04user" # 乱码

/code>

code class='prism'>public void test() throws JsonProcessingException {


User user = new User("user",22); //对象需要序列化


redisTemplate.opsForValue().set("user",user);


System.out.println(redisTemplate.opsForValue().get("user"));


}

/code>

code class='prism'>127.0.0.1:6379>keys *


1) "\xac\xed\x00\x05t\x00\x04user" //仍然乱码

/code>

code class='prism'>@Bean


@ConditionalOnMissingBean(


name = {


"redisTemplate"}


) //我们可以自定义RedisTemplate


public RedisTemplate

redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {

RedisTemplate 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 redisTemplate(RedisConnectionFactory redisConnectionFactory){

RedisTemplate 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 #redis 占用最大内存
# maxmemory-policy noeviction #内存达到上限之后的处理策略
编程小号
上一篇 2025-07-01 19:17
下一篇 2025-07-11 13:11

相关推荐

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