亚信偷鸡

亚信偷鸡

Redis实现分布式锁(通过Jedis)

保证四个条件

互斥性

任意时刻,只有一个客户端能持有锁

容错性

大部分Redis节点正常运行时,客户端就可以加锁和解锁

不会发生死锁

即使有一个客户端在持有锁期间没有主动解锁,也能保证后续其他客户端能加锁

解铃还须系铃人

谁加的谁释放

代码实现

组件依赖

引入jedis依赖

`<dependency>`
`<groupId>redis.clients</groupId>`
`<artifactId>jedis</artifactId>`
`<version>2.9.0</version>`
`</dependency>`

加锁代码

`public class RedisTool{`
`private static final string LocK_sUccEss`
`private static final String SET IF NOTXIST = "NX";`
`private static final String SET WITH EXPIRE TIME = "PX";`
`/**`
`*尝试获取分布式锁`
`*@param jedis Redis客户端`
`@param lockKey 锁`
`@param requestId 请求标识`
`*@param expireTime 超期时间`
`*@return 是否获取成功`
`米`
`public static boolean tryGetDistributedLock(Jedis jedis, string lockKey, string requestId, int expireTime){`

`String result = jedis.set(lockKey, requeStId, SET IF NOT EXIST, SET WITH EXPIRE TIME,expireTime);if(LOCK SUCCEss.equals(result)){`
`return true;}`
`return false;}`

通过锁的唯一标识,和SET IF NOT EXIST,以及SET WITH EXPIRE TIME来保证在无锁时加锁并设置过期时间,这里因为只考虑了单机部署,所以使用requestID做了唯一标识,如果多机部署,可以考虑实例ID

常见错误加锁示例

使用jedis.setnx()和jedis.expire()加锁

public static void wrongGetLock1(Jedis jedis, string lockKey, string requestId, int expireTime)

{ Long result=jedis.setnx(lockKey,requestId);

if(result ==1){ //若在这里程序突然崩溃,则无法设置过期时间,将发生死锁

jedis.expire(lockKey,expireTime);}

用sentx()实现了原子加锁,expire()加上过期时间,但这是两条redis命令,所以就没有保证原子性

使用sentx加锁,没有则加,有则比较过期时间
`public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime)`

`{`
`long expires=System.currentTimeMillis()+expireTime;`

`String expiresStr=String.value0f(expires);`
`//如果当前锁不存在,返回加锁成功`

`if(jedis.setnx(lockKey,expiresStr)==1){`
`return true;`
`//如果锁存在,获取锁的过期时间`
`string currentValuestr = jedis.get(lockkey)`
`if (currentValueStr != null &&Long.parseong(currentValuestr)<system.currentTimeMillis()){//锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间String oldValueStr=jedis.getSet(lockKey,expiresStr);`

`if(oldValueStr != null && oldValueStr.equals(currentValueStr)){`
`//考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才有权利加锁return true;`
`//其他情况,一律返回加锁失败`
`return false;}`

1.客户端自己生成过期时间,那每个实例的时间都应一致

2.锁过期,使用getSet方法会产生过期时间覆盖的问题

3.锁不具备拥有者标识,任何人都可以释放锁

解锁代码

public class RedisTool {
    private static final Long RELEASE_SUCCESS = 1L;

/**

 * 释放分布式锁

 * @param jedis Redis客户端

 * @param lockKey 锁的key

 * @param requestId 请求唯一标识(需与加锁时的值一致)

 * @return 是否释放成功
   */
   public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
   // Lua脚本(原子性验证锁值并删除)
   String script = "if redis.call('get', KEYS[1]) == ARGV[1] then "

      + "    return redis.call('del', KEYS[1]) "

           + "    return 0 "

   // 执行脚本(包含1个键、1个参数)
   Object result = jedis.eval(
       script,
       Collections.singletonList(lockKey),
       Collections.singletonList(requestId)
   );

   return RELEASE_SUCCESS.equals(result);
   }


解锁错误示例

直接使用jedis.del解锁
public static void wrongReleaseLock1(Jedis jedis, String lockKey)
{
jedis.del(lockKey);}

错误在没有判度锁是谁的,谁都可以释放锁

使用两条命令去解锁
public static void wrongReleaseLock2(Jedis jedis, string lockKey, string requestId){
//判断加锁与解锁是不是同一个客户端
if(requestId.equals(jedis.get(lockKey))){
//若在此时,这把锁突然不是这个客户端的,则会误解锁
jedis.del(lockKey);
}

redis的原子性,get和del是两条指令,这样会导致误解锁

总结

以上只是考虑了单机情况下,主要核心在原子操作lua脚本和先判断再解锁,多机部署可以使用Redission。

Docker实践

两项支持容器的技术储备

cgroup

控制资源使用量,由goole贡献

namespaces

隔离环境

Docker容器

容器能力

提供隔离运行环境

不同容器间的应用不能通信,容器内应用不能与宿主机应用通信

受到的资源限制

cpu计算资源

内存资源

磁盘I/O资源

镜像

什么是镜像

每个下拉镜像有两个属性

-repository

-tag 镜像tag

镜像实现

多层封装

image

容器与镜像的交互

image

编写Dockerfile


一、基础指令解析

  1. FROM
    指定基础镜像,必须为第一条指令。建议优先使用官方镜像以减少安全风险。

    FROM node:18-alpine  # 使用Node.js官方镜像的轻量版本
    
  2. RUN
    在镜像构建过程中执行命令,常用于安装依赖或配置环境。合并多条命令以减少镜像层数:

    RUN apt-get update && apt-get install -y \
        git \
        python3 \
        && rm -rf /var/lib/apt/lists/*  # 清理缓存减小镜像体积
    
  3. COPY与ADD
    • COPY 用于复制本地文件到镜像中(推荐优先使用)

    • ADD 支持自动解压压缩包和远程URL(慎用远程资源)

    COPY package.json yarn.lock /app/  # 仅复制依赖文件
    ADD https://example.com/data.tar.gz /tmp/  # 自动解压tar文件
    
  4. WORKDIR
    设置容器内的工作目录,后续命令均在此目录执行。避免使用RUN cd

    WORKDIR /app  # 后续操作默认在/app目录下执行
    
  5. CMD与ENTRYPOINT
    • CMD 定义容器启动时的默认命令(可被docker run覆盖)

    • ENTRYPOINT 设置主进程命令(常与CMD配合传参)

    ENTRYPOINT ["java", "-jar"]
    CMD ["app.jar"]  # 实际命令为 java -jar app.jar
    

二、高级配置指令

  1. ENV与ARG
    • ENV 设置容器内的环境变量(运行时生效)

    • ARG 定义构建时的临时变量(构建结束后失效)

    ARG APP_VERSION=1.0
    ENV VERSION=$APP_VERSION  # 将构建参数转为环境变量
    
  2. EXPOSE
    声明容器监听的端口(需通过-p参数映射到宿主机):

    EXPOSE 3000  # 提示用户此端口需映射
    
  3. USER
    指定运行容器的用户(增强安全性,避免root权限):

    USER node  # 使用非特权用户
    

三、优化技巧与最佳实践

  1. 减少镜像体积
    • 使用多阶段构建(分离编译环境与运行环境):

      # 第一阶段:编译
      FROM node:18 AS build
      WORKDIR /app
      COPY . .
      RUN npm install && npm run build
    
      # 第二阶段:运行
      FROM nginx:alpine
      COPY --from=build /app/dist /usr/share/nginx/html
    
  2. 加速构建过程
    • 合理利用缓存:将频繁变动的指令(如COPY . .)放在文件末尾

    • 使用.dockerignore排除无关文件(如node_modules

  1. 安全性增强
    • 定期更新基础镜像版本(修复CVE漏洞)

    • 避免在镜像中存储敏感信息(如私钥)


四、完整示例模板

# 多阶段构建示例
# 阶段1:构建
FROM golang:1.20 AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app .

# 阶段2:运行
FROM alpine:3.18
WORKDIR /app
COPY --from=builder /app /app
COPY config.yaml ./ 
EXPOSE 8080
USER 1001
CMD ["./app"]

镜像仓库

Docker Registry

实现Docker镜像的全局存储

提供API接口

提供Docker镜像的下载/推送/查询

DockerHub

Docker官方仓库

本地开发实践

image

状态问题

状态指程序中需要维护的上下文信息,它通常由程序存储并被后续程序使用

  • 在之前的开发模式中,开发者通常喜欢将程序运行的状态保存在程序自身的数据结构中,这些状态包括HTTP的session、程序暂存的数据、程序的日志等。如果一个程序是这样实现的,我们将其称之为有状态的程序(stateful)但如果一个程序在设计中就将“状态”保存在程序之外,比如通过外部的缓存数据库或者消息总线来保存,那么这样的程序被称为无状态的程序(statless)
  • Docker偏爱无状态。有状态影响可用和可扩展性。有状态通过volume实现。

无状态应用是Docker的最佳实践

  • 配置不通过文件,利用环境变量实现
  • 日志不输出文件,输出到stdout和stderr

容器进阶

容器网络概览

  • 与外界建立通信
  • 单机网络模式

桥接模式(使用最广,默认模式)

主机模式(使用主机网络)

容器模式(共享其他容器网络栈)

  • 集群网络模式(overlay 通过libnetwork插件)
  • 隔离的网络栈

网桥模式

image

主机模式

image

利用其他容器

image

None模式

image

Docker集群网络

overlay和swarm

容器存储

docker文件系统

容器编排

link命令和compose文件

容器日志

  • docker logs 命令
  • 使用docker exec进入容器查看
  • 配置 log-driver 输出到外部系统
  • 使用docker-compose logs查看日志
  • 集中化日志管理工具 ELK(ES+Logstash+Kibana,Promethus+Grafana)

DevOps

一篇文章

微服务

单体模式的不足

  • 应用工程变得又大又复杂
  • 敏捷开发和部署举步维艰
  • 启动时间长
  • 可靠性差
  • 难以采用新技术新语言
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇