





- @SpringBootTest
- public class CaffeineTest {
-
- @Test
- void test() {
- //构建cache对象
- Cache
cache = Caffeine.newBuilder().build(); - //存数据
- cache.put("gf", "lb");
-
- //取数据
- String gf = cache.getIfPresent("gf");
- System.out.println(gf);
-
- //取取数据,如果未命中,查询数据库
- String defaultGF = cache.get("defaultGF", key -> {
- //根据key,去查询数据库
- return "hana";
- });
-
- System.out.println(defaultGF);
- }
-
- @Test
- void maxSize() throws InterruptedException {
- //构建cache对象,最大存储数量为1,如果存入多个,不会立刻删除
- Cache
cache = Caffeine.newBuilder().maximumSize(1).build(); - cache.put("gf1", "gf1");
- cache.put("gf2", "gf2");
- cache.put("gf3", "gf3");
-
- Thread.sleep(10L);
-
- System.out.println(cache.getIfPresent("gf1"));
- System.out.println(cache.getIfPresent("gf2"));
- System.out.println(cache.getIfPresent("gf3"));
- }
-
- @Test
- void expire() throws InterruptedException {
- //构建cache对象,写入后多长时间失效
- Cache
cache = Caffeine.newBuilder().expireAfterWrite(1L, TimeUnit.SECONDS).build(); - cache.put("gf1", "gf1");
- System.out.println(cache.getIfPresent("gf1"));
-
- Thread.sleep(1200L);
-
- System.out.println(cache.getIfPresent("gf1"));
- }
- }

- @Configuration
- public class CaffeineConfig {
-
- @Bean
- public Cache
userCache(){ - return Caffeine.newBuilder()
- .initialCapacity(100)
- .maximumSize(10_000)
- .build();
- }
-
- @Bean
- public Cache
blogCache(){ - return Caffeine.newBuilder()
- .initialCapacity(100)
- .maximumSize(10_000)
- .build();
- }
- }
-
- @Component
- public class CaffeineService {
- @Autowired
- private Cache
userCache; -
- @Autowired
- private Cache
blogCache; -
-
- public User getUser(Long id) {
- //当缓存中没有,则查询数据库,查到结果放入缓存。减少数据库交互,提供并发量
- return userCache.get(id, key -> {
- //select * from user where id = key
- return new User("lb", 32);
- });
- }
-
- public Blog getBlog(Long id) {
- return blogCache.get(id, key -> {
- //select * from blog where id = key
- return new Blog();
- });
- }
- }
Lua语言可以在nginx中开发使用的语言








1.安装开发库
首先要安装OpenResty的依赖开发库,执行命令:
yum install -y pcre-devel openssl-devel gcc --skip-broken
2.安装OpenResty仓库
你可以在你的 CentOS 系统中添加 openresty 仓库,这样就可以便于未来安装或更新我们的软件包(通过 yum check-update 命令)。运行下面的命令就可以添加我们的仓库:
yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo
如果提示说命令不存在,则运行:
yum install -y yum-utils
然后再重复上面的命令
3.安装OpenResty
然后就可以像下面这样安装软件包,比如 openresty:
yum install -y openresty
4.安装opm工具
opm是OpenResty的一个管理工具,可以帮助我们安装一个第三方的Lua模块。
如果你想安装命令行工具 opm,那么可以像下面这样安装 openresty-opm 包:
yum install -y openresty-opm
5.目录结构
默认情况下,OpenResty安装的目录是:/usr/local/openresty

看到里面的nginx目录了吗,OpenResty就是在Nginx基础上集成了一些Lua模块。
6.配置nginx的环境变量
打开配置文件:
vi /etc/profile
在最下面加入两行:
- export NGINX_HOME=/usr/local/openresty/nginx
- export PATH=${NGINX_HOME}/sbin:$PATH
NGINX_HOME:后面是OpenResty安装目录下的nginx的目录
然后让配置生效:
source /etc/profile
OpenResty底层是基于Nginx的,查看OpenResty目录的nginx目录,结构与windows中安装的nginx基本一致:

所以运行方式与nginx基本一致:
- # 启动nginx
- nginx
- # 重新加载配置
- nginx -s reload
- # 停止
- nginx -s stop
nginx的默认配置文件注释太多,影响后续编辑,这里将nginx.conf中的注释部分删除,保留有效部分。
修改/usr/local/openresty/nginx/conf/nginx.conf文件,内容如下:
- #user nobody;
- worker_processes 1;
- error_log logs/error.log;
-
- events {
- worker_connections 1024;
- }
-
- http {
- include mime.types;
- default_type application/octet-stream;
- sendfile on;
- keepalive_timeout 65;
-
- server {
- listen 8081;
- server_name localhost;
- location / {
- root html;
- index index.html index.htm;
- }
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- root html;
- }
- }
- }
在Linux的控制台输入命令以启动nginx:
nginx
然后访问页面:http://192.168.150.101:8081,注意ip地址替换为你自己的虚拟机IP:

请注意这里的nginx作用是下图中本地缓存nginx,不是反向代理的nginx

在反向代理的Nginx服务上已经配置 /api 的url将路由到 openResty的Nginx服务上

1.修改nginx.comf文件

- #lua 模块
- lua_package_path "/usr/local/openresty/lualib/?.lua;;";
- #c模块
- lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
- location /api/item {
- default_type application/json;
- content_by_lua_file lua/item.lua;
- }
2.编写item.lua文件

ngx.say('{"id":10001,"name":"Phone"}')
3.openResty 获取请求参数


- local id = ngx.var[1]
- -- ..是lua中的拼接符号
- ngx.say('{"id":' .. id .. ',"name":"Phone"}')



实现:
nginx.conf
- location /item {
- proxy_pass http://192.168.150.1:8081;
- }
封装http请求工具类

common.lua
- -- 封装函数,发送http请求,并解析响应
- local function read_http(path, params)
- local resp = ngx.location.capture(path,{
- method = ngx.HTTP_GET,
- args = params,
- })
- if not resp then
- -- 记录错误信息,返回404
- ngx.log(ngx.ERR, "http not found, path: ", path , ", args: ", args)
- ngx.exit(404)
- end
- return resp.body
- end
- -- 将方法导出
- local _M = {
- read_http = read_http
- }
- return _M
其中会用到cjson工具库

lua/item.lua
- --导入common函数库
- local common = require('commnon')
- local read_http = common.read_http
-
- --导入cjson函数库,
- local cjson = require('cjson')
-
- --获取路径参数
- local id = ngx.var[1]
-
- --查询商品信息 nil代表无参
- local itemJSON = read_http("/item/" .. id, nil)
- --查询库存信息
- local stockJSON = read_http("/item/stock/" .. id, nil)
-
- --转化为lua的table类型
- --JSON转table则需要cjson工具包
- local item = cjson.decode(itemJSON)
- local stock = cjson.decode(stockJSON)
-
- --组合数据 商品的库存(stock)和销量(sold),存储在stock对象中
- item.stock = stock.stock
- item.sold = stock.sold
-
- --返回结果
- ngx.say(cjson.encode(item))
但是如果存在多台JVM服务器时,缓存只存在上一次接受请求的JVM服务器上,其他的JVM中没有JVM缓存。如果JVM服务器数量很多,并且负载均衡的策略为轮训时,则依旧会造成对数据库的大量访问。则可以对request的uri进行哈希算法,让同一个id永远只会路由到同一台JVM服务器上使其JVM缓存永远生效.

修改/usr/local/openresty/nginx/conf/nginx.conf文件,内容如下:
-
- #user nobody;
- worker_processes 1;
- error_log logs/error.log;
-
- events {
- worker_connections 1024;
- }
-
- http {
- include mime.types;
- default_type application/octet-stream;
- sendfile on;
- keepalive_timeout 65;
- #lua 模块
- lua_package_path "/usr/local/openresty/lualib/?.lua;;";
- #c模块
- lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
-
- upstream tomcat-cluster {
- hash $request_uri;
- server 192.168.150.1:8081;
- server 192.168.150.1:8082;
- }
-
- server {
- listen 8081;
- server_name localhost;
- location /item {
- proxy_pass http://tomcat-cluster;
- }
- location ~ /api/(\d+) {
- default_type application/json;
- content_by_lua_file lua/item.lua;
- }
- location / {
- root html;
- index index.html index.htm;
- }
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- root html;
- }
- }
- }



- @Component
- public class RedisHandler implements InitializingBean {
-
- @Autowired
- StringRedisTemplate stringRedisTemplate;
-
- @Override
- public void afterPropertiesSet() throws Exception {
- //初始化缓存
- //查询热点数据存储到Redis中使用json
- //查询热点数据的库存和销量到Redis中使用json
- }
- }
下图中导入的Redis工具类"resty.redis"表示在lua文件夹下的resty文件夹下的redis.lua文件


common.lua
- --导入redis
- local redis = require('resty.redis')
- --初始化redis
- local red = redis:new()
- red:set_timeouts(1000,1000,1000)
-
- -- 关闭redis连接的工具方法,其实是放入连接池
- local function close_redis(red)
- local pool_max_idle_time = 10000 -- 连接的空闲时间,单位是毫秒
- local pool_size = 100 --连接池大小
- local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
- if not ok then
- ngx.log(ngx.ERR, "放入redis连接池失败: ", err)
- end
- end
-
- -- 查询redis的方法 ip和port是redis地址,key是查询的key
- local function read_redis(ip, port, key)
- -- 获取一个连接
- local ok, err = red:connect(ip, port)
- if not ok then
- ngx.log(ngx.ERR, "连接redis失败 : ", err)
- return nil
- end
- -- 查询redis
- local resp, err = red:get(key)
- -- 查询失败处理
- if not resp then
- ngx.log(ngx.ERR, "查询Redis失败: ", err, ", key = " , key)
- end
- --得到的数据为空处理
- if resp == ngx.null then
- resp = nil
- ngx.log(ngx.ERR, "查询Redis数据为空, key = ", key)
- end
- close_redis(red)
- return resp
- end
-
- -- 封装函数,发送http请求,并解析响应
- local function read_http(path, params)
- local resp = ngx.location.capture(path,{
- method = ngx.HTTP_GET,
- args = params,
- })
- if not resp then
- -- 记录错误信息,返回404
- ngx.log(ngx.ERR, "http not found, path: ", path , ", args: ", args)
- ngx.exit(404)
- end
- return resp.body
- end
- -- 将方法导出
- local _M = {
- read_http = read_http,
- read_redis = read_redis
- }
- return _M
则修改lua/item.lua的逻辑为先查询Redis,未查到在查JVM服务器

- --导入common函数库
- local common = require('commnon')
- local read_http = common.read_http
- local read_redis = common.read_redis
-
- --导入cjson函数库,
- local cjson = require('cjson')
-
- local function read_data(key, path, params)
- --查询redis
- local resp = read_redis('192.168.99.100', 6379, key)
- --判断查询结果
- if not resp then
- ngx.log('redis查询失败,尝试查询http,key:', key)
- resp = read_http(path, params)
- end
- return resp
- end
-
- --获取路径参数
- local id = ngx.var[1]
-
- --查询商品信息 nil代表无参
- local itemJSON = read_data('item:id:' .. id, "/item/" .. id, nil)
- --查询库存信息
- local stockJSON = read_data('item:stock:id:' .. id, "/item/stock/" .. id, nil)
-
- --转化为lua的table类型
- --JSON转table则需要cjson工具包
- local item = cjson.decode(itemJSON)
- local stock = cjson.decode(stockJSON)
-
- --组合数据 商品的库存(stock)和销量(sold),存储在stock对象中
- item.stock = stock.stock
- item.sold = stock.sold
-
- --返回结果
- ngx.say(cjson.encode(item))


修改/usr/local/openresty/nginx/conf/nginx.conf文件,内容如下:
- #user nobody;
- worker_processes 1;
- error_log logs/error.log;
-
- events {
- worker_connections 1024;
- }
-
- http {
- include mime.types;
- default_type application/octet-stream;
- sendfile on;
- keepalive_timeout 65;
- #lua 模块
- lua_package_path "/usr/local/openresty/lualib/?.lua;;";
- #c模块
- lua_package_cpath "/usr/local/openresty/lualib/?.so;;";
- #添加共享词典,本地缓存
- lua_shared_dict item_cache 150m;
-
- upstream tomcat-cluster {
- hash $request_uri;
- server 192.168.150.1:8081;
- server 192.168.150.1:8082;
- }
-
- server {
- listen 8081;
- server_name localhost;
- location /item {
- proxy_pass http://tomcat-cluster;
- }
- location ~ /api/(\d+) {
- default_type application/json;
- content_by_lua_file lua/item.lua;
- }
- location / {
- root html;
- index index.html index.htm;
- }
- error_page 500 502 503 504 /50x.html;
- location = /50x.html {
- root html;
- }
- }
- }

item.lua
- --导入common函数库
- local common = require('commnon')
- local read_http = common.read_http
- local read_redis = common.read_redis
-
- --导入cjson函数库,
- local cjson = require('cjson')
-
- --导入共享词典,本地缓存
- local item_cache = ngx.shared.item_cache
-
- local function read_data(key, path, params, expire)
- --查询本地缓存
- local resp = item_cache:get(key);
- if not resp then
- ngx.log(ngx.ERR, '本地缓存查询失败,尝试查询redis , key:', key)
- --查询redis
- resp = read_redis('192.168.99.100', 6379, key)
- --判断查询结果
- if not resp then
- ngx.log(ngx.ERR, 'redis查询失败,尝试查询http , key:', key)
- resp = read_http(path, params)
- end
- --查询成功,把数据写入本地缓存
- item_cache:set(key, resp, expire)
- end
- return resp
- end
-
- --获取路径参数
- local id = ngx.var[1]
-
- --查询商品信息 nil代表无参
- local itemJSON = read_data('item:id:' .. id, "/item/" .. id, nil, 1800)
- --查询库存信息
- local stockJSON = read_data('item:stock:id:' .. id, "/item/stock/" .. id, nil, 60)
-
- --转化为lua的table类型
- --JSON转table则需要cjson工具包
- local item = cjson.decode(itemJSON)
- local stock = cjson.decode(stockJSON)
-
- --组合数据 商品的库存(stock)和销量(sold),存储在stock对象中
- item.stock = stock.stock
- item.sold = stock.sold
-
- --返回结果
- ngx.say(cjson.encode(item))






canal
优点:,对代码没有任何耦合性,直接监控mysql的binary log在触发时间,异步MQ依然会存在少许的藕合。
缺点:需要Mysql数据库开启主从
1.开启Mysql主从
Canal是基于MySQL的主从同步功能,因此必须先开启MySQL的主从功能才可以。
这里以之前用Docker运行的mysql为例:
1.1开启binlog
打开mysql容器挂载的日志文件,我的在/tmp/mysql/conf目录:

修改文件:
vi /tmp/mysql/conf/my.cnf
添加内容:
- log-bin=/var/lib/mysql/mysql-bin
- binlog-do-db=heima
配置解读:
log-bin=/var/lib/mysql/mysql-bin:设置binary log文件的存放地址和文件名,叫做mysql-bin
binlog-do-db=heima:指定对哪个database记录binary log events,这里记录heima这个库
最终效果:
- skip-name-resolve
- character_set_server=utf8
- datadir=/var/lib/mysql
- server-id=1000
- log-bin=/var/lib/mysql/mysql-bin
- binlog-do-db=heima
1.2设置用户权限
接下来添加一个仅用于数据同步的账户,出于安全考虑,这里仅提供对test这个库的操作权限.
- create user canal@'%' IDENTIFIED by 'canal';
- GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal';
- FLUSH PRIVILEGES;
重启mysql容器即可
docker restart mysql
测试设置是否成功:在mysql控制台,或者Navicat中,输入命令:
show master status;

2.安装Canal
2.1创建网络
我们需要创建一个网络,将MySQL、Canal、MQ放到同一个Docker网络中:
docker network create heima
让mysql加入这个网络:
docker network connect heima mysql
2.2安装Canal
提前准备好的canal的镜像压缩包 canal.tar
docker load -i canal.tar
2.3运行Canal容器
然后运行命令创建Canal容器:
- docker run -p 11111:11111 --name canal \
- -e canal.destinations=heima \
- -e canal.instance.master.address=mysql:3306 \
- -e canal.instance.dbUsername=canal \
- -e canal.instance.dbPassword=canal \
- -e canal.instance.connectionCharset=UTF-8 \
- -e canal.instance.tsdb.enable=true \
- -e canal.instance.gtidon=false \
- -e canal.instance.filter.regex=heima\\..* \
- --network heima \
- -d canal/canal-server:v1.1.5
说明:
-p 11111:11111:这是canal的默认监听端口
-e canal.instance.master.address=mysql:3306:数据库地址和端口,如果不知道mysql容器地址,可以通过docker inspect 容器id来查看
-e canal.instance.dbUsername=canal:数据库用户名
-e canal.instance.dbPassword=canal :数据库密码
-e canal.instance.filter.regex=:要监听的表名称
表名称监听支持的语法:
- mysql 数据解析关注的表,Perl正则表达式.
- 多个正则之间以逗号(,)分隔,转义符需要双斜杠(\\)
- 常见例子:
- 1. 所有表:.* or .*\\..*
- 2. canal schema下所有表: canal\\..*
- 3. canal下的以canal打头的表:canal\\.canal.*
- 4. canal schema下的一张表:canal.test1
- 5. 多个规则组合使用然后以逗号隔开:canal\\..*,mysql.test1,mysql.test2
2.4查看canal运行状态
1.Canal容器启动日志
docker logs -f canal

2.查看canal是否和mysql建立连接
进入canal容器
docker exec -it canal bash
查看canal日志
tail -f canal-server/logs/canal/canal.log

查看canal和数据库相关的日志
tail -f canal-server/logs/heima/heima.log



要和启动canal时配置的desitinations一致


@Id,@Transient,@Column 使用spring的注解就可以 
- <dependency>
- <groupId>top.javatoolgroupId>
- <artifactId>canal-spring-boot-starterartifactId>
- <version>1.2.1-RELEASEversion>
- dependency>
-
-
- canal:
- destination: heima
- server: 192.168.150.101:11111
- import com.baomidou.mybatisplus.annotation.IdType;
- import com.baomidou.mybatisplus.annotation.TableField;
- import com.baomidou.mybatisplus.annotation.TableId;
- import com.baomidou.mybatisplus.annotation.TableName;
- import lombok.Data;
- import org.springframework.data.annotation.Id;
- import org.springframework.data.annotation.Transient;
-
- import javax.persistence.Column;
- import java.util.Date;
- @Data
- @TableName("tb_item")
- public class Item {
- @TableId(type = IdType.AUTO)
- @Id
- private Long id;//商品id
- @Column(name = "name")
- private String name;//商品名称
- private String title;//商品标题
- private Long price;//价格(分)
- private String image;//商品图片
- private String category;//分类名称
- private String brand;//品牌名称
- private String spec;//规格
- private Integer status;//商品状态 1-正常,2-下架
- private Date createTime;//创建时间
- private Date updateTime;//更新时间
- @TableField(exist = false)
- @Transient
- private Integer stock;
- @TableField(exist = false)
- @Transient
- private Integer sold;
- }
-
- @CanalTable("tb_item")
- @Component
- public class ItemHandler implements EntryHandler
- {
-
- @Autowired
- private RedisHandler redisHandler;
- @Autowired
- private Cache
itemCache; -
- @Override
- public void insert(Item item) {
- // 写数据到JVM进程缓存
- itemCache.put(item.getId(), item);
- // 写数据到redis
- redisHandler.saveItem(item);
- }
-
- @Override
- public void update(Item before, Item after) {
- // 写数据到JVM进程缓存
- itemCache.put(after.getId(), after);
- // 写数据到redis
- redisHandler.saveItem(after);
- }
-
- @Override
- public void delete(Item item) {
- // 删除数据到JVM进程缓存
- itemCache.invalidate(item.getId());
- // 删除数据到redis
- redisHandler.deleteItemById(item.getId());
- }
- }
