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
- 导入对应依赖
<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>
- 连接数据库并操作
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 #内存达到上限之后的处理策略