概述

本文主要是基于前一篇 ​​Redis 核心数据结构和应用​​ 的一个补充主要讲述一下几个数据类型和运用。

  1. pipline
  2. lua 脚本
  3. Geo
  4. bitmap

主要是简单的说明使用场景和 demo 代码。 jedis 版本 ​​2.9.0​

pipline 管道

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  • 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
  • 服务端处理命令,并将结果返回给客户端。

Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。

public class RedisPipelineTest {

public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);

// 使用管道
long start = System.currentTimeMillis();
Pipeline pipeline = jedis.pipelined();
for (int i = 1; i <= 10000; i++) {
pipeline.set("abc" + i, "i=" + i);
}
pipeline.multi();
pipeline.exec();
long end = System.currentTimeMillis();
// 50ms
System.out.println("pipeline cost time :" + (end - start) + "ms");


jedis.resetState();
start = System.currentTimeMillis();
for (int i = 1; i <= 10000; i++) {
jedis.set("abcd" + i, "i=" + i);
}
end = System.currentTimeMillis();
//261ms
System.out.println("pipeline cost time :" + (end - start) + "ms");
}
}

由此我们可以的出来结论,pipeline 适合批量操作的场景,对于时间的开销有明显的优势。

lua 脚本

Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。

由于 LUA 脚本在 Redis 得执行过程中具有原子性,并且保证 LUA 脚本内得所有操作要么同时成功,如果失败会进行回滚,如果我们对于事务操作就可以用 Lua 脚本来实现, 下面是一个互斥锁的实例代码:

public class RedisLuaTest {

public static void main(String[] args) {
// 实现排他锁 demo
Jedis jedis = new Jedis("127.0.0.1", 6379);
String key = "test:redis:lock";
// 每个线程 value 不同,这里可以使用 uuid
String val = "1000";
String result = jedis.set(key, val, "nx", "px", 100000);
if ("OK".equals(result)) {
System.out.println("加锁成功: key = " + key + "; value=" + val);
}
// lua 脚本的使用
Long count = (Long) jedis.eval("if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
" return redis.call(\"del\",KEYS[1])\n" +
"else\n" +
" return 0\n" +
"end", Arrays.asList(key), Arrays.asList(val));
if (count == 1) {
System.out.println("解锁成功: key = " + key + "; value=" + val);
}
}
}

Geo 地址位置信息

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,该功能在 Redis 3.2 版本新增。

Redis GEO 操作方法有:

  • geoadd:添加地理位置的坐标。
  • geopos:获取地理位置的坐标。
  • geodist:计算两个位置之间的距离。
  • georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
  • georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
  • geohash:返回一个或多个位置对象的 geohash 值。

使用场景:

  1. 附近得人,附近得位置搜索

下面是一个通过城市位置查询周边城市得一个 demo, 查询条件通过经纬度查询周边信息:

public class RediGeoTest {

static class Coordinate {
//经度
public double lng;
//纬度
public double lat;
//位置
public String name;

public Coordinate(double lng, double lat, String name) {
this.lng = lng;
this.lat = lat;
this.name = name;
}
}

public static void main(String[] args) {
// GEO 调用
Jedis jedis = new Jedis("127.0.0.1", 6379);
// 高德地图坐标拾取: https://lbs.amap.com/console/show/picker
//104.066143,30.573095 成都
//104.741722,31.46402 绵阳
//103.001033,29.987722 雅安
//103.761263,29.582024 乐山
//106.550464,29.563761 重庆
List<Coordinate> list = new ArrayList<>();
list.add(new Coordinate(30.573095, 104.066143, "chengdu"));
list.add(new Coordinate(31.46402, 104.741722, "mianyang"));
list.add(new Coordinate(29.987722, 103.001033, "yaan"));
list.add(new Coordinate(29.582024, 103.761263, "lesan"));
list.add(new Coordinate(29.563761, 106.550464, "chongqing"));

// 存储位置
String key = "geo:city:001";
for (Coordinate item : list) {
// geoadd 106.550464 29.563761 chongqing
jedis.geoadd(key, item.lat, item.lng, item.name);
}

//查询成都为中心 50km 内的城市
// georadius geo:city:001 104.066143 30.573095 200 km asc withcoord withdist
List<GeoRadiusResponse> citys = jedis.georadius(key, 104.066143, 30.573095, 2000, GeoUnit.KM,
GeoRadiusParam.geoRadiusParam()
//由近到远
.sortAscending()
//返回经纬度
.withCoord()
//返回距离
.withDist());
citys.stream().forEach(item -> {
String val = "member:" + new String(item.getMember()) + "; distance:" + item.getDistance() + ";";
GeoCoordinate coordinate = item.getCoordinate();
if (coordinate != null) {
val += " lat:" + coordinate.getLatitude() + "; lng:" + coordinate.getLongitude();
}
System.out.println(val);
});

//返回结果
//member:chengdu; distance:2.0E-4;lat:30.573095634661236; lng:104.06614512205124
//member:lesan; distance:114.0718;lat:29.582024730802026; lng:103.76126378774643
//member:mianyang; distance:118.1798;lat:31.464019705514964; lng:104.74172383546829
//member:yaan; distance:121.2653;lat:29.987722060681172; lng:103.00103455781937
//member:chongqing; distance:264.1675;lat:29.56376206484898; lng:106.55046612024307

}
}

bitmap 位图

Bitmap 位图,数据结构。 操作二进制位来进行记录,就只有0 和 1 两个状态。 可以用来记录打卡,查看当天是否打卡,统计打卡天数, 或者点赞次数统计。下面是一个简单的 demo 代码 优点:通过一个 bit 位来表示某个元素对应的值或者状态, 其中的key就是对应元素本身。我们知道 8 个 bit可以组成一个 Byte,所以 bitmap 本身会极大的节省储存空间。

public class RedisBitMapTest {

public static void main(String[] args) throws IOException {
// redis 位图
Jedis jedis = new Jedis("127.0.0.1", 6379);

//记录新闻订阅用户
String key = "article:like:1001";
Pipeline pp = jedis.pipelined();
for (int offset = 1; offset <= 500000; offset++) {
pp.setbit(key, offset, offset % 3 == 0);
}
pp.multi();
pp.exec();
pp.close();

Long count = jedis.bitcount(key);
System.out.println("key=" + key + "; count=" + count);

//用户签到(不考虑数据持久化存储)
int start = 20200101;
int now = Integer.parseInt(new SimpleDateFormat("yyyyMMdd").format(new Date()));
key = "user:sign:1001";
int offset = now - start;
for (int i = 0; i <= 365; i++) {
jedis.setbit(key, i, String.valueOf((i % 3 == 0 ? 1 : 0)));
}
// 累计签到
count = jedis.bitcount(key);
System.out.println("用户: 1001 累计签到: " + count);

// 是否签到
Boolean isSign = jedis.getbit(key, offset);
System.out.println("用户: 1001 " + now + " 是否签到: " + isSign);

// 1 月份签到天数
count = jedis.bitcount(key, 0, 30);
System.out.println("用户: 1001 1 月份累计签到: " + count);

// 输出结果
// key=article:like:1001; count=333332
// 用户: 1001 累计签到: 122
// 用户: 1001 20210127 是否签到: false
// 用户: 1001 1 月份累计签到: 83
}
}